From 7a072d892f855b632697eae662fa3b7b2089d5b1 Mon Sep 17 00:00:00 2001 From: LantisPrime Date: Sat, 13 Jun 2026 16:13:49 +0800 Subject: [PATCH] =?UTF-8?q?docs(rfc-008):=20P2c=20doc=20sweep=20=E2=80=94?= =?UTF-8?q?=20align=20spec=20to=20shipped=20P2a/P2b=20validators?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Apply the deferred P2-plan-review findings (F-2, F-5, F-6, F-9, F-10, N-3, N-4) as documentation-only edits, correcting RFC-008 spec text to match the validators merged in P2a (#381) and P2b (#384). - F-5: reword the validate-schemas.mjs "meta-meta/official-meta-schema" over-claim to keyword-grammar doc-validity linter (5 locations incl. two line-wrapped in P2-bp-contracts.md). - N-3: drop the vacuous bp-XXX arms from assertions 7 + 11 (contracts carry hashes, not label/event ids; real binding is the version-hash equality). - F-2: document event-id uniqueness in assertion 12 (events mirror of assertion 6) — matches shipped validate-bp-contract.mjs. - N-4: assertion 8 reworded to the shipped set-difference invariant (merge-base, fail-closed, bootstrap carve-out); bp set = 11 (bp-007 absent). - F-6: drop the never-shipped validate-taxonomy-schema.mjs from P2-bp-contracts. - F-9: tests/lib/{mini-jsonschema,version-hash} refs updated to scripts/lib (promoted P1b/P2a) — rewrite in living docs, annotate in historical rows. - F-10: P2 phase-table counts 12/2 -> 39/5 (git-stat grounded). Plan second-opinion reviewed via harness (codex): R1 HOLD (one same-class F-5 miss) -> R2 ACCEPT converged. Co-Authored-By: Claude Fable 5 --- ...008-decouple-enforcement-from-substrate.md | 20 +++++++------- docs/rfcs/RFC-008/P0-schema-contracts.md | 19 ++++++++----- docs/rfcs/RFC-008/P2-bp-contracts.md | 27 ++++++++++--------- scripts/validate-bp-contract.mjs | 4 +-- 4 files changed, 39 insertions(+), 31 deletions(-) diff --git a/docs/rfcs/RFC-008-decouple-enforcement-from-substrate.md b/docs/rfcs/RFC-008-decouple-enforcement-from-substrate.md index 891fcb1..97c433c 100644 --- a/docs/rfcs/RFC-008-decouple-enforcement-from-substrate.md +++ b/docs/rfcs/RFC-008-decouple-enforcement-from-substrate.md @@ -446,8 +446,8 @@ Each plugin manifest carries: #### `validate-bp-contract.mjs` — additional assertions for events vocabulary 10. **Events meta-schema (F37):** `events.json` validates against `events.schema.json`. -11. **Events vocabulary closure (F37):** every event id referenced in any `bp-XXX.json`, every plugin manifest's `capabilities` keys, every plugin manifest's `event_translations` keys is ⊆ `events[].id`. -12. **Action enum closure (F37):** ∀ event, ∀ tier ∈ {STRONG, MEDIUM, WEAK}: `events[event].actions[tier].id` ∈ `{block, warn, inject, modify, observe, refuse_stop, inject_context, inject_static, write_artifact, unsupported}` (closed enum). +11. **Events vocabulary closure (F37):** every plugin manifest's `capabilities` keys and every plugin manifest's `event_translations` keys are ⊆ `events[].id`. (A `bp-XXX.json` carries no event ids — only tier values + an `events_version` hash — so it has no vocabulary arm here; its binding to the events vocabulary is the hash-equality check, assertion 15. N-3.) +12. **Action enum closure + event-id uniqueness (F37, F-2):** ∀ event, ∀ tier ∈ {STRONG, MEDIUM, WEAK}: `events[event].actions[tier].id` ∈ `{block, warn, inject, modify, observe, refuse_stop, inject_context, inject_static, write_artifact, unsupported}` (closed enum); AND `events[].id` are unique (the events mirror of label assertion 6 / F1d — without it a duplicate event id silently dedups into the validator's id Set). 13. **Payload schema resolution (F37):** every event's `payload_schema` path resolves on disk + validates as JSON Schema 2020-12. 14. **Events stable-ID integrity (F37):** events may be ADDED but never REMOVED or RENAMED without major version bump (mirror of F7 for events). 15. **`events_version` binding (F37):** every `bp-XXX.json` + every `manifest.json` carries `events_version`; validator asserts equals current computed hash. @@ -482,8 +482,8 @@ The thin-waist runtime layer does NOT coerce an unrecognized classifier label to 4. **Action-enum closure (F1b):** ∀ label, ∀ gate: `label.gates[gate] ∈ {allow, block}`. 5. **Overridability equality (F1c, F9):** stored `non_overridable` (sorted) ≡ derived non-overridable set. 6. **No duplicate ids (F1d):** label ids are unique. -7. **Vocabulary closure — no dangling references (F6):** the label set referenced by every `bp-XXX.json`, every `manifest.classifier.emits_labels`, the classifier's emitted labels, and the classifier `_priority` case-arms is a SUBSET of `taxonomy.labels`. (`emits_labels ⊆ taxonomy.labels` is shared with `validate-plugin-registry.mjs`; BOTH validators carry it.) -8. **Stable-ID integrity (F7):** labels may be ADDED but never REMOVED or RENAMED without a major `version` bump. The validator diffs the previous `taxonomy.json` from git; if `|old_ids| == |new_ids|` and `old_ids ≠ new_ids`, fail "possible rename — add the new label + mark the old `deprecated: true`, never rename." `bp-XXX.json.taxonomy_version` mismatch (F8) is a hard fail. +7. **Vocabulary closure — no dangling references (F6):** the label set in every `manifest.classifier.emits_labels`, the classifier's emitted labels, and the classifier `_priority` case-arms is a SUBSET of `taxonomy.labels`. (`emits_labels ⊆ taxonomy.labels` is shared with `validate-plugin-registry.mjs`; BOTH validators carry it.) A `bp-XXX.json` references no labels by id — only tier values + a `taxonomy_version` hash — so it has no label-vocabulary arm here; its binding to the taxonomy is the hash-equality check (assertion 8 / F8). N-3. +8. **Stable-ID integrity (F7):** labels may be ADDED but never REMOVED or RENAMED without a major `version` bump. The validator diffs the current id-set against the baseline `taxonomy.json` at the **merge-base of `HEAD` and `origin/main`** using a **set-difference invariant**: `removed = old_ids \ new_ids`; if `removed ≠ ∅` and the major `version` is unchanged, fail "id(s) removed/renamed without a major version bump — add the new label + mark the old `deprecated: true`, or bump major." Set-difference (not a cardinality heuristic) catches a rename hidden behind a simultaneous add. The check is **fail-closed (exit 2)** when the baseline is unavailable — a shallow clone or an unresolvable merge-base errors rather than skips (CI requires `fetch-depth: 0`); a path absent from the baseline tree is the bootstrap carve-out (passes). `bp-XXX.json.taxonomy_version` mismatch (F8) is a hard fail. 9. **Golden tests (F5):** `validate-bp-contract.mjs` ships known-good + known-bad fixtures (missing gate, bad action value, overridability mismatch, duplicate id, extra gate key, dangling reference, rename) and asserts the expected pass/fail per fixture. The 10 round-1 negative scenarios map 1:1 to assertions 2–8 + the runtime hard-reject and form the golden-test corpus. ### Plugin-manifest-validation-contract specification (v10 — closes P1 gap, findings F11–F20) @@ -495,7 +495,7 @@ The taxonomy got its full normative spec in v9 (F1–F10). The plugin manifest l - **`plugins/_index.schema.json`** — registry meta-schema. JSON Schema 2020-12. `additionalProperties: false` at every level. Closed enums for `harness` and `status`. - **`plugins/manifest.schema.json`** — per-plugin manifest meta-schema. JSON Schema 2020-12. `additionalProperties: false` at every level. Closed enums for `harness`, event keys, capability tier values, classifier mode. -Both are themselves validated by `scripts/validate-schemas.mjs` against the JSON Schema 2020-12 meta-meta-schema (mirror of F5). +Both are themselves checked by `scripts/validate-schemas.mjs` — the P0 keyword-grammar linter promoted to a shipped CI validator (same engine as the test linter), a faithful zero-dep approximation of 2020-12 doc-validity, **not** instance-validation against the published meta-schema (the wrong patch class; `scripts/lib/json-instance-validate.mjs` fail-closes on `$dynamicRef`/`$dynamicAnchor`/`$vocabulary`). Mirror of F5. #### Harness-name stable-ID rules (M3) @@ -1182,7 +1182,7 @@ Summary (full file lists + architecture in the linked per-phase files): |---|-------|----:|----:|-----|-----------|--------| | **P0** | Locked schema + data contracts (no code) | 20 | 0 | ~20K | — | R3, R4 (+ F11–F51) | | **P1** | Plugin directory + registry + test gauntlet | 5 | 7 | ~84–104K (4 PRs) | P0, R0b′ | R1, R6, R8 | -| **P2** | BP contract instances + contract validators | 12 | 2 | ~35K | P0 | R2, R3, R4 | +| **P2** | BP contract instances + contract validators | 39 | 5 | ~35K | P0 | R2, R3, R4 | | **P3** | Thin waist + classifier runtime-sourcing + em-recall purification | 3 | 6 | ~55K | P0, P2 | R1, R2, R3, R4, R5, R9 | | **P4** | Per-project `enforce-config.json` | 1 | 3 | ~25K | P3 | R3, R5 | | **P5** | OpenCode plugin (TS) | 5 | 0 | ~45K | P3 | R6, R10 | @@ -1205,7 +1205,7 @@ Serves R1, R6, R8 · depends on: P0, R0b′ · **DONE (P1a #373, P1b #374, P1c # #### P2 — BP contract instances + contract validators → [RFC-008/P2-bp-contracts.md](RFC-008/P2-bp-contracts.md) -Serves R2, R3, R4 · depends on: P0 · queued (parallelizable with P1). `bp-001.json … bp-012.json` + `validate-bp-contract.mjs` + `validate-taxonomy-schema.mjs`; lands the shared-negative-corpus drift guard (issue #368). +Serves R2, R3, R4 · depends on: P0 · queued (parallelizable with P1). `bp-001..006, bp-008..012.json` (11 contracts; **bp-007 absent**) + `scaffold-bp.mjs` + `validate-bp-contract.mjs` + `validate-schemas.mjs`; lands the shared-negative-corpus drift guard (issue #368). #### P3 — Thin waist + classifier runtime-sourcing + em-recall purification → [RFC-008/P3-thin-waist.md](RFC-008/P3-thin-waist.md) @@ -1339,7 +1339,7 @@ graph TD | PR/Commit | Files changed | Tests | Notes | |---|---|---|---| -| **P0** — PR #367 (`bf58e8f`) | 20 P0 files (17 schemas + 3 data) + test-only `tests/lib/mini-jsonschema.mjs` + `tests/lib/version-hash.mjs` + `tests/test-p0-schemas.mjs` + `tests/fixtures/plugins/*` (≥16 golden + `_corpus-index.json`) + `tests/fixtures/harness-events/claude-code/*` | `node tests/test-p0-schemas.mjs` → 89/0; `em-rfc-validate` consistent | Locked schema + data layer (R3, R4; F11–F51). NO shipped `.mjs` validators (T17). Validity gate = self-asserting keyword-grammar linter (closes R0b-R3 fail-open class). Plan reviewed 3 rounds; impl review ACCEPT-with-FU (4 FUs fixed inline). Post-merge FU: P2 shared negative corpus → issue #368. | +| **P0** — PR #367 (`bf58e8f`) | 20 P0 files (17 schemas + 3 data) + test-only `tests/lib/mini-jsonschema.mjs` + `tests/lib/version-hash.mjs` (both later promoted to `scripts/lib/` in P1b/P2a, with re-export shims left at the `tests/lib/` paths) + `tests/test-p0-schemas.mjs` + `tests/fixtures/plugins/*` (≥16 golden + `_corpus-index.json`) + `tests/fixtures/harness-events/claude-code/*` | `node tests/test-p0-schemas.mjs` → 89/0; `em-rfc-validate` consistent | Locked schema + data layer (R3, R4; F11–F51). NO shipped `.mjs` validators (T17). Validity gate = self-asserting keyword-grammar linter (closes R0b-R3 fail-open class). Plan reviewed 3 rounds; impl review ACCEPT-with-FU (4 FUs fixed inline). Post-merge FU: P2 shared negative corpus → issue #368. | | _P1+ pending_ | _pending_ | _pending_ | _pending_ | --- @@ -1389,7 +1389,7 @@ After acceptance, the champion flagged the validation/contract layer as thin. A | F2 | P1 | `stop` in `schema.json` per-label but only 3 per-label gates | `stop` is root-level pattern metadata; per-label = 3 classification gates | | F3 | P1 | runtime coerce→`unknown` is wrong | runtime HARD-REJECTS + structured alert; coercion prohibited | | F4 | P1 | OQ-2 open blocks Phase 4 | OQ-2 closed — classifier runtime-sources the taxonomy | -| F5 | P1 | no meta-validation of validators | `taxonomy.schema.json` + `validate-taxonomy-schema.mjs` + golden tests | +| F5 | P1 | no meta-validation of validators | `taxonomy.schema.json` + `validate-taxonomy-schema.mjs` + golden tests *(P2: folded into `validate-bp-contract.mjs` assertions 1 + 9 plus the `validate-schemas.mjs` doc-validity linter; the standalone `validate-taxonomy-schema.mjs` was dropped — F-6)* | | F6 | P2 | vocabulary-closure gap (dangling labels) | closure assertion across bp-XXX / manifest / classifier | | F7 | P2 | stable-ID rename unprotected | add-never-rename git-diff assertion | | F8 | P2 | `taxonomy_version` hash undefined | `sha256` over sorted labels array; carried by contracts + manifests | @@ -1506,7 +1506,7 @@ Consolidated second-opinion review of the v11 + v11.1 + v11.2 spec edits (13 fin | T26 | v11.4 round-2 review fold (F61–F63): codex round-2 confirmed F52–F60 materially fixed + evidence files auditable, then HOLD with 3 new findings — F61 (linked-worktree authority root: `store_root = resolveRepoRoot(project_root)` added to Contract 4, worktree→main / submodule→own-toplevel, `episode_file` under `store_root`, + linked-worktree acceptance test), F62 (Contract 4 conditional alert schema as `if/then/else`/`oneOf` on `alert_type`, `emitted_label` retyped `string\|null`, +2 golden negatives), F63 (redaction invariant extended to adapter-owned log sinks: route through helper OR declare log paths for step-9 scan, +1 golden negative). All ACCEPT. | v11.4 | codex round-2 (reply `…231947…93fc`). F61 surfaced via a real worktree→main-checkout repro; grounded in `scripts/lib/local-dir.mjs` convergence. F62/F63 were pre-flagged in the round-2 second-order review table. Going to round 3 (of 4). | | T27 | v11.5 round-3 review fold (F64–F65): codex round-3 confirmed F61+F62 closed, HOLD on 2 internal-consistency drifts from the v11.4 edits — F64 (`log_paths` was prose-only; now a declared top-level §9 schema field with M7e presence-for-api + posix-path typing + gauntlet step-9 scan + `bad-runbook-api-missing-log-paths.json`), F65 (Contract 4 JSON example refreshed to the linked-worktree case with `store_root` + absolute `episode_file`). Both ACCEPT. | v11.5 | codex round-3 (reply `…232447…2a68`). Both findings were the "added a field to the table/prose but not the schema/example" drift class, traceable to the immediately-prior v11.4 fold; F64 was pre-flagged in the round-3 second-order review. Round 4 of 4 next. | | T28 | v11.6 round-4 (cap) review fold (F66–F67): codex round-4 confirmed F64+F65 closed, HOLD at the 4-round cap with 2 trivial drifts — F66 (`log_paths` authority root: entries absolute or relative-to-`project_root`, M7e-normalized; adapter writes ≡ step-9 scan root by construction), F67 (stale `` alert-location line → `store_root/.episodic-memory/`). Both ACCEPT + folded. Codex instructed "do not spawn round 5; consult the champion" — escalated; no round 5 auto-dispatched. | v11.6 | codex round-4 (reply `…233020…42dc`). Round-cap discipline engaged (`20260528-082255-codex-round-cap-engagement-signal`): monotonic convergence (8→3→2→2), all post-round-1 findings were second-order-prefigured or prior-fix drift, never architectural — champion decides accept-as-folded vs one confirmation round. | -| T30 | v11.8 R0b pre-implementation plan review folds (codex R1→R2 + claude-subagent R3 closeout): (a) **F37 event-payload lock completed** — the four previously hand-waved canonical payload shapes (`tool_result`, `stop`, `session_start`, `session_end`) now carry explicit `required`/optional field tables + cross-event invariants in §"Five canonical event-payload schemas", so plugin authors cannot invent incompatible payloads (the exact F37 gap P0 exists to close); (b) **P0 validity-verification gate specified** — "valid 2020-12 doc" is proven by a test-only keyword-grammar linter (`tests/lib/mini-jsonschema.mjs`) with allowlist + fail-on-unknown-keyword + a declared `SUBSCHEMA_KEYWORDS` table that **self-asserts completeness against the allowlist** (closes the fail-open class where invalid subschemas nested under un-recursed keywords like `propertyNames`/`unevaluatedProperties`/`dependentSchemas` would pass); full official-meta-schema validation deferred to P2's `validate-schemas.mjs`, sharing one negative corpus to prevent drift; (c) **T17 `.mjs` carve-out clarified** — the exclusion scopes to shipped runtime/CI validators under `scripts/`; test-only `.mjs` under `tests/` is permitted/required; (d) **fixture detection-layer attribution** — `tests/fixtures/plugins/_corpus-index.json` annotates each golden fixture with `_detected_by ∈ {p0-schema, p1-registry-validator, p2-cross-file-validator, p3-runtime}` + records the canonical hash-serialization contract. | v11.8 | Rule-18 step-2 plan review before P0 implementation. Codex R1 HOLD (3 findings, all ACCEPT: validity test, event-schema lock, fixture attribution) → R2 HOLD (1: `$dynamicRef`-guarded fail-open) → claude-subagent R3 closeout (codex ETIMEDOUT ×3 from SessionStart-bloated stdin; documented fallback) caught the same class one nesting level deeper (incomplete recurse-set) — closed via the self-asserting `SUBSCHEMA_KEYWORDS` table. Trajectory 3→1→1; no architectural REJECT; no R1–R10 challenged. Reply episodes `…060801…9a99` / `…063458…b5ef` / `…064415…537a`. No requirement/deliverable count changed (still 20 P0 files). | +| T30 | v11.8 R0b pre-implementation plan review folds (codex R1→R2 + claude-subagent R3 closeout): (a) **F37 event-payload lock completed** — the four previously hand-waved canonical payload shapes (`tool_result`, `stop`, `session_start`, `session_end`) now carry explicit `required`/optional field tables + cross-event invariants in §"Five canonical event-payload schemas", so plugin authors cannot invent incompatible payloads (the exact F37 gap P0 exists to close); (b) **P0 validity-verification gate specified** — "valid 2020-12 doc" is proven by a test-only keyword-grammar linter (`tests/lib/mini-jsonschema.mjs`) with allowlist + fail-on-unknown-keyword + a declared `SUBSCHEMA_KEYWORDS` table that **self-asserts completeness against the allowlist** (closes the fail-open class where invalid subschemas nested under un-recursed keywords like `propertyNames`/`unevaluatedProperties`/`dependentSchemas` would pass); the linter later promoted to P2's shipped CI validator `scripts/validate-schemas.mjs` (a zero-dep doc-validity linter, not published-meta-schema instance-validation — see F-5), sharing one negative corpus to prevent drift; (c) **T17 `.mjs` carve-out clarified** — the exclusion scopes to shipped runtime/CI validators under `scripts/`; test-only `.mjs` under `tests/` is permitted/required; (d) **fixture detection-layer attribution** — `tests/fixtures/plugins/_corpus-index.json` annotates each golden fixture with `_detected_by ∈ {p0-schema, p1-registry-validator, p2-cross-file-validator, p3-runtime}` + records the canonical hash-serialization contract. | v11.8 | Rule-18 step-2 plan review before P0 implementation. Codex R1 HOLD (3 findings, all ACCEPT: validity test, event-schema lock, fixture attribution) → R2 HOLD (1: `$dynamicRef`-guarded fail-open) → claude-subagent R3 closeout (codex ETIMEDOUT ×3 from SessionStart-bloated stdin; documented fallback) caught the same class one nesting level deeper (incomplete recurse-set) — closed via the self-asserting `SUBSCHEMA_KEYWORDS` table. Trajectory 3→1→1; no architectural REJECT; no R1–R10 challenged. Reply episodes `…060801…9a99` / `…063458…b5ef` / `…064415…537a`. No requirement/deliverable count changed (still 20 P0 files). | | T29 | v11.7 phase-boundary restructure (no spec-content change): consolidated the three overlapping phase representations (the "9 phases" estimate table, the prose "Phase 1–9" blocks, and the "Build priority" P-table) into ONE per-phase manifest under §"Build phases (P0–P9): manifest and boundaries". Each phase is now a delimited block with an exact file manifest + explicit Depends-on / Done-when boundary; added a Phase-to-P crosswalk retiring the legacy-"Phase N" vs P-N numbering mismatch (legacy Phase 4 classifier folds into P3; Phase 5 = P4; Phase 6/7/8 = P5/6/7); surfaced P0 (schemas, no validators per T17) and P8 (Cursor+Windsurf per F43) as explicit slots that the old table lacked; pinned the P0 file count to an enumerated 20 (17 schemas + 3 data), superseding the "18 schema-class docs" shorthand. TOC anchors updated. | v11.7 | User: "in the rfc its hard for me to distinguish which belongs to which phases like p0, i cant immediately see the boundary where p0 starts and where it stops." Pure presentation/clarity refactor; no requirement or deliverable semantics changed. | | T31 | v11.9 capability-model + typed-registry folds (user requirements #1–#2 + capability-charter boundary): (a) **`CAPABILITIES.md` established** — new top-level guiding-post charter naming the three substrate capability families (memory-store strategy, recall strategy, learning strategy) + the forward rule for adding new ones as plugin types; anchored from `PRINCIPLES.md` P1 and `CLAUDE.md` project-structure; (b) **R8 typed-registry clause** — every registry entry declares `type ∈ {enforcement, recall-strategy, store-strategy, learning}` (missing/unknown → hard-reject); each type carries its own registry sub-schema + descriptor + runtime-IO schema + conformance gauntlet; types span two layers (enforcement vs substrate); (c) **R8 versioned-contract clause** — REQUIRED `schema_version` (semver) mapped to schema + validator; validator supports a range with backward / forward (fail-closed-forward) / additive-minor semantics; `CURRENT_SCHEMA_VERSION` byte-equal drift-guard across schema/validator/fixtures; (d) **R2 clarified** — the `enforcement` type is the sole enforcement-layer type, bound to `bp-XXX`; the three substrate-capability types use episodes and never enforce; (e) **R11 (pluggable memory-store strategies)** + **R12 (pluggable learning)** added — substrate-side, enforcement-free (R1); derived-index / learning algorithms cross-ref RFC-007 / RFC-001. Requirements R1–R10 → R1–R12; traceability matrix + heading/TOC updated. | v11.9 | User directives 2026-05-30: (#1) "schema version must be present … mapped to schema and registry validator … ensures backward and forward compatibility"; (#2) "registry and plugin management must have plugin type … behavior pattern's enforcement plugin, recall strategy plugins, memory store strategy plugins, and learning plugins … each capability must be supported … complete with schema validator and tests"; plus the boundary correction "capabilities must focus on using the memory substrates, not enforcing any workflows — that's the job of behavior patterns." Schema/validator/test *implementation* is future (P0 `type` + `schema_version` fields; P1 validator dispatch) — the requirements now gate it. The versioning requirement was initially mis-placed as a standalone R11 and folded into R8 per champion ("part of the major 10 requirements"); R11/R12 are reserved for the two genuinely-new substrate capabilities. | diff --git a/docs/rfcs/RFC-008/P0-schema-contracts.md b/docs/rfcs/RFC-008/P0-schema-contracts.md index 53384c9..7993634 100644 --- a/docs/rfcs/RFC-008/P0-schema-contracts.md +++ b/docs/rfcs/RFC-008/P0-schema-contracts.md @@ -37,8 +37,8 @@ graph TB end subgraph GATE["Test-only validity gate (tests/)"] - LINT["tests/lib/mini-jsonschema.mjs
(2020-12 keyword-grammar linter;
self-asserting SUBSCHEMA_KEYWORDS;
ALLOWLIST=57)"] - VH["tests/lib/version-hash.mjs"] + LINT["scripts/lib/mini-jsonschema.mjs
(2020-12 keyword-grammar linter;
self-asserting SUBSCHEMA_KEYWORDS;
ALLOWLIST=57; promoted from tests/lib in P2a)"] + VH["scripts/lib/version-hash.mjs
(promoted from tests/lib in P1b)"] T["tests/test-p0-schemas.mjs = 89/0"] FIX["tests/fixtures/plugins/* (>=16 golden)
tests/fixtures/harness-events/claude-code/*"] end @@ -65,8 +65,11 @@ graph TB `event-session-start` · `event-session-end` *(`.schema.json`)* - `schemas/runbook-agent-manifest.schema.json` *(F49)* -Test-only (T17-permitted): `tests/lib/mini-jsonschema.mjs`, `tests/lib/version-hash.mjs`, -`tests/test-p0-schemas.mjs`, `tests/fixtures/plugins/*`, `tests/fixtures/harness-events/claude-code/*`. +Test-only (T17-permitted): `tests/test-p0-schemas.mjs`, `tests/fixtures/plugins/*`, +`tests/fixtures/harness-events/claude-code/*`. (`mini-jsonschema.mjs` and `version-hash.mjs` +shipped test-only here at P0; both were later promoted to `scripts/lib/` — version-hash in +P1b, mini-jsonschema in P2a when `scripts/validate-schemas.mjs` began importing it — leaving +re-export shims at the `tests/lib/` paths.) ## Implementation notes (as shipped) @@ -81,8 +84,10 @@ Test-only (T17-permitted): `tests/lib/mini-jsonschema.mjs`, `tests/lib/version-h - **Canonical hashes baked:** `taxonomy_version = sha256:7ea41ed8…`, `events_version = sha256:13f01e5a…` (computed over the sorted `labels` array only — editorial fields change without invalidating classification behavior). -- Full official-meta-schema validation is owned by **P2**'s `scripts/validate-schemas.mjs`, - which re-validates these P0 schemas when it lands. +- Doc-validity checking of these schemas is owned by **P2**'s `scripts/validate-schemas.mjs` — + the same keyword-grammar linter promoted to a shipped CI validator (a zero-dep approximation + of 2020-12 doc-validity, **not** instance-validation against the published meta-schema, which + is the wrong patch class). It re-validates these P0 schemas. ## Done when ✓ @@ -135,7 +140,7 @@ Plan reviewed cross-tool (codex, 3 rounds → ACCEPT): request `…044435…2fe8 ## Follow-up **Issue [#368](https://github.com/lantisprime/episodic-memory/issues/368)** — P2 must share -ONE negative corpus between the P0 linter (`tests/lib/mini-jsonschema.mjs`) and P2's +ONE negative corpus between the P0 linter (`scripts/lib/mini-jsonschema.mjs`, promoted from `tests/lib/` in P2a with a re-export shim) and P2's `scripts/validate-schemas.mjs` (RFC line 1188; drift guard, Rule 14). Blocked by P2. ## Maps to diff --git a/docs/rfcs/RFC-008/P2-bp-contracts.md b/docs/rfcs/RFC-008/P2-bp-contracts.md index e9c9fa1..6f7cc5a 100644 --- a/docs/rfcs/RFC-008/P2-bp-contracts.md +++ b/docs/rfcs/RFC-008/P2-bp-contracts.md @@ -10,7 +10,7 @@ ## What P2 is -P2 ships the **contract DATA** (`bp-001.json` … `bp-012.json`) plus the validators that +P2 ships the **contract DATA** (11 contracts — `bp-001..006`, `bp-008..012`; bp-007 absent) plus the validators that enforce the §Validation-contract assertion checklist against it. This is where the behavior-practice patterns become machine-readable contracts the thin waist (P3) reads. @@ -26,29 +26,30 @@ graph TB end subgraph P2NEW["P2 - new artifacts"] - BP["patterns/bp-001.json ... bp-012.json
{ gates, stop, taxonomy_ref,
taxonomy_version, events_version }"] + BP["patterns/bp-001..006, bp-008..012.json
(11 contracts; bp-007 absent)
{ gates, stop, taxonomy_ref,
taxonomy_version, events_version }"] VBP["scripts/validate-bp-contract.mjs
(assertions: gate-completeness, action-enum
closure, overridability equality, vocab
closure, stable-ID, version binding, 10-15)"] - VTS["scripts/validate-taxonomy-schema.mjs
(meta-validation, F5)"] - VS["scripts/validate-schemas.mjs
(official 2020-12 meta-schema, M2;
re-validates P0 schemas)"] + VS["scripts/validate-schemas.mjs
(keyword-grammar doc-validity linter, M2;
re-validates P0 schemas)"] end META -. validates .-> BP TAX -. taxonomy_version + emits_labels subset of labels .-> VBP EVS -. events_version .-> VBP BP --> VBP - META --> VTS CORP -. shared fixture .-> VS - CORP -. shared fixture .-> VTS ``` ## Ships -- `patterns/bp-001.json` … `patterns/bp-012.json` — the contract DATA; each carries - `taxonomy_version` + `events_version` bindings. +- `patterns/bp-001..006.json`, `patterns/bp-008..012.json` — 11 contract DATA files + (**bp-007 absent**); each carries `taxonomy_version` + `events_version` bindings. +- `scripts/scaffold-bp.mjs` — derives the 11-id set from `patterns/_index.json` and emits + contracts with live-computed hashes. - `scripts/validate-bp-contract.mjs` — the full normative §Validation-contract assertion checklist: gate-completeness, action-enum closure, overridability equality, vocabulary closure, stable-ID integrity, version binding, events assertions 10–15. -- `scripts/validate-taxonomy-schema.mjs` — meta-validation (F5). +- `scripts/validate-schemas.mjs` — keyword-grammar doc-validity linter (M2); re-validates + P0 schemas. (F5 is owned by assertions 1 + 9 of `validate-bp-contract.mjs` plus this + doc-validity linter — there is no separate `validate-taxonomy-schema.mjs`.) ## Contract shape @@ -68,9 +69,11 @@ per-label, F2/F10). ## Implementation note — shared negative corpus (issue #368) P2 lands the drift guard from P0's follow-up: the P0 linter -(`tests/lib/mini-jsonschema.mjs`) and P2's `scripts/validate-schemas.mjs` (the official -2020-12 meta-schema validator, M2) **share one negative corpus** as a common conformance -fixture, so the two hand-rolled validators cannot drift (Rule 14). RFC line 1188. +(`scripts/lib/mini-jsonschema.mjs`, promoted from `tests/lib/` in P2a with a re-export shim) +and P2's `scripts/validate-schemas.mjs` (the keyword-grammar doc-validity linter, M2 — +a zero-dep approximation of 2020-12 doc-validity, not published-meta-schema instance-validation) +**share one negative corpus** as a common conformance fixture, so the two hand-rolled +validators cannot drift (Rule 14). RFC line 1188. ## Done when ✓ diff --git a/scripts/validate-bp-contract.mjs b/scripts/validate-bp-contract.mjs index d8e4139..8f7c663 100644 --- a/scripts/validate-bp-contract.mjs +++ b/scripts/validate-bp-contract.mjs @@ -505,8 +505,8 @@ export function validateBpContract({ projectRoot, taxonomyPath = null, eventsPat // --------------------------------------------------------------------------- // Assertion 12 — action-enum closure per event x tier, plus event-id // uniqueness (step-6 F-2 — the events mirror of assertion 6; without it a - // duplicate event id silently dedups into the eventIds Set. RFC L448-453 - // carries no explicit dup-id assertion — asymmetry flagged for the P2c + // duplicate event id silently dedups into the eventIds Set. RFC assertion 12 + // documents this event-id uniqueness check — asymmetry resolved in the P2c // doc sweep). // --------------------------------------------------------------------------- assertionsRun.add(12);