From 74a6c6efb5ec30163b9cfcf3cce6d3865f56dcae Mon Sep 17 00:00:00 2001 From: qwerfunch Date: Sat, 29 Aug 2026 01:45:28 +0900 Subject: [PATCH 1/2] feat(spec): establish Spec 0.2 design baseline --- AGENTS.md | 1 + README.html | 12 +- README.ja.md | 6 +- README.ko.html | 12 +- README.ko.md | 6 +- README.md | 6 +- README.zh.md | 6 +- docs/README.md | 5 +- docs/design/ironclad-obligation-rfc.md | 199 +++++ docs/design/spec-0.2.md | 157 ++++ docs/design/spec-0.2/assurance-evidence.md | 64 ++ docs/design/spec-0.2/assurance.md | 444 ++++++++++ .../spec-0.2/context-and-orchestration.md | 232 ++++++ docs/design/spec-0.2/decision-log.md | 124 +++ docs/design/spec-0.2/delivery.md | 159 ++++ docs/design/spec-0.2/evidence.md | 198 +++++ docs/design/spec-0.2/graph.md | 90 ++ docs/design/spec-0.2/hygiene.md | 50 ++ docs/design/spec-0.2/mcp.md | 98 +++ docs/design/spec-0.2/model-and-migration.md | 378 +++++++++ docs/design/spec-0.2/proof-and-editing.md | 150 ++++ docs/design/spec-0.2/validation.md | 82 ++ docs/knowledge-graph/design.md | 12 +- package.json | 1 + scripts/spec-0.2-validate.ts | 773 ++++++++++++++++++ spec.yaml | 4 +- spec/_doc-links.yaml | 35 +- spec/attestation.yaml | 38 +- .../spec-02-design-validation-0a29d024.yaml | 45 + .../spec-02-final-audit-closure-b8d77abf.yaml | 48 ++ ...lidation-findings-refinement-2f840a6c.yaml | 34 + spec/index.yaml | 3 + .../design/spec-0.2/design-validation.test.ts | 160 ++++ tests/design/spec-0.2/mcp-validation.test.ts | 124 +++ tests/design/spec-0.2/requirements.yaml | 109 +++ tests/readme-record-honesty.test.ts | 23 +- 36 files changed, 3847 insertions(+), 41 deletions(-) create mode 100644 docs/design/ironclad-obligation-rfc.md create mode 100644 docs/design/spec-0.2.md create mode 100644 docs/design/spec-0.2/assurance-evidence.md create mode 100644 docs/design/spec-0.2/assurance.md create mode 100644 docs/design/spec-0.2/context-and-orchestration.md create mode 100644 docs/design/spec-0.2/decision-log.md create mode 100644 docs/design/spec-0.2/delivery.md create mode 100644 docs/design/spec-0.2/evidence.md create mode 100644 docs/design/spec-0.2/graph.md create mode 100644 docs/design/spec-0.2/hygiene.md create mode 100644 docs/design/spec-0.2/mcp.md create mode 100644 docs/design/spec-0.2/model-and-migration.md create mode 100644 docs/design/spec-0.2/proof-and-editing.md create mode 100644 docs/design/spec-0.2/validation.md create mode 100644 scripts/spec-0.2-validate.ts create mode 100644 spec/features/spec-02-design-validation-0a29d024.yaml create mode 100644 spec/features/spec-02-final-audit-closure-b8d77abf.yaml create mode 100644 spec/features/spec-02-validation-findings-refinement-2f840a6c.yaml create mode 100644 tests/design/spec-0.2/design-validation.test.ts create mode 100644 tests/design/spec-0.2/mcp-validation.test.ts create mode 100644 tests/design/spec-0.2/requirements.yaml diff --git a/AGENTS.md b/AGENTS.md index 6a60fe78..e058affb 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -71,6 +71,7 @@ Convert every internal id at the user surface boundary via `src/ui/softShell.ts` - `GOVERNANCE.md` — sync policy, versioning, contributor policy, PR contract, v1.0 graduation criteria. - `CONTRIBUTING.md` — first-PR walkthrough. - `CODE_OF_CONDUCT.md`, `SECURITY.md` — community standards + private security reports. +- `docs/design/spec-0.2.md` — accepted 0.10.0 continuation router; read it, then load only the task-routed canonical file under `docs/design/spec-0.2/` (implementation is still pending). - `docs/code-style.md` — per-language Google Style Guides table + comment policy in full. - `docs/ux-routing-coverage.md` — applied-status of `ironclad-design/03-ux-routing.md` prescriptions. - `docs/multi-provider-roadmap.md` — host vs sdk adapter model + adapter matrix + how to add one. diff --git a/README.html b/README.html index 1d91cb42..c636f69d 100644 --- a/README.html +++ b/README.html @@ -235,7 +235,7 @@

cladding

ironclad spec - tests + tests detectors license

@@ -271,7 +271,7 @@

cladding

- cladding builds itself with cladding too — 269 of its 277 features cleared this same gate, the first L4 implementation of the Ironclad standard. + cladding builds itself with cladding too — 280 of its 284 features cleared this same gate, the first L4 implementation of the Ironclad standard.

@@ -557,7 +557,7 @@

Status

version
v0.9.4
-
2026-08
+
2026-08 · worktree
conformance
@@ -566,7 +566,7 @@

Status

tests
-
2981/2981
+
2992/2992
all pass
@@ -576,8 +576,8 @@

Status

features
-
281
-
277 done · self-spec
+
284
+
280 done · self-spec
diff --git a/README.ja.md b/README.ja.md index 704ef536..e79bea90 100644 --- a/README.ja.md +++ b/README.ja.md @@ -12,7 +12,7 @@

ironclad spec - tests + tests detectors license

@@ -31,7 +31,7 @@ - **たどれる** — **出荷されたものは記録に残る**: 何を検証したかはコミットされた内容に刻まれ、誰がいつやったかはローカルのセッション台帳に、なぜかは spec に残る — だから引き継ぎもレビューも、掘り起こさずに済む。 - **拡張しても揺るがない** — 人と AI が増えれば、普通は衝突と乖離も増える。だが全員が一つの spec を基準に働くので、それらは自動でせき止められる — だから規模を広げても崩れない。 -cladding は **自分自身も cladding で作っている** — 273 個の feature のうち 269 個が同じゲートを通過した、[Ironclad](https://github.com/qwerfunch/ironclad) 標準を L4 で実装した最初の事例だ。 +cladding は **自分自身も cladding で作っている** — 284 個の feature のうち 280 個が同じゲートを通過した、[Ironclad](https://github.com/qwerfunch/ironclad) 標準を L4 で実装した最初の事例だ。 @@ -347,7 +347,7 @@ clad update # 3. プロジェクト接続と派生状態を更新 | Version | 準拠レベル | Tests | Gate | Features | |---|---|---|---|---| -| v0.9.4(2026-08) | L4 · [自己申告](https://github.com/qwerfunch/ironclad/blob/main/GOVERNANCE.md) | 2981 / 2981 | 15 段階 · 41 detectors | 281(277 done) | +| v0.9.4 worktree(2026-08) | L4 · [自己申告](https://github.com/qwerfunch/ironclad/blob/main/GOVERNANCE.md) | 2992 / 2992 | 15 段階 · 41 detectors | 284(280 done) | 253 test files · capability 6 個 · カバレッジ低下は COVERAGE_DROP detector がブロック diff --git a/README.ko.html b/README.ko.html index fbb0522f..de441570 100644 --- a/README.ko.html +++ b/README.ko.html @@ -277,7 +277,7 @@

cladding

ironclad spec - tests + tests detectors license

@@ -304,7 +304,7 @@

cladding

- cladding은 자기 자신도 cladding으로 만든다 — 기능 281개 중 277개가 같은 게이트를 통과했고, Ironclad 표준을 L4로 구현한 첫 사례다. + cladding은 자기 자신도 cladding으로 만든다 — 기능 284개 중 280개가 같은 게이트를 통과했고, Ironclad 표준을 L4로 구현한 첫 사례다.

@@ -591,7 +591,7 @@

Status

version
v0.9.4
-
2026-08
+
2026-08 · worktree
준수 등급
@@ -600,7 +600,7 @@

Status

tests
-
2981/2981
+
2992/2992
all pass
@@ -610,8 +610,8 @@

Status

features
-
281
-
277 done · 자기 스펙
+
284
+
280 done · 자기 스펙
diff --git a/README.ko.md b/README.ko.md index 947eae9f..67cdd94a 100644 --- a/README.ko.md +++ b/README.ko.md @@ -12,7 +12,7 @@

ironclad spec - tests + tests detectors license

@@ -31,7 +31,7 @@ - **추적** — **나간 것은 기록에 남는다**: 무엇을 검증했는지는 커밋된 내용에 새겨지고, 누가·언제는 로컬 세션 로그에, 왜는 스펙에 남아, 인수인계와 리뷰가 파헤치지 않아도 된다. - **확장** — 사람과 AI를 늘리면 보통 충돌과 어긋남도 함께 불어난다. 하지만 모두가 스펙 하나를 기준으로 일하니 그게 자동으로 걸린다 — 그래서 규모를 키워도 무너지지 않는다. -cladding은 **자기 자신도 cladding으로 만든다** — 기능 273개 중 269개가 같은 게이트를 통과했고, [Ironclad](https://github.com/qwerfunch/ironclad) 표준을 L4로 구현한 첫 사례다. +cladding은 **자기 자신도 cladding으로 만든다** — 기능 284개 중 280개가 같은 게이트를 통과했고, [Ironclad](https://github.com/qwerfunch/ironclad) 표준을 L4로 구현한 첫 사례다. @@ -346,7 +346,7 @@ clad update # 3. 프로젝트 연결과 파생 데이터를 함께 | version | 준수 등급 | tests | gate | features | |---|---|---|---|---| -| v0.9.4 · 2026-08 | L4 · [L0–L4 중 최고 · 자가 선언](https://github.com/qwerfunch/ironclad/blob/main/GOVERNANCE.md) | 2981 / 2981 · all pass | 15 단계 · 41 detectors | 281 · 277 done · 자기 스펙 | +| v0.9.4 worktree · 2026-08 | L4 · [L0–L4 중 최고 · 자가 선언](https://github.com/qwerfunch/ironclad/blob/main/GOVERNANCE.md) | 2992 / 2992 · all pass | 15 단계 · 41 detectors | 284 · 280 done · 자기 스펙 | 253 test files · capability 6개 · coverage는 COVERAGE_DROP detector가 하락 차단 diff --git a/README.md b/README.md index fa60cd25..769c90fd 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@

ironclad spec - tests + tests detectors license

@@ -31,7 +31,7 @@ So you can ship AI-written code held to **the same standard as human-written cod - **Traced** — **What shipped is on the record**: what was verified is stamped into committed content, who and when land in the local session ledger, and the why lives in the spec — so handoff and review skip the archaeology. - **Scales** — adding people and AIs would normally multiply conflicts and drift; because everyone works from one shared spec, those get caught automatically — so you can grow without it breaking down. -cladding builds **itself** with cladding too — 269 of its 273 features cleared this same gate, the first L4 implementation of the [Ironclad](https://github.com/qwerfunch/ironclad) standard. +cladding builds **itself** with cladding too — 280 of its 284 features cleared this same gate, the first L4 implementation of the [Ironclad](https://github.com/qwerfunch/ironclad) standard. @@ -360,7 +360,7 @@ Reconcile the drift the update flagged. | Version | Conformance | Tests | Gate | Features | |---|---|---|---|---| -| v0.9.4 (2026-08) | L4 · [self-declared](https://github.com/qwerfunch/ironclad/blob/main/GOVERNANCE.md) | 2981 / 2981 | 15 stages · 41 detectors | 281 (277 done) | +| v0.9.4 worktree (2026-08) | L4 · [self-declared](https://github.com/qwerfunch/ironclad/blob/main/GOVERNANCE.md) | 2992 / 2992 | 15 stages · 41 detectors | 284 (280 done) | 253 test files · 6 capabilities · coverage drop blocked by the COVERAGE_DROP detector diff --git a/README.zh.md b/README.zh.md index 0870b492..04d9e3eb 100644 --- a/README.zh.md +++ b/README.zh.md @@ -12,7 +12,7 @@

ironclad spec - tests + tests detectors license

@@ -31,7 +31,7 @@ - **可追溯** —— **交付出去的一切都留有记录**:验证了什么,写进已提交的内容;谁、何时,记在本地会话账本;为什么,留在 spec —— 于是交接与评审无需考古,就能追溯每一个决定。 - **可扩展** —— 人和 AI 越多,通常冲突和漂移也越多。但所有人都以同一份 spec 为基准,这些会被自动挡下 —— 所以不断扩张也不会崩。 -cladding 连**自己**也是用 cladding 造的 —— 273 个 feature 里有 269 个通过了同一道门禁,成为 [Ironclad](https://github.com/qwerfunch/ironclad) 标准的首个 L4 实现。 +cladding 连**自己**也是用 cladding 造的 —— 284 个 feature 里有 280 个通过了同一道门禁,成为 [Ironclad](https://github.com/qwerfunch/ironclad) 标准的首个 L4 实现。 @@ -343,7 +343,7 @@ clad update # 3. 刷新项目连接和派生状态 | 版本 | 一致性 | Tests | Gate | Features | |---|---|---|---|---| -| v0.9.4(2026-08) | L4 · [自我声明](https://github.com/qwerfunch/ironclad/blob/main/GOVERNANCE.md) | 2981 / 2981 | 15 阶段 · 41 检测器 | 281(277 done) | +| v0.9.4 worktree(2026-08) | L4 · [自我声明](https://github.com/qwerfunch/ironclad/blob/main/GOVERNANCE.md) | 2992 / 2992 | 15 阶段 · 41 检测器 | 284(280 done) | 253 个测试文件 · 6 项 capability · 覆盖率下降由 COVERAGE_DROP 检测器拦下 diff --git a/docs/README.md b/docs/README.md index 60bba121..4e6f4bf8 100644 --- a/docs/README.md +++ b/docs/README.md @@ -13,13 +13,15 @@ Human-readable documentation for cladding. Mixed-tier: design SSoT (Tier B) live | `conventions.md` (in adopting projects) | **C** | derived from observed code OR greenfield seed | `clad init --scan` | `developer` persona when writing code + human reviewers | | `code-style.md` | **C** | hand-authored, legacy (cladding-self only) | manual | cladding contributors (legacy reference; will deprecate in favour of conventions.md) | | `multi-provider-roadmap.md` | **B** | design SSoT — host vs SDK adapter model | manual | maintainers + adopters routing through multi-host adapters | +| [`design/spec-0.2.md`](design/spec-0.2.md) + `design/spec-0.2/` | **B/C** | accepted target-design router plus bounded canonical owners for model/migration, proof/editing, delivery, hygiene, GraphIR, context/orchestration, assurance, MCP, split evidence, executable validation guide, and decision history *(implementation pending)* | amend one owner + central `decision-log.md` change log; reproduce projections with `npm run validate:spec-0.2` | maintainers and fresh sessions; read the router and only the owner files routed for the current task | +| [`design/ironclad-obligation-rfc.md`](design/ironclad-obligation-rfc.md) | **B** | non-authoritative, unsubmitted draft proposal derived from Spec 0.2 D21–D23 | manual; never treated as accepted Ironclad policy | maintainers preparing a later upstream discussion | | `spec-ids-multi-dev.md` | **B** | design SSoT — hash-based ID conventions (v0.3.9+) | manual | planner persona + every spec author | | `ux-routing-coverage.md` | **B** | design SSoT — Soft Shell coverage status | manual | reviewer + roadmap planners | | `glossary.md` | **C** | terminology SSoT — public-name definitions | manual | contributors + `self-consistency.test.ts` | | `feature-cycle.md` | **B** | design SSoT — one feature's spec→code→test→done lifecycle | manual | orchestrator + planner + developer personas | | `gate-scope.md` | **B** | design SSoT — module-scoped gate for monorepos | manual | reviewer + monorepo adopters | | `ssot-testing.md` | **B** | governance policy — SSoT lifecycle test strategy | manual | reviewer + test-author developer | -| `knowledge-graph/design.md` | **B** | design SSoT — knowledge-graph model & cost | manual | maintainers + graph contributors | +| `knowledge-graph/design.md` | **B** | historical SSoT — shipped v1 graph model, cost record, and post-ship departures; superseded target is `design/spec-0.2/graph.md` D17 | manual historical amendment | maintainers auditing v1 or migrating graph consumers | | `benchmarks/` | **D** audit (post-hoc reports) | append-only per benchmark run; a superseded run-log may be compressed to a dated abstract + git-history pointer once no live claim cites its numbers | manual benchmark commits | maintainers reviewing performance regressions | | `dogfood/` | **D** audit (post-hoc reports) | append-only per dogfood session | manual dogfood commits | maintainers reviewing harness behaviour on real adoption | @@ -32,5 +34,6 @@ The alternative — tier-named directories (`tier-b/`, `tier-c/`) — was reject ## See also - [`ssot-model.md`](./ssot-model.md) — the 4-tier governance policy this README indexes +- [`design/spec-0.2.md`](./design/spec-0.2.md) — accepted 0.10.0 task router; it links the one canonical owner for each decision *(not current runtime behavior)* - [`../spec/README.md`](../spec/README.md) — Tier A + B (structured spec data) directory index - [`../src/agents/README.md`](../src/agents/README.md) — persona role table + how each persona reads which tier diff --git a/docs/design/ironclad-obligation-rfc.md b/docs/design/ironclad-obligation-rfc.md new file mode 100644 index 00000000..0e7781a4 --- /dev/null +++ b/docs/design/ironclad-obligation-rfc.md @@ -0,0 +1,199 @@ + + +# Ironclad obligation model — RFC draft + +> **Status: draft proposal only.** This document is a Cladding design input derived from the accepted [Spec 0.2 assurance design](spec-0.2/assurance.md#d21--iron-law-assurance-kernel) for a possible upstream Ironclad discussion. It is not part of the Ironclad standard, has not been submitted or accepted upstream, and creates no conformance claim. The current upstream [Iron Law draft](https://github.com/qwerfunch/ironclad/blob/main/iron-law.md) remains authoritative for its 13 stages and four cumulative levels. + +## Problem statement + +Ironclad's current stage catalog is a useful interoperable execution and reporting surface. A fixed stage list alone cannot express which contract subject was proved, which input bytes were observed, whether the result is current, why a skip was permitted, or whether independent/human evidence was actually verified. Extensions also need a way to add checks without renumbering the standard or presenting a tool-specific count as Ironclad itself. + +This RFC proposes an additive semantic layer: + +```text +compiled contract + → profile-selected obligations + → stage and extension adapters + → normalized observations + → freshness and failure reduction + → profile verdict and attestation +``` + +The proposal preserves every existing standard stage ID and L1–L4 meaning. + +## Compatibility boundary + +The standard catalog remains 13 stages: + +- L1: `stage_1.1` Type, `stage_1.2` Lint, `stage_1.3` Drift, `stage_1.4` Commit, `stage_1.5` Arch, `stage_1.6` Secret; +- L2: `stage_2.1` Unit, `stage_2.2` Coverage; +- L3: `stage_3.1` Smoke, `stage_3.2` Performance, `stage_3.3` Visual; +- L4: `stage_4.1` Audit, `stage_4.2` UAT. + +An implementation that supports only the existing catalog and cumulative rule remains interpretable. The obligation model adds subject, closure, provenance, and freshness semantics; it does not delete, rename, or reorder a stage. Legacy scalar stage results can be imported as observations with the narrowest honestly known subject and an explicit `legacy` precision marker. Missing subject precision is not fabricated. + +## Extension registry + +Extensions use a namespaced identity rather than claiming a new standard stage: + +```yaml +extensions: + - id: cladding.spec-conformance + compatibility_alias: stage_2.3 + claim: contract_behavior + environment: isolated_runtime + - id: cladding.deliverable-smoke + compatibility_alias: stage_2.4 + claim: deliverable_runtime + environment: declared_runtime +``` + +An extension registration declares: + +- stable namespaced ID and version; +- human label and optional legacy/tool alias; +- claim and valid subject kinds; +- required input-closure rule; +- accepted observation channels and environment class; +- deterministic, side-effecting, network, and human requirements; +- N/A rule and default blocking policy; +- adapter/tool identity used to execute it. + +Adding an extension does not change the 13-stage count or silently raise an Ironclad level. A profile may require extensions and reports them alongside, not inside, its derived standard level. + +## Core records + +### Obligation + +```ts +interface IroncladObligationProposal { + id: string; + subject: string; + claim: string; + closure_sha256: string; + accepted_channels: readonly string[]; + environment: string; + required: boolean; + blocking: 'hard' | 'report'; + source: {kind: 'ironclad-stage' | 'extension'; id: string}; +} +``` + +The obligation is derived from the current compiled contract and selected profile. It is not authored as a duplicate requirement. `subject` is an address such as project, feature, criterion, artifact, or deliverable. `closure_sha256` covers every contract, implementation, verification, policy, and evidence input relevant to the claim. `blocking` expresses the selected profile's effective policy; a tool that hardens a standard reporting stage records that policy separately from the source stage's strictness rather than rewriting the standard. + +### Observation + +```ts +interface IroncladObservationProposal { + obligation: string; + subject: string; + closure_sha256: string; + outcome: 'passed' | 'failed' | 'unobserved' | 'not_applicable'; + reason?: 'skipped' | 'timeout' | 'pending_env' | 'unsupported' | 'stale' | 'cancelled'; + provenance: 'authored' | 'derived' | 'observed'; + channel: string; + tool: {id: string; version?: string; config_sha256: string}; + environment_sha256?: string; + evidence?: readonly string[]; + observed_at: string; +} +``` + +These proposal-scoped records are a simplified upstream projection, not aliases of Cladding's internal D21 types or a promised wire format. A declared test binding is authored provenance. A matching case-level runner result is observed provenance. They may join but never collapse into the same fact. A result is reusable only when its subject and complete closure still match. + +## Profiles and cumulative levels + +An assurance profile selects required obligations and its maximum claimed Ironclad level. L1–L4 remain cumulative: level N requires every hard standard obligation through N to pass and every reporting obligation to report an explicit `pass | fail | n/a`. A reporting failure completes the standard reporting requirement but remains visible; a tool profile may explicitly harden it. Tool extensions are evaluated separately. + +A proposed tool default may be L2, matching the current prototype-level boundary, while project or risk policy selects L1, L3, or L4. A tool must report the selected profile, derived standard level, required extension set, and unresolved obligations. It must not call an extension pass “L3” or “L4” unless the corresponding standard obligations are also complete. + +## No-vacuous-GREEN reduction + +A profile is GREEN only when all of the following hold: + +1. every required obligation has an observation bound to the same subject and current closure; +2. at least one accepted observed channel establishes each required behavioral claim; +3. an explicit failure in any declared applicable channel dominates positive evidence for that obligation, after which its effective `hard | report` policy decides profile blocking; +4. skipped-only, absent, stale, or unknown observations do not satisfy a required obligation; +5. N/A is accepted only under the obligation's declared policy rule; +6. a passing unrelated test, same-file result, sibling subject, or other project cannot transfer proof; +7. reporting metrics such as coverage cannot substitute for a missing behavioral observation unless a profile explicitly defines that distinct claim. + +An unknown that needs an unavailable environment, verifier, or human channel should produce an escalation result. A deterministic, locally repairable absence should produce an actionable failure. Neither is GREEN. + +## Attestation + +An attestation seals one completed profile, not the proposition that every possible check ran. It records: + +- profile and policy identities; +- derived Ironclad level; +- required standard and extension obligation IDs; +- normalized successful and N/A observation identities plus compact counts; +- contract, implementation, verification, and evidence closure digests; +- engine, tool, environment, extension-registry, and trust-snapshot identities. + +Only a foreground authoritative reduction of the current closure may write an attestation. A background or partial run may cache observations but cannot change lifecycle state or attest. A stronger profile cannot be inferred from a weaker profile's GREEN record. + +## Stage 4 receipt semantics + +Stage 4 requires more than a workspace-controlled `author: human` string. A qualifying receipt should contain, directly or through signed canonical framing: + +```ts +type Stage4Check = 'pass' | 'fail'; + +interface IroncladStage4ReceiptProposal { + issuer: string; + issuer_key_id: string; + issuer_proof: string; + subject: string; + subject_sha256: string; + claim: 'audit' | 'uat'; + criterion_verdicts: Readonly>; + checks: Readonly>; + reviewed_inputs_sha256: string; + runtime_dependency_sha256: string; + implementation_authors_sha256: string; + observed_at: string; +} +``` + +- This is a proposal projection, not the Cladding receipt wire type. The claim-specific outcome is derived from its criterion rows and required named checks rather than persisted as a second verdict fact. +- Audit has one exact composite-criterion row and attests evidence sufficiency, code/test review, and independence for that scope. +- UAT is signed once for a feature but enumerates every current composite criterion's intent-alignment result and attests no surprise plus trade-off acceptance over the full runtime dependency closure. +- An explicit failed row or check dominates. A pass requires the exact applicable criterion set and all required checks; missing rows remain unobserved and unknown addresses invalidate the receipt. +- Signature verification establishes that a registered issuer made the assertion; it does not prove universal human identity or semantic truth. +- Audit compares the verified issuer with the bound implementation-author set. A same-author receipt remains self-certified history and cannot satisfy an obligation that requires independent review. +- Verified blind evidence may establish independent provenance but never substitutes for human UAT. +- A stale subject, implementation, reviewed-input, trust, or signature closure cannot satisfy Stage 4. + +## Background execution and host topology + +Implementations may speculatively execute deterministic, isolated obligations after edits. Such observations are marked non-authoritative and content-addressed. A foreground reducer may reuse them only after validating the exact current closure, tool configuration, policy, and relevant environment. Speculative work never writes evidence, lifecycle state, canonical generated artifacts, or attestation. + +Agent topology is outside the proof rule. A host may use one or many general agents to plan, implement, test, review, or observe. Gate and profile verdicts depend on contract and evidence state, not persona names. Identity enters only where an obligation explicitly requires independent, blind, or human provenance. A blind claim requires an evidenced negative-capability boundary rather than a prompt label. + +## Conformance and migration + +Adoption is additive: + +1. keep current 13 stage IDs and results; +2. register tool-specific checks under namespaced extension IDs; +3. normalize stage outputs into observations without inventing missing subjects or selectors; +4. introduce profiles and profile-complete attestation; +5. tighten required behavioral and Stage 4 claims only when their address, observation, and receipt adapters exist. + +During migration, legacy observations may remain visible as `unknown`, `stale`, or imprecise. Compatibility must never turn absence of precision into a pass. Tools may retain old stage aliases in CLI and JSON output while making namespaced identity canonical in the extension registry. + +## Claim limits and open questions + +This model by itself does not prove lower latency, better correctness, agent adoption, human identity, or semantic requirement quality. Those need separate benchmarks or evidence. In particular, background availability is not background reuse, a pushed result is not agent adoption, and a smaller active-wait time may trade for greater CPU consumption. + +Questions for any upstream discussion include: + +- whether obligation/observation schemas belong in Ironclad core or an optional protocol; +- which environment identity is sufficient for reusable L3 observations; +- how standard soft/reporting stages interact with tool profiles; +- whether Stage 4 trust roots are standardized or deliberately implementation-defined; +- how extension registry versions enter cross-tool conformance fixtures. + +No answer in this draft is an upstream decision until Ironclad's own governance accepts and publishes it. diff --git a/docs/design/spec-0.2.md b/docs/design/spec-0.2.md new file mode 100644 index 00000000..f002c7b8 --- /dev/null +++ b/docs/design/spec-0.2.md @@ -0,0 +1,157 @@ + + +# Spec 0.2 — continuation router + +## Status + +| Field | Value | +|---|---| +| Target release | Cladding 0.10.0 | +| Target schema | `"0.2"` | +| Current shipped schema | `"0.1"` | +| Design status | Accepted; implementation pending | +| Last design review | 2026-08-29 | +| Authority | Navigation SSoT for the accepted target-design set; normative decisions live in the linked owner documents | + +The current runtime remains governed by [`docs/ssot-model.md`](../ssot-model.md), [`spec/README.md`](../../spec/README.md), and source code until each target-design part lands. + +## Continuation contract + +For a fresh planning or implementation session: + +1. Read [`AGENTS.md`](../../AGENTS.md), this router, and only the canonical document(s) routed for the current task. +2. Load [Evidence snapshot](spec-0.2/evidence.md#evidence-snapshot), or the narrower [Assurance evidence](spec-0.2/assurance-evidence.md), only to reproduce measurements or evaluate a claim; do not preload either for ordinary implementation. +3. Treat accepted target decisions separately from shipped runtime behavior. +4. Change a decision only in its canonical owner, then append one row to [Change log](spec-0.2/decision-log.md#change-log). Move displaced alternatives to [Rejected decisions](spec-0.2/decision-log.md#rejected-decisions). +5. Label numbers as **verified measurement**, **acceptance target**, or **projection**. Never promote a projection without a reproducible artifact. + +Old chat transcripts are not authority. This router intentionally contains no duplicate normative contract. + +## Task routing + +| Work | Read | +|---|---| +| F1 compiler/registry bootstrap | D10 in [Model and migration](spec-0.2/model-and-migration.md), D17 in [GraphIR](spec-0.2/graph.md), and D15/D16 in [Delivery](spec-0.2/delivery.md) | +| F2 parsing or F3 catalog/rules | [Model and migration](spec-0.2/model-and-migration.md), the relevant D17 subsection, and D15/D16 | +| Schema-upgrade preview or apply | D14 in [Model and migration](spec-0.2/model-and-migration.md), D12 in [Proof and editing](spec-0.2/proof-and-editing.md), and D15/D16 | +| F4 transactional editing and cycle begin | [Proof and editing](spec-0.2/proof-and-editing.md) and D15/D16 in [Delivery](spec-0.2/delivery.md) | +| F5 bindings/evidence or F6 assurance/attestation | [Proof and editing](spec-0.2/proof-and-editing.md), [Assurance](spec-0.2/assurance.md), D17 closures, and D15/D16 | +| F7 scenarios | D09 in [Model and migration](spec-0.2/model-and-migration.md#d09--scenario-contract), D15/D16, and D17 | +| F8 GraphIR cutover, graph query/export/viewer | [GraphIR and bounded retrieval](spec-0.2/graph.md) and D15/D16 | +| F9 task projection, context measurement, background scheduler, or orchestration A/B | [Context and orchestration](spec-0.2/context-and-orchestration.md), [Assurance](spec-0.2/assurance.md), and D15/D16 | +| Iron Law theory, stage/profile policy, incremental verification, or upstream RFC | [Assurance](spec-0.2/assurance.md), then D15/D16 and [Assurance evidence](spec-0.2/assurance-evidence.md) only when reproducing cadence measurements | +| F10 headless task-state loop (0.10.x) | [Context and orchestration](spec-0.2/context-and-orchestration.md) and D15/D16 | +| MCP adapter, host compatibility, or catalog/token validation | [MCP boundary](spec-0.2/mcp.md), D15/D16, and the [Validation protocol](spec-0.2/validation.md) | +| Implementation hygiene, code/doc/test retirement | D18 in [Implementation hygiene](spec-0.2/hygiene.md#d18--implementation-hygiene-and-documentation-surface) | +| Claims, alternatives, later work, history | [Decision log](spec-0.2/decision-log.md) | +| Measurement reproduction only | [Evidence snapshot](spec-0.2/evidence.md); use [Assurance evidence](spec-0.2/assurance-evidence.md) for cadence, invalidation, and A/B/C policy measurements | + +## Decision map + +Each heading below preserves the former monolithic-document anchor and routes to its one canonical owner. + +## D01 — Objective and guarantee boundary + +Owner: [D01](spec-0.2/model-and-migration.md#d01--objective-and-guarantee-boundary). + +## D02 — WHY / WHAT / HOW model and terminology + +Owner: [D02](spec-0.2/model-and-migration.md#d02--why--what--how-model-and-terminology). + +## D03 — Artifact layout and necessity + +Owner: [D03](spec-0.2/model-and-migration.md#d03--artifact-layout-and-necessity). + +## D04 — Identity and sharding + +Owner: [D04](spec-0.2/model-and-migration.md#d04--identity-and-sharding). + +## D05 — Project contract + +Owner: [D05](spec-0.2/model-and-migration.md#d05--project-contract). + +## D06 — Feature and criterion contract + +Owner: [D06](spec-0.2/model-and-migration.md#d06--feature-and-criterion-contract). + +## D07 — Capability contract and edge ownership + +Owner: [D07](spec-0.2/model-and-migration.md#d07--capability-contract-and-edge-ownership). + +## D08 — Architecture contract + +Owner: [D08](spec-0.2/model-and-migration.md#d08--architecture-contract). + +## D09 — Scenario contract + +Owner: [D09](spec-0.2/model-and-migration.md#d09--scenario-contract). + +## D10 — Artifact registry and compiler boundary + +Owner: [D10](spec-0.2/model-and-migration.md#d10--artifact-registry-and-compiler-boundary). + +## D11 — Test binding and observation + +Owner: [D11](spec-0.2/proof-and-editing.md#d11--test-binding-and-observation). + +## D12 — Transactional spec editing + +Owner: [D12](spec-0.2/proof-and-editing.md#d12--transactional-spec-editing). + +## D13 — Attestation v3 + +Owner: [D13](spec-0.2/proof-and-editing.md#d13--attestation-v3). + +## D14 — Schema migration + +Owner: [D14](spec-0.2/model-and-migration.md#d14--schema-migration). + +## D15 — Implementation sequence + +Owner: [D15](spec-0.2/delivery.md#d15--implementation-sequence). + +## D16 — Acceptance gates + +Owner: [D16](spec-0.2/delivery.md#d16--acceptance-gates). + +## D17 — Knowledge Graph v2 as compiler IR + +Owner: [D17](spec-0.2/graph.md#d17--knowledge-graph-v2-as-compiler-ir). + +## D18 — Implementation hygiene and documentation surface + +Owner: [D18](spec-0.2/hygiene.md#d18--implementation-hygiene-and-documentation-surface). + +## D19 — Cycle context envelope and token discipline + +Owner: [D19](spec-0.2/context-and-orchestration.md#d19--cycle-context-envelope-and-token-discipline). + +## D20 — Host-owned orchestration and verified independence + +Owner: [D20](spec-0.2/context-and-orchestration.md#d20--host-owned-orchestration-and-verified-independence). + +## D21 — Iron Law assurance kernel + +Owner: [D21](spec-0.2/assurance.md#d21--iron-law-assurance-kernel). + +## D22 — Profiles, cadence, and background scheduling + +Owner: [D22](spec-0.2/assurance.md#d22--profiles-cadence-and-background-scheduling). + +## D23 — Verdict, attestation, compatibility, and acceptance + +Owner: [D23](spec-0.2/assurance.md#d23--verdict-attestation-compatibility-and-acceptance). + +## D24 — MCP as optional transport and bounded projection + +Owner: [D24](spec-0.2/mcp.md#d24--mcp-as-optional-transport-and-bounded-projection). + +## Appendices + +- [Evidence snapshot](spec-0.2/evidence.md#evidence-snapshot) +- [Assurance evidence](spec-0.2/assurance-evidence.md#iron-law-cadence-and-assurance-invalidation) +- [Claim discipline](spec-0.2/decision-log.md#claim-discipline) +- [Rejected decisions](spec-0.2/decision-log.md#rejected-decisions) +- [Executable validation protocol](spec-0.2/validation.md) +- [Evolution queue](spec-0.2/decision-log.md#evolution-queue) +- [Change log](spec-0.2/decision-log.md#change-log) diff --git a/docs/design/spec-0.2/assurance-evidence.md b/docs/design/spec-0.2/assurance-evidence.md new file mode 100644 index 00000000..bae010c4 --- /dev/null +++ b/docs/design/spec-0.2/assurance-evidence.md @@ -0,0 +1,64 @@ + + +# Spec 0.2 — assurance evidence + +> Canonical measurement record for the [D21–D23 assurance design](assurance.md#d21--iron-law-assurance-kernel). Return to the [Spec 0.2 continuation router](../spec-0.2.md). + +## Iron Law cadence and assurance invalidation + +The upstream [Iron Law](https://github.com/qwerfunch/ironclad/blob/main/iron-law.md#stages) defines 13 stages (L1–L4: 6/2/3/2). Shipped `TIER_STAGES` has 15, adding `stage_2.3` Spec conformance and `stage_2.4` Deliverable smoke. Distinguish standard conformance from Cladding's gate; a count does not prove every stage ran or produced non-vacuous proof. + +The 2026-08-29 policy truth-table simulation separates standard strictness from +Cladding enforcement: + +| Current observation | Ironclad level projection | Cladding strict profile | +|---|---|---| +| hard pass + report pass | complete | GREEN | +| hard pass + report fail | complete; failure reported | RED | +| hard pass + report unobserved | incomplete | unresolved | +| hard fail + report pass | failed | RED | + +This preserves shipped blocking while representing the upstream reporting rule; +it does not claim that the Spec 0.2 reducer is implemented. + +| Local cadence observation | Result | Reproduction | +|---|---:|---| +| Non-strict pre-commit, 3 shipped stages | 9.06 s real | `/usr/bin/time -p node bin/clad check --tier=pre-commit --json` | +| Non-strict pre-push, 9 shipped stages | 29.73 s real | `/usr/bin/time -p node bin/clad check --tier=pre-push --json` | +| Repository tests | 12.68 s real; 2,981/2,981 passed | `/usr/bin/time -p npm test` | + +These are 2026-08-28 single samples on Darwin 25.5.0 arm64, Node 26.0.0 and npm 11.12.1, not portable benchmarks. Capture stdout and `time -p` stderr separately, including failed runs. + +| Verified observation | Result | Source | +|---|---:|---| +| Interactive drift partition | significant-edit hook about 5.8 s → 0.5 s | `CHANGELOG.md`; `interactive-drift-profile-6ed216f3.yaml` owns the mechanism | +| Duplicate detector execution | madge 1.27 s + secretlint 3.75 s, about 5.0 s of 11.4 s pre-commit | `gate-run-detector-cache-e53596dd.yaml` | +| Shared Unit/Coverage run | synthetic pre-push median reduction 17.4–33.5%, identical findings | `docs/ab-evaluation/case-test-run-dedup.md` | +| Verdict polling | 2.58 s verdict vs 2.56 s gate, one gate touch | `docs/ab-evaluation/loop-features-live-verification.md` | + +These cited observations establish mechanisms and local deltas, not portable latency or adoption claims. + +Closure method: `loadSpec` → `reverseIndexOf`; for each of 427 normalized module paths, union all owners and transitive reverse-`depends_on` descendants. Run `inferDependsOn` on exact source bytes; suggestions are not authored truth. Run `buildIterativeImpactSlice` for each of 281 features and count deduplicated returned `test_refs`. + +| Measurement | Value | Consequence | +|---|---:|---| +| Features owning a shared module | 257/281 (91.5%) | Multi-owner invalidation is normal. | +| Shared module paths | 202/427 | One path may stale several feature receipts. | +| Owners per module | median 1; p95 7; p99 18; max 61 (`src/cli/clad.ts`) | Direct implementation/receipt fan-out. | +| Authored dependency edges | 249 | Current authoritative ledger; cycle scan returned zero. | +| Import-inferred undeclared edges | 996 across 149 features | The authored DAG is not proven complete; do not silently treat an empty closure as safe. | +| Module change, all-on-edit | 281 features | Sound fallback when closure completeness is unknown. | +| Module owner + full dependent closure | average 22.43; p95 81; max 151 | About 92.0% fewer feature profiles than global on average, conditional on a complete graph. | +| Bounded feature regression set | median 3; p95 31; max 60 test refs | Current iterative query result, not proof that every hidden dependency is covered. | + +Negative controls: `UNVERIFIED_AC` accepts an unrelated same-file pass when its target is skipped and stays silent when optional JUnit is absent or unreadable; a criterion-only edit leaves module-only v2 attestation fresh; missing Stage 4 evidence is non-blocking; free-form `identity.author: human` needs no portable verified receipt. The conformance runner has 26 isolated pass/fail fixtures for the upstream 13, none for `stage_2.3`/`stage_2.4`; it misses ordering faults such as Drift reading JUnit before Unit or attestation preceding Audit/UAT. + +Preregister three assurance policies against the same mutation corpus: + +| Arm | Acceptance criterion | +|---|---| +| A — all-on-every-edit oracle | Recompute all 281 feature profiles and catch every seeded spec/code/test/receipt/tool/environment fault; this is the comparison oracle, not the target cadence. | +| B — event tiers | Produce the same criterion verdict, feature verdict, first blocking stage, and stale receipt set as A. Any unknown/unowned/dynamic input escalates to A; owner-only or fixed-depth dependency bounds are forbidden as GREEN proof. | +| C — closure DAG | Match an independent full recomputation exactly while invalidating only content-addressed contract, implementation, proof, receipt, tool, and environment closures. Injected missing/dynamic/ambiguous edges must mark completeness unknown and escalate, not return an empty safe set. | + +A/B/C report affected criteria/features, cache hits, reruns, wall time, and first blocker. Accept efficiency only when B and C are fault-equivalent to A; blast-radius reduction alone is not correctness evidence. diff --git a/docs/design/spec-0.2/assurance.md b/docs/design/spec-0.2/assurance.md new file mode 100644 index 00000000..b6f6c13c --- /dev/null +++ b/docs/design/spec-0.2/assurance.md @@ -0,0 +1,444 @@ + + +# Spec 0.2 — assurance kernel and scheduling + +> Canonical owner of D21–D23. This document extends the [Spec 0.2 continuation +> router](../spec-0.2.md). Artifact and compiler ownership remain in +> [D10](model-and-migration.md#d10--artifact-registry-and-compiler-boundary), +> proof and attestation inputs in [D11–D13](proof-and-editing.md), GraphIR +> closures in [D17](graph.md), and task projection and host ownership in +> [D19–D20](context-and-orchestration.md). Dated cadence and invalidation +> measurements live in [Assurance evidence](assurance-evidence.md); the +> [upstream obligation RFC](../ironclad-obligation-rfc.md) is a non-authoritative +> proposal derived from this accepted Cladding design. + +## D21 — Iron Law assurance kernel + +### Guarantee boundary + +The Iron Law is a cumulative set of proof obligations, not a script that runs +every available command after every edit. An obligation names a governed fact, +the inputs that can change it, the observation that can decide it, and the +assurance level and profiles that require it. A stage is a compatibility view +over one or more obligations. Stage numbering never determines execution order, +freshness, scope, or authority. + +The kernel is synchronous, deterministic over a sealed input snapshot, and +LLM-free. It may execute project-owned tools and record their observations, but +an agent statement, tool exit alone, or previously present report is not proof +unless a registered adapter resolves it against the current inputs. The kernel +proves that declared obligations received current qualifying observations. It +does not prove unstated intent, universal correctness, or the identity of a +person beyond a verified receipt issuer's signed assertion. + +### Assurance levels and stage compatibility + +Schema 0.2 adds `project.assurance_level: L1 | L2 | L3 | L4`. New 0.2 projects +persist `L2`; migration infers no level from old stage history, proposes `L2`, +and requires explicit operator resolution before apply. Levels are cumulative: + +| Level | Required obligation families | Legacy stage view | +|---|---|---| +| `L1` | static and structural integrity | Type `stage_1.1`, Lint `stage_1.2`, Drift `stage_1.3`, Commit `stage_1.4`, Architecture `stage_1.5`, Secret `stage_1.6` | +| `L2` | L1 plus executable test and coverage proof | Unit `stage_2.1`, Coverage `stage_2.2` | +| `L3` | L2 plus system-quality observations | Smoke `stage_3.1`, Performance `stage_3.2`, Visual `stage_3.3` | +| `L4` | L3 plus verified independent human evidence | Audit `stage_4.1`, UAT `stage_4.2` | + +Ironclad owns 13 stages; Spec Conformance and Deliverable Smoke are Cladding +extensions. All 15 IDs remain readable in machine output, history, plugins, and +compatibility tests, with labels and aliases derived from the registry. A +profile may require an applicable extension, but its pass never raises the +derived Ironclad level. + +Ironclad strictness and Cladding enforcement are separate axes. The standard +marks Coverage and Performance `report` and every other core stage `hard`: a +reporting stage must produce `pass | fail | na`, but its failure does not lower +the derived standard level. Cladding's authoritative profiles deliberately +harden both reporting stages, preserving the shipped rule that a failed check +blocks completion. This tool policy does not redefine Ironclad, and `report` is +never an alias for the shipped blocking `GateStatus.advisory` disposition. + +An assurance level requires applicable obligations; it does not invent a +command. Only a compiler proof from current contract and artifact kinds permits +`na`. A missing runner, required adapter, trust key, or observation is +`unobserved`, never `na`, `skip`, or GREEN. Oracle and deliverable policies own +the two extension applicability rules; project-kind rules own Coverage, Smoke, +Performance, and Visual applicability. + +### Kernel records + +The code-owned `ObligationDescriptor[]` registry is the sole owner of obligation +identity, assurance level, applicability, dependencies, adapters, cache and +resource policy, background eligibility, standard strictness, effective +blocking, and legacy aliases. It complements the artifact registry; neither +copies the other's facts. + +```ts +type AssuranceLevel = 'L1' | 'L2' | 'L3' | 'L4'; +type StandardStrictness = 'hard' | 'report'; +type BlockingPolicy = 'hard' | 'report'; +type ObservationState = + | 'pass' + | 'fail' + | 'unobserved' + | 'na'; +type UnobservedReason = + | 'skipped' + | 'timeout' + | 'pending_env' + | 'unsupported' + | 'stale' + | 'cancelled'; + +interface ProofObligation { + id: string; + subject: string; // project, feature, criterion, artifact, or anchor address + assurance_level: AssuranceLevel; + descriptor: string; + input_addresses: readonly string[]; + applicability: 'required' | 'na' | 'unresolved'; + source_strictness?: StandardStrictness; + blocking: BlockingPolicy; +} + +interface Observation { + obligation: string; + subject: string; + state: ObservationState; + input_sha256: string; + adapter: {id: string; version: string}; + provenance: 'authored' | 'derived' | 'observed'; + assurance: 'asserted' | 'verified'; + reason?: UnobservedReason; + locator?: string; + observed_at: string; + environment_class: string; +} + +interface AssuranceProfile { + id: 'feedback' | 'checkpoint' | 'completion' | 'push' | 'release'; + assurance_level: AssuranceLevel; + scope: 'changed' | 'feature' | 'integration' | 'repository'; + obligations: readonly string[]; + authoritative: boolean; +} + +interface AssuranceVerdict { + profile: AssuranceProfile['id']; + assurance_level: AssuranceLevel; + scope_sha256: string; + input_sha256: string; + state: 'green' | 'red' | 'unresolved'; + profile_complete: boolean; + results: readonly { + obligation: string; + subject: string; + state: ObservationState; + source_strictness?: StandardStrictness; + blocking: BlockingPolicy; + }[]; + independence: 'independent' | 'self-certified' | 'not-applicable'; +} +``` + +Public JSON may add versioned fields around these records, but no consumer may +reconstruct obligation joins, applicability, or freshness from stage labels. +`contractClosure`, `subjectClosure`, and `verificationClosure` remain the single +implementations that supply their respective addresses and digests. + +### Failure, freshness, and provenance + +- `fail` means a current adapter observed an explicit negative result. It wins + channel reduction; effective `blocking` then decides whether the profile is + RED. +- A `report` obligation is complete with current `pass`, `fail`, or proven `na`; + its failure stays visible. `unobserved` never satisfies reporting. +- `unobserved` means the obligation applies but no current qualifying + observation exists. Its reason preserves whether work was skipped, timed out, + pending an environment, unsupported, stale, or cancelled. Every reason blocks + an authoritative profile that requires the obligation. +- `na` requires a current compiler-owned applicability proof. Empty ledgers, + missing config, absent binaries, and unknown scope are never evidence of + non-applicability. +- Authored bindings, derived joins, and observed results retain distinct + provenance. `asserted` history cannot become `verified` through copying, + migration, a Boolean flag, or an agent identity string. +- A report is reusable only when its adapter and complete declared input digest + match. Presence, path, timestamp, git revision alone, or a successful process + exit is insufficient. + +### Stage adapter DAG + +One sealed snapshot feeds a dependency graph: + +```text +compile canonical inputs and GraphIR once +├─ applicability, references, lifecycle, ownership, and receipt eligibility +├─ Type + Lint +├─ Architecture + Secret +└─ proof execution plan + └─ one runner observation → Unit + Coverage + test/oracle channels + └─ post-observation contract/subject/verification reconciliation + └─ required deliverable/system smoke and visual observations + └─ isolated performance observations + └─ profile verdict + └─ attestation, when profile-complete +``` + +Independent nodes may run concurrently. The registry declares resource locks +such as `cpu-exclusive`, `network`, `display`, `port`, and `workspace-write`. +Performance runs alone. Mutating, network-dependent, GUI/device, or otherwise +non-repeatable adapters need an explicit environment contract and never become +background-safe merely because their process exits zero. + +Architecture and Secret detectors run once; their legacy standalone stages are +aliases over the same observations. A compatible test adapter runs the suite +once while producing case-level results and coverage, then classifies Unit, +Coverage, ordinary test, and oracle channels without re-executing the cases. +When a runner cannot distinguish test failure from coverage-threshold failure, +the adapter may use a second diagnostic execution, but the profile stays +unresolved until attribution is sound. Oracle authoring may require isolation; +oracle execution does not require a second test run. + +### Audit, UAT, and blind evidence + +Audit ranges over every applicable composite criterion address, never only IDs +already present in an evidence ledger. It is cleared only by a current verified +human Audit receipt for that exact criterion and its required checks. The +receipt must record an applicable pass; a human-authored failure or note remains +evidence history but does not clear Audit. + +UAT requires one current verified human feature receipt whose subject, full +runtime-dependency digest, and implementation-author-set digest match the +current feature. Its signed criterion matrix must enumerate every current +composite criterion address. Each passing row clears that criterion's upstream +UAT obligation; a missing row remains unobserved, any explicit failure +dominates, and the receipt's feature checks separately cover no surprise and +trade-off acceptance. This preserves one approval interaction without replacing +the Iron Law's per-criterion UAT meaning with a coarser claim. Criterion-only +receipts, asserted human strings, TTY presence, usernames, hand-written YAML, +and blind receipts never substitute for UAT. + +A verified blind receipt contributes independent provenance only when its exact +evidence bytes, capability manifest, subject digest, and a current matching +testcase pass all resolve. It never clears a human-only obligation. Same-author +verification may satisfy behavioral proof but remains `self-certified` unless a +verified independent channel qualifies under project policy. These rules refine +the receipt contract in D20 without making agent count or persona name a gate +input. + +## D22 — Profiles, cadence, and background scheduling + +### Profile contract + +Profiles select obligations and scope; they do not weaken an obligation's pass +meaning. The configured `project.assurance_level` is the minimum level for +completion, push, and release. A caller may request a stronger profile for one +run but cannot silently downgrade the persisted policy. + +| Profile | Trigger and minimum safe work | Authority and deferral | +|---|---|---| +| `feedback` | After a canonical spec, source, test, runner-config, or receipt change: compile the changed inputs, classify the write scope, invalidate affected observations, and run relevant background-safe checks. | Advisory and partial. Never writes attestation or changes lifecycle. External scanners, project commands, coverage, and system tests may defer. | +| `checkpoint` | Explicit user/host checkpoint, session Stop, or pre-commit: reconcile the changed closure; run required L1 checks and focused directly affected tests when a runnable proof changed. | Blocking only for the checkpoint consumer. It is not completion and never stamps. Stop may degrade its UI response, but that cannot change obligation state. | +| `completion` | `clad done`: evaluate the feature as done; run every applicable obligation through the configured level over the feature, observed write scope, co-owners, dependents, and proof closure. | Authoritative for that feature. One invocation only; do not run a duplicate manual push profile immediately before it. Release-only clean-tree and unrelated repository checks defer. | +| `push` | Git pre-push, PR integration, or authoritative CI: compile the integrated change closure and run all applicable obligations through the configured level. Unknown or cross-cutting scope expands to the repository. | Authoritative for integration. Local observations may be reused only by exact trusted digest; CI independently re-executes rather than trusting workspace cache. | +| `release` | Clean committed tree: reproducible build and generated mirrors, repository-wide obligations through the configured level, conformance fixtures, and every release-required adapter. | Strongest authoritative profile. Nothing required may remain unobserved. Performance is isolated; explicit non-applicability remains visible. | + +Cheap failure prerequisites run before expensive dependents. The normal order is +compiler/reference failures, required receipt eligibility, static tools, +functional tests, proof reconciliation, smoke/visual, then isolated performance. +This is fail-fast scheduling, not a rule that a positive earlier result can mask +a later failure. + +Changed-path scope is sound only when GraphIR can prove the complete owner, +co-owner, prerequisite, dependent, test, oracle, evidence, and configuration +closure. An unknown write scope, unresolved edge, shared compiler/runner config, +lockfile or toolchain change, generated output that mutates after the snapshot, +or adapter whose inputs cannot be enumerated escalates to whole-repository +scope. Unknown never means empty impact. + +### Content-addressed invalidation and reuse + +Each descriptor declares every byte and policy input that can affect its result. +The cache key includes the normalized obligation and subject addresses, relevant +closure digest, source/test/oracle/config bytes, adapter and tool versions, +detector catalog, trust snapshot, environment class, and explicit missing-file +sentinels. Changing any input invalidates only the dependent observations. + +Cache policy is descriptor-owned: + +- `same-session` for volatile or environment-sensitive observations; +- `same-commit` for deterministic tools whose complete inputs and environment + class match; +- `never` for performance, release reproducibility, unsealed external state, and + any adapter unable to enumerate its inputs. + +Caches live under `.cladding/cache/` and are disposable. They never become proof +authority, migrate into the spec, or cross a trust boundary. A cached failure +remains a failure; a cached pass is usable only in a profile that permits its +reuse class. CLI processes may consume a valid cache, but absence or corruption +falls back to execution, never pass. + +### Persistent background scheduler + +Cladding 0.10 includes a scheduler inside the persistent `clad serve` process. +It has `auto | off` policy under local `.cladding/config.yaml`, defaulting to +`auto`; this operational preference is not a spec fact. `off` disables only +anticipatory work: every explicit checkpoint, completion, push, and release +profile executes the same authoritative kernel. Without a persistent server, +background work is absent and correctness is unchanged. + +In `auto` mode the scheduler: + +1. watches canonical compiler inputs and registered proof inputs; +2. coalesces pure compile/in-process feedback after 500 ms of quiet and waits + for 5 s of edit-idle before starting an external background-safe adapter; +3. runs one snapshot at a time (single-flight); +4. merges invalidations arriving during a run and immediately schedules the next + snapshot; +5. commits an observation only if its input digest still matches current bytes, + otherwise retaining it solely as stale diagnostic history; and +6. emits no canonical spec, receipt, lifecycle, attestation, or generated-mirror + write. + +Only adapters marked `background_safe` may run. Such an adapter is offline, +read-only, bounded, killable, isolated from user terminals and shared mutable +services, and explicit about resource locks and outputs. Pure compiler and +in-process detector work is eligible by default. Arbitrary project scripts, +package installation, network access, device/GUI work, performance measurement, +and workspace mutation are not. A project command becomes eligible only through +a reviewed adapter that supplies this isolation contract; it never inherits the +flag from its stage family. + +Single-flight applies per workspace, not per client connection. Scheduler +results are content-addressed, so an explicit profile may reuse them without +waiting when its exact snapshot and cache policy match. Explicit authoritative +profiles take priority, cancel or supersede obsolete background work, and never +race it for an attestation write. + +The worktree-local cache has a 256 MiB LRU ceiling. Eviction affects latency +only. A same-key foreground request joins the running result and revalidates its +root before use; a different-key request cancels the background adapter. An +adapter without isolated output and cooperative cancellation is foreground-only. + +### Latency targets + +| Boundary | Product target | Required timeout result | +|---|---:|---| +| Feedback | p50 ≤ 250 ms, p95 ≤ 750 ms, hard 1 s | advisory `unobserved` | +| Checkpoint | p95 ≤ 10 s, hard 15 s | consumer-visible `unobserved` | +| Completion | soft 60 s, default hard 300 s | blocking `unobserved` | +| Push | soft 5 min, hard 10 min | blocking `unobserved` | +| Release | default hard 15 min or explicit project budget | blocking `unobserved` | + +These are acceptance targets, not portable performance claims. A timeout never +becomes pass, skip-green, or compiler-proven `na`. + +### Feedback and context + +Background execution is a latency optimization, not an agent loop. It dispatches +no model and requires no persona. The host receives at most the current first +blocker, affected business subject, required next proof, deferred-check count, +and observation revision. Identical blocker fingerprints are deduplicated. +`implement`, `verify`, and `observe` packets follow D19: current failures and +freshness are included only when relevant, optional diagnostics remain bounded, +and no background result proves that a fresh agent retained prior context. + +## D23 — Verdict, attestation, compatibility, and acceptance + +### Profile-complete verdict + +An authoritative verdict is GREEN only when: + +1. the compiler sealed one input snapshot and complete requested scope; +2. every effective `hard` obligation has current `pass` or permitted proven + `na` and no reduced `fail`; +3. every effective `report` obligation has current `pass`, `fail`, or permitted + proven `na`; +4. no required obligation is `unobserved`; +5. evidence meets verified provenance and freshness policy; and +6. the reducer records the exact profile, assurance level, registry, policy, + scope, input digest, and `profile_complete: true`. + +`profile_complete` means all required observations resolved; it is not a +synonym for GREEN. A complete profile may be RED because a hard obligation +failed. Channel failure dominance determines the obligation state before +effective blocking determines the profile state. + +Warnings and diagnostics may coexist with GREEN only when no obligation maps +them to a required negative state. A stage-count total, process exit aggregate, +clean report directory, or all-skip run cannot establish profile completeness. + +### Attestation writer boundary + +Attestation v3 is written only from a profile-complete authoritative verdict. +This refines D13's earlier “strict full gate” wording: authority comes from the +declared profile and its complete obligation set, not the `strict` flag or a +numeric stage count. `completion` may attest its exact feature and impact scope; +`push` may attest its integration scope; `release` may attest the repository. +No narrower attestation is rendered as a broader one. + +The attestation records profile ID, configured and achieved assurance level, +scope and input digests, contract/subject/verification closure digests, current +obligation and adapter registry identities, detector catalog, tool/environment +class, trust snapshot, and the sorted observation identities that earned GREEN. +It embeds no receipt body and copies no stale observation forward. Feedback, +checkpoint, silent verdict polling, background work, and incomplete profiles +never stamp. + +### Compatibility and rollout + +- Keep the legacy tier names and all 15 stage IDs readable throughout 0.10. + Their adapters call the kernel and project its observations; they do not own a + second execution or reducer path. +- `pre-commit`, `pre-push`, and `all` map to versioned profile aliases. Machine + output includes both the legacy tier/stage view and the canonical profile, + obligation, completeness, and assurance fields. `pre-commit`, `pre-push`, and + `all` alias `checkpoint`, `push`, and `release`; `clad done` calls + `completion` directly. +- F1–F6 may add the registry, observation model, and adapters behind parity + fixtures. Old and new blocking results remain identical for 0.1 inputs; + criterion-addressed and no-vacuous-GREEN enforcement activates with 0.2. +- Default 0.2 authoritative profiles preserve strict completion: Coverage and + Performance remain `report` in the standard projection but compile as + effective `hard` obligations for Cladding. No 0.10 project setting weakens + this policy. +- Schema 0.1 keeps its shipped policy and readable reports. Schema 0.2 writers + persist `assurance_level`; no inference from old stage history silently grants + L3 or L4. +- Existing report, event, plugin, and audit consumers receive legacy aliases + from the registry. New consumers use obligations and profiles. The 15 IDs may + be retired from a future public default only through a separate compatibility + decision; their historical interpretation remains stable. +- The background scheduler ships with the 0.10 assurance kernel, but correctness + and acceptance fixtures run with both `auto` and `off`. Verdict bytes apart + from timing/cache diagnostics, requested scope, failure set, freshness, and + attestation content must be topology- and scheduler-invariant. + +### Acceptance and measured baselines + +Measurements and their reproduction sources live in [Assurance +evidence](assurance-evidence.md); they are evidence, not portable performance +promises. + +Committed fixtures must additionally prove: + +- official-13 and legacy-15 projections resolve to one obligation registry; +- `L1`–`L4` are cumulative and new 0.2 persists `L2`; +- report failure completes the standard observation requirement but remains RED + under Cladding's strict policy; report absence is unresolved in both; +- every failure, missing runner, stale report, unknown scope, and empty ledger + follows the states above with no vacuous GREEN; +- prior JUnit, coverage, performance, evidence, and receipt artifacts cannot + satisfy changed inputs; +- Unit/Coverage/oracle observations batch without changing result attribution; +- Audit covers the complete composite-criterion set; one verified current UAT + receipt contains an exact pass matrix over that same set plus both feature + checks; and blind evidence never clears UAT; +- focused scope equals repository scope on complete closures and escalates on + every unknown/control-file negative case; +- `auto` and `off`, one or many host agents, and cold or reusable caches produce + identical authoritative verdicts and attestation inputs; F9 also proves + single-flight, resource-lock exclusion, cooperative cancellation, exact-key + reuse, and rejection of every stale background result; and +- only a profile-complete completion, push, or release verdict can write v3. diff --git a/docs/design/spec-0.2/context-and-orchestration.md b/docs/design/spec-0.2/context-and-orchestration.md new file mode 100644 index 00000000..da004136 --- /dev/null +++ b/docs/design/spec-0.2/context-and-orchestration.md @@ -0,0 +1,232 @@ + + +# Spec 0.2 — context and orchestration + +> Canonical owner of D19–D20. Return to the [Spec 0.2 continuation router](../spec-0.2.md). + +## D19 — Cycle context envelope and token discipline + +The context objective is not “send less text” in isolation. It is to deliver every fact required for the current operation, omit unrelated facts, make omissions visible, and prove that the final physical request is bounded. A logical graph slice is insufficient evidence because system prompts, tool catalogs, schemas, escaping, hook cards, and prior tool results also occupy the model input. + +### Task projections + +Context is keyed by an operation, not by a persona name: + +| Task profile | Required projection | +|---|---| +| `spec-edit` | The operation-derived read/write set: relevant intent, target contract node, referenced constraints, affected links, and canonical input revisions. | +| `implement` | Purpose, criteria, constraints, candidate affected paths, predicted write scope, prerequisites, required proof, current failure, and prior-attempt summary when present. | +| `verify` | Contract, observed write scope, changed artifacts, declared bindings, observed results, impact closure, evidence state, and freshness. | +| `observe` | Gate results, detector findings, proof freshness, attestation digest, and unresolved state; no implementation body by default. | +| `blind-oracle` | One criterion, public module signatures, target test path, subject/context revisions, and no implementation body or prior implementation result. | + +These profiles are deterministic projection policies. Except for `blind-oracle`, they neither select a model nor restrict host permissions. A host may use one general agent for several profiles or several agents for one profile. + +### Internal measurement contract + +Use an internal envelope; do not change the frozen `clad_get_context` wire to expose it: + +```ts +interface CycleContextEnvelope { + task: 'spec-edit' | 'implement' | 'verify' | 'observe' | 'blind-oracle'; + feature: string; + context_revision: string; + input_revisions: Readonly>; + write_scope: { + paths: readonly string[]; + provenance: 'predicted' | 'observed' | 'unknown'; + }; + sections: readonly ContextSection[]; + budget: { + payload_utf8_bytes: number; + resident_utf8_bytes: number; + total_utf8_bytes: number; + cache: 'cold' | 'warm' | 'unknown'; + estimator: string; + estimated_tokens: { + payload: number; + resident: number; + total: number; + }; + omitted: readonly OmissionSummary[]; + required_overflow: boolean; + }; +} +``` + +`payload_utf8_bytes` measures the final task packet after revision, write-scope, budget, and omission metadata are serialized. `resident_utf8_bytes` measures every Cladding-controlled input present outside that packet for the same model request: role/system text, MCP server instructions and catalogs, tool schemas, hook cards, and retained Cladding tool results or retry summaries. `total_utf8_bytes` is their exact sum. Classify each byte once. Provider-owned hidden context is outside this total; when a provider reports input/cache/output tokens, record those alongside the envelope rather than substituting them for it. The cache field describes the observed resident-prefix state and stays `unknown` when the host cannot prove it. + +Measure and classify all Cladding-controlled input: + +- optional role-brief/system text; +- task packet and final provider/MCP JSON after escaping; +- MCP server instructions, tool catalog, and tool schemas; +- tool results and retry/prior-attempt summaries; +- hook push cards; +- context/input revisions, cursor, budget, and omission metadata. + +Exact UTF-8 bytes are primary. Token estimates always name their estimator. Provider-reported input, cache-read, cache-write, and output tokens are recorded only in live same-host A/B runs. Cold catalog cost and warm cached cost are separate results. + +### Packing and reuse + +- Required contract facts are never silently truncated. If they do not fit, return `required_overflow` and the exact oversized section rather than a plausible-looking partial contract. +- Pack optional sections in task-specific priority order, aggregate omission metadata, serialize the complete payload, and remeasure until byte/token totals reach a fixed point. +- Fresh agents receive the full mandatory core. Delta and `not_modified` responses require a proven same-session `context_revision`; a matching git revision alone does not prove that a new agent retained prior context. +- Before an edit, derive `write_scope` from the operation's predicted write set. After an edit, replace it with paths observed from the actual diff. Impact traversal starts from these paths, not from every path in `feature.modules`. +- An unknown write scope is explicit and never means empty impact. Keep module ownership/fan-out summaries available lazily, surface incompleteness, and request or observe a narrower path set before claiming a complete regression closure. +- Avoid repeating the same project, criterion, path, or proof fact inside one envelope. References may point within the envelope by stable address. +- A verification projection always retains `{criterion, state, selector, locator-or-digest}` for every relevant proof. Diagnostics are failure-first optional detail packed only with remaining budget. +- An internal `diagnostic_cursor` may continue omitted diagnostics only within the same session and `context_revision`. It is not a public GraphIR cursor and does not change the frozen `clad_get_context` v1 wire. +- Keep the shipped hook limits of 600 characters per push card and 2,500 estimated tokens per session. Measure the complete MCP catalog and ratchet its sorted serialized bytes separately from task payloads; adding a public capability may raise the ratchet only through a reviewed catalog diff. + +Initial payload ceilings are task-profile rules: 16 KiB for `spec-edit`, `verify`, and `observe`; 24 KiB for `implement` and `blind-oracle`. They do not claim that total cold context fits inside them. `spec-edit` scopes required content to the typed operation: a criterion operation includes feature intent, the target criterion, referenced constraints, affected links, and its write set; a feature-level operation may require the whole feature; deterministic global migration is executed by the compiler rather than sending the whole corpus to an agent. Across the self corpus, required semantic-address recall is 100%, forbidden blind data leakage is zero, avoidable duplicate facts are zero, and `required_overflow` is zero. A future legitimately huge feature may return honest overflow instead of truncation. Comparable contract-only p50 and p95 payload and total bytes may not regress from 0.1; a 20% payload p50 reduction is a target, not permission to drop required facts. + +### A–E simulation + +Run the same preregistered work items through: + +- **A — shipped persona path:** current developer/reviewer prompts and dispatch packet; +- **B — one general host agent:** task profiles with no general persona prompt; +- **C — several general host agents:** the same task profiles split by the host; +- **D — restricted blind oracle:** fresh context plus enforced negative capabilities; +- **E — legacy reconstruction:** the reads/greps/tool calls needed to reconstruct the same facts without the new projection. + +A, B, and C must produce the same contract addresses, deterministic gate result, verdict, stale closure, and observed write-scope impact. Only verified evidence provenance may change the independence label. Removing purpose, criterion identity, constraint rationale, selector, provenance, direction, impact, or freshness must fail the corresponding ablation. Removing every general persona prompt must not stop the cycle or alter a gate. + +Fixtures cover every self-corpus feature plus small, p50, p95, and maximum packets; shared-module hubs; deep dependency closures; proof-heavy and unresolved nodes; retries; non-ASCII content; blind signatures; and a 5,000-feature synthetic graph. A no-retry full cycle's p50/p95 Cladding-controlled input may not regress against the comparable shipped path. D24's AB01–AB12 live A/B may support only a task-scoped efficiency claim; broader GraphIR generalization requires the separately preregistered optional expansion described by D17. + +## D20 — Host-owned orchestration and verified independence + +This decision refines the shipped role-contract architecture; it does not invert it. The current orchestrator and managed AGENTS contract already say that the host owns agent count, names, models, threads, and parallelism, while Cladding declares outcome conditions and judges recorded evidence. Current specialist Markdown already describes selectable role briefs. The remaining mismatch is the fixed five-step persona narrative in the cycle guide and the experimental headless loop's hard-coded developer/reviewer dispatch. + +### Enforcement boundary + +Cladding enforces invariants, not a roster: + +- the host chooses whether one or many general agents plan, edit specs, implement, test, review, or observe; +- deterministic gates judge filesystem, contract, observation, and evidence truth independently of the chosen topology; +- a same-author verification pass is allowed but remains `self-certified`; +- no gate or verdict depends on a general persona ID; +- identity matters only when a claim requires independent evidence; +- a new enforced role is admitted only when an ablation proves that its negative capabilities are necessary for the guarantee. + +Role briefs remain because their domain contracts measurably steer interpretation and serve as interface manuals for hosts that want them. They are optional execution aids, not the source of gate authority. The blind profile is the exception: not seeing implementation is an epistemic precondition, so its fresh context and negative capabilities must be evidenced rather than promised in prose. + +### Evidence assurance and canonical receipts + +Keep `identity.author` and add a 0.2 assurance boundary: + +```ts +type EvidenceAssurance = 'asserted' | 'verified'; +type ReceiptCheck = 'pass' | 'fail'; + +interface ReceiptBase { + receipt_schema: '1'; + issuer: string; + issuer_key_id: string; + issuer_proof: string; + subject: `feature:${string}` | `criterion:${string}/${string}`; + subject_sha256: string; + observed_at: string; +} + +interface HumanReceiptBase extends ReceiptBase { + method: 'human_channel'; + reviewed_inputs_sha256: string; + runtime_dependency_sha256: string; + implementation_authors_sha256: string; +} + +interface AuditReceipt extends HumanReceiptBase { + claim: 'audit'; + subject: `criterion:${string}/${string}`; + checks: Readonly<{ + evidence_sufficiency: ReceiptCheck; + code_test_review: ReceiptCheck; + independence: ReceiptCheck; + }>; +} + +interface UatReceipt extends HumanReceiptBase { + claim: 'uat'; + subject: `feature:${string}`; + criterion_verdicts: Readonly>; + checks: Readonly<{ + no_surprise: ReceiptCheck; + tradeoff_acceptance: ReceiptCheck; + }>; +} + +type HumanReceipt = AuditReceipt | UatReceipt; + +interface BlindReceipt extends ReceiptBase { + method: 'blind_capability'; + claim: 'independent_oracle'; + verdict: 'pass' | 'fail'; + evidence: { locator: string; sha256: string }; + capability_manifest_sha256: string; +} +``` + +Receipts live at `spec/evidence//.yaml`: the feature directory is derived from `subject`, and the filename is the SHA-256 of the complete canonical receipt including its signature. There is no duplicate body ID. Receipts are create-only committed evidence, not regenerable output; `evidence.revoke` removes an exact receipt. + +Receipt schema 1 uses a detached Ed25519 signature over a newly defined canonical frame. Parse the YAML into a JSON-compatible value, rejecting aliases, tags, non-string map keys, non-finite numbers, and other non-JSON values. Remove `issuer_proof`, serialize the remaining value with RFC 8785 JSON Canonicalization Scheme, and encode it as UTF-8 `payload`. Let `domain` be the ASCII bytes of `cladding.receipt/1`; the signed bytes are `u32be(domain.length) || domain || u64be(payload.length) || payload`. Store the signature as unpadded base64url. After inserting `issuer_proof`, compute the lowercase hexadecimal filename SHA-256 over the RFC 8785 UTF-8 bytes of the complete receipt. `verified` and the receipt verdict are derived, never stored. + +The strict gate is synchronous and offline. `issuer_key_id` selects a key from an immutable trust snapshot supplied by the Cladding installation or registered host adapter outside the writable workspace; the gate and attestation record that snapshot's digest. The verifier checks the signature and recomputes the subject, runtime-dependency, implementation-author, reviewed-input, evidence, and capability-manifest hashes without a network lookup. A missing verifier, unknown key, or online-only identity leaves the evidence unresolved/asserted and cannot satisfy UAT or required independence. A networked host channel may verify identity at ingestion time only if it emits this portable signed proof; later gates recheck bytes and signatures locally. + +`subject_sha256` is canonical and address-sensitive: + +- For a criterion subject, hash the feature purpose, referenced capability outcomes, the target criterion's ID and parsed contract, its local/referenced constraint rationales, applicable required-scenario intent, and any legacy-exemption identity. Exclude sibling criteria, modules, notes, formatting, and unrelated scenarios or rules. +- For a feature subject, use the full feature `contract_sha256` closure from D13. +- A receipt never transfers between addresses even when two subject serializations happen to be equal. + +Scenario applicability is conservative and explicit because schema 0.2 has no +criterion-level scenario edge. When `project.scenario_policy` is `required`, +every scenario whose `feature_refs` contains the parent feature applies to every +criterion in that feature. Hash sorted records of scenario ID plus `actor`, +`goal`, `success`, and ordered `steps`. Under `off | advisory` no scenario enters +the criterion subject hash. Removing the parent feature ref removes that record; +changing an unrelated scenario or sibling criterion does not stale the target +criterion receipt. A future narrower rule requires an explicit schema edge and +must not infer criterion coverage from step prose. + +A human receipt additionally binds the sorted runtime dependency closure, including the feature's complete module paths and bytes and explicit missing-file sentinels. `implementation_authors_sha256` hashes the sorted unique normalized `{root, assurance, author, name}` mutation-provenance records for every implementation root in that closure; an unattributed root receives `{root, assurance: 'asserted', author: 'unknown', name: ''}`. Hashing binds the declared mapping; it does not upgrade asserted identity into verified identity. An incomplete author mapping leaves an independence requirement unobserved, and a matching verified issuer remains self-certified. + +An Audit receipt is criterion-scoped and records the three named checks above. A UAT receipt is signed once per feature but its `criterion_verdicts` addresses every current composite criterion: each value is that criterion's intent-alignment decision, while the two named feature checks cover negative space and accepted trade-offs. A UAT pass requires the exact current criterion set and every criterion/check to pass. Any explicit fail is an applicable failure even if the rest of the matrix is incomplete; an unknown address invalidates the receipt, and a missing address remains unobserved rather than passing. The reducer derives the receipt verdict, so no persisted summary can disagree with its checks. Editing a shared or prerequisite module, reviewed input, author set, trust snapshot, or signature therefore stales every receipt whose closure includes it. [D21–D23](assurance.md#d21--iron-law-assurance-kernel) own reduction and escalation. + +A blind receipt binds the exact evidence locator and bytes plus the capability manifest. It contributes independent provenance only when a current observed testcase pass confirms the bound criterion; a receipt for an unexecuted blind test remains unverified. Blind evidence never substitutes for UAT. + +- `asserted` records useful history but cannot satisfy a new 0.2 independence requirement. +- `verified` means the offline verifier accepted a supported signed channel or isolation boundary. It proves the registered issuer's assertion, not universal human identity. +- A receipt is bound to the current subject hash. A target criterion, relevant constraint/outcome/scenario, or feature-level subject change makes it stale; editing an unrelated sibling criterion does not. +- A verified human receipt contributes to feature independence and clears only the matching Audit or UAT obligations when its complete claim-specific checks reduce to pass. +- A verified blind receipt contributes to feature independence but never substitutes for UAT or another human-only rule. + +F5 owns receipt schema, canonical framing, offline verification, trust-snapshot +resolution, validated ingestion, content-addressed storage/revocation, and +reducer wiring. Its deterministic fixtures may supply signed receipts, but F5 +ships no product issuer that can manufacture verified human or blind evidence. +It retains `clad signoff`; bare TTY or pseudo-TTY input records asserted audit +history only. Without a registered issuer, verified signoff returns +`HUMAN_REQUIRED`. There is no `--verified` bypass. OS/git identity, caller text, +generic `blind: true`, and hand-written YAML are asserted only. + +Keep `clad_author_oracle` readable for 0.1. Under 0.2, a registered adapter must supply a fresh-context/capability receipt before the result is verified blind. The generic MCP `blind: true` flag remains readable but records only asserted/attested provenance and cannot satisfy `independence_policy: require`. + +### Runtime rollout + +F9 ships the envelope, task projections, A–E invariance suite, and the first real +registered issuer paths as 0.10.0 core: at least one host-elicitation or external +signing path for human receipts and one fresh-context/capability path for blind +receipts. Both call F5's ingestion boundary and emit portable receipts that a +later offline gate re-verifies. F9 does not replace the experimental +developer→reviewer loop. F10 may introduce a headless task-state loop in 0.10.x +only after: + +1. A–E proves topology-invariant contracts, gates, verdicts, and stale closures; +2. verified human and blind evidence are produced through real product paths; +3. `independence_policy: require` is no weaker than before; +4. blind leakage remains zero; +5. general role-brief removal changes no deterministic result. + +The F10 reference loop may request `implement`, `verify`, or `observe` work, but those are operations rather than required identities. The host may satisfy several with one agent; the receipt and evidence rules, not dispatch count, decide independence. Rich public GraphIR cursors, viewer expansion, and any broader retrieval generalization study remain non-blocking 0.10.x work; F9's session-local `diagnostic_cursor` is a narrowly scoped task-projection exception. diff --git a/docs/design/spec-0.2/decision-log.md b/docs/design/spec-0.2/decision-log.md new file mode 100644 index 00000000..9d7664e6 --- /dev/null +++ b/docs/design/spec-0.2/decision-log.md @@ -0,0 +1,124 @@ + + +# Spec 0.2 — decision log + +> Canonical owner of claim boundaries, rejected alternatives, the evolution queue, and change history. Return to the [Spec 0.2 continuation router](../spec-0.2.md). + +## Claim discipline + +The graph claim ladder in D17 is cumulative but not substitutable: structural validity does not prove retrieval benefit, retrieval benefit when called does not prove adoption, and adoption does not prove implementation correctness. + +### Claims allowed now + +- Spec 0.2 reduces semantic prose to purpose, statement, and constraint rationale while retaining typed structural links. +- It removes internal duplicate AC representations for new/edited nodes. +- It eliminates shared capability-edge writes for ordinary feature creation. +- It can detect stale contract and proof inputs that attestation v2 cannot see. +- Verified repository scale makes feature-local mutation and proof closure materially relevant. +- The shipped role contract assigns execution form to the host and computes independence labels from recorded evidence rather than persona membership. +- D21–D23 define an obligation-based target in which the existing runners are adapters, the default completion profile derives at most Ironclad L2, and stronger profile labels require their complete current closure. +- Bounded background checking may provide speculative observations in 0.10, but only a foreground profile-complete reduction may change lifecycle state or write attestation. +- The current MCP server's declared catalog, in-memory negotiation, and bootstrap list-change path are executable wire facts; D24 makes MCP an optional adapter over shared kernels, not a second authority. + +### Claims prohibited until new evidence exists + +- **“34 features produced nine false blocks”** — the repository records nine prior false rejections and a separate 34-feature experiment, but no raw fixture ties them together. +- **“26% false-block class removed”** — this is the unsupported `9/34` conflation. +- **“Landmine protection 2/2”** — [`case-working-set-landmine.md`](../../ab-evaluation/case-working-set-landmine.md) reports a NULL real-agent outcome. Two structural instruments discriminated deterministically; that is not an effect measurement. +- **“37/37 design simulation”** — the 37 IDs are now a newly preregistered contract matrix, not a recovered session artifact. Claim a result only after all one-to-one committed fixtures run. +- **“Done proof 100%”** — 277/277 measures declared proof addresses and attestation markers, not observed testcase-level verification. +- **“Cost falls from +45% to +20–30%”** — the latter is an acceptance projection until measured under the new authoring loop. +- **“N=300 prevents 25–50 latent defects”** — model projection, not observed defect count. +- **“Feature count below eight has no value”** — existing eight-feature constants are detector grace thresholds, not an economic break-even result. +- **“Every `blind: true` record is structurally blind”** — the packaged role restricts tools, but generic MCP currently accepts a caller assertion without a runtime isolation receipt. +- **“General persona prompts are unnecessary”** — deterministic gates must be prompt-independent, but the briefs have affected agent interpretation. Claim only topology-invariant gate behavior after D19's committed ablation; do not claim equal authoring quality without a live benchmark. +- **“A smaller task payload proves lower total input cost”** — the current full MCP list bundle is 53,585 bytes before server instructions and host framing; the historical 29,866-byte subset used a narrower serializer. Cache reuse is host-dependent. Compare payload, resident, total, retries, and provider-reported cache tokens separately in a live A/B. +- **“Background checking makes completion faster”** — availability is not a latency result. Measure foreground active wait, total CPU, cancellation/stale-result rate, cache-hit promotion, and authoritative completion wall time against the same edit trace. +- **“A latency budget is a performance guarantee”** — D22 budgets are product targets and downgrade rules. Claim an achieved percentile only from a dated, environment-labelled benchmark. +- **“A speculative pass is proof”** — a background result has no lifecycle or attestation authority until a foreground reducer revalidates its exact input, tool, policy, and environment closure. +- **“A fired hook or background result proves adoption”** — pushed delivery proves that Cladding spoke, not that an agent used the result. Preserve the existing pull/completed-cycle adoption protocol and report background reuse separately. +- **“The new cadence improves correctness”** — cadence may reduce time-to-finding without changing the authoritative obligation set. Correctness or defect-prevention claims require a preregistered fault corpus or live controlled comparison. +- **“MCP availability proves efficacy, efficiency, or adoption”** — wire conformance is only the first rung. Require a reference-host Spec 0.2 cycle for efficacy, an equivalent-task A/B for efficiency when used, and voluntary pull telemetry for adoption. + +## Rejected decisions + +Do not reopen these without new evidence that invalidates the stated reason. + +| Rejected | Reason / reopen condition | +|---|---| +| Tier-named spec directories | Tier is mutable and path changes would break consumers. Reopen only with a path-independent external standard and measured migration benefit. | +| Rename `modules` or `depends_on` | Adds vocabulary migration without removing an active ambiguity. Use Soft Shell descriptions instead. | +| Rename the scenario concept to journey | Scenario is established and unambiguous as a user-journey artifact; only its field grammar changes. | +| Add `interface` criterion kind | No consumer or distinct enforcement exists. | +| Six-axis assurance taxonomy | Existing verdict/disposition surfaces already separate outcomes; add only when a concrete consumer cannot express a needed distinction. | +| Persist `traceability.yaml` | Authored-edge projections and reverse edges are live IR and would otherwise become stale committed copies. | +| Runtime `L ∪ N` capability edges | Creates two simultaneous authorities. Comparison is upgrade-preview-only. | +| Allow multiple `shall` modals | Violates atomic AC and case-level binding granularity. Split the AC. | +| Automatically write inferred dependencies | Current candidate graph can introduce large cyclic components. `dependency.promote` remains explicit and cycle-checked. | +| Move existing generated files in 0.10 | Registry and aliases must land before path migration. | +| Generic filesystem/JSON-Patch MCP editor | Cannot provide domain invariants or honest write-set calculation. | +| Automatically classify legacy ACs as behavior | Converts meaning without evidence. Use `legacy_unclassified` until the node changes. | +| Guess test selectors for path-only refs | Manufactures proof. Use the accepted node-level baseline fallback. | +| Claim duplicate elimination proves correctness | Omission, stale evidence, unobserved behavior, and concurrency remain independent failure classes. | +| Persist a graph or vector index as authority | Live compiler IR already has deterministic identities and exact links; another store adds freshness and opaque-retrieval failure modes. | +| Preserve a v1 graph compatibility serializer | It would keep kind twins and criterion loss alive as a second public model. Cut the graph surface atomically. | +| Change frozen `clad_get_context` to resemble GraphIR | It is a distinct accepted wire contract, and pull adoption is not confirmed. Use per-surface versions. | +| Use generic undirected BFS as the default query | Direction and containment semantics matter, and the corpus simulation shows depth-2 token blow-up. | +| Require a fixed planner→developer→reviewer→observability topology | Host orchestration is already the shipped contract, and deterministic guarantees must not depend on general persona membership. Reopen only if an ablation proves a necessary negative capability. | +| Delete the specialist role briefs | They are optional interface manuals and their wording has changed agent interpretation. Remove a brief only after a committed ablation shows that no supported host or contract loses needed guidance. | +| Add an F7.5 `clad_get_context` v2 | `clad_get_context` v1 stays frozen throughout 0.10; F8 versions the graph surface only. A future context successor needs its own adoption and compatibility decision. | +| Treat raw `blind: true` or a free-form human claim as verified evidence in 0.2 | A Boolean assertion cannot prove isolation or a human channel. Require a receipt from a supported adapter and preserve unverified input as asserted history. | +| Treat TTY/pseudo-TTY presence or hand-written receipt YAML as verified | The process cannot prove user presence or a trusted issuer from workspace-controlled input. Bare `clad signoff` is asserted; verified paths require an offline-verifiable external signature. | +| Perform network identity lookup during a strict gate | Violates synchronous deterministic gating and makes old commits depend on remote state. Verify pinned signed receipts offline; online integrations may issue portable proof at ingestion only. | +| Embed evidence receipt bodies in attestation | Creates a second receipt authority and merge surface. Keep immutable receipts under `spec/evidence/`; attestation seals their current identities and closure only. | +| Put workspace schema markers in child spec documents | Mixed selectors permit partial interpretation. `spec.yaml#schema` is the single workspace switch; `receipt_schema` versions only the independent receipt protocol. | +| Add `feature.unarchive` to the mutation API | Archive is an integration-terminal historical decision. Correct mistakes through VCS before integration instead of adding a second lifecycle transition with unclear proof semantics. | +| Use feature `contract_sha256` for every criterion receipt | Sibling criteria would invalidate unrelated proof. Bind criterion receipts to their subject closure and keep the broad hash for feature attestation. | +| Seed impact from every declared feature module | Shared modules amplify one focused edit into dozens of unrelated owners and dependents. Seed from predicted/observed write scope; keep module fan-out lazy when scope is unknown. | +| Use one revision for both context reuse and writes | Projection retention and byte-level write concurrency are different claims. Keep `context_revision` and `input_revisions` separate. | +| Apply one payload ceiling by content class or by a two-task aggregate | The measured 27,854-byte value combines implement and verify and made the former wording ambiguous. Enforce 16/24 KiB per task profile after operation-scoped packing; do not raise a class limit from an aggregate. | +| Make an atomicity heuristic blocking | One modal does not ensure one obligation, while length/conjunction heuristics also flag valid prose. Keep the hard grammar deterministic and the semantic risk advisory. | +| Defer all cleanup to a final cleanup-only phase | Superseded authorities would coexist through several features and become new dependencies. Retire them in the same proven cutover cycle; keep F1 additive by design. | +| Require monotonically lower LOC or a monotonically higher test count | Both reward the wrong proxy. Require one authority, no unjustified orphans, preserved behavior, and discriminating replacement tests. | +| Delete compatibility or historical paths before a replacement is adopted | Absence of a static import does not remove spec, entrypoint, generated, runtime-lookup, or external obligations. Apply the D18 deletion proof after consumer cutover. | +| Run the full gate after every edit | It multiplies active wait, executes environment/HITL work at the wrong boundary, and encourages hook bypass without strengthening the eventual authoritative obligation set. Keep bounded interactive feedback and one profile-complete completion reduction. | +| Make local hooks or a local background worker the sole authority | Local surfaces are optional, bypassable, and may lack the release environment or verified evidence channels. They reduce latency; completion, protected push, and release profiles retain authority at their declared boundaries. | +| Treat the number of registered stages as the Iron Law theory | The upstream draft defines 13 standard stages, while Cladding ships two extensions. Theory belongs to obligations, cumulative profiles, freshness, and failure reduction; preserve runner IDs only as compatibility/reporting surfaces. | +| Defer the persistent assurance scheduler to 0.10.x | The 0.10.0 design deliberately ships the scheduler with F9, but makes `auto` and `off` verdict-equivalent. Single-flight, stale-result rejection, cancellation, resource locks, and cache-key invariance are release gates; background availability never becomes proof authority. | +| Make MCP a second compiler, graph, or verdict authority | CLI, MCP, and in-process entry points must share domain kernels. Transport-specific facts would create exactly the drift Spec 0.2 removes. | +| Require MCP resources, prompts, subscriptions, or sampling for correctness | Supported tools-only hosts would lose the cycle, and optional delivery would become an authority. These surfaces may optimize use but cannot weaken the tools-only path. | +| Cut over to a task-scoped MCP catalog from byte reduction alone | Smaller metadata does not prove tool discoverability, equivalent task success, or adoption. Require the preregistered host A/B and dynamic-discovery controls. | + +## Evolution queue + +These are intentionally outside the 0.10.0 core and may evolve independently. + +| Item | Entry condition | +|---|---| +| Move `index`, `_doc-links`, and `attestation` into `spec/generated/` | Registry aliases, old/new reader fixtures, and measured compatibility are green. Target: 0.11. | +| Additional test-framework adapters | Each adapter has source→selector→JUnit round-trip fixtures and a real adopter. | +| Executable scenario verification | A scenario runner or binding carrier exists; only then may `scenario_policy: required` imply runtime proof. | +| Authoring-provider bridge, including Spec Kit input | The canonical IR is stable and a provider can map without weakening identity, proof, or transaction contracts. | +| Advisory semantic comparison | Deterministic compiler is complete; LLM comparison remains labelled advisory and never defines GREEN. | +| Cost and false-block benchmark | F1–F6 land and the preregistered new benchmark can compare 0.1 and 0.2 maintenance. | +| Broader LLM GraphIR retrieval study (40 tasks is the current scale candidate) | AB01–AB12 has first established task-scoped efficiency-when-used; a separately preregistered expansion may test generalization without altering adoption telemetry. Target: optional 0.10.x. | +| Viewer proof-detail polish and public graph cursor/`not_modified` support | The minimum v2 viewer/export cutover is stable; add only against a measured payload or navigation need. F9's session-bound diagnostic continuation is not this public surface. Target: 0.10.x. | +| F10 task-state headless loop | F1–F9 are green; verified human/blind evidence ingress exists; D19 A–E preserves contracts, gates, verdicts, stale scope, and `require` strength. Target: 0.10.x. | +| Generated-file physical migration | See first item; do not combine with schema migration. | + +## Change log + +| Date | Decision | Change | Evidence | +|---|---|---|---| +| 2026-08-28 | D01–D16 | Initial accepted Spec 0.2 design recorded as the continuation SSoT. Chose node-level legacy binding baseline, strict-new/lenient-legacy grammar, live IR with no traceability file, scenario `goal`, proof-content attestation, and bounded commit locking. | Repository corpus audit and adversarial Claude/Codex review summarized in [Evidence snapshot](evidence.md#evidence-snapshot). | +| 2026-08-28 | D17 | Made GraphIR v2 the shared compiler model for contract/proof closures; separated its structural justification from optional LLM retrieval; preserved frozen `clad_get_context` v1 while requiring an atomic graph-v2 cutover; and fixed independent validity, efficiency-when-used, and adoption claim gates. | v1 fidelity census, v2 read-only query reconstruction, token/ablation simulation, source-reference audit, and adoption protocol review. | +| 2026-08-28 | D15–D17 refinement | Fixed the F1 schema-0.1 bootstrap, additive no-cutover boundary, independent sorted-record oracle, occurrence/unique/resolution census vocabulary, F1/F5 adapter boundary, and one authoritative completion-gate cadence. | Scoped import-graph census, YAML/CST locator prototype, typed evidence-reference census, known unresolved controls, and feature-cycle/CI audit. | +| 2026-08-28 | D18–D20 | Added adjacent-cleanup proof, complete physical context accounting, task-keyed projections, host-owned orchestration refinement, and verified human/blind evidence receipts; kept F8 as graph-v2 cutover and placed the task-state headless loop at F9. | Current role-contract/code-path audit, 281-feature dispatch-size census, generic-MCP blindness boundary review, and 46 focused GREEN tests. | +| 2026-08-28 | D06–D07, D12–D14, D16, D19–D20 refinement | Added an explicit cycle-begin boundary, subject-level evidence hashes, predicted/observed write scope, physical payload/resident/total accounting, explicit capability links, and advisory-only semantic atomicity checks. | One-cycle F-06dfdad6 simulation, current MCP catalog serialization, impact fan-out comparison, stale matrix, and 53 focused GREEN tests. | +| 2026-08-28 | D15–D18 routing and retirement | Split the continuation SSoT into a small task router and bounded semantic owners; added same-cycle code/document/test retirement, contract-aware orphan checks, and replacement-test rules without LOC or test-count quotas. | 92,189-byte monolith census, Madge orphan graph, and 1,608-line supersession-candidate census. | +| 2026-08-28 | D03, D06, D10, D12–D16, D19–D20 execution refinement | Made root schema selection singular; added canonical signed receipts and offline trust verification; completed lifecycle/link operations; separated F3 proof, F4 apply, and F7 self-migration; replaced the ambiguous content-class packet ceiling with operation-scoped task limits; preregistered the 37-case matrix; promoted envelope/A–E work to F9 core and moved the headless loop to F10. | Adversarial document audit, receipt/freshness simulations, task-envelope serialization, module fan-out census, and registered-detector recount. | +| 2026-08-28 | D21–D23 | Reframed Iron Law as profile-selected obligations over current closures; preserved the 13-stage standard plus two named Cladding extensions; selected default L2, explicit unknown escalation, bounded 0.10 background checks, profile-complete attestation, and host-owned topology. | Repository gate/hook/config audit, assurance-cadence simulation, upstream Ironclad draft comparison, current latency/adoption records, and independent strategy critique. | +| 2026-08-28 | D12, D16, D18, D20–D23 refinement | Defined receipt canonical bytes and author-set freshness, made one feature UAT receipt carry the complete per-criterion decision matrix, aligned stale gates to runtime closure, completed block/archive transitions, retained the F9 scheduler in 0.10 core, and split hygiene/cadence evidence owners for routing headroom. | Current document/schema audit, upstream per-criterion Stage 4 comparison, doc-link extraction parity, lifecycle simulation, and owner-byte census. | +| 2026-08-29 | D15–D16, D24 | Added an additive V0 validation ledger and made MCP an optional, bounded adapter with separate conformance, efficacy, efficiency-when-used, and adoption gates; preregistered twelve MCP scenarios and preserved unrun/pending states. | Executable owner/case ledger, in-memory MCP negotiation and bootstrap transition, canonical catalog-byte census, and legacy host-smoke/adoption evidence audit. | +| 2026-08-29 | D16–D17, D19, D24 evidence refinement | Scoped AB01–AB12 to the first task-scoped MCP decision, retained a 40-task GraphIR study only as optional later generalization, refreshed physical-context and catalog evidence, and restored router editing headroom without changing the accepted architecture. | `npm run validate:spec-0.2`, exact UTF-8 census, graph/cadence model controls, and owner/anchor integrity tests. | +| 2026-08-29 | D09, D14–D16, D20–D24 final audit closure | Separated upstream hard/report strictness from Cladding's strict blocking policy, assigned receipt protocol consumption to F5 and real issuer paths to F9, defined required-scenario criterion freshness, fixed L2→L4 self-migration staging, and widened normative-owner validation. | Upstream Iron Law comparison, policy/scenario truth-table simulations, duplicate-heading fault injection, and the full contributor test suite. | diff --git a/docs/design/spec-0.2/delivery.md b/docs/design/spec-0.2/delivery.md new file mode 100644 index 00000000..191527a4 --- /dev/null +++ b/docs/design/spec-0.2/delivery.md @@ -0,0 +1,159 @@ + + +# Spec 0.2 — delivery and acceptance + +> Canonical owner of D15 and D16. Return to the [Spec 0.2 continuation router](../spec-0.2.md). + +## D15 — Implementation sequence + +**V0 precedes F1:** land the additive design-validation ledger, deterministic simulator, token accounting, and MCP wire/efficacy boundary described by [D24](mcp.md#d24--mcp-as-optional-transport-and-bounded-projection). V0 may pass its own infrastructure while target decisions remain `implementation_pending`; it may not call preregistered cases implemented or a host E2E run completed. Challengers produced by validation remain proposals until a maintainer changes the canonical owner and decision log. + +Follow one feature cycle at a time; author each shard immediately before implementation. + +F1 is necessarily bootstrapped under schema 0.1 because the 0.2 reader does not exist until F2. Author the F1 shard with the current EARS/`text` contract, put its WHY under the established `notes: "## Why\n..."` convention, and point it at D10 and D17. Do not pre-author 0.2-only `purpose`, `statement`, or `kind` fields. The F1 node then becomes a normal input to the later self-migration rather than a privileged exception. + +F1 begins with an independent, sorted corpus-record snapshot. Its oracle scans source YAML without importing the production loader, reverse index, GraphIR builder, or query closures. The additive compiler proves parity while existing consumers remain unchanged, keeping a failed F1 local and discardable. + +1. **F1:** commit this design, executable artifact/ID registries, the GraphIR v2 address/role/provenance skeleton, and the node-baseline schema. Preserve the stale `hash6` wording as a parity negative control until the ID registry owns the policy, then repair that comment before F1 completion. +2. **F2:** version dispatch, strict 0.2 parser, non-blocking atomicity advisory, total legacy scanner, and migration preview. +3. **F3:** project, explicit feature-capability links, capability catalog, and architecture 0.2 contracts; prove the candidate `L = N` edge set in memory without changing repository schema or bytes. +4. **F4:** `clad_edit_spec`, `feature.begin`/`clad begin`, context/input revision separation, commit lock, journal recovery, journaled migration apply, and existing-tool adapters. +5. **F5:** doc/source/test/oracle/evidence adapters, covers and case-level observations, legacy binding fallback, receipt framing/offline verification/ingestion/storage/revocation, asserted signoff fallback, and verification detector rewiring. Test issuers prove the protocol; no verified product issuer ships here. +6. **F6:** shared contract/subject/runtime-dependency/verification closures, the [D21–D23](assurance.md#d21--iron-law-assurance-kernel) obligation DAG and profile reducer, legacy 15-stage compatibility projection, profile-aware `clad done`, and attestation v3. +7. **F7:** scenario v2 and `off | advisory | required` policy, including its GraphIR edges; then preview, resolve, and atomically self-migrate Cladding from 0.1 to 0.2, complete F7 on 0.2, and write the first pure-0.2 v3 attestation. +8. **F8:** atomically cut `clad_get_graph`, CLI graph queries/JSON, and the existing exporters/viewer to GraphIR v2; do not retain a v1 graph adapter. +9. **F9:** ship `CycleContextEnvelope`, operation-scoped task projections, the content-addressed single-flight background scheduler, registered human and blind receipt issuer paths, and the A–E topology/context/cadence invariance suite as 0.10.0 core; keep the existing experimental headless loop compatible. +10. **F10 (0.10.x tail):** after F9 is green, replace that loop's hard-coded developer→reviewer choreography with task-state dispatch. Rich public graph cursors, viewer detail UX, and any broader GraphIR retrieval study remain independent tail work. +11. **0.11:** move existing generated index, doc-link, and attestation files only after registry aliases are proven. + +There is no F7.5 context-wire migration. F8 remains the graph-v2 public cutover. Standards, canonical documentation, glossary entries, and generated plugin mirrors update inside the feature that changes their contract; they are completion work, not a separate numbered feature. + +### Cutover and retirement map + +F1 is the deliberate exception to same-cycle retirement: it establishes an additive parity boundary and deletes no shipped compiler, loader, graph, detector, or optimizer path. From F2 onward, every cycle inventories the authority it supersedes before implementation and retires that authority after all consumers cross the proven boundary. + +| Boundary | Required retirement after cutover | +|---|---| +| F4 transactional writer | Move direct shard/status mutation in `src/spec/new.ts` and `src/cli/done.ts` behind the transaction engine. Remove forwarding-only or second-writer logic once existing commands are thin domain adapters; retain 0.1 reading, not a second write authority. | +| F5/F6 proof compiler | Remove duplicate reference normalization, binding joins, and proof-closure calculations after detectors, report paths, and attestation consume the shared implementations. Keep legacy references as input compatibility, not as a parallel proof model. | +| F8 GraphIR cutover | Retire graph v1 identities, reverse-index materialization, undirected/repeated traversal, and tests that assert those obsolete internals. CLI, MCP, report, impact, working-set, export, and viewer paths become queries or serializers over GraphIR; no v1 adapter survives. | +| F9 context-envelope implementation | Revise the F-041/F-063 contracts that currently protect `src/optimizer/preamble.ts` and `src/optimizer/tail.ts`, then retire those production orphans after equivalent omission, tail, budget, and fixed-point behavior is covered by the envelope packer. | +| F10 task-state loop | Retire hard-coded developer/reviewer identity choreography and loop-only mock/stub dispatch after topology invariance and real evidence ingress are green. Product transport fallbacks remain until their separately owned compatibility contracts are intentionally revised. | + +A path may survive only when it still owns a distinct public contract or compatibility obligation; that owner and exit condition are recorded in the feature rationale or test. There is no permanent retirement manifest and no separate cleanup-only release phase. + +Each cycle follows simulation → implementation → verification → `clad done`. Edit feedback compiles without subprocess work; checkpoint checks changed inputs; `clad done` authoritatively checks the proven impact closure and escalates unknown scope to the whole repository. Push and release remain integration boundaries. Exact-digest results may be reused; background work never writes lifecycle or attestation state. Rebuild plugin mirrors before completion and still run contributor push checks. + +## D16 — Acceptance gates + +Corpus gates compare sorted semantic records, not hand-maintained totals. The independent scanner snapshot stores owner, composite criterion address, channel, raw reference, normalized target, selector, resolution state, source path, YAML path, and source range. Occurrence count, unique-address count, and resolved/unresolved count are separate derived views. An intentional corpus edit updates the reviewed record diff; it must not require editing a second literal total in the assertion. Dated totals remain below in [Evidence snapshot](evidence.md#evidence-snapshot) as design evidence rather than permanent acceptance constants. + +### Compatibility and grammar + +- Every feature and criterion record in the canonical self-corpus snapshot compiles as 0.1 with an identical owner, address, source locator, and unchanged blocking result. +- New strict parser fixtures cover five patterns, compound ordering, comma rules, a single modal, negation, protected spans, fragments, and unbalanced spans. +- `ATOMICITY_RISK` reports its observed advisory signals on a one-modal multi-obligation control, while a long atomic control proves that any heuristic false positive remains non-blocking under strict mode. +- Property tests show the legacy scanner returns `parsed | opaque | conflict` and throws zero times for arbitrary strings. +- Unknown schema versions hard-fail before artifact merging. +- `spec.yaml#schema` alone selects workspace schema. Child `schema` fields and mixed 0.1/0.2 spellings are rejected in 0.2; independent `receipt_schema` dispatch cannot switch workspace interpretation. + +### Migration + +- One changed node loses only its own exemption; unrelated criteria in the same feature remain grandfathered. +- Every legacy test-reference record in the canonical snapshot survives in the baseline without changing its raw address or fabricating a selector. +- Live binding and baseline fallback never union for one criterion. +- Capability edge cutover proves `L = N` before schema switch. +- F3 performs that proof without disk cutover; only F4's journaled apply may remove legacy child `schema`/`source`, write new edges, and switch the root schema. +- A feature with no legacy capability edge receives an explicit empty `capability_refs`; suggested candidates never enter the applied candidate without human confirmation. +- Unresolved preview and normal failure write zero bytes. +- Crash recovery is byte-exact, and a second successful apply is zero-diff. +- The F7 self-migration proves preview-on-0.1, human resolution, one atomic apply, post-switch `clad done`, and pure-0.2 v3 output; F8 begins from that real 0.2 tree. + +### Binding and proof + +- Adapter fixtures prove source carrier → normalized selector → JUnit testcase → composite criterion round trips. +- Bare IDs produce no bindings; unknown addresses block. +- Unrelated same-file passes, skipped-only cases, mixed pass/fail, and multiple valid bindings follow D11 exactly. +- A fresh preregistered benchmark contains nine valid bindings plus unbound, unknown, unrelated, skipped, and failing injections. Do not call it a reproduction of the old 34-feature run without its raw fixture. +- A verified criterion Audit receipt satisfies only its matching Audit obligation. One verified feature UAT receipt may satisfy all matching UAT obligations only when its signed matrix contains every current composite criterion and every row plus both feature checks passes; a verified blind receipt may satisfy independence but never UAT. +- A generic MCP `blind: true`, a free-form human claim, same-author review, and receipt-free legacy evidence do not gain verified 0.2 status. +- With project scenario policy `required`, every scenario referencing the parent feature contributes its ID and intent fields to every criterion subject in that feature. Changing that set or intent stales those receipts; `off | advisory`, unrelated scenarios, and sibling criteria do not. No receipt transfers to another address or hash. +- Receipt fixtures verify RFC 8785 serialization, the `cladding.receipt/1` length-prefixed domain frame, detached Ed25519 signatures offline with a pinned out-of-workspace key, full-digest filenames, subject-derived feature directories, create-only writes, and exact revocation. Missing trust or a network-only verifier is unresolved/asserted and never GREEN proof. +- Bare TTY and pseudo-TTY signoff, hand-written receipt YAML, caller strings, and OS/git identity remain asserted. Registered host elicitation/external signing produces a portable verified human receipt; absent that adapter, verified signoff returns `HUMAN_REQUIRED`. +- A human receipt becomes stale when its subject, reviewed inputs, complete runtime-dependency byte/sentinel closure, implementation-author set, trust snapshot, or signature changes. A direct feature-module edit is only one member of that closure. Two branches adding different content-addressed receipts merge with both files preserved; deleting one is possible only through explicit revocation. +- A blind receipt contributes independence only with a current matching testcase pass and never clears UAT. A past pass, skipped-only observation, or unexecuted generated test is insufficient. + +### Transactions and attestation + +- `feature.begin` covers `planned | blocked | done → in_progress`, idempotent `in_progress`, archived/unknown refusal, and a begin-plus-intent-edit batch. Every successful transition has exactly one recoverable pre-batch checkpoint; every refusal or interrupted rollback leaves the shard, inventory, and event stream at the specified boundary. +- Schema 0.2 `clad done` rejects every source status except `in_progress` without running a completion transition; the 0.1 compatibility fixture preserves shipped behavior. +- Parallel different-shard edits both succeed; same-shard second edit is stale; lock timeout is BUSY and writes nothing. +- A matching `context_revision` cannot authorize a write with stale `input_revisions`; same-session delta reuse and region write concurrency are tested independently. +- Inventory-region writes do not stale project-region revisions. +- Attestation matrix covers contract, implementation module, out-of-module test, runner configuration, oracle, evidence, capability outcome, architecture constraint, notes, and required/advisory scenario changes. +- Only the intended feature set becomes stale for a shared rule/outcome/scenario change. +- F6's pre-migration v3 fixture serializes exact schema-0.1 contract nodes without invented purpose/kind; F7 migration intentionally stales them and rewrites pure-0.2 hashes. +- Lifecycle operations cover block reasons, terminal archive/no-unarchive policy, proof-ref edits, exact receipt revocation, and `set_links` omitted-versus-empty semantics. Both bulk link replacement and dependency promotion reject self, duplicate, unresolved, and cyclic dependency states. + +### Assurance profiles and cadence + +- The same compiled obligations yield identical authoritative verdicts through legacy stage projection, profile execution, and split/merged runner adapters. +- Fixtures distinguish upstream `hard | report` strictness from effective Cladding blocking: a reporting failure completes the standard report but remains RED under the default strict profile, while a missing report is unresolved in both. Shipped `advisory` remains a distinct blocking disposition. +- Feedback is non-authoritative and bounded to pure checks; completion is feature/impact scoped; push is branch-integration scoped; release is whole-repository. Required unobserved, timeout, pending environment, or stale results never become GREEN. +- Authored and observed dependency edges both invalidate proof. Ambiguous edges fan out to every candidate; dynamic, unresolved, unowned, or otherwise incomplete scope escalates the affected layer to whole-repository verification. +- A/B/C replay compares every-edit full execution, tiered foreground execution, and tiered plus background execution. Authoritative verdicts must match, stale background PASS promotion is zero, injected required defects are caught before completion, and active wait falls by at least 50% without increasing foreground p95 by more than 10%. +- Background adapters require isolated outputs and cooperative cancellation, run single-flight per worktree, yield to foreground, and publish only exact-digest cache entries. CI/release ignores local background cache. + +### Graph validity and bounded retrieval + +- Every authored Spec 0.1/0.2 fact round-trips through GraphIR without changing its owner, address, selector, or provenance. +- The independent scanner and GraphIR return identical prerequisite, dependent, artifact-owner, criterion-proof, and regression record sets across the self corpus and generated fixtures. +- A path with several roles is one artifact node; a path with several feature owners never chooses one owner silently. +- Unknown, unresolved, stale, skipped, and unobserved are explicit states, never empty-success aliases. +- Impact fixtures seed traversal from predicted paths before editing and observed diff paths afterward. Unknown scope remains incomplete; expanding every declared feature module is a negative control and must not become the default. +- Fixture, command, file, test, oracle, and evidence references preserve their raw authored spelling as well as their normalized target and resolution state. No missing target is silently aliased to a nearby artifact. +- The `clad_get_graph` payload measures the final serialized response, including schema metadata and omission counts. It never defaults to an unbounded walk. +- Context fixtures independently account for payload, resident, and total UTF-8 bytes, prove `total = payload + resident`, label cold/warm/unknown cache state honestly, and remeasure after budget metadata reaches a fixed point. +- The sorted full MCP list surface and each task-profile challenger have separate reviewed byte ratchets and explicit serialization scope. A task payload ceiling cannot be reported as a total-context ceiling, and an unproven host cache cannot be reported as warm. +- Task-profile ceilings are enforced on fixed-point serialized bytes: 16 KiB for spec-edit/verify/observe and 24 KiB for implement/blind-oracle. Spec-edit fixtures derive required content from the typed operation; no global migration corpus is sent as a task packet. Every self-corpus case has `required_overflow: false` without truncation. +- Verification packets retain criterion, state, selector, and locator/digest before optional failure-first diagnostics. A `diagnostic_cursor` works only in the same session/revision and is neither a public graph cursor nor a `clad_get_context` v1 change. +- Removing criterion nodes, selectors, provenance, direction-aware traversal, purpose, or rationale must make its corresponding ablation fixture fail. +- A 5,000-feature synthetic graph remains linear in nodes plus edges and completes within the repository's existing 15-second CI scale ceiling. Reference targets are at most 500 ms for a cold self-corpus compile plus graph build and 50 ms for a warm focused projection; report these as environment-specific benchmarks, not portable guarantees. + +### Preregistered 37-case contract matrix + +These are new test obligations, not evidence that an earlier session ran “37/37.” Each ID maps to exactly one test title: + +- **P01–P10 parser:** ubiquitous, event, state, optional, unwanted+then, compound order, negation, protected modal, multiple-modal rejection, fragment/unbalanced rejection. +- **L01–L04 legacy:** parsed, opaque, conflict, arbitrary-input no-throw. +- **B01–B06 baseline:** title/purpose trigger, criterion add/remove, statement/kind trigger, rationale/constraint trigger, non-trigger fields, sibling-exemption/live-baseline exclusivity. +- **C01–C06 covers:** bracket token, bare ID ignored, unknown address, unrelated same-file, skipped-only, failure dominance/pass verification. +- **T01–T04 transaction:** different shards, same-shard stale, BUSY no-write, crash recovery. +- **U01–U04 upgrade:** unresolved no-write, `L = N`, atomic apply/zero-diff, interrupted restore-or-finish. +- **A01–A03 attestation:** selective contract stale, proof-input stale, target-versus-sibling receipt freshness. + +### Repository gates + +- Run `npm run validate:spec-0.2`; a `fail` blocks immediately, while each feature/release boundary explicitly promotes the pending/not-run scenarios it owns to required pass conditions. +- Keep the D24 MCP scenarios distinct: deterministic wire/semantic/mutation parity and one reference-host full Spec 0.2 cycle block 0.10.0 release; multi-host adoption and token advantage do not. +- Keep J01–J13 and AB01–AB12 unique in the executable ledger. A passing model simulation may challenge a design alternative but cannot satisfy a journey labelled implementation-pending or not-run. +- Commit all 37 preregistered fixtures before using “37/37” as evidence; self-consistency rejects missing, duplicate, or unmapped IDs. +- Build the committed plugin mirrors before F1 completion and require the build to produce no uncommitted mirror drift after regeneration. +- Use `clad done` as the one authoritative feature-completion strict gate and attestation refresh; do not duplicate the same full gate on an unchanged tree. +- Run `npm test`, `npm run typecheck`, `npm run lint`, and `node bin/clad check --tier=pre-push --strict`. +- Register newly shipped public terms in the glossary and keep detector-count/self-consistency checks green. +- Run the D19 A–E topology/context suite as F9 acceptance and prove that removing general persona prompts changes neither contract, deterministic gate, verdict, nor stale scope. F5 fixtures must accept valid portable receipts and reject bad signatures/trust; F9 must add real signed human/blind production paths while preserving the asserted fallback. + +### Supersession, documentation, and test gates + +- Compare the post-cutover source graph with artifact-registry entrypoints, package/bin/hooks, runtime lookups, generated consumers, and spec-owned paths. A production orphan is either removed in the same cycle or has an explicit contract owner and retirement boundary; it never enters an anonymous permanent allowlist. +- Prove that no consumer imports or reconstructs the superseded authority. Compatibility readers may remain, but compatibility must not preserve a second writer, closure, graph identity, or traversal implementation. +- Replace tests of retired internals with contract, oracle, property, or adapter tests before deleting them. Test count and line count may decrease; preserved behavior and fault discrimination may not. +- When the reported public test total changes, regenerate its canonical claims through `scripts/test-count.mjs --write` instead of hand-editing copies. +- Keep this continuation router at or below the 8 KiB hard ceiling and each routed design owner at or below 24 KiB. Maintain a 7.5 KiB operational router ratchet for editing headroom. D01–D24 each have exactly one normative owner, former router anchors remain navigable, and repository-relative links resolve. +- Measure fresh-session context as exact UTF-8 bytes for `AGENTS.md` + router + current task owner documents. Evidence and decision history are on-demand inputs, not a default payload; compare this routed baseline with the former monolith before claiming token savings. +- If `_doc-links.yaml` changes shape, its writer, extractor, GraphIR reader, `DOC_REFERENCE_INTEGRITY`, sync idempotence, and downgrade/regeneration fixture cut over together. + + + +D18 moved to [Implementation hygiene](hygiene.md#d18--implementation-hygiene-and-documentation-surface); this pointer is non-normative and preserves the former owner-document anchor. diff --git a/docs/design/spec-0.2/evidence.md b/docs/design/spec-0.2/evidence.md new file mode 100644 index 00000000..c9336183 --- /dev/null +++ b/docs/design/spec-0.2/evidence.md @@ -0,0 +1,198 @@ + + +# Spec 0.2 — evidence snapshot + +> Canonical measurement record supporting the accepted design. Return to the [Spec 0.2 continuation router](../spec-0.2.md). + +## Evidence snapshot + +All values below are **verified measurements** of the repository on 2026-08-28 unless labelled otherwise. Re-run them at implementation start; they are a design snapshot, not permanent invariants. + +### Reproduction rules + +- Corpus counts parse the sorted YAML shards and count typed records, never filenames or grep hits as semantic objects. F1 replaces the ad-hoc script with its committed independent scanner snapshot. +- Source `@see` counts use the exact pattern `@see\s+spec/features/` over `src/**/*.ts`; broader `spec/features/` searches intentionally find unrelated compiler references. +- A legacy test ref is resolvable only when its normalized repository path exists; occurrences and unique targets remain separate measures. +- Module fan-out parses each feature's `modules` list, normalizes paths, and counts distinct feature owners per path. +- Role-brief measurements include the six role Markdown files under `src/agents/` and exclude `src/agents/README.md`. + +Any implementation-start difference is reviewed as a sorted record diff with a per-record cause; do not reconcile it by changing a literal expected total. + +### Intent and AC representation + +| Measurement | Value | Design consequence | +|---|---:|---| +| Feature shards | 281 | Migration and compatibility corpus. | +| Acceptance criteria | 1,118 | Migration and grammar corpus. | +| Feature `purpose` | 0/281 | A typed WHY home is genuinely absent. | +| ACs with `notes` | 306/1,118 (27.4%) | WHY leaks into an optional non-normative field. | +| Features with `design_impact` | 20 | Keep governance metadata but do not confuse it with default WHY. | +| Scenarios | 2 | Both lack actor in the 0.1 model. | +| ACs with `text` | 1,118 | `text` is the only universal migration source. | +| ACs with `action` | 830 | Structured fields are incomplete. | +| ACs with `response` | 839 | Structured fields are incomplete. | +| ACs with `condition` | 543 | Structured fields are incomplete. | +| Text-only ACs | 130 | Deriving text from structured data would lose contracts. | +| Current structured rendering matching text | 44/830 | Do not regenerate legacy statements from the triplet. | +| One `shall` modal | 1,023 | Most current text is close to atomic surface grammar. | +| Multiple `shall` modals | 95 | Strict-new must reject; legacy remains grandfathered. | +| Mixed-polarity multi-modal | 21 | The earlier 98.2% acceptance figure mainly tolerated positive compound modals and is not the strict-new target. | +| Simple pattern/leading-keyword conflicts | 22 | Do not freeze a claimed 19 without explicit per-case opaque exclusions. | + +The current [`src/spec/ears.ts`](../../../src/spec/ears.ts) validates legacy `condition` fields and uses a different complex ordering from the upstream grammar. It is evidence for version dispatch, not the implementation to reuse unchanged. + +### Identity and sharding + +| Measurement | Value | Design consequence | +|---|---:|---| +| Distinct AC ID strings | 742 across 1,118 ACs | AC IDs are feature-scoped. | +| AC occurrences using a duplicated ID | 402 | External binding requires the composite address. | +| `AC-001` occurrences | 72 | Bare-AC test binding would be unsafe. | +| Feature body slugs | 201 | All 201 match their filename slug. The redundant body field can be removed mechanically when the filename is valid. | +| Legacy `F-NNN.yaml` shards | 80 | Continue 0.1 reading. | +| Six-hex filename shards | 95 | Continue 0.1 reading. | +| Eight-hex filename shards | 106 | Production already emits the target form. | + +Current production emits eight hex characters in [`src/spec/new.ts`](../../../src/spec/new.ts), while older docs still name hash6. The ID registry must become the single description source. + +### Compiler bootstrap and reference resolution + +Dependency counts below use Madge's resolved import graph. “Production” scans `src`; “repository TypeScript” scans `src`, `tests`, and `conformance`. They measure dependency centrality, not a prediction that every dependent file must be edited. + +| Measurement | Value | Design consequence | +|---|---:|---| +| `src/spec/types.ts` production dependents | 56 direct; 95 transitive | Replacing the central type surface before parity has a wide production blast radius. | +| `src/spec/load.ts` production dependents | 33 direct; 58 transitive | Keep the first compiler additive instead of cutting over the loader. | +| `src/spec/types.ts` repository-TypeScript dependents | 85 direct; 265 transitive | Tests and conformance substantially enlarge the validation surface. | +| `src/spec/load.ts` repository-TypeScript dependents | 52 direct; 189 transitive | Loader replacement belongs after compiler parity, not before it. | +| Registered default drift detectors | 41 | Count entries in `allDetectors`. The directory has 44 TypeScript files: 41 detector modules, `index.ts`, and the two shared helpers `with-spec.ts` and `spec-first-window.ts`; file count is not catalog authority. | +| Source-locator prototype | 281/281 features; 1,118/1,118 criteria; zero parse errors; 60.96 ms | A provenance-bearing envelope is feasible over Spec 0.1 without changing `loadSpec`. | +| Evidence-reference occurrences | 349 total: 281 file-style, 52 `fixture:`, 16 `self-dogfood:` | Keep channel and raw spelling; one generic path counter is insufficient. | +| Fixture addresses | 52 occurrences; 45 unique; all registry-resolved | `conformance/fixtures.yaml` can supply stable anchors without inventing proof observations. | +| Self-dogfood addresses | 16 occurrences; 14 unique; 13 exact package-script targets; one unresolved unique target | Missing commands remain explicit; nearby command names are not aliases. | +| File-style evidence addresses | 281 occurrences; 280 resolve; `agents/` is the one unresolved occurrence | Existing paths and unresolved declarations must both survive compilation. | + +These totals are dated observations. Acceptance derives them from the sorted record snapshot, where occurrence, unique address, and resolution status cannot be conflated by grep strategy. + +### Iron Law cadence and assurance invalidation + +The dated cadence, invalidation, and A/B/C fault-equivalence measurements moved to [Assurance evidence](assurance-evidence.md#iron-law-cadence-and-assurance-invalidation). This heading preserves the former evidence anchor; normative profile behavior remains in D21–D23. + +### Test binding and attestation gap + +| Measurement | Value | Design consequence | +|---|---:|---| +| Non-pseudo legacy test refs | 1,208 | Migration population. | +| Refs without `#selector` | 803 | A testcase cannot be selected honestly by automation. | +| Refs with selector | 405 | Candidate population for exact adapter migration. | +| Simple exact source-title matches | 372 | Upper-bound candidate under a literal Vitest/Jest scan, not proof of framework round-trip. | +| Resolvable legacy test-ref occurrences | 1,202 | Six of 1,208 occurrences currently do not resolve. | +| Existing refs outside owner `modules` | 1,025 | Current module-only attestation misses most test changes. | +| Features with an outside-module test ref | 190 | Verification digest must be feature-specific. | +| Resolved file-style evidence refs | 280 of 281 occurrences | Evidence content needs its own freshness closure; unresolved refs remain visible rather than disappearing from the graph. | +| Resolved file evidence outside owner modules | 58 | Module hashing does not protect it. | +| Done features with a declared proof address on every AC | 277/277 | Declaration completeness only. | +| Current attestation feature markers | 277 | Last-GREEN module signature only, not case-level proof. | + +The current [`src/spec/attestation.ts`](../../../src/spec/attestation.ts) hashes module bytes, and [`src/stages/junit-report.ts`](../../../src/stages/junit-report.ts) aggregates results per file rather than selector. D11 and D13 close both gaps. + +### Knowledge graph fidelity, query, and token simulation + +The following audit used the same 281-feature/1,118-criterion corpus. “Prototype” means an in-memory read-only model of the proposed identities and relations, not shipped GraphIR v2 behavior. + +| Measurement | Value | Classification / consequence | +|---|---:|---| +| v1 dependency facts represented | 249/249 | Verified measurement; coarse forward links work. | +| v1 affected-path facts represented | 1,071/1,071 | Verified measurement; `touches` works at feature level. | +| v1 criterion facts represented | 0/1,118 | Verified measurement; v1 is not a contract graph. | +| v1 criterion-proof facts represented | 0/1,557 | Verified measurement; file-level feature edges lose proof ownership. | +| Existing source `@see` references | 47 total; 41 resolve, 6 break | Verified measurement; strict provenance resolution detects real drift. | +| Prototype prerequisite queries | 281/281 exact | Prototype result. | +| Prototype impact queries | 281/281 exact | Prototype result. | +| Prototype criterion-proof queries | 281/281 exact | Prototype result. | +| Prototype regression queries | 281/281 exact | Prototype result. | +| Prototype artifact-owner queries | 427/427 exact | Prototype result. | +| Compact semantic manifest | median 503; p95 871; max 2,702 estimated tokens | Projection with simulated one-sentence project/feature purposes; not a migrated-corpus measurement. | +| v1 raw depth-2 neighborhood | 124/281 above 3,000 estimated tokens | Verified simulation; generic BFS is not a safe LLM default. | +| v1 unbounded neighborhood | median about 72k estimated tokens | Verified simulation and consistent with the shipped server's ~285 KB/~70k note. | +| Prototype v2 lower bound | 2,086 nodes; 4,200 edges | Projection before every future anchor/rule is present. | +| Prototype build | 98.39 ms cold graph phase; 27.07 ms warm median after a 134.9 ms spec load | Environment-specific projection over about 7.9 MB of readable tracked input. | + +The token simulation found a concrete packing failure before correction: content fitted under the limit, then per-path omission metadata raised a packet above it. The accepted packer therefore aggregates omissions and remeasures the final response, including its own budget fields, until the estimate is stable. + +Ablations justify the model rather than merely decorating it: removing criterion nodes loses all 1,118 criterion identities; reducing test selectors to paths loses 405 selector citations; undirected depth-2 traversal creates the measured token overflow. The implementation must turn these into committed fixtures before treating the prototype results as product evidence. + +### Role contract, cycle context, and evidence ingress + +The role-contract architecture is shipped, while task-keyed projection and full-envelope accounting are not. Measurements below use exact UTF-8 bytes and a named `characters / 4` token estimator. They include the current role prompt plus serialized feature packet but exclude provider-owned hidden instructions and later tool traffic unless stated otherwise. + +| Measurement | Value | Classification / consequence | +|---|---:|---| +| Canonical role-brief Markdown | 6 files; 27,420 bytes; 406 lines | Verified measurement; excludes the directory README. Preserve unique role contracts, but remove repeated choreography only through canonical-source edits. | +| Current developer role body | 4,955 bytes; about 1,229 estimated tokens | Verified measurement; stable prompt prefix is a material cold-input component. | +| Current reviewer role body | 4,521 bytes; about 1,118 estimated tokens | Verified measurement. | +| Current per-dispatch wire across 281 features | p50 7,977 bytes; p95 11,392; max 27,848 | Verified measurement; developer and reviewer receive the same full feature JSON rather than task-specific projections. | +| Current two-dispatch headless cycle | p50 15,836 bytes; p95 22,460; max 55,286 | Verified measurement; excludes subsequent tool results and MCP catalog residence, so it is a lower bound on physical cycle context. | +| Current serialized feature body | p50 about 704; p95 1,507; max 5,454 estimated tokens | Verified measurement under the `characters / 4` estimator. | +| Prototype 0.2 contract-only two-task aggregate, legacy notes retained | p50 13,044 bytes; p95 15,978; max 35,976 | Combined implement+verify projection lower bound, not a per-task ceiling result; proof, impact, catalog, and retry sections are incomplete. | +| Prototype strict two-task aggregate without legacy notes | p50 12,412 bytes; p95 14,698; max 27,854 | Combined implement+verify projection lower bound. The former content-class ceiling wording was ambiguous; D19 now judges each task profile independently. | +| Conservative full-feature `spec-edit` envelope | max 12,186 bytes (`F-0f4dd6`) | Read-only projection including feature contract, parsed architecture, project intent, relevant capabilities, revisions, write scope, and budget metadata; below the 16 KiB class limit. | +| Criterion-target `spec-edit` envelope | max 1,473 bytes | Read-only operation-scoped projection; supports deriving required content from the write set rather than shipping a whole feature unnecessarily. | +| Focused independence/oracle regression suite | 46/46 tests passed | Verified run over independence, done-policy, oracle recording, and spec-conformance contracts. | + +The two `spec-edit` projections enumerate every feature and criterion target, construct a fixed-key compact JSON envelope from the operation read/write set, include all fields named in the table, and measure `Buffer.byteLength(JSON.stringify(envelope), 'utf8')`; the reported value is the maximum record. F9 converts this read-only method into a committed fixture before treating the ceiling as a product result. + +### Single-cycle simulation: working-set assembler + +The read-only design simulation used the live `F-06dfdad6` working-set feature because it combines six legacy criteria, two prerequisites, three affected paths, eleven legacy test references across three test files, shared-module ownership, and no authored capability edge. The proposed 0.2 delta added purpose `Give an implementation agent the smallest complete, impact-aware context needed to change a feature safely.` and criterion `F-06dfdad6/AC-1b7e4a2c`: `When a task context envelope is serialized, the system shall report its exact UTF-8 byte length after budget and omission metadata are included.` Its test carrier was `[covers:F-06dfdad6/AC-1b7e4a2c] reports final UTF-8 bytes after omission metadata`. These values are fixture inputs, not authored corpus changes. + +| Observation | Value | Classification / consequence | +|---|---:|---| +| Spec only | RED | Prototype lifecycle result; no proof declaration exists. | +| Source `[covers:]` without observation | RED / unverified | Declaration is not execution. | +| Matching JUnit pass plus tool evidence | Normal strict GREEN; `independence_policy: require` remains RED; UAT remains RED | Same-author proof is self-certified. | +| Verified blind receipt added | `require` GREEN; UAT remains RED | Isolation can establish independence but cannot impersonate a human channel. | +| Verified human receipt added | Full GREEN | Human ingress closes the human-only boundary. | +| Bound JUnit failure injected | RED in every topology | Explicit failure dominates positive independent evidence. | +| Current developer+reviewer reconstructed task payload | 22,476 bytes | Verified serialization of the selected current fixture; excludes resident catalogs. | +| One general-agent same-session task payload | 5,099 bytes | Prototype lower bound, not product efficiency evidence. | +| Separate general implement+verify task payloads | 7,652 bytes | Prototype lower bound. | +| Restricted blind packet | 580 bytes | Prototype; implementation content leakage was zero. | +| Related-file reconstruction upper bound | 57,849 bytes | Prototype legacy-reconstruction comparator. | +| Historical generic-MCP tool subset | 22 tools; 27,928 bytes | Earlier in-memory serializer; retain as dated evidence, not as the current full list-surface cost. | +| Historical generic-MCP prompt subset | 7 prompts; 1,938 bytes | Earlier serializer; tool+prompt subset was 29,866 bytes before server instructions. | +| Current full MCP list surface | 22 tools + 3 resources + 7 prompts; 53,585 bytes | Verified 2026-08-29 by `npm run validate:spec-0.2`; canonical JSON includes schemas and annotations, but excludes server instructions and host framing. | +| Task-profile MCP challenger | 6,557–26,401 bytes | Controlled tools-only projections are 50.7–87.8% smaller than that full list bundle; host discovery, retries, and provider tokens remain unmeasured. | +| Graph projection model | directed 375 bytes; undirected 610; avoidable 235 | Same required synthetic task output; validates projection mechanics, not GraphIR v2 product efficiency. | +| Assurance cadence model | every-edit 36 units; tiered 15 | Same completion obligations and GREEN reduction; deterministic relative units, not wall-time evidence. | +| V0 validation state | 12 pass; 2 implementation-pending; 3 not-run; 2 inconclusive; 0 fail | Infrastructure and model state only. Pending runtime and host evidence are not promoted. | +| Feature-only impact seed | 3 direct dependents | Verified current graph relation. | +| Predicted write path `src/optimizer/working-set.ts` | 4 owners + 2 downstream | Prototype write-scope projection. | +| Expanding all three declared modules | 23 owner seeds + 73 downstream | Negative control; feature modules are too broad as default impact seeds. | +| Focused current tests | 53/53 passed | Verified run of working-set, code-excerpt, and server tests. | + +The stale matrix exposed one granularity defect: a receipt tied to feature `contract_sha256` becomes stale when an unrelated sibling criterion changes. The accepted `subject_sha256` repair preserves that receipt while still staling on the target criterion, referenced capability outcome, architecture constraint, or required-scenario intent. The grammar audit also found a 471-character, one-modal multi-obligation criterion and a separate long statement that a naive conjunction/length rule falsely flags. This is why one modal remains the hard grammar floor while `ATOMICITY_RISK` stays advisory. + +Repository source confirms: + +- the orchestrator and managed AGENTS text already state that the host owns execution form and that role briefs are not a permitted-agent roster; +- `computeIndependence` and `independence_policy: label | require` already derive the feature label from evidence rather than persona membership; +- the packaged `blind-author` role omits read/search/edit capabilities, but generic `clad_author_oracle` accepts caller-supplied `blind: true` and stores it directly; +- production evidence writers emit LLM/tool entries and oracle entries, but no public CLI/MCP path records an actual human criterion sign-off; +- the cycle guide still prescribes separate planner/developer/test-author/reviewer/observability steps, and the experimental headless loop still hard-codes developer and reviewer dispatches with the same feature payload. + +Existing feature rationales record that role-brief wording changed live-agent interpretation, but the raw A/B/C session transcript is not a durable repository fixture. D20 therefore retains briefs conservatively and requires the new committed persona-removal ablation before making a topology-invariance product claim. + +### Supersession and document surface + +The current production orphan scan (`npx madge --extensions ts --orphans src`) resolves four real entrypoints — `src/cli/benchmark.ts`, `src/cli/clad.ts`, `src/graph/viewer/main.ts`, and `src/spec/cli.ts` — plus two production-unreferenced optimizer files, `src/optimizer/preamble.ts` and `src/optimizer/tail.ts`. The latter are not presently deletable because F-041 and F-063 still own their behavior. This is the control case for contract-aware retirement rather than grep-only deletion. + +The measured F8/D19 supersession candidate surface is 788 source lines and 820 directly coupled test lines across graph v1, reverse-index, reverse/iterative slice, preamble, and tail files: 1,608 lines total. This is a candidate authority surface, not a promised net deletion. GraphIR, serializers, envelope code, and replacement contract/property tests will remain, so the acceptance signal is removal of duplicate models and traversals rather than a line-count target. + +Before semantic routing, this design occupied 92,189 UTF-8 bytes in one Markdown file, about 23k tokens under the deliberately named `characters / 4` estimator. The 2026-08-29 refresh measures a 7,534-byte router and twelve routed owner/evidence/validation documents below 24 KiB (4,254–23,982 bytes). A default fresh session containing the 5,161-byte `AGENTS.md`, router, and one canonical decision owner is 16,949–36,677 bytes instead of 97,350 bytes for `AGENTS.md` plus the monolith: a 62.3–82.6% physical-input reduction before host-owned instructions and tool traffic. The complete routed design set is 185,982 bytes (181.6 KiB); selective loading is the gain, not disappearance of authority. Reproduce the census with exact UTF-8 file bytes over `AGENTS.md`, the router, and `docs/design/spec-0.2/*.md`; the separate unsubmitted upstream RFC is not part of the routed target set. + +### Orphan and low-value fields + +- Corpus `adr_refs`: 0. [`REFERENCE_INTEGRITY`](../../../src/stages/detectors/reference-integrity.ts) explicitly scopes ADRs out until an ADR subsystem exists. Remove the field from new 0.2 authoring. +- Capability `surface` is parsed and written but has no material downstream decision consumer. Remove it from 0.2 rather than preserving a taxonomy for its own sake. +- Preserve `modules` and `depends_on`; their established consumers and meaning justify them, and renaming would create avoidable long-lived dual vocabulary. diff --git a/docs/design/spec-0.2/graph.md b/docs/design/spec-0.2/graph.md new file mode 100644 index 00000000..f5a14f66 --- /dev/null +++ b/docs/design/spec-0.2/graph.md @@ -0,0 +1,90 @@ + + +# Spec 0.2 — GraphIR and bounded retrieval + +> Canonical owner of D17. Return to the [Spec 0.2 continuation router](../spec-0.2.md). + +## D17 — Knowledge Graph v2 as compiler IR + +### Primary justification and guarantee boundary + +Knowledge Graph v2 is first an internal compiler representation, not an LLM feature. The Spec compiler builds one live graph that supplies: + +- `contractClosure` for attestation v3 and contract freshness; +- `subjectClosure` for criterion/feature evidence-receipt freshness without sibling over-invalidation; +- `verificationClosure` for binding, observation, oracle, evidence, and proof freshness; +- direction-aware dependency and predicted/observed write-scope impact queries; +- document and source reference resolution; +- graph export and optional bounded LLM projections. + +The closures are single implementations, not parallel graph consumers that reproduce the same joins. GraphIR, attestation, `UNTESTED_AC`/`UNVERIFIED_AC`, impact analysis, and working-set assembly must call those implementations. This is the release justification even if no agent ever invokes an MCP retrieval tool. + +The graph guarantees structural fidelity, address resolution, provenance separation, freshness inputs, and deterministic query results. It does not prove that a requirement is true, that the implementation satisfies unstated intent, that an LLM uses the graph, or that using it improves implementation correctness. + +### Why v1 cannot remain the compiler model + +| v1 defect | Verified self-corpus consequence | v2 correction | +|---|---|---| +| No criterion node | 1,118 criteria and 1,557 criterion-proof relationships cannot be represented. | Composite-address criterion nodes and criterion-level proof edges. | +| One path becomes kind twins | 95 paths materialise as more than one module/test/doc node, giving one artifact several identities. | One artifact node with several roles. | +| Undirected generic BFS | Depth 2 exceeds 3,000 estimated tokens for 124/281 feature queries; an unbounded feature query reaches about 72k estimated tokens. | Relation-specific directions, no project-hub expansion, and projection-first reads. | + +v1 remains a historical shipped design, not a compatibility target. GraphIR v2 replaces its node/edge model and graph wire representation in 0.10.0. No v2-to-v1 graph serializer is retained. + +### Canonical identities + +Semantic addresses are: + +- `project`; +- `capability:`; +- `feature:`; +- `criterion:/`; +- `scenario:`; +- `architecture_rule:`. + +Physical addresses are `artifact:` and `anchor:#`. Bare AC ids are never externally resolvable. A source line is a navigation hint, not identity. + +An artifact has any applicable roles from `spec | doc | source | test | oracle | evidence | skill | generated`. A canonical signed receipt is one immutable `evidence`-role artifact; the graph exposes its address, provenance, state, and digest rather than duplicating its body. A doc section, source reference, or testcase is an anchor on its artifact. Project membership is artifact metadata rather than a traversal edge, so inventory membership cannot turn the project node into a whole-repository two-hop hub. + +### Edges, ownership, and truth status + +GraphIR uses these relations: + +- structure: `contains`, `defined_in`, `contributes_to`, `depends_on`, `participates_in`; +- implementation boundary: `touches`, `constrained_by`, `traces_to`; +- proof: `covers`, `supports`; +- documents: `explains`, `mentions`, `links_to`. + +Every edge records `provenance: authored | derived | observed` and an owner locator. Where truth is observed, the edge also carries `resolved | unresolved | passed | failed | skipped | stale | unknown`. A test title's `[covers:F-id/AC-id]` is authored; a matching JUnit testcase is observed. They may be joined but never collapsed into one fact. A declaration with no observation is not verified, and absence in an empty ledger is unknown rather than safe. + +F1 materialises only relationships authored inside Spec 0.1 plus their structural targets. A legacy `test_refs`, `oracle_refs`, or `evidence_refs` entry becomes an authored `supports` edge with its channel, raw spelling, selector precision, owner locator, and `resolved | unresolved` state; it does not become `covers`, `passed`, or `verified`. File and registry lookup may resolve an address structurally, but it is not proof observation. Source-comment harvesting, `[covers:]` carriers, framework selectors, case-level JUnit, and executable oracle/evidence adapters land together in F5. + +The structural resolver never guesses an alias for an absent target. In the current corpus, `self-dogfood:stage:commit-postcommit` remains unresolved rather than being silently rebound to the nearby `stage:commit` script. The original reference remains available for diagnosis and later explicit repair. + +Default traversal is relation-aware. Forward prerequisites and reverse dependents are distinct operations; containment may add a parent as orientation without expanding all siblings; an artifact query fans out to every owner. Full graph export remains an explicit CLI operation. + +### Documents and source references + +The artifact registry enumerates canonical documents and classifies design/operational contracts, general/legal material, generated output, mirrors, and fixtures. Every canonical document becomes an artifact even when it has no semantic edge. Tier A/B and design/operational-contract documents require an explicit semantic binding; general/legal/generated documents require link integrity only. + +When the F5 document/source adapters land, explicit `clad-doc-links` declarations compile to `explains`; organic ids compile to non-authoritative `mentions`; tracked repo-local links compile to `links_to`; and existing `@see spec/features/.yaml AC-…` comments compile to `traces_to`. Declared references are strict after that cutover even though adding a source reference remains optional. Until then, F1 does not ingest source comments or change Spec 0.1 blocking behavior; the measured broken `@see` set is retained as an F5 negative-control fixture. + +`spec/_doc-links.yaml` remains a generated, greppable projection at its current path in 0.10.0. If its format changes to typed addresses, the extractor, writer, `DOC_REFERENCE_INTEGRITY`, GraphIR reader, sync idempotence fixtures, and downgrade/regeneration fixture change in one feature cycle. The file is never a second graph authority. + +### Public wire boundary + +`clad_get_context` is not a graph-v1 adapter. It is a separately accepted, frozen no-code context-slice contract. Keep its exact shape and `schema_version: 1` throughout 0.10; do not revise F-06dfdad6 merely to make it resemble GraphIR. Replace the server-wide version assumption with per-surface constants so `clad_get_graph` can emit graph `schema_version: 2` without changing unrelated tools. + +The 0.10.0 release-blocking outward work is the minimum atomic v2 cutover: focused graph JSON, CLI query/export, and enough renderer/viewer support that existing graph commands are not broken. The default focused graph read is depth 1 and bounded; a no-query MCP call returns statistics, not the graph. + +Richer semantic packets are a tail consumer. If implemented, they present sparse, deterministic sections in the order WHY, WHAT, constraints, affected paths, proof, impact, documents, and health. Human-readable keys remain intact; empty/default fields are omitted; criterion ids may be locally scoped under a feature. Default task projections include receipt summaries only, never signatures or full receipt bodies. Packing measures the complete serialized payload to a fixed point, including context/input revisions and omission metadata. `token` figures must name their estimator and report exact serialized bytes because provider tokenizers differ. + +### Claim ladder and rollout + +Keep three independent claims: + +1. **Structural validity** — permitted after committed fidelity, query-reconstruction, negative-control, freshness, and ablation tests pass. +2. **Retrieval efficiency when used** — permitted only after a preregistered same-host/model/settings benchmark preserves exact-address accuracy while reducing measured input. This says nothing about correctness beyond the benchmark task. +3. **Actual adoption** — permitted only when the existing pull telemetry reaches `confirmed`. A successful retrieval A/B cannot raise adoption; current recorded evidence is `not_confirmed`. + +Therefore the internal IR, closures, detectors, attestation, and minimum graph v2 wire block 0.10.0. D24's AB01–AB12 two-arm run is the bounded first experiment for a task-scoped MCP surface; a pass supports only efficiency-when-used on those tasks. A separately preregistered broader study—40 tasks is the current optional scale candidate—may generalize the GraphIR retrieval claim in 0.10.x, but neither experiment blocks 0.10.0 or proves adoption. Viewer polish, broader MCP projections, and public graph cursor/`not_modified` expansion remain independent tail work. D19's session-bound `diagnostic_cursor` is not a graph-wire exception. diff --git a/docs/design/spec-0.2/hygiene.md b/docs/design/spec-0.2/hygiene.md new file mode 100644 index 00000000..c04b404c --- /dev/null +++ b/docs/design/spec-0.2/hygiene.md @@ -0,0 +1,50 @@ + + +# Spec 0.2 — implementation hygiene + +> Canonical owner of D18. Return to the [Spec 0.2 continuation router](../spec-0.2.md). + +## D18 — Implementation hygiene and documentation surface + +Spec 0.2 implementation should leave each touched subsystem smaller in authority surface even when the feature adds capability. “Compact” means one owner per fact, direct data flow, low branching, and no adapter or wrapper that exists only to rename an unchanged value. It does not mean compressed names, hidden invariants, or deleting explanation that carries a decision. + +### Same-cycle supersession protocol + +For every non-additive feature cycle: + +1. inventory the code, document paragraphs, generated surfaces, and tests whose authority the feature may replace; +2. implement one replacement owner and prove its parity or intentional behavior delta independently; +3. cut every in-scope consumer to that owner; +4. run the deletion proof below and classify each candidate as `retire` or `retain-with-contract`; +5. delete retired code, forwarding wrappers, duplicate normative prose, and implementation-coupled tests in the same cycle; +6. consolidate the surviving tests around public contracts and failure modes, then run the feature and repository gates. + +Do not optimize toward monotonically rising or falling LOC, file count, or test count. Optimize toward zero duplicate authority, zero unjustified production orphan, and the smallest test set that still distinguishes every governed failure. Record the disposition in the feature rationale or tests rather than creating another lifecycle ledger. + +### Code and comments + +- Keep the first compiler additive under `src/spec/compiler/`; do not mix parity work with an early replacement of `src/spec/types.ts`, `src/spec/load.ts`, graph v1, or detector inputs. +- Extract a helper only when it removes repeated policy or gives a stable test seam. Prefer a local expression over a one-use forwarding abstraction. +- Document exported behavior and non-obvious invariants. Use only applicable `@param`, `@returns`, `@throws`, and `@see` tags; do not add empty or restated tags to satisfy a visual template. +- Comments explain why, ownership, trust boundaries, or failure behavior. Delete comments that merely paraphrase the following code. +- A feature may clean adjacent code that shares its authority or execution path. Broad style rewrites and unrelated renames stay outside the feature cycle. + +### Canonical documents and mirrors + +- A policy has one canonical Markdown source. Entry documents give a compact summary and link to it rather than copying its full rule set. +- The six role-brief Markdown files under `src/agents/` (excluding its README) remain canonical sources; host/plugin copies are generated mirrors and are never edited independently. +- Role briefs retain unique scope, outcome, evidence, capability, and Soft Shell contracts. Repeated choreography and generic coding advice move to the shared cycle/style authority. +- Managed blocks such as the AGENTS persona map are owned by the artifact registry and regenerated through their existing producer. +- A documentation edit must preserve resolvable links, generated-mirror idempotence, glossary coverage, and the semantic edge that explains why the document exists. + +### Deletion proof + +Search results alone never prove dead code. Before deleting a source, export, script, or document, show that it has no: + +1. static or dynamic import; +2. package/bin/hook/host entrypoint; +3. spec `modules`, test, oracle, evidence, or source-reference contract; +4. generated mirror or artifact-registry consumer; +5. runtime string lookup, plugin manifest, or external compatibility obligation. + +Record the proof in the feature test or change rationale. `src/optimizer/preamble.ts` and `src/optimizer/tail.ts` are currently not production-consumed but remain protected by shipped spec contracts, so F1 does not delete them. The duplicated pseudo-reference helper in graph v1 and reverse index stays until the F8 cutover gives it one surviving owner. The measured broken `@see` set stays as an F5 negative-control until the strict source adapter repairs it. diff --git a/docs/design/spec-0.2/mcp.md b/docs/design/spec-0.2/mcp.md new file mode 100644 index 00000000..b2a77720 --- /dev/null +++ b/docs/design/spec-0.2/mcp.md @@ -0,0 +1,98 @@ + + +# Spec 0.2 — MCP boundary + +> Canonical owner of D24. Return to the [Spec 0.2 continuation router](../spec-0.2.md). Reproduce the validation projection through [Validation protocol](validation.md), not by copying measurements into this contract. + +## D24 — MCP as optional transport and bounded projection + +MCP is an adapter over the Spec compiler, transaction engine, GraphIR, and assurance reducer. It is not another specification authority, graph implementation, proof reducer, or lifecycle writer. CLI, MCP, and in-process callers must invoke the same domain operations and obtain the same semantic result for the same inputs. Turning MCP off may remove convenience and live projection, but must not change compilation, staleness, gate, or verdict semantics. + +The minimum supported MCP host needs only tools. Resources, prompts, subscriptions, and future sampling are optional accelerators. A host that ignores resource updates or never reads a prompt must still be able to complete a supported cycle through tools. A host that cannot observe `notifications/tools/list_changed` must reconnect or relist after initialization; the server advertises the capability and never assumes that a cached pre-init catalog expanded. + +### Authority and composition + +| Layer | Owns | Must not own | +|---|---|---| +| Spec compiler and GraphIR | identities, normalized contracts, closures, impact, projections | transport framing or host behavior | +| Transaction and assurance kernels | write sets, revisions, recovery, obligations, evidence reduction, verdict | MCP-specific duplicate mutation/proof logic | +| MCP adapter | discovery, schemas, bounded serialization, text/structured compatibility, transport errors | alternate facts, joins, staleness, or GREEN authority | +| Host | when to call, model/context orchestration, optional resource/prompt use | weakening domain preconditions or claiming unobserved evidence | + +Every mutating tool follows prepare/validate/apply semantics where user consent or a multi-file boundary exists. Domain errors are stable structured results and have text-content parity for clients that ignore `structuredContent`. Unknown, stale, malformed, oversized, out-of-root, unsupported, and unverified inputs fail closed without partial writes. + +### Surface roles + +The public catalog is described by operation, not persona: + +| Role | Examples | Context rule | +|---|---|---| +| Bootstrap | prepare, stage, initialize, clarify | Pre-init exposes only bootstrap tools; initialized tools arrive through list-changed/relist. | +| Inspect | feature, context, working set, impact, graph | Return bounded projections with omission and revision metadata. | +| Spec edit | create, link, resolve, future `clad_edit_spec` | Invoke the one transaction engine and declare the smallest write set. | +| Verify | check, gate, verdict, oracle | Invoke the assurance kernel; transport cannot manufacture observation. | +| Observe | events, resources, subscriptions | Optional delivery; a notification is not adoption or proof. | +| Release | changelog and release-scope reductions | Preserve the same profile and closure used by CLI/in-process entry points. | + +A code-owned `McpSurfaceDescriptor[]` with task-profile projections is a candidate consolidation, not an accepted runtime change. It may replace scattered registration only after schema, annotations, handler identity, dynamic discovery, and catalog ordering are parity-tested. Task-scoped catalogs are likewise a challenger: their byte reduction alone cannot justify a cutover if hosts fail to discover required tools. + +### Validation ladder + +The validation suite separates claims that are often conflated: + +1. **Wire conformance:** initialize negotiation, declared capabilities, schemas, list/get/call behavior, error framing, and text/structured parity. +2. **Semantic parity:** MCP, CLI, and in-process callers reach the same compiler/edit/assurance operation and normalized result. +3. **Mutation safety:** consent, path bounds, size bounds, optimistic concurrency, journal recovery, idempotent replay, and rollback. +4. **Reference-host efficacy:** one supported host completes a real Spec 0.2 spec-edit → implementation → verification → attestation cycle using MCP where routed. +5. **Efficiency when used:** an equivalent successful task uses fewer measured input/output tokens or less active time without worse correctness, retries, or omissions. +6. **Observed adoption:** agents voluntarily pull the surface across completed cycles under the existing B1 telemetry thresholds. + +Each rung requires its own evidence. Wire success does not prove end-to-end efficacy; efficacy does not prove efficiency; efficiency when forced does not prove adoption. The 0.10.0 MCP release gate is deterministic conformance, semantic/mutation parity for shipped Spec 0.2 operations, and one reference-host end-to-end cycle. Multi-host adoption and token advantage remain reported but non-blocking. + +### Preregistered MCP scenarios + +The executable ledger owns the exact IDs; these groups define their intent: + +| ID | Scenario | 0.10.0 role | +|---|---|---| +| MCP01 | handshake and capability negotiation | blocking | +| MCP02 | pre-init bootstrap, list-changed, relist/reconnect | blocking | +| MCP03 | text and structured-content semantic parity | blocking | +| MCP04 | CLI/kernel/MCP semantic parity | blocking as each 0.2 operation lands | +| MCP05 | prepare/apply replay and rollback | blocking | +| MCP06 | different-write-set concurrency and same-write-set stale rejection | blocking | +| MCP07 | malformed, oversized, traversal, symlink, and out-of-root inputs | blocking | +| MCP08 | graph/context/catalog byte budgets and omission metadata | blocking for hard ceilings; efficiency claim non-blocking | +| MCP09 | F5 receipt ingestion/offline verification and asserted fallback; F9 registered human/blind production paths | blocking at each owning feature boundary | +| MCP10 | tools-only host with resources/prompts/subscriptions absent | blocking | +| MCP11 | reference-host full Spec 0.2 cycle | blocking before 0.10.0 release, not before F1 starts | +| MCP12 | delivery-versus-pull adoption telemetry | measurement; adoption result non-blocking | + +An unimplemented operation is `implementation_pending`; an available but unexecuted host scenario is `not_run`; missing discriminating evidence is `inconclusive`. None is serialized as pass. + +### Context and token accounting + +Every deterministic measurement reports: + +- the exact controlled UTF-8 bytes after final serialization; +- the estimator name (`characters_div_4_ceiling` in the contributor harness) rather than calling it a tokenizer; +- provider-reported input, output, cached-input, and reasoning tokens when a host exposes them; +- cache state as `cold`, `warm`, or `unknown` without inference; +- payload, resident catalog/instructions, tool results, retries, and total as separate quantities. + +“Waste” is only the positive byte/token difference between two executions that produced the same required semantic output and passed the same fault checks. Large context, an unused field, or a smaller challenger is not waste by inspection. Counterfactual equivalence must be stated in the result. + +The committed deterministic comparison measures the full initialized catalog and task-profile challenger using canonical JSON bytes. It is a lower-level cost input, not provider token use. The executable ledger fixes AB01–AB12 across contract lookup, composite identity, impact/closure retrieval, purpose/criterion/capability edits, disjoint/same-shard concurrency, scoped proof, stale failure explanation, independence, and delivery-versus-adoption. A live host A/B runs those twelve tasks in two arms (current full catalog and the candidate projection), at most twenty-four host task calls. It records success, retries, missing-tool recovery, active time, provider usage, cache knowledge, fault-control detection, and resulting contract/verdict parity. If provider usage is unavailable, exact controlled bytes and wall time remain labelled estimates; no token-superiority claim is allowed. + +### Change discipline + +MCP optimizations follow challenger → simulation → acceptance → cutover. The validation command may recommend a challenger but cannot edit this owner or promote a decision. Maintainer acceptance updates this document and the central decision log first; implementation then changes the descriptor/adapter and parity fixtures together. A failed challenger leaves the existing surface intact. + +The reproducible command is: + +```sh +npm run validate:spec-0.2 +npm run --silent validate:spec-0.2 -- --json +``` + +Its passing infrastructure validates the ledger and current wire facts only. D01–D23 remain implementation-pending until their own executable evidence lands. diff --git a/docs/design/spec-0.2/model-and-migration.md b/docs/design/spec-0.2/model-and-migration.md new file mode 100644 index 00000000..5b4a9031 --- /dev/null +++ b/docs/design/spec-0.2/model-and-migration.md @@ -0,0 +1,378 @@ + + +# Spec 0.2 — model and migration + +> Canonical owner of D01–D10 and D14. Return to the [Spec 0.2 continuation router](../spec-0.2.md). + +## D01 — Objective and guarantee boundary + +LLMs should be able to maintain the spec continuously without carrying a large authoring burden or creating internal contradictions. + +The governing rule is: + +> Each governed fact and authoritative forward relationship has one canonical owner. The compiler derives inverse edges, projections, observations, and indexes on every run, validates them against their owner, and seals the contract and proof inputs that earned the last GREEN gate. + +Eliminating duplicate authority prevents structural contradiction; it does not prove that a requirement is semantically correct or complete. Cladding separately handles: + +- missing intent or evidence through detectors; +- stale code, tests, or evidence through verification and attestation; +- concurrent edits through optimistic revisions and a journaled commit boundary; +- behavioral truth through executable tests, oracles, and evidence rather than schema shape alone. + +The default authored semantic prose for a new feature is two sentences: + +1. `feature.purpose` — why the feature exists; +2. `criterion.statement` — what observable promise must hold. + +A `constraint` criterion adds one `rationale` sentence unless it references an architecture rule that already carries the rationale. IDs, titles, lifecycle state, paths, and forward links remain structural data, normally supplied or validated by tools. + +## D02 — WHY / WHAT / HOW model and terminology + +Intent is a directed graph, not a `project → scenario → feature` tree. A scenario crosses several features and may cover several capabilities. + +```text +project.purpose + └─ capability.outcome + └─ feature.purpose + └─ criterion.statement + +scenario ── cross-feature user journey +architecture / constraint ── permitted implementation boundary +``` + +| Term | Canonical question | Normative meaning | +|---|---|---| +| `project.description` | WHAT | What the project is, in one line. | +| `project.purpose` | WHY | Why the project exists and which problem it exists to solve. | +| `capability.outcome` | WHAT | The user-visible result supplied by a capability. | +| `feature.title` | WHAT label | Human label; not the requirement body. | +| `feature.purpose` | WHY | Why this feature is needed within the project outcome. | +| `criterion.statement` | WHAT | One observable, testable EARS promise. | +| `criterion.kind` | classification | `behavior`, `quality`, or solution-limiting `constraint`. | +| `criterion.rationale` | WHY | Why a constraint is necessary. | +| `scenario.actor` | WHO | The party performing the journey. | +| `scenario.goal` | user WHY/WHAT | What the actor is trying to achieve. `purpose` is not used here because it ambiguously suggests the reason the scenario document exists. | +| `scenario.success` | observable outcome | The end state that makes the journey successful. | +| `scenario.steps` | journey sequence | User/system interaction order, never an implementation recipe. | +| `architecture.rules` | constrained HOW | Boundaries implementation must not cross. | +| `modules` | WHERE | Affected paths. The field is retained; it is not an implementation narrative. | +| `depends_on` | prerequisite | Authoritative forward dependency. Soft Shell describes it as “prerequisites.” | +| `notes` | non-normative context | Explanation that cannot add a hidden requirement, exception, or proof obligation. | + +There is deliberately no generic `feature.how`. Ordinary implementation detail belongs in code. A HOW is admitted into the spec only when it is a durable constraint, a cross-cutting architecture rule, or a reviewed `design_impact` that points to the relevant design artifact. + +## D03 — Artifact layout and necessity + +### Target layout + +```text +spec.yaml +spec/ +├─ features/-.yaml +├─ scenarios/-.yaml +├─ architecture.yaml +├─ capabilities.yaml +├─ evidence//.yaml +├─ generated/ +│ ├─ README.md +│ └─ migration-baseline-0.1-to-0.2.yaml +├─ index.yaml +├─ _doc-links.yaml +└─ attestation.yaml +``` + +- Do not add tier-named directories. Tier is mutable governance metadata; feature, scenario, and architecture are stable semantic domains. +- `generated/` names an operational property: every byte is machine-written. It is not a synonym for governance Tier C. +- `evidence/` stores externally issued, content-addressed proof receipts. They are machine-issued but cannot be regenerated from workspace state, so they do not belong under `generated/`. +- Name the migration receipt `migration-baseline-0.1-to-0.2.yaml`, not `schema-upgrade-0.2.yaml`; the file stores a project-specific baseline, not the upgrade algorithm. +- Generate `generated/README.md` from the artifact registry. It must never become another hand-maintained policy source. +- Keep `index.yaml`, `_doc-links.yaml`, and `attestation.yaml` at their current paths in 0.10.0. Introduce registry aliases before moving them in 0.11. + +### Why each artifact remains + +| Artifact | Canonical role | Presence | +|---|---|---| +| `spec.yaml` | Schema anchor, project identity, policies, and generated inventory region. | Required. | +| `features/` | One independently mergeable behavior contract per feature. | Scaffolded; shards appear with features. | +| `capabilities.yaml` | Stable catalog of user outcomes. Feature links live in feature shards. | Scaffolded; may be empty during early onboarding. | +| `architecture.yaml` | Enforced layers and solution constraints. | Scaffolded; may be empty until architecture is known. | +| `scenarios/` | Cross-feature user journeys. | Governed by `scenario_policy`. | +| `evidence/` | Immutable, signed proof receipts keyed by feature and receipt digest. | Created only by a registered evidence channel. | +| `generated/migration-baseline-0.1-to-0.2.yaml` | Immutable-by-tool legacy exemption and binding receipt. | Only for upgraded 0.1 projects. | +| `index.yaml` | Committed lookup projection for sharded specs. | Generated when sharded. | +| `_doc-links.yaml` | Committed document-link projection. | Generated when document declarations exist. | +| `attestation.yaml` | Last-GREEN verification signature. | Generated only by a qualifying GREEN gate. | + +`docs/project-context.md` remains outside `spec/`. It expands audience, problem context, scope, constraints, and trade-offs, but does not independently redefine the normative project purpose. + +## D04 — Identity and sharding + +- New IDs use `F-<8hex>`, `AC-<8hex>`, `S-<8hex>`, and `AR-<8hex>`. +- An AC's complete external address is `F-id/AC-id`. An AC ID alone is feature-scoped and cannot bind a test. +- New feature and scenario files use `-.yaml`. +- The filename is the canonical slug owner. Schema 0.2 removes `slug` from feature/scenario bodies, and the compiler injects it into IR from the source path. +- The body `id` is the logical graph address. Its hash must equal the filename suffix; this is an address checksum, not a second slug owner. +- A title may change without moving the path. The path slug is a stable locator, not a derived copy of the latest title. +- The 0.1 reader continues to accept legacy sequential and six-or-more-hex IDs. New writers emit eight hex characters only. +- Put ID policy, examples, schema descriptions, and MCP tool wording behind one executable ID-policy registry so the current `hash6`/`hash8` documentation split cannot recur. + +## D05 — Project contract + +### New or human-edited 0.2 target + +```yaml +schema: "0.2" +project: + name: cladding + language: typescript + description: Reference implementation of the Ironclad harness. + purpose: Make AI-coupled development measurably safer and more honest. + assurance_level: L2 + scenario_policy: advisory +``` + +- Require `project.purpose` for new 0.2 projects. +- `intent_summary` is a 0.1 source field, not a 0.2 alias. The 0.2 validator rejects it. +- During migration, exact-copy `intent_summary` into a proposed `purpose` when it exists. A project without one receives a node-level legacy exemption until its project intent is edited. +- Exact-copy means the complete current value, including any detector-count or policy suffix. A human may revise the proposed purpose before apply; the example above is such a reviewed target, not a byte-for-byte rendering of this repository's 0.1 value. +- Writers persist `scenario_policy`; they do not rely on an invisible default. +- Writers persist `assurance_level`. New 0.2 projects start at `L2`; the level selects the cumulative proof obligations a completion must satisfy, as defined by [D21–D23](assurance.md#d21--iron-law-assurance-kernel). +- Migration does not infer an assurance claim from the old stage layout. Preview proposes `L2`, the operator confirms or changes it, and unresolved policy writes nothing. +- Retain existing project runtime, oracle, independence, deliverable, smoke, and AI-hint policies unless a dedicated feature changes them. + +`docs/project-context.md` must point to `spec.yaml#project.purpose` as the normative one-line purpose. Its prose may explain the evidence and trade-offs behind that purpose but may not create a competing summary field. + +## D06 — Feature and criterion contract + +### Target + +```yaml +id: F-ab12cd34 +title: Login flow +status: planned +purpose: Allow a customer to begin an authenticated session safely. +modules: + - src/auth/session.ts +depends_on: [] +capability_refs: + - authentication +acceptance_criteria: + - id: AC-12ab34cd + kind: behavior + statement: When valid credentials are submitted, the system shall create a session. +``` + +- Require `purpose` on a new feature. +- Require an explicitly persisted `capability_refs` on a new feature and on the first intent-bearing edit of an exempt legacy feature. An empty list is a deliberate statement that the feature contributes directly to project purpose; it is valid but advisory-visible, never an omitted/unknown alias. +- Require `statement` and a persisted `kind` on a new criterion. Authoring tools may suggest `behavior`, but the schema does not hide the value behind a default. +- Begin with `behavior | quality | constraint`. Add kinds only after a real consumer exists. +- A `constraint` requires either a local `rationale` or one or more `constraint_refs` resolving to architecture rules with rationales. +- Retain `modules`, `depends_on`, `design_impact`, lifecycle state, archive metadata, oracle refs, evidence refs, and free-form `notes`. +- Persist `blocked_reason` exactly when `status: blocked`: it is required and non-empty for blocked features, forbidden otherwise, cleared by `feature.begin` and archive, and excluded from intent triggers and contract hashing. +- Treat `design_impact` as governance state, not default semantic prose. An unresolved structural impact still blocks completion. +- Exclude `notes` from intent-change triggers and canonical contract serialization. A requirement hidden only in notes is invalid authoring. +- Reject `ears`, `condition`, `action`, `response`, `text`, `test_refs`, and `adr_refs` in schema 0.2. + +### Strict statement grammar + +Use the [Ironclad EARS grammar](https://github.com/qwerfunch/ironclad/blob/main/ears.md) without inventing a second dialect. + +- Support five basic patterns plus compound. +- Compound clauses appear at most once and in fixed `When → While → Where → If` order. +- Require the grammar's comma boundaries; an `If` clause requires `then`. +- Require exactly one unprotected `shall` or `shall not` modal. +- Reject multiple modals as a non-atomic AC and instruct the author to split it. +- Treat the response predicate as opaque; do not manufacture structured subfields. +- Ignore modal-looking text inside protected backticks, quotes, and balanced parentheses while scanning. +- Reject unbalanced protected spans and statement fragments in new authoring. +- Apply strict parsing to new or intent-edited 0.2 nodes. Preserve the existing 0.1 gate result for legacy input. + +The single-modal rule is a syntactic floor, not a proof that an AC is semantically atomic. `ATOMICITY_RISK` may report an advisory when a one-modal statement still contains signals such as a top-level obligation list, coordinated independent predicates, excessive length, or several independently selectable outcomes. It must identify the signals it observed, never auto-split prose, and never block authoring or a strict gate by itself. Corpus fixtures include both a genuine multi-obligation statement and a long-but-acceptable false-positive control so this heuristic cannot silently become a second grammar. + +## D07 — Capability contract and edge ownership + +### Target + +```yaml +capabilities: + - id: spec-governance + title: Spec governance + outcome: Teams detect code and specification divergence before release. +``` + +- Keep only `id`, `title`, and required `outcome` on each 0.2 capability. +- Remove top-level `source` and capability `summary`, `surface`, and `features[]` from 0.2. +- Own the forward edge in `feature.capability_refs`; derive capability-to-feature projections live. +- A new or intent-edited 0.2 feature must confirm this edge set explicitly, including the empty set. The existing `CAPABILITIES_FEATURE_MAPPING` surface may advise on an explicit empty set when a capability catalog exists, but emptiness remains valid direct-to-project contribution. +- A 0.1 runtime consumes legacy capability `features[]` only. A 0.2 runtime consumes feature `capability_refs` only. Never use `L ∪ N` at runtime. +- The upgrade planner may compare legacy edge set `L` with candidate edge set `N` in memory and must prove `L = N` before cutover. +- Migration may suggest likely capability links for human review, but it never persists a non-legacy edge by inference. A feature with no legacy edge migrates to an explicit empty `capability_refs` unless a human resolves a different edge before apply. +- Present each exact `summary → outcome` copy as a human-confirmation item because `outcome` makes a stronger semantic promise than `summary`. + +## D08 — Architecture contract + +### Target + +```yaml +layers: + - [spec, core, report] + - [stages, adapters] + - [cli] +rules: + - id: AR-ab12cd34 + kind: forbidden_import + from: spec + to: stages + rationale: The specification compiler must not depend on stage runners. +``` + +- Accept one 0.2 layer spelling: ordered `string[][]`, from foundation to outer entry layers. +- Replace top-level `forbidden_imports` with `rules`; start with `kind: forbidden_import` only. +- Require stable rule `id`, `from`, `to`, and `rationale`. +- Define `from` as the importing layer and `to` as the imported dependency in schema text and Soft Shell output. +- Use `AR-*` as the address space for criterion `constraint_refs`. +- Treat a legacy object-form layer as a human-resolution item unless the upgrade can prove that converting it loses no path or layer meaning. + +## D09 — Scenario contract + +### Target + +```yaml +id: S-ab12cd34 +title: First login +actor: New customer +goal: Begin an authenticated session. +success: The authenticated home screen is visible. +steps: + - Open the login screen. + - Submit valid credentials. +feature_refs: + - F-ab12cd34 +``` + +- Keep the `scenario` concept and `spec/scenarios/` path; do not rename it to journey. +- Use `goal`, not `purpose`, for actor intent. +- Require non-empty `actor`, `goal`, `success`, ordered `steps`, and at least one resolving `feature_ref` for a new scenario. +- `scenario_policy: off` disables coverage demand but retains schema/reference checks. +- `advisory` is the persisted default and emits non-blocking information for absent or hollow scenarios. +- `required` makes scenario presence and structural completeness blocking. In 0.10 it does not claim executable journey verification. +- Only a `required` scenario participates in the referenced feature's contract hash. Executable scenario proof is a later feature. + +## D10 — Artifact registry and compiler boundary + +Create one code-owned `ArtifactDescriptor[]` registry. Each descriptor declares: + +- logical artifact or byte-region ID; +- path matcher and supported schema versions; +- domain and authority class (`canonical`, `generated`, `migration`, `evidence`, `transient`); +- mutability and persistence; +- producer, consumers, inputs, and refresh policy; +- current path and compatibility aliases; +- file or region ownership. + +The registry owns `spec/evidence//.yaml` as create-only canonical evidence. It validates that the directory equals the receipt subject's feature and that the filename equals the full digest of the canonical signed receipt. This authority is distinct from regenerable artifacts. + +Model `spec.yaml#project` and `spec.yaml#inventory` as separate logical regions. An inventory refresh must not invalidate a project-purpose revision. + +All spec consumers use one compiler pipeline: + +```text +version dispatch +→ parse with source locations +→ normalize to canonical IR +→ validate identities, references, lifecycle, and ownership +→ derive reverse edges, bindings, and projections +→ expose detector and query inputs +→ optionally cache disposable digests +``` + +- Sort shard paths before loading. +- Preserve provenance and source ranges on IR nodes and edges. +- Make unknown schema versions a hard error. +- Keep 0.1 validation and blocking results compatible. +- Store no committed `traceability.yaml`; reverse edges and authored-edge projections have a live, non-stale home in IR. +- Store only disposable compiler digests under `.cladding/cache/`. +- Require every Cladding-managed write target and every managed region to resolve to exactly one descriptor. +- Generate the prose artifact table and generated-directory notice from the executable registry rather than maintaining another list by hand. + +## D14 — Schema migration + +CLI and MCP use one engine: + +```text +clad migrate --to 0.2 # read-only preview +clad migrate --to 0.2 --apply # explicit transaction +project.upgrade_schema # MCP operation +``` + +Do not overload `clad update`; that command reconciles installed Cladding wiring and intentionally does not rewrite authored spec meaning. + +### Preview + +1. Compile 0.1 through its existing compatibility path. +2. Run a separate total legacy scanner that returns `parsed | opaque | conflict` for every string and never throws. +3. Build the entire 0.2 candidate in memory. +4. Copy legacy `text` exactly into `statement`; do not derive it from structured fields. +5. Propose exact `intent_summary → purpose` and `summary → outcome` values. +6. Validate filename/body identity, remove redundant body slugs, and remove child `schema`/legacy `source` markers from capability and architecture candidates. +7. Move legacy test refs into node-level baseline entries without inventing selectors. +8. Invert capability edges and prove exact edge-set equality. +9. Classify legacy human/blind evidence as asserted unless it carries a receipt from a supported verification channel. For a project with `independence_policy: require`, report every done feature that would lose independent status under the 0.2 rule. + +Human-resolution items are: + +- legacy EARS pattern/leading-keyword structural conflicts; +- capability outcome confirmation; +- architecture rule rationales and any lossy legacy layer conversion; +- scenario actor/goal/success/steps meaning; +- any actual `adr_refs` in an adopter corpus. +- a `require` project's choice to obtain verified replacement evidence or remain on 0.1 when legacy asserted evidence is its only independence basis. + +Text disagreement with legacy `condition/action/response` is not itself a conflict: those fields cover only part of the corpus and encode materially different prose. The universally present authored `text` is the migration source. + +### Baseline and enforcement + +Make the baseline node-granular: + +- project-intent projection; +- feature title/purpose projection; +- each criterion's intent projection and legacy binding list; +- scenario and architecture migration records. + +Do not write `kind: behavior` onto a legacy criterion by assumption. An unchanged migrated AC remains internally `legacy_unclassified`. On its first intent-bearing edit, require an explicit kind and strict statement. A new node has no baseline and is strict immediately. + +Intent-bearing changes are feature title/purpose changes and criterion add/remove or statement/kind/rationale/constraint-ref changes. Status, modules, dependencies, capability links, proof bindings, notes, ordering, and dependency promotion do not revoke unrelated intent exemptions. + +Before committing the first intent-bearing edit of an exempt feature, require the author to persist `capability_refs`, including an explicitly confirmed empty list. Changing only capability links remains non-intent-bearing and does not revoke criterion exemptions. + +The baseline is immutable through public tools and has no refresh command. Live bindings can supersede a criterion's baseline binding, but the historical receipt stays byte-stable. + +Do not transform a legacy `identity.author: human` or `blind: true` flag into a verified 0.2 evidence receipt. A supported human-confirmation or capability-isolation channel must issue the new receipt against the current contract. This is a proof refresh, not a schema spelling migration. + +### Apply and rollout + +- Abort with no writes while any resolution item remains. +- Prove feature/criterion identity and count preservation, exact statement transfer, and `L = N` capability edges. +- Treat `spec.yaml#schema` as the sole workspace schema selector. Child spec documents and shards carry no workspace schema field; receipt-local `receipt_schema` versions the receipt protocol independently. +- Write the baseline, converted artifacts, derived projections, and root schema switch in one journaled transaction. +- A second successful run produces zero diff. +- A failed normal run writes nothing; crash recovery returns byte-identical originals or finishes an already committed transaction. +- Continue reading schema 0.1 indefinitely. Reject old spellings inside a 0.2 document and reject unknown versions. +- Treat upgrade as a coordinated team event: install 0.10+, close or rebase open spec branches, preview, resolve, then apply once on the integration branch. + +### Cladding self-migration sequence + +F3 implements and proves the candidate capability/architecture edge set entirely in memory; it does not cut over repository bytes. F4 supplies the journaled apply boundary. At the end of F7, Cladding then: + +1. implements and tests the complete 0.2 reader/writer surface while its own repository remains 0.1; +2. runs preview and resolves every human item on that 0.1 tree; +3. performs the one atomic F4-backed apply; +4. completes F7 with `clad done` against the resulting 0.2 workspace; and +5. writes the first pure-0.2 attestation v3. + +The F7 preview explicitly selects `L2`; the existing self-declared L4 history is +not converted into verified 0.2 evidence. F8 therefore dogfoods a real 0.2 +GraphIR rather than a hybrid transition state. After F9 ships and exercises the +registered human and blind issuer paths, Cladding uses `project.set_policy` to +raise its own workspace to L4 and earns the 0.10 release attestation under that +policy. diff --git a/docs/design/spec-0.2/proof-and-editing.md b/docs/design/spec-0.2/proof-and-editing.md new file mode 100644 index 00000000..121c1be2 --- /dev/null +++ b/docs/design/spec-0.2/proof-and-editing.md @@ -0,0 +1,150 @@ + + +# Spec 0.2 — proof and editing + +> Canonical owner of D11–D13. Return to the [Spec 0.2 continuation router](../spec-0.2.md). + +## D11 — Test binding and observation + +### Source carrier + +Vitest/Jest begins with a title carrier: + +```ts +it('[covers:F-ab12cd34/AC-12ab34cd] creates a session', () => {}); +``` + +- Require the token at the start of the source test title. +- Permit consecutive leading tokens when one test independently covers several atomic ACs. +- Ignore bare IDs and organic ID mentions outside a registered carrier. +- Other frameworks use native metadata or annotation only after an adapter passes committed source→selector→JUnit round-trip fixtures. + +The normalized IR is: + +```ts +interface TestBinding { + criterion: string; // F-id/AC-id + framework: string; + file: string; + selector: string; + carrier: 'title' | 'metadata' | 'annotation'; +} +``` + +Source harvesting establishes a declared binding. A case-level JUnit record establishes observation. + +- Any bound failure makes the AC failed. +- With no failure, at least one observed pass makes it verified. +- Skipped-only or absent results are unverified. +- A passing unrelated test in the same file is never evidence. +- An unknown composite address is a blocking reference error. +- Test, oracle, and evidence channels retain their distinct provenance. A positive channel cannot hide an explicit failure in another declared channel. + +### Legacy binding fallback + +The accepted migration policy is node-level baseline fallback because most current refs cannot be mapped honestly to a testcase. + +- Store legacy `test_refs` only in `migration-baseline-0.1-to-0.2.yaml` for unchanged criteria. +- If a criterion has any live `[covers:]` binding, ignore all of its baseline refs. Never union the two sources. +- If the criterion's intent projection changes, end its baseline exemption and require a live supported binding or another qualifying proof channel. +- If a baseline ref becomes stale, do not rewrite the baseline; add an explicit live binding. +- Hash the whole referenced test file for a legacy path-only binding because no honest source span is known. + +## D12 — Transactional spec editing + +Expose `clad_edit_spec` as a typed operation batch, not JSON Patch and not a generic file editor. + +Initial operation set: + +- `project.set_description`, `project.set_purpose`, `project.set_policy`; +- `feature.create`, `feature.begin`, `feature.block`, `feature.archive`, `feature.set_title`, `feature.set_purpose`, `feature.set_links`, `feature.set_design_impact`; +- `criterion.upsert`, `criterion.remove`, `criterion.set_proof_refs`; +- `capability.upsert`, `capability.remove`; +- `architecture.set_layers`, `architecture_rule.upsert`, `architecture_rule.remove`; +- `scenario.upsert`, `scenario.remove`; +- `dependency.promote`; +- `evidence.revoke`; +- `project.upgrade_schema`. + +Existing create, capability-link, oracle, and design-impact surfaces become thin adapters over the same mutation engine. No edit operation may set `status: done`; completion stays exclusive to `clad done`. + +Operation semantics are deliberately narrow: + +- `feature.block(reason)` requires a non-empty reason. It transitions `planned | in_progress → blocked`; an already-blocked feature with the same reason is an idempotent no-change, while a different reason replaces the current reason and records the transition. `done` must pass through `feature.begin` before blocking; archived and unknown features are no-write errors. `feature.begin` clears the reason. +- `feature.archive(reason, superseded_by?)` accepts every non-archived feature, records immutable archive metadata, and clears a blocked reason. Repeating the exact archive metadata is an idempotent no-change; different metadata, an unknown feature, or any attempted unarchive is a no-write error. An erroneous integrated archive must be reverted in version control. +- `feature.set_links` is a typed partial replacement of exactly `modules`, `depends_on`, and `capability_refs`. Omission preserves a field and `[]` clears it. Every replacement validates references, duplicates, self-edges, and the complete dependency graph for cycles. +- `dependency.promote` accepts only a current unambiguous inferable candidate; an already-authored edge is an idempotent no-change. +- `criterion.set_proof_refs` owns oracle/evidence declarations without making them verified observations. +- `evidence.revoke` removes exactly one content-addressed receipt. Receipts are otherwise create-only and immutable. + +### Lifecycle start contract + +Expose `clad begin ` as the CLI adapter over `feature.begin` so every implementation cycle has an explicit start boundary. + +- Transition `planned | blocked | done` to `in_progress`. Beginning a done feature is a normal reopen, not a hidden contract exception. +- Treat an already-`in_progress` feature as an idempotent no-change success. Reject an archived or unknown feature with no spec, inventory, checkpoint, or event write. +- Capture the pre-batch state as one `feature_checkpoint`, change the status, refresh derived inventory, and apply any companion spec operations under the same journal. Recovery must expose either the complete begin or the byte-exact pre-begin state, never a status flip without its checkpoint. +- A batch may combine `feature.begin` with feature-local intent edits. One checkpoint covers the whole pre-batch state, and the result returns the post-commit context and input revisions. +- For schema 0.2, `clad done` accepts only `in_progress → done`; any other source status returns a no-write lifecycle error. The 0.1 compatibility path retains its shipped behavior. + +### Revision and commit contract + +- Read and projection tools return a `context_revision` for same-session delta reuse plus `input_revisions` keyed by logical artifact/region. These values have different authority and are never interchangeable. +- `context_revision` identifies the compiled projection and packing inputs. It may support `not_modified` or delta delivery only when the same session proves retention; it never authorizes a write. +- `input_revisions` are byte hashes of the canonical write regions derived for the requested operation. They are the only optimistic-concurrency precondition. +- The operation schema and registry derive read/write sets; callers cannot under-declare them. +- A caller supplies `input_revisions` for canonical write regions only. +- A shard-local operation requires only that shard's input revision. `project.upgrade_schema` requires the canonical workspace input revision. +- Prepare and validate patches without the commit lock. +- Acquire a short workspace commit lock with a five-second bounded wait. +- Under the lock, recover any journal, re-read input revisions, reload the latest full compiler inputs, apply the operation in memory, and validate all references. +- Different-shard concurrent edits wait briefly and both succeed against the latest IR. +- The second same-shard edit returns stale with no write. +- A lock timeout returns BUSY with no write; it is not reported as stale. +- Write a journal and before-images before same-directory temp-file replacement. On ordinary errors, write nothing. On process interruption, the next compiler or mutation run completes cleanup or restores byte-exact originals. +- Regenerate derived inventory/index state under the commit lock from the latest IR, but do not include derived regions in caller input revisions. + +## D13 — Attestation v3 + +Retain the current per-module byte map and replace the feature `ok` marker with compact per-feature closure and assurance seals. [D21–D23](assurance.md#d21--iron-law-assurance-kernel) own profile and obligation semantics; this section owns the persisted freshness boundary. + +### `contract_sha256` + +Canonical serialization includes: + +- feature ID, title, purpose; +- criterion IDs, parsed EARS AST, kind, rationale, and constraint refs; +- modules, dependencies, and capability refs; +- referenced capability outcomes and full architecture rules; +- active `design_impact` governance state; +- required scenarios that reference the feature; +- the applicable legacy-exemption identity for baseline-covered nodes. + +Exclude YAML comments, formatting, ordering where order is non-semantic, and criterion notes. + +### `verification_sha256` + +Canonical serialization includes: + +- binding address, framework, file, selector, and carrier; +- exact testcase source-span bytes, or whole-file bytes when the adapter cannot isolate a span; +- adapter-declared runner configuration that determines whether the testcase executes; +- oracle and evidence declarations plus their resolved bytes or resolver definitions; +- verified evidence receipts by full content address, including their method, issuer, subject and reviewed-input hashes, human runtime-dependency/author-set/criterion-matrix fields, and blind capability-manifest digest where applicable; +- whole-file bytes for baseline legacy test refs. + +This digest must cover tests and evidence outside `feature.modules`; module hashing alone does not protect them. + +### Runtime and assurance seals + +- `runtime_dependency_sha256` seals the sorted implementation roots of every authored or observed prerequisite needed by the feature. Unknown dependency completeness cannot produce a scoped seal; D23 escalates that verification layer to the whole repository. +- `profile_sha256` seals the assurance profile, required level, policy, adapter/catalog, environment, and trust-snapshot identities. +- `obligation_sha256` is a Merkle root over the sorted normalized obligation results. Persist compact required/pass/NA counts rather than duplicating the live GraphIR ledger. + +Only an authoritative, profile-complete GREEN run writes v3. A feature-completion entry may seal its proven impact closure; push/release entries seal their integration scope. The attestation policy continues to record engine and detector-catalog identity. A shared rule, capability outcome, required scenario, or prerequisite implementation change stales only features whose compiled closure references it; incomplete closure escalates instead of claiming selective freshness. + +While Cladding itself remains schema 0.1 during F6, serialize a schema-tagged legacy contract node containing exact legacy `text`, supported `ears`/`condition`/`action`/`response` values, scanner state (`parsed | opaque | conflict`), `legacy_unclassified`, and the applicable baseline/exemption identity. Do not invent 0.2 purpose or kind. The deliberate F7 self-migration invalidates those transitional hashes and the first post-migration GREEN gate writes a pure-0.2 v3. + +Receipts remain canonical files under `spec/evidence/`; attestation does not embed their bodies. It derives and seals the sorted set of current receipt identities and proof inputs. A stale or revoked receipt cannot be copied forward or automatically reissued merely to make attestation GREEN. + +The feature-level `contract_sha256` remains deliberately broader than an individual proof receipt. Evidence receipts bind to the subject-level hash defined in D20, so editing an unrelated sibling criterion can stale the feature attestation without invalidating proof that never claimed to cover that sibling. diff --git a/docs/design/spec-0.2/validation.md b/docs/design/spec-0.2/validation.md new file mode 100644 index 00000000..500b8db5 --- /dev/null +++ b/docs/design/spec-0.2/validation.md @@ -0,0 +1,82 @@ + + +# Spec 0.2 validation protocol + +This guide explains how to challenge the accepted design without confusing a sound test harness with a finished Spec 0.2 runtime. Normative decisions remain in the [continuation router](../spec-0.2.md) and its canonical owners; the committed requirement ledger is [`tests/design/spec-0.2/requirements.yaml`](../../../tests/design/spec-0.2/requirements.yaml). + +## Reproduce + +```sh +npm run validate:spec-0.2 +npm run --silent validate:spec-0.2 -- --json +npx vitest run tests/design/spec-0.2 +``` + +The command reads repository state and opens an in-memory MCP client/server pair. It does not mutate the spec, refresh attestation, run a provider, or accept a challenger. The Vitest suite additionally exercises the real pre-init → initialized dynamic-tool transition in a temporary workspace. + +## Status vocabulary + +| Status | Meaning | +|---|---| +| `pass` | The named executable check observed its required result. | +| `fail` | The check observed a contradiction or broken invariant. | +| `implementation_pending` | Accepted target behavior has no runtime implementation yet. | +| `not_run` | The scenario is implementable but this run did not execute it. | +| `inconclusive` | Some evidence exists, but it cannot establish the named claim. | + +Only `fail` makes the contributor command exit non-zero during V0. That lets the validation boundary land before F1 while keeping pending and absent evidence visible. A feature acceptance gate may separately require a particular pending/not-run item to become pass before that feature completes. + +## Current V0 coverage + +The initial harness validates: + +- D01–D24 have one declared canonical owner, one matching owner heading, one + matching router navigation heading, and no normative heading in another + routed document; +- the P/L/B/C/T/U/A matrix contains 37 unique preregistered IDs documented by D16; +- J01–J13 preserve model-simulated, implementation-pending, and reference-host-not-run journeys as different states; +- preregistration is not presented as 37 passing runtime cases; +- current MCP identifiers equal the live in-memory tool/resource/prompt catalog; +- dynamic tool discovery is negotiated and the real initialization path emits list-changed; +- every shipped tool belongs to at least one candidate task profile; +- exact catalog and document bytes use a named estimator and unknown cache by default; +- legacy host-smoke evidence is labelled legacy read-surface evidence; +- reference-host Spec 0.2 E2E, live token A/B, and unconfirmed adoption remain visibly unproven. + +The model simulations currently compare composite versus bare criterion identity, shared versus feature-local capability writes, proof provenance versus persona topology, directed versus undirected graph projection, required versus advisory scenario freshness, upstream report strictness versus Cladding blocking, and every-edit versus tiered/background assurance cadence. These establish design mechanics and expose token/merge trade-offs; they are not substitutes for the pending production adapters. + +AB01–AB12 is the bounded first comparison for the task-scoped MCP challenger and may support claims only on those tasks. D17 retains a separately preregistered larger retrieval study—40 tasks is the current optional candidate—only for later generalization; it is not a 0.10.0 gate and cannot prove adoption. + +It does not validate the unimplemented 0.2 compiler, migration, transaction, GraphIR v2, receipt, assurance, scheduler, or attestation behavior. Those checks become executable beside F1–F9 and replace `implementation_pending` one decision/scenario at a time. + +## Independent-oracle rule + +A validation fixture must not calculate its expected answer with the production join, traversal, parser, revision, closure, or reducer it is testing. Small hand-authored records, byte snapshots, property invariants, and independent sorted-record scans are preferred. Do not build a giant shadow compiler: it would become a second authority and could drift in the same direction. + +For each accepted decision, record: + +1. the owner and exact scenario ID; +2. the independent oracle or fault injection; +3. controlled inputs and final serialized outputs; +4. status and evidence location; +5. implementation or environment prerequisites; +6. a negative control that fails when the claimed design element is removed. + +## Challenger comparison + +When simulation suggests a better design, add it as a named challenger. Compare the accepted and challenger variants against the same correctness, failure-injection, concurrency, retrieval, context, and token fixtures. A challenger wins only if it preserves every required invariant and materially improves its preregistered objective. The harness reports the result; it never silently rewrites canonical owners. + +For MCP and LLM context comparisons, measure final UTF-8 bytes, named token estimates, provider usage when available, cache state, retries, and active time. Count avoidable bytes only when both arms produce the same required semantic output. Forced tool use may prove “efficiency when used”; only voluntary pull telemetry across completed cycles can prove adoption. + +## Feature boundary + +V0 is an additive pre-F1 safety boundary. Each later feature: + +1. changes its ledger entries from pending to executable only when fixtures exist; +2. runs the focused design simulation before production edits; +3. implements through the canonical kernel and adapter boundary; +4. runs the negative control and relevant integration journey; +5. updates the owner and decision log only for an accepted design change; +6. completes through the ordinary contributor and `clad done` gates. + +The complete target is not one global “simulation passed” badge. It is a ledger in which every release-required scenario has discriminating evidence and no pending/not-run state remains inside that release boundary. diff --git a/docs/knowledge-graph/design.md b/docs/knowledge-graph/design.md index ec81ec94..ff22f8a2 100644 --- a/docs/knowledge-graph/design.md +++ b/docs/knowledge-graph/design.md @@ -1,7 +1,13 @@ -# Cladding Knowledge Graph — design & cost model + -> Status: design (v0.7.0 track), with a **post-ship addendum (§8)** recording where -> the shipped capability deliberately departed from this document. Honest framing: +# Cladding Knowledge Graph v1 — historical design & cost model + +> **Historical status.** This document records the v0.7.0 design and its post-ship +> addendum (§8). It is evidence for why the shipped v1 looks as it does, not the +> target graph contract. The accepted 0.10.0 replacement is +> [`Spec 0.2` D17](../design/spec-0.2/graph.md#d17--knowledge-graph-v2-as-compiler-ir). +> Do not carry this document's seven node kinds, kind twins, or undirected +> neighborhood semantics into new implementation. Honest v1 framing: > this layer improves **traceability completeness** and **context-selection > efficiency (retrieval)**. It does **not** improve LLM correctness or "reasoning > depth" — cladding's own A/B record shows conformance is orthogonal to the spec diff --git a/package.json b/package.json index de91477a..066624a8 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "stage:audit": "tsx src/stages/audit.ts", "stage:uat": "tsx src/stages/uat.ts", "spec:validate": "tsx src/spec/cli.ts", + "validate:spec-0.2": "tsx scripts/spec-0.2-validate.ts", "conformance": "tsx conformance/runner.ts", "benchmark": "tsx src/cli/benchmark.ts", "clad": "node bin/clad", diff --git a/scripts/spec-0.2-validate.ts b/scripts/spec-0.2-validate.ts new file mode 100644 index 00000000..98915800 --- /dev/null +++ b/scripts/spec-0.2-validate.ts @@ -0,0 +1,773 @@ +// Cladding · Spec 0.2 design-validation harness (F-0a29d024). + +import {readFileSync, readdirSync} from 'node:fs'; +import {join, resolve} from 'node:path'; +import {fileURLToPath} from 'node:url'; + +import {Client} from '@modelcontextprotocol/sdk/client/index.js'; +import {InMemoryTransport} from '@modelcontextprotocol/sdk/inMemory.js'; +import {parse as parseYaml} from 'yaml'; + +import {readEventsIncludingRolled} from '../src/events/log.js'; +import {summarizeAdoption} from '../src/events/session-report.js'; +import {clearAuditObserversForTesting} from '../src/hitl/audit.js'; +import { + buildServer, + PERSONA_IDS, + PERSONA_PROMPT_ALIASES, + RESOURCE_URIS, + TOOL_NAMES, +} from '../src/serve/server.js'; + +/** Outcomes are intentionally non-Boolean so absent evidence cannot look green. */ +export type ValidationStatus = + | 'pass' + | 'fail' + | 'inconclusive' + | 'not_run' + | 'implementation_pending'; + +interface DecisionRequirement { + readonly id: string; + readonly owner: string; + readonly scenario: string; + readonly implementation: 'pending' | 'validation-active'; +} + +interface CaseRequirement { + readonly id: string; + readonly decision: string; +} + +interface IntegrationJourney { + readonly id: string; + readonly decisions: readonly string[]; + readonly status: 'simulated' | 'implementation_pending' | 'not_run'; + readonly scenario: string; +} + +interface HostAbTask { + readonly id: string; + readonly profile: string; + readonly objective: string; + readonly fault_control: string; +} + +export interface ValidationManifest { + readonly schema: number; + readonly decisions: readonly DecisionRequirement[]; + readonly preregistered_cases: readonly CaseRequirement[]; + readonly integration_journeys: readonly IntegrationJourney[]; + readonly mcp_scenarios: readonly string[]; + readonly host_ab_tasks: readonly HostAbTask[]; +} + +export interface UsageMeasurement { + readonly label: string; + readonly utf8_bytes: number; + readonly token_estimator: 'characters_div_4_ceiling'; + readonly estimated_tokens: number; + readonly cache: 'unknown' | 'cold' | 'warm'; + readonly avoidable_bytes: number | null; + readonly comparator: string | null; +} + +export interface ValidationCheck { + readonly id: string; + readonly status: ValidationStatus; + readonly evidence: string; +} + +interface CatalogSnapshot { + readonly tools: readonly Record[]; + readonly resources: readonly Record[]; + readonly prompts: readonly Record[]; + readonly capabilities: Record; +} + +export interface ValidationReport { + readonly schema: 1; + readonly target: 'spec-0.2'; + readonly checks: readonly ValidationCheck[]; + readonly measurements: readonly UsageMeasurement[]; + readonly mcp: { + readonly tool_count: number; + readonly resource_count: number; + readonly prompt_count: number; + readonly tools_list_changed: boolean; + readonly classified_tools: number; + readonly full_catalog_bytes: number; + readonly largest_task_profile_bytes: number; + readonly task_profile_reduction_ratio: number; + readonly adoption_verdict: string; + readonly reference_host_spec_02_e2e: 'not_run'; + readonly host_smoke: HostSmokeSummary | null; + }; +} + +interface HostSmokeSummary { + readonly file: string; + readonly hosts_verified: readonly string[]; + readonly hosts_failed: readonly string[]; + readonly provider_reported_tokens: Readonly>; + readonly scope: 'legacy-read-surface'; +} + +/** A challenger projection only; it does not change the shipped MCP catalog. */ +export const TASK_PROFILE_TOOLS = { + bootstrap: [ + 'clad_prepare_init', + 'clad_stage_init', + 'clad_init', + 'clad_prepare_clarify', + 'clad_clarify', + 'clad_resolve_onboarding_review', + ], + 'spec-edit': [ + 'clad_list_features', + 'clad_get_feature', + 'clad_create_feature', + 'clad_resolve_design_impact', + 'clad_create_scenario', + 'clad_link_capability', + 'clad_get_impact', + ], + implement: [ + 'clad_list_features', + 'clad_get_feature', + 'clad_get_context', + 'clad_get_working_set', + 'clad_get_impact', + 'clad_get_graph', + 'clad_run_check', + ], + verify: [ + 'clad_get_feature', + 'clad_run_check', + 'clad_run_gate', + 'clad_verdict', + 'clad_author_oracle', + 'clad_get_events', + 'clad_get_graph', + ], + observe: [ + 'clad_get_events', + 'clad_get_context', + 'clad_get_working_set', + 'clad_get_impact', + 'clad_get_graph', + 'clad_changelog', + ], +} as const satisfies Readonly>; + +/** Recursively sorts object keys so equivalent results serialize byte-identically. */ +export function stableValue(value: unknown): unknown { + if (Array.isArray(value)) return value.map(stableValue); + if (value !== null && typeof value === 'object') { + return Object.fromEntries( + Object.entries(value as Record) + .sort(([left], [right]) => left.localeCompare(right)) + .map(([key, child]) => [key, stableValue(child)]), + ); + } + return value; +} + +/** Canonical JSON used only for validation measurements and reproducible reports. */ +export function stableJson(value: unknown): string { + return `${JSON.stringify(stableValue(value), null, 2)}\n`; +} + +/** Measures controlled bytes without pretending to know a provider tokenizer or cache. */ +export function measureUsage( + label: string, + value: string, + options: { + cache?: UsageMeasurement['cache']; + comparator?: {label: string; value: string; equivalentOutput: boolean}; + } = {}, +): UsageMeasurement { + const utf8Bytes = Buffer.byteLength(value, 'utf8'); + const comparatorBytes = options.comparator?.equivalentOutput !== true + ? null + : Buffer.byteLength(options.comparator.value, 'utf8'); + return { + label, + utf8_bytes: utf8Bytes, + token_estimator: 'characters_div_4_ceiling', + estimated_tokens: Math.ceil(value.length / 4), + cache: options.cache ?? 'unknown', + avoidable_bytes: comparatorBytes === null ? null : Math.max(0, utf8Bytes - comparatorBytes), + comparator: options.comparator?.label ?? null, + }; +} + +/** Loads the committed validation projection. The design owners remain authoritative. */ +export function loadValidationManifest(cwd: string): ValidationManifest { + const path = join(cwd, 'tests', 'design', 'spec-0.2', 'requirements.yaml'); + return parseYaml(readFileSync(path, 'utf8')) as ValidationManifest; +} + +function unique(values: readonly string[]): boolean { + return new Set(values).size === values.length; +} + +function withoutFencedMarkdown(body: string): string { + let fence: '`' | '~' | null = null; + return body.split('\n').map((line) => { + const marker = /^\s*(`{3,}|~{3,})/.exec(line)?.[1]; + if (marker) { + const kind = marker[0] as '`' | '~'; + if (fence === null) fence = kind; + else if (fence === kind) fence = null; + return ''; + } + return fence === null ? line : ''; + }).join('\n'); +} + +/** Returns normative-owner violations for a supplied canonical document set. */ +export function decisionOwnershipIssues( + manifest: ValidationManifest, + documents: ReadonlyMap, +): string[] { + const routerPath = 'docs/design/spec-0.2.md'; + const router = withoutFencedMarkdown(documents.get(routerPath) ?? ''); + const issues: string[] = []; + for (const decision of manifest.decisions) { + const heading = new RegExp(`^## ${decision.id}\\b`, 'gm'); + for (const [path, raw] of documents) { + const count = withoutFencedMarkdown(raw).match(heading)?.length ?? 0; + const expected = path === decision.owner || path === routerPath ? 1 : 0; + if (count !== expected) issues.push(`${decision.id}:${path} has ${count}, expected ${expected}`); + } + const ownerLink = decision.owner.replace(/^docs\/design\//, ''); + if (!router.includes(`Owner: [${decision.id}](${ownerLink}#`)) { + issues.push(`${decision.id} router owner link does not target ${decision.owner}`); + } + } + return issues; +} + +function checkDecisionOwnership(cwd: string, manifest: ValidationManifest): ValidationCheck { + const ids = manifest.decisions.map((decision) => decision.id); + const expected = Array.from({length: 24}, (_, index) => `D${String(index + 1).padStart(2, '0')}`); + const issues: string[] = []; + if (!unique(ids) || stableJson([...ids].sort()) !== stableJson(expected)) { + issues.push('ledger must contain D01-D24 exactly once'); + } + const routerPath = 'docs/design/spec-0.2.md'; + const designDirectory = join(cwd, 'docs', 'design', 'spec-0.2'); + const paths = [routerPath, ...readdirSync(designDirectory) + .filter((name) => name.endsWith('.md')) + .map((name) => `docs/design/spec-0.2/${name}`)]; + const documents = new Map(); + for (const path of paths) documents.set(path, readFileSync(join(cwd, path), 'utf8')); + for (const decision of manifest.decisions) { + if (!documents.has(decision.owner)) issues.push(`${decision.id} owner is missing: ${decision.owner}`); + } + issues.push(...decisionOwnershipIssues(manifest, documents)); + return { + id: 'design-ownership', + status: issues.length === 0 ? 'pass' : 'fail', + evidence: issues.length === 0 + ? 'D01-D24 each have one owner heading and one matching router navigation heading.' + : issues.join('; '), + }; +} + +function checkDocumentationRatchets(cwd: string): ValidationCheck { + const router = readFileSync(join(cwd, 'docs', 'design', 'spec-0.2.md'), 'utf8'); + const delivery = readFileSync(join(cwd, 'docs', 'design', 'spec-0.2', 'delivery.md'), 'utf8'); + const routerBytes = Buffer.byteLength(router, 'utf8'); + const issues: string[] = []; + if (routerBytes > 7.5 * 1024) issues.push(`router=${routerBytes} bytes exceeds the 7.5 KiB operational ratchet`); + if (!delivery.includes('D01–D24 each have exactly one normative owner')) { + issues.push('D16 does not cover D01-D24 ownership'); + } + return { + id: 'documentation-ratchets', + status: issues.length === 0 ? 'pass' : 'fail', + evidence: issues.length === 0 + ? `Router is ${routerBytes} bytes and D16 covers D01-D24 ownership.` + : issues.join('; '), + }; +} + +function checkPreregisteredCases(cwd: string, manifest: ValidationManifest): ValidationCheck { + const ids = manifest.preregistered_cases.map((entry) => entry.id); + const owners = new Set(manifest.decisions.map((decision) => decision.id)); + const delivery = readFileSync(join(cwd, 'docs', 'design', 'spec-0.2', 'delivery.md'), 'utf8'); + const groups = [ + ['P', 10], ['L', 4], ['B', 6], ['C', 6], ['T', 4], ['U', 4], ['A', 3], + ] as const; + const expected = groups.flatMap(([prefix, count]) => + Array.from({length: count}, (_, index) => `${prefix}${String(index + 1).padStart(2, '0')}`), + ); + const undocumentedGroups = groups + .map(([prefix, count]) => `${prefix}01–${prefix}${String(count).padStart(2, '0')}`) + .filter((range) => !delivery.includes(range)); + const unmapped = manifest.preregistered_cases.filter((entry) => !owners.has(entry.decision)); + const valid = stableJson([...ids].sort()) === stableJson([...expected].sort()) + && unique(ids) + && undocumentedGroups.length === 0 + && unmapped.length === 0; + return { + id: 'preregistered-case-ledger', + status: valid ? 'pass' : 'fail', + evidence: valid + ? '37 unique preregistered case IDs are mapped and documented; this is a ledger check, not 37 passing implementations.' + : `count=${ids.length}; duplicates=${ids.length - new Set(ids).size}; undocumented_groups=${undocumentedGroups.join(',') || 'none'}; unmapped=${unmapped.map((entry) => entry.id).join(',') || 'none'}`, + }; +} + +function implementationCheck(manifest: ValidationManifest): ValidationCheck { + const pending = manifest.decisions.filter((decision) => decision.implementation === 'pending').map((decision) => decision.id); + return { + id: 'target-runtime-implementation', + status: pending.length === 0 ? 'pass' : 'implementation_pending', + evidence: pending.length === 0 ? 'Every target decision has implementation evidence.' : `${pending.length} decisions remain target design only: ${pending.join(', ')}.`, + }; +} + +function checkJourneyLedger(manifest: ValidationManifest): ValidationCheck { + const expected = Array.from({length: 13}, (_, index) => `J${String(index + 1).padStart(2, '0')}`); + const ids = manifest.integration_journeys.map((journey) => journey.id); + const decisions = new Set(manifest.decisions.map((decision) => decision.id)); + const invalid = manifest.integration_journeys.filter((journey) => + journey.decisions.length === 0 || journey.decisions.some((decision) => !decisions.has(decision)), + ); + const valid = stableJson(ids) === stableJson(expected) && unique(ids) && invalid.length === 0; + return { + id: 'integration-journey-ledger', + status: valid ? 'pass' : 'fail', + evidence: valid + ? 'J01-J13 map model simulations, pending implementation journeys, and the unrun reference-host cycle without collapsing their states.' + : `journey_ids=${ids.join(',')}; invalid=${invalid.map((journey) => journey.id).join(',') || 'none'}`, + }; +} + +function checkMcpScenarioLedger(manifest: ValidationManifest): ValidationCheck { + const scenarioIds = manifest.mcp_scenarios.map((scenario) => scenario.split('-', 1)[0]); + const expectedScenarios = Array.from({length: 12}, (_, index) => `MCP${String(index + 1).padStart(2, '0')}`); + const taskIds = manifest.host_ab_tasks.map((task) => task.id); + const expectedTasks = Array.from({length: 12}, (_, index) => `AB${String(index + 1).padStart(2, '0')}`); + const taskProfiles = new Set(Object.keys(TASK_PROFILE_TOOLS)); + const invalidProfiles = manifest.host_ab_tasks.filter((task) => !taskProfiles.has(task.profile)); + const valid = stableJson(scenarioIds) === stableJson(expectedScenarios) + && unique(manifest.mcp_scenarios) + && stableJson(taskIds) === stableJson(expectedTasks) + && unique(taskIds) + && invalidProfiles.length === 0; + return { + id: 'mcp-scenario-ledger', + status: valid ? 'pass' : 'fail', + evidence: valid + ? 'MCP01-MCP12 and AB01-AB12 are uniquely preregistered; the A/B ledger caps the live comparison at 24 host task calls.' + : `mcp=${scenarioIds.join(',')}; ab=${taskIds.join(',')}; invalid_profiles=${invalidProfiles.map((task) => task.id).join(',') || 'none'}`, + }; +} + +function simulateWhyAndIdentity(): ValidationCheck { + const featureA = {id: 'F-a', purpose: 'Protect account access.', criteria: [{id: 'AC-001', statement: 'The system shall reject an expired token.'}]}; + const featureB = {id: 'F-b', purpose: 'Explain account access.', criteria: [{id: 'AC-001', statement: 'The system shall record a rejection reason.'}]}; + const addresses = [featureA, featureB].flatMap((feature) => + feature.criteria.map((criterion) => `${feature.id}/${criterion.id}`), + ); + const bareIds = [featureA, featureB].flatMap((feature) => feature.criteria.map((criterion) => criterion.id)); + const whyLadder = [ + 'Keep customer accounts safe.', + 'A customer signs in with an expiring token.', + featureA.purpose, + featureA.criteria[0].statement, + ]; + const valid = whyLadder.every((value) => value.length > 0) + && new Set(addresses).size === 2 + && new Set(bareIds).size === 1; + return { + id: 'model-why-identity', + status: valid ? 'pass' : 'fail', + evidence: 'J01/J02 model: the WHY ladder is explicit and composite F-id/AC-id remains unique where bare AC-001 collides.', + }; +} + +function simulateMergeAndTransaction(): ValidationCheck { + const legacyWrites = [new Set(['spec/capabilities.yaml']), new Set(['spec/capabilities.yaml'])]; + const featureWrites = [new Set(['spec/features/F-a.yaml']), new Set(['spec/features/F-b.yaml'])]; + const overlaps = (left: ReadonlySet, right: ReadonlySet): boolean => + [...left].some((path) => right.has(path)); + const valid = overlaps(legacyWrites[0], legacyWrites[1]) + && !overlaps(featureWrites[0], featureWrites[1]) + && overlaps(featureWrites[0], new Set(['spec/features/F-a.yaml'])); + return { + id: 'model-merge-transaction', + status: valid ? 'pass' : 'fail', + evidence: 'J03 model: feature-owned edges permit disjoint-shard writes while the same write set still conflicts; crash recovery remains J07 implementation work.', + }; +} + +function simulateProofAndTopology(): ValidationCheck { + type Observation = 'pass' | 'fail' | 'skipped' | 'absent'; + const reduce = (observations: readonly Observation[]): 'verified' | 'failed' | 'unverified' => { + if (observations.includes('fail')) return 'failed'; + if (observations.includes('pass')) return 'verified'; + return 'unverified'; + }; + const receipts = [ + {subject: 'F-a/AC-001', provenance: 'test', observations: ['pass'] as const}, + {subject: 'F-a', provenance: 'human-signed', observations: ['pass'] as const}, + ]; + const topologyVerdicts = ['persona-chain', 'single-generalist', 'host-parallel'].map(() => + receipts.map((receipt) => reduce(receipt.observations)).join(','), + ); + const valid = reduce(['pass']) === 'verified' + && reduce(['pass', 'fail']) === 'failed' + && reduce(['skipped']) === 'unverified' + && new Set(topologyVerdicts).size === 1; + return { + id: 'model-proof-topology', + status: valid ? 'pass' : 'fail', + evidence: 'J08/J11 model: current observation and provenance determine proof; persona topology does not change the reducer.', + }; +} + +function simulateGraphProjection(): {check: ValidationCheck; measurements: readonly UsageMeasurement[]} { + const nodes = { + project: {id: 'project', purpose: 'Protect accounts.'}, + featureA: {id: 'F-a', purpose: 'Reject expired tokens.'}, + criterionA: {id: 'F-a/AC-001', statement: 'The system shall reject an expired token.'}, + shared: {id: 'src/token.ts', roles: ['source']}, + testA: {id: 'tests/token.test.ts', roles: ['test']}, + featureB: {id: 'F-b', purpose: 'Record token metrics.'}, + criterionB: {id: 'F-b/AC-001', statement: 'The system shall record expiry metrics.'}, + testB: {id: 'tests/metrics.test.ts', roles: ['test']}, + } as const; + const directed = stableJson([ + nodes.project, nodes.featureA, nodes.criterionA, nodes.shared, nodes.testA, + ]); + const undirected = stableJson(Object.values(nodes)); + const required = ['project', 'F-a', 'F-a/AC-001', 'src/token.ts', 'tests/token.test.ts']; + const valid = required.every((id) => directed.includes(`\"id\": \"${id}\"`)) + && !directed.includes('F-b/AC-001') + && Buffer.byteLength(directed, 'utf8') < Buffer.byteLength(undirected, 'utf8'); + return { + check: { + id: 'model-graph-projection', + status: valid ? 'pass' : 'fail', + evidence: 'J10 model: direction-aware task projection retains every required contract/proof node and excludes a sibling reached only through a shared artifact.', + }, + measurements: [ + measureUsage('model-graph-directed-task', directed), + measureUsage('model-graph-undirected-depth', undirected, { + comparator: {label: 'model-graph-directed-task', value: directed, equivalentOutput: true}, + }), + ], + }; +} + +function simulateScenarioFreshness(): ValidationCheck { + const feature = { + id: 'F-a', + purpose: 'Complete checkout safely.', + criteria: [ + {id: 'AC-a', statement: 'The system shall charge once.'}, + {id: 'AC-b', statement: 'The system shall issue a receipt.'}, + ], + }; + const scenario = { + id: 'S-checkout', + feature_refs: ['F-a'], + actor: 'buyer', + goal: 'place an order', + success: 'the order is confirmed', + steps: ['pay', 'confirm'], + }; + const subject = ( + criterion: (typeof feature.criteria)[number], + policy: 'off' | 'advisory' | 'required', + scenarios: readonly (typeof scenario)[], + ): string => stableJson({ + purpose: feature.purpose, + criterion, + scenarios: policy === 'required' ? scenarios + .filter((entry) => entry.feature_refs.includes(feature.id)) + .map(({id, actor, goal, success, steps}) => ({id, actor, goal, success, steps})) + .sort((left, right) => left.id.localeCompare(right.id)) : [], + }); + const before = feature.criteria.map((criterion) => subject(criterion, 'required', [scenario])); + const changedScenario = {...scenario, steps: [...scenario.steps, 'email']}; + const afterRequired = feature.criteria.map((criterion) => subject(criterion, 'required', [changedScenario])); + const advisoryBefore = feature.criteria.map((criterion) => subject(criterion, 'advisory', [scenario])); + const advisoryAfter = feature.criteria.map((criterion) => subject(criterion, 'advisory', [changedScenario])); + const unrelated = {...changedScenario, id: 'S-other', feature_refs: ['F-b']}; + const targetBefore = subject(feature.criteria[0], 'required', [scenario]); + const targetAfterUnrelated = subject(feature.criteria[0], 'required', [scenario, unrelated]); + const valid = before.every((value, index) => value !== afterRequired[index]) + && stableJson(advisoryBefore) === stableJson(advisoryAfter) + && targetBefore === targetAfterUnrelated; + return { + id: 'model-scenario-freshness', + status: valid ? 'pass' : 'fail', + evidence: 'J05 model: required scenario intent stales every criterion in its referenced feature; advisory and unrelated scenarios do not.', + }; +} + +function simulateAssuranceCadence(): ValidationCheck { + const obligations = [ + {id: 'type', cost: 1, state: 'pass'}, + {id: 'integration', cost: 4, state: 'pass'}, + {id: 'uat', cost: 7, state: 'pass'}, + ] as const; + type State = 'pass' | 'fail' | 'unobserved' | 'na'; + type PolicyRecord = {source: 'hard' | 'report'; blocking: 'hard' | 'report'; state: State}; + const standard = (records: readonly PolicyRecord[]): 'complete' | 'failed' | 'incomplete' => { + if (records.some((record) => record.state === 'unobserved')) return 'incomplete'; + if (records.some((record) => record.source === 'hard' && record.state === 'fail')) return 'failed'; + return 'complete'; + }; + const cladding = (records: readonly PolicyRecord[]): 'GREEN' | 'RED' | 'unresolved' => { + if (records.some((record) => record.state === 'unobserved')) return 'unresolved'; + if (records.some((record) => record.blocking === 'hard' && record.state === 'fail')) return 'RED'; + return 'GREEN'; + }; + const verdict = (records: readonly {state: string}[]): string => + records.some((record) => record.state !== 'pass') ? 'RED' : 'GREEN'; + const edits = 3; + const fullCost = edits * obligations.reduce((sum, obligation) => sum + obligation.cost, 0); + const tieredCost = edits * obligations[0].cost + + obligations.reduce((sum, obligation) => sum + obligation.cost, 0); + const variants = [verdict(obligations), verdict(obligations), verdict(obligations)]; + const reportFailure: PolicyRecord[] = [ + {source: 'hard', blocking: 'hard', state: 'pass'}, + {source: 'report', blocking: 'hard', state: 'fail'}, + ]; + const reportMissing: PolicyRecord[] = [ + {source: 'hard', blocking: 'hard', state: 'pass'}, + {source: 'report', blocking: 'hard', state: 'unobserved'}, + ]; + const hardFailure: PolicyRecord[] = [ + {source: 'hard', blocking: 'hard', state: 'fail'}, + {source: 'report', blocking: 'hard', state: 'pass'}, + ]; + const valid = new Set(variants).size === 1 + && variants[0] === 'GREEN' + && tieredCost < fullCost + && standard(reportFailure) === 'complete' + && cladding(reportFailure) === 'RED' + && standard(reportMissing) === 'incomplete' + && cladding(reportMissing) === 'unresolved' + && standard(hardFailure) === 'failed' + && cladding(hardFailure) === 'RED'; + return { + id: 'model-assurance-cadence', + status: valid ? 'pass' : 'fail', + evidence: `J12 model: cadence variants stay GREEN and active-cost units fall ${fullCost}→${tieredCost}; report fail is standard-complete/Cladding-RED, report absence unresolved, and hard fail RED.`, + }; +} + +async function inspectMcp(cwd: string): Promise { + const server = buildServer({cwd, name: 'cladding-spec-02-validator', version: '0.0.0-validation'}); + const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair(); + const client = new Client({name: 'cladding-spec-02-validator', version: '0.0.0-validation'}); + try { + await Promise.all([server.connect(serverTransport), client.connect(clientTransport)]); + const [toolResult, resourceResult, promptResult] = await Promise.all([ + client.listTools(), + client.listResources(), + client.listPrompts(), + ]); + return { + tools: toolResult.tools as unknown as readonly Record[], + resources: resourceResult.resources as unknown as readonly Record[], + prompts: promptResult.prompts as unknown as readonly Record[], + capabilities: (client.getServerCapabilities() ?? {}) as Record, + }; + } finally { + await client.close(); + await server.close(); + clearAuditObserversForTesting(); + } +} + +function summarizeHostSmoke(cwd: string): HostSmokeSummary | null { + const auditDir = join(cwd, '.cladding', 'audit'); + let names: string[]; + try { + names = readdirSync(auditDir) + .filter((name) => /^host-smoke-.*\.json$/.test(name)) + .sort(); + } catch { + return null; + } + const latest = names.at(-1); + if (latest === undefined) return null; + const parsed = JSON.parse(readFileSync(join(auditDir, latest), 'utf8')) as { + hosts?: Record; + }; + const hosts = parsed.hosts ?? {}; + const tokens: Record = {}; + for (const [host, record] of Object.entries(hosts)) { + const found = (record.surfaces ?? []).flatMap((surface) => + [...(surface.evidence ?? '').matchAll(/tokens used ([\d,]+)/g)] + .map((match) => Number.parseInt(match[1].replaceAll(',', ''), 10)), + ); + if (found.length > 0) tokens[host] = found; + } + return { + file: join('.cladding', 'audit', latest), + hosts_verified: Object.entries(hosts).filter(([, record]) => record.grade === 'verified').map(([host]) => host).sort(), + hosts_failed: Object.entries(hosts).filter(([, record]) => record.grade === 'fail').map(([host]) => host).sort(), + provider_reported_tokens: tokens, + scope: 'legacy-read-surface', + }; +} + +function catalogCheck(snapshot: CatalogSnapshot): ValidationCheck { + const toolNames = snapshot.tools.map((tool) => String(tool.name)).sort(); + const resourceUris = snapshot.resources.map((resource) => String(resource.uri)).sort(); + const promptNames = snapshot.prompts.map((prompt) => String(prompt.name)).sort(); + const expectedPrompts = [...PERSONA_IDS, ...Object.keys(PERSONA_PROMPT_ALIASES)].sort(); + const valid = stableJson(toolNames) === stableJson([...TOOL_NAMES].sort()) + && stableJson(resourceUris) === stableJson(Object.values(RESOURCE_URIS).sort()) + && stableJson(promptNames) === stableJson(expectedPrompts); + return { + id: 'mcp-wire-catalog', + status: valid ? 'pass' : 'fail', + evidence: valid + ? `${toolNames.length} tools, ${resourceUris.length} resources, and ${promptNames.length} prompts match the declared wire surface.` + : 'The live in-memory MCP catalog differs from exported identifiers.', + }; +} + +function taskProfileCatalogs(snapshot: CatalogSnapshot): Readonly[]>> { + return Object.fromEntries(Object.entries(TASK_PROFILE_TOOLS).map(([profile, names]) => { + const allowed = new Set(names); + return [profile, snapshot.tools.filter((tool) => allowed.has(String(tool.name)))]; + })); +} + +/** Builds the V0 report. Passing validation infrastructure never proves pending product behavior. */ +export async function validateSpec02(cwd = process.cwd()): Promise { + const root = resolve(cwd); + const manifest = loadValidationManifest(root); + const snapshot = await inspectMcp(root); + const fullCatalog = stableJson({tools: snapshot.tools, resources: snapshot.resources, prompts: snapshot.prompts}); + const profiles = taskProfileCatalogs(snapshot); + const profileMeasurements = Object.entries(profiles).map(([profile, tools]) => + measureUsage(`mcp-task-profile:${profile}`, stableJson({tools})), + ); + const largestProfileBytes = Math.max(...profileMeasurements.map((measurement) => measurement.utf8_bytes)); + const fullCatalogBytes = Buffer.byteLength(fullCatalog, 'utf8'); + const reductionRatio = fullCatalogBytes === 0 ? 0 : Number((1 - largestProfileBytes / fullCatalogBytes).toFixed(3)); + const classified = new Set(Object.values(TASK_PROFILE_TOOLS).flat()); + const adoption = summarizeAdoption(readEventsIncludingRolled(root)); + const graphSimulation = simulateGraphProjection(); + const ownerPaths = [...new Set(manifest.decisions.map((decision) => decision.owner))].sort(); + const router = readFileSync(join(root, 'docs', 'design', 'spec-0.2.md'), 'utf8'); + const ownerBody = ownerPaths.map((path) => readFileSync(join(root, path), 'utf8')).join(''); + const measurements: UsageMeasurement[] = [ + measureUsage('design-router', router), + measureUsage('design-owner-set', ownerBody), + measureUsage('mcp-full-catalog', fullCatalog), + ...profileMeasurements, + ...graphSimulation.measurements, + ]; + const checks: ValidationCheck[] = [ + checkDecisionOwnership(root, manifest), + checkDocumentationRatchets(root), + checkPreregisteredCases(root, manifest), + checkJourneyLedger(manifest), + checkMcpScenarioLedger(manifest), + simulateWhyAndIdentity(), + simulateMergeAndTransaction(), + simulateProofAndTopology(), + graphSimulation.check, + simulateScenarioFreshness(), + simulateAssuranceCadence(), + { + id: 'integration-journey-runtime', + status: 'implementation_pending', + evidence: `Runtime-dependent journeys remain pending: ${manifest.integration_journeys.filter((journey) => journey.status === 'implementation_pending').map((journey) => journey.id).join(', ')}.`, + }, + { + id: 'integration-journey-reference-host', + status: 'not_run', + evidence: 'J13 requires the implemented Spec 0.2 runtime and one real reference host; legacy read-surface smoke is not substituted.', + }, + implementationCheck(manifest), + catalogCheck(snapshot), + { + id: 'mcp-tools-list-changed-capability', + status: snapshot.capabilities.tools !== undefined + && (snapshot.capabilities.tools as {listChanged?: boolean}).listChanged === true ? 'pass' : 'fail', + evidence: 'The negotiated MCP capability must advertise dynamic tool-list changes for bootstrap-to-project registration.', + }, + { + id: 'mcp-task-profile-challenger', + status: classified.size === TOOL_NAMES.length && reductionRatio >= 0.2 ? 'inconclusive' : 'fail', + evidence: classified.size !== TOOL_NAMES.length + ? `${TOOL_NAMES.length - classified.size} tools are unclassified.` + : `Largest proposed task-scoped catalog is ${(reductionRatio * 100).toFixed(1)}% smaller by controlled bytes; discoverability and host behavior are not yet proven.`, + }, + { + id: 'mcp-reference-host-spec-02-e2e', + status: 'not_run', + evidence: 'Existing host smoke covers the legacy read surface, not a full Spec 0.2 edit→verify→attest cycle.', + }, + { + id: 'mcp-adoption', + status: adoption.verdict === 'confirmed' ? 'pass' : 'inconclusive', + evidence: `Existing pull telemetry verdict is ${adoption.verdict}; delivery or successful smoke calls cannot substitute for voluntary adoption.`, + }, + { + id: 'live-host-token-ab', + status: 'not_run', + evidence: 'No provider-controlled 12-task × 2-arm run was supplied; deterministic catalog bytes remain a cost input, not an LLM-efficiency result.', + }, + ]; + return stableValue({ + schema: 1, + target: 'spec-0.2', + checks, + measurements, + mcp: { + tool_count: snapshot.tools.length, + resource_count: snapshot.resources.length, + prompt_count: snapshot.prompts.length, + tools_list_changed: snapshot.capabilities.tools !== undefined + && (snapshot.capabilities.tools as {listChanged?: boolean}).listChanged === true, + classified_tools: classified.size, + full_catalog_bytes: fullCatalogBytes, + largest_task_profile_bytes: largestProfileBytes, + task_profile_reduction_ratio: reductionRatio, + adoption_verdict: adoption.verdict, + reference_host_spec_02_e2e: 'not_run', + host_smoke: summarizeHostSmoke(root), + }, + }) as ValidationReport; +} + +/** Human report keeps proof states visible instead of collapsing them into one score. */ +export function renderValidationReport(report: ValidationReport): string { + const lines = ['Spec 0.2 design validation', '']; + for (const check of report.checks) { + lines.push(`${check.status.padEnd(22)} ${check.id}: ${check.evidence}`); + } + lines.push('', 'Controlled context measurements'); + for (const measurement of report.measurements) { + lines.push(`${measurement.label}: ${measurement.utf8_bytes} bytes, ~${measurement.estimated_tokens} tokens (${measurement.token_estimator}, cache=${measurement.cache})`); + } + lines.push('', `MCP: ${report.mcp.tool_count} tools; catalog ${report.mcp.full_catalog_bytes} bytes; largest task projection ${report.mcp.largest_task_profile_bytes} bytes.`); + return `${lines.join('\n')}\n`; +} + +async function main(): Promise { + const report = await validateSpec02(); + process.stdout.write(process.argv.includes('--json') ? stableJson(report) : renderValidationReport(report)); + if (report.checks.some((check) => check.status === 'fail')) process.exitCode = 1; +} + +const invokedPath = process.argv[1] === undefined ? '' : resolve(process.argv[1]); +if (invokedPath === fileURLToPath(import.meta.url)) { + await main(); +} diff --git a/spec.yaml b/spec.yaml index efe3dfa1..4fba40e5 100644 --- a/spec.yaml +++ b/spec.yaml @@ -54,7 +54,7 @@ project: # Auto-maintained by `clad sync` (F-5b9f9f). Do not edit by hand. inventory: - features: 281 + features: 284 scenarios: 2 capabilities: 6 - test_files: 259 + test_files: 261 diff --git a/spec/_doc-links.yaml b/spec/_doc-links.yaml index e98e261d..b9e54d75 100644 --- a/spec/_doc-links.yaml +++ b/spec/_doc-links.yaml @@ -3,7 +3,7 @@ schema: "0.1" docs: "docs/README.md": - doc_links: ["docs/ssot-model.md", "spec/README.md", "src/agents/README.md"] + doc_links: ["docs/design/ironclad-obligation-rfc.md", "docs/design/spec-0.2.md", "docs/ssot-model.md", "spec/README.md", "src/agents/README.md"] "docs/ab-evaluation/case-081-cycle-conformance.md": features: [F-0023ba22, F-10cc42d1, F-1e7a10c3, F-6ed216f3, F-b0f898a6] "docs/ab-evaluation/case-doverunner-scale.md": @@ -20,12 +20,45 @@ docs: features: [F-06dfdad6] "docs/conventions.md": doc_links: ["docs/code-style.md"] + "docs/design/ironclad-obligation-rfc.md": + doc_links: ["docs/design/spec-0.2/assurance.md"] + "docs/design/spec-0.2.md": + doc_links: ["AGENTS.md", "docs/design/spec-0.2/assurance-evidence.md", "docs/design/spec-0.2/assurance.md", "docs/design/spec-0.2/context-and-orchestration.md", "docs/design/spec-0.2/decision-log.md", "docs/design/spec-0.2/delivery.md", "docs/design/spec-0.2/evidence.md", "docs/design/spec-0.2/graph.md", "docs/design/spec-0.2/hygiene.md", "docs/design/spec-0.2/mcp.md", "docs/design/spec-0.2/model-and-migration.md", "docs/design/spec-0.2/proof-and-editing.md", "docs/design/spec-0.2/validation.md", "docs/ssot-model.md", "spec/README.md"] + "docs/design/spec-0.2/assurance-evidence.md": + doc_links: ["docs/design/spec-0.2.md", "docs/design/spec-0.2/assurance.md"] + "docs/design/spec-0.2/assurance.md": + doc_links: ["docs/design/ironclad-obligation-rfc.md", "docs/design/spec-0.2.md", "docs/design/spec-0.2/assurance-evidence.md", "docs/design/spec-0.2/context-and-orchestration.md", "docs/design/spec-0.2/graph.md", "docs/design/spec-0.2/model-and-migration.md", "docs/design/spec-0.2/proof-and-editing.md"] + "docs/design/spec-0.2/context-and-orchestration.md": + doc_links: ["docs/design/spec-0.2.md", "docs/design/spec-0.2/assurance.md"] + "docs/design/spec-0.2/decision-log.md": + features: [F-06dfdad6] + doc_links: ["docs/ab-evaluation/case-working-set-landmine.md", "docs/design/spec-0.2.md", "docs/design/spec-0.2/evidence.md"] + "docs/design/spec-0.2/delivery.md": + features: [F-041, F-063] + doc_links: ["docs/design/spec-0.2.md", "docs/design/spec-0.2/assurance.md", "docs/design/spec-0.2/evidence.md", "docs/design/spec-0.2/hygiene.md", "docs/design/spec-0.2/mcp.md"] + "docs/design/spec-0.2/evidence.md": + features: [F-041, F-063] + doc_links: ["docs/design/spec-0.2.md", "docs/design/spec-0.2/assurance-evidence.md"] + "docs/design/spec-0.2/graph.md": + features: [F-06dfdad6] + doc_links: ["docs/design/spec-0.2.md"] + "docs/design/spec-0.2/hygiene.md": + doc_links: ["docs/design/spec-0.2.md"] + "docs/design/spec-0.2/mcp.md": + doc_links: ["docs/design/spec-0.2.md", "docs/design/spec-0.2/validation.md"] + "docs/design/spec-0.2/model-and-migration.md": + doc_links: ["docs/design/spec-0.2.md", "docs/design/spec-0.2/assurance.md"] + "docs/design/spec-0.2/proof-and-editing.md": + doc_links: ["docs/design/spec-0.2.md", "docs/design/spec-0.2/assurance.md"] + "docs/design/spec-0.2/validation.md": + doc_links: ["docs/design/spec-0.2.md"] "docs/gate-stages.md": doc_links: ["src/stages/detectors/README.md"] "docs/glossary.md": features: [F-1aab1bba, F-1d23a6, F-6ba22c5c, F-7ce18e, F-b84c38] "docs/knowledge-graph/design.md": features: [F-02343cd1, F-64a5c159, F-77f7ead0] + doc_links: ["docs/design/spec-0.2/graph.md"] "docs/multi-provider-roadmap.md": features: [F-049, F-073, F-074, F-075] "docs/project-context.md": diff --git a/spec/attestation.yaml b/spec/attestation.yaml index b17b2fa6..512ae2f3 100644 --- a/spec/attestation.yaml +++ b/spec/attestation.yaml @@ -26,17 +26,17 @@ attested_modules: CHANGELOG.md: 62efa82238103bd2 CLAUDE.md: 9f2fa4edd5c6df80 GOVERNANCE.md: 21cc28eaaf637a20 - README.html: e648da173a27d318 - README.ja.md: bda546d9642b960d - README.ko.html: 105a0068180d3892 - README.ko.md: 3e1293e9f5d5bcc7 - README.md: 9cba3b899b969660 - README.zh.md: 624317af25d6ab0e + README.html: a5de2dd3bf9aa61d + README.ja.md: 299b210e1ce026fa + README.ko.html: 5a679e58b96c0feb + README.ko.md: c1db33962075bb94 + README.md: 20cd9c1fc650c1b8 + README.zh.md: 5b82a51345b6480d SECURITY.md: df1d0c80304b2f28 bin/clad: 77b80666665dd1b0 conformance/fixtures.yaml: 5b461bb43a79a983 conformance/runner.ts: 5e638e070dbb10c2 - docs/README.md: 5672e5726104d845 + docs/README.md: d6813adc8bfeb342 docs/ab-evaluation-extended/README.md: f690562df2e5ec06 docs/ab-evaluation-extended/scenarios/dashboard/report.md: a656cad8c8ac2772 docs/ab-evaluation-extended/scenarios/task-manager/report.md: 91df11ca4e9bed26 @@ -56,6 +56,18 @@ attested_modules: docs/benchmarks/v0.4.0-consistency-bench.md: e49a0dc671ff6672 docs/benchmarks/v0.6.0-real-user-verification.md: 9af806f48f681d1a docs/code-style.md: f1fd100fa0d11c5e + docs/design/ironclad-obligation-rfc.md: 73612d73272aa976 + docs/design/spec-0.2.md: 21fc8033026458fc + docs/design/spec-0.2/assurance-evidence.md: 3f1c5a070f0a4c10 + docs/design/spec-0.2/assurance.md: f0e4b4fa51d65ed0 + docs/design/spec-0.2/context-and-orchestration.md: b75932cebb1696da + docs/design/spec-0.2/decision-log.md: 6299219e6626b508 + docs/design/spec-0.2/delivery.md: b7731dca72723616 + docs/design/spec-0.2/evidence.md: 7635990032b8d527 + docs/design/spec-0.2/graph.md: ad2cf28a217aae66 + docs/design/spec-0.2/mcp.md: 18ac5d95e9b57d23 + docs/design/spec-0.2/model-and-migration.md: 287933402e5b5162 + docs/design/spec-0.2/validation.md: e38ed5caa2843f59 docs/dogfood/antigravity-cli-2026-07-15.md: c7191d8548535998 docs/dogfood/claude-code-2026-05-20.md: 04870d41e75576d6 docs/dogfood/codex-cli-2026-07-15.md: 4ed02eed031aeda8 @@ -78,7 +90,7 @@ attested_modules: docs/ssot-model.md: 66b9439e2f71ac4b docs/ssot-testing.md: abf3b2bd5acb29a1 package-lock.json: 446ffb2632fbb457 - package.json: 6e246e66421c0d84 + package.json: 6c28f36f067656e6 plugins/claude-code/.claude-plugin/plugin.json: 0b3e617b8d8ea59a plugins/claude-code/agents/developer.md: 3002b4ef69ddab43 plugins/claude-code/agents/observability.md: 637fde18c012e2a7 @@ -108,6 +120,7 @@ attested_modules: scripts/build.mjs: 3a4b204063024ef1 scripts/migrate-dogfood-v0.3.16.mjs: 1e265fb370019996 scripts/shard-spec.ts: 0c728bbc1e869421 + scripts/spec-0.2-validate.ts: c0206f5a21919385 scripts/test-count.d.mts: a392f5dea372a40e scripts/test-count.mjs: aea2620221c8d5ff scripts/version-bump.mjs: 770b066b8279db39 @@ -123,7 +136,7 @@ attested_modules: skills/serve/SKILL.md: f08bbdbbfeb05041 skills/status/SKILL.md: 09faadc50b3449da skills/sync/SKILL.md: 775c0f990a52a3d9 - spec.yaml: 9b979cd4e7d137d2 + spec.yaml: 2c432e93afdc9583 spec/README.md: 7c257426396d435c spec/architecture.yaml: f0888480405a13a8 spec/features/: a4d0f0eb87fed960 @@ -357,6 +370,9 @@ attested_modules: tests/cli/init.test.ts: e88394472a0b95b3 tests/cli/intent-onboarding.test.ts: 0681b98ce2e74c22 tests/conformance/registry.test.ts: 018b1e5c0d8d4baf + tests/design/spec-0.2/design-validation.test.ts: 55d5a65deac3da53 + tests/design/spec-0.2/mcp-validation.test.ts: ca20015a268ce3bb + tests/design/spec-0.2/requirements.yaml: 0a46cc965db9add8 tests/drive/loop.test.ts: ae49bcfa745a8cdb tests/events/log.test.ts: 221f74acfdb7f7c0 tests/init/git-hook.test.ts: 6e43b2a970e5c39b @@ -367,6 +383,7 @@ attested_modules: tests/oracle-payload.test.ts: 3251c3a6c5ede131 tests/oracle-policy.test.ts: f6716e0cbb00d282 tests/oracle-record.test.ts: 0d1e89f51977a45a + tests/readme-record-honesty.test.ts: 2c7ac44fc1848142 tests/router/intent.test.ts: 0f2a158364f6bdf3 tests/scenarios/_assertions.ts: 75ef37ef006aa3fe tests/scenarios/_fixtures/sample-existing-ts/: a4d0f0eb87fed960 @@ -536,6 +553,7 @@ attested_features: F-098d3b: ok F-09a98261: ok F-09d68b: ok + F-0a29d024: ok F-0e84628e: ok F-0ed2db: ok F-0f2984d0: ok @@ -563,6 +581,7 @@ attested_features: F-2c02991f: ok F-2de65d: ok F-2e28cc72: ok + F-2f840a6c: ok F-315fd7: ok F-31eeb8: ok F-32b1e0: ok @@ -666,6 +685,7 @@ attested_features: F-b81d203e: ok F-b84c38: ok F-b8d74801: ok + F-b8d77abf: ok F-b99577: ok F-ba2e05: ok F-ba4b7a: ok diff --git a/spec/features/spec-02-design-validation-0a29d024.yaml b/spec/features/spec-02-design-validation-0a29d024.yaml new file mode 100644 index 00000000..ad86930c --- /dev/null +++ b/spec/features/spec-02-design-validation-0a29d024.yaml @@ -0,0 +1,45 @@ +# Cladding · Tier A · SSoT — Iron Law sealed · Refreshed by: manual +id: F-0a29d024 +slug: spec-02-design-validation +title: "Spec 0.2 design validation — executable scenarios, MCP efficacy, and token accounting" +status: done +modules: + - scripts/spec-0.2-validate.ts + - tests/design/spec-0.2/requirements.yaml + - tests/design/spec-0.2/design-validation.test.ts + - tests/design/spec-0.2/mcp-validation.test.ts + - docs/design/spec-0.2/validation.md + - docs/design/spec-0.2/mcp.md + - docs/design/spec-0.2.md + - docs/design/spec-0.2/delivery.md + - docs/design/spec-0.2/decision-log.md + - docs/README.md + - package.json +acceptance_criteria: + - id: AC-21dfe21a + ears: ubiquitous + text: "The validation ledger shall map every D01-D24 decision to a canonical owner, an executable or explicitly deferred scenario, and a unique evidence classification without presenting preregistered work as a passing result." + test_refs: ["tests/design/spec-0.2/design-validation.test.ts#maps every D01-D24 decision exactly once and rejects fabricated pass evidence"] + notes: "## Why\nThe accepted design is larger than the 37 parser/migration seeds, so section-level prose confidence is not enough to begin a long implementation safely." + - id: AC-e0f0c4b1 + ears: ubiquitous + text: "The deterministic simulator shall evaluate design ownership, migration and proof contracts, GraphIR and context constraints, assurance scheduling, and delivery ordering with independent fixtures while leaving implementation-pending behavior explicitly unverified." + test_refs: ["tests/design/spec-0.2/design-validation.test.ts#runs deterministic design scenarios without converting pending implementation into evidence"] + - id: AC-bd12a73c + ears: ubiquitous + text: "The MCP validation suite shall distinguish wire conformance, semantic parity, mutation safety, reference-host end-to-end use, efficiency when used, and observed adoption, and shall fail closed when any required evidence is absent." + test_refs: ["tests/design/spec-0.2/mcp-validation.test.ts#separates MCP conformance efficacy and adoption without vacuous success"] + - id: AC-4c8c08d1 + ears: ubiquitous + text: "Every deterministic and live validation result shall report exact controlled UTF-8 bytes, a named token estimator, cache knowledge, and only counterfactually demonstrated avoidable bytes as waste." + test_refs: ["tests/design/spec-0.2/design-validation.test.ts#accounts for controlled bytes tokens cache and counterfactual waste honestly"] + - id: AC-55c159b4 + ears: event + condition: "when the Spec 0.2 validation command runs" + text: "When the Spec 0.2 validation command runs, the system shall emit a deterministic machine-readable summary and a human-readable report whose pass, fail, inconclusive, not-run, and implementation-pending states remain distinct." + test_refs: ["tests/design/spec-0.2/design-validation.test.ts#emits byte-identical summaries with distinct result states"] +design_impact: + classification: additive + rationale: "The work adds a contributor-only validation boundary and target-design owner without changing the shipped 0.1 runtime contract." + status: resolved + artifacts: [] diff --git a/spec/features/spec-02-final-audit-closure-b8d77abf.yaml b/spec/features/spec-02-final-audit-closure-b8d77abf.yaml new file mode 100644 index 00000000..a608aca2 --- /dev/null +++ b/spec/features/spec-02-final-audit-closure-b8d77abf.yaml @@ -0,0 +1,48 @@ +# Cladding · Tier A · SSoT — Iron Law sealed · Refreshed by: manual +id: F-b8d77abf +slug: spec-02-final-audit-closure +title: "Spec 0.2 final audit closure — assurance, evidence ownership, and validation" +status: done +modules: + - docs/design/spec-0.2/assurance.md + - docs/design/spec-0.2/assurance-evidence.md + - docs/design/spec-0.2/context-and-orchestration.md + - docs/design/spec-0.2/delivery.md + - docs/design/spec-0.2/model-and-migration.md + - docs/design/spec-0.2/mcp.md + - docs/design/spec-0.2/decision-log.md + - docs/design/spec-0.2/evidence.md + - docs/design/spec-0.2/validation.md + - docs/design/ironclad-obligation-rfc.md + - scripts/spec-0.2-validate.ts + - tests/design/spec-0.2/design-validation.test.ts + - tests/readme-record-honesty.test.ts + - README.md + - README.ko.md + - README.ja.md + - README.zh.md + - README.html + - README.ko.html +acceptance_criteria: + - id: AC-eee960eb + ears: ubiquitous + text: "The accepted assurance design shall distinguish upstream hard-or-report strictness from Cladding effective blocking while preserving the shipped strict completion policy." + test_refs: ["tests/design/spec-0.2/design-validation.test.ts#runs deterministic design scenarios without converting pending implementation into evidence"] + notes: "## Why\nThe final audit found that the accepted kernel could not represent Ironclad's reporting-only Coverage and Performance stages without confusing them with Cladding's blocking advisory state." + - id: AC-f31b6ee2 + ears: ubiquitous + text: "The receipt and scenario contracts shall assign verified receipt production to F9 and derive criterion freshness from every required scenario that references the parent feature." + test_refs: ["tests/design/spec-0.2/design-validation.test.ts#runs deterministic design scenarios without converting pending implementation into evidence"] + - id: AC-6e5f6558 + ears: ubiquitous + text: "The design validator shall reject normative decision headings outside each declared owner while permitting the router navigation copy." + test_refs: ["tests/design/spec-0.2/design-validation.test.ts#maps every D01-D24 decision exactly once and rejects fabricated pass evidence"] + - id: AC-e2c6b5f8 + ears: ubiquitous + text: "Every public README status surface shall label worktree counts and match the canonical feature inventory without changing the release-history claim." + test_refs: ["tests/readme-record-honesty.test.ts#current worktree feature counts match the canonical inventory"] +design_impact: + classification: additive + rationale: "The correction closes final design ambiguities and strengthens executable validation without changing the shipped schema-0.1 runtime." + status: resolved + artifacts: [] diff --git a/spec/features/spec-02-validation-findings-refinement-2f840a6c.yaml b/spec/features/spec-02-validation-findings-refinement-2f840a6c.yaml new file mode 100644 index 00000000..32c70b58 --- /dev/null +++ b/spec/features/spec-02-validation-findings-refinement-2f840a6c.yaml @@ -0,0 +1,34 @@ +# Cladding · Tier A · SSoT — Iron Law sealed · Refreshed by: manual +id: F-2f840a6c +slug: spec-02-validation-findings-refinement +title: "Spec 0.2 validation findings — evidence refresh and benchmark boundary" +status: done +modules: + - docs/design/spec-0.2.md + - docs/design/spec-0.2/delivery.md + - docs/design/spec-0.2/graph.md + - docs/design/spec-0.2/context-and-orchestration.md + - docs/design/spec-0.2/evidence.md + - docs/design/spec-0.2/decision-log.md + - docs/design/spec-0.2/validation.md + - scripts/spec-0.2-validate.ts + - tests/design/spec-0.2/design-validation.test.ts +acceptance_criteria: + - id: AC-9ea2eae1 + ears: ubiquitous + text: "The accepted design shall distinguish the bounded AB01-AB12 MCP challenger experiment from an optional broader GraphIR generalization study without making either a 0.10.0 release prerequisite." + test_refs: ["tests/design/spec-0.2/design-validation.test.ts#keeps bounded MCP and broader GraphIR benchmark claims distinct"] + notes: "## Why\nThe validation added a 12-task MCP experiment while older owners still named a 40-task retrieval experiment, leaving their authority and scope ambiguous." + - id: AC-21a9e51f + ears: ubiquitous + text: "The evidence snapshot shall preserve the earlier partial MCP census while separately recording the current full list-surface bytes, task-profile challenger bytes, graph projection comparison, cadence model, and refreshed routed-document census with explicit measurement scope." + test_refs: ["tests/design/spec-0.2/design-validation.test.ts#keeps validation evidence measurements scoped and reproducible"] + - id: AC-3e816b4c + ears: ubiquitous + text: "The continuation router shall preserve D01-D24 anchors and task routes within a 7.5 KiB operational ratchet while the normative hard ceiling remains 8 KiB." + test_refs: ["tests/design/spec-0.2/design-validation.test.ts#keeps each routed design owner below the documented byte ceiling"] +design_impact: + classification: additive + rationale: "The refinement reconciles accepted owners and dated evidence without changing the shipped 0.1 runtime or the accepted Spec 0.2 architecture." + status: resolved + artifacts: [] diff --git a/spec/index.yaml b/spec/index.yaml index 8936f075..a024c8e4 100644 --- a/spec/index.yaml +++ b/spec/index.yaml @@ -91,6 +91,7 @@ features: F-098d3b: {slug: detector-count-auto-recompute, status: done, modules: 1} F-09a98261: {slug: terminology-canon, status: done, modules: 8} F-09d68b: {slug: clad-refine, status: done, modules: 7} + F-0a29d024: {slug: spec-02-design-validation, status: done, modules: 11} F-0e84628e: {slug: merge-ritual-docs, status: done, modules: 2} F-0ed2db: {slug: ai-hints-consumer-instructions, status: done, modules: 11} F-0f2984d0: {slug: infer-deps-dynamic-flag, status: done, modules: 2} @@ -118,6 +119,7 @@ features: F-2c02991f: {slug: arch-gate-skips-build-output, status: done, modules: 1} F-2de65d: {slug: drive-auto-rollback, status: done, modules: 1} F-2e28cc72: {slug: clad-verdict, status: done, modules: 2} + F-2f840a6c: {slug: spec-02-validation-findings-refinement, status: done, modules: 9} F-315fd7: {slug: scenario-coverage-detector, status: done, modules: 2} F-31eeb8: {slug: scan-bfs-walk, status: done, modules: 2} F-32b1e0: {slug: ai-hints-preferred-patterns, status: done, modules: 6} @@ -224,6 +226,7 @@ features: F-b81d203e: {slug: vacuous-test-guard, status: done, modules: 2} F-b84c38: {slug: lifecycle-events-identity, status: done, modules: 4} F-b8d74801: {slug: verb-rename-residue-sweep, status: done, modules: 6} + F-b8d77abf: {slug: spec-02-final-audit-closure, status: done, modules: 19} F-b99577: {slug: stale-archive-suggestion, status: done, modules: 4} F-ba2e05: {slug: ab-outcome-quality, status: done, modules: 8} F-ba4b7a: {slug: pulse-progressive, status: done, modules: 2} diff --git a/tests/design/spec-0.2/design-validation.test.ts b/tests/design/spec-0.2/design-validation.test.ts new file mode 100644 index 00000000..393dd169 --- /dev/null +++ b/tests/design/spec-0.2/design-validation.test.ts @@ -0,0 +1,160 @@ +// Cladding · Spec 0.2 deterministic design-validation contract (F-0a29d024). + +import {readFileSync, readdirSync} from 'node:fs'; +import {join} from 'node:path'; + +import {beforeAll, describe, expect, test} from 'vitest'; + +import { + decisionOwnershipIssues, + loadValidationManifest, + measureUsage, + stableJson, + validateSpec02, + type ValidationReport, +} from '../../../scripts/spec-0.2-validate.js'; + +describe('Spec 0.2 validation ledger', () => { + let report: ValidationReport; + + beforeAll(async () => { + report = await validateSpec02(process.cwd()); + }); + + test('maps every D01-D24 decision exactly once and rejects fabricated pass evidence', () => { + const manifest = loadValidationManifest(process.cwd()); + expect(manifest.decisions.map((decision) => decision.id)).toEqual( + Array.from({length: 24}, (_, index) => `D${String(index + 1).padStart(2, '0')}`), + ); + expect(report.checks.find((check) => check.id === 'design-ownership')?.status).toBe('pass'); + expect(report.checks.find((check) => check.id === 'target-runtime-implementation')?.status) + .toBe('implementation_pending'); + expect(report.checks.find((check) => check.id === 'preregistered-case-ledger')?.evidence) + .toContain('not 37 passing implementations'); + + const designRoot = join(process.cwd(), 'docs/design/spec-0.2'); + const documents = new Map([ + ['docs/design/spec-0.2.md', readFileSync(join(process.cwd(), 'docs/design/spec-0.2.md'), 'utf8')], + ...readdirSync(designRoot).filter((name) => name.endsWith('.md')).map((name) => [ + `docs/design/spec-0.2/${name}`, + readFileSync(join(designRoot, name), 'utf8'), + ] as [string, string]), + ]); + expect(decisionOwnershipIssues(manifest, documents)).toEqual([]); + + const poisoned = new Map(documents); + poisoned.set( + 'docs/design/spec-0.2/validation.md', + `${poisoned.get('docs/design/spec-0.2/validation.md')}\n## D21 — duplicate owner\n`, + ); + expect(decisionOwnershipIssues(manifest, poisoned)).toContain( + 'D21:docs/design/spec-0.2/validation.md has 1, expected 0', + ); + + const fenced = new Map(documents); + fenced.set( + 'docs/design/spec-0.2/validation.md', + `${fenced.get('docs/design/spec-0.2/validation.md')}\n\`\`\`md\n## D21 — example only\n\`\`\`\n`, + ); + expect(decisionOwnershipIssues(manifest, fenced)).toEqual([]); + }); + + test('runs deterministic design scenarios without converting pending implementation into evidence', () => { + const manifest = loadValidationManifest(process.cwd()); + expect(manifest.preregistered_cases).toHaveLength(37); + expect(new Set(manifest.preregistered_cases.map((entry) => entry.id))).toHaveProperty('size', 37); + expect(report.checks.find((check) => check.id === 'preregistered-case-ledger')).toMatchObject({ + status: 'pass', + }); + expect(report.checks.find((check) => check.id === 'model-scenario-freshness')).toMatchObject({ + status: 'pass', + }); + expect(report.checks.find((check) => check.id === 'model-assurance-cadence')).toMatchObject({ + status: 'pass', + evidence: expect.stringContaining('standard-complete/Cladding-RED'), + }); + expect(report.checks.filter((check) => check.status === 'implementation_pending').length) + .toBeGreaterThan(0); + }); + + test('accounts for controlled bytes tokens cache and counterfactual waste honestly', () => { + const value = '가나다abc'; + const uncontrolled = measureUsage('candidate', value, { + comparator: {label: 'different-output', value: 'x', equivalentOutput: false}, + }); + const equivalent = measureUsage('candidate', value, { + cache: 'cold', + comparator: {label: 'same-output', value: 'x', equivalentOutput: true}, + }); + expect(uncontrolled).toMatchObject({ + utf8_bytes: Buffer.byteLength(value, 'utf8'), + token_estimator: 'characters_div_4_ceiling', + cache: 'unknown', + avoidable_bytes: null, + comparator: 'different-output', + }); + expect(equivalent.avoidable_bytes).toBe(Buffer.byteLength(value, 'utf8') - 1); + expect(report.measurements.every((measurement) => measurement.cache === 'unknown')).toBe(true); + const claimedWaste = report.measurements.filter((measurement) => measurement.avoidable_bytes !== null); + expect(claimedWaste).toEqual([ + expect.objectContaining({ + label: 'model-graph-undirected-depth', + comparator: 'model-graph-directed-task', + }), + ]); + expect(claimedWaste[0].avoidable_bytes).toBeGreaterThan(0); + }); + + test('emits byte-identical summaries with distinct result states', async () => { + const second = await validateSpec02(process.cwd()); + expect(stableJson(second)).toBe(stableJson(report)); + const states = new Set(report.checks.map((check) => check.status)); + expect(states).toEqual(new Set(['pass', 'inconclusive', 'not_run', 'implementation_pending'])); + }); + + test('keeps each routed design owner below the documented byte ceiling', () => { + const manifest = loadValidationManifest(process.cwd()); + for (const path of new Set(manifest.decisions.map((decision) => decision.owner))) { + const bytes = Buffer.byteLength(readFileSync(join(process.cwd(), path), 'utf8'), 'utf8'); + expect(bytes, path).toBeLessThanOrEqual(24 * 1024); + } + const routerBytes = Buffer.byteLength( + readFileSync(join(process.cwd(), 'docs/design/spec-0.2.md'), 'utf8'), + 'utf8', + ); + expect(routerBytes).toBeLessThanOrEqual(7.5 * 1024); + expect(report.checks.find((check) => check.id === 'documentation-ratchets')).toMatchObject({ + status: 'pass', + }); + }); + + test('keeps bounded MCP and broader GraphIR benchmark claims distinct', () => { + const graph = readFileSync(join(process.cwd(), 'docs/design/spec-0.2/graph.md'), 'utf8'); + const context = readFileSync(join(process.cwd(), 'docs/design/spec-0.2/context-and-orchestration.md'), 'utf8'); + const delivery = readFileSync(join(process.cwd(), 'docs/design/spec-0.2/delivery.md'), 'utf8'); + const decisions = readFileSync(join(process.cwd(), 'docs/design/spec-0.2/decision-log.md'), 'utf8'); + expect(graph).toContain('AB01–AB12 two-arm run is the bounded first experiment'); + expect(graph).toContain('40 tasks is the current optional scale candidate'); + expect(context).toContain('AB01–AB12 live A/B may support only a task-scoped efficiency claim'); + expect(delivery).not.toContain('40-task host A/B remain independent tail work'); + expect(decisions).toContain('Broader LLM GraphIR retrieval study'); + }); + + test('keeps validation evidence measurements scoped and reproducible', () => { + const evidence = readFileSync(join(process.cwd(), 'docs/design/spec-0.2/evidence.md'), 'utf8'); + const designDir = join(process.cwd(), 'docs/design/spec-0.2'); + const completeBytes = Buffer.byteLength( + readFileSync(join(process.cwd(), 'docs/design/spec-0.2.md'), 'utf8'), + 'utf8', + ) + readdirSync(designDir) + .filter((name) => name.endsWith('.md')) + .reduce((sum, name) => sum + Buffer.byteLength(readFileSync(join(designDir, name), 'utf8'), 'utf8'), 0); + const recordedComplete = /complete routed design set is ([\d,]+) bytes/.exec(evidence); + expect(recordedComplete).not.toBeNull(); + expect(Number(recordedComplete![1].replaceAll(',', ''))).toBe(completeBytes); + expect(evidence).toContain('Historical generic-MCP tool subset'); + expect(evidence).toContain(`${report.mcp.full_catalog_bytes.toLocaleString('en-US')} bytes`); + expect(evidence).toContain('directed 375 bytes; undirected 610; avoidable 235'); + expect(evidence).toContain('every-edit 36 units; tiered 15'); + }); +}); diff --git a/tests/design/spec-0.2/mcp-validation.test.ts b/tests/design/spec-0.2/mcp-validation.test.ts new file mode 100644 index 00000000..de971b9d --- /dev/null +++ b/tests/design/spec-0.2/mcp-validation.test.ts @@ -0,0 +1,124 @@ +// Cladding · MCP efficacy boundary for Spec 0.2 (F-0a29d024). + +import {existsSync, mkdtempSync, rmSync} from 'node:fs'; +import {tmpdir} from 'node:os'; +import {join} from 'node:path'; + +import {Client} from '@modelcontextprotocol/sdk/client/index.js'; +import {InMemoryTransport} from '@modelcontextprotocol/sdk/inMemory.js'; +import {ToolListChangedNotificationSchema} from '@modelcontextprotocol/sdk/types.js'; +import {afterEach, describe, expect, test} from 'vitest'; + +import {refineOnboarding, resolveOnboardingReview} from '../../../src/cli/clarify.js'; +import {prepareHostClarify, prepareHostInit, renderHostDraft} from '../../../src/cli/host-onboarding.js'; +import {runInit} from '../../../src/cli/init.js'; +import {clearAuditObserversForTesting} from '../../../src/hitl/audit.js'; +import {buildServer, TOOL_NAMES} from '../../../src/serve/server.js'; +import { + loadValidationManifest, + TASK_PROFILE_TOOLS, + validateSpec02, +} from '../../../scripts/spec-0.2-validate.js'; + +const draft = { + mode: 'greenfield', + project_context: { + why: 'Make design changes auditable.', + problem: 'Design evidence is easy to overclaim.', + purpose: 'Validate Spec 0.2 before runtime cutover.', + }, + capabilities: [ + {id: 'design', title: 'Design', summary: 'Compile design requirements.', surface: 'feature'}, + {id: 'proof', title: 'Proof', summary: 'Preserve evidence boundaries.', surface: 'platform'}, + {id: 'transport', title: 'Transport', summary: 'Expose bounded tools.', surface: 'infrastructure'}, + ], + architecture: {layers: [{name: 'core', forbidden_imports: ['transport']}]}, + scenarios: [{slug: 'design-cycle', title: 'Design cycle', flow: 'A maintainer validates and accepts a design change.'}], + questions: [], +} as const; + +function payload(result: Awaited>): Record { + if (result.structuredContent !== undefined && result.structuredContent !== null) { + return result.structuredContent as Record; + } + const content = result.content as Array<{type: string; text: string}>; + return JSON.parse(content[0].text) as Record; +} + +describe('Spec 0.2 MCP validation', () => { + const temporary: string[] = []; + + afterEach(() => { + for (const path of temporary.splice(0)) rmSync(path, {recursive: true, force: true}); + clearAuditObserversForTesting(); + }); + + test('separates MCP conformance efficacy and adoption without vacuous success', async () => { + const report = await validateSpec02(process.cwd()); + const checks = Object.fromEntries(report.checks.map((check) => [check.id, check.status])); + expect(checks['mcp-wire-catalog']).toBe('pass'); + expect(checks['mcp-reference-host-spec-02-e2e']).toBe('not_run'); + expect(checks['mcp-adoption']).not.toBe('pass'); + expect(checks['live-host-token-ab']).toBe('not_run'); + expect(report.mcp.host_smoke?.scope).toBe('legacy-read-surface'); + expect(loadValidationManifest(process.cwd()).mcp_scenarios).toHaveLength(12); + }); + + test('negotiates dynamic tool discovery and emits list-changed after bootstrap', async () => { + const dir = mkdtempSync(join(tmpdir(), 'clad-spec-02-mcp-')); + temporary.push(dir); + const server = buildServer({ + cwd: dir, + name: 'cladding-spec-02-mcp-test', + version: '0.0.0-test', + onboarding: { + renderDraft: (value) => renderHostDraft(value as Parameters[0]), + prepareInit: ({cwd, mode, intent}) => prepareHostInit(cwd, mode, intent), + initialize: runInit, + prepareClarify: (answer, {cwd}) => prepareHostClarify(cwd, answer), + clarify: refineOnboarding, + resolveReview: (targets, {cwd}) => resolveOnboardingReview(targets, {cwd}), + }, + }); + const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair(); + const client = new Client({name: 'spec-02-mcp-test', version: '0.0.0-test'}); + let listChanged = 0; + client.setNotificationHandler(ToolListChangedNotificationSchema, () => { + listChanged++; + }); + await Promise.all([server.connect(serverTransport), client.connect(clientTransport)]); + try { + expect(client.getServerCapabilities()?.tools?.listChanged).toBe(true); + expect((await client.listTools()).tools).toHaveLength(3); + const prepared = payload(await client.callTool({ + name: 'clad_prepare_init', + arguments: {mode: 'idea', intent: 'Spec 0.2 validation project'}, + })); + const result = await client.callTool({ + name: 'clad_init', + arguments: { + token: prepared.token, + confirmation: prepared.approvalChallenge, + draft, + }, + }); + expect(result.isError).not.toBe(true); + expect(existsSync(join(dir, 'spec.yaml'))).toBe(true); + expect((await client.listTools()).tools.map((tool) => tool.name).sort()) + .toEqual([...TOOL_NAMES].sort()); + expect(listChanged).toBeGreaterThan(0); + } finally { + await client.close(); + await server.close(); + } + }); + + test('classifies every shipped tool while keeping the task-scoped catalog a challenger', async () => { + const classified = new Set(Object.values(TASK_PROFILE_TOOLS).flat()); + expect([...classified].sort()).toEqual([...TOOL_NAMES].sort()); + const report = await validateSpec02(process.cwd()); + expect(report.mcp.task_profile_reduction_ratio).toBeGreaterThanOrEqual(0.2); + expect(report.checks.find((check) => check.id === 'mcp-task-profile-challenger')?.status) + .toBe('inconclusive'); + }); +}); diff --git a/tests/design/spec-0.2/requirements.yaml b/tests/design/spec-0.2/requirements.yaml new file mode 100644 index 00000000..8e91cd0d --- /dev/null +++ b/tests/design/spec-0.2/requirements.yaml @@ -0,0 +1,109 @@ +# Cladding · Spec 0.2 validation ledger — proof projection, never design authority. +schema: 1 +decisions: + - {id: D01, owner: docs/design/spec-0.2/model-and-migration.md, scenario: authority-boundary, implementation: pending} + - {id: D02, owner: docs/design/spec-0.2/model-and-migration.md, scenario: why-what-how, implementation: pending} + - {id: D03, owner: docs/design/spec-0.2/model-and-migration.md, scenario: artifact-layout, implementation: pending} + - {id: D04, owner: docs/design/spec-0.2/model-and-migration.md, scenario: identity-sharding, implementation: pending} + - {id: D05, owner: docs/design/spec-0.2/model-and-migration.md, scenario: project-contract, implementation: pending} + - {id: D06, owner: docs/design/spec-0.2/model-and-migration.md, scenario: criterion-contract, implementation: pending} + - {id: D07, owner: docs/design/spec-0.2/model-and-migration.md, scenario: capability-ownership, implementation: pending} + - {id: D08, owner: docs/design/spec-0.2/model-and-migration.md, scenario: architecture-contract, implementation: pending} + - {id: D09, owner: docs/design/spec-0.2/model-and-migration.md, scenario: scenario-contract, implementation: pending} + - {id: D10, owner: docs/design/spec-0.2/model-and-migration.md, scenario: compiler-registry, implementation: pending} + - {id: D11, owner: docs/design/spec-0.2/proof-and-editing.md, scenario: test-binding, implementation: pending} + - {id: D12, owner: docs/design/spec-0.2/proof-and-editing.md, scenario: transactional-editing, implementation: pending} + - {id: D13, owner: docs/design/spec-0.2/proof-and-editing.md, scenario: attestation-freshness, implementation: pending} + - {id: D14, owner: docs/design/spec-0.2/model-and-migration.md, scenario: schema-migration, implementation: pending} + - {id: D15, owner: docs/design/spec-0.2/delivery.md, scenario: delivery-order, implementation: pending} + - {id: D16, owner: docs/design/spec-0.2/delivery.md, scenario: acceptance-gates, implementation: pending} + - {id: D17, owner: docs/design/spec-0.2/graph.md, scenario: graph-fidelity, implementation: pending} + - {id: D18, owner: docs/design/spec-0.2/hygiene.md, scenario: authority-retirement, implementation: pending} + - {id: D19, owner: docs/design/spec-0.2/context-and-orchestration.md, scenario: context-accounting, implementation: pending} + - {id: D20, owner: docs/design/spec-0.2/context-and-orchestration.md, scenario: evidence-orchestration, implementation: pending} + - {id: D21, owner: docs/design/spec-0.2/assurance.md, scenario: assurance-kernel, implementation: pending} + - {id: D22, owner: docs/design/spec-0.2/assurance.md, scenario: assurance-scheduler, implementation: pending} + - {id: D23, owner: docs/design/spec-0.2/assurance.md, scenario: assurance-verdict, implementation: pending} + - {id: D24, owner: docs/design/spec-0.2/mcp.md, scenario: mcp-role, implementation: validation-active} + +preregistered_cases: + - {id: P01, decision: D06} + - {id: P02, decision: D06} + - {id: P03, decision: D06} + - {id: P04, decision: D06} + - {id: P05, decision: D06} + - {id: P06, decision: D06} + - {id: P07, decision: D06} + - {id: P08, decision: D06} + - {id: P09, decision: D06} + - {id: P10, decision: D06} + - {id: L01, decision: D14} + - {id: L02, decision: D14} + - {id: L03, decision: D14} + - {id: L04, decision: D14} + - {id: B01, decision: D14} + - {id: B02, decision: D14} + - {id: B03, decision: D14} + - {id: B04, decision: D14} + - {id: B05, decision: D14} + - {id: B06, decision: D14} + - {id: C01, decision: D11} + - {id: C02, decision: D11} + - {id: C03, decision: D11} + - {id: C04, decision: D11} + - {id: C05, decision: D11} + - {id: C06, decision: D11} + - {id: T01, decision: D12} + - {id: T02, decision: D12} + - {id: T03, decision: D12} + - {id: T04, decision: D12} + - {id: U01, decision: D14} + - {id: U02, decision: D14} + - {id: U03, decision: D14} + - {id: U04, decision: D14} + - {id: A01, decision: D13} + - {id: A02, decision: D13} + - {id: A03, decision: D13} + +integration_journeys: + - {id: J01, decisions: [D02, D05, D06, D09], status: simulated, scenario: why-ladder} + - {id: J02, decisions: [D04, D06, D11], status: simulated, scenario: composite-identity-and-binding} + - {id: J03, decisions: [D07, D12], status: simulated, scenario: feature-local-capability-ownership} + - {id: J04, decisions: [D08, D13], status: implementation_pending, scenario: architecture-constraint-freshness} + - {id: J05, decisions: [D09, D21], status: implementation_pending, scenario: scenario-policy-obligation} + - {id: J06, decisions: [D03, D10, D14], status: implementation_pending, scenario: atomic-schema-upgrade} + - {id: J07, decisions: [D12, D15], status: implementation_pending, scenario: transaction-crash-recovery} + - {id: J08, decisions: [D11, D20, D21], status: simulated, scenario: proof-observation-and-provenance} + - {id: J09, decisions: [D13, D23], status: implementation_pending, scenario: selective-attestation-staleness} + - {id: J10, decisions: [D17, D19], status: simulated, scenario: bounded-graph-projection} + - {id: J11, decisions: [D19, D20], status: simulated, scenario: topology-invariant-context} + - {id: J12, decisions: [D21, D22, D23], status: simulated, scenario: cadence-verdict-equivalence} + - {id: J13, decisions: [D15, D16, D24], status: not_run, scenario: reference-host-spec-02-cycle} + +mcp_scenarios: + - MCP01-handshake-and-capabilities + - MCP02-bootstrap-and-list-changed + - MCP03-text-and-structured-content-parity + - MCP04-cli-and-kernel-semantic-parity + - MCP05-prepare-apply-replay-and-rollback + - MCP06-concurrent-write-and-stale-revision + - MCP07-malformed-oversized-and-path-inputs + - MCP08-graph-context-and-catalog-budgets + - MCP09-receipt-and-blindness-boundary + - MCP10-tools-only-host-degradation + - MCP11-reference-host-spec-02-cycle + - MCP12-adoption-versus-delivery-telemetry + +host_ab_tasks: + - {id: AB01, profile: implement, objective: locate-feature-contract, fault_control: wrong-feature} + - {id: AB02, profile: implement, objective: locate-composite-criterion, fault_control: bare-id-collision} + - {id: AB03, profile: implement, objective: predict-shared-module-impact, fault_control: sibling-overexpansion} + - {id: AB04, profile: implement, objective: retrieve-prerequisite-closure, fault_control: reverse-direction} + - {id: AB05, profile: spec-edit, objective: set-feature-purpose, fault_control: stale-revision} + - {id: AB06, profile: spec-edit, objective: upsert-atomic-criterion, fault_control: multiple-modal} + - {id: AB07, profile: spec-edit, objective: link-capability, fault_control: shared-catalog-write} + - {id: AB08, profile: spec-edit, objective: edit-disjoint-shards, fault_control: same-shard-race} + - {id: AB09, profile: verify, objective: run-scoped-proof, fault_control: unrelated-pass} + - {id: AB10, profile: verify, objective: explain-current-failure, fault_control: stale-observation} + - {id: AB11, profile: verify, objective: obtain-independent-proof, fault_control: asserted-blindness} + - {id: AB12, profile: observe, objective: inspect-cycle-and-adoption, fault_control: push-as-pull} diff --git a/tests/readme-record-honesty.test.ts b/tests/readme-record-honesty.test.ts index 79a78fe9..163cc6ec 100644 --- a/tests/readme-record-honesty.test.ts +++ b/tests/readme-record-honesty.test.ts @@ -12,7 +12,7 @@ // These are cladding-SELF pins (they read this repo's own files), NOT shipped // detectors — an adopting project may word its own README however it likes. -import {readFileSync} from 'node:fs'; +import {readFileSync, readdirSync} from 'node:fs'; import {join} from 'node:path'; import {describe, expect, test} from 'vitest'; @@ -24,6 +24,27 @@ const EN_VARIANTS = ['README.md', 'README.html']; const KO_VARIANTS = ['README.ko.md', 'README.ko.html']; const ALL_VARIANTS = [...EN_VARIANTS, ...KO_VARIANTS]; +test('current worktree feature counts match the canonical inventory', () => { + const featureFiles = readdirSync(join(ROOT, 'spec', 'features')) + .filter((name) => name.endsWith('.yaml') || name.endsWith('.yml')); + const total = featureFiles.length; + const done = featureFiles.filter((name) => + /^status:\s*done\s*$/m.test(read(`spec/features/${name}`)), + ).length; + const claims: Readonly> = { + 'README.md': [`${done} of its ${total} features`, 'v0.9.4 worktree', `${total} (${done} done)`], + 'README.ko.md': [`기능 ${total}개 중 ${done}개`, 'v0.9.4 worktree', `${total} · ${done} done`], + 'README.ja.md': [`${total} 個の feature のうち ${done} 個`, 'v0.9.4 worktree', `${total}(${done} done)`], + 'README.zh.md': [`${total} 个 feature 里有 ${done} 个`, 'v0.9.4 worktree', `${total}(${done} done)`], + 'README.html': [`${done} of its ${total} features`, '2026-08 · worktree', `>${total}`, `>${done} done · self-spec`], + 'README.ko.html': [`기능 ${total}개 중 ${done}개`, '2026-08 · worktree', `>${total}`, `>${done} done · 자기 스펙`], + }; + for (const [file, expected] of Object.entries(claims)) { + const body = read(file); + for (const claim of expected) expect(body, `${file}: ${claim}`).toContain(claim); + } +}); + describe('AC-ce8fe171 · record claim states the verified level in every variant', () => { test('EN variants pin what -> committed content, who/when -> local session ledger', () => { for (const f of EN_VARIANTS) { From 05f0c645b7a522cc3f918ae7f7288b86705f9fbf Mon Sep 17 00:00:00 2001 From: qwerfunch Date: Sat, 29 Aug 2026 02:13:13 +0900 Subject: [PATCH 2/2] fix(test): make MCP host smoke fixture-independent --- scripts/spec-0.2-validate.ts | 8 ++++- spec/attestation.yaml | 4 +-- tests/design/spec-0.2/mcp-validation.test.ts | 31 ++++++++++++++++++-- 3 files changed, 38 insertions(+), 5 deletions(-) diff --git a/scripts/spec-0.2-validate.ts b/scripts/spec-0.2-validate.ts index 98915800..8d66af55 100644 --- a/scripts/spec-0.2-validate.ts +++ b/scripts/spec-0.2-validate.ts @@ -589,7 +589,13 @@ async function inspectMcp(cwd: string): Promise { } } -function summarizeHostSmoke(cwd: string): HostSmokeSummary | null { +/** + * Summarizes optional local host evidence without manufacturing a missing run. + * + * @param cwd Workspace whose local audit directory may contain host smoke data. + * @returns The latest local host summary, or null when no run was recorded. + */ +export function summarizeHostSmoke(cwd: string): HostSmokeSummary | null { const auditDir = join(cwd, '.cladding', 'audit'); let names: string[]; try { diff --git a/spec/attestation.yaml b/spec/attestation.yaml index 512ae2f3..651535df 100644 --- a/spec/attestation.yaml +++ b/spec/attestation.yaml @@ -120,7 +120,7 @@ attested_modules: scripts/build.mjs: 3a4b204063024ef1 scripts/migrate-dogfood-v0.3.16.mjs: 1e265fb370019996 scripts/shard-spec.ts: 0c728bbc1e869421 - scripts/spec-0.2-validate.ts: c0206f5a21919385 + scripts/spec-0.2-validate.ts: 37ed4756d6e68803 scripts/test-count.d.mts: a392f5dea372a40e scripts/test-count.mjs: aea2620221c8d5ff scripts/version-bump.mjs: 770b066b8279db39 @@ -371,7 +371,7 @@ attested_modules: tests/cli/intent-onboarding.test.ts: 0681b98ce2e74c22 tests/conformance/registry.test.ts: 018b1e5c0d8d4baf tests/design/spec-0.2/design-validation.test.ts: 55d5a65deac3da53 - tests/design/spec-0.2/mcp-validation.test.ts: ca20015a268ce3bb + tests/design/spec-0.2/mcp-validation.test.ts: 9c14f76982a757d3 tests/design/spec-0.2/requirements.yaml: 0a46cc965db9add8 tests/drive/loop.test.ts: ae49bcfa745a8cdb tests/events/log.test.ts: 221f74acfdb7f7c0 diff --git a/tests/design/spec-0.2/mcp-validation.test.ts b/tests/design/spec-0.2/mcp-validation.test.ts index de971b9d..dcc8a11c 100644 --- a/tests/design/spec-0.2/mcp-validation.test.ts +++ b/tests/design/spec-0.2/mcp-validation.test.ts @@ -1,6 +1,6 @@ // Cladding · MCP efficacy boundary for Spec 0.2 (F-0a29d024). -import {existsSync, mkdtempSync, rmSync} from 'node:fs'; +import {existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync} from 'node:fs'; import {tmpdir} from 'node:os'; import {join} from 'node:path'; @@ -16,6 +16,7 @@ import {clearAuditObserversForTesting} from '../../../src/hitl/audit.js'; import {buildServer, TOOL_NAMES} from '../../../src/serve/server.js'; import { loadValidationManifest, + summarizeHostSmoke, TASK_PROFILE_TOOLS, validateSpec02, } from '../../../scripts/spec-0.2-validate.js'; @@ -60,8 +61,34 @@ describe('Spec 0.2 MCP validation', () => { expect(checks['mcp-reference-host-spec-02-e2e']).toBe('not_run'); expect(checks['mcp-adoption']).not.toBe('pass'); expect(checks['live-host-token-ab']).toBe('not_run'); - expect(report.mcp.host_smoke?.scope).toBe('legacy-read-surface'); + if (report.mcp.host_smoke !== null) { + expect(report.mcp.host_smoke.scope).toBe('legacy-read-surface'); + } expect(loadValidationManifest(process.cwd()).mcp_scenarios).toHaveLength(12); + + const dir = mkdtempSync(join(tmpdir(), 'clad-spec-02-host-smoke-')); + temporary.push(dir); + expect(summarizeHostSmoke(dir)).toBeNull(); + const auditDir = join(dir, '.cladding', 'audit'); + mkdirSync(auditDir, {recursive: true}); + writeFileSync( + join(auditDir, 'host-smoke-fixture.json'), + JSON.stringify({ + hosts: { + codex: { + grade: 'verified', + surfaces: [{evidence: 'tokens used 1,234'}], + }, + }, + }), + ); + expect(summarizeHostSmoke(dir)).toEqual({ + file: join('.cladding', 'audit', 'host-smoke-fixture.json'), + hosts_verified: ['codex'], + hosts_failed: [], + provider_reported_tokens: {codex: [1234]}, + scope: 'legacy-read-surface', + }); }); test('negotiates dynamic tool discovery and emits list-changed after bootstrap', async () => {