@@ -0,0 +1,95 @@
|
||||
# Extensions
|
||||
|
||||
The plugin story, and the gate keeping it from arriving early.
|
||||
|
||||
**STATUS: not buildable yet.** A feature is its own directory under `internal/ext/<name>/`, called
|
||||
explicitly from `wire.go` (ADR-0027) — correct and sufficient until the counters say otherwise. This document exists so the eventual shape is known, not
|
||||
so it can be built now.
|
||||
|
||||
## The gate
|
||||
|
||||
| Stage of growth | What a feature looks like | Trigger to advance |
|
||||
|---|---|---|
|
||||
| Now (0–2 features) | Its own directory under `internal/ext/<name>/`, called explicitly from `wire.go` | — |
|
||||
| Transform counter due | Extract the Stage pipeline: an ordered `[]Stage` in one wire file | `state.md` |
|
||||
| Extension counter due | Extract the `Extension` struct below; move each into `internal/ext/<name>` | `state.md` |
|
||||
| After Arc 2 | Composition only; the core no longer grows | Arc 2 closes |
|
||||
|
||||
`state.md` holds the thresholds and is the only place they are written. Do not extract early. Do not
|
||||
"prepare".
|
||||
|
||||
## Target shape
|
||||
|
||||
Compile-time registry. No `plugin.so`, no `init()` side effects, no discovery, no config file listing
|
||||
plugins. One slice, one file, source order — the order *is* the semantics.
|
||||
|
||||
Compile-time is not a preference: Go's `plugin` package forbids a static binary and demands an exact
|
||||
toolchain match, which the container target (ADR-0010) rules out. Dynamic loading buys only
|
||||
extension-without-recompiling, worth nothing to the single author (ADR-0006) holding commit access.
|
||||
The registry costs a few hundred lines that render zero pages — hence real callers before a contract.
|
||||
|
||||
```go
|
||||
// internal/ext/ext.go — the whole contract, once earned.
|
||||
type Extension struct {
|
||||
Name string
|
||||
Stages []Stage // ordered; Phase decides placement
|
||||
Views map[string]View // named, referenced by frontmatter `view:`
|
||||
Shortcodes map[string]Shortcode // trusted content only (ADR-0003)
|
||||
Effects []Effect // derived artifacts and outbound calls, off the request path
|
||||
Adapters []Adapter // Interaction sources, Arc 3
|
||||
Routes []Route // additional URL cases, via the resolver
|
||||
}
|
||||
```
|
||||
|
||||
`cmd/atelier/wire.go` holds the only list of enabled extensions. Enabling or disabling one is a
|
||||
one-line diff and a rebuild. Removing one leaves no trace elsewhere — that property is the test of
|
||||
whether the contract is right.
|
||||
|
||||
## Stage phases
|
||||
|
||||
An ordered list, not a dependency graph. Two stages needing a graph to be correct are one stage
|
||||
wearing a disguise.
|
||||
|
||||
| Phase | Operates on | Examples |
|
||||
|---|---|---|
|
||||
| `PhaseLoad` | raw bytes + frontmatter | includes, translation fallback |
|
||||
| `PhaseParse` | the parsed Markdown tree | shortcodes, transclusion, image derivatives |
|
||||
| `PhaseMarkup` | rendered HTML fragments, code spans skipped | smart quotes, dashes, widows, Bengali numerals |
|
||||
| `PhasePage` | the assembled page object | OpenGraph, JSON-LD, related posts, series nav |
|
||||
| `PhaseOutput` | the final byte stream | minification, dithering, gemtext conversion |
|
||||
|
||||
Every Stage runs on every bundle unless the cascade disables it (ADR-0017), and declares its trust
|
||||
requirement. A Stage evaluating templates or shortcodes runs in trusted mode only, and the pipeline
|
||||
refuses it otherwise — enforced in code, not by convention, and
|
||||
tested with an untrusted-input case.
|
||||
|
||||
## Rules for any extension
|
||||
|
||||
1. Deletable without trauma. Removing the package leaves the engine building and serving.
|
||||
2. Reads only what already exists on the page; adds through the `Extra` bag, never by widening the
|
||||
core struct for its own convenience.
|
||||
3. Owns its output files under a namespaced path, or none.
|
||||
4. No new dependency without an ADR — extensions get no looser budget than the core, and their Go
|
||||
lines count against `EXT_LOC_MAX`. Presentation features (OpenGraph, galleries, series nav,
|
||||
related posts) belong in templates and frontmatter where they cost nothing; reach for Go only when
|
||||
there is real logic.
|
||||
5. Failure degrades: a broken extension logs and is skipped, never takes a request down.
|
||||
6. Needing a permanent external service or a primitive change makes it a **trunk** — see
|
||||
`exploration.md`.
|
||||
7. One directory, no sibling imports, and a `doc.go` in this shape (ADR-0027):
|
||||
|
||||
```go
|
||||
// Package feeds emits RSS and Atom for the primary feed and each section.
|
||||
//
|
||||
// Contributes: Effect (on change).
|
||||
// Cascade keys: feeds.enabled, feeds.limit.
|
||||
// Contract fields: none.
|
||||
// Not doing: JSON Feed, WebSub — separate features if wanted.
|
||||
package feeds
|
||||
```
|
||||
|
||||
## ContentAPI
|
||||
|
||||
A thin internal write path, introduced **with comments** in Arc 3 and not before. It exists so a
|
||||
second client (admin panel, Micropub endpoint) becomes possible without the engine growing a UI.
|
||||
Read paths keep going straight to the filesystem; git remains the source of truth.
|
||||
Reference in New Issue
Block a user