# description
Manifest shape for an `Extractor` extension. An extractor consumes a parsed node (frontmatter + body) and emits output through three context-supplied callbacks rather than returning a value: `ctx.emitLink(link)` writes to the kernel's `links` table (validated against the global closed enum of link kinds before persistence; per-extractor whitelisting was retired with structure-as-truth, the global enum is the contract), `ctx.enrichNode(partial)` merges author-canonical properties into the kernel's enrichment layer (separate from the author-supplied frontmatter), `ctx.emitContribution(id, payload)` emits per-node view contributions validated against the slot payload schema, and `ctx.store` persists into the plugin's own KV namespace. The runtime method is `extract(ctx) → void`. Extractors run in isolation: they MUST NOT read other nodes, the graph, or the DB. Cross-node reasoning lives in Analyzers. Extractors are deterministic-only: pure code, runs synchronously inside `sm scan`, same input → same output every run. LLM-driven enrichment of a node is an Action concern (queued as a job), not an Extractor concern.

# properties.scope.description
Which part of the node this extractor consumes. The kernel passes only the declared scope to the extractor, a `frontmatter` extractor that tries to read `body` receives an empty string.

# properties.precondition.description
Optional declarative filter. The extractor runs only on nodes that satisfy every declared sub-filter. Same shape used by Analyzer and Action so the kernel ships a single matcher.

# properties.precondition.properties.kind.description
Qualified node kinds the extractor accepts, written as `<provider-plugin>/<kindName>` (e.g. `claude/agent`). Qualified by design so two providers declaring the same kind name never collide. Unknown qualified kinds (no provider declares them) load OK but emit a `precondition-kind-unknown` warning in `sm plugins doctor`.

# properties.precondition.properties.provider.description
Provider ids whose nodes the extractor accepts. Coarser than `kind`. Useful when an extractor applies to every kind a given provider declares.

# properties.ui.description
Plugin-contributed view contributions. Each entry declares one rendering surface in the UI by picking a `slot` name from the closed catalog at `view-slots.schema.json#/$defs/SlotName`. The kernel validates the manifest at load (`invalid-manifest` on unknown slot); the extractor emits per-node payloads via `ctx.emitContribution(<contributionId>, payload)` during scan; the runtime validates payloads against the slot's payload schema in `view-slots.schema.json#/$defs/payloads/<slot>`; off-shape payloads emit `extension.error` and drop silently (mirror of `emitLink` off-contract drop). The kernel exposes the runtime catalog via `kernel.getRegisteredViewContributions()`; the BFF surfaces it at `GET /api/contributions/registered`. Only `extractor` and `analyzer` kinds may declare this field. Renamed from `viewContributions` with the structure-as-truth refactor.