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

**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)
