Standards governance
The rulebook for how standards change in BenchWeave. Enforced in review by the standards-governor agent (
.claude/agents/standards-governor.md); the drift gates inbenchweave.standardsare its mechanical half. Ratified 2026-09-16, founded by the version reset to the 0.1.0 baseline (prior ad-hoc version history lives in git before that date).
Identity and homes
A standard is <id> + semver (MAJOR.MINOR.PATCH), living at standards/<id>/<version>/ — machine corpus and prose companions together (see docs/doc-taxonomy.md for the full placement taxonomy). Two locks declare and pin it:
standards/standards-manifest.json— governance: which versions are current, their status, and the normative path setstandards/corpus-manifest.json— byte truth: one sha256 per machine file, rows relative tostandards/
Change classes and their bumps
| Change | Bump | Notes |
|---|---|---|
| Prose clarification (companion docs) | none | Prose is not digest-pinned; keep it truthful |
| Additive machine errata (backwards-compatible) | PATCH of that standard | The interface approver_token pattern: new optional field, nothing removed or retyped |
| Breaking machine change | MINOR at least | Old version retained frozen; supersedes recorded |
| New standard | admission | Review + status: stable; new tree dir at its first version |
| Deprecation | none (status) | Status change only; the version stays, digest-frozen |
| Deletion of a version | never | Retired corpora live in git history, not the tree |
Bump minimization (#69). One bump per (standard, release train). A release train is one merge window of a run: PRs opened concurrently against the same origin/main. Queued changes to the SAME standard that share a train batch into a single bump (one copy step from the current active version, one repin); a batch’s bump follows the highest change class it contains (errata batched with a breaking change bumps MINOR). Never stack a bump on an unmerged bump — if the predecessor merges first, re-copy from the new predecessor. Different standards may share a merge only when they are one increment. CI tests the merge result, so every in-tree artifact declaring the old version moves in-arc (in the same change/PR) with the bump — the in-tree motion is part of the bump, not a follow-up.
Bump window (#97). The train rule is prescriptive, not descriptive: a standard may not bump more than once per 48-hour window, measured between the committer timestamps of the commits that added each new version directory. A queued change to a standard still inside its window WAITS — it batches into the next bump of that standard (the highest-class rule above already governs what the batch becomes). The window is per standard: bumping otdp does not open or close a window for registry. Exempt: a standard’s first version (admission), and a reset-class commit — identified by a shape heuristic (one commit adding version directories for three or more standards, the 2026-09-16 signature), not by the Resets section’s full definition; the known residual is that a coordinated multi-standard increment of that same shape also escapes the window, accepted because resets are executive-rare. Enforced mechanically by benchweave.standards.train_window in the standards suite; the clock self-anchors at that module’s own arrival commit, so history before the rule is grandfathered by mechanism. The 48-hour floor is a starting figure ratified with this rule; it is revisited after three windows, not silently.
Bump mechanics — copy, never move. A version bump copies the old version dir to the new version and edits bytes only in the copy; the old dir and its corpus-manifest rows stay in place, digest-frozen. Moving or in-place-editing a retained version is a governance violation, not a shortcut. The new version’s rows record the old corpus path as their source. Digest pins move only through uv run python -m benchweave.standards repin — the loop is edit → repin → export, never a hand-spliced digest. A bump carrying corpus bytes for any standard with a machine-written validation report (otdp, registry, execution, interface) regenerates that version’s report via its writer (check_<suite>.py --write-report, which refuses on a failing run). The four standards-tree family scripts derive their corpus directory from standards-manifest.json‘s active entry for their standard — devices names it OUT, the siblings name it CONTRACT_DIR — and the pin test’s report paths and mutation fixtures derive the same way (#102 D2, generalized to the family by D1: each script reads the manifest and refuses loudly without it), so a bump makes no hand-moves on those. Report titles are version-free constants needing no derivation (# Registry contract verification, # Procedure and bench contract verification, …) — devices’ alone embeds its derived version (# OTDP {version} specification verification). Closure’s report is docs-rooted (CONTRACT_DIR = DOCS / "acceptance" — no version directory to derive); its CROSS-standard contract reads are manifest-derived like the rest. The docs/README.md rows linking the reports remain conventional moves; a FORGOTTEN row is caught mechanically by the pin’s exactly-once link assert (a stale row left beside the new one is not — copy-never-move keeps the old target resolving). With the version moved, the pin fails on the copied stale report until the regen runs.
Retention
Superseded versions stay digest-frozen forever. A version is retired by deleting its directory AND its corpus-manifest rows only as part of a reset class event (see below); ordinary supersession keeps both trees side by side. The obsolete-version guard in tests/contract/test_baseline.py refuses any corpus the manifest does not admit.
Resets
A reset re-baselines every standard to a chosen version label in one versioned act (founding precedent: the 2026-09-16 0.1.0 reset). Resets are rare and executive decisions; they must reset every surface together — governance versions, tree dirs, and version strings inside the normative bytes — following the cascade order in the governor’s runbook. History is preserved by git and by the source provenance fields in the corpus manifest: reset-imported rows name the upstream authoring path, and supersession-copied rows name the corpus path they were copied from (per Bump mechanics above) — never a path that did not produce the bytes.
Gates (mechanical, all must be green)
- Drift gates: normative bytes changing without a version bump is refused (
normative_hash_mismatch,content_drift_without_version) - Coverage: corpus-manifest rows == machine files on disk (nothing vendored escapes the manifest, nothing listed is missing; both directions are enforced fail-closed by
benchweave.standards repin, the only mechanical pin writer) - Architecture validator suites for the touched standard(s)
- SDK round-trip:
make check-sdk-standards(manifest, bundle, lock and vendored tree agree) - Compatibility matrix matches a fresh render (
matrix --check)
Direction
Standards changes land main-side first, flow outward via the exported bundle, and are consumed by revision-pinned locks (SDK vendored tree; plugin contract locks). Never SDK-first, never consumer-side edits to corpus bytes.
Deliberately versioned elsewhere (not governed here)
Sub-capability ids (otdp:dc_psu:* profiles, otdp.*/*/ action ids), registry fixture package versions, plugin release versions, the architecture document edition (STG 1.5), and the MCP date. These carry their own versions; only the six standards above are governed by this file.
The adapter API version is declared in the corpus-manifest identity block (identity.adapter_api); its authority is the active OTDP descriptor schema’s $defs.adapter.properties.api_version const, and validate_identity fails closed on absence or disagreement at every export/check. Identity-block edits move no corpus rows and need no repin. Promotion trigger: when the adapter surface stabilizes, this becomes a seventh corpus standard carrying a canonical importable Python contract; until then the declaration plus the three-way agreement test (tests/sdk/test_adapter_agreement.py) is the honest boundary — it pins names, arity, keyword-only-ness, coroutine-ness and key/enum sets, not semantics.