Status: Adopted (agent-recorded design decision) — pending named architecture ratification Date: 2026-07-24 Authors: V1 Domain Workbenches extraction audit (Phase S) Reviewers: pending — Oshun Studio Architecture + affected package owners
Records the generic parameters and extension points for the shared workbench-kit (
V1_DOMAIN_WORKBENCHES_TODOS_2026-07-23.md§S0.7). Follows ADR-S1 (staged, threshold-gated hybrid): the real@oshun/workbench-kitis created only once a 2nd consumer clears the §S0.11 deletion threshold. Every claim here is backed by a compiling reference underevidence/v1-workbenches/workbench-kit-extension-model/(type-checks under stricttsc --noEmit, gated byverify:inventory:v1-workbenches) and by the machine- checkedworkbench-kit-extension-model.json.
Context#
The kit must absorb genuinely domain-neutral workbench behavior (the 13 neutral interaction contracts from §S0.2) while letting each domain vary its own vocabulary — without the kit ever naming a domain concept. This ADR fixes where variability lives and which invariants the kit owns.
S0.7.a/b — Variability table → extension mechanism#
Eleven variability dimensions, each assigned to exactly one of five mechanisms.
Dimensions that the §S0.2 matrix already extracted as a neutral contract are
linked (grounding). Full table in workbench-kit-extension-model.json.
| Dimension | Mechanism | S0.2 contract |
|---|---|---|
| identity | generic type parameter (TEntity) |
— |
| stage | discriminated interface (StageDescriptor<TStage>) |
PipelineFunnel |
| block | composition slot | CatalogBrowser |
| score | domain adapter (ScoreProvider) |
EvidenceGroundingPanel |
| gate | plugin capability (GateDescriptor + runGates) |
GateRunnerPanel |
| artifact | generic type parameter (TArtifact) |
BundlePublisher |
| preview | composition slot | — |
| inspector | composition slot | SourceInspector |
| import | domain adapter (ImportAdapter) |
SourceLibrary |
| publish | domain adapter (PublishAdapter) |
BundlePublisher |
| route | domain adapter (RouteNamespace) |
— |
Rule: identity/artifact vary by type (generics); stage varies by a
discriminated union; block/preview/inspector are UI slots the host fills;
score/import/publish/route are domain adapters; gate is a declared
capability the kit runs. No dimension uses any.
S0.7.c — Invariant core (a plugin may not override)#
Six kit-owned invariants, each inherited from an earlier section:
- authorization (§S0.3) — every mutation carries an authorized actor decision.
- audit (§S0.8) — every mutation emits an audit event; no silent mutation.
- idempotency (§S0.3.c) — every mutation carries an idempotency/concurrency key.
- honest-capability (§S0.3.e) — an unconfigured capability returns a typed not-configured result, never a fabricated success.
- no-escape-hatch (§S0.7.g) — the
MutationEnvelopeis a closed shape with noany/raw; a plugin cannot bypass the invariants. - deletion-threshold (§S0.11) — a shared contract needs ≥2 real consumers.
The reference encodes these in enforceInvariants(env) and a closed
MutationEnvelope (kit-contract.ts).
S0.7.d — Capability negotiation#
capabilities(): ReadonlySet<CapabilityId>+supports(id)for discovery.- Every capability returns
CapabilityResult<T> = {configured:true,value} | {configured:false,reason}— the type-level form of the fail-loud seam (TaraWorkbenchNotConfiguredError). The operations example honestly declares it does not exposepublish, proving negotiation is real (index.tsreadscanPublish: falsefor it).
S0.7.e/f — Two compiling example plugins#
Both instantiate the same WorkbenchPlugin<TEntity, TStage, TArtifact> with
disjoint domain types, proving the model is archetype-neutral:
- Content-authoring (Tara-like,
plugin-content-authoring.ts): stagesideation→drafting→review→published, entityAuthoringDraft, artifactAuthoringBundle. No meditation vocabulary (meditation/spark/plane/cadence/ invitational) appears anywhere in the code — the leak check enforces this. - Operations (Aja/Bellona-like,
plugin-operations.ts): stagesqueued→running→succeeded→failed, entityProcessingJob(host target, resource units, queue depth), artifactRenderOutput. Models jobs/queues/resources/host targets in the plugin, not the kit.
Both type-check under strict mode (noUncheckedIndexedAccess,
exactOptionalPropertyTypes, noUnusedLocals/Parameters).
S0.7.g — Abstraction challenge (rejections)#
- Reject a single-domain generic (e.g.
TMeditationStage): a generic with one concrete use is not generic — it is domain-owned (matches the 2 S0.2 REJECT components whose neutral residue was too thin). - Reject an
extraData: any/raw: unknownescape hatch: it would let a plugin bypass the authz/audit/idempotency invariants. - Reject a kit-level
score()that computes a number: scoring is domain logic; a kit computing it would fabricate a domain result.
Consequences#
- The kit's public surface is
kit-contract.ts— generics + adapters + slots + the invariant envelope, and zero domain nouns. - Extraction of each neutral contract (§S0.2) still gates on a real 2nd consumer (§S0.11 / ADR-S1). This ADR defines the shape they extract into.
- Named architecture + owner ratification remains open; the model, the invariants, and the compiling examples are gated in CI so the design cannot silently drift.
References#
V1_DOMAIN_WORKBENCHES_TODOS_2026-07-23.md§S0.7evidence/v1-workbenches/workbench-kit-extension-model/(compiling reference)evidence/v1-workbenches/workbench-kit-extension-model.json(machine-checked model)docs/adr/ADR-S1-workbench-kit-package-ownership.md;evidence/v1-workbenches/tara-component-extraction-matrix.md(§S0.2)