Disciplines · Decisions (ADRs)

ADR-S2: Generic Parameters and Extension Points for @oshun/workbench-kit

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.

Adopted · 2026-07-24
8sections4 minread

On this page

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-kit is created only once a 2nd consumer clears the §S0.11 deletion threshold. Every claim here is backed by a compiling reference under evidence/v1-workbenches/workbench-kit-extension-model/ (type-checks under strict tsc --noEmit, gated by verify:inventory:v1-workbenches) and by the machine- checked workbench-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:

  1. authorization (§S0.3) — every mutation carries an authorized actor decision.
  2. audit (§S0.8) — every mutation emits an audit event; no silent mutation.
  3. idempotency (§S0.3.c) — every mutation carries an idempotency/concurrency key.
  4. honest-capability (§S0.3.e) — an unconfigured capability returns a typed not-configured result, never a fabricated success.
  5. no-escape-hatch (§S0.7.g) — the MutationEnvelope is a closed shape with no any/raw; a plugin cannot bypass the invariants.
  6. 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 expose publish, proving negotiation is real (index.ts reads canPublish: false for 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): stages ideation→drafting→review→published, entity AuthoringDraft, artifact AuthoringBundle. 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): stages queued→running→succeeded→failed, entity ProcessingJob (host target, resource units, queue depth), artifact RenderOutput. 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: unknown escape 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.7
  • evidence/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)