diff --git a/CHANGELOG.md b/CHANGELOG.md index 265b63e..78e39ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## 0.15.3 - 2026-05-22 + +### Added + +- **I7 now ships a canonical scope-closure packet trail** — `.runs/i7/` records the frozen basis, bounded-generics packet, async/IO packet, decision freeze, authority merge, and explicit I8 handoff for the post-I6 Rust V1 closeout. + +### Changed + +- **Repo-facing docs now ratify one final pre-I8 Rust V1 story** — bounded generics defer to `V1.1`, Rust V1 stays synchronous-only, `BENCH-CROSSLIB` remains the companion negative-proof wall, and I8 inherits the existing five-command proof wall unchanged. + ## 0.15.2 - 2026-05-21 ### Added diff --git a/DECISIONS.md b/DECISIONS.md index 51eab2a..2972a69 100644 --- a/DECISIONS.md +++ b/DECISIONS.md @@ -8,6 +8,31 @@ This file records project-level decisions that should remain stable across the r The ICP for v0.5 is a solo engineer or a small team of roughly 2-5 people who use AI coding assistants every day, work on systems where correctness matters, and want generated code they can trust because it is governed by explicit spec contracts and machine-readable evidence rather than terminal scraping or guesswork. +--- + +## 2026-05-22 — Rust V1 Scope Closure (I7) + +**Decision** + +Rust V1 stays narrow, synchronous, and benchmark-backed for the I8 proof run: + +- bounded generics defer to `V1.1` +- async flows, runtime adapters, and IO-owned boundaries defer to `V1.1` +- `BENCH-CROSSLIB` stays active as companion negative proof and never earns positive supported credit +- `I8` uses the existing five-command proof wall without any new slice-specific commands + +**Rationale** + +- The existing `BENCH-ECOM` and `BENCH-SERVICE` walls already prove one honest narrow-core Rust claim. +- No bounded-generic or async/IO slice could be named in one paragraph with believable proof commands on top of the frozen I3.5 wall. +- Explicit deferral is safer than widening the Rust V1 claim by prose drift or by importing runtime/framework expectations as ambient support. + +**Revisit when** + +- A future milestone can name one bounded generic or async/IO slice, its exact repo surfaces, and the exact added proof commands required to prove it honestly. + +--- + ## 2026-04-02 — Validation Strategy (0.1.x–0.2.x): JSON Schema **Decision** @@ -49,6 +74,7 @@ Until then: JSON Schema + semantic validation in Rust is the implementation path - The pipeline commands are optimized for validate → generate → cargo execution in one local flow. - The existing `.spec-generated` marker and output cleanup rules already model the output tree as spec-owned. - Teams that want committed generated code for diffs or IDE discoverability can continue to use `spec generate` directly and commit that output intentionally. +- Co-located proof artifacts may still be tracked by examples or fixtures, so no-op `spec generate` runs should avoid timestamp-only passport rewrites when no proof or authored truth changed. **Revisit when** diff --git a/ORCH_PLAN.md b/ORCH_PLAN.md index 383193f..189e597 100644 --- a/ORCH_PLAN.md +++ b/ORCH_PLAN.md @@ -1,652 +1,870 @@ -# I6 Orchestration Plan +# M101 Orchestration Plan -Status: **authoritative execution runbook** -Milestone: **I6 Rust V1 service benchmark activation** +Status: **authoritative execution runbook for the current `PLAN.md` wedge** +Milestone: **M101** Plan authority: **`/home/azureuser/__Active_Code/atomize-hq/spec/PLAN.md`** -Frozen basis: **current `HEAD` of `codex/i6-service-benchmark-activation` when the run begins** +Historical context only: **prior `ORCH_PLAN.md` content is superseded in full** Primary workspace: **`/home/azureuser/__Active_Code/atomize-hq/spec`** -Last rewritten: **2026-05-21** +Parent baseline branch: **`feat/i8-final-proof-run`** +Parent baseline head: **`4c41fb36845b30d5527554b6a365f15f6fa58bc5`** +Last rewritten: **2026-05-25** ## Summary -- Execute from `/home/azureuser/__Active_Code/atomize-hq/spec`. -- Treat `PLAN.md` as the only milestone authority. -- Treat the existing `ORCH_PLAN.md` as stale I5 context only. -- Keep the live primary checkout on `codex/i6-service-benchmark-activation` as the parent lane and canonical run-state root. -- Use dedicated worker worktrees under `/home/azureuser/__Active_Code/atomize-hq/.worktrees/spec-i6/`. -- All worker lanes use **GPT-5.4** with **`reasoning_effort=high`**. -- Cap concurrency at **2 active worker lanes**: - - Lane A runs first alone - - Lane B and conditional Lane C may overlap only after the Lane A freeze commit exists - - Lane D starts only after the post-B/C truth gate passes -- The parent agent remains the **only integrator**, **only merge authority**, and **only final acceptance authority**. -- Keep the critical path local to the parent for: - - basis freeze - - queue freeze - - service-freeze record - - worker launch - - gate decisions - - merge order - - final proof wall - - final fast-forward of the parent branch - -## Starting Truth - -Observed on `codex/i6-service-benchmark-activation` at the then-current `HEAD`: - -- `cargo run -p spec-cli -- status . --format json` reports: - - `schema_version: 4` - - `scope_authority: "inventory_only"` - - `BENCH-CROSSLIB` is `active`, `passing`, and `positive_credit_cases: 0` - - `BENCH-ECOM` is `active`, `passing`, and `readability_review_status: "current"` - - `BENCH-SERVICE` is `reserved`, `accounting_status: "reserved_missing_cases"`, and `summary.total_cases: 0` -- `benchmarks/labels.json` already declares: - - `id: "BENCH-SERVICE"` - - `root: "examples/service/units"` - - `generated_root: "examples/service/src/generated"` - - `readability_scope: "supported_closure"` - - `cases: []` -- `benchmarks/snapshots/BENCH-SERVICE.snapshot.json` exists and is still reserved-form. -- `benchmarks/reviews/BENCH-SERVICE.readability.review.json` does not exist. -- `examples/service/` does not exist. -- `spec-cli/tests/rust_v1_service.rs` does not exist. -- `.runs/` already exists and can host `.runs/i6/`. -- Existing reusable patterns live in: - - `examples/ecommerce/units/pricing/discount_strategy.unit.spec` - - `examples/ecommerce/units/pricing/pricing_quote.unit.spec` - - `examples/ecommerce/units/pricing/*.test.spec` - - `spec-cli/tests/rust_v1_closure.rs` - - `spec-cli/tests/fixtures/benchmarks/**` - - `spec-cli/tests/fixtures/m19/semantic_falsification_pack/units/billing/**` +- This runbook executes the current category-truth wedge exactly as defined by + `PLAN.md`. It does not re-scope the wedge and it does not widen supported + surface area. +- The parent agent keeps the producer contract and the final CLI integration + local. Those are the two drift-prone points. +- The only safe parallel window is after the contract spine lands: + benchmark-core adoption and export projection adoption may run in parallel. +- `spec-cli/src/commands.rs` is a known merge-conflict hotspot because + `PLAN.md` requires one shared helper there. No worker lane may touch it. +- Final snapshot/readability refresh happens only after the benchmark, export, + and CLI projections agree from the same parent baseline. + +## Outcome Target + +This wedge is complete only when the branch can say all of the following +honestly with fresh local verification: + +- there is one producer-owned category registry in `spec-core` +- category qualification is computed by one shared function +- benchmark, status, export, and snapshot surfaces all consume that same + qualification result +- `BENCH-ECOM` remains passing +- `BENCH-SERVICE` full projection flips to `accounting_status = invalid`, + `benchmark_status = invalid`, and `gate_status = open` +- `spec status --format json` and `spec export` both expose additive + `category_qualification` +- export keeps qualification read-side only and does not persist it into + `.spec.passport.json` ## Hard Guards -- Do not widen M66 support. -- Do not reopen M68 benchmark design or command-scope mechanics. -- Do not introduce async, IO, traits, generics, lifetimes, framework-heavy authored surfaces, or cross-library service proof. -- Keep `BENCH-SERVICE` single-library under `examples/service/**`. -- Keep the positive service roster exactly: - - `billing/apply_membership_discount` - - `billing/apply_regional_fee` - - `billing/checkout_net_total` - - `billing/checkout_net_total_guarded_fee` - - `billing/discount_strategy` - - `billing/pricing_quote` -- Keep the required molecule roster exactly: - - `billing/checkout_success_flow` - - `billing/checkout_declined_discount_flow` - - `billing/discount_strategy_quote_flow` -- Treat `.unit.spec` and `.test.spec` files as authored truth. -- Treat generated Rust, passports, molecule evidence, snapshots, and readability reviews as derived or observation surfaces. -- Allow projection/core edits only if the active service benchmark exposes a real read-side truth bug. -- Keep `BENCH-ECOM` and `BENCH-CROSSLIB` green and truthful throughout I6. -- Keep repo-root `status . --format json` diagnostic-only. It must remain `scope_authority: "inventory_only"` and is not a zero-exit acceptance gate. - -## Worktree And Branch Plan - -Create the I6 worktree root once: +- Preserve the parent baseline exactly as: + - branch: `feat/i8-final-proof-run` + - head: `4c41fb36845b30d5527554b6a365f15f6fa58bc5` +- Treat `PLAN.md` as the authority. Do not change wedge scope in the + orchestration flow. +- Do not touch files outside the lane-owned write set. +- Do not revert unrelated working-tree edits. +- Do not change `benchmarks/labels.json`. +- Do not add a checked-in external registry file. +- Do not persist `category_qualification` into `.spec.passport.json`. +- Do not let any worker lane edit `spec-cli/src/commands.rs`. +- Do not let any lane infer support from benchmark labels, health, or semantic + review presence alone. +- Do not merge a lane that passes its local tests but fails an out-of-scope + verification command. +- Stop and escalate if the parent baseline head is no longer the branch head + when execution starts. The runbook assumes that exact commit as the frozen + parent base. + +## Parent Ownership + +The parent agent owns all of the following for the full run: + +- the canonical checkout at + `/home/azureuser/__Active_Code/atomize-hq/spec` +- all state files under `.runs/category-truth/` +- the contract spine: + - `spec-core/src/category_truth.rs` + - `spec-core/src/lib.rs` + - `spec-core/src/semantic_review.rs` +- the `commands.rs` shared-helper consolidation and all final CLI integration: + - `spec-cli/src/commands.rs` + - `spec-cli/tests/cli.rs` + - `spec-cli/tests/m14_regressions.rs` +- final snapshot/readability refresh: + - `benchmarks/snapshots/*.snapshot.json` + - `benchmarks/reviews/*.readability.review.json` +- merge acceptance, conflict resolution, final verification, and closeout + +Workers own only their explicit lane write sets and worker-local artifacts. +Workers do not own parent state, merge decisions, or final interpretation. + +## Subagent Execution Policy + +- All worker lanes use fresh `GPT-5.4` subagents with + `reasoning_effort=high`. +- Maximum worker concurrency is `2`. +- The benchmark lane and export lane are the only lanes allowed to run in + parallel. +- The parent agent is the only integrator. +- The parent agent is the only writer of canonical run-state files under + `/home/azureuser/__Active_Code/atomize-hq/spec/.runs/category-truth/`. +- Worker-produced notes, sentinels, and verification captures are advisory + until the parent reviews them and updates canonical state. +- Worker completion is not acceptance. Acceptance happens only when the parent: + - reviews the lane output + - updates `tasks.json` + - updates the canonical per-task sentinel + - records accepted verification artifacts in canonical run-state paths + +## Worktree Layout + +Parent checkout: + +- `/home/azureuser/__Active_Code/atomize-hq/spec` + +Worker worktree root: + +- `/home/azureuser/__Active_Code/atomize-hq/.worktrees/spec-category-truth` + +Worker worktrees and branches: + +- benchmark lane: + - branch: `ws/ct-benchmark` + - worktree: + `/home/azureuser/__Active_Code/atomize-hq/.worktrees/spec-category-truth/benchmark` +- export lane: + - branch: `ws/ct-export` + - worktree: + `/home/azureuser/__Active_Code/atomize-hq/.worktrees/spec-category-truth/export` +- conditional blocker lane: + - branch: `ws/ct-blocker` + - worktree: + `/home/azureuser/__Active_Code/atomize-hq/.worktrees/spec-category-truth/blocker` + +No dedicated worker worktree is created for `commands.rs`. That lane stays in +the parent checkout on the parent baseline branch. + +Exact worktree creation commands after Gate 1: ```bash -mkdir -p /home/azureuser/__Active_Code/atomize-hq/.worktrees/spec-i6 +mkdir -p /home/azureuser/__Active_Code/atomize-hq/.worktrees/spec-category-truth +git -C /home/azureuser/__Active_Code/atomize-hq/spec worktree add /home/azureuser/__Active_Code/atomize-hq/.worktrees/spec-category-truth/benchmark -b ws/ct-benchmark feat/i8-final-proof-run +git -C /home/azureuser/__Active_Code/atomize-hq/spec worktree add /home/azureuser/__Active_Code/atomize-hq/.worktrees/spec-category-truth/export -b ws/ct-export feat/i8-final-proof-run ``` -Freeze the live basis before any worker branch is created: +Conditional blocker worktree command: ```bash -git -C /home/azureuser/__Active_Code/atomize-hq/spec rev-parse --abbrev-ref HEAD -git -C /home/azureuser/__Active_Code/atomize-hq/spec rev-parse --short HEAD +git -C /home/azureuser/__Active_Code/atomize-hq/spec worktree add /home/azureuser/__Active_Code/atomize-hq/.worktrees/spec-category-truth/blocker -b ws/ct-blocker feat/i8-final-proof-run ``` -Expected basis: +## Canonical State And Queue Files -- branch: `codex/i6-service-benchmark-activation` -- commit: record the current `HEAD` in `.runs/i6/basis.json` at queue freeze and use that as the run-local basis for all later drift checks +Run root: -Use the live primary checkout as the parent lane: +- `/home/azureuser/__Active_Code/atomize-hq/spec/.runs/category-truth` -- Parent branch: `codex/i6-service-benchmark-activation` -- Parent workspace: `/home/azureuser/__Active_Code/atomize-hq/spec` +Canonical parent-owned files: -Create Lane A first and only: +- `tasks.json` +- `session-log.md` +- `preflight.json` +- `merge-order.md` +- `blockers.md` +- `sentinels/.json` +- `verification/contract-spine/*` +- `verification/benchmark/*` +- `verification/export/*` +- `verification/cli/*` +- `verification/final/*` -```bash -git -C /home/azureuser/__Active_Code/atomize-hq/spec worktree add /home/azureuser/__Active_Code/atomize-hq/.worktrees/spec-i6/lane-a -b codex/i6-lane-a-service-root codex/i6-service-benchmark-activation -``` - -After Lane A merges and the parent records `service_freeze_commit`, create integration and Lane B from that exact commit: - -```bash -git -C /home/azureuser/__Active_Code/atomize-hq/spec worktree add /home/azureuser/__Active_Code/atomize-hq/.worktrees/spec-i6/int -b codex/i6-int -git -C /home/azureuser/__Active_Code/atomize-hq/spec worktree add /home/azureuser/__Active_Code/atomize-hq/.worktrees/spec-i6/lane-b -b codex/i6-lane-b-service-regressions -``` - -Create Lane C only if the parent confirms a real read-side truth bug: - -```bash -git -C /home/azureuser/__Active_Code/atomize-hq/spec worktree add /home/azureuser/__Active_Code/atomize-hq/.worktrees/spec-i6/lane-c -b codex/i6-lane-c-projection-fix -``` - -Create Lane D only after Gate 2 passes: +Worker-local advisory files: -```bash -git -C /home/azureuser/__Active_Code/atomize-hq/spec worktree add /home/azureuser/__Active_Code/atomize-hq/.worktrees/spec-i6/lane-d -b codex/i6-lane-d-closeout codex/i6-int -``` - -Final landing is parent-owned only: +- `.runs/category-truth-worker/summary.md` +- `.runs/category-truth-worker/verification/*` +- `.runs/category-truth-worker/sentinel.json` -```bash -git -C /home/azureuser/__Active_Code/atomize-hq/spec merge --ff-only codex/i6-int -``` +Task state vocabulary: -## Orchestration State +- `pending` +- `running` +- `worker_complete` +- `accepted` +- `rejected` +- `blocked` +- `done` -Canonical run state lives under: +The parent is the only writer of canonical state. `.runs/category-truth/` is +the single source of truth for run progress, lane status, acceptance, block +state, merge order, and accepted verification evidence. -- `I6_RUN_ROOT=/home/azureuser/__Active_Code/atomize-hq/spec/.runs/i6` -- queue: `/home/azureuser/__Active_Code/atomize-hq/spec/.runs/i6/tasks.json` -- session log: `/home/azureuser/__Active_Code/atomize-hq/spec/.runs/i6/session-log.md` -- basis record: `/home/azureuser/__Active_Code/atomize-hq/spec/.runs/i6/basis.json` -- Gate 1 record: `/home/azureuser/__Active_Code/atomize-hq/spec/.runs/i6/service-freeze.json` -- Gate 2 record: `/home/azureuser/__Active_Code/atomize-hq/spec/.runs/i6/service-truth-gate.json` -- Gate 3 record: `/home/azureuser/__Active_Code/atomize-hq/spec/.runs/i6/final-acceptance.json` -- per-task state: `/home/azureuser/__Active_Code/atomize-hq/spec/.runs/i6/task//` +### Per-task Sentinel Contract -The parent owns all writes under `.runs/i6/`. Workers do not update orchestration state directly. +Canonical sentinel path: -Per-task sentinels inside `.runs/i6/task//`: +- `/home/azureuser/__Active_Code/atomize-hq/spec/.runs/category-truth/sentinels/.json` -| Sentinel | Meaning | -| --- | --- | -| `QUEUED` | task frozen, not started | -| `RUNNING` | lane or parent actively executing | -| `BLOCKED` | waiting on parent decision or dependency | -| `READY` | lane believes acceptance is satisfied | -| `MERGED` | parent merged result | -| `REJECTED` | parent bounced lane for scope or quality reasons | +Required fields for every canonical sentinel: -Each task directory carries: +- `task_id` +- `owner` +- `branch` +- `worktree` +- `status` +- `started_at` +- `updated_at` +- `owned_write_set` +- `required_commands` +- `required_artifacts` +- `worker_summary_path` +- `worker_mirror_paths` +- `canonical_artifact_paths` +- `acceptance_notes` +- `rejection_notes` +- `blocker_notes` +- `accepted_commit` -- `scope.md` -- `acceptance.md` -- `handoff.md` -- `decisions.md` +Required status rules: -## Lane Map +- `pending`: task exists but work has not started +- `running`: owner is actively executing the task +- `worker_complete`: worker reports completion; parent review still pending +- `accepted`: parent accepted the lane and canonicalized its state +- `rejected`: parent rejected the lane; rerun required +- `blocked`: a hard guard or direct blocker stopped the lane +- `done`: parent-only terminal state for completed parent tasks -| Lane | Branch | Worktree | Owned write set | Goal | -| --- | --- | --- | --- | --- | -| Parent | `codex/i6-service-benchmark-activation` | `/home/azureuser/__Active_Code/atomize-hq/spec` | `.runs/i6/**`, gate records, merge decisions, final fast-forward | orchestration and final authority | -| Lane A | `codex/i6-lane-a-service-root` | `/home/azureuser/__Active_Code/atomize-hq/.worktrees/spec-i6/lane-a` | `benchmarks/labels.json`, `examples/service/**`, generated Rust under `examples/service/src/generated/**`, service passports, service molecule evidence | service scaffold, active labels, authored proof wall | -| Lane B | `codex/i6-lane-b-service-regressions` | `/home/azureuser/__Active_Code/atomize-hq/.worktrees/spec-i6/lane-b` | `spec-cli/tests/rust_v1_service.rs`, `spec-cli/tests/fixtures/benchmarks/**` | service regression truth and benchmark fixtures | -| Lane C | `codex/i6-lane-c-projection-fix` | `/home/azureuser/__Active_Code/atomize-hq/.worktrees/spec-i6/lane-c` | `spec-core/src/benchmark.rs`, `spec-cli/src/commands.rs` | minimal read-side truth fix only if needed | -| Lane D | `codex/i6-lane-d-closeout` | `/home/azureuser/__Active_Code/atomize-hq/.worktrees/spec-i6/lane-d` | `benchmarks/snapshots/BENCH-SERVICE.snapshot.json`, `benchmarks/reviews/BENCH-SERVICE.readability.review.json`, `README.md`, `docs/rust_v1_contract_stack.md`, `TODOS.md`, `CHANGELOG.md`, and only-if-truthfully-required closeout refreshes for existing ECOM/CROSSLIB snapshot or readability or doc surfaces | artifact/docs closeout only | -| Integration | `codex/i6-int` | `/home/azureuser/__Active_Code/atomize-hq/.worktrees/spec-i6/int` | merge-only plus minimal parent reconciliation | integration and gate execution | +Worker-local mirror sentinel path convention: -## Gate Model +- `/.runs/category-truth-worker/sentinel.json` -### Gate 0: Basis Freeze / Queue Freeze +Worker mirrors may include the same fields, but they remain non-canonical until +the parent copies the accepted facts into the canonical sentinel. -Owner: parent -Task id: `i6-a0-freeze-basis` +### Canonical vs Worker-local Verification Outputs -Advance only when: +Canonical verification outputs live only under: -- live branch is `codex/i6-service-benchmark-activation` -- live commit is the branch `HEAD` recorded in `basis.json` for this run -- `.runs/i6/` exists -- `basis.json`, `tasks.json`, and task packets exist -- frozen positive service roster and required molecules are recorded -- Lane A is the only worker branch created +- `/home/azureuser/__Active_Code/atomize-hq/spec/.runs/category-truth/verification/contract-spine/` +- `/home/azureuser/__Active_Code/atomize-hq/spec/.runs/category-truth/verification/benchmark/` +- `/home/azureuser/__Active_Code/atomize-hq/spec/.runs/category-truth/verification/export/` +- `/home/azureuser/__Active_Code/atomize-hq/spec/.runs/category-truth/verification/cli/` +- `/home/azureuser/__Active_Code/atomize-hq/spec/.runs/category-truth/verification/final/` -Reopens if: +Worker-local mirrors live only under: -- the live parent basis drifts before worker launch -- queue ownership or frozen rosters are unclear -- any worker needs to guess write-set or gate behavior +- `/home/azureuser/__Active_Code/atomize-hq/.worktrees/spec-category-truth/benchmark/.runs/category-truth-worker/verification/` +- `/home/azureuser/__Active_Code/atomize-hq/.worktrees/spec-category-truth/export/.runs/category-truth-worker/verification/` +- `/home/azureuser/__Active_Code/atomize-hq/.worktrees/spec-category-truth/blocker/.runs/category-truth-worker/verification/` -### Gate 1: Post-Lane-A Service-Freeze Gate +Rules: -Owner: parent -Task id: `i6-a1-service-freeze` +- worker captures remain mirrors until parent acceptance +- parent acceptance copies or re-runs the accepted verification outputs into + canonical verification paths +- rejected or blocked worker outputs stay worker-local and are referenced from + the canonical sentinel only as diagnostics +- `tasks.json` and canonical sentinels always outrank worker-local mirrors -Advance only when: +## Command Capture Rule -- `examples/service/**` exists as a real single-library benchmark root -- `benchmarks/labels.json` marks `BENCH-SERVICE` active -- labels list exactly the frozen six service units and three required molecules -- all six service units have fresh passports -- all three service molecules have fresh evidence -- the parent writes `service-freeze.json` with: - - `service_freeze_commit` - - frozen rosters - - current `label_digest` - - whether a projection bug is open +Every archived verification command writes both raw output and a sibling +`.exitcode` file. Use a `bash -lc` wrapper with `set -o pipefail` so captured +stdout does not mask failure. -Reopens Lane A if: +Locked pattern: -- authored service truth is incomplete, stale, invalid, or out of roster -- service root or labels drift from the frozen roster -- any claimed proof depends on widened support - -### Gate 2: Post-B/C Service-Truth Gate +```bash +bash -lc ' +set -o pipefail + | tee +cmd_status=$? +printf "%s\n" "$cmd_status" > +exit "$cmd_status" +' +``` -Owner: parent -Task id: `i6-d0-service-truth-gate` +If a command is expected to fail meaningfully, assert the expected exit code +explicitly in the wrapper and record that fact in the lane summary. -Advance only when: +## Workstream Matrix -- Lane B is merged into `codex/i6-int` -- Lane C is either merged or explicitly skipped -- `cargo run -p spec-cli -- status examples/service/units --format json` and `cargo run -p spec-cli -- export examples/service/units` project truthful service-root benchmark state -- service-root benchmark truth is passing with full proof -- service-root fixtures and assertions in Lane B are derived from direct service-root `status` and `export` truth, not only indirect unit-test expectations -- partial service scopes emit zero positive credit -- `BENCH-ECOM` remains green/current -- `BENCH-CROSSLIB` remains green with zero positive credit +| Workstream | Task id | Owner | Branch / worktree | Owned modules and artifacts | Start gate | +| --- | --- | --- | --- | --- | --- | +| Preflight freeze | `task/ct-p0-preflight` | parent | parent checkout | `.runs/category-truth/**` | run start | +| Contract spine | `task/ct-p1-contract-spine` | parent | parent checkout | `spec-core/src/category_truth.rs`, `spec-core/src/lib.rs`, `spec-core/src/semantic_review.rs`, substrate tests | Gate 0 | +| Benchmark adoption | `task/ct-b1-benchmark` | worker | `ws/ct-benchmark` | `spec-core/src/benchmark.rs`, `spec-cli/tests/rust_v1_service.rs`, `spec-cli/tests/rust_v1_closure.rs`, `spec-cli/tests/fixtures/benchmarks/*.json` | Gate 1 | +| Export adoption | `task/ct-c1-export` | worker | `ws/ct-export` | `spec-core/src/export.rs`, `spec-core/src/passport.rs`, export-focused tests | Gate 1 | +| CLI integration | `task/ct-p2-cli-integration` | parent | parent checkout | `spec-cli/src/commands.rs`, `spec-cli/tests/cli.rs`, `spec-cli/tests/m14_regressions.rs` | Gate 2 | +| Snapshot/readability refresh | `task/ct-p3-snapshot-refresh` | parent | parent checkout | `benchmarks/snapshots/*.snapshot.json`, `benchmarks/reviews/*.readability.review.json`, final expectation fixes caused by CLI output shape | Gate 3 | +| Conditional blocker lane | `task/ct-x1-blocker` | parent or worker | `ws/ct-blocker` only if needed | only the direct failing surface plus proof reruns | conditional | +| Final closeout | `task/ct-p4-closeout` | parent | parent checkout | `.runs/category-truth/**` | Gate 4 | -Reopens prior lanes if: +## Lane Boundaries -- service-root truth is wrong because authored proof or labels are wrong: reopen Lane A -- service-root truth is wrong because regression tests or fixtures are missing or dishonest: reopen Lane B -- service-root truth is wrong because projection/core behavior is wrong: reopen Lane C -- any cross-lane disagreement conflicts with `PLAN.md`: bounce to the owning lane, do not resolve creatively in integration +### Contract spine stays local -### Gate 3: Final Acceptance Gate +The parent keeps category substrate and semantic-review descriptor work in one +local stream because together they define the producer-owned truth contract. +This stream includes: -Owner: parent -Task id: `i6-e-final-acceptance` +- `spec-core/src/category_truth.rs` +- `spec-core/src/lib.rs` +- `spec-core/src/semantic_review.rs` +- unit tests proving: + - supported ecommerce descriptors qualify + - service seam siblings do not qualify as supported + - unsupported rows qualify only as unsupported + - missing `descriptor_id` is explicit failure -Advance only when: +Do not split category substrate and semantic-review descriptor work into +separate workers. The type boundary is too shared and the merge win is too +small. -- `BENCH-SERVICE` is active, passing, and gate-satisfied at service-root scope -- `BENCH-SERVICE` readability review is `current` -- committed `BENCH-SERVICE.snapshot.json` is stable on rerun -- service-root `status` and `export` remain the proof wall -- `BENCH-ECOM` and `BENCH-CROSSLIB` stay truthful -- repo-root `status . --format json` remains diagnostic inventory with `scope_authority: "inventory_only"` +### Benchmark lane -Reopens prior lanes if: +The benchmark lane owns benchmark truth adoption only: -- snapshot or readability truth drifts: reopen Lane D -- service-root proof wall drifts: reopen Lane A, B, or C based on cause -- docs overstate shipped truth: reopen Lane D +- `spec-core/src/benchmark.rs` +- benchmark-facing service and closure tests +- benchmark JSON fixtures under `spec-cli/tests/fixtures/benchmarks/` -## Conflict And Bounce-Back Rules +This lane must not touch: -- If a lane edits outside its write set, the parent rejects it. -- If a lane discovers required scope drift to the frozen positive roster or required molecule roster, it must stop, mark `BLOCKED`, and escalate to the parent. -- Lane A may not change regression suites, fixtures, snapshots, reviews, or docs. -- Lane B may not patch core behavior. If it requires `spec-core/src/benchmark.rs` or `spec-cli/src/commands.rs` changes, it must bounce to the parent and conditional Lane C. -- Lane C may not patch labels, specs, proof artifacts, tests, fixtures, snapshots, reviews, or docs. -- Lane D is closeout-only. It may not reopen service semantics, labels, tests, fixtures, or core behavior. -- Integration does not resolve creative cross-lane disagreements. It either: - - applies `PLAN.md` literally, or - - bounces the issue back to the owning lane -- If a lane believes another lane’s owned surface must change, it does not edit that surface directly. It returns a narrow parent escalation. +- `spec-cli/src/commands.rs` +- `spec-core/src/export.rs` +- `spec-core/src/passport.rs` +- `benchmarks/snapshots/*.snapshot.json` +- `benchmarks/reviews/*.readability.review.json` -## Shared Ownership And Artifact Policy +### Export lane -- `PLAN.md` is the only milestone authority. -- Source service specs are authored truth: - - `examples/service/units/**/*.unit.spec` - - `examples/service/units/**/*.test.spec` -- Lane A may refresh the derived service proof surfaces required by those specs: - - `examples/service/src/generated/**` - - `examples/service/units/**/*.spec.passport.json` - - `examples/service/units/**/*.test.evidence.json` -- No lane hand-edits generated Rust, passports, molecule evidence, snapshot JSON, or readability review JSON. -- Lane D alone owns committed snapshot and readability refresh for `BENCH-SERVICE`. -- If merged I6 truth makes `BENCH-ECOM` or `BENCH-CROSSLIB` snapshot or readability or closeout docs require refresh, that refresh belongs to Lane D and nowhere else. -- Lane B owns benchmark test fixtures, including any repo-root benchmark fixture JSON that changes because `BENCH-SERVICE` becomes active. -- Repo-root `status . --format json` is always interpreted as structured inventory, never as a zero-exit proof command. +The export lane owns export projection adoption only: -## Workstream Plan +- `spec-core/src/export.rs` +- `spec-core/src/passport.rs` +- export/passport regression coverage -### WS-PARENT-0 — basis freeze and queue freeze +This lane must not touch: -Task id: `i6-a0-freeze-basis` +- `spec-cli/src/commands.rs` +- benchmark fixtures +- snapshots or readability artifacts -Parent actions: - -- confirm live branch and commit -- create `.runs/i6/` -- write `basis.json` -- write `tasks.json`, `session-log.md`, and task packets -- create Lane A only -- freeze the exact positive roster and required molecule roster in run state +### CLI integration lane -Acceptance: +The parent-owned CLI lane owns the shared helper consolidation required by +`PLAN.md`. This lane must: -- Gate 0 is green -- no worker needs to guess branch names, worktree paths, write-sets, or gate rules +- add one shared `commands.rs` helper for projected unit truth +- wire `status`, benchmark read-side, and snapshot paths to that helper +- add `category_qualification` to status JSON +- keep benchmark snapshot output aligned with live benchmark projection -### WS-A — service scaffold, labels, and proof wall +No other lane may add benchmark-truth extraction logic in `spec-cli`. -Task id: `i6-a1-service-freeze` +### Snapshot/readability lane -Lane A owns: +The parent-owned refresh lane runs only after CLI integration is stable. It +owns: -- `benchmarks/labels.json` -- `examples/service/**` -- generated Rust under `examples/service/src/generated/**` -- service passports and service molecule evidence +- snapshot refresh for `BENCH-ECOM` and `BENCH-SERVICE` +- readability review refresh only as additive artifact maintenance +- no semantic reinterpretation of readability freshness -Required outcomes: +## Gate Model -- scaffold the service example -- author the six frozen service units -- author the three frozen molecule tests -- activate `BENCH-SERVICE` -- build generated Rust -- refresh all six unit passports -- refresh all three molecule evidence files +- Gate 0: preflight freeze + - confirm branch and exact head + - record current dirty files without reverting them + - create run state and worktree plan + - on pass, parent updates: + - `preflight.json` + - `tasks.json` + - `session-log.md` + - `sentinels/task-ct-p0-preflight.json` + - on fail, parent updates: + - `blockers.md` + - `tasks.json` + - `session-log.md` + - `sentinels/task-ct-p0-preflight.json` + - stop immediately on fail +- Gate 1: contract spine complete + - category substrate and semantic-review descriptor work merged locally + - substrate verification is green + - worker worktrees are created from this updated parent head only after this + - on pass, parent updates: + - `tasks.json` + - `session-log.md` + - `merge-order.md` + - `verification/contract-spine/*` + - `sentinels/task-ct-p1-contract-spine.json` + - on fail, parent updates: + - `blockers.md` + - `tasks.json` + - `session-log.md` + - `sentinels/task-ct-p1-contract-spine.json` + - do not spawn workers if Gate 1 fails +- Gate 2: benchmark and export lanes accepted + - both worker lanes return in-scope diffs only + - both lane verification sets are green + - worker completion alone is insufficient; each lane must be parent-reviewed + and explicitly marked `accepted` + - on pass, parent updates: + - `tasks.json` + - `session-log.md` + - `merge-order.md` + - `verification/benchmark/*` + - `verification/export/*` + - `sentinels/task-ct-b1-benchmark.json` + - `sentinels/task-ct-c1-export.json` + - on fail, parent updates: + - `blockers.md` + - `tasks.json` + - `session-log.md` + - rejected lane sentinel(s) + - if one lane is rejected, stop the run, document the rejection, and rerun + from a clean worker lane before proceeding +- Gate 3: CLI integration complete + - `commands.rs` helper is in place + - status, benchmark, export, and snapshot read-side surfaces agree + - on pass, parent updates: + - `tasks.json` + - `session-log.md` + - `verification/cli/*` + - `sentinels/task-ct-p2-cli-integration.json` + - on fail, parent updates: + - `blockers.md` + - `tasks.json` + - `session-log.md` + - `sentinels/task-ct-p2-cli-integration.json` + - conditional blocker lane may be activated only after this failure is + written canonically +- Gate 4: snapshot/readability refresh and full sweep complete + - frozen artifacts match live output + - no remaining consumer drift + - on pass, parent updates: + - `tasks.json` + - `session-log.md` + - `verification/final/*` + - `sentinels/task-ct-p3-snapshot-refresh.json` + - on fail, parent updates: + - `blockers.md` + - `tasks.json` + - `session-log.md` + - `sentinels/task-ct-p3-snapshot-refresh.json` + - blocker lane may run only after those files are updated +- Gate 5: closeout + - canonical state updated to `done` or `blocked` + - on pass, parent updates: + - `tasks.json` + - `session-log.md` + - `merge-order.md` + - `sentinels/task-ct-p4-closeout.json` + - on blocked stop, parent updates: + - `blockers.md` + - `tasks.json` + - `session-log.md` + - `sentinels/task-ct-p4-closeout.json` + +The flow is human-free by default. Pause only if a gate fails or a hard guard +is tripped. + +### Blocked Path + +When a task or gate blocks: + +- parent writes the blocker first to: + - `blockers.md` + - `tasks.json` + - the task sentinel + - `session-log.md` +- parent records: + - the failing command + - exit code + - affected files + - whether the blocker is rerunnable or terminal +- if the blocker is rerunnable within scope: + - rerun only the affected task commands + - then rerun all downstream parent verification gates +- if the blocker would require scope expansion or forbidden-file ownership: + - mark the run `blocked` + - do not proceed to downstream gates + - stop after closeout state is written + +## Execution Order + +1. Initialize `.runs/category-truth/` in the parent checkout. +2. Record branch, head, and dirty files in `preflight.json`, + `session-log.md`, `tasks.json`, and the preflight sentinel. +3. Execute the contract spine locally on `feat/i8-final-proof-run`. +4. Run contract-spine verification and archive accepted outputs under + `verification/contract-spine/`. +5. Update parent task state for `task/ct-p1-contract-spine` to `done`. +6. Create worker worktrees with the exact `git worktree add ... -b ws/...` + commands in this document. +7. Spawn exactly two fresh `GPT-5.4` worker lanes: + - benchmark + - export +8. Mark both worker tasks `running` in `tasks.json` and canonical sentinels + before the workers start editing. +9. Wait for worker completion via worker summaries and worker-local mirrors. +10. When a worker reports complete, mark only `worker_complete` in canonical + state. Do not mark `accepted` yet. +11. Review the export lane first: + - inspect owned-file diff + - inspect worker command exits + - inspect worker-local verification mirrors + - reject if any forbidden surface changed +12. If export lane is accepted: + - cherry-pick its commit into the parent branch + - copy or rerun accepted verification outputs into + `verification/export/` + - update `tasks.json`, `merge-order.md`, `session-log.md`, and the export + sentinel to `accepted` + - close the export worker +13. If export lane is rejected: + - write rejection details canonically + - close the worker + - stop and rerun that lane before benchmark acceptance proceeds +14. Review the benchmark lane with the same acceptance process. +15. If benchmark lane is accepted: + - cherry-pick its commit into the parent branch + - copy or rerun accepted verification outputs into + `verification/benchmark/` + - update `tasks.json`, `merge-order.md`, `session-log.md`, and the + benchmark sentinel to `accepted` + - close the benchmark worker +16. If benchmark lane is rejected: + - write rejection details canonically + - close the worker + - stop and rerun that lane before continuing +17. Run the parent-owned CLI integration lane in the parent checkout. +18. Archive accepted CLI verification outputs under `verification/cli/` and + update canonical state. +19. Refresh snapshots and readability artifacts locally from the post-CLI + truth. +20. Run the full verification sweep and archive accepted final outputs under + `verification/final/`. +21. If a direct blocker remains, activate `task/ct-x1-blocker`, run only the + narrow blocker repair, rerun the affected task verification, then rerun + the full verification sweep. +22. Update closeout state, mark the run `done` or `blocked`, and stop. + +This flow walks the current `PLAN.md` session to completion. The run is not +finished at code integration; it is finished only after canonical verification, +state closeout, and final wedge-truth confirmation are complete. + +## Integration And Merge Rules + +- The contract spine is not optional staging. It is the branch-local truth base. +- Each worker lane should produce one cohesive commit if possible. +- Parent integration uses non-interactive git only. +- Preferred integration method is `git cherry-pick -x ` into the + parent branch after parent review. +- Merge order is fixed: + - export first because it is the smaller, more isolated core surface + - benchmark second because its invalid `BENCH-SERVICE` truth becomes the + parent baseline for CLI and snapshot adoption +- Do not merge a worker branch directly if it touched a forbidden file. Reject + and rerun the lane instead. +- If a worker lane conflicts with unrelated dirty parent files, stop and + rebase the lane around the current parent tree without reverting those files. + +## Conflict Flags + +| Flag | Surface | Risk | Required handling | +| --- | --- | --- | --- | +| `CF-1` | `spec-cli/src/commands.rs` | highest merge-conflict hotspot; shared helper required | parent-only lane; no worker edits permitted | +| `CF-2` | `spec-core/src/semantic_review.rs` | producer contract drift if lane-split | keep inside parent contract spine | +| `CF-3` | `spec-cli/tests/fixtures/benchmarks/*.json` vs snapshots | fixture truth can drift from final CLI truth | benchmark lane owns benchmark fixtures; snapshots wait for parent refresh lane | +| `CF-4` | readability review artifacts | qualification failure can be misread as readability change | refresh artifact timestamps/content only as generated output; do not rewrite freshness semantics | +| `CF-5` | dirty non-owned files in workspace | accidental overwrite of teammate work | record in preflight; never reset or force-checkout them | + +## Workstream Detail + +### `task/ct-p0-preflight` + +Required parent actions: + +- record: + - branch + - head + - `git status --short` + - worker worktree plan +- initialize: + - `.runs/category-truth/tasks.json` + - `.runs/category-truth/session-log.md` + - `.runs/category-truth/preflight.json` + - `.runs/category-truth/sentinels/` + - `.runs/category-truth/verification/` + - `task/ct-p0-preflight` canonical sentinel Required commands: ```bash -cargo run -p spec-cli -- build examples/service/units --output examples/service/src/generated -cargo run -p spec-cli -- test examples/service/units/billing/apply_membership_discount.unit.spec -cargo run -p spec-cli -- test examples/service/units/billing/apply_regional_fee.unit.spec -cargo run -p spec-cli -- test examples/service/units/billing/checkout_net_total.unit.spec -cargo run -p spec-cli -- test examples/service/units/billing/checkout_net_total_guarded_fee.unit.spec -cargo run -p spec-cli -- test examples/service/units/billing/discount_strategy.unit.spec -cargo run -p spec-cli -- test examples/service/units/billing/pricing_quote.unit.spec -cargo run -p spec-cli -- test examples/service/units/billing/checkout_success_flow.test.spec -cargo run -p spec-cli -- test examples/service/units/billing/checkout_declined_discount_flow.test.spec -cargo run -p spec-cli -- test examples/service/units/billing/discount_strategy_quote_flow.test.spec -cargo run -p spec-cli -- status examples/service/units --format json -cargo run -p spec-cli -- export examples/service/units +git -C /home/azureuser/__Active_Code/atomize-hq/spec branch --show-current +git -C /home/azureuser/__Active_Code/atomize-hq/spec rev-parse HEAD +git -C /home/azureuser/__Active_Code/atomize-hq/spec status --short +mkdir -p /home/azureuser/__Active_Code/atomize-hq/spec/.runs/category-truth/{sentinels,verification} +mkdir -p /home/azureuser/__Active_Code/atomize-hq/.worktrees/spec-category-truth ``` -Hard rules: - -- no snapshot refresh -- no readability review authoring -- no test fixture edits -- no docs edits -- no core projection edits - Acceptance: -- Gate 1 is green -- if read-side truth is wrong, Lane A returns a narrow reproduced bug statement rather than patching core behavior itself +- branch is `feat/i8-final-proof-run` +- head is `4c41fb36845b30d5527554b6a365f15f6fa58bc5` +- unrelated dirty files are recorded, not reverted +- `tasks.json`, `session-log.md`, and the preflight sentinel all agree -### WS-PARENT-1 — post-freeze fan-out +### `task/ct-p1-contract-spine` -Task id: `i6-a2-launch-int-b-c` +Owned files: -Parent actions: +- `spec-core/src/category_truth.rs` +- `spec-core/src/lib.rs` +- `spec-core/src/semantic_review.rs` -- create `codex/i6-int` and `codex/i6-lane-b-service-regressions` from `service_freeze_commit` -- create `codex/i6-lane-c-projection-fix` only if needed -- mark Lane B running -- mark Lane C running only if spawned +Required verification commands: -Acceptance: +```bash +cargo test -p spec-core category_truth +cargo test -p spec-core semantic_review +``` -- all post-freeze branches fork from the exact same `service_freeze_commit` -- active worker count never exceeds 2 +Acceptance: -### WS-B — service regression truth and benchmark fixtures +- registry rows and qualification types exist +- `SemanticReview` exposes producer-owned `descriptor_id` +- supported ecommerce descriptors qualify +- service seam descriptors remain visible but not supported-qualified +- parent writes accepted verification artifacts to + `verification/contract-spine/` +- parent marks the task `done` in canonical state before workers are spawned -Task id: `i6-b1-service-regressions` +### `task/ct-b1-benchmark` -Lane B owns: +Owned files: +- `spec-core/src/benchmark.rs` - `spec-cli/tests/rust_v1_service.rs` -- `spec-cli/tests/fixtures/benchmarks/**` - -Required outcomes: - -- add a dedicated `rust_v1_service` suite -- add or refresh benchmark fixtures for: - - full service-root `status` - - full service-root `export` - - partial service-scope zero-credit behavior - - active repo-root benchmark inventory with `BENCH-SERVICE` -- prove regressions for: - - missing required molecule proof - - stale required molecule proof - - failing required molecule proof - - readability drift becoming non-current - - partial service scopes emitting zero positive credit - - preserved `BENCH-ECOM` - - preserved `BENCH-CROSSLIB` - - preserved repo-root inventory semantics +- `spec-cli/tests/rust_v1_closure.rs` +- `spec-cli/tests/fixtures/benchmarks/*.json` -Required commands: +Required worker verification commands: ```bash -cargo run -p spec-cli -- status examples/service/units --format json -cargo run -p spec-cli -- export examples/service/units +cargo test -p spec-core benchmark cargo test -p spec-cli rust_v1_service cargo test -p spec-cli rust_v1_closure -cargo run -p spec-cli -- status examples/ecommerce/units --format json -cargo run -p spec-cli -- status examples/crosslib-app/units --format json -cargo run -p spec-cli -- status . --format json ``` -Hard rules: - -- no edits to `benchmarks/labels.json` -- no edits under `examples/service/**` -- no snapshot or readability-review edits -- no docs edits -- no core behavior edits - Acceptance: -- lane diff stays entirely within Lane B ownership -- service-root fixtures are derived from direct `status examples/service/units --format json` and `export examples/service/units` truth -- service-root fixtures are not justified only by indirect unit-test expectations -- repo-root inventory fixtures stay truthful to active `BENCH-SERVICE` - -### WS-C — minimal projection/core fix only if needed - -Task id: `i6-c1-projection-fix` - -Lane C owns: +- `BenchmarkCaseProjection` carries `category_qualification` +- supported positive credit depends on qualified supported truth +- `BENCH-SERVICE` full benchmark becomes invalid/open +- partial mismatch projects `partial_invalid` +- worker summary and worker-local mirrors are reviewed before acceptance +- acceptance is recorded only after cherry-pick plus canonical state update -- `spec-core/src/benchmark.rs` -- `spec-cli/src/commands.rs` +### `task/ct-c1-export` -Required outcomes: +Owned files: -- reproduce the exact read-side truth bug -- fix only the minimum code needed for truthful service-root projection -- preserve M68 mechanics and current command-scope behavior +- `spec-core/src/export.rs` +- `spec-core/src/passport.rs` -Required commands: +Required worker verification commands: ```bash -cargo run -p spec-cli -- status examples/service/units --format json -cargo run -p spec-cli -- export examples/service/units +cargo test -p spec-core export ``` -Hard rules: - -- no edits to specs, proof artifacts, labels, tests, fixtures, snapshots, reviews, or docs -- no benchmark-schema redesign -- no support expansion - Acceptance: -- diff stays entirely inside Lane C ownership -- bug is explained as read-side truth repair, not feature work +- `ExportBundle` adds `projected_units` +- projected units carry `semantic_review` plus `category_qualification` +- `.spec.passport.json` persistence remains unchanged +- export schema version is `5` +- worker summary and worker-local mirrors are reviewed before acceptance +- acceptance is recorded only after cherry-pick plus canonical state update -### WS-INT-2 — post-B/C service-truth gate +### `task/ct-p2-cli-integration` -Task id: `i6-d0-service-truth-gate` +Owned files: -Parent actions in `/home/azureuser/__Active_Code/atomize-hq/.worktrees/spec-i6/int`: +- `spec-cli/src/commands.rs` +- `spec-cli/tests/cli.rs` +- `spec-cli/tests/m14_regressions.rs` -- merge Lane C first if present -- merge Lane B second -- run Gate 2 before Lane D exists - -Required commands: +Required parent verification commands: ```bash +cargo test -p spec-cli cli +cargo test -p spec-cli m14_regressions +cargo run -p spec-cli -- status examples/ecommerce/units --format json cargo run -p spec-cli -- status examples/service/units --format json +cargo run -p spec-cli -- export examples/ecommerce/units cargo run -p spec-cli -- export examples/service/units -cargo test -p spec-cli rust_v1_service -cargo test -p spec-cli rust_v1_closure -cargo run -p spec-cli -- status examples/ecommerce/units --format json -cargo run -p spec-cli -- status examples/crosslib-app/units --format json ``` Acceptance: -- Gate 2 is green -- service-root `status` and `export` are truthful proof walls -- `readability_review_status` may still be non-current here because Lane D has not run yet +- `STATUS_JSON_SCHEMA_VERSION` is `5` +- status rows include additive `category_qualification` +- benchmark/status/snapshot paths share the same derived projected-unit truth +- no extra consumer-local semantic-review extraction logic remains in + `commands.rs` +- parent archives accepted verification outputs under `verification/cli/` -### WS-D — artifact/docs closeout only +### `task/ct-p3-snapshot-refresh` -Task id: `i6-d1-closeout` - -Lane D owns: +Owned files: +- `benchmarks/snapshots/BENCH-ECOM.snapshot.json` - `benchmarks/snapshots/BENCH-SERVICE.snapshot.json` +- `benchmarks/reviews/BENCH-ECOM.readability.review.json` - `benchmarks/reviews/BENCH-SERVICE.readability.review.json` -- `README.md` -- `docs/rust_v1_contract_stack.md` -- `TODOS.md` -- `CHANGELOG.md` -- only-if-truthfully-required closeout refreshes for ECOM/CROSSLIB snapshot, readability, or docs surfaces - -Required outcomes: -- refresh `BENCH-SERVICE.snapshot.json` from reserved-form to active-form -- author the first `BENCH-SERVICE.readability.review.json` -- align repo-facing docs to shipped truth only - -Required commands: +Required parent verification commands: ```bash +cargo run -p spec-cli -- benchmark snapshot BENCH-ECOM cargo run -p spec-cli -- benchmark snapshot BENCH-SERVICE -cargo run -p spec-cli -- status examples/service/units --format json -cargo run -p spec-cli -- export examples/service/units ``` -Hard rules: +Acceptance: -- no service semantic edits -- no label edits -- no spec edits -- no test edits -- no fixture edits -- no core behavior edits -- Lane D is artifact/docs closeout only +- snapshot output includes case `category_qualification` +- `BENCH-ECOM` remains passing +- `BENCH-SERVICE` full snapshot is invalid/open +- readability freshness remains additive and unchanged in meaning +- parent archives accepted verification outputs under `verification/final/` -Acceptance: +### `task/ct-x1-blocker` -- `BENCH-SERVICE` snapshot is active-form -- `BENCH-SERVICE` readability review is current against final projection digest and generated file set -- docs teach the same story the CLI now projects +Activate only if one of the following remains after the normal flow: -### WS-INT-3 — final acceptance and landing +- a worker lane can only pass by touching a forbidden surface +- `commands.rs` integration reveals inconsistent qualification semantics that + cannot be fixed locally in the parent lane without crossing owned boundaries +- the full verification sweep fails on a direct wedge surface -Task id: `i6-e-final-acceptance` +Rules: -Parent actions: +- create `ws/ct-blocker` only after documenting the blocker in + `.runs/category-truth/blockers.md` +- keep the owned write set as narrow as the failing surface allows +- rerun the affected targeted commands and then rerun the full sweep +- stop if fixing the blocker would widen support, alter schema scope beyond + `PLAN.md`, or require new orchestration lanes -- merge Lane D into `codex/i6-int` -- run Gate 3 -- if green, fast-forward the parent branch -- if red, bounce only the owning lane +## Context-Control Rules -Required commands: +- Parent context stays centered on: + - `PLAN.md` + - `ORCH_PLAN.md` + - the current lane diff + - the current lane verification outputs + - `.runs/category-truth/tasks.json` + - `.runs/category-truth/session-log.md` +- Worker prompts contain only: + - owned write set + - exact required commands + - forbidden surfaces + - acceptance criteria + - artifact output paths +- Workers return only: + - changed files + - commands run with exit codes + - blocker notes + - assumptions +- Parent records worker completion as `worker_complete`, not `accepted`. +- Close worker lanes immediately after parent acceptance or rejection. + +## Full Verification Sweep + +Run this exact sweep from the parent checkout after snapshot refresh and again +after any blocker repair: ```bash -cargo run -p spec-cli -- build examples/service/units --output examples/service/src/generated -cargo run -p spec-cli -- test examples/service/units/billing/apply_membership_discount.unit.spec -cargo run -p spec-cli -- test examples/service/units/billing/apply_regional_fee.unit.spec -cargo run -p spec-cli -- test examples/service/units/billing/checkout_net_total.unit.spec -cargo run -p spec-cli -- test examples/service/units/billing/checkout_net_total_guarded_fee.unit.spec -cargo run -p spec-cli -- test examples/service/units/billing/discount_strategy.unit.spec -cargo run -p spec-cli -- test examples/service/units/billing/pricing_quote.unit.spec -cargo run -p spec-cli -- test examples/service/units/billing/checkout_success_flow.test.spec -cargo run -p spec-cli -- test examples/service/units/billing/checkout_declined_discount_flow.test.spec -cargo run -p spec-cli -- test examples/service/units/billing/discount_strategy_quote_flow.test.spec -cargo run -p spec-cli -- status examples/service/units --format json -cargo run -p spec-cli -- export examples/service/units -cargo run -p spec-cli -- benchmark snapshot BENCH-SERVICE +cargo test -p spec-core cargo test -p spec-cli rust_v1_service cargo test -p spec-cli rust_v1_closure +cargo test -p spec-cli m14_regressions +cargo test -p spec-cli cli + cargo run -p spec-cli -- status examples/ecommerce/units --format json -cargo run -p spec-cli -- status examples/crosslib-app/units --format json -cargo run -p spec-cli -- status . --format json +cargo run -p spec-cli -- status examples/service/units --format json +cargo run -p spec-cli -- export examples/ecommerce/units +cargo run -p spec-cli -- export examples/service/units +cargo run -p spec-cli -- benchmark snapshot BENCH-ECOM +cargo run -p spec-cli -- benchmark snapshot BENCH-SERVICE ``` -Acceptance: - -- Gate 3 is green -- `BENCH-SERVICE` is active, passing, gate-satisfied, and `readability_review_status: "current"` -- rerunning `cargo run -p spec-cli -- benchmark snapshot BENCH-SERVICE` leaves the committed snapshot stable -- service-root `status` and `export` are the proof wall -- repo-root `status . --format json` remains diagnostic inventory with `scope_authority: "inventory_only"` - -## Merge Order - -1. Parent freezes basis and queue. -2. Lane A lands on the parent branch. -3. Parent records `service_freeze_commit`. -4. Parent creates `codex/i6-int` and Lane B from `service_freeze_commit`. -5. Parent creates Lane C only if a real projection bug exists. -6. Lane C, if present, merges into `codex/i6-int` first. -7. Lane B rebases if Lane C changed truth-surface behavior, then merges into `codex/i6-int`. -8. Parent runs Gate 2. -9. Lane D branches from `codex/i6-int` only after Gate 2 passes. -10. Lane D merges into `codex/i6-int`. -11. Parent runs Gate 3. -12. Parent fast-forwards `codex/i6-service-benchmark-activation` to `codex/i6-int`. +Expected final truth: -## Context-Control Rules - -- Parent keeps only: - - `PLAN.md` - - `.runs/i6/tasks.json` - - gate records - - the latest narrow diff summary per lane -- Each worker prompt contains only: - - owned files - - forbidden files - - relevant `PLAN.md` excerpt - - required commands - - the recorded `service_freeze_commit` when applicable -- Workers return only: - - changed files - - commands run and exit codes - - blockers - - unresolved assumptions -- Workers do not write `.runs/i6/**`. -- Close each worker immediately after merge or rejection. +- ecommerce full benchmark remains passing +- service full benchmark is invalid/open +- status and export expose the same qualification result for seam-backed rows +- snapshot output matches live benchmark projection +- `.spec.passport.json` stays free of persisted `category_qualification` ## Tests And Acceptance -- Lane A acceptance is authored service truth plus refreshed proof. -- Lane B acceptance is direct service-root regression truth plus truthful benchmark fixtures. -- Lane C acceptance is a minimal reproduced-and-fixed read-side bug. -- Lane D acceptance is snapshot, readability, and docs closeout only. -- Final acceptance is the full service benchmark wall plus non-regression across `BENCH-ECOM`, `BENCH-CROSSLIB`, and repo-root inventory semantics. - -The service benchmark wall is closed only when all are true at once: - -- `BENCH-SERVICE` is active rather than reserved. -- the roster is exactly six positive units and three required molecules. -- all six positive units are valid with fresh proof. -- all three required molecules are valid with fresh evidence. -- service-root `status` and `export` both project passing active truth. -- service partial scopes do not launder positive credit. -- `BENCH-SERVICE` readability review is current. -- committed `BENCH-SERVICE.snapshot.json` is stable on rerun. -- `BENCH-ECOM` remains passing/current. -- `BENCH-CROSSLIB` remains passing with zero positive credit. -- repo-root `status . --format json` remains diagnostic inventory and not a zero-exit acceptance gate. +### Workstream Acceptance + +- Preflight is accepted only when: + - branch and head match the frozen baseline + - dirty files are recorded + - canonical run-state files are initialized +- Contract spine is accepted only when: + - category substrate and semantic-review descriptor work are complete + - contract-spine verification artifacts are canonicalized + - worker lanes have not started yet +- Benchmark lane is accepted only when: + - owned-file diff stays within lane boundaries + - worker verification passes + - `BENCH-SERVICE` benchmark truth flips to invalid/open + - parent cherry-picks the lane and updates canonical state +- Export lane is accepted only when: + - owned-file diff stays within lane boundaries + - worker verification passes + - export schema is `5` + - parent cherry-picks the lane and updates canonical state +- CLI integration is accepted only when: + - `commands.rs` remains parent-owned + - shared-helper consolidation is in place + - status and export commands reflect the same qualification contract +- Snapshot/readability refresh is accepted only when: + - snapshot artifacts match live projection output + - readability artifacts remain additive only +- Blocker lane is accepted only when: + - it fixes a direct, in-scope blocker + - it does not widen scope + - downstream verification is rerun and passes + +### Final Wedge Acceptance + +- `BENCH-ECOM` remains `passing` +- `BENCH-SERVICE` full projection is: + - `accounting_status = invalid` + - `benchmark_status = invalid` + - `gate_status = open` +- `spec status --format json` and `spec export` expose additive + `category_qualification` +- seam-backed rows show status/export parity for qualification truth +- benchmark snapshots match live benchmark projection +- `.spec.passport.json` does not persist `category_qualification` +- no worker lane edited `spec-cli/src/commands.rs` +- the parent closeout records enough canonical evidence to replay the run logic + +## Closeout Criteria + +Mark the run `done` only when all of the following are true: + +- every workstream is `accepted` or `done` +- no forbidden-file edits were needed +- no worker lane changed `spec-cli/src/commands.rs` +- the final verification sweep is green under the expected semantics +- `tasks.json`, `merge-order.md`, and `session-log.md` let a future maintainer + reconstruct what landed and in what order + +Otherwise mark the run `blocked` and record the narrowest honest blocker in +`.runs/category-truth/blockers.md`. ## Assumptions -- The primary checkout on `codex/i6-service-benchmark-activation` remains the parent workspace for the full run. -- `spec-cli/tests/fixtures/benchmarks/**` is broad enough that Lane B may own all service-root and repo-root benchmark fixture refresh required by active `BENCH-SERVICE`. -- `.runs/i6/**` is parent-owned run state and is not assumed to be a checked-in deliverable. +- The current `PLAN.md` frozen decisions remain valid at execution time. +- The exact parent baseline head remains the correct launch point for this + wedge. +- Benchmark and export adoption remain separable after the contract spine lands. +- `commands.rs` remains the only shared-helper hotspot significant enough to + justify parent-only ownership. diff --git a/PLAN.md b/PLAN.md index 5244d26..c1a023d 100644 --- a/PLAN.md +++ b/PLAN.md @@ -1,894 +1,346 @@ -# I6: Rust V1 Service Benchmark Activation Plan + +# Corrected Stored-Truth-Strict Category Qualification Respin Plan -Status: **authoritative implementation plan** -Iteration: **I6** -Milestone family: **Rust V1 service-shaped proof closure** -Implementation readiness: **ready for implementation** -Plan scope: **turn `BENCH-SERVICE` from a reserved benchmark into the active service-shaped proof workload required by `M67`, without widening `M66` support rows, reopening `M68` mechanics, or admitting async/IO, generics, traits, lifetimes, or framework-heavy authored surfaces** -Base branch: **main** -Working branch: **`codex/i6-service-benchmark-activation`** -Validated on branch: **`codex/i6-service-benchmark-activation`** -Last rewritten: **2026-05-21** +Status: **authoritative implementation plan** +Milestone family: **`operator-consumer-tooling`** +Executable wedge: **`corrected stored-truth-strict category qualification respin for benchmark/status/export/snapshot consumers`** +Implementation readiness: **ready-now** +Base branch: **`feat/i8-final-proof-run`** +Planning date: **2026-05-25** -Supersedes: +Primary authority inputs: -- the prior `I5: Rust V1 Supported-Core Closure Plan` +- handoff: `.codex/handoffs/2026-05-25-183145-m101-correction-respin.md` +- corrective design note: `docs/category_truth_contract_correction_v0.1.md` +- original contract doc: `docs/category_truth_contract_v0.1.md` +- backlog anchor: `TODOS.md` under `M101 backlog` +- current authority context: `PLAN.md` and `ORCH_PLAN.md` from the prior M101 attempt +- live repo state observed on this branch: + - `cargo run -p spec-cli -- status examples/service/units --format json` + - `cargo run -p spec-cli -- export examples/service/units` + - both currently report `BENCH-SERVICE` as `benchmark_status=passing`, `accounting_status=valid`, `gate_status=open`, `positive_credit_cases=4` -Locked authority inputs: +## Why This Respin Exists -- contract-stack index: `docs/rust_v1_contract_stack.md` -- `M65`: `/home/azureuser/.gstack/projects/atomize-hq-spec/spensermcconnell-feat-m60-plus-design-20260517-200036.md` -- `M66`: `/home/azureuser/.gstack/projects/atomize-hq-spec/spensermcconnell-feat-m60-plus-design-20260517-213928.md` -- `M67`: `/home/azureuser/.gstack/projects/atomize-hq-spec/spensermcconnell-feat-m60-plus-design-20260517-220646.md` -- `M68`: `/home/azureuser/.gstack/projects/atomize-hq-spec/spensermcconnell-feat-m60-plus-design-20260517-225503.md` -- live repo truth run on branch `codex/i6-service-benchmark-activation` at the then-current `HEAD`: - - `cargo run -p spec-cli -- status . --format json` - - `git rev-parse --short HEAD` - - `git branch --show-current` +The milestone is still right. The implementation shape was not. -Historical context, not authority: +The old M101 direction correctly aimed for: -- `README.md` -- `CHANGELOG.md` -- `TODOS.md` -- `benchmarks/snapshots/*.snapshot.json` -- `benchmarks/reviews/*.readability.review.json` -- `spec-cli/tests/fixtures/m19/semantic_falsification_pack/units/billing/**` +- one registry-owned category truth substrate +- one shared qualification vocabulary +- benchmark, status, export, and snapshot/readability parity -Primary repo surfaces: +But the reviewed implementation crossed the repo's honesty boundary by allowing +read-side consumers to derive category-bearing truth from refreshed semantic +projection. That would create a second truth producer outside `spec test`. -- `benchmarks/labels.json` -- `benchmarks/snapshots/BENCH-SERVICE.snapshot.json` -- `benchmarks/reviews/BENCH-SERVICE.readability.review.json` -- `examples/service/**` and `examples/service/units/**` -- `spec-core/src/benchmark.rs` -- `spec-cli/src/commands.rs` -- `spec-cli/tests/cli.rs` -- `spec-cli/tests/rust_v1_closure.rs` -- `spec-cli/tests/rust_v1_service.rs` -- `spec-cli/tests/fixtures/benchmarks/**` - -## Executive Summary - -I5 closed the supported-core benchmark wall. - -The remaining Rust V1 proof gap is narrower now than it was when `I5` was first -drafted: the repo already proves the positive narrow-core benchmark -(`BENCH-ECOM`) and the companion-negative wall (`BENCH-CROSSLIB`), while -`BENCH-SERVICE` still exists only as a reserved placeholder with zero cases and -no readability review. - -I6 closes exactly that gap and nothing else. - -This plan activates `BENCH-SERVICE` as one real single-library benchmark rooted -at `examples/service/units`, backed by six supported positive cases, three -required molecule proofs, a current snapshot, a current readability review, and -a dedicated regression wall. When I6 is complete, the repo can truthfully claim -that the current Rust V1 narrow-core contract is proven by both an example-domain -workload and a service-shaped workload. - -## Current Validated Truth - -Observed on branch `codex/i6-service-benchmark-activation` at the then-current -`HEAD` on 2026-05-21: - -- `cargo run -p spec-cli -- status . --format json` reports: - - `schema_version: 4` - - `scope_authority: "inventory_only"` - - `BENCH-ECOM` is `active`, `passing`, and `readability_review_status: - "current"` - - `BENCH-CROSSLIB` is `active`, `passing`, and `positive_credit_cases: 0` - - `BENCH-SERVICE` is `reserved`, `reserved_missing_cases`, and has - `total_cases: 0` -- `cargo run -p spec-cli -- status . --format json` currently exits non-zero - because repo-root inventory includes intentionally non-green fixture surfaces. - For I6, repo-root status remains a diagnostic read surface; acceptance must - assert its JSON fields, not require a zero exit code. -- `benchmarks/labels.json` already declares the future service benchmark root: - - `root: "examples/service/units"` - - `generated_root: "examples/service/src/generated"` - - `readability_scope: "supported_closure"` - - `cases: []` -- `benchmarks/snapshots/BENCH-SERVICE.snapshot.json` already exists, but only in - reserved form -- `benchmarks/reviews/` contains `BENCH-ECOM.readability.review.json`, but - there is no `BENCH-SERVICE.readability.review.json` -- `examples/service/` does not exist yet -- the closest already-shipped service-shape building blocks are: - - seam examples under `examples/ecommerce/units/pricing/` - - benchmark fixtures under `spec-cli/tests/fixtures/benchmarks/` - - closure regression patterns in `spec-cli/tests/rust_v1_closure.rs` -- service-shaped billing vocabulary already exists in fixture form under - `spec-cli/tests/fixtures/m19/semantic_falsification_pack/units/billing/**` -- `docs/rust_v1_contract_stack.md` still teaches the ladder only through `I4`, - so the implementation ladder needs a closeout update after the benchmark lands - -That means the benchmark system is already capable of carrying the claim. The -missing work is the benchmark payload, proof, review artifact, and regression -coverage. - -## Step 0: Scope Challenge - -### Premise correction - -The problem is not "add broader Rust support." - -The problem is: - -```text -turn the reserved BENCH-SERVICE placeholder into one active, -single-library, service-shaped benchmark that proves the already-shipped -Rust V1 narrow core on a real workflow -``` - -If implementation expands beyond that sentence, it has escaped the milestone. +This respin keeps the shared contract and benchmark honesty gains, then rebuilds +consumer projections so every read-side surface interprets stored semantic truth +only. -### Scope verdict - -The complete version is still cheap here. +## Frozen Decisions -I6 does not need: +These are fixed for this wedge: + +1. `semantic_review` remains producer-owned truth. +2. Only `spec test` may refresh semantic review truth. +3. `category_qualification` is read-side interpretation of stored semantic truth. +4. No read-only path may use `SemanticProjectionMode::Refresh` to mint fresher + category truth for benchmark, status, export, or snapshot output. +5. The first-scope registry remains the same four seam rows: + - `sum.discount_strategy.v1` + - `data.pricing_quote.v1` + - `unsupported.sum.v1` + - `unsupported.data.v1` +6. Service seam siblings stay visible but unqualified in this wedge. +7. `benchmarks/labels.json` is not relabeled in this wedge. +8. `.spec.passport.json` schema is not widened to persist + `category_qualification`. +9. If `spec status` and `spec export` gain additive + `category_qualification`, their public JSON schema versions bump. +10. For seam category-claim candidates, positive benchmark credit must require + `category_qualification.claim_status == supported_qualified` plus + `positive_credit_eligibility == eligible`. +11. Legacy `semantic_support_status` fallback may remain only for non-seam + benchmark rows. It must not award positive credit to seam rows when + `category_qualification` is absent. +12. `spec export` must choose one exact no-passport behavior for seam rows: + if no passported semantic review exists, export may still emit the unit row, + but it must emit no supported semantic/category claim. The allowed first + landing shape is: + - `projected_units[]` row present + - `semantic_review = null` + - `category_qualification = null` + - never `supported_qualified` +13. The refresh audit is owned by named read-side helpers, not whole-file grep. + The implementation must identify the exact benchmark/status/export/snapshot + read-side projection helpers and prove those helpers use preserved semantic + truth only. + +## Exact File Scope + +### Expected code edits + +- `spec-core/src/category_truth.rs` + - keep the registry and qualification substrate + - tighten it around preserved semantic truth only +- `spec-core/src/lib.rs` + - export the corrected category-truth module +- `spec-core/src/semantic_review.rs` + - keep producer-owned routing and descriptor hooks + - do not widen read-side refresh semantics +- `spec-core/src/benchmark.rs` + - keep positive-credit gating and benchmark invalidation logic + - consume preserved semantic review truth only +- `spec-core/src/export.rs` + - add or keep additive qualification on export surfaces only if it is derived + from preserved/passported semantic review + - remove any refresh-based category truth minting path +- `spec-cli/src/commands.rs` + - route benchmark/status/export/snapshot consumers through one preserved-truth + qualification helper + - remove any `SemanticProjectionMode::Refresh` path that feeds + `category_qualification` on read-side surfaces -- a new benchmark subsystem -- new benchmark schemas -- cross-library service proof -- async or IO semantics -- new semantic-family promotion -- trait, lifetime, macro, or framework-heavy authored support +### Expected tests and artifact refresh -It needs one truthful service benchmark and the regression wall that keeps it -honest. +- `spec-cli/tests/rust_v1_service.rs` +- `spec-cli/tests/rust_v1_closure.rs` +- `spec-cli/tests/cli.rs` +- `spec-cli/tests/m14_regressions.rs` +- `spec-cli/tests/fixtures/benchmarks/export-ecommerce-full.json` +- `spec-cli/tests/fixtures/benchmarks/export-service-full.json` +- `spec-cli/tests/fixtures/benchmarks/export-service-billing-partial-full.json` +- `spec-cli/tests/fixtures/benchmarks/status-ecommerce-full.json` +- `spec-cli/tests/fixtures/benchmarks/status-service-full.json` +- `spec-cli/tests/fixtures/benchmarks/status-service-billing-partial-full.json` +- `spec-cli/tests/fixtures/benchmarks/status-repo-root-full.json` +- `benchmarks/snapshots/BENCH-ECOM.snapshot.json` +- `benchmarks/snapshots/BENCH-SERVICE.snapshot.json` -Scope is accepted as-is. +### Explicitly out of scope -### Failure-path definition for I6 +- `benchmarks/labels.json` +- `.spec.passport.json` schema changes +- new live analysis surfaces +- widened support for service seam siblings +- broader seam alias/sibling cleanup +- recommendation-policy or corpus-run work +- ORCH plan expansion beyond what this corrected wedge needs -`M67` requires a service-shaped failure-path workflow. For I6, that means a -business-path unhappy flow that stays inside the already-supported narrow core. +## Keep / Drop Commit Intent -Allowed examples: +The old branch is reference material, not a branch to land. -- a declined or absent discount path expressed through the supported plain sum - seam -- a guarded-fee path that clamps invalid business input into the supported - non-negative surface +### Keep or adapt -Disallowed examples: +| Commit | Intent | Use in respin | +| --- | --- | --- | +| `397837e` | add category truth contract spine | strongest salvage candidate; keep the registry and qualification types after a preserve-only audit | +| `688d673` | pass semantic review into benchmark truth builders | keep the idea; benchmark projection needs semantic-review input, but only preserved truth | +| `81aa154` | invalidate service benchmark category mismatches | keep the benchmark honesty outcome and test expectations | -- network failure -- database failure -- async cancellation -- retry or fallback orchestration -- framework-managed request lifecycles +### Drop or re-implement narrowly -Those remain outside `M66` and outside I6. +| Commit | Why it does not land as-is | Respin instruction | +| --- | --- | --- | +| `c24d4f7` | projected export truth was built in the same implementation wave that introduced read-side refresh risk | rebuild export from preserved passport truth only | +| `29fd15b` | CLI truth integration touched the risky status/export paths directly | do not cherry-pick wholesale; re-thread through one preserved-truth helper | +| `701019e` | fixture refresh captured the old export shape | regenerate after corrected export semantics are in place | +| `3d81942` | snapshot preservation landed after the read-side truth drift already existed | rebuild snapshot parity after live benchmark projection is corrected | +| `e2af919` | refreshed semantic-review benchmark inputs are suspect under the corrected contract | re-evaluate case by case; default to drop | +| `3ee00b4` | compile-surface fix was tied to the exploratory branch line | re-apply only if the corrected respin actually needs it | -### Complexity check +## Corrected Contract Shape + +The implementation contract for this respin is: + +- producer truth: + - authored unit specs + - passports + - molecule evidence + - `semantic_review` refreshed only by `spec test` +- consumer truth: + - `CategoryQualification` + - benchmark case accounting + - `spec status --format json` + - `spec export` + - snapshot/readability projection -Expected write scope: +The consumer rule is simple: -- `examples/service/**` -- `benchmarks/labels.json` -- `benchmarks/snapshots/BENCH-SERVICE.snapshot.json` -- `benchmarks/reviews/BENCH-SERVICE.readability.review.json` -- `spec-cli/tests/rust_v1_service.rs` -- `spec-cli/tests/fixtures/benchmarks/**` -- targeted `spec-core/src/benchmark.rs` or `spec-cli/src/commands.rs` only if - activation exposes a truthful projection gap -- `README.md`, `docs/rust_v1_contract_stack.md`, `TODOS.md`, and - `CHANGELOG.md` during closeout +> if preserved semantic truth is missing, stale, unsupported, or descriptor-mismatched, +> the consumer may surface that failure, but it may not mint a fresher supported claim. -If implementation starts redesigning projection, widening support rows, or -re-arguing `M66`/`M67`/`M68`, stop. That is different scope. +## Implementation Slices -## What Already Exists +### Slice 1: Re-establish the contract spine -| Sub-problem | Existing owner | I6 action | -| --- | --- | --- | -| positive benchmark mechanics | `benchmarks/labels.json`, `spec-core/src/benchmark.rs`, `spec-cli/src/commands.rs` | reuse unchanged unless service activation exposes a real truth bug | -| benchmark-root truth surfaces | `spec status`, `spec export`, `spec benchmark snapshot` | preserve contract, add service-root coverage | -| positive narrow-core proof shape | `BENCH-ECOM` | reuse as the primary pattern for labels, required molecules, readability, and snapshot assertions | -| companion-negative wall | `BENCH-CROSSLIB` | preserve unchanged and assert non-regression | -| readability review mechanics | `benchmarks/reviews/BENCH-ECOM.readability.review.json` | reuse the same digest and generated-file-set contract for `BENCH-SERVICE` | -| benchmark fixture pattern | `spec-cli/tests/fixtures/benchmarks/**` | add service-specific fixtures alongside the existing benchmark fixture family | -| benchmark regression pattern | `spec-cli/tests/rust_v1_closure.rs` | preserve this suite and add a dedicated `rust_v1_service` suite rather than overloading closure tests | -| service billing vocabulary | `spec-cli/tests/fixtures/m19/semantic_falsification_pack/units/billing/**` | promote only the supported positive shapes into `examples/service/units/billing/**` | -| supported seam patterns | `examples/ecommerce/units/pricing/discount_strategy.unit.spec`, `examples/ecommerce/units/pricing/pricing_quote.unit.spec`, and their molecule tests | adapt the same supported seam shape into the service domain | -| repo-root inventory semantics | current repo-root `status . --format json` behavior | preserve as diagnostic-only inventory with benchmark visibility, not as a proof-success command | +Deliverables: -## Frozen Decisions +- land `spec-core/src/category_truth.rs` as the shared registry and stable + reason-code surface +- keep the qualification vocabulary small: + - `supported_qualified` + - `unsupported_qualified` + - `unqualified` +- ensure qualification takes preserved semantic-review input, not a refresh-mode + projection +- keep descriptor approval narrow so service siblings remain unqualified -These are locked. I6 implements them and does not reopen them. - -1. **I6 activates `BENCH-SERVICE`.** - - The milestone is not complete while `BENCH-SERVICE` remains - `lifecycle: "reserved"`. - - The benchmark must become an active positive benchmark with real cases, - real required molecules, current proof, and a current readability review. - -2. **I6 does not widen the Rust V1 support contract.** - - `ROW-GENERIC-BOUNDED` stays deferred. - - `ROW-ASYNC-IO` stays deferred. - - no trait-authored, lifetime-heavy, or macro/framework-heavy authored - surfaces enter the benchmark - - no new support rows or interaction claims are admitted - -3. **I6 stays single-library.** - - `BENCH-SERVICE` lands under `examples/service/**` - - no `[libraries]` graph or sibling-library dependency is part of the - service benchmark - - the benchmark proves service shape, not cross-library shape - -4. **I6 uses only already-supported families and seam kinds.** - - supported function families already shipped in the repo are allowed - - supported seam examples already shipped in ecommerce are allowed - - unsupported-function or unsupported-seam cases stay fixture-only, not - positive benchmark cases - -5. **The positive service roster is fixed in this plan.** - - `billing/apply_membership_discount` - - `billing/apply_regional_fee` - - `billing/checkout_net_total` - - `billing/checkout_net_total_guarded_fee` - - `billing/discount_strategy` - - `billing/pricing_quote` - -6. **The required molecule roster is fixed in this plan.** - - `billing/checkout_success_flow` - - `billing/checkout_declined_discount_flow` - - `billing/discount_strategy_quote_flow` - -7. **Source specs remain the only authored truth for service behavior.** - - edit `.unit.spec` and `.test.spec` files by hand - - never hand-edit generated Rust, passports, molecule evidence, or snapshots - - refresh generated Rust with `spec build` - - refresh passports and molecule evidence with `spec test` - - refresh benchmark snapshot with `spec benchmark snapshot` - -8. **`BENCH-SERVICE` keeps the existing writer/reader contract.** - - source specs are authored truth - - passports and molecule evidence are proof writers - - benchmark labels are benchmark-accounting truth - - readability review is human-authored observation truth - - `status`, `export`, and `benchmark snapshot` are read-side projections - -9. **A service case that needs widened support is a failed case, not a widened contract.** - - if any proposed positive case cannot stay inside current supported rows and - interactions, replace the case - - do not widen `M66` to rescue the benchmark - -10. **`BENCH-ECOM` and `BENCH-CROSSLIB` stay green and truthful throughout I6.** - - the service landing may refresh shared fixtures only where the benchmark - roster legitimately changes - - it may not regress the already-shipped benchmark wall - -11. **Minimal diff still wins.** - - prefer promoting existing billing fixture vocabulary plus adapting shipped - seam patterns - - only touch projection code when the active service benchmark exposes a - real truth gap - -12. **Implementation finishes on the current dedicated branch.** - - work continues on `codex/i6-service-benchmark-activation` - - refresh validation truth on that branch before declaring I6 done - -## Intended Service Workload - -I6 authors exactly this service example scaffold: - -- `examples/service/Cargo.toml` -- `examples/service/README.md` -- `examples/service/src/main.rs` -- `examples/service/units/.gitignore` -- `examples/service/units/billing/apply_membership_discount.unit.spec` -- `examples/service/units/billing/apply_regional_fee.unit.spec` -- `examples/service/units/billing/checkout_net_total.unit.spec` -- `examples/service/units/billing/checkout_net_total_guarded_fee.unit.spec` -- `examples/service/units/billing/discount_strategy.unit.spec` -- `examples/service/units/billing/pricing_quote.unit.spec` -- `examples/service/units/billing/checkout_success_flow.test.spec` -- `examples/service/units/billing/checkout_declined_discount_flow.test.spec` -- `examples/service/units/billing/discount_strategy_quote_flow.test.spec` - -Concrete authored intent for each positive case: - -- `billing/apply_membership_discount` - - promoted from the M19 billing fixture pack - - supported arithmetic leaf, monotone-down nonnegative -- `billing/apply_regional_fee` - - promoted from the M19 billing fixture pack - - supported arithmetic leaf, monotone-up -- `billing/checkout_net_total` - - promoted from the M19 billing fixture pack - - supported wrapper pipeline over discount then fee -- `billing/checkout_net_total_guarded_fee` - - new guarded wrapper over the same leaf pair - - uses the already-shipped normalized-required-arg shape to clamp - `regional_rate` at zero -- `billing/discount_strategy` - - new plain `kind:sum` seam adapted from the ecommerce pattern - - variants: `none`, `percentage { rate }`, `fixed_amount { amount }` -- `billing/pricing_quote` - - new plain `kind:data` seam adapted from the ecommerce pattern - - fields: `subtotal`, `membership_rate`, `regional_rate` - - methods expose discounted subtotal and final net total - -Concrete molecule obligations: - -- `billing/checkout_success_flow` - - proves the happy-path multi-unit business flow -- `billing/checkout_declined_discount_flow` - - proves the unhappy-path service flow using supported business semantics - - this is where "failure path" becomes concrete for I6 -- `billing/discount_strategy_quote_flow` - - proves seam usage and seam-to-business coherence - -## I6 Service Closure Matrix - -| Required proof dimension | I6 proof owner | Required proof surface | -| --- | --- | --- | -| real multi-unit business workflow | `billing/checkout_net_total`, `billing/checkout_success_flow` | benchmark-root `status` + `export` + fresh passport/evidence | -| business-path unhappy flow | `billing/checkout_net_total_guarded_fee`, `billing/checkout_declined_discount_flow` | required molecule proof + benchmark gate | -| supported function rows | `billing/apply_membership_discount`, `billing/apply_regional_fee`, `billing/checkout_net_total`, `billing/checkout_net_total_guarded_fee` | fresh passports + benchmark case projections | -| supported seam rows | `billing/discount_strategy`, `billing/pricing_quote` | fresh passports + seam-focused molecule proof | -| supported seam/business interaction | `billing/discount_strategy_quote_flow` | required molecule proof + read-side projection | -| generated readability pressure | `examples/service/src/generated/**` | readability review + generated Rust inspection | -| truthful read-side projection | `BENCH-SERVICE` benchmark-root `status`, `export`, and snapshot | full-scope projections only; partial scopes remain zero-credit | -| benchmark-scoped readability | `benchmarks/reviews/BENCH-SERVICE.readability.review.json` | current digest + current generated-file set | -| preserved prior closure | `BENCH-ECOM`, `BENCH-CROSSLIB` | regression suite + repo-root inventory projection | - -## Architecture - -```text - M66 NARROW-CORE CLAIM (UNCHANGED) - | - +----------------------+----------------------+ - | | - v v - shipped supported-core wall new service-shaped wall - (BENCH-ECOM + BENCH-CROSSLIB) (BENCH-SERVICE) - | - +--------------------------------+-------------------------------+ - | | | - v v v - positive service cases required molecule flows readability review - | | | - v v v - fresh passports fresh evidence current digest + file set - \___________________________ | _________________________/ - \ | / - v v v - benchmark projection core - | - +-----------------------------+-----------------------------+ - | | - v v - service-root status/export (proof wall) BENCH-SERVICE snapshot - | - v - repo can honestly claim service-shaped proof exists - - BENCH-ECOM and BENCH-CROSSLIB remain active and must stay green. - Repo-root status remains inventory_only even after BENCH-SERVICE activates. -``` +Proof: -## Authoring and Proof Contract - -I6 must follow the repo's spec workflow rather than mutating artifacts directly. - -Implementation rules: - -- edit `.unit.spec` and `.test.spec` files, not generated Rust or proof artifacts -- use `cargo run -p spec-cli -- validate --format json` as the - first machine-readable failure surface when a spec is invalid -- use `cargo run -p spec-cli -- build examples/service/units --output examples/service/src/generated` - to regenerate the shared output tree -- use `cargo run -p spec-cli -- test ` to refresh the exact passport or - molecule evidence that changed -- treat `status` as projection and inventory; treat `test` as proof-writer -- author the readability review only after the generated file set and projection - digest are final - -This removes the only dangerous ambiguity in the implementation: which files are -authored truth versus derived truth. - -## Target Outcome - -I6 is done only when all of these are true at the same time: - -1. `BENCH-SERVICE` is no longer reserved. -2. `benchmarks/labels.json` lists exactly the six positive service cases and - three required service molecules from this plan. -3. `examples/service/units` exists as a real single-library authored workload. -4. `cargo run -p spec-cli -- status examples/service/units --format json` - exits successfully and reports: - - `benchmark_id: "BENCH-SERVICE"` - - `lifecycle: "active"` - - `accounting_status: "valid"` - - `benchmark_status: "passing"` - - `gate_status: "satisfied"` - - `readability_review_status: "current"` - - `summary.total_cases: 6` - - `summary.supported_valid_cases: 6` - - `summary.required_molecule_total: 3` - - `summary.required_molecule_status_counts.valid: 3` -5. `cargo run -p spec-cli -- export examples/service/units` projects the same - benchmark truth without inventing any new proof. -6. `cargo run -p spec-cli -- benchmark snapshot BENCH-SERVICE` writes an active - snapshot, not a reserved one. -7. `BENCH-ECOM` still passes with current readability truth. -8. `BENCH-CROSSLIB` still passes with zero positive credit. -9. repo-root `status . --format json` still reports `scope_authority: - "inventory_only"` and includes all three benchmarks, regardless of whether - unrelated inventory rows keep the overall command non-green. -10. closeout docs teach the same story the benchmark wall now proves. - -## Implementation Contract - -Implementation is four phases. Phases 1-3 land the benchmark truth. Phase 4 is -the closeout pass after the service wall is stable. - -### Phase 1: Service scaffold and benchmark activation - -Objective: - -- create the real service example tree and flip `BENCH-SERVICE` from reserved - to active with the exact roster frozen above - -Primary write scope: - -- `examples/service/**` -- `benchmarks/labels.json` +- `cargo test -p spec-core` -Required changes: +### Slice 2: Restore benchmark honesty first -- scaffold `examples/service/` to mirror the single-library shape used by - `examples/ecommerce/` -- promote the positive M19 billing function fixtures into - `examples/service/units/billing/**` -- author `billing/checkout_net_total_guarded_fee.unit.spec` -- author `billing/discount_strategy.unit.spec` -- author `billing/pricing_quote.unit.spec` -- switch `BENCH-SERVICE` to `lifecycle: "active"` -- add the six positive cases and the three required molecules -- keep `required_for_v1: true` -- keep `kind: "positive"` and `readability_scope: "supported_closure"` +Deliverables: -Hard rules: +- thread preserved semantic-review truth into benchmark case qualification +- keep `counts_as_supported_positive` gated on explicit supported qualification +- remove or fence the seam-row legacy fallback so + `semantic_support_status` alone cannot award positive credit for seam + category candidates +- make full positive benchmarks invalid when supported-labeled seam cases are + present but unqualified +- preserve unsupported visibility without positive credit -- no `[libraries]` graph in the service example -- no `examples/shared-spec` dependency -- no extra positive service cases in I6 -- do not author the readability review yet +Expected contract outcome: -Exit criteria: +- `BENCH-ECOM` full stays passing +- `BENCH-SERVICE` full flips away from `passing/valid` +- target state for `BENCH-SERVICE` full: + - `accounting_status = invalid` + - `benchmark_status = invalid` + - `gate_status = open` -- `examples/service/units` loads as a real benchmark root -- `BENCH-SERVICE` is active on purpose rather than reserved by placeholder -- the label roster matches this plan exactly +Proof: -### Phase 2: Service proof wall +- `cargo test -p spec-cli --test rust_v1_service` +- `cargo test -p spec-cli --test rust_v1_closure` +- `cargo run -p spec-cli -- status examples/ecommerce/units --format json` +- `cargo run -p spec-cli -- status examples/service/units --format json` -Objective: +### Slice 3: Rebuild status and export as stored-truth consumers -- refresh real proof for every positive service case and every required service - molecule so the active benchmark can pass truthfully +Deliverables: -Primary write scope: +- add additive `category_qualification` only where the surface is reading + preserved/passported semantic review +- remove any status/export path that reaches `SemanticProjectionMode::Refresh` + for category qualification +- ensure a missing passport never yields `supported_qualified` +- pin exact no-passport export behavior for seam rows: + - keep the `projected_units[]` row + - do not synthesize `semantic_review` + - do not synthesize `supported_qualified` + - allow only `semantic_review = null` with `category_qualification = null` +- ensure stale stored truth remains stale instead of being silently refreshed by + a consumer +- bump public JSON schema versions when additive qualification is introduced -- `examples/service/units/**` -- generated proof artifacts under `examples/service/units/**` -- `examples/service/src/generated/**` +Proof: -Required changes: +- `cargo test -p spec-cli --test cli` +- `cargo test -p spec-cli --test m14_regressions` +- `cargo run -p spec-cli -- export examples/ecommerce/units` +- `cargo run -p spec-cli -- export examples/service/units` -- author and prove `billing/checkout_success_flow.test.spec` -- author and prove `billing/checkout_declined_discount_flow.test.spec` -- author and prove `billing/discount_strategy_quote_flow.test.spec` -- run `spec test` for all six positive service units -- run `spec test` for all three service molecule files -- run `spec build examples/service/units --output examples/service/src/generated` -- confirm every positive service case is `status: "valid"` -- confirm every required service molecule is `status: "valid"` +### Slice 4: Re-freeze snapshot and artifact parity -Hard rules: +Deliverables: -- if a service case needs unsupported control flow, replace the case -- if a service failure-path idea needs async/IO or fallback, reject the idea -- if a seam shape no longer fits current support truth, adapt it to the shipped - ecommerce seam pattern instead of widening the contract +- snapshot output reuses the same benchmark qualification result as live + projection +- readability artifacts continue to report readability state, not smuggled + category-truth state +- fixture refresh happens only after slices 1 through 3 are green -Exit criteria: +Proof: -- `status examples/service/units --format json` can truthfully pass except for - readability review if Phase 3 has not run yet -- `examples/service/src/generated/**` is final enough for readability review +- `cargo run -p spec-cli -- benchmark snapshot BENCH-ECOM` +- `cargo run -p spec-cli -- benchmark snapshot BENCH-SERVICE` -### Phase 3: Service benchmark projection, snapshot, readability, and regressions +## Required Regression Cases -Objective: +These are mandatory. The respin is not done without all of them. -- make the active service benchmark visible, current, and difficult to regress +| Case | Why it exists | Expected result | +| --- | --- | --- | +| missing passport seam unit | protects the no-synthetic-truth boundary | export keeps the `projected_units[]` row but emits `semantic_review = null`, `category_qualification = null`, and never `supported_qualified` | +| stale stored seam truth | protects against read-side refresh minting | consumer output may stay stale or unqualified, but may not become fresher than stored truth | +| service sibling with supported benchmark label | protects benchmark honesty | case remains visible, unqualified, and non-credit-bearing; full `BENCH-SERVICE` becomes invalid/open | +| canonical ecommerce seam row | protects the happy path | remains `supported_qualified` and credit-eligible | +| terminal unsupported seam row | protects additive unsupported visibility | may be `unsupported_qualified`, but never positive-credit eligible | -Primary write scope: +## Failure Modes Registry -- `benchmarks/snapshots/BENCH-SERVICE.snapshot.json` -- `benchmarks/reviews/BENCH-SERVICE.readability.review.json` -- `spec-cli/tests/rust_v1_service.rs` -- `spec-cli/tests/fixtures/benchmarks/**` -- targeted `spec-core/src/benchmark.rs` or `spec-cli/src/commands.rs` only if - the active service benchmark exposes a read-side truth bug - -Required changes: - -- refresh `BENCH-SERVICE.snapshot.json` from reserved state to active state -- author the first `BENCH-SERVICE` readability review against the final - projection digest and generated file set -- add a dedicated integration suite for service-benchmark assertions -- refresh or add benchmark fixtures for: - - service benchmark-root `status` - - service benchmark-root `export` - - service benchmark snapshot - - repo-root inventory showing active `BENCH-SERVICE` -- add regressions that prove: - - active `BENCH-SERVICE` becomes non-passing if any required molecule is - missing, stale, or failing - - active `BENCH-SERVICE` becomes non-current when the readability digest or - file set drifts - - service partial scopes emit zero positive credit - - `BENCH-ECOM` and `BENCH-CROSSLIB` remain truthful while service activates - -Hard rules: - -- do not re-open benchmark schema design -- do not merge service activation by only changing fixtures -- projection code changes are allowed only to fix truthful active-benchmark - behavior - -Exit criteria: - -- the service benchmark is active, current, snapshot-backed, and regression - protected - -### Phase 4: Docs and closeout - -Objective: - -- align repo-facing docs and milestone bookkeeping to the now-active service - benchmark wall - -Primary write scope: - -- `README.md` -- `docs/rust_v1_contract_stack.md` -- `TODOS.md` -- `CHANGELOG.md` - -Required changes: - -- update `README.md` benchmark roster so `BENCH-SERVICE` is no longer described - as reserved-only -- update `docs/rust_v1_contract_stack.md` so the implementation ladder and repo - note reflect shipped `I5` closure and active `I6` service proof work -- update `TODOS.md` to retire stale post-`M68` closure placeholders and leave - only true follow-on oceans -- record the active service benchmark landing in `CHANGELOG.md` -- if the benchmark wall truly closes the final narrow-core proof gate, say that - explicitly in docs; if not, do not overstate it - -Exit criteria: - -- docs, labels, snapshots, reviews, and CLI truth all teach the same story - -## Implementation Tasks - -Synthesized from the plan's locked decisions and risk surfaces. - -- [ ] **T1 (P1, human: ~2h / CC: ~20min)** — service scaffold — create - `examples/service/` as a single-library benchmark root - - Surfaced by: Phase 1 — `BENCH-SERVICE` cannot activate without a real root - - Files: `examples/service/**`, `benchmarks/labels.json` - - Verify: `cargo run -p spec-cli -- status examples/service/units --format json` - -- [ ] **T2 (P1, human: ~2h / CC: ~20min)** — service function roster — promote - the billing leaf and wrapper cases from the M19 fixture pack - - Surfaced by: Intended Service Workload — supported billing shapes already - exist and should be reused rather than re-invented - - Files: `examples/service/units/billing/*.unit.spec` - - Verify: `cargo run -p spec-cli -- test examples/service/units/billing/apply_membership_discount.unit.spec` - -- [ ] **T3 (P1, human: ~2h / CC: ~20min)** — service seam roster — author the - plain `discount_strategy` and `pricing_quote` seams inside the service - root - - Surfaced by: I6 Service Closure Matrix — service proof must include seam - usage, not functions only - - Files: `examples/service/units/billing/discount_strategy.unit.spec`, - `examples/service/units/billing/pricing_quote.unit.spec` - - Verify: `cargo run -p spec-cli -- test examples/service/units/billing/discount_strategy.unit.spec` - -- [ ] **T4 (P1, human: ~90min / CC: ~15min)** — service molecule gate — author - and prove the three required service flows - - Surfaced by: Frozen Decisions 6 — required molecule roster is fixed - - Files: `examples/service/units/billing/*.test.spec` - - Verify: `cargo run -p spec-cli -- test examples/service/units/billing/checkout_success_flow.test.spec` - -- [ ] **T5 (P1, human: ~60min / CC: ~10min)** — active benchmark labels — - switch `BENCH-SERVICE` to active and populate the frozen case roster - - Surfaced by: Frozen Decisions 1 and 5 - - Files: `benchmarks/labels.json` - - Verify: `cargo run -p spec-cli -- status examples/service/units --format json` - -- [ ] **T6 (P1, human: ~2h / CC: ~20min)** — service regression wall — add a - dedicated `rust_v1_service` integration suite and service benchmark - fixtures - - Surfaced by: Phase 3 — active service truth must be hard to regress - - Files: `spec-cli/tests/rust_v1_service.rs`, - `spec-cli/tests/fixtures/benchmarks/**` - - Verify: `cargo test -p spec-cli rust_v1_service` - -- [ ] **T7 (P2, human: ~45min / CC: ~10min)** — snapshot plus readability — - refresh the active service snapshot and author the first service - readability review - - Surfaced by: Target Outcome 4 and 6 - - Files: `benchmarks/snapshots/BENCH-SERVICE.snapshot.json`, - `benchmarks/reviews/BENCH-SERVICE.readability.review.json` - - Verify: `cargo run -p spec-cli -- benchmark snapshot BENCH-SERVICE` - -- [ ] **T8 (P2, human: ~45min / CC: ~10min)** — docs closeout — update the repo - narrative to reflect active service proof - - Surfaced by: Phase 4 — current docs still teach the pre-I5 ladder - - Files: `README.md`, `docs/rust_v1_contract_stack.md`, `TODOS.md`, - `CHANGELOG.md` - - Verify: manual doc consistency pass against live CLI output - -## Acceptance Commands - -Phase-2 proof refresh: +| Risk | Trigger | Guard | +| --- | --- | --- | +| read-side truth minting returns through a helper | status/export/snapshot accidentally reuse refresh projection | name the exact read-side helpers and prove they use preserve-mode only, then cover with fixture regressions | +| benchmark honesty fixed but status/export still drift | only benchmark code is corrected | require parity assertions across service status and export fixtures | +| seam rows still receive positive credit through legacy fallback | benchmark keeps `semantic_support_status` shortcut for seam candidates | add a targeted benchmark regression that seam candidates require `category_qualification`, and keep legacy fallback non-seam-only | +| missing passport path still looks supported | export/status silently synthesize semantic review | add explicit no-passport service fixture assertions | +| stale proof gets hidden by live recompute | read-side consumer re-evaluates current authored truth | add stale-passport regression and compare against stored output | +| fixture refresh masks a logic bug | artifacts are updated before invariants are asserted | refresh fixtures only after targeted tests and live CLI checks pass | -```bash -cargo run -p spec-cli -- build examples/service/units --output examples/service/src/generated -cargo run -p spec-cli -- test examples/service/units/billing/apply_membership_discount.unit.spec -cargo run -p spec-cli -- test examples/service/units/billing/apply_regional_fee.unit.spec -cargo run -p spec-cli -- test examples/service/units/billing/checkout_net_total.unit.spec -cargo run -p spec-cli -- test examples/service/units/billing/checkout_net_total_guarded_fee.unit.spec -cargo run -p spec-cli -- test examples/service/units/billing/discount_strategy.unit.spec -cargo run -p spec-cli -- test examples/service/units/billing/pricing_quote.unit.spec -cargo run -p spec-cli -- test examples/service/units/billing/checkout_success_flow.test.spec -cargo run -p spec-cli -- test examples/service/units/billing/checkout_declined_discount_flow.test.spec -cargo run -p spec-cli -- test examples/service/units/billing/discount_strategy_quote_flow.test.spec -``` +## Proof Commands -Benchmark proof wall: +Run these in order before calling the wedge complete: ```bash +cargo test -p spec-core +cargo test -p spec-cli --test rust_v1_service +cargo test -p spec-cli --test rust_v1_closure +cargo test -p spec-cli --test cli +cargo test -p spec-cli --test m14_regressions +cargo run -p spec-cli -- status examples/ecommerce/units --format json cargo run -p spec-cli -- status examples/service/units --format json +cargo run -p spec-cli -- export examples/ecommerce/units cargo run -p spec-cli -- export examples/service/units +cargo run -p spec-cli -- benchmark snapshot BENCH-ECOM cargo run -p spec-cli -- benchmark snapshot BENCH-SERVICE ``` -Non-regression wall: - -```bash -cargo run -p spec-cli -- status examples/ecommerce/units --format json -cargo run -p spec-cli -- status examples/crosslib-app/units --format json -cargo test -p spec-cli rust_v1_service -cargo test -p spec-cli rust_v1_closure -cargo run -p spec-cli -- status . --format json -``` - -Acceptance notes: - -- the benchmark-root commands above must exit successfully when I6 is complete -- the final repo-root `status . --format json` command is diagnostic; it must - preserve `scope_authority: "inventory_only"` and show all three benchmarks, - but it is not required to become a zero-exit proof command for this workspace - -## Test Strategy - -### Coverage diagram - -```text -CODE PATH COVERAGE -=========================== -[+] Service benchmark activation - | - +- benchmarks/labels.json - | +- [CHANGE] BENCH-SERVICE reserved -> active - | +- [CHANGE] add 6 service cases - | \- [CHANGE] add 3 required molecules - | - +- examples/service/units/billing/** - | +- [CHANGE] positive function units - | +- [CHANGE] seam units - | \- [CHANGE] molecule flows - | - +- benchmark projection core - | +- [TEST] active BENCH-SERVICE passes only with full current proof - | \- [TEST] partial service scope never earns positive credit - | - \- snapshot + readability - +- [CHANGE] active BENCH-SERVICE snapshot - \- [TEST] current vs stale review truth - -[+] Non-regression coverage - | - +- BENCH-ECOM - | \- [TEST] remains passing/current - | - +- BENCH-CROSSLIB - | \- [TEST] remains passing/zero-credit - | - \- repo-root inventory - \- [TEST] remains inventory_only while showing active BENCH-SERVICE - -USER FLOW COVERAGE -=========================== -[+] Maintainer service-proof flow - | - +- author service specs - +- validate failing specs with `spec validate --format json` - +- build service generated Rust - +- refresh unit passports - +- refresh molecule evidence - +- run service benchmark-root status/export/snapshot - \- confirm active current BENCH-SERVICE - -[+] Failure states - | - +- active benchmark with missing molecule proof becomes non-passing - +- service review digest drift becomes non-current - +- partial service scope cannot launder positive credit - +- attempted unsupported service shape gets rejected from the positive roster - \- prior benchmarks remain green while service activates -``` - -### Mandatory regression tests - -1. `BENCH-SERVICE` becomes non-passing if any required service molecule proof is - missing, stale, or failing. -2. `BENCH-SERVICE` reports current readability only when both - `projection_digest` and `readability_generated_files` match the live - projection. -3. benchmark-root `status` and `export` agree on the active service benchmark - summary and case truth. -4. service namespace and single-file scopes emit zero positive supported credit. -5. repo-root `status . --format json` remains `scope_authority: - "inventory_only"` after service activation. -6. `BENCH-ECOM` still reports passing/current after the service benchmark lands. -7. `BENCH-CROSSLIB` still reports passing with `positive_credit_cases: 0` after - the service benchmark lands. -8. the unhappy-path service flow remains a supported business-path proof, not a - fallback-backed or unsupported workaround. - -### Test files - -Preferred write shape: - -- keep broad benchmark command-wall fixtures in `spec-cli/tests/cli.rs` -- keep I5 closure assertions in `spec-cli/tests/rust_v1_closure.rs` -- add a new dedicated suite `spec-cli/tests/rust_v1_service.rs` for the active - service benchmark -- keep benchmark fixture JSON under `spec-cli/tests/fixtures/benchmarks/` -- if adversarial service fixtures are needed, keep them under a dedicated - service subtree rather than mixing them into unrelated milestone packs - -## Failure Modes Registry - -| Failure mode | Why it matters | Coverage requirement | Critical gap? | -| --- | --- | --- | --- | -| service benchmark goes active with the wrong roster or empty cases | fake V1 closure | label-registry assertions plus full benchmark fixture checks | yes | -| service workload quietly depends on unsupported control flow or widened semantics | benchmark claims more than M66 allows | positive-roster review plus service integration tests | yes | -| unhappy-path flow is implemented as fallback or unsupported trickery | failure-path proof becomes dishonest | explicit unhappy-path molecule proof plus benchmark projection checks | yes | -| service readability review exists but is stale | maintainers think service code was reviewed when read-side truth disagrees | current-vs-stale review regression | yes | -| service partial scope emits positive credit | read-side anti-laundering rule regresses | namespace and single-file zero-credit regressions | yes | -| repo-root acceptance wrongly expects zero exit | implementation appears blocked even when contract is preserved | acceptance notes must treat repo-root status as diagnostic inventory | yes | -| ECOM or CROSSLIB regresses while BENCH-SERVICE activates | I6 breaks shipped proof walls | non-regression suite across all three benchmarks | yes | -| contract-stack doc stays out of sync after I6 lands | repo authority becomes confusing again | closeout doc pass | no | - -## Performance / Complexity Guardrails - -I6 should not introduce a meaningful runtime or maintenance cost increase. - -Guardrails: - -- benchmark projection remains label-driven and linear in loaded benchmark cases -- service example stays single-library and local -- no new benchmark commands are introduced -- no new artifact families are introduced -- no new semantic-family support is introduced -- service failure-path proof must stay in business semantics, not runtime - orchestration -- snapshot and review refresh remain explicit artifact updates, never hidden - side effects of `status` or `export` - -If implementation wants a new registry, a new service runtime, or a broader Rust -admission story, it is solving the wrong problem. - -## Worktree Parallelization Strategy - -I6 has real parallelization opportunities, but they have to be sequenced so the -benchmark roster freezes before tests, snapshots, and docs start depending on -it. - -### Dependency table - -| Step | Modules touched | Depends on | -| --- | --- | --- | -| service scaffold and benchmark activation | `examples/service/`, `benchmarks/` | — | -| service proof refresh | `examples/service/`, `examples/service/src/generated/` | service scaffold and benchmark activation | -| service regression suite | `spec-cli/tests/`, `spec-cli/tests/fixtures/benchmarks/` | service scaffold and benchmark activation | -| projection/core fixes if needed | `spec-core/`, `spec-cli/src/` | service scaffold and benchmark activation | -| snapshot, readability, and docs closeout | `benchmarks/snapshots/`, `benchmarks/reviews/`, `docs/`, repo root docs | service proof refresh, service regression suite, and any projection/core fixes | - -### Parallel lanes - -Lane A: service scaffold and benchmark activation -> service proof refresh -(sequential, shared `examples/service/`) - -Lane B: service regression suite (independent after Lane A freezes the roster) - -Lane C: projection/core fixes only if activation exposes a real truth bug -(independent from Lane B, but dependent on Lane A) - -Lane D: snapshot, readability, and docs closeout (sequential after A/B/C merge) - -### Execution order - -1. Launch Lane A first and freeze the benchmark roster. -2. Once Lane A has frozen `BENCH-SERVICE`, launch Lane B in parallel. -3. Launch Lane C only if Lane A or Lane B exposes a real read-side truth bug. -4. Merge A, B, and C. -5. Launch Lane D after that merge, not before. - -### Conflict flags - -- `benchmarks/labels.json` belongs to Lane A only. -- `examples/service/**` belongs to Lane A only. -- `spec-cli/tests/fixtures/benchmarks/**` belongs to Lane B until fixture shape - stabilizes; Lane D should not touch it in parallel. -- `benchmarks/snapshots/BENCH-SERVICE.snapshot.json` and - `benchmarks/reviews/BENCH-SERVICE.readability.review.json` belong to Lane D - only. -- if Lane B discovers it needs a label change, queue that change for post-merge - integration instead of editing `benchmarks/labels.json` in parallel. - -## What Success Looks Like - -After I6: - -- `BENCH-SERVICE` is an active, passing, current positive benchmark -- the repo proves both example-domain and service-shaped workloads on the same - narrow-core contract -- service proof includes both a happy path and a business-path unhappy flow -- the read-side benchmark wall stays truthful under `status`, `export`, and - snapshot -- the benchmark roster still distinguishes positive proof from companion - negative proof -- the next milestone can debate bounded generics, async/IO, or broader V1.1 - scope from a fully closed proof baseline instead of from a reserved placeholder - -## NOT in scope - -- bounded generics admission into V1 -- async or IO admission into V1 -- trait-authored, lifetime-heavy, or macro/framework-heavy authored support -- cross-library service benchmark work -- benchmark schema redesign -- new benchmark commands or a standalone benchmark subsystem -- repository-wide command-scope redesign -- ORCH plan authoring -- broader TypeScript-lane work unrelated to Rust V1 service proof - -## Completion Summary - -- Step 0: Scope challenge accepted as service benchmark activation only -- Architecture review structure: one active service wall defined beside the - shipped ECOM and CROSSLIB walls -- Code quality structure: authored truth, proof writers, and read-side surfaces - are now separated explicitly -- Test review structure: coverage diagram, mandatory regressions, failure modes, - and acceptance semantics are all specified -- Parallelization: 4 lanes total, 2 conditional/parallel lanes, 2 sequential - dependency stages -- Unresolved decisions: none; if new ambiguity appears during implementation, - the implementation is out of plan and should stop for a scope check +Required code-review audit before calling the wedge complete: + +- name the exact benchmark/status/export/snapshot read-side helpers that project + semantic/category truth +- verify those helpers use preserved semantic truth only +- verify any remaining `SemanticProjectionMode::Refresh` callsites are limited to + write-path or explicitly non-read-side flows + +## Done Means + +This respin is complete only when all of the following are true: + +1. the repo has one shared category-truth registry and qualification contract in + `spec-core` +2. benchmark, status, export, and snapshot consumers all derive qualification + from preserved semantic truth only +3. a seam benchmark row cannot receive positive credit through legacy + `semantic_support_status` fallback alone +4. a unit with no passport does not surface `supported_qualified` + and does not synthesize seam semantic review on export +5. the no-passport export shape is stable and explicit for machine readers + rather than left to consumer guesswork: + - `projected_units[]` row present + - `semantic_review = null` + - `category_qualification = null` +6. stale stored semantic truth is never upgraded by a read-side consumer into + fresher category truth +7. `BENCH-ECOM` remains a passing positive wall +8. `BENCH-SERVICE` no longer reports a full supported-positive success while + its seam siblings remain unqualified +9. status/export schema bumps and fixture refreshes are complete wherever + additive `category_qualification` is exposed +10. `.spec.passport.json` remains free of persisted `category_qualification` + +## Not Doing In This Plan + +- landing the old `feat/m101-category-truth-registry` branch +- inventing a second semantic/category truth plane +- widening supported seam truth to rescue service siblings +- relabeling benchmark cases to avoid the mismatch +- mixing this contract repair with separate family-analysis or seam-substrate + roadmap work diff --git a/README.md b/README.md index 3f25454..54f3be2 100644 --- a/README.md +++ b/README.md @@ -1,155 +1,84 @@ # spec -`spec` is a Rust workspace for authoring semantic units in `*.unit.spec` files, validating them with JSON Schema, normalizing them into internal IR, and generating readable Rust source. +`spec` is a Rust-first semantic-source workflow for code that you want to be +explicit, machine-checkable, and provable. -## Workflow +You author units in `*.unit.spec` files, run a tight validate/build/test loop, +generate readable Rust, and keep proof artifacts that say what actually passed. +The project is built for AI-assisted and human-assisted development where "it +probably works" is not a good enough answer. -1. Author a `.unit.spec` file. -2. Validate it with the CLI. -3. Build: `spec build` validates, generates, and compiles in one step. -4. Test: `spec test` runs the full pipeline and writes observed evidence to unit passports plus molecule evidence artifacts. -5. Export: `spec export` emits a machine-readable JSON bundle for downstream tooling. -6. Plan: `spec plan validate` and `spec plan export` turn authored change intent into truthful local-library impact data. +## What spec is -## Workspace +At a high level, `spec` gives you: -- `spec-core`: parsing, validation, normalization, generation, pipeline, and export primitives -- `spec-cli`: CLI for `validate`, `generate`, `build`, `test`, `export`, and `plan` -- `examples/ecommerce`: a small single-library example with pricing units -- `examples/shared-spec`, `examples/shared-crate`, `examples/crosslib-app`: a direct sibling-library example for `[libraries]` and `shared::...` deps +- semantic source files instead of raw implementation as the only source of truth +- generated Rust from explicit contracts +- atom and molecule proof surfaces with tracked evidence +- machine-readable `validate`, `status`, and `export` outputs for agents and tools +- a bounded, honest product claim surface instead of fuzzy support language -## Project docs +This repo is the implementation of that workflow, plus the example libraries and +proof walls used to keep the claims honest. -- [`CHANGELOG.md`](CHANGELOG.md): shipped release history -- [`PLAN.md`](PLAN.md): active implementation roadmap, with shipped milestone context below the current contract -- [`DECISIONS.md`](DECISIONS.md): project-level decisions that stay stable across releases -- [`TODOS.md`](TODOS.md): backlog and follow-up inventory -- [`AGENTS.md`](AGENTS.md): agent workflow and machine-readable `spec` authoring loop -- [`CLAUDE.md`](CLAUDE.md): lightweight routing rules for Claude/Codex sessions in this repo -- [`docs/rust_v1_contract_stack.md`](docs/rust_v1_contract_stack.md): Rust V1 contract-stack index and the frozen I3.5 command wall -- [`examples/ecommerce/README.md`](examples/ecommerce/README.md): local example walkthrough -- [`examples/crosslib-app/README.md`](examples/crosslib-app/README.md): cross-library example walkthrough -- [`docs/north_star_v0.2.md`](docs/north_star_v0.2.md), [`docs/high_level_technical_architecture_v0.2.md`](docs/high_level_technical_architecture_v0.2.md), and [`docs/roadmap_and_release_shape_v0.1.md`](docs/roadmap_and_release_shape_v0.1.md): historical design context from the pre-ship planning phase -- [`.implemented/`](.implemented/): archived milestone release plans and early design artifacts +## What you need -## Quickstart +- Rust `1.89.0` +- `cargo` +- this repo checked out locally -```bash -cargo test -cargo run -p spec-cli -- validate examples/ecommerce/units -cargo run -p spec-cli -- generate examples/ecommerce/units -``` +The pinned toolchain lives in [`rust-toolchain.toml`](rust-toolchain.toml). -After installation, the binary is `spec`: +If you want a local binary instead of `cargo run`, install from the repo root: ```bash -cargo install spec-cli -spec validate examples/ecommerce/units -spec generate examples/ecommerce/units +cargo install --path spec-cli ``` -## Benchmarks (M68) +## The core loop -Rust V1 benchmark mechanics are repo-root read surfaces, not authored spec truth. The authored benchmark registry lives in `benchmarks/labels.json`; readability review anchors live in `benchmarks/reviews/*.readability.review.json`; and `spec benchmark snapshot ` writes derived snapshots under `benchmarks/snapshots/`. +If you are new here, do this first. -The benchmark-root proof wall after I6 is: +From the repo root: ```bash +cargo run -p spec-cli -- validate examples/ecommerce/units/pricing/apply_tax.unit.spec --format json +cargo run -p spec-cli -- build examples/ecommerce/units --output examples/ecommerce/src/generated +cargo run -p spec-cli -- test examples/ecommerce/units/pricing/apply_tax.unit.spec +cargo run -p spec-cli -- test examples/ecommerce/units/pricing/discount_strategy_checkout_flow.test.spec cargo run -p spec-cli -- status examples/ecommerce/units --format json -cargo run -p spec-cli -- export examples/ecommerce/units -cargo run -p spec-cli -- status examples/service/units --format json -cargo run -p spec-cli -- export examples/service/units -``` - -Repo-root surfaces remain available, but they have a narrower contract: - -- `cargo run -p spec-cli -- status . --format json` is a supported broad-scope inventory surface only. Treat its scope authority as `inventory_only`, not as the fresh-clone proof default. -- `cargo run -p spec-cli -- export .` is unsupported for this workspace shape and must fail with `SPEC_UNSUPPORTED_SCOPE` instead of emitting a fake aggregate bundle. - -The seeded roster is: - -- `BENCH-ECOM`: active positive benchmark rooted at `examples/ecommerce/units` -- `BENCH-CROSSLIB`: active companion-negative benchmark rooted at `examples/crosslib-app/units` -- `BENCH-SERVICE`: active positive benchmark rooted at `examples/service/units` - -Path scope is intentional: - -- Exact benchmark-root scopes are the proof-authoritative default and emit full projections with digests, benchmark status, gate status, and readability review state. -- Broad repo-root scope still emits a full projection, but only as `inventory_only` reporting. -- Namespace and single-file scopes emit partial projections only. Partial scope never emits positive supported credit, even when the covered carrier is individually `valid`. -- Companion-negative cases stay visible but never count as positive supported credit. - -Writer versus reader wall: - -- `spec status` and `spec export` only project benchmark truth; they do not write passports, molecule evidence, or benchmark proof. -- `spec benchmark snapshot ` writes only snapshot artifacts under `benchmarks/snapshots/`. -- `.spec.passport.json` and `*.test.evidence.json` remain the read-only proof inputs for benchmark accounting during M68. - -`spec export` now emits `schema_version: 4` and additively includes top-level `benchmarks[]` under the same contract as `spec status` for the same scope. - -## Bounded TypeScript lane (M61) - -`spec` now exposes one bounded TypeScript execution lane. It is intentionally narrow: - -M61 extends the bounded Bun-backed TypeScript lane to recursive local-plus-cross-library closure across the already-supported function families, while preserving family-specific direct-dep contracts, additive proof, atom-only execution, and the broader bans on arbitrary 4+ topology parity and molecule TypeScript execution. - -- Bun is the only TypeScript prerequisite. The lane shells out to `bun`; no alternate Node, npm, or tsx contract is supported. -- Eligible roots are `kind: function` specs whose reachable closure resolves from the loaded unit set, classifies to already-supported semantic-review function families, and authors non-empty `body.typescript` at every reachable unit. -- Closure collection is semantic-review-driven and dep-driven. It recurses across same-tree and direct sibling-library deps, dedupes repeated local-plus-cross-library subgraphs, and excludes unrelated loaded units from the generated TypeScript tree. -- The preserved portability exceptions remain in place for direct cross-library roots. A `function.arithmetic_leaf.monotone_up.v1` root may still use exactly one direct cross-library helper dep that classifies to `function.helper.identity_passthrough.v1`, authors non-empty `body.typescript`, and resolves from the loaded unit set and generated tree. -- The preserved direct cross-library wrapper lane remains frozen to the `function.wrapper.pipeline.v1` family with the direct dep tuple `function.arithmetic_leaf.monotone_down_nonnegative.v1` then `function.arithmetic_leaf.monotone_up.v1`. -- The preserved direct cross-library chain3 lane remains frozen to the `function.wrapper.pipeline.chain3.v1` family with the direct dep tuple `function.wrapper.pipeline.v1` or `function.wrapper.pipeline.chain3.v1` in slot 1, then `function.arithmetic_leaf.monotone_up.v1`, then `function.arithmetic_leaf.monotone_down_nonnegative.v1`. -- Family-specific direct dep contracts remain frozen even when closure collection recurses across library boundaries. The M60 required-arg wrapper family stays bounded to its current supported shape rather than widening the lane to arbitrary wrapper argument forms. -- Rust remains the default target. TypeScript proof is additive only; it writes `target_proofs.typescript` without replacing the Rust proof surface. -- TypeScript execution is atom-only. Only `local_tests` run in this lane; `.test.spec` molecule tests remain unsupported for `--target-language typescript` and fail before Bun runs. -- The accepted `local_tests.expect` grammar is deliberately small: `(Decimal::new(int, scale), ...) == Decimal::new(int, scale)`. The left-hand side must be a direct call to the current unit, and both the arguments and expected value must use integer-literal `Decimal::new(...)` forms. - -Commands available in this lane: - -```bash -spec generate --target-language typescript -spec build --target-language typescript -spec test --target-language typescript -spec status --target-language typescript ``` -`spec status --target-language typescript` reports target-specific proof only. In M61, a root-level status over a mixed example like `examples/ecommerce` or `examples/crosslib-app` may stay non-green unless every unit in scope is either freshly proven in the bounded recursive local-plus-cross-library lane, covered by the preserved portability contracts, or truthfully remains outside those lanes. +What each step tells you: -The generated helper filenames remain frozen in M61: +1. `validate` answers: is the authored source legal `spec` input? +2. `build` answers: can the backend lower and compile it? +3. `test` answers: did it actually prove, and did it refresh the proof record? +4. `status` answers: what does the repo project as true right now? -- `__spec_ts/runtime.ts` -- `__spec_ts/build_entry.ts` -- `__spec_ts/local_tests.ts` +Files that change during that loop: -Commands and surfaces that do not widen for M61: +- generated Rust under `examples/ecommerce/src/generated/` +- unit proof in `*.spec.passport.json` +- molecule proof in `*.test.evidence.json` -- `spec validate` does not accept `--target-language` -- `spec export` does not accept `--target-language` -- The broader TypeScript oceans still explicitly deferred are arbitrary authored 4+ direct-dep topology parity, new semantic-family promotion, molecule TypeScript execution, and seam-kind TypeScript execution. +`src/generated/` is ephemeral output and is gitignored in this repo. The +canonical example's proof artifacts are tracked on purpose so `spec status` stays +truthful on a fresh clone. Re-running `spec generate` on an unchanged tree +should not rewrite tracked passports just to bump `generated_at`. -## Spec format +If you install the binary, the command name is `spec`, but the repo-native +`cargo run -p spec-cli -- ...` flow is the safest place to start. -Each unit is a YAML document with these common required fields: +## A tiny example -- `id`: hierarchical unit id like `pricing/apply_discount` -- `kind`: authored unit shape, currently `function`, `data`, or `sum` -- `intent.why`: why the unit exists - -Kind-specific authored fields: - -| `kind` | Required authored fields | Optional authored fields | Forbidden top-level fields | -| --- | --- | --- | --- | -| `function` | `contract`, `body.rust` | `deps`, `imports`, `local_tests`, `links` | none | -| `data` | `data.fields`, one or more `constructors`, one or more `methods` | `local_tests`, `links`, `backends.rust` | `contract`, `deps`, `imports`, `body.rust` | -| `sum` | `sum.variants`, one or more `methods` | `local_tests`, `links`, `backends.rust` | `contract`, `deps`, `imports`, `body.rust`, `constructors` | - -For `kind: function`, `spec` generates the complete `pub fn` signature from `contract.inputs` and `contract.returns`. A minimal unit with a contract looks like: +This is the shape of a simple function unit: ```yaml id: pricing/apply_tax kind: function intent: - why: Add sales tax to a subtotal using a rate expressed as a decimal fraction. + why: Add sales tax to a subtotal. contract: inputs: subtotal: Decimal @@ -160,379 +89,142 @@ imports: body: rust: | { - let taxed = subtotal + subtotal * rate; - round(taxed) + subtotal + subtotal * rate } ``` -This generates: +That source is not just documentation. It is the authored input to validation, +generation, proof, and read-side status/export surfaces. -```rust -pub fn apply_tax(subtotal: Decimal, rate: Decimal) -> Decimal { - let taxed = subtotal + subtotal * rate; - round(taxed) -} -``` +## Vocabulary starter -For `kind: data`, one `.unit.spec` file authors a top-level data seam with shared fields plus one or more nested constructors and one or more nested methods. A minimal seam based on the canonical M50 `pricing/pricing_quote` example looks like: +These are the words that matter on day 1. -```yaml -id: pricing/pricing_quote -kind: data -intent: - why: Quote a checkout total from subtotal plus discount and tax rates. -data: - fields: - subtotal: - type: rust_decimal::Decimal - discount_rate: - type: rust_decimal::Decimal - tax_rate: - type: rust_decimal::Decimal -constructors: - - id: new - intent: - why: Create a quote from explicit subtotal and rates. - contract: - inputs: - subtotal: rust_decimal::Decimal - discount_rate: rust_decimal::Decimal - tax_rate: rust_decimal::Decimal - initializes: - subtotal: subtotal - discount_rate: discount_rate - tax_rate: tax_rate -methods: - - id: discounted_subtotal - intent: - why: Return the discounted subtotal before tax. - receiver: shared_ref - contract: - returns: rust_decimal::Decimal - deps: - - pricing/apply_discount - lowering: - rust: - body: | - { - apply_discount(self.subtotal, self.discount_rate) - } - - id: total - intent: - why: Return the final checkout total after discount and tax. - receiver: shared_ref - contract: - returns: rust_decimal::Decimal - deps: - - pricing/apply_tax - lowering: - rust: - body: | - { - apply_tax(self.discounted_subtotal(), self.tax_rate) - } -backends: - rust: - derives: - - Clone - - Debug - - PartialEq -``` +| Term | Meaning | Why you care | +| --- | --- | --- | +| unit | One authored semantic source file, usually `*.unit.spec` | It is the basic building block. | +| kind | The top-level unit shape: currently `function`, `data`, or `sum` | It tells you what the unit is structurally. | +| seam | A non-function shared semantic shape such as `kind:data` or `kind:sum` | It helps explain why seam behavior is different from function-family behavior. | +| atom test | A unit-local test from `local_tests` | It proves one unit in isolation. | +| molecule test | A `.test.spec` interaction test across multiple units | It proves behavior across unit boundaries. | +| passport | The co-located `.spec.passport.json` proof record for a unit | It stores current unit proof truth and freshness anchors. | +| semantic review | The bounded classifier that can understand some `kind:function` meanings | It is intentionally narrow, not general code understanding. | +| family | A shipped semantic-review bucket such as `function.wrapper.pipeline.v1` | It tells you which supported function meaning shape a unit matches. | +| benchmark | A curated proof wall used for bounded public product claims | It answers what the repo can honestly claim today. | -`kind: data` keeps shared semantics in `data.fields`, `constructors`, and `methods`. Rust-specific lowering stays inside `methods[].lowering.rust.body` and `backends.rust`. +## The mental model -`kind: sum` follows the same seam boundary: keep shared semantics in `sum.variants` and `methods`. Enum cases are authored via `sum.variants`, not `constructors`. Rust-specific lowering stays inside `methods[].lowering.rust.body` and `backends.rust`. +Most confusion in this repo comes from asking one overloaded "is this supported?" +question. Break it into four smaller questions: -## Migrating from 0.2.x +1. Is the authored source valid? +2. Can the backend lower and run it? +3. Do we have current proof? +4. What, if anything, can the repo publicly claim from that proof? -In 0.2.x, `body.rust` contained the full function declaration: +Those are different layers. -```yaml -# 0.2.x format (no longer valid) -body: - rust: | - pub fn apply_tax(subtotal: Decimal, rate: Decimal) -> Decimal { - let taxed = subtotal + subtotal * rate; - round(taxed) - } -``` +The clean split is: -In 0.3.0, strip the `pub fn name(params) -> ReturnType` line and keep only the body block. Move the parameter names and types into `contract.inputs`, and the return type into `contract.returns`: +- write-path truth: authored specs, validation, generation, build/test, passports, molecule evidence +- read-side interpretation: semantic review, status/export projection, benchmark accounting, recommendation analysis -```yaml -# 0.3.0 format -contract: - inputs: - subtotal: Decimal - rate: Decimal - returns: Decimal -body: - rust: | - { - let taxed = subtotal + subtotal * rate; - round(taxed) - } -``` - -Running `spec validate` on a 0.2.x unit will emit a clear migration error pointing to the file. - -## Example - -The ecommerce example demonstrates the canonical M13 migration wedge alongside the existing pricing units, three molecule tests, one checked-in plan artifact, and tracked molecule evidence so the shipped example stays green on a fresh clone: - -- `money/round` -- `pricing/apply_discount` -- `pricing/apply_tax` -- `pricing/calculate_total` -- `pricing/discount_strategy` (`kind: sum`) -- `pricing/pricing_quote` (`kind: data`) -- `pricing/checkout_flow` -- `pricing/discount_strategy_checkout_flow` -- `pricing/discount_plus_tax` -- `plans/refactors/checkout-tax-refactor.plan.spec` -- `examples/ecommerce/src/raw_baseline/pricing/discount_strategy.rs` (hand-written Rust baseline for the canonical pricing sum seam) -- `examples/ecommerce/src/raw_baseline/pricing/pricing_quote.rs` (hand-written Rust baseline for the canonical pricing data seam) - -Recorded adversarial calibration for the canonical wedge is locked: `pricing/discount_strategy` scored `19`, `pricing/pricing_quote` scored `16`, and `pricing/discount_plus_tax` scored `14`. Canonical wedge remains `pricing/discount_strategy`. - -The example crate is intentionally minimal. It provides a realistic place to keep unit specs, hand-written Rust baselines for the M12 and M13 migration wedges, and a Rust project scaffold that can host generated output. The checked-in `pricing/*.test.evidence.json` files are generated artifacts for this canonical example, not hand-authored source. - -## Commands - -```bash -spec validate # directory or single .unit.spec: schema + semantic validation -spec validate --no-strict # downgrade missing deps to warnings -spec validate --format json # machine-readable JSON output for agents -spec generate # directory only: emit .rs files (default: {crate_root}/src/generated) -spec generate --output # explicit output directory - -spec build # directory only: validate → generate → cargo build -spec build --output # explicit output directory -spec test # directory, single .unit.spec, or single .test.spec -spec test --output # explicit output directory for directory-scoped test runs -spec test # scope to a single unit (filter by module path) -spec test # run one molecule test and write only its .test.evidence.json - -spec status # directory or single .unit.spec: per-root unit and molecule-test health -spec status --format json # machine-readable status for agents - -spec export # directory or single .unit.spec: emit JSON bundle to stdout -spec export --output # write JSON bundle to file - -spec plan validate # validate one .plan.spec file and compute local impact -spec plan validate --format json # machine-readable plan validation + computed impact -spec plan export # emit dedicated plan bundle to stdout -spec plan export --output # write dedicated plan bundle to file -``` - -Canonical ecommerce example loop from the repo root: - -```bash -cargo run -p spec-cli -- validate examples/ecommerce/units/pricing/discount_strategy.unit.spec --format json -cargo run -p spec-cli -- build examples/ecommerce/units --output examples/ecommerce/src/generated -cargo run -p spec-cli -- test examples/ecommerce/units/pricing/discount_strategy.unit.spec -cargo run -p spec-cli -- test examples/ecommerce/units/pricing/discount_strategy_checkout_flow.test.spec -cargo run -p spec-cli -- status examples/ecommerce/units --format json -cargo run -p spec-cli -- export examples/ecommerce/units -``` - -`validate` checks schema and semantic rules. `--no-strict` downgrades missing internal deps to warnings for validation only. `generate` always remains strict, is directory-scoped only, and emits `.rs` files under the output directory while managing `mod.rs` files plus the `.spec-generated` safety marker. - -`spec build` and directory-scoped `spec test` wrap the full pipeline so you can validate, generate, and compile in one step. `spec build` is directory-scoped only. `spec test` updates each unit's `.spec.passport.json` with observed local-test evidence and writes co-located `*.test.evidence.json` artifacts for molecule tests. Passports persist a `freshness_anchor` snapshot as the proof anchor from the last unit test run, while `freshness` is a live projection against the current spec. Marked seam passports may also carry additive `escape_hatch_gate` metadata. In M14 that gate requires both `atom` and `molecule` proof; `atom` is present only when the authored local tests pass and the seam's projected freshness is still current, and an open gate uses a stable reason like `missing required escape-hatch proof: molecule`. +If you keep that split in your head, the rest of the docs get much easier. -Semantic review for `kind:function` is bounded to a small shipped family vocabulary, not arbitrary function understanding. The current supported function family keys are `function.helper.identity_passthrough.v1`, `function.arithmetic_leaf.monotone_down_nonnegative.v1`, `function.arithmetic_leaf.monotone_up.v1`, `function.wrapper.pipeline.normalized_required_arg.v1`, `function.wrapper.pipeline.v1`, and `function.wrapper.pipeline.chain3.v1`. In the canonical ecommerce and TypeScript fixture flows, `money/round`, `pricing/apply_discount`, `pricing/apply_tax`, `pricing/calculate_total`, and the bounded chain3 forms refresh to those family keys when their authored and executable shapes fit honestly. M60 adds one supported wrapper family for apply_tax(discounted, tax_rate.max(Decimal::ZERO)); broader required-argument expressions remain unsupported. +## What to ignore at first -M20 also makes unsupported-function truth explicit. The public fields are exactly `semantic_review.support_status`, `semantic_review.unsupported_reason_codes`, and `semantic_review.rewrite_hints`. New supported reviews write `support_status: supported`; unsupported function reviews write `support_status: unsupported`. Consumers should branch on `semantic_review.support_status == "unsupported"` instead of inferring unsupported state from `verdict` or `evaluator_scope`, though legacy reviews without `support_status` still fall back to `evaluator_scope` plus `unsupported.*.v1` compatibility-key inference. +If you are just authoring or fixing a unit, do not start with: -Unsupported near-miss function shapes remain additive-only, health-neutral, and keyed as `unsupported.function.v1`. Only `spec test` refreshes semantic review truth. `spec build`, `spec generate`, `spec status`, and `spec export` only project stored truth; they do not mint new supported-function semantic review. Fresh unsupported function proof is preserved on read-side surfaces such as `spec status` and `spec export`, while stale unsupported function proof is dropped there and only the underlying freshness or stale health remains visible. +- benchmark roster details +- milestone history +- recommendation and corpus-program decisions +- the full bounded TypeScript lane contract -When you pass a single `.unit.spec` file to `spec validate`, `spec test`, or `spec export`, the CLI stays scoped to that exact unit. When you pass a single `.test.spec` file to `spec test`, the CLI runs only that molecule test and writes only that test's evidence artifact. Sibling `.test.spec` files are otherwise loaded for directory invocations. +Start with the core loop. Come back to the deeper surfaces once you need to +explain a status projection, a semantic-review result, or a public product claim. -Single-file `spec test` runs generate Rust into an isolated internal build surface. They do not rewrite your checked-out `src/generated/` tree, and `--output` is accepted only for directory-scoped `test` runs. +## Current project shape -`spec export` emits a machine-readable JSON bundle containing all units, passports, dependency graph edges, and warnings for any passports that could not be read. Stored `escape_hatch_gate` metadata may appear in passports, but live `spec status` and `spec export` recompute the gate from current passport freshness and molecule evidence rather than trusting the stored field blindly. Canonical seam `proof_coverage` also adds the `molecule` surface only when a covering molecule evidence artifact is both current and passing. +This is the honest short version of what the repo currently is: -`spec plan validate` and `spec plan export` are single-file commands. They accept exactly one `.plan.spec` file, resolve the enclosing library root by walking up to the directory that owns `units/`, validate the authored change set against the current graph, and compute advisory impact for the current local library only. `add` changes stay truthful by contributing `unresolved[]` entries instead of fabricated impact. The repo ships a checked-in example at `examples/ecommerce/plans/refactors/checkout-tax-refactor.plan.spec`. +- Rust-first and proof-first +- semantic source for `function`, `data`, and `sum` units +- atom and molecule proof with tracked evidence +- machine-readable CLI surfaces for validation, status, export, and plan artifacts +- a bounded TypeScript execution lane that is additive, not the default +- benchmark-backed public claims instead of broad "maybe supported" language -The `--output` path for `generate`/`build`/`test` must resolve to a directory inside your project root. Paths that escape the project root are rejected as a safety guardrail to prevent accidental deletion of files outside the project. +The repo's current public claim surface is intentionally narrow. That is a +feature, not a gap. The project prefers small honest boundaries over large +hand-wavy ones. -**Nextest:** `spec test` parses standard `cargo test` output format only. `cargo nextest` uses a different output format and is not supported. Running `spec test` in a project configured for nextest will produce `status: "unknown"` for all local tests. Use standard `cargo test`. +## Repo layout -For both `.unit.spec` and directory-scoped `.test.spec` validation, the path segment `molecule_tests` is reserved. Molecule tests generate `molecule_tests.rs` per namespace, so allowing that literal segment in an authored spec ID would create module/file collisions in generated output. +- `spec-core/`: parsing, validation, normalization, generation, proof, export primitives +- `spec-cli/`: the `spec` CLI +- `examples/ecommerce/`: the canonical single-library example +- `examples/service/`: the service benchmark example +- `examples/shared-spec/`, `examples/shared-crate/`, `examples/crosslib-app/`: direct sibling-library examples +- `docs/`: explanation docs, contract stacks, and deeper mechanism guides -## AI-Native Usage +## Where to read next -`spec` is especially useful when an AI agent is the one making the edit loop. The toolchain gives the agent a structured contract to follow, a machine-readable validation result to fix against, and a passport plus molecule-evidence trail that records what was actually observed to pass. In this repo, the canonical ecommerce example ships both unit passports and molecule evidence so `cargo run -p spec-cli -- status examples/ecommerce/units --format json` is the trustworthy fresh-clone starting point. Repo-root `status . --format json` remains useful when you want broad inventory, but it is intentionally `inventory_only`. +Read these in this order if you want the shortest path to fluency: -The loop is simple: inspect status, validate the exact unit, edit the `.unit.spec`, build to catch Rust-level issues, then test to write fresh evidence. Single-file `validate` and `test` stay on that unit and do not pull sibling molecule tests into the run. +1. [`docs/README.md`](docs/README.md) + Use this if you want the categorized docs map and the "current authority + versus historical draft" split. +2. [`docs/core_mechanisms_guide_v0.1.md`](docs/core_mechanisms_guide_v0.1.md) + Start here for the repo mental model and the main vocabulary boundaries. +3. [`examples/ecommerce/README.md`](examples/ecommerce/README.md) + Use this for the canonical end-to-end example and the concrete command loop. +4. [`AGENTS.md`](AGENTS.md) + Use this if you are editing `.unit.spec` or `.test.spec` files and want the exact workflow. +5. [`docs/rust_v1_contract_stack.md`](docs/rust_v1_contract_stack.md) + Use this if you need the benchmark-backed Rust V1 claim boundary and command-wall semantics. -```bash -spec validate examples/ecommerce/units --format json -``` - -```json -{ - "schema_version": 3, - "status": "invalid", - "errors": [ - { - "unit": "shipping/calculate", - "code": "SPEC_MISSING_DEP", - "dep": "currency/convert", - "path": "units/shipping/calculate.unit.spec" - } - ], - "warnings": [] -} -``` +Project-state docs: -That JSON form is meant for agents: parse `status`, `errors`, and `warnings` instead of scraping terminal prose. Pre-validation workspace-config failures, including broken `[libraries]` entries, also stay in this JSON contract for `validate --format json`. -`warnings` is a structured array in `schema_version: 3`, so machine consumers can key off stable warning codes instead of parsing display text. - -For molecule tests, `covers` declares the semantic exercised-unit set. Add explicit `.test.spec` `imports` when the Rust body needs names in scope. If `imports` is omitted, validation emits a deprecation warning and generation temporarily falls back to cover-derived implicit imports for backward compatibility. +- [`CHANGELOG.md`](CHANGELOG.md): shipped history +- [`DECISIONS.md`](DECISIONS.md): durable project decisions +- [`PLAN.md`](PLAN.md): active implementation roadmap +- [`TODOS.md`](TODOS.md): backlog and follow-up inventory -For plan artifacts, the machine-readable loop is: +## CLI surface ```bash -spec plan validate path/to/checkout-tax-refactor.plan.spec --format json +spec validate # validate .unit.spec files +spec status # project per-unit and molecule health +spec generate # generate Rust from .unit.spec files +spec build # validate, generate, and cargo build +spec test # validate, generate, cargo build, cargo test +spec export # export a machine-readable bundle +spec benchmark # write derived benchmark snapshots +spec plan # validate and export .plan.spec files ``` -Checked-in example: +From the repo root, the same commands are available through: ```bash -spec plan validate examples/ecommerce/plans/refactors/checkout-tax-refactor.plan.spec --format json +cargo run -p spec-cli -- ... ``` -Valid plan responses reuse the same top-level envelope and add `plan_id` plus derived `computed_impact`: - -```json -{ - "schema_version": 3, - "status": "valid", - "errors": [], - "warnings": [], - "plan_id": "checkout-tax-refactor", - "computed_impact": { - "status": "partial", - "units": ["pricing/apply_tax", "pricing/calculate_total"], - "molecule_tests": ["pricing/checkout_flow", "pricing/discount_plus_tax"], - "unresolved": [ - { - "unit": "pricing/tiered_rate", - "action": "add", - "reason": "current graph has no node for action=add" - } - ] - } -} -``` +## Working rules that matter -`spec plan export` emits a dedicated bundle with `{schema_version, spec_version, exported_at, plan, computed_impact, warnings}` and does not change the existing `spec export` bundle contract. +- Edit source specs, not generated Rust or observed proof artifacts. +- Treat `spec validate --format json` as the main machine-readable feedback channel. +- Use atom tests for one-unit behavior and molecule tests for cross-unit behavior. +- Do not assume semantic review understands every valid unit. Valid proof and semantic support are different things. +- Do not assume repo-root surfaces are the proof-authoritative default. Some broad scopes are inventory or diagnostic surfaces only. -`spec status` uses simple symbols so you can scan a whole tree quickly. Any unit or molecule test whose status is not `valid` exits with code `1`. +## Why this project exists -`spec status --format json` emits `schema_version: 3` and groups results by discovered library root: - -```json -{ - "schema_version": 3, - "roots": [ - { - "root": ".", - "units": [{ "id": "pricing/apply_tax", "status": "valid", "errors": [] }], - "molecule_tests": [{ "id": "pricing/checkout_flow", "status": "untested", "reason": "no molecule evidence", "errors": [] }] - } - ], - "units": [{ "id": "pricing/apply_tax", "status": "valid", "errors": [] }] -} -``` - -`roots[].units` and `roots[].molecule_tests` are the authoritative per-root planes. The top-level `units` array remains as a flattened compatibility view for existing consumers. Workspace-config failures that happen before any row can be computed surface as top-level `loader_errors` entries instead of raw stderr text, and zero discovered roots is a non-green status result. - -For marked seam units, `status` rows may also include additive `escape_hatch_gate` metadata. In M14 the required surfaces are always `["atom", "molecule"]`, and live `spec status` / `spec export` recompute that gate from current passport freshness and molecule evidence. An open gate can demote an otherwise-`valid` marked seam to `incomplete`, but a seam that is already `stale` remains `stale`. - -- `✓` valid -- `✗` invalid or failing -- `~` stale -- `?` incomplete (for example `unknown` evidence or an open marked-seam gate) -- `—` untested - -Use the companion skill at [`.claude/skills/spec/SKILL.md`](.claude/skills/spec/SKILL.md) when you want the full workflow spelled out for an AI coding session. - -## Validation error codes - -`spec validate --format json` returns error objects with a `code` field. These are the recognized codes returned by the current CLI: - -| Code | Description | -|------|-------------| -| `SPEC_IO` | Filesystem I/O error | -| `SPEC_INVALID_UTF8` | File is not valid UTF-8 | -| `SPEC_YAML_PARSE` | YAML syntax error in the unit file | -| `SPEC_JSON` | JSON serialization/deserialization error | -| `SPEC_SCHEMA_VALIDATION` | Unit file failed JSON Schema validation | -| `SPEC_SEMANTIC_VALIDATION` | Unit passed schema but failed a semantic rule | -| `SPEC_RUST_KEYWORD` | An `id` segment is a Rust reserved keyword | -| `SPEC_DUPLICATE_ID` | Two unit files share the same `id` | -| `SPEC_DEP_COLLISION` | A dep collides with another generated callable name, including another dep or the owning unit function | -| `SPEC_MISSING_DEP` | A declared dep has no matching unit in the spec set | -| `SPEC_UNKNOWN_LIBRARY_NAMESPACE` | A dep references a library alias that is not configured in `[libraries]` | -| `SPEC_CROSS_LIBRARY_DEP_NOT_FOUND` | A cross-library dep has no matching unit in the resolved library set | -| `SPEC_LIBRARY_CRATE_ALIAS_MISSING` | The root crate is missing the Cargo dependency alias required by a cross-library dep | -| `SPEC_LIBRARY_PATH_NOT_FOUND` | A `[libraries]` entry points to a path that does not exist | -| `SPEC_LIBRARY_OUT_OF_ROOT` | A `[libraries]` entry resolves outside the repo root | -| `SPEC_LIBRARY_ALIAS_SELF` | A `[libraries]` entry points back to the invoking library root | -| `SPEC_DUPLICATE_LIBRARY_ROOT` | Two `[libraries]` aliases resolve to the same canonical root | -| `SPEC_CYCLIC_DEP` | Units form a dependency cycle | -| `SPEC_CROSS_LIBRARY_CYCLE` | Units form a dependency cycle across library boundaries | -| `SPEC_USE_STATEMENT_IN_BODY` | `body.rust` contains a `use` statement — move it to `imports` or `deps` | -| `SPEC_BODY_RUST_MUST_BE_BLOCK` | `body.rust` failed to parse as a Rust block expression | -| `SPEC_BODY_RUST_LOOKS_LIKE_FN_DECLARATION` | `body.rust` contains the full `pub fn` signature — keep only the body block (see migration guide) | -| `SPEC_LOCAL_TEST_EXPECT_NOT_EXPR` | A `local_tests[].expect` value is not a valid Rust expression | -| `SPEC_DUPLICATE_LOCAL_TEST_ID` | Two local tests in the same unit share the same `id` | -| `SPEC_CONTRACT_TYPE_INVALID` | A `contract.inputs` or `contract.returns` type is not valid Rust | -| `SPEC_CONTRACT_INPUT_NAME_INVALID` | A `contract.inputs` key is not a valid Rust identifier | -| `SPEC_TRAVERSAL` | Error walking the units directory tree | -| `SPEC_GENERATOR` | Code generation failure | -| `SPEC_OUTPUT_DIR` | Output directory creation or safety check failed | -| `SPEC_MISSING_MARKER` | Output dir lacks the `.spec-generated` marker — refusing to clean | -| `SPEC_MOLECULE_CROSS_LIBRARY_COVERS_UNSUPPORTED` | A molecule test `covers` entry references another library, which remains unsupported in M9 | -| `SPEC_RESERVED_UNIT_NAME` | A slash-delimited spec `id` contains a reserved segment such as `molecule_tests` | -| `SPEC_PLAN_DIRECTORY_INPUT` | `spec plan validate/export` received a directory instead of a single `.plan.spec` file | -| `SPEC_PLAN_OUTSIDE_LIBRARY_ROOT` | The `.plan.spec` file does not live under any resolved library root | -| `SPEC_PLAN_SYMLINK_ESCAPE` | The `.plan.spec` path escapes the repo/library root through a symlink | -| `SPEC_PLAN_CROSS_LIBRARY_UNIT` | A plan authored a cross-library `changes[].unit` or acceptance unit ref, which M10 forbids | -| `SPEC_PLAN_DUPLICATE_CHANGE_UNIT` | The same `changes[].unit` appears more than once in one plan file | -| `SPEC_PLAN_UNIT_MISSING_FOR_ACTION` | `modify` or `remove` references a unit that does not exist in the current graph | -| `SPEC_PLAN_UNIT_ALREADY_EXISTS_FOR_ADD` | `add` references a unit that already exists in the current graph | -| `SPEC_PLAN_MOLECULE_TEST_NOT_FOUND` | A plan acceptance target references a molecule test id that does not exist in the current library | - -## Consuming Generated Code - -Generated units import internal deps with `use crate::...` paths. The consuming crate must -re-export the generated module tree from its root so those paths resolve consistently: - -```rust -mod generated; -pub use generated::*; -``` - -The ecommerce example uses this pattern in -[`examples/ecommerce/src/main.rs`](examples/ecommerce/src/main.rs). - -## Workspace Config - -An optional `spec.toml` at the repo root can relax `local_tests[].expect` validation for trusted workspaces and configure pipeline defaults: - -```toml -[validation] -allow_unsafe_local_test_expect = false - -[pipeline] -crate_root = "." # path to Cargo.toml containing your crate (default: auto-detected) -cargo_target_dir = "target" # cargo target dir (default: temp dir per run) -timeout_secs = 60 # abort cargo build/test if it exceeds this many seconds -``` +The project is trying to make one development style real: -When `allow_unsafe_local_test_expect = true`, `local_tests[].expect` still must parse as a Rust expression, but block, unsafe, closure, and other otherwise-rejected expression forms are allowed. +- author intent explicitly +- make the contract machine-readable +- generate readable implementation +- attach proof to what actually passed +- give humans and agents the same truthful surfaces to reason from -`spec build` and `spec test` auto-detect the nearest member crate (`[package]` Cargo.toml without `[workspace]`) to scope cargo to the right crate in a workspace. Override with `--crate-root ` or `[pipeline].crate_root` in `spec.toml`. +That is the whole game. diff --git a/TODOS.md b/TODOS.md index 2c5553e..560a2b6 100644 --- a/TODOS.md +++ b/TODOS.md @@ -1,5 +1,9 @@ ## TODOs for M1 (Release 0.1) +## M101 backlog + +- [ ] **Land the category truth contract implementation wedge** — The design anchor is [`docs/category_truth_contract_v0.1.md`](./docs/category_truth_contract_v0.1.md). The failure class is broader than one benchmark bug: read-side consumers must not infer supported category status or positive benchmark credit from partial truth. First adoption scope should cover `sum.discount_strategy.v1`, `data.pricing_quote.v1`, `unsupported.sum.v1`, and `unsupported.data.v1`, with benchmark accounting, `spec status`, `spec export`, and readability/snapshot surfaces consuming the same explicit contract. + ### Required for Release - [x] **Set up GitHub Actions for automated builds and releases** — Completed v0.2.0 (2026-04-02) - Build matrix: linux/darwin x amd64/arm64 using `cross` crate @@ -211,3 +215,9 @@ - [x] **M68: Rust V1 benchmark mechanics landing** — Completed main (2026-05-18). The repo now ships `benchmarks/labels.json`, a shared benchmark projection core, additive schema-version-4 `benchmarks[]` in `spec status` / `spec export`, `spec benchmark snapshot `, seeded readability review anchoring for `BENCH-ECOM`, reserved `BENCH-SERVICE` visibility, committed snapshot artifacts, and repo-facing CLI fixtures/docs for the full-versus-partial benchmark contract. - [x] **M69: supported-core closure after mechanics-only landing** — Completed main (2026-05-21). `BENCH-SERVICE` is now an active single-library benchmark rooted at `examples/service/units`, ships the frozen six-unit / three-molecule roster with fresh proof, keeps benchmark accounting read-only over specs/passports/evidence, and still does not widen the Rust V1 support vocabulary beyond the existing supported-core closure. + +## Post-I7 scope closure + +- [x] **I7: Rust V1 scope-decision closure** — Completed main (2026-05-22). The repo now freezes one explicit pre-I8 story: bounded generics defer to `V1.1`, Rust V1 stays synchronous-only so async/IO also defer to `V1.1`, `BENCH-CROSSLIB` remains the companion negative-proof wall, and the final I8 wall stays the existing five-command proof run. + +- [x] **I8: Rust V1 final proof run** — Completed main (2026-05-23). The frozen five-command wall still holds on the live branch: `BENCH-ECOM` and `BENCH-SERVICE` both reran as passing positive proof walls with current readability state, repo-root `status . --format json` remained the expected `inventory_only` non-green inventory surface with exit code `1`, and the bounded Rust V1 claim stayed narrow without widening scope. diff --git a/benchmarks/snapshots/BENCH-ECOM.snapshot.json b/benchmarks/snapshots/BENCH-ECOM.snapshot.json index ed6b8c5..a385bcd 100644 --- a/benchmarks/snapshots/BENCH-ECOM.snapshot.json +++ b/benchmarks/snapshots/BENCH-ECOM.snapshot.json @@ -1,5 +1,5 @@ { - "generated_at": "2026-05-20T22:06:56Z", + "generated_at": "2026-05-25T19:41:28Z", "projection": { "benchmark_id": "BENCH-ECOM", "kind": "positive", @@ -7,15 +7,15 @@ "required_for_v1": true, "path_scope": "full", "accounting_status": "valid", - "benchmark_status": "passing", - "gate_status": "satisfied", - "readability_review_status": "current", + "benchmark_status": "incomplete", + "gate_status": "open", + "readability_review_status": "stale", "readability_verdict": { "decision": "approved", "summary": "Generated benchmark surfaces remain readable and structurally traceable for the seeded BENCH-ECOM closure." }, "label_digest": "sha256:b3cb83c0fc1d42b0c2d469db332870227e1f4819e7274636cbef9a9d2dc9cee8", - "projection_digest": "sha256:07abdc7583aeb203ad6063fafc77bac72845674e5736acdda8fd6a8424f4e00e", + "projection_digest": "sha256:2715e254148589baa08e0d1454b9e5b770b157a554b3bf42e94119ac88834f53", "summary": { "total_cases": 7, "supported_cases": 7, @@ -138,6 +138,13 @@ "classification": "supported", "status": "valid", "semantic_support_status": "supported", + "category_qualification": { + "category_id": "sum.discount_strategy.v1", + "descriptor_id": "discount_strategy.ecommerce.v1", + "claim_status": "supported_qualified", + "positive_credit_eligibility": "eligible", + "reason_code": "qualified" + }, "proof_refs": { "passport": "examples/ecommerce/units/pricing/discount_strategy.spec.passport.json", "covering_molecule_evidence": [ @@ -153,6 +160,13 @@ "classification": "supported", "status": "valid", "semantic_support_status": "supported", + "category_qualification": { + "category_id": "data.pricing_quote.v1", + "descriptor_id": "pricing_quote.ecommerce.v1", + "claim_status": "supported_qualified", + "positive_credit_eligibility": "eligible", + "reason_code": "qualified" + }, "proof_refs": { "passport": "examples/ecommerce/units/pricing/pricing_quote.spec.passport.json", "covering_molecule_evidence": [ diff --git a/benchmarks/snapshots/BENCH-SERVICE.snapshot.json b/benchmarks/snapshots/BENCH-SERVICE.snapshot.json index e841f0d..826b14d 100644 --- a/benchmarks/snapshots/BENCH-SERVICE.snapshot.json +++ b/benchmarks/snapshots/BENCH-SERVICE.snapshot.json @@ -1,26 +1,26 @@ { - "generated_at": "2026-05-21T17:12:56Z", + "generated_at": "2026-05-25T19:41:28Z", "projection": { "benchmark_id": "BENCH-SERVICE", "kind": "positive", "lifecycle": "active", "required_for_v1": true, "path_scope": "full", - "accounting_status": "valid", - "benchmark_status": "passing", - "gate_status": "satisfied", - "readability_review_status": "current", + "accounting_status": "invalid", + "benchmark_status": "invalid", + "gate_status": "open", + "readability_review_status": "stale", "readability_verdict": { "decision": "approved", "summary": "Generated service benchmark surfaces remain readable and structurally traceable for the activated service closure." }, "label_digest": "sha256:69eb48437324c944450825de3db57784b0b9e80496d0f9005ac7b595f066e9cc", - "projection_digest": "sha256:23b445f912ce8bc23a2430495cf57a8b8f866fe1be56ff2976667e26e742edda", + "projection_digest": "sha256:c3b32ffaa471d0d08e32b8e821f527f84b78ec6b4627e5f4011bb07551095705", "summary": { "total_cases": 6, "supported_cases": 6, "supported_valid_cases": 6, - "positive_credit_cases": 6, + "positive_credit_cases": 0, "case_status_counts": { "invalid": 0, "failing": 0, @@ -70,7 +70,7 @@ "examples/service/units/billing/checkout_success_flow.test.evidence.json" ] }, - "counts_as_supported_positive": true + "counts_as_supported_positive": false }, { "case_id": "billing/apply_regional_fee", @@ -86,7 +86,7 @@ "examples/service/units/billing/discount_strategy_quote_flow.test.evidence.json" ] }, - "counts_as_supported_positive": true + "counts_as_supported_positive": false }, { "case_id": "billing/checkout_net_total", @@ -101,7 +101,7 @@ "examples/service/units/billing/checkout_success_flow.test.evidence.json" ] }, - "counts_as_supported_positive": true + "counts_as_supported_positive": false }, { "case_id": "billing/checkout_net_total_guarded_fee", @@ -116,7 +116,7 @@ "examples/service/units/billing/checkout_declined_discount_flow.test.evidence.json" ] }, - "counts_as_supported_positive": true + "counts_as_supported_positive": false }, { "case_id": "billing/discount_strategy", @@ -131,7 +131,7 @@ "examples/service/units/billing/discount_strategy_quote_flow.test.evidence.json" ] }, - "counts_as_supported_positive": true + "counts_as_supported_positive": false }, { "case_id": "billing/pricing_quote", @@ -147,7 +147,7 @@ "examples/service/units/billing/discount_strategy_quote_flow.test.evidence.json" ] }, - "counts_as_supported_positive": true + "counts_as_supported_positive": false } ], "readability_generated_files": [ diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..98b8b02 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,92 @@ +# Documentation Map + +This repo has two different kinds of docs: + +- current authority docs that describe what `spec` does today +- versioned design drafts that preserve historical planning context + +If two docs disagree, trust the current authority docs first: + +1. [`README.md`](../README.md) +2. [`core_mechanisms_guide_v0.1.md`](./core_mechanisms_guide_v0.1.md) +3. [`CHANGELOG.md`](../CHANGELOG.md) +4. [`PLAN.md`](../PLAN.md) +5. [`DECISIONS.md`](../DECISIONS.md) + +That ordering keeps the repo's newcomer story, shipped behavior, active plan, +and durable decisions aligned. + +## Start Here + +- [`README.md`](../README.md) + Repo entry point. What `spec` is, the first command loop, and the day-1 vocabulary. +- [`core_mechanisms_guide_v0.1.md`](./core_mechanisms_guide_v0.1.md) + The mental model doc. Use this when you need to separate authored truth, proof, semantic review, families, and benchmarks. +- [`kind_coverage_map_v0.1.md`](./kind_coverage_map_v0.1.md) + The progress-map doc. Use this when you need to know which kinds and per-kind categories are shipped, deferred, or still unnamed. +- [`function_category_matrix_v0.1.md`](./function_category_matrix_v0.1.md) + The detailed `kind:function` matrix. Use this when you need per-category truth for families, TypeScript, proof, and benchmark role. +- [`data_category_taxonomy_v0.1.md`](./data_category_taxonomy_v0.1.md) + The detailed `kind:data` taxonomy. Use this when you need the exact supported data descriptor, its benchmark role, and the remaining unnamed pressure inside seam categories. +- [`sum_category_taxonomy_v0.1.md`](./sum_category_taxonomy_v0.1.md) + The detailed `kind:sum` taxonomy. Use this when you need the exact supported sum descriptor, its benchmark role, and the current split between canonical detector wording and the broader service sibling surface. +- [`category_truth_contract_v0.1.md`](./category_truth_contract_v0.1.md) + The cross-cutting honesty contract for category-backed read-side claims. Use this when you need to know how benchmark, status, export, and future consumers should decide support and positive-credit eligibility without inferring from partial truth. +- [`category_truth_contract_correction_v0.1.md`](./category_truth_contract_correction_v0.1.md) + The corrective note for M101. Use this when you need the explicit decision that category qualification must consume stored semantic truth rather than minting fresh read-side category truth. +- [`examples/ecommerce/README.md`](../examples/ecommerce/README.md) + The canonical concrete walkthrough. Use it when you want one real example root to run and inspect. +- [`AGENTS.md`](../AGENTS.md) + Exact workflow rules for editing `.unit.spec`, `.test.spec`, and `.plan.spec` files. + +## Current Authority + +- [`CHANGELOG.md`](../CHANGELOG.md) + Shipped changes and repo-facing behavior changes. +- [`PLAN.md`](../PLAN.md) + Active implementation plan and current milestone authority. +- [`DECISIONS.md`](../DECISIONS.md) + Durable decisions that should not be re-litigated casually. +- [`ORCH_PLAN.md`](../ORCH_PLAN.md) + Current orchestration runbook for the active plan. +- [`rust_v1_contract_stack.md`](./rust_v1_contract_stack.md) + The Rust V1 command-wall and benchmark-claim index. + +## Vision And Architecture + +These docs are still useful, but they are design context, not the day-to-day +source of shipped CLI truth. + +- [`north_star_v0.2.md`](./north_star_v0.2.md) + Long-term product vision. Read this for where the system is trying to go. +- [`high_level_technical_architecture_v0.2.md`](./high_level_technical_architecture_v0.2.md) + High-level system design. Good for boundaries and component thinking. +- [`roadmap_and_release_shape_v0.1.md`](./roadmap_and_release_shape_v0.1.md) + Early sequencing logic. Useful for why the repo grew in this order. +- [`diagrams.md`](../diagrams.md) + Visual map of the write path, read surfaces, family-analysis lane, and planning boundary. + +## Semantic Family And Recommendation Docs + +- [`semantic-families/README.md`](../semantic-families/README.md) + Packet rules, promotion workflow boundaries, and corpus-analysis context. +- [`semantic_family_capability_corpus_guide_v0.1.md`](./semantic_family_capability_corpus_guide_v0.1.md) + Deeper explanation of family capability, coverage, and recommendation surfaces. +- [`recommendation_corpus_expansion_program_v0.1.md`](./recommendation_corpus_expansion_program_v0.1.md) + Corpus growth and recommendation-pressure design context. +- [`ai_promotion_and_multilanguage_milestones_v0.1.md`](./ai_promotion_and_multilanguage_milestones_v0.1.md) + Milestone framing for family promotion and second-language work. + +## Historical Design Packets + +These are useful when you are reconstructing why a milestone exists or how a +specific bounded wedge was scoped. They are not the right starting point for a +new reader. + +- `docs/m26_*` +- `docs/m27_5_*` +- `docs/m52_*` + +Some of these older design packets preserve machine-local path references from +the original authoring session. Read them as historical design records unless +they are re-grounded by current authority docs above. diff --git a/docs/category_truth_contract_correction_v0.1.md b/docs/category_truth_contract_correction_v0.1.md new file mode 100644 index 0000000..fef1d31 --- /dev/null +++ b/docs/category_truth_contract_correction_v0.1.md @@ -0,0 +1,180 @@ +# spec — Category Truth Contract Correction +**Version:** v0.1 +**Status:** Corrective design note +**Date:** 2026-05-25 + +## Purpose + +This note records the contract correction discovered while reviewing +`feat/m101-category-truth-registry`. + +The category-truth milestone direction is still right: + +- shared registry-owned category truth +- one shared qualification contract +- benchmark, status, export, and snapshot consumers using the same rules + +But the current M101 implementation shape is not safe to land as-is. + +It tightened benchmark honesty, then crossed the producer/consumer boundary by +letting read-side surfaces recompute fresh seam category truth. + +This note makes the correction explicit so the repo can keep the good parts of +M101 without silently changing the semantic-truth contract. + +## Decision + +The repo should keep the **stored-truth strict** model. + +That means: + +1. `semantic_review` remains producer-owned truth. +2. Only `spec test` may refresh `semantic_review`. +3. `category_qualification` is consumer-side interpretation of already-stored + semantic truth. +4. `spec status`, `spec export`, benchmark projection, snapshot projection, and + readability projection are consumers, not parallel truth producers. + +The repo should **not** introduce a live read-side semantic/category truth plane +inside M101. + +If the product ever needs live analysis distinct from passported proof truth, +that must be a later, explicitly named architecture move with its own surface +and contract. + +## What Went Wrong In M101 + +The design in [`category_truth_contract_v0.1.md`](./category_truth_contract_v0.1.md) +correctly identified the failure class: + +> consumers must not infer support or positive credit from partial truth + +But the first implementation appears to have solved that partly by letting +`status` and `export` derive category-bearing truth from refreshed semantic +projection on read-only paths. + +That creates a new failure class: + +> a read-side consumer becomes a second semantic/category truth producer + +This is the wrong trade. + +It makes the system look more honest at the benchmark layer while quietly +weakening the repo's deeper truth boundary. + +## Why The Stored-Truth Model Must Win + +The repo already has a durable rule: + +- passports store semantic review truth from the last proof refresh +- only `spec test` refreshes semantic review truth +- `spec build`, `spec generate`, `spec status`, and `spec export` project + stored truth + +That boundary is valuable because it keeps one answer to: + +- where semantic truth comes from +- when it changed +- what evidence anchored it + +If M101 lets `status` or `export` recompute fresh category-bearing truth, the +repo no longer has one answer. It has two: + +1. proof-time semantic truth +2. read-time semantic/category truth + +That is precisely the kind of split that created earlier honesty bugs. + +## Correction To The Contract Doc + +The contract in [`category_truth_contract_v0.1.md`](./category_truth_contract_v0.1.md) +should be read more narrowly than the first implementation did. + +The intended rule must be: + +- `category_qualification` may be read-side projected +- but only from preserved/passported `semantic_review` +- not from `SemanticProjectionMode::Refresh` on `status` / `export` +- not from missing-passport synthetic review + +In other words: + +**read-side projected** does not mean **freshly recomputed semantic truth**. + +It means: + +**shared consumer interpretation of already-produced truth**. + +## Required Implementation Correction + +The corrective respin should preserve these M101 ideas: + +- the registry substrate in `spec-core` +- stable category qualification enums and reason codes +- benchmark positive-credit gating through qualification +- explicit service-sibling non-qualification in the first wedge + +The corrective respin should drop or rewrite these parts: + +- any `status` path where refreshed semantic projection feeds + `category_qualification` +- any `export` path where refreshed semantic projection feeds + `category_qualification` +- any path where a missing passport can still yield + `supported_qualified` +- any benchmark/snapshot input path that consumes refreshed seam semantic truth + instead of preserved truth + +## Concrete Respin Plan + +Start from the safer pre-M101 line on `feat/i8-final-proof-run` and rebuild the +milestone narrowly. + +### Phase 1 + +Keep the benchmark honesty fix and the shared category-truth substrate. + +### Phase 2 + +Apply qualification at benchmark projection only from preserved semantic truth. + +### Phase 3 + +Add additive `category_qualification` to `status` and `export`, but derive it +only from preserved/passported semantic truth. + +### Phase 4 + +Re-freeze snapshot and readability parity using the same preserved-truth input. + +## Acceptance Criteria + +The corrected M101 is done only when all of these are true: + +1. A unit with no passport does **not** surface `supported_qualified`. +2. `spec status` never emits fresher category truth than the stored passport. +3. `spec export` never emits fresher category truth than the stored passport. +4. Benchmark, snapshot, readability, status, and export all consume the same + preserved semantic truth when deriving qualification. +5. Service seam siblings remain visible but unqualified until producer truth is + widened explicitly. + +## Not Doing + +- introducing a new live analysis truth plane inside this milestone +- mutating passport on-disk schema in the correction wedge +- widening seam semantic support +- solving the broader seam-substrate alias/sibling question in the same patch + +## Follow-On If Needed Later + +If the repo later decides it truly needs live, recomputed semantic/category +analysis on read-side surfaces, that work should ship as a separate design +packet with: + +- a new named output surface +- explicit non-proof status +- a clear distinction from passported semantic truth +- rules for how consumers choose between proof truth and analysis truth + +That is a future architecture decision, not part of the M101 correction. diff --git a/docs/category_truth_contract_v0.1.md b/docs/category_truth_contract_v0.1.md new file mode 100644 index 0000000..bc4926c --- /dev/null +++ b/docs/category_truth_contract_v0.1.md @@ -0,0 +1,855 @@ +# spec — Category Truth Registry and Consumer Qualification Contract +**Version:** v0.1 +**Status:** Implementation-ready design +**Date:** 2026-05-25 + +## Purpose + +This design closes the cross-consumer honesty gap exposed by commit `47dcd1f` +("Require explicit support for benchmark positive credit"). + +That fix proved the repo had a real bug class: + +> a read-side consumer can over-credit support, category ownership, or positive +> benchmark value from partial truth instead of one explicit contract + +This document turns the earlier contract memo into an implementation-ready +design for the next wedge: + +- a machine-readable category truth registry +- one shared consumer qualification contract +- first adoption in benchmark accounting, `spec status`, `spec export`, and + readability/snapshot surfaces + +This doc does not reopen `PLAN.md` or `ORCH_PLAN.md`. It narrows one bounded +artifact into an executable implementation shape. + +## Decision Summary + +The first wedge will ship two new shared primitives inside `spec-core`: + +1. `CategoryTruthRegistry` + A typed, serializable registry that declares repo-owned category truth for + claim-bearing seams. + +2. `qualify_category_claim(...)` + One shared function that combines a registry row with projected semantic + review truth and consumer context to decide: + - whether a consumer may make a supported-category claim + - whether a consumer may make an unsupported-category claim + - whether a consumer may award positive benchmark credit + - why not, when qualification fails + +The authoritative source for this first wedge lives in Rust code under +`spec-core`, not in a freestanding JSON file. That keeps the first adoption +small, compile-time checked, and shared by all current Rust consumers. + +The registry must still be machine-readable: + +- rows use serializable structs and stable enums +- tests and read-side artifacts may render the registry or qualification output + as JSON +- a file-based external registry can come later if non-Rust consumers need a + direct checked-in data artifact + +This wedge does **not** let benchmark labels, stored passports, or other +consumer-local metadata widen producer-owned support truth. A case may still be +labeled `classification: supported` at the benchmark layer and yet remain +category-unqualified when producer truth does not support that claim. + +## Why This Is The Right Scope + +The producer boundary already exists: + +- `spec-core/src/semantic_review.rs` owns category routing and effective support + status + +The consumer drift risk also already exists: + +- `spec-core/src/benchmark.rs` decides positive credit from projected support + truth +- `spec-cli/src/commands.rs` projects benchmark/status/export surfaces from + passport truth +- `spec-core/src/export.rs` preserves read-side semantic review truth for + downstream consumers +- snapshot and readability surfaces reuse benchmark projections and can inherit + the same optimism bug if qualification rules drift + +So the next honest move is not another one-off fix. It is to centralize the +claim contract at the producer/consumer seam. + +## Non-Goals + +This wedge does not: + +- change semantic-review routing semantics for function families +- promote new categories +- widen seam support beyond the currently shipped four-category first scope +- add a new public file format for external tools +- rewrite `BENCH-SERVICE` labels to align with producer truth in the same wedge +- resolve the broader seam-substrate cleanup by itself + +The broader seam-substrate ambiguity stays visible. This wedge makes consumers +honest about it. + +## Failure Class + +The failure class is any path where a consumer infers support or positive value +without explicit qualification. + +Examples: + +- awarding positive benchmark credit because a case is labeled `supported` and + the unit is otherwise valid, even if semantic support is absent or + unsupported +- treating `support_status`, `compatibility_key`, and sibling handling as + consumer-local interpretation instead of shared repo truth +- letting `status`, `export`, benchmark projection, and snapshot/readability + surfaces disagree about whether a category is support-bearing or + observation-only + +## Current Producer And Consumer Surfaces + +### Producer + +`spec-core/src/semantic_review.rs` + +- owns supported seam compatibility keys: + - `sum.discount_strategy.v1` + - `data.pricing_quote.v1` +- owns terminal unsupported seam routing: + - `unsupported.sum.v1` + - `unsupported.data.v1` +- owns `effective_support_status()` +- already preserves the distinction between supported surfaces and unsupported + observation surfaces + +### First consumers + +1. `spec-core/src/benchmark.rs` + Current risk: positive credit and support-bearing benchmark interpretation. + +2. `spec-cli/src/commands.rs` status projection + Current risk: read-side category interpretation from projected semantic truth + without one shared qualification vocabulary. + +3. `spec-core/src/export.rs` and `spec-cli/src/commands.rs` export projection + Current risk: downstream readers can inherit raw semantic truth but still + invent category meaning locally. + +4. benchmark readability and snapshot surfaces + Current risk: they echo benchmark conclusions and can preserve optimism bugs + if qualification is not shared. + +## Design Principles + +1. No inference by default. + Missing contract truth never upgrades to supported or positive-credit truth. + +2. Producer owns category routing. + Consumers do not reinterpret compatibility keys, evaluator scopes, or + sibling boundaries. + +3. Qualification is additive and explicit. + Consumers receive a qualified answer plus a failure reason, not a suggestion + to infer. + +4. Unsupported categories stay visible. + Observation-only categories remain readable, but they never count as + supported or positive-credit truth. + +5. Transitional ambiguity must be named. + The current benchmark-label-versus-producer mismatch is preserved as + explicit contract truth, not hidden behind vague wording. + +6. Producer truth outranks benchmark labels. + Benchmark `classification` is consumer input, not category authority. It may + request a supported claim, but only qualification may grant one. + +## Core Design + +### 1. Category Truth Registry + +Add a new typed registry module in `spec-core`, for example: + +- `spec-core/src/category_truth.rs` + +It will expose serializable types roughly shaped like this: + +```rust +pub struct CategoryTruthRegistry { + pub schema_version: u8, + pub categories: &'static [CategoryTruthRow], +} + +pub struct CategoryTruthRow { + pub category_id: &'static str, + pub kind: CategoryKind, + pub contract_support_status: ContractSupportStatus, + pub producer_surface: ProducerSurface, + pub alias_sibling_policy: AliasSiblingPolicy, + pub descriptor_set: DescriptorSet, + pub positive_credit_policy: PositiveCreditPolicy, + pub consumer_requirements: ConsumerRequirements, + pub notes: &'static str, +} +``` + +This is machine-readable repo truth, not just documentation. + +### 2. Consumer Qualification Contract + +Add one shared qualification function in `spec-core`, for example: + +```rust +pub fn qualify_category_claim( + consumer: ConsumerKind, + semantic_review: Option<&SemanticReview>, + consumer_context: ConsumerQualificationContext, +) -> CategoryQualification +``` + +The function must: + +1. resolve the registry row from `semantic_review.compatibility_key` +2. reject qualification when semantic review is missing +3. reject qualification when the registry row is missing +4. resolve the producer-owned `descriptor_id` from the same semantic projection + truth that produced the compatibility key +5. require that `descriptor_id` to match either: + - `descriptor_set.canonical_descriptor.descriptor_id` + - or one member of `descriptor_set.approved_siblings[]` +6. require the review's `effective_support_status()` to match the registry row's + `contract_support_status` +7. apply consumer-specific rules for positive credit or read-side claim display +8. return one stable result object + +Suggested result shape: + +```rust +pub struct CategoryQualification { + pub category_id: Option, + pub descriptor_id: Option, + pub claim_status: ClaimStatus, + pub positive_credit_eligibility: PositiveCreditEligibility, + pub reason_code: QualificationReasonCode, +} +``` + +This object should be the shared serializable projection shape reused across +benchmark cases, `spec status`, and `spec export`. The wedge should not invent +slightly different per-surface field bundles for the same concept. + +With stable enums: + +- `ClaimStatus` + - `supported_qualified` + - `unsupported_qualified` + - `unqualified` +- `PositiveCreditEligibility` + - `eligible` + - `ineligible` +- `QualificationReasonCode` + - `semantic_review_missing` + - `registry_row_missing` + - `descriptor_id_missing` + - `descriptor_not_approved` + - `support_status_mismatch` + - `consumer_requirement_failed` + - `positive_credit_disallowed` + - `qualified` + +The key rule is simple: + +> if qualification does not return an explicit qualified result, the consumer +> must not invent one + +### Producer-owned descriptor identity + +`descriptor_set` is an active contract boundary, not metadata. + +So the first wedge must carry one extra producer-owned identity at +qualification time: + +- `descriptor_id` + +This is a stable per-unit descriptor identity derived from the same producer +truth that already yields the compatibility key. Consumers do not invent it. + +First-wedge source of truth: + +- seam producer logic in `spec-core/src/semantic_review.rs` (or a minimal + helper extracted from it) must emit both: + - `compatibility_key` + - `descriptor_id` + +Bounded implementation rule: + +- do **not** redesign the whole semantic review schema +- do add a producer-owned way for the qualification path to obtain + `descriptor_id` from the semantic projection result or a tightly-coupled + helper in `spec-core` + +Acceptable first-wedge implementation shapes: + +1. extend projected semantic truth to carry `descriptor_id` +2. derive `descriptor_id` in `qualify_category_claim(...)` by calling one + producer-owned helper on the same unit/spec context that produced semantic + review + +The required invariant is the same either way: + +> `descriptor_id` must come from producer-owned truth in `spec-core`, not from +> benchmark labels, export readers, status formatting, or other consumer-local +> interpretation + +## Registry Schema + +The first wedge uses this exact vocabulary. + +### `kind` + +- `sum` +- `data` + +### `contract_support_status` + +- `supported` +- `unsupported` + +This is static category capability truth from the registry row, not a per-unit +runtime health signal. + +### `producer_surface` + +```json +{ + "owner": "semantic_review", + "compatibility_key": "sum.discount_strategy.v1", + "evaluator_scope": "supported_sum_surface" +} +``` + +First wedge rule: + +- all first-scope rows are owned by `semantic_review` +- consumers must key by `compatibility_key` +- consumers may not infer category support from `evaluator_scope` alone + +### `alias_sibling_policy` + +This field freezes the repo vocabulary for sibling and alias handling. + +Allowed values in this wedge: + +- `canonical_only` + The category means exactly the canonical producer-owned descriptor. + +- `approved_sibling_extension` + The repo intentionally ships more than one sibling descriptor under the same + category. Consumers may honor that category only because the registry row + says so. They may not widen it further. + +- `unsupported_terminal` + The category is an observation-only unsupported sink. It is never + support-bearing or positive-credit eligible. + +In the first implementation-ready wedge: + +- supported rows start as `canonical_only` +- unsupported seam rows are `unsupported_terminal` +- `approved_sibling_extension` remains reserved for a later producer-backed + widening, not for this first landing + +`alias_sibling_policy` is only the coarse classification. It is not enough on +its own for machine use. Every supported row that is not `canonical_only` must +also carry an explicit `descriptor_set` so consumers and tests can read the +allowed sibling boundary without relying on prose notes. + +### `descriptor_set` + +This field is the machine-readable sibling boundary. + +Suggested shape: + +```json +{ + "canonical_descriptor": { + "descriptor_id": "pricing_quote.ecommerce.v1", + "representative_unit_id": "pricing/pricing_quote" + }, + "approved_siblings": [] +} +``` + +First wedge rules: + +- `canonical_only` rows must still declare `canonical_descriptor` +- `approved_sibling_extension` rows must declare both + `canonical_descriptor` and every approved sibling entry +- `unsupported_terminal` rows use an empty descriptor set +- qualification must fail unless the producer-owned `descriptor_id` matches the + canonical descriptor or one approved sibling entry +- no consumer may widen beyond `descriptor_set.approved_siblings[]` +- prose notes may explain why a sibling exists, but the sibling boundary itself + must be derivable from the registry alone + +### `positive_credit_policy` + +Suggested shape: + +```json +{ + "eligible": true, + "requires_supported_case_classification": true, + "requires_full_scope_projection": true, + "requires_valid_benchmark_accounting": true +} +``` + +For unsupported categories: + +```json +{ + "eligible": false +} +``` + +### `consumer_requirements` + +First wedge requirements are explicit and shared: + +```json +{ + "require_semantic_review": true, + "require_matching_compatibility_key": true, + "allow_health_only_inference": false, + "allow_label_only_inference": false +} +``` + +This is the contract that closes the recent bug class. + +## First Registry Rows + +The first adoption scope is intentionally small. + +| Category | Kind | Contract support | Alias/sibling policy | Approved sibling ids | Positive credit | +| --- | --- | --- | --- | --- | --- | +| `sum.discount_strategy.v1` | `sum` | `supported` | `canonical_only` | none | eligible | +| `data.pricing_quote.v1` | `data` | `supported` | `canonical_only` | none | eligible | +| `unsupported.sum.v1` | `sum` | `unsupported` | `unsupported_terminal` | none | ineligible | +| `unsupported.data.v1` | `data` | `unsupported` | `unsupported_terminal` | none | ineligible | + +## First-Scope Policy Notes + +### `sum.discount_strategy.v1` + +Current repo truth says: + +- the canonical semantic-review wording is still narrow around + `none` / `percentage` / `fixed_amount` +- the service seam sibling (`declined` / `percentage` / `fixed_credit`) is + still routed by producer truth to `unsupported.sum.v1` +- `BENCH-SERVICE` still labels `billing/discount_strategy` as + `classification: supported`, but current benchmark fixtures already deny it + positive credit because semantic-review truth is unsupported + +So the first registry row must record: + +- `contract_support_status = supported` +- `alias_sibling_policy = canonical_only` +- `descriptor_set.canonical_descriptor.descriptor_id = + discount_strategy.ecommerce.v1` +- `descriptor_set.approved_siblings = []` +- `notes = canonical ecommerce descriptor only; service sibling remains visible + but unqualified until producer truth is widened explicitly` + +This is intentional. The first wedge is about honest read-side qualification, +not about retroactively upgrading an unsupported producer surface into +supported category truth. + +### `data.pricing_quote.v1` + +Current repo truth says: + +- the canonical semantic-review detector is still narrow around + `subtotal` / `discount_rate` / `tax_rate` +- the service seam uses + `subtotal` / `membership_rate` / `regional_rate` +- producer truth still routes that service seam to `unsupported.data.v1` +- `BENCH-SERVICE` still labels `billing/pricing_quote` as + `classification: supported`, but current benchmark fixtures already deny it + positive credit because semantic-review truth is unsupported + +So the first registry row must record: + +- `contract_support_status = supported` +- `alias_sibling_policy = canonical_only` +- `descriptor_set.canonical_descriptor.descriptor_id = + pricing_quote.ecommerce.v1` +- `descriptor_set.approved_siblings = []` +- `notes = canonical ecommerce descriptor only; service sibling remains visible + but unqualified until producer truth is widened explicitly` + +This keeps the authority chain coherent: + +- semantic review stays the producer-owned router +- the registry qualifies only the categories producer truth actually projected +- benchmark labels stay visible but cannot overrule unsupported producer truth + +### `unsupported.sum.v1` and `unsupported.data.v1` + +These rows exist so unsupported seam truth is explicit and shared. + +They must stay: + +- visible +- additive +- qualification-bearing as unsupported only +- never positive-credit eligible + +### Current service benchmark mismatch + +The repo currently has a real split that this wedge must make explicit instead +of papering over: + +- `benchmarks/labels.json` marks `billing/discount_strategy` and + `billing/pricing_quote` as `classification: supported` +- producer-owned semantic review still projects those seam units as + `unsupported.sum.v1` and `unsupported.data.v1` +- frozen `BENCH-SERVICE` status/export fixtures therefore already show + `counts_as_supported_positive = false` for both carriers + +The first wedge must preserve that checked-in truth honestly: + +- the benchmark label remains visible +- the unit remains visible +- the category claim becomes explicitly `unqualified` or + `unsupported_qualified`, depending on surface +- no new registry row rescues the claim into supported truth + +## Consumer Behavior Contract + +### Benchmark accounting + +Target surface: + +- `spec-core/src/benchmark.rs` + +Current positive-credit logic already checks: + +- benchmark kind +- lifecycle +- path scope +- accounting validity +- case classification +- projected semantic support status + +The new rule is stricter: + +- `classification: supported` is necessary input for positive benchmark credit, + but it is never sufficient authority on its own +- `counts_as_supported_positive` must require + `CategoryQualification { claim_status: supported_qualified, positive_credit_eligibility: eligible, ... }` +- that qualified result must already have passed descriptor approval against the + registry row's `descriptor_set` + +Additive projection should carry the shared qualification object per case, for +example as: + +```json +{ + "category_qualification": { + "category_id": "sum.discount_strategy.v1", + "descriptor_id": "discount_strategy.ecommerce.v1", + "claim_status": "supported_qualified", + "positive_credit_eligibility": "eligible", + "reason_code": "qualified" + } +} +``` + +For the current service seam mismatch, the same projection stays visible but +reports an unqualified or unsupported result instead of silently relying on the +benchmark label. + +If qualification fails: + +- positive credit becomes `false` +- the case stays visible +- the failure reason is preserved in qualification output +- if the benchmark projection is `full`, `accounting_status` becomes `invalid` +- if the benchmark projection is `partial`, `accounting_status` becomes + `partial_invalid` +- `benchmark_status` follows the existing checked-in benchmark semantics: + because `determine_benchmark_status(...)` treats `accounting_status = invalid` + as terminal, the full projection becomes `benchmark_status = invalid` +- `gate_status` remains `open` +- `readability_review_status` does **not** get rewritten by qualification + failure; it stays whatever the readability artifact truth already says + (`current`, `stale`, `missing`, or `not_applicable`) +- partial projections keep the current checked-in shape: they surface the + `partial_invalid` accounting result, but do not invent full-scope + `benchmark_status`, `gate_status`, or `readability_review_status` fields + +This keeps honesty stronger than zero-credit-only silence while staying aligned +to the current benchmark status model. The wedge should not overload +`readability_review_status` into a second benchmark-accounting signal. + +### `spec status` + +Target surface: + +- `spec-cli/src/commands.rs` + +`spec status` must not infer category-bearing support from base health or raw +semantic-review presence. + +First wedge behavior: + +- preserve existing health logic +- preserve projected semantic review +- add the shared additive `category_qualification` output for read-side + consumers +- if qualification fails, show the unit as semantically present but category + claim unqualified + +This is a read-side honesty improvement, not a health-model rewrite. + +Because `spec status --format json` is a published machine surface, the wedge +should bump `STATUS_JSON_SCHEMA_VERSION` when this object is added. + +### `spec export` + +Target surfaces: + +- `spec-core/src/export.rs` +- `spec-cli/src/commands.rs` + +`spec export` must preserve projected semantic review and add the same additive +qualification output used by `status`. + +Reason: + +- downstream consumers currently receive raw semantic truth and can still drift +- export should be the main machine-readable place where the repo says + "this category claim is qualified" versus "this row exists but is not + support-bearing" + +The first wedge should keep qualification as read-side projected truth only: + +- do **not** persist category qualification into passports +- do **not** add benchmark-local or export-local category vocabularies +- do bump `EXPORT_SCHEMA_VERSION` when the shared qualification object is added + +### Readability and snapshot surfaces + +Target surfaces: + +- benchmark snapshot projection in `spec-cli/src/commands.rs` +- snapshot/readability fields derived from `spec-core/src/benchmark.rs` + +These surfaces must consume the same qualification result as live benchmark +projection. + +No separate snapshot-local support logic is allowed. + +First wedge projection rule: + +- snapshot and export/readability surfaces must preserve the same per-case + qualification output as live benchmark projection +- a supported-labeled case that fails qualification must appear with + `counts_as_supported_positive = false` +- the enclosing full benchmark projection must show + `accounting_status = invalid`, `benchmark_status = invalid`, and + `gate_status = open` +- `readability_review_status` remains a freshness/applicability signal only and + must not be rewritten to encode qualification failure + +## Migration Plan + +### Phase 1 — Add shared contract substrate + +Files: + +- `spec-core/src/category_truth.rs` (new) +- `spec-core/src/lib.rs` or module exports +- minimal producer-owned descriptor-id hook in `spec-core` so qualification can + enforce `descriptor_set` + +Deliverables: + +- registry structs and enums +- first four rows +- `qualify_category_claim(...)` +- producer-owned `descriptor_id` resolution for first-scope seam categories +- stable qualification reason codes +- unit tests for registry lookup and qualification behavior +- explicit tests that current service seam descriptors do **not** qualify as + supported in the first wedge + +### Phase 2 — Migrate benchmark accounting first + +Files: + +- `spec-core/src/benchmark.rs` +- `spec-cli/tests/rust_v1_service.rs` +- `spec-cli/tests/rust_v1_closure.rs` +- snapshot fixtures under `benchmarks/snapshots/` and + `spec-cli/tests/fixtures/benchmarks/` + +Deliverables: + +- benchmark positive credit gated through qualification +- category qualification echoed in case projection output +- explicit regression coverage for the dishonesty bug class +- explicit regression coverage that `BENCH-SERVICE` seam cases remain visible, + labeled `supported`, and still do not earn positive credit while producer + truth stays unsupported + +### Phase 3 — Migrate `spec status` and `spec export` + +Files: + +- `spec-cli/src/commands.rs` +- `spec-core/src/export.rs` +- `spec-cli/tests/cli.rs` +- `spec-cli/tests/m14_regressions.rs` + +Deliverables: + +- additive shared `category_qualification` output on status/export surfaces +- status and export consume the same registry and reason codes +- tests prove supported seam rows qualify and unsupported seam rows remain + observation-only +- tests prove status/export surface the service benchmark mismatch explicitly + rather than silently collapsing it + +### Phase 4 — Freeze snapshot/readability parity + +Files: + +- `spec-cli/src/commands.rs` +- `spec-core/src/benchmark.rs` +- benchmark fixtures and snapshots + +Deliverables: + +- live and snapshot benchmark projections share the same qualification path +- readability surfaces cannot silently reintroduce optimistic category claims + +## Expected File Blast Radius + +The first implementation should stay near this boundary: + +- `spec-core/src/semantic_review.rs` + Reuse existing compatibility keys; do not reopen routing semantics unless the + contract demands a minimal extraction point. + +- `spec-core/src/benchmark.rs` + Main positive-credit adoption point. + +- `spec-core/src/export.rs` + Main machine-readable read-side adoption point. + +- `spec-cli/src/commands.rs` + Main `status`, `export`, and snapshot consumption point. + +- tests and JSON fixtures for benchmark/status/export parity + +This wedge should not sprawl into unrelated family-analysis or corpus files. + +It also should not mutate passport on-disk schema in the first landing. The new +truth is read-side projected, not persisted proof state. + +## Proof Plan + +The implementation is done only when all of these are true. + +### Registry proof + +- lookup succeeds for all four first-scope category ids +- lookup fails cleanly for unknown category ids +- unsupported seam rows never report positive-credit eligibility +- producer-owned descriptor resolution emits the canonical descriptor id for the + ecommerce seam exemplars +- service seam exemplars do not qualify for supported rows while producer truth + still routes them through unsupported surfaces + +### Benchmark proof + +- a supported benchmark case with missing semantic review gets zero positive + credit and a qualification failure reason +- a supported benchmark case with unsupported semantic truth gets zero positive + credit +- a supported benchmark case with missing producer-owned `descriptor_id` gets + zero positive credit and `descriptor_id_missing` +- a supported benchmark case with the same compatibility key but an unapproved + descriptor id gets zero positive credit and `descriptor_not_approved` +- a supported benchmark case with category qualification failure forces: + - full projection `accounting_status = invalid` + - full projection `benchmark_status = invalid` + - full projection `gate_status = open` + - unchanged `readability_review_status` +- a fully qualified supported seam case still earns positive credit +- snapshot output matches live projection output + +### Status/export proof + +- supported seam rows show qualified support only when their producer-owned + descriptor id is approved by the row's `descriptor_set` +- unsupported seam rows show qualified unsupported observation +- canonical descriptor qualifies +- current service seam siblings do not qualify for supported rows +- same compatibility key with unapproved descriptor does not qualify +- missing registry or missing semantic review yields `unqualified`, not + implicit support + +### Sibling-policy proof + +- supported rows with `approved_sibling_extension` must declare a non-empty + `descriptor_set.approved_siblings[]` +- no first-scope row uses `approved_sibling_extension` until producer truth + explicitly widens +- no consumer may widen beyond the registry row's explicit policy + +## Acceptance Criteria + +This wedge is complete when: + +1. the repo has one authoritative category truth registry in `spec-core` +2. benchmark, status, export, and snapshot/readability all call the same + qualification function +3. positive benchmark credit is impossible without explicit qualified support +4. the four first-scope categories have stable registry rows and tests +5. the current `BENCH-SERVICE` label-vs-producer mismatch is explicit contract + truth instead of hidden lore +6. `descriptor_set` is actively enforced through producer-owned descriptor + identity rather than treated as documentation-only metadata +7. no first-scope consumer infers support from health, labels, or + compatibility-key folklore alone + +## Deferred Follow-On Work + +After this wedge lands, the next follow-on can decide whether to: + +- widen producer-owned semantic routing so specific service seam descriptors + become truly supported +- tighten `BENCH-SERVICE` labels so they stop asking for supported claims that + producer truth does not currently grant +- externalize the registry into a checked-in JSON artifact for non-Rust + consumers +- expand the registry beyond the first four seam categories + +Those are real follow-ons, but they are not prerequisites for this wedge. + +## Recommended Implementation Order + +1. add the registry and qualification substrate +2. land benchmark-accounting adoption and regressions +3. land `status` / `export` additive qualification output +4. lock snapshot/readability parity + +That order keeps the original bug class closed first, then prevents the same +dishonesty from surviving elsewhere. diff --git a/docs/core_mechanisms_guide_v0.1.md b/docs/core_mechanisms_guide_v0.1.md new file mode 100644 index 0000000..6ce29dd --- /dev/null +++ b/docs/core_mechanisms_guide_v0.1.md @@ -0,0 +1,558 @@ +# spec — Core Mechanisms Guide +**Version:** v0.1 +**Status:** Active repo guide +**Date:** 2026-05-24 + +## Purpose + +This document is the repo's "start here" explanation for the main mechanisms +at play in `spec`. + +It exists because several different systems are active at once: + +- authored semantic units +- backend lowering and execution +- atom and molecule proof +- semantic review +- promoted semantic families +- corpus and recommendation analysis +- benchmark-based product claims + +These systems are related, but they are not the same thing. The goal of this +guide is to make the boundaries memorable so repo docs can point back here +instead of re-explaining the whole stack from scratch every time. + +## Who This Is For + +This guide is for a builder or engineer who is new to `spec` and needs the +repo mental model quickly enough to make or review a real change. + +It is especially aimed at people doing one of these jobs: + +- author or fix a `*.unit.spec` +- explain why `status`, `export`, or a passport says what it says +- figure out whether a behavior is core support, backend support, proof, + semantic review, or benchmark policy + +## What You Should Be Able To Do After Reading This + +After this guide, you should be able to: + +- identify whether a unit question is about kind, backend, proof, semantic + review, family, or benchmark claim +- run the first proof loop on a concrete unit without guessing which command + matters +- know where proof is stored and which commands refresh it +- know when corpus, promotion, and benchmark docs matter, and when they do not + +## If You Changed A Spec File + +If you just changed a `*.unit.spec` or `.test.spec`, start here instead of +trying to load the whole architecture into your head first: + +```bash +cargo run -p spec-cli -- validate examples/ecommerce/units/pricing/apply_tax.unit.spec --format json +cargo run -p spec-cli -- build examples/ecommerce/units --output examples/ecommerce/src/generated +cargo run -p spec-cli -- test examples/ecommerce/units/pricing/apply_tax.unit.spec +cargo run -p spec-cli -- status examples/ecommerce/units --format json +``` + +What each command answers: + +- `validate`: is the authored source legal `spec` input? +- `build`: can the backend lower and compile it? +- `test`: did it prove, and did it refresh passport plus semantic-review truth? +- `status`: what health state does the repo project right now? + +Artifacts to watch: + +- generated Rust: `examples/ecommerce/src/generated/` +- unit proof record: `*.spec.passport.json` +- molecule proof record: `*.test.evidence.json` + +If you changed an interaction across units, also run the relevant `.test.spec` +molecule test. For the canonical seam flow, that is: + +```bash +cargo run -p spec-cli -- test examples/ecommerce/units/pricing/discount_strategy_checkout_flow.test.spec +``` + +## Running Example + +Keep `pricing/apply_tax` in your head while reading this doc. It is the clean +function example for the current system: + +- `kind:function` +- lowers on Rust, and in the bounded lane on TypeScript +- proves through unit-local atom tests +- can be semantically reviewed into + `function.arithmetic_leaf.monotone_up.v1` + +When seam behavior matters, contrast it with `pricing/pricing_quote`, which is +a `kind:data` seam that can be valid and benchmark-positive without semantic +review understanding it as a function family. + +## Short Version + +If you feel lost, ask these questions in order: + +1. What **kind** of unit is this? +2. Can it **lower and execute** on the backend I care about? +3. Does it have **current proof**? +4. Does **semantic review** understand its meaning? +5. If yes, which **family** is it in? +6. Does it count toward a **benchmark/product claim**? +7. If it is not understood yet, does the **corpus** suggest a new family to + promote? + +That is the main stack. + +## What You Can Ignore On Day 1 + +If your job is just to author or fix a unit, you can ignore corpus, +recommendation, promotion, and benchmark details until the validate/build/test +loop is green. + +Come back to those layers when you are: + +- changing semantic-review capability +- deciding whether a new family should be promoted +- making or auditing a public product claim such as Rust V1 + +## The Two Layers + +The cleanest separation in this repo is: + +### 1. Write-path truth + +This is the product's semantic-source system. + +It owns: + +- `*.unit.spec` +- `*.test.spec` +- validation +- normalization +- graph resolution +- generation +- build and test execution +- passports +- molecule evidence +- `status` and `export` projections + +This is the layer that changes semantic repo truth. + +### 2. Read-side interpretation + +This is everything that reads the write-path truth and interprets it. + +Examples: + +- semantic review +- benchmark accounting +- coverage and recommendation artifacts +- corpus-program decisions + +These consumers interpret the graph and its proof state. They do not define the +semantic graph itself. + +## The Main Questions + +Each unit can be asked several different questions. Most confusion comes from +mixing them together. + +| Question | Mechanism | Short meaning | +| --- | --- | --- | +| Is this shape legal `spec` source? | Core support | The authored unit shape is allowed and valid. | +| Can this run on Rust or TypeScript? | Backend support | A target-language lane can lower and execute it. | +| Did it actually prove? | Atom / molecule proof | Current local-test and molecule evidence exists and is fresh. | +| Does the reviewer understand its function meaning? | Semantic review support | The bounded semantic reviewer can classify the function honestly. | +| Which meaning bucket is it in? | Family support | The function matches one shipped semantic family. | +| Does it count toward a public claim? | Benchmark support | It earns positive credit toward a bounded product claim such as Rust V1. | +| What should we teach the reviewer next? | Corpus / recommendation | Checked-in examples create pressure for future family promotion. | + +## The Core Mechanisms + +### Kinds + +Kinds answer: + +> What sort of unit is this structurally? + +Current top-level authored kinds: + +- `function` +- `data` +- `sum` + +Kinds are part of `spec` core support. If the repo supports `kind:data`, that +means the authored seam shape is valid and can participate in the normal +pipeline. It does **not** automatically mean semantic review understands that +kind. + +### Seams + +In current repo language, a seam is usually a non-function unit shape that owns +shared semantics plus nested behavior. + +Examples: + +- `kind:data` +- `kind:sum` + +Seams are about the product model and authored shape. They are not the same as +semantic families. + +### Backends + +Backends answer: + +> Can this authored unit lower and execute on backend X? + +Today the important lanes are: + +- Rust +- bounded TypeScript + +Backend support is narrower than core support. A unit can be valid `spec` +source and still not be executable in every backend lane. + +The current TypeScript lane is intentionally smaller than Rust: + +- only bounded `kind:function` closure graphs +- only already-supported function-family shapes +- atom-only proof +- no molecule TypeScript execution +- no seam-kind TypeScript execution + +### Atom and Molecule Proof + +Proof answers: + +> Did this thing actually validate, build, and test successfully, and is that +> proof current? + +Two proof surfaces matter: + +- **atom proof**: local unit-owned tests from `local_tests` +- **molecule proof**: multi-unit interaction proof from `.test.spec` + +This is the main runtime truth layer. A unit can be structurally supported and +still have failing or stale proof. + +### Passports + +Passports answer: + +> What is this unit's stored proof record? + +Passports are derived per-unit artifacts. They store things like: + +- observed test results +- freshness anchors +- semantic review truth from the last proof refresh +- provenance + +Passports are about current per-unit proof state. They are not recommendation +artifacts and they are not benchmark declarations. + +### Semantic Review + +Semantic review answers: + +> Does the bounded reviewer understand this function's meaning shape? + +Important constraints: + +- it is intentionally narrow +- it is not arbitrary code understanding +- it is mainly first-class for `kind:function` +- only `spec test` refreshes semantic review truth + +Today it recognizes a small shipped function vocabulary such as arithmetic +leaves and wrapper pipeline forms. It can also explicitly mark a function as +unsupported. + +It does **not** currently imply general understanding of seam kinds like +`kind:data` or `kind:sum`. + +### Families + +Families answer: + +> If semantic review understands this function, what supported meaning bucket is +> it in? + +A family is a shipped semantic-review bucket, not a seam and not a benchmark. + +Examples: + +- `function.arithmetic_leaf.monotone_down_nonnegative.v1` +- `function.arithmetic_leaf.monotone_up.v1` +- `function.wrapper.pipeline.v1` + +Families are the current semantic-review vocabulary. + +### Promotion + +Promotion answers: + +> When does a candidate family become real shipped support? + +A family is not supported just because someone can name it or because the +corpus contains several examples of it. It becomes real shipped support only +after the promotion workflow proves and certifies it as an honest boundary. + +Promotion is what turns: + +- "we see this shape" + +into: + +- "the reviewer now supports this shape honestly" + +### Corpus + +The corpus answers: + +> What shapes are actually showing up in checked-in examples and regressions? + +The corpus is an observation surface. It is used to detect: + +- already-supported coverage +- unsupported pressure +- repeated adjacent shapes +- whether a future family might be worth promotion + +The corpus does **not** itself expand semantic-review capability. + +### Coverage and Recommendation Artifacts + +These artifacts answer: + +> Given the corpus, what does the repo currently observe and what should happen +> next? + +Examples: + +- `coverage.latest.json` +- `recommendation.latest.json` +- `corpus-program-decision.latest.json` + +These are read-side analysis outputs. They do not define repo truth; they +interpret it. + +Most contributors can ignore this section on day 1. It matters when you are +working on family promotion, recommendation honesty, or corpus-program +decisions. + +### Benchmarks + +Benchmarks answer: + +> Which selected cases count toward a bounded public product claim today? + +Benchmarks are curated proof walls, not authored semantic source. + +For Rust V1, they back claims like: + +- which roots are proof-authoritative +- which cases count as positive credit +- which negative walls stay visible without earning positive credit + +Benchmarks read existing proof inputs such as passports and molecule evidence. +They do not mint those proofs themselves. + +Most contributors should only care about benchmarks when they are asking what +the repo can publicly claim today, not when they are just trying to prove a +local fix. + +## What "Supported" Actually Means + +"Supported" means different things in different layers. Do not collapse them +into one flag. + +### Core support + +Can `spec` author, validate, and run this shape honestly? + +### Backend support + +Can backend X lower and execute this shape? + +### Proof support + +Do we have current atom and molecule evidence? + +### Semantic support + +Does the bounded reviewer understand this function meaning? + +### Family support + +Is this function in one of the currently shipped family buckets? + +### Product-claim support + +Does this case count toward a benchmark-backed public claim such as Rust V1? + +### Future-support pressure + +Does the checked-in corpus suggest the next family that should be promoted? + +## The Most Common Confusions + +### Family vs seam + +These are different. + +- **Family**: a semantic-review bucket for function meaning +- **Seam**: a product/unit-shape concept such as `kind:data` or `kind:sum` + +Example: + +- `pricing_quote` being a `data` seam means `spec` core supports that unit + shape. +- It does **not** mean semantic review supports data seams. +- `apply_tax` being in `function.arithmetic_leaf.monotone_up.v1` means semantic + review supports that function family. + +### Valid proof vs semantic support + +A unit can prove successfully without semantic review understanding it. + +That is why a seam can be: + +- valid in Rust +- benchmark-positive +- current in proof + +while still remaining unsupported by semantic review. + +### Corpus pressure vs capability + +The corpus can show that a shape is common without teaching the reviewer how to +understand it. + +More examples increase pressure. They do not automatically increase semantic +understanding. + +### Benchmarks vs source truth + +Benchmarks do not define what the unit is. They define how some selected units +count toward a public product claim. + +## A Simple Flow + +Here is the first successful loop for the running example. This is the fastest +way to connect the concepts in this guide to something real. + +1. Start with authored truth. + The source of truth is + `examples/ecommerce/units/pricing/apply_tax.unit.spec`. + +2. Validate the authored unit: + + ```bash + cargo run -p spec-cli -- validate examples/ecommerce/units/pricing/apply_tax.unit.spec --format json + ``` + + If this fails, you still have a source-shape problem. Nothing about backend + lowering, proof, semantic review, or benchmarks matters yet. + +3. Build the example root: + + ```bash + cargo run -p spec-cli -- build examples/ecommerce/units --output examples/ecommerce/src/generated + ``` + + If this fails, the authored source may be valid, but the backend-lowering or + compile lane is not. + +4. Refresh proof: + + ```bash + cargo run -p spec-cli -- test examples/ecommerce/units/pricing/apply_tax.unit.spec + ``` + + This rewrites + `examples/ecommerce/units/pricing/apply_tax.spec.passport.json`. + If you also changed cross-unit behavior, run the matching `.test.spec` file + and refresh the corresponding `*.test.evidence.json`. + +5. Project current health: + + ```bash + cargo run -p spec-cli -- status examples/ecommerce/units --format json + ``` + + Now you are looking at read-side truth. `status` tells you whether the unit + is `valid`, `stale`, `failing`, `incomplete`, or `untested`; it does not + create proof on its own. + +6. Ask the semantic-review question only after proof exists. + If the unit is a supported `kind:function` shape, `spec test` may refresh + semantic-review truth inside the passport. For `pricing/apply_tax`, that can + include the family + `function.arithmetic_leaf.monotone_up.v1`. + +7. Ask the benchmark question last. + Benchmark accounting is about whether a proved case counts toward a bounded + public claim such as Rust V1. It is not the same question as whether the + unit is valid, compiled, or semantically understood. + +That is one flow with several distinct readers, not one monolithic support +engine. + +## Where To Look + +Use this map when you want the source of truth for a concept: + +| If you want to understand... | Start here | +| --- | --- | +| the docs tree itself and which docs are current authority | [`docs/README.md`](./README.md) | +| the core authored model and workflow | [`README.md`](../README.md) | +| semantic-family capability, corpus, and promotion | [`semantic_family_capability_corpus_guide_v0.1.md`](./semantic_family_capability_corpus_guide_v0.1.md) | +| the current Rust V1 benchmark and claim boundary | [`rust_v1_contract_stack.md`](./rust_v1_contract_stack.md) | +| long-range product vision | [`north_star_v0.2.md`](./north_star_v0.2.md) | +| high-level system architecture | [`high_level_technical_architecture_v0.2.md`](./high_level_technical_architecture_v0.2.md) | +| milestone sequencing and release shape | [`roadmap_and_release_shape_v0.1.md`](./roadmap_and_release_shape_v0.1.md) | + +## One-Page Cheat Sheet + +Keep this version in your head: + +- **Kinds / seams** answer: what is this unit structurally? +- **Backends** answer: can it run on Rust / TypeScript? +- **Passports / evidence** answer: did it prove? +- **Semantic review / families** answer: does the reviewer understand this + function shape? +- **Corpus / recommendation** answer: what should we teach the reviewer next? +- **Benchmarks** answer: what can we publicly claim today? + +## Non-Goals + +This document does not try to: + +- replace milestone plans +- replace the README command reference +- define current benchmark rosters exhaustively +- define every current family packet in detail +- become the only architecture doc in the repo + +Its job is narrower: keep the mental model clean enough that the deeper docs +stay readable. + +## GSTACK REVIEW REPORT + +| Review | Trigger | Why | Runs | Status | Findings | +|--------|---------|-----|------|--------|----------| +| CEO Review | `/autoplan` | Scope and strategy | 1 | CLEAR | The guide direction was right, but it needed a much faster newcomer path and a clearer day-1 versus advanced-systems boundary. | +| Codex Review | `codex exec` | Independent second opinion | 1 | ISSUES_FOUND | Outside review agreed the taxonomy was strong but said the doc still did not get a first-time reader to one successful repo action fast enough. | +| Eng Review | `/autoplan` | Commands and artifact pathing | 1 | CLEAR | Added a concrete validate/build/test/status loop, explicit artifact locations, and a running example that ties the concepts to live repo surfaces. | +| Design Review | `/autoplan` | UI and visual UX gaps | 0 | — | No UI scope in this document review. | +| DX Review | `/plan-devex-review` | Developer experience gaps | 1 | CLEAR | Time to first useful outcome improved from conceptual-only to a five-minute guided loop with commands, expected artifacts, and clear "ignore this for now" boundaries. | + +**CODEX:** Independent review said the guide named the system well but front-loaded maintainers' concepts before showing a first successful action. +**UNRESOLVED:** The broader README and docs tree still need a follow-on cleanup so this guide becomes the stable anchor instead of one strong page inside an overloaded docs set. +**VERDICT:** CEO + ENG + DX CLEARED for this revision. Good mental-model anchor, now with a usable newcomer path. diff --git a/docs/data_category_taxonomy_v0.1.md b/docs/data_category_taxonomy_v0.1.md new file mode 100644 index 0000000..8024e42 --- /dev/null +++ b/docs/data_category_taxonomy_v0.1.md @@ -0,0 +1,233 @@ +# spec — Data Category Taxonomy +**Version:** v0.1 +**Status:** Active working map +**Date:** 2026-05-24 + +## Purpose + +This document is the per-kind follow-on for `kind:data`. + +Use it when the higher-level [kind coverage map](./kind_coverage_map_v0.1.md) +is not enough and you need to answer more specific questions such as: + +- what named data categories does the repo actually own today? +- which parts of `kind:data` are structural support versus descriptor support? +- what makes a seam qualify for `data.pricing_quote.v1`? +- what is still unnamed pressure inside the data kind? + +## Scope + +This document tracks the current **product/support surface** for `kind:data`. + +It does **not** try to invent future data categories just to make the map feel +complete. If the repo does not currently own a category strongly enough to name +it, this doc should say `Unnamed` rather than pretend the taxonomy is settled. + +## Reading Rules + +This taxonomy distinguishes four different things that are easy to blur: + +- **Structural legality** + Is a `kind:data` seam valid authored `spec` source at all? +- **Named descriptor category** + Does the bounded semantic-review system recognize a specific data shape? +- **Proof and benchmark role** + Does a data seam merely validate, or does it carry benchmark-positive public + claim weight? +- **Unsupported observation** + Can the repo honestly say a seam is outside the owned descriptor subset? + +## Structural Baseline For `kind:data` + +Every current `kind:data` seam starts from the same structural contract: + +- authored data fields live under `data.fields` +- shared construction lives under `constructors` +- callable seam behavior lives under `methods` +- top-level `contract` is not authored +- top-level `deps` is not authored +- top-level `imports` is not authored +- top-level `body.rust` must stay empty +- top-level `body.typescript` is not authored today + +That means `kind:data` already has a real authored shape even where it does not +yet have a broad category taxonomy. + +## Current Named Categories + +As of this doc's date, the repo owns exactly one supported named data category +plus one explicit unsupported observation surface: + +- `data.pricing_quote.v1` +- `unsupported.data.v1` + +That is the main truth to internalize: `kind:data` is real, but its named +descriptor vocabulary is still intentionally narrow. + +## Data Category Matrix + +| Category | Semantic route | Structural support | Rust lane | TypeScript lane | Atom proof | Molecule role | Benchmark role | Public role | Notes | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| `data.pricing_quote.v1` | Supported descriptor | Shipped | Yes | No seam-kind execution lane | Yes | Direct and flow-bearing | Positive in `BENCH-ECOM` and `BENCH-SERVICE` | Direct as plain data seam | Canonical quote seam; descriptor-based and namespace-agnostic | +| `unsupported.data.v1` | Terminal unsupported surface | N/A as a supported category | N/A as a supported category | N/A as a supported category | N/A as a supported category | N/A | No positive credit | Observation only | Honest additive fallback when a data seam misses the owned descriptor subset | + +## Owned Descriptor: `data.pricing_quote.v1` + +`data.pricing_quote.v1` is the only currently supported named data category. + +It is descriptor-based rather than path-based. The repo proves that both: + +- `pricing/pricing_quote` +- `billing/pricing_quote` + +route to the same compatibility key when they preserve the same authored and +executable seam meaning. + +### Descriptor requirements + +The current semantic-review detector requires: + +- exactly three decimal-authored fields: + - `subtotal` + - `discount_rate` + - `tax_rate` +- exactly one constructor: + - `new` +- that constructor must accept the same three decimal fields as inputs +- required semantic method roles: + - `discounted_subtotal` + - `total` +- those required methods must: + - use `shared_ref` receivers + - take no additional inputs + - return decimal values + +The repo does **not** currently treat this as a loose “quote-like data seam.” +It is an exact bounded descriptor. + +### Execution and proof shape + +The canonical ecommerce and service examples show the intended support shape: + +- Rust lowering is first-class +- atom proof is first-class through `local_tests` +- molecule proof is first-class for multi-unit checkout flows +- benchmark-positive proof exists in both required positive walls + +### Helper and example methods + +The owned descriptor is stricter about required semantic roles than it is about +extra helper/example methods. + +That means additive helper or example methods may coexist so long as the seam +still preserves the required quote roles and authored/executable meaning. +What the repo does **not** currently own is a separately named helper-only data +category. + +## Unsupported Observation: `unsupported.data.v1` + +The repo also owns an honest unsupported observation surface for `kind:data`. + +This matters because it lets the system say: + +- this is a valid `kind:data` seam structurally +- but it is outside the narrow owned descriptor subset + +The canonical near-miss example today is renaming descriptor vocabulary, such +as changing `discount_rate` to `discount_percent`. That does **not** produce a +new supported category. It routes to `unsupported.data.v1`. + +So the current taxonomy boundary is: + +- valid `kind:data` seam support is broader than one descriptor +- supported semantic-category ownership is not + +## Benchmark And Claim Role + +`data.pricing_quote.v1` is not just structurally supported. It is part of the +current positive benchmark-backed product claim: + +- `pricing/pricing_quote` is a positive case in `BENCH-ECOM` +- `billing/pricing_quote` is a positive case in `BENCH-SERVICE` + +That makes the current data category map different from a purely exploratory +taxonomy. The one owned category already carries real public-claim weight. + +`unsupported.data.v1` carries no such credit. It is observation-only. + +## TypeScript Boundary + +`kind:data` currently has **no seam-kind TypeScript execution lane**. + +That boundary should be stated narrowly: + +- the repo can reason about supported data seams in bounded semantic context +- that does **not** mean data seams execute in the shipped TypeScript lane +- current TypeScript execution support remains function-only + +So any future `kind:data` TypeScript work would be a **support-dimension +expansion**, not evidence that more data categories already exist today. + +## What Is Still Unnamed For `kind:data` + +This is still one of the largest open map holes in the repo. + +The current unnamed pressure includes at least: + +- quote-like data seams that keep the same role pattern but rename fields or + constructor vocabulary +- data seams with multiple constructors instead of one canonical `new` +- data seams with more than two owned semantic methods +- data seams that are valid structural seams but have no benchmark or semantic + category ambition +- any future seam kinds that want TypeScript execution rather than Rust-only + seam lowering + +The important constraint is that none of those are repo-owned named categories +yet. They are pressure, not shipped taxonomy. + +## What This Taxonomy Clarifies + +This document makes a few important truths visible: + +1. `kind:data` is already real and claim-bearing even though its named category + map is thin. +2. The current data taxonomy is descriptor-based, not namespace-based. +3. The repo owns one benchmark-positive supported descriptor today, not a broad + family of data seams. +4. Unsupported data review is explicit, which is healthier than silently + stretching `data.pricing_quote.v1` to fit near-misses. +5. The next work for `kind:data` is likely naming categories or support + boundaries, not re-proving that seam structure exists. + +## How To Update This Taxonomy + +When `kind:data` truth changes, update this document after checking: + +1. `docs/kind_coverage_map_v0.1.md` +2. `README.md` +3. `docs/core_mechanisms_guide_v0.1.md` +4. `PLAN.md` +5. `DECISIONS.md` +6. `benchmarks/labels.json` +7. current semantic review, validator, CLI, and example-root truth + +Update the smallest correct thing: + +- add or edit a row in the matrix +- tighten the descriptor requirements +- widen or narrow the TypeScript boundary +- clarify which surfaces are benchmark-positive versus observation-only + +Do **not** name a new data category just because several seams “feel similar.” +Wait until the repo has: + +- explicit checked-in routing truth +- clear authored and executable role requirements +- a stable compatibility key + +## Likely Next Follow-On + +After this doc, the next natural follow-on is probably +`docs/sum_category_taxonomy_v0.1.md` so seam kinds stop being asymmetric in the +map. diff --git a/docs/function_category_matrix_v0.1.md b/docs/function_category_matrix_v0.1.md new file mode 100644 index 0000000..7d5a403 --- /dev/null +++ b/docs/function_category_matrix_v0.1.md @@ -0,0 +1,183 @@ +# spec — Function Category Matrix +**Version:** v0.1 +**Status:** Active working map +**Date:** 2026-05-24 + +## Purpose + +This document is the per-category follow-on for `kind:function`. + +Use it when the higher-level [kind coverage map](./kind_coverage_map_v0.1.md) +is not enough and you need to answer more specific questions such as: + +- which function categories are actually shipped? +- which ones run in Rust? +- which ones run in the bounded TypeScript lane? +- which ones carry benchmark-positive public claim weight? +- which ones are helper-only or companion-negative only? + +## Scope + +This document tracks the **product/support surface** for `kind:function`. + +It does **not** try to document every maintainer command separately. +That distinction matters because some maintainer-facing paths are narrower than +the shipped product surface. In particular: + +- the bounded `spec test --target-language typescript` lane covers more + supported function categories than the narrower maintainer-facing + `cargo xtask family prove --target-language typescript` path +- this matrix tracks the repo's shipped support surface first +- command-specific maintenance constraints are called out in notes when they + matter + +## Reading Rules + +This matrix distinguishes four different ideas that are easy to blur: + +- **Promoted packet** + Is there a real promoted semantic-family packet? +- **Runtime route** + Does current runtime/semantic routing recognize the category as supported? +- **TypeScript lane** + Can the bounded product TypeScript lane execute roots or local graphs for it? +- **Benchmark role** + Does current benchmark evidence make it part of the positive Rust V1 proof + wall, helper-only closure support, companion-negative visibility, or no claim + at all? + +## Inventory Snapshot + +As of this doc's date, `cargo xtask family inventory --format json` reports: + +- promoted families: + - `function.wrapper.pipeline.chain3.v1` + - `function.wrapper.pipeline.normalized_required_arg.v1` + - `function.wrapper.pipeline.v1` + - `function.arithmetic_leaf.monotone_down_nonnegative.v1` + - `function.arithmetic_leaf.monotone_up.v1` + - `function.helper.identity_passthrough.v1` +- runtime-supported routes: + - the same six categories above +- supported-but-unpromoted families: + - none + +That means the current `kind:function` map has no runtime-supported shadow +category waiting outside promotion. The main gaps are deferred or still unnamed, +not "supported but unpromoted." + +## Matrix Legend + +- **Yes**: shipped and repo-owned today +- **Bounded**: shipped, but intentionally narrow +- **Indirect**: not the main claim carrier, but participates in proof or + closure around a claim-bearing category +- **Helper-only**: real support, but not the main category being claimed +- **Companion-negative**: visible in negative proof only, zero positive credit +- **Deferred**: explicitly named future boundary +- **No**: not part of the current shipped truth +- **N/A**: not the right axis for that row + +## Function Category Matrix + +| Category | Promoted packet | Runtime route | Rust lane | TypeScript lane | Atom proof | Molecule role | Semantic review | Benchmark role | Public role | Notes | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| `function.arithmetic_leaf.monotone_down_nonnegative.v1` | Yes | Yes | Yes | Yes, bounded | Yes | Indirect | Yes | Positive in `BENCH-ECOM` and `BENCH-SERVICE`; companion-negative visible in `BENCH-CROSSLIB` | Direct | Canonical discount-like leaf; supports zero-or-one helper dep shapes in current truth | +| `function.arithmetic_leaf.monotone_up.v1` | Yes | Yes | Yes | Yes, bounded | Yes | Indirect | Yes | Positive in `BENCH-ECOM` and `BENCH-SERVICE`; companion-negative visible in `BENCH-CROSSLIB` | Direct | Canonical tax-like leaf; also the explicit M32 second-language pilot anchor | +| `function.wrapper.pipeline.v1` | Yes | Yes | Yes | Yes, bounded | Yes | Indirect and flow-bearing | Yes | Positive in `BENCH-ECOM` and `BENCH-SERVICE`; companion-negative visible in `BENCH-CROSSLIB` | Direct | Straight-line two-call wrapper; current benchmark-positive wrapper baseline | +| `function.wrapper.pipeline.normalized_required_arg.v1` | Yes | Yes | Yes | Yes, bounded | Yes | Indirect and flow-bearing | Yes | Positive in `BENCH-ECOM` and `BENCH-SERVICE` | Direct | Narrow required-arg wrapper follow-on; TypeScript root eligibility is covered in validator truth even though examples lean more on wrapper and chain3 | +| `function.wrapper.pipeline.chain3.v1` | Yes | Yes | Yes | Yes, bounded | Yes | Indirect and flow-bearing | Yes | Companion-negative visible in `BENCH-CROSSLIB`; not part of current positive `BENCH-ECOM` or `BENCH-SERVICE` walls | Shipped but not positive-wall anchored | Straight-line three-call wrapper pipeline; current product support is ahead of current positive benchmark anchoring | +| `function.helper.identity_passthrough.v1` | Yes | Yes | Yes | Yes, bounded | Yes | Helper-only | Yes | Positive closure support inside `BENCH-ECOM`; helper-only marker in benchmark evidence | Helper-only | Real shipped function category, but not separately named in the plain-English Rust V1 claim | +| `unsupported.function.v1` | No | Terminal catch-all | N/A as a supported category | N/A as a supported category | N/A as a supported category | N/A | Yes, as unsupported verdict | No positive credit | Observation only | Honest additive unsupported surface; may coexist with real backend/proof truth for a unit, but never upgrades to supported-family credit | +| bounded generics for `kind:function` | No | No | Deferred | Deferred | Deferred | Deferred | Deferred | No | Deferred | Explicit `V1.1` authored-shape expansion; not a new kind | +| async flows, runtime adapters, and IO-owned boundaries | No | No | Deferred | Deferred | Deferred | Deferred | Deferred | No | Deferred | Explicit `V1.1` backend/runtime/proof expansion; Rust V1 remains synchronous-only today | + +## TypeScript Lane Notes + +The bounded TypeScript lane for `kind:function` currently has these repo-level +constraints: + +- function-only, not seam kinds +- atom-only proof +- no `.test.spec` molecule execution +- bounded local-graph and cross-library closure rules +- every reachable unit must classify to a supported semantic review +- every reachable unit must author `body.typescript` + +That means the TypeScript column in the matrix is not a broad "JavaScript +backend parity" claim. It is a bounded execution lane over already-supported +function categories. + +### Important maintainer-path caveat + +The maintainer-facing family workflow is narrower than the product lane: + +- `cargo xtask family prove --target-language typescript` currently allows only + `function.arithmetic_leaf.monotone_up.v1` and `function.wrapper.pipeline.v1` + +That does **not** shrink the product-lane truth recorded above. It means the +family-maintainer command surface is a stricter path than the broader bounded +`spec` execution lane. + +## What This Matrix Clarifies + +This matrix makes a few important truths visible: + +1. `kind:function` is not one undifferentiated support blob. + Some categories are positive-wall anchored, some are helper-only, and some + are shipped but not yet positive benchmark anchors. +2. The main `function` gap is no longer "do we have families?" + The main gap is category completion, benchmark anchoring, and deferred-edge + decisions. +3. `chain3` is a real shipped function category, but current public proof walls + do not anchor it the way they anchor the arithmetic, wrapper, and + normalized-required-arg categories. +4. The bounded TypeScript lane is broader than the narrow maintainer + `xtask family prove --target-language typescript` path. +5. Deferred `V1.1` surfaces are support-dimension expansions for + `kind:function`, not candidates for a fourth top-level kind. + +## How To Update This Matrix + +When function truth changes, update this document after checking: + +1. `cargo xtask family inventory --format json` +2. `README.md` +3. `docs/core_mechanisms_guide_v0.1.md` +4. `PLAN.md` +5. `DECISIONS.md` +6. benchmark labels and current benchmark evidence +7. current validator / semantic review / CLI test truth for TypeScript and proof + +Update the smallest correct thing: + +- a row in the matrix +- a note about benchmark role +- the TypeScript notes +- the inventory snapshot + +Do **not** mark a function category as positive-wall anchored unless the current +benchmark labels and evidence actually include it in `BENCH-ECOM` or +`BENCH-SERVICE`. + +Do **not** mark a category as helper-only unless the benchmark and claim surfaces +really treat it as closure support rather than the thing being claimed. + +## Likely Next Follow-On + +If `kind:function` keeps growing, the next artifact after this one should +probably be machine-readable rather than another prose-only doc: + +- a checked-in category registry with columns for: + - category id + - promoted packet status + - runtime route status + - Rust lane + - TypeScript lane + - atom proof + - molecule role + - semantic-review status + - benchmark role + - public role + +That would let the repo compute progress instead of only describing it. diff --git a/docs/high_level_technical_architecture_v0.2.md b/docs/high_level_technical_architecture_v0.2.md index 32d34ca..844f629 100644 --- a/docs/high_level_technical_architecture_v0.2.md +++ b/docs/high_level_technical_architecture_v0.2.md @@ -8,6 +8,22 @@ > [`README.md`](../README.md), [`CHANGELOG.md`](../CHANGELOG.md), [`PLAN.md`](../PLAN.md), and > [`DECISIONS.md`](../DECISIONS.md). +## Current Grounding + +This architecture draft is still useful for boundaries, but some of the +examples below were written before the current repo shape settled. + +Use these current facts as the grounding layer while reading it: + +- the current authored sources are `*.unit.spec`, `.test.spec`, and `.plan.spec` +- the shipped top-level unit kinds are `function`, `data`, and `sum` +- the current proof story is atom plus molecule proof; organism remains future vocabulary, not a shipped repo-wide contract +- the current default backend lane is Rust, with a bounded additive TypeScript lane +- passports and molecule evidence are co-located derived artifacts, while generated Rust lives under example `src/generated/` trees + +Treat this doc as architecture direction and component framing, not as the +authoritative day-to-day CLI contract. + ## Purpose This document captures the initial technical architecture for **spec**, a semantic-unit system for authoring, validating, compiling, linking, and verifying software as structured semantic records. @@ -97,7 +113,7 @@ A test artifact represents verification at one of several levels: - atom - molecule -- organism +- organism (future vocabulary, not a current shipped repo-wide proof surface) Atom tests are usually owned directly by a unit. Molecule and organism tests are separate artifacts that may cover multiple units. @@ -154,10 +170,10 @@ A semantic unit source file should contain human-authored truth: A non-local test file should contain: - `id` -- `tier` +- `intent` - `covers` -- `scenario` -- optional setup and fixtures metadata +- optional `imports` +- `body.rust` ### Derived artifacts The following should not be hand-authored in source files: @@ -178,29 +194,26 @@ These belong in the build output. ```text spec/ - schema/ - unit.cue - test.cue - policies/ - units/ - pricing/ - apply_discount.unit.spec - apply_tax.unit.spec - checkout/ - finalize_total.unit.spec - tests/ - pricing/ - discount_plus_tax.test.spec - checkout/ - final_total_flow.test.spec - build/ - generated/ - passports/ - docs/ - diagnostics/ + README.md + docs/ + examples/ + ecommerce/ + README.md + units/ + money/round.unit.spec + pricing/apply_discount.unit.spec + pricing/apply_tax.unit.spec + pricing/calculate_total.unit.spec + pricing/pricing_quote.unit.spec + pricing/discount_strategy.unit.spec + pricing/discount_plus_tax.test.spec + src/generated/ ``` -This shape is illustrative, not final. +This shape is illustrative, but it is closer to the current checked-in repo +than the original March draft layout. In the live repo, passports and molecule +evidence are co-located next to the source specs rather than collected into one +separate `build/passports/` tree. --- @@ -242,14 +255,25 @@ links: ## Example Test File ```yaml -kind: test -tier: molecule id: pricing/discount_plus_tax +spec_version: "0.3.0" +intent: + why: Verify that applying discount then tax produces the correct final price. covers: - pricing/apply_discount - pricing/apply_tax -scenario: - description: Discount is applied before tax in final pricing flow. + - money/round +imports: + - rust_decimal::Decimal + - crate::pricing::apply_discount::apply_discount + - crate::pricing::apply_tax::apply_tax +body: + rust: | + { + let discounted = apply_discount(Decimal::new(10000, 2), Decimal::new(10, 2)); + let taxed = apply_tax(discounted, Decimal::new(725, 4)); + assert!(taxed > Decimal::ZERO); + } ``` --- diff --git a/docs/kind_coverage_map_v0.1.md b/docs/kind_coverage_map_v0.1.md new file mode 100644 index 0000000..f108cc1 --- /dev/null +++ b/docs/kind_coverage_map_v0.1.md @@ -0,0 +1,429 @@ +# spec — Kind Coverage Map +**Version:** v0.1 +**Status:** Active working map +**Date:** 2026-05-24 + +## Purpose + +This document is the repo's first-pass progress map for `spec` kinds and the +categories inside each kind. + +It exists because the repo already has the raw truth needed to answer support +questions, but it does not yet have one place that turns that truth into a +navigation map. + +Without that map, the project keeps re-asking vague questions like: + +- "what is still missing?" +- "is this a new kind or just a wider function surface?" +- "how far are we from completion for this shape?" + +This doc is meant to make those questions concrete. + +## What This Map Measures + +This map tracks three different things: + +1. **Top-level kinds** + The structural unit shapes the repo currently owns. +2. **Per-kind categories** + The named buckets or sub-surfaces inside a kind. +3. **Support dimensions** + The axes that determine how "real" a category is today. + +The support dimensions used here are: + +- **Core** + Is the authored shape legal `spec` source? +- **Rust** + Can the Rust lane lower and execute it? +- **TypeScript** + Can the bounded TypeScript lane lower and execute it? +- **Proof** + Does the repo have current atom and/or molecule proof patterns for it? +- **Semantic review** + Can the bounded reviewer classify it honestly? +- **Benchmark claim** + Does it contribute directly to a bounded public product claim such as Rust V1? + +## Legend + +- **Shipped**: repo-owned and live today +- **Bounded**: shipped, but intentionally narrow +- **Deferred**: explicitly named as future work, not part of current truth +- **Unnamed**: clearly a gap, but not yet owned as a named category +- **N/A**: not the right axis for that row + +## The Main Rule + +Every roadmap move should answer one of these questions explicitly: + +1. Is this adding a **new kind**? +2. Is this adding a **new category inside an existing kind**? +3. Is this widening a **support dimension** for an existing category? + +If a proposal cannot be placed in one of those buckets, it is probably still +too vague to steer by. + +## Top-Level Kind Inventory + +| Kind | Structural status | Current category-map status | Rust V1 claim role | Biggest current gap | +| --- | --- | --- | --- | --- | +| `function` | Shipped | Strongest named map in the repo | Direct | Deferred categories are named, but the completion map above them is still thin | +| `data` | Shipped | Thin | Direct as plain seam | No broad category vocabulary beyond one known supported surface | +| `sum` | Shipped | Thin | Direct as plain seam | No broad category vocabulary beyond one known supported surface | +| future kinds | Unnamed | None | None | No checked-in authority for additional top-level kinds today | + +## What Is Actually Missing At The Top Level + +The repo does **not** currently have a checked-in list of "next kinds." + +That means the honest first-pass answer is: + +- the current owned kinds are `function`, `data`, and `sum` +- additional top-level kinds are not yet repo-owned enough to appear as real + roadmap targets +- the larger immediate blind spot is not "which fourth kind is missing?" +- the larger immediate blind spot is "which categories inside `data` and `sum` + are owned, which are merely hinted, and which support dimensions are still + absent?" + +So the project's map gap is currently more severe **inside kinds** than +**between kinds**. + +## Kind: `function` + +`kind:function` is the most mature map in the repo. + +It already has: + +- the richest semantic-review vocabulary +- the richest promotion and corpus-analysis machinery +- the clearest deferred edges +- the clearest distinction between direct claim-bearing families and helper-only + routes + +### Current repo-owned categories + +| Category | Current status | Public role | Notes | +| --- | --- | --- | --- | +| `function.arithmetic_leaf.monotone_down_nonnegative.v1` | Shipped | Direct | Canonical discount-like arithmetic leaf | +| `function.arithmetic_leaf.monotone_up.v1` | Shipped | Direct | Canonical tax-like arithmetic leaf | +| `function.wrapper.pipeline.v1` | Shipped | Direct | Straight-line two-call wrapper pipeline | +| `function.wrapper.pipeline.normalized_required_arg.v1` | Shipped | Direct | Narrow required-arg wrapper follow-on | +| `function.wrapper.pipeline.chain3.v1` | Shipped | Direct | Straight-line three-call wrapper pipeline | +| `function.helper.identity_passthrough.v1` | Shipped | Helper-only | Real supported route, but not separately named in the plain-English Rust V1 claim | +| `unsupported.function.v1` | Shipped terminal catch-all | No | Honest additive unsupported surface, not a supported family | + +### Explicitly deferred `function` categories + +These are already named in repo authority, which is good. The map knows they +exist, even though they are not shipped. + +| Deferred category pressure | Current status | Why it matters | +| --- | --- | --- | +| bounded generics for `kind:function` | Deferred to `V1.1` | This is a real authored-shape expansion, not a new top-level kind | +| async flows, runtime adapters, and IO-owned boundaries | Deferred to `V1.1` | This is a backend/runtime/proof expansion for function-shaped units | + +### Capability snapshot for `function` + +| Dimension | Current truth | Gap shape | +| --- | --- | --- | +| Core | Shipped | Main gaps are not legality; they are category and boundary growth | +| Rust | Strongest lane | Deferred edges are generics and async/IO | +| TypeScript | Bounded | Narrower than Rust and not a parity claim | +| Proof | Strong | Atom proof is first-class; molecule proof exists for interactions | +| Semantic review | Strongest area in repo | Still intentionally narrow, not general code understanding | +| Benchmark claim | Strong | Direct contributor to the benchmark-backed Rust V1 story | + +### What is still missing for `function` + +The missing map work for `function` is **not** "what is a function?" +The missing map work is: + +- an explicit completion matrix for every named function category across Rust, + TypeScript, proof, semantic review, and benchmark use +- a sharper distinction between categories that are merely supported and + categories that are central to the product claim +- a decision framework for when a deferred `V1.1` category is worth more than a + non-function wedge such as migration, recommendation trust, or workflow + completion + +## Kind: `data` + +`kind:data` is structurally real and publicly claim-bearing today, but its map +is much thinner than `kind:function`. + +Current structural truth is clear: + +- `data.fields` +- `constructors` +- `methods` +- no top-level `contract` +- no top-level `deps` +- no top-level `imports` +- top-level `body.rust` must stay empty +- no top-level `body.typescript` + +That means the authored shape is real. The missing piece is a broader category +vocabulary above that shape. + +### Current repo-owned categories + +| Category | Current status | Public role | Notes | +| --- | --- | --- | --- | +| `data.pricing_quote.v1` | Shipped, bounded | Direct as plain data seam | Specific descriptor-based supported data surface | + +### Capability snapshot for `data` + +| Dimension | Current truth | Gap shape | +| --- | --- | --- | +| Core | Shipped | The structural seam contract is real | +| Rust | Shipped | Part of the current narrow-core Rust V1 claim as a plain data seam | +| TypeScript | No | No seam-kind TypeScript execution today | +| Proof | Shipped | Can be benchmark-positive and proof-bearing today | +| Semantic review | Bounded and specific | Not a broad family system; currently specific supported data surfaces only | +| Benchmark claim | Direct | Plain data seams are part of the current Rust V1 claim | + +### What is still missing for `data` + +This is one of the biggest map holes in the repo. + +What is missing is not basic shape support. What is missing is a named category +taxonomy such as: + +- which data-surface descriptors are first-class and worth owning +- which are merely one-off example seams +- which should get descriptor-based semantic review keys +- which should count toward benchmark claims +- which should remain valid core seams without any semantic classification + +So the first-pass honest state for `data` is: + +- one known supported semantic surface exists +- the general category map for data seams is still mostly **Unnamed** + +## Kind: `sum` + +`kind:sum` is in the same overall shape as `data`: + +- structurally real +- publicly claim-bearing as a plain seam +- much thinner category map than `function` + +Current structural truth is clear: + +- `sum.variants` +- `methods` +- no top-level `contract` +- no top-level `deps` +- no top-level `imports` +- top-level `body.rust` must stay empty +- no top-level `body.typescript` + +### Current repo-owned categories + +| Category | Current status | Public role | Notes | +| --- | --- | --- | --- | +| `sum.discount_strategy.v1` | Shipped, bounded | Direct as plain sum seam | Specific descriptor-based supported sum surface | + +### Capability snapshot for `sum` + +| Dimension | Current truth | Gap shape | +| --- | --- | --- | +| Core | Shipped | The structural seam contract is real | +| Rust | Shipped | Part of the current narrow-core Rust V1 claim as a plain sum seam | +| TypeScript | No | No seam-kind TypeScript execution today | +| Proof | Shipped | Can participate in proof and benchmark-positive roots today | +| Semantic review | Bounded and specific | Not a broad family ecosystem; currently specific supported sum surfaces only | +| Benchmark claim | Direct | Plain sum seams are part of the current Rust V1 claim | + +### What is still missing for `sum` + +The missing map work mirrors `data`: + +- a named category vocabulary beyond one known supported surface +- a rule for which sum descriptors deserve semantic-review keys +- a rule for which sum surfaces are benchmark-relevant versus merely valid +- a progress view that distinguishes "valid sum seam" from "strategically + important sum seam" + +So the first-pass honest state for `sum` is: + +- one known supported semantic surface exists +- the general category map for sum seams is still mostly **Unnamed** + +## Cross-Kind Gaps + +Across all current kinds, the main missing map work is: + +1. **A real per-kind category registry** + `function` mostly has one. `data` and `sum` mostly do not. +2. **A per-category capability matrix** + Today the repo often knows a category exists, but not all the support + dimensions that make it feel "complete." +3. **A benchmark-eligibility map** + Especially important for keeping helper-only routes distinct from direct + claim-bearing categories. +4. **A completion scoreboard** + Something that answers: + - how many named categories exist in this kind? + - how many are shipped? + - how many are deferred? + - how many are still unnamed pressure? + +## What This Map Says About "What Next" + +This first-pass map suggests a sharper roadmap frame: + +- if the project wants to expand `kind:function`, it should say which category + or deferred edge is being widened +- if the project wants to make seam kinds feel complete, it probably does **not** + need a new top-level kind first +- it probably needs explicit category taxonomies for `data` and `sum` +- if the project wants a durable progress meter, the next artifact after this + one should be a machine-readable or table-driven category registry rather than + another broad roadmap essay + +## Immediate Follow-On Artifacts + +The next useful docs after this one would be: + +1. `docs/function_category_matrix_v0.1.md` + One row per function category, one column per support dimension. +2. `docs/data_category_taxonomy_v0.1.md` + Name the currently owned and still-unnamed `kind:data` categories. +3. `docs/sum_category_taxonomy_v0.1.md` + Name the currently owned and still-unnamed `kind:sum` categories. +4. A future machine-readable registry + So the repo can compute progress instead of reasoning from prose only. + +## How To Maintain This Map + +This map should change whenever repo truth changes in a way that affects: + +- top-level kinds +- named categories inside a kind +- support dimensions for an existing category +- public claim-bearing status for a category + +### Update triggers + +Update this document when any of these happen: + +1. A new top-level kind is added or explicitly proposed in checked-in authority. +2. A new per-kind category becomes repo-owned enough to name. +3. A category moves between `Unnamed`, `Deferred`, `Bounded`, and `Shipped`. +4. A backend lane expands or contracts for a category. +5. Semantic review gains or loses honest understanding for a category. +6. A benchmark or public product claim starts or stops depending on a category. +7. A milestone freezes a new boundary such as `V1.1` deferrals or admissions. + +### Source-of-truth order + +When updating this map, trust sources in this order: + +1. `README.md` +2. `docs/core_mechanisms_guide_v0.1.md` +3. `PLAN.md` +4. `DECISIONS.md` +5. `docs/rust_v1_contract_stack.md` +6. checked-in code and tests that prove a narrower or wider truth surface + +If a design draft says more than current authority docs or live code prove, +prefer the narrower current truth. + +### The update procedure + +When you update the map, walk this checklist in order: + +1. Identify the change type: + - new kind + - new category inside a kind + - widened support dimension + - narrowed support dimension + - public-claim status change +2. Identify the affected kind: + - `function` + - `data` + - `sum` + - or a newly owned top-level kind +3. Identify whether the change is structural or interpretive: + - structural means authored/core shape changed + - interpretive means backend, proof, semantic review, or benchmark meaning changed +4. Update the smallest correct row: + - the top-level kind inventory + - the kind's category table + - the kind's capability snapshot + - the cross-kind gaps or follow-on artifacts, if needed +5. Add or adjust prose only after the table-level truth is correct. + +### Classification rules + +Use these rules to avoid map drift: + +- Do **not** add a new kind just because a new function surface appears. +- Do **not** mark a category `Shipped` just because a draft doc names it. +- Do **not** mark a category `Direct` in public role unless current benchmark or + claim surfaces actually depend on it. +- Do **not** treat one example seam as a full category taxonomy for that kind. +- Do **not** collapse helper-only support into the same role as direct + claim-bearing support. + +### When to name a new category + +A category is ready to be named in this map when at least one of these is true: + +- current authority docs name it explicitly +- semantic review or routing code names it explicitly +- benchmark or claim surfaces depend on it explicitly +- the repo has frozen it as a deferred boundary that future work is expected to + cross honestly + +If none of those are true, leave the pressure as `Unnamed` rather than +inventing a category too early. + +### When to split this map into a more detailed artifact + +This file should stay high-signal and cross-kind. + +Create or expand a deeper follow-on doc when: + +- one kind has more than a handful of meaningful categories +- one kind needs per-category backend and proof detail +- one kind needs a machine-readable registry or scoreboard +- the maintenance burden of keeping detail here starts hiding the big picture + +That is why this doc points toward: + +- `docs/function_category_matrix_v0.1.md` +- `docs/data_category_taxonomy_v0.1.md` +- `docs/sum_category_taxonomy_v0.1.md` + +### Minimum review questions before merging an update + +Before landing a map change, ask: + +1. What changed in repo truth? +2. Is this a new kind, a new category, or a support-dimension change? +3. What evidence makes this true now? +4. Is the change direct claim-bearing, helper-only, bounded, deferred, or still + unnamed? +5. Did the update accidentally overstate support beyond current proof? + +If any answer is fuzzy, the map is probably being updated too early. + +## Bottom Line + +The repo does have a map now, but only in fragments. + +Today: + +- `function` has the strongest category map +- `data` and `sum` have real structural support but thin category vocabularies +- no additional top-level kinds are currently owned enough to steer by + +So the clearest current planning need is: + +> build the per-kind category maps to the point where the repo can measure +> completion honestly, instead of talking about support in one overloaded way diff --git a/docs/m26_implementation_plan_v0.1.md b/docs/m26_implementation_plan_v0.1.md deleted file mode 100644 index b10fcbf..0000000 --- a/docs/m26_implementation_plan_v0.1.md +++ /dev/null @@ -1,729 +0,0 @@ -# M26 - Approval-Gated AI Family Promotion Loop - -Status: **implementation plan** -Base branch: **main** -Working branch: **feat/m26** -Last rewritten: **2026-04-29** - -Source of truth for this plan: - -- repo code and docs in this checkout -- [PLAN.md](/Users/spensermcconnell/__Active_Code/atomize-hq/spec/PLAN.md) -- [docs/ai_promotion_and_multilanguage_milestones_v0.1.md](/Users/spensermcconnell/__Active_Code/atomize-hq/spec/docs/ai_promotion_and_multilanguage_milestones_v0.1.md) -- [docs/m26_approval_gated_ai_family_promotion_loop_design_v0.1.md](/Users/spensermcconnell/__Active_Code/atomize-hq/spec/docs/m26_approval_gated_ai_family_promotion_loop_design_v0.1.md) -- [docs/north_star_v0.2.md](/Users/spensermcconnell/__Active_Code/atomize-hq/spec/docs/north_star_v0.2.md) -- [docs/high_level_technical_architecture_v0.2.md](/Users/spensermcconnell/__Active_Code/atomize-hq/spec/docs/high_level_technical_architecture_v0.2.md) -- [docs/roadmap_and_release_shape_v0.1.md](/Users/spensermcconnell/__Active_Code/atomize-hq/spec/docs/roadmap_and_release_shape_v0.1.md) - -M21 through M24 proved the narrow Rust wedge first: the runtime semantic reviewer can distinguish -aligned truth, drift, under-specification, and unsupported near misses for real promoted function -families. - -M26 does not try to re-prove that thesis. M26 turns that proof into the right operator model. - -The causal chain is fixed: - -1. prove the intent-drift thesis in one narrow Rust wedge -2. make family promotion operable by AI under hard proof gates -3. use that promotion machinery to broaden per-language semantic review coverage -4. only then factor toward multi-language support - -If step 2 remains manual, steps 3 and 4 do not scale. - -## Problem Statement - -After M24, the repo has two kinds of truth: - -- runtime semantic-review truth in `spec-core` and read-side surfaces in `spec-cli` -- packetized promotion truth and proof gates in `xtask` plus `semantic-families/` - -What is still missing is the operator loop that joins those surfaces without falling back to hidden -human ceremony. - -Today, promotion still assumes a human can hold too much in their head: - -- which family to promote next -- whether that choice is justified by repo truth -- how to scaffold and curate the packet -- how to interpret `smoke`, `prove`, and `certify` failures -- when a failure is fixable versus when it is an honest blocker - -That is not the north-star operator model. The north-star model is slower, safer, -verify-as-it-builds AI work under explicit gates. - -M26 therefore locks the operator model: - -1. AI recommends the next family candidate from repo truth. -2. Human approves or rejects that candidate. -3. AI performs the full promotion loop under hard gates. -4. Human approves or rejects the final promoted output. - -Human approval is limited to the candidate family and the final output. The human does not rescue -the loop between those points. - -## Milestone Outcome - -When M26 is done, the repo can truthfully claim: - -- family promotion is operable by AI rather than only by a repo expert -- the hard gates remain `cargo xtask family smoke`, `prove`, and `certify` -- recommendation, execution, and blocker surfaces are durable machine-readable artifacts rather - than chat-only reasoning -- a human intervenes only at the two planned approval boundaries -- the promotion loop now exists at the right abstraction layer to unlock broader Rust family - coverage first and multi-language work later - -M26 does **not** claim: - -- the recommendation engine is globally optimal -- multi-language promotion is already solved -- non-function families are now in scope -- family authoring ceremony is fully minimized -- AI can invent new semantic families without repo-grounded seeds - -## Scope - -### In scope - -- lock the approval-gated operator model for family promotion -- add durable machine-readable artifacts for recommendation, execution, and blockers -- define a deterministic repo-truth input surface for the AI recommender -- keep `xtask` as the hard proof primitive layer -- place recommendation ranking, retry logic, approval checkpoints, and blocker synthesis in the - AI-operated workflow while keeping the deterministic proof kernel in `xtask` -- complete one real Rust family promotion through the approval-gated AI loop -- choose the first live proof target from current repo truth, not from speculation - -### NOT in scope - -- multi-language backend work -- new target-language lowering -- non-function family promotion for `sum` or `data` -- global coverage optimization across all future families -- maintainer UI or dashboard work -- generic long-horizon agent workflow infrastructure beyond what M26 directly needs -- replacing `xtask` proof commands with a new orchestration command family - -## Why This Milestone Now - -The narrow Rust proof came first on purpose. - -The repo thesis was never "AI should guess family semantics." The thesis was: - -- semantic review should detect intent drift honestly -- AI should operate inside validation, build, test, and evidence gates - -M21 through M24 proved the first half in one language and one bounded function subset. - -That changes the next bottleneck. The blocker is no longer semantic-review credibility. The blocker -is throughput. - -Without an approval-gated AI promotion loop: - -- broad Rust family coverage stays manual -- the set of promoted families grows too slowly to matter -- multi-language work arrives before the Rust workflow has learned how to scale - -So M26 is the bridge milestone. It turns a manually proved Rust wedge into an AI-operated family -promotion loop, which is the prerequisite for both wider Rust coverage and later multi-language -portability. - -## Premises - -1. Human approvals are limited to candidate selection and final output approval. -2. AI must do the recommendation, editing, iteration, and failure handling work between those - approvals. -3. `xtask` remains the deterministic proof primitive because it already owns scaffold, smoke, - prove, certify, and artifact writing for family packets. -4. Recommendation logic should consume repo truth; it should not live inside `xtask` as opaque - policy. -5. The first live M26 family proof should use a family class the repo already supports at runtime - but has not yet promoted through the packet workflow. -6. M26 proves the operator model, not global selection optimality. M27 can optimize ranking later. - -## What Already Exists - -| Area | Current truth | M26 reuse decision | -|---|---|---| -| North-star operator thesis | AI should work in a slower, safer, verify-as-it-builds loop | Reuse directly. M26 makes it concrete for family promotion. | -| Current promoted packet registry | `semantic-families/` currently promotes `function.wrapper.pipeline.chain3.v1`, `function.arithmetic_leaf.monotone_down_nonnegative.v1`, and `function.arithmetic_leaf.monotone_up.v1` | Reuse as the baseline packet and artifact contract. | -| Current runtime supported function routes | `spec-core/src/semantic_review.rs` routes `WrapperPipelineChain3`, `WrapperPipeline`, `ArithmeticLeafMonotoneDownNonnegative`, and `ArithmeticLeafMonotoneUp` in explicit order | Use this as recommendation input. Do not invent new runtime families in M26. | -| Current proof primitives | `cargo xtask family new`, `smoke`, `prove`, `certify` | Keep as the hard proof loop. Do not replace them. | -| Current proof artifacts | `prove.latest.json`, `attempt-*.json`, and `certification.report.json` under `.semantic-family-artifacts/semantic-families//` | Reuse as gate truth and reference them from M26 orchestration artifacts. | -| Current blocker vocabulary | unsupported-function reason codes and certify routing diagnostics already exist | Reuse as the first machine evidence for blocker classification. | -| Supported-but-unpromoted candidate | `function.wrapper.pipeline.v1` exists in runtime semantic review, is referenced in routing metadata, and has a canonical seed at `examples/ecommerce/units/pricing/calculate_total.unit.spec` | This is the recommended first live M26 proof family unless implementation truth disproves it. | -| Existing wedge evidence for that candidate | `spec-cli/tests/m14_regressions.rs` already has `calculate_total` drift, under-specified, and unsupported-near-miss rewrites | Reuse as the narrowest truthful first promotion case. | - -## Minimal Change Strategy - -M26 stays narrow on purpose. - -- Do not move semantic-family proof logic out of `xtask`. -- Do not put recommendation ranking or approval state into `spec-core`. -- Do not invent a multi-language kernel yet. -- Do not prove the AI loop on a brand-new semantic family. - -The minimum honest diff is: - -1. expose deterministic repo-truth inventory for recommendation input -2. add durable M26 orchestration artifacts -3. define the approval-gated command loop around existing `xtask` proof gates -4. prove the loop on one already-supported but unpromoted Rust family - -Anything broader is M27 or later. - -## Architecture / Dependency View - -```text -spec-core runtime semantic review - │ - ├── explicit supported routes - ├── unsupported reason codes - └── canonical seed behavior in examples/tests - │ - ▼ -deterministic repo-truth inventory surface - │ - ▼ -AI recommendation layer - │ - ├── writes recommendation packet - └── waits for human target approval - │ - ▼ -AI promotion loop - │ - ├── edits harness / packet / tests / docs for approved family - ├── runs cargo xtask family smoke - ├── runs cargo xtask family prove - ├── runs cargo xtask family certify - ├── reads prove/certify artifacts - └── repeats until green or blocked - │ - ├── green -> promotion execution report -> human final approval - └── blocked -> blocker report -``` - -### Primary module boundaries - -| Layer | Responsibility in M26 | Must not happen | -|---|---|---| -| `spec-core` | remain source of runtime family truth, supported routing order, and unsupported diagnostics | do not absorb AI recommendation or approval state | -| `spec-cli` | remain source of read-side truth-surface and corpus regressions | do not become the orchestration layer | -| `xtask/src/family/**` | keep deterministic family packet primitives: scaffold, smoke, prove, certify, report emission, pure repo-truth export, and artifact validation | do not embed ranking heuristics, human approvals, or LLM-specific policy | -| AI operator workflow | recommend candidate families, checkpoint approvals, run the edit-and-proof loop, interpret failures, and write M26 orchestration artifacts | do not reimplement `smoke`, `prove`, or `certify` logic inside repo code | -| `.semantic-family-artifacts/**` | hold machine-readable proof and orchestration artifacts | do not become authored source | - -## Locked Operator Contract - -M26 does not leave the operator model to implementer taste. - -### Human approvals - -- approval point 1: approve or reject the AI recommendation packet -- approval point 2: approve or reject the final promotion execution report - -No third approval is allowed for ordinary promotion work. - -### AI responsibilities - -- inspect repo truth -- rank candidate families -- emit a recommendation packet -- after approval, perform repo edits for the approved family -- run proof commands -- inspect machine-readable artifacts and command failures -- retry within hard gates -- emit either a promotion execution report or a blocker report - -### Hard boundary - -- `xtask` owns deterministic proof primitives, pure repo projections, and artifact validation -- the AI operator owns recommendation generation, approval gating, loop control, and blocker synthesis - -That boundary is fixed for M26. - -## Locked Artifact Contract - -M26 adds three orchestration artifacts. These are provisional schemas, but their role and paths are -locked by the plan. - -### Artifact locations - -- recommendation packet: - `.semantic-family-artifacts/family-promotion/recommendation.latest.json` -- promotion execution report: - `.semantic-family-artifacts/family-promotion///promotion.execution.json` -- blocker report: - `.semantic-family-artifacts/family-promotion///blocker.report.json` - -The execution and blocker artifacts must reference the existing `xtask` proof artifacts rather than -copying their payloads. - -### Recommendation packet - -Purpose: give the human a repo-grounded candidate choice before any promotion work starts. - -Provisional schema: - -```json -{ - "schema_version": 1, - "artifact_kind": "family_recommendation", - "generated_at": "2026-04-29T00:00:00Z", - "inventory_path": ".semantic-family-artifacts/family-promotion/inventory/20260429T120000Z-function.wrapper.pipeline.v1.json", - "inventory_sha256": "sha256-of-that-exact-snapshot", - "target_language": "rust", - "current_promoted_families": [ - "function.wrapper.pipeline.chain3.v1", - "function.arithmetic_leaf.monotone_down_nonnegative.v1", - "function.arithmetic_leaf.monotone_up.v1" - ], - "ranked_candidates": [ - { - "family": "function.wrapper.pipeline.v1", - "status": "recommended", - "why_now": "Supported in runtime semantic review but not yet promoted through the packet workflow.", - "evidence": [ - { - "kind": "runtime_supported_route", - "path": "spec-core/src/semantic_review.rs" - }, - { - "kind": "canonical_seed", - "path": "examples/ecommerce/units/pricing/calculate_total.unit.spec" - }, - { - "kind": "existing_wedge_regressions", - "path": "spec-cli/tests/m14_regressions.rs" - } - ], - "expected_leverage": { - "language": "rust", - "surface": "kind:function", - "topology": "two-dep wrapper pipeline" - }, - "expected_risks": [ - "routing overlap with function.wrapper.pipeline.chain3.v1", - "packet curation may reveal family boundary drift" - ] - } - ] -} -``` - -Locked rules: - -- recommendation is evidence-backed, not gut feel -- `ranked_candidates[0]` is the only approval target -- the packet must cite repo paths for every substantive claim -- `inventory_path` must point to the exact captured inventory snapshot used for ranking -- `inventory_sha256` must hash the exact bytes at `inventory_path` -- Gate 1 is a pre-edit approval over repo truth before approved-family edits begin -- Gate 1 approval remains valid only while a fresh inventory snapshot of the unchanged - pre-edit basis yields both the same `inventory_sha256` and the same - `ranked_candidates[0].family` -- the required recheck happens immediately before the first approved-family edit -- after the first approved-family edit lands, Gate 1 is no longer compared against live - post-edit inventory; from that point on, correctness is governed by the hard gates and - the post-promotion inventory expectations -- if either pre-edit basis check changes, the prior approval is stale; AI must halt, - write a fresh recommendation artifact, rerun validation, and wait for a new human approval - -### Promotion execution report - -Purpose: record the whole AI-operated run, the proof commands, the edits made, and the final gate -state for human approval. - -Provisional schema: - -```json -{ - "schema_version": 1, - "artifact_kind": "promotion_execution", - "run_id": "20260429T120000Z-function.wrapper.pipeline.v1", - "family": "function.wrapper.pipeline.v1", - "status": "green", - "recommendation_path": ".semantic-family-artifacts/family-promotion/recommendation.latest.json", - "approvals": { - "target_family": { - "status": "approved", - "approved_at": "2026-04-29T12:05:00Z" - }, - "final_output": { - "status": "pending" - } - }, - "repo_state": { - "git_commit_sha": "HEAD-at-start", - "rust_toolchain": "rustc --version" - }, - "files_changed": [ - "xtask/src/family/harness.rs", - "semantic-families/function.wrapper.pipeline.v1/family.toml" - ], - "commands": [ - { - "step": "smoke", - "command": "cargo xtask family smoke function.wrapper.pipeline.v1", - "exit_code": 0 - }, - { - "step": "prove", - "command": "cargo xtask family prove function.wrapper.pipeline.v1", - "exit_code": 0, - "artifact_path": ".semantic-family-artifacts/semantic-families/function.wrapper.pipeline.v1/prove.latest.json" - }, - { - "step": "certify", - "command": "cargo xtask family certify function.wrapper.pipeline.v1", - "exit_code": 0, - "artifact_path": ".semantic-family-artifacts/semantic-families/function.wrapper.pipeline.v1/certification.report.json" - } - ], - "iterations": 3, - "gate_summary": { - "smoke": "pass", - "prove": "pass", - "certify": "pass" - }, - "notes": [ - "AI resolved one packet-layout failure without extra human steering." - ] -} -``` - -Locked rules: - -- this is the final human approval surface -- it must reference every hard-gate command that ran -- it must list changed source surfaces -- it must distinguish green completion from blocked termination - -### Blocker report - -Purpose: terminate honestly when AI cannot complete promotion inside the hard gates without hidden -human rescue work. - -Provisional schema: - -```json -{ - "schema_version": 1, - "artifact_kind": "promotion_blocker", - "run_id": "20260429T120000Z-function.wrapper.pipeline.v1", - "family": "function.wrapper.pipeline.v1", - "blocking_step": "certify", - "blocker_kind": "routing_incoherence", - "summary": "Gate D failed because runtime routing and manifest routing disagree.", - "machine_evidence": [ - { - "command": "cargo xtask family certify function.wrapper.pipeline.v1", - "exit_code": 1, - "artifact_path": ".semantic-family-artifacts/semantic-families/function.wrapper.pipeline.v1/attempt-2026-04-29T12:40:00Z.json" - } - ], - "required_human_action": "Decide whether function.wrapper.pipeline.v1 should precede or follow chain3 in the promoted registry.", - "safe_next_actions": [ - "Keep prove/certify gates unchanged.", - "Resolve runtime/harness routing disagreement explicitly." - ], - "terminal": true -} -``` - -Locked rules: - -- a blocker is a first-class honest outcome in M26 -- blocker classification must cite machine evidence, not conversational opinion -- the report must tell the human exactly what decision or missing truth prevented completion - -## Locked `xtask` vs Operator Boundary - -M26 must answer where logic lives. - -### `xtask` should own - -- `family new`, `family smoke`, `family prove`, `family certify` -- packet layout validation -- manifest validation -- routing diagnostics -- existing proof artifact emission -- a new pure repo-truth export surface if needed, for example: - `cargo xtask family inventory --format json` - -Why: - -- these are deterministic repo-local computations -- they should be testable without AI -- they already define the hard proof contract - -### The AI operator should own - -- ranking candidate families from repo truth -- writing `recommendation.latest.json` -- waiting at approval boundaries -- making repo edits across harness, packet, tests, and docs -- deciding when to rerun fast inner-loop commands versus full proof gates -- synthesizing `promotion.execution.json` and `blocker.report.json` - -Why: - -- this layer is stateful and workflow-oriented -- it must encode approval checkpoints -- it should evolve without destabilizing the deterministic proof kernel or adding - a new workspace crate in M26 - -### Hard non-goals for `xtask` - -- no hidden LLM prompt logic -- no approval checkpoint persistence -- no ranking heuristics that choose the next family autonomously -- no `spec-orchestrator` workspace crate in M26 - -## First Live Proof-Family Selection - -M26 needs one real family promotion to prove the operator loop. That first target must be chosen -by repo truth, not by taste. - -### Selection criteria - -The first live M26 family should satisfy all of these: - -1. it is already recognized as a supported runtime semantic-review family or route -2. it has a canonical real seed unit in repo truth -3. it already has aligned, drift, under-specified, or unsupported-near-miss wedge material that - can be reused truthfully -4. it does not require new evaluator-scope invention -5. it broadens Rust coverage meaningfully rather than duplicating a direct sibling proof -6. it keeps M26 focused on workflow proof, not semantic-theory expansion - -### Recommended first live target - -The recommended first live M26 proof family is: - -- `function.wrapper.pipeline.v1` - -Reasoning, grounded in current repo truth: - -- `spec-core/src/semantic_review.rs` already contains the runtime compatibility key - `function.wrapper.pipeline.v1` and a `WrapperPipeline` route. -- the canonical seed already exists at - `examples/ecommerce/units/pricing/calculate_total.unit.spec`. -- `spec-cli/tests/m14_regressions.rs` already contains `calculate_total` drift, - under-specified, and unsupported-near-miss rewrite helpers. -- `spec-cli/tests/cli.rs`, `spec-core/src/passport.rs`, and `spec-core/src/export.rs` already - exercise `pricing/calculate_total` as `function.wrapper.pipeline.v1`. -- `function.wrapper.pipeline.chain3.v1` already declares `function.wrapper.pipeline.v1` in its - routing boundary, so promoting the two-dep wrapper family stabilizes an existing shadow line - rather than inventing a new one. - -This is a stronger first M26 target than another arithmetic leaf because it broadens function -topology coverage from single-leaf arithmetic to the reusable two-dep wrapper shape that the repo -already understands. - -If implementation truth later proves that `function.wrapper.pipeline.v1` is not promotion-ready, -the fallback rule is not "pick something convenient." The fallback rule is "choose the next -repo-supported but unpromoted family that still satisfies the criteria above." - -## Exact AI Operator Command Loop - -M26 should lock one exact command loop for the AI operator. - -### Phase A - inventory and recommendation - -Required machine step: - -```bash -cargo xtask family inventory --format json -``` - -AI then writes: - -- `.semantic-family-artifacts/family-promotion/recommendation.latest.json` - -The operator must also retain the exact inventory snapshot used for that -recommendation under: - -- `.semantic-family-artifacts/family-promotion/inventory/.json` - -Human then approves or rejects `ranked_candidates[0].family`. - -The operator must also record the pre-edit basis commit that produced that -snapshot. That commit is the only commit on which Gate 1 inventory equality may -be rechecked. - -### Phase B - promotion loop after approval - -For approved family ``, the AI loop is: - -```bash -cargo fmt --all -cargo test -p xtask -cargo xtask family smoke -cargo xtask family prove -cargo xtask family certify -``` - -Fast inner-loop commands are allowed between hard-gate runs, but they do not replace the hard -gates. The AI may also run targeted suites while iterating, for example: - -```bash -cargo test -p spec-core --lib -cargo test -p spec-cli --test m14_regressions -``` - -Loop rule: - -1. read the approved recommendation artifact -2. before the first approved-family edit, rerun inventory on the unchanged pre-edit basis - and capture a fresh inventory snapshot -3. compare that fresh snapshot against the approved Gate 1 basis: - - `inventory_sha256` - - `ranked_candidates[0].family` -4. if either pre-edit Gate 1 basis check changed, stop and reopen Gate 1 with a fresh - recommendation artifact -5. edit repo truth for the approved family -6. run the fast inner loop if useful -7. rerun `smoke` -8. rerun `prove` -9. rerun `certify` -10. if green, write the execution report -11. if blocked, write the blocker report - -The human does not steer any of those retries. -The live post-edit repo state is not required to match the original Gate 1 inventory -snapshot, because approved-family edits are expected to change inventory-visible truth. - -### Phase C - final approval - -When all hard gates are green, AI writes: - -- `.semantic-family-artifacts/family-promotion///promotion.execution.json` - -Human then approves or rejects the final output using that report. - -## Implementation Sequence - -1. Lock the artifact and operator contract in code-facing docs and tests. - Add the M26 artifact shapes, approval states, and command-loop expectations as executable - invariants where possible. - -2. Add deterministic repo-truth inventory export. - This belongs in `xtask` because it is a pure projection of current registry, runtime supported - routes, promoted packets, and seed evidence paths. - -3. Add path-aware artifact validation in `xtask`. - Keep runtime approval gating anchored on repo-owned schema and path checks rather than - shell-only convention. - -4. Lock the wrapper family contract and scaffold in `xtask`. - Freeze suite slug, starter paths, per-bucket semantics, and the unsupported-near-miss - boundary before parallel lane work begins. - -5. Capture the Gate 1 recommendation basis. - Emit `recommendation.latest.json`, retain the exact inventory snapshot under - `.semantic-family-artifacts/family-promotion/inventory/.json`, and require - `inventory_path` plus `inventory_sha256` in the approval artifact. - -6. Launch the three bounded post-contract lanes. - Packet curation, runtime proof, and CLI truth-surface/regression work may proceed in - parallel only after the wrapper-family contract is frozen. - -7. Integrate the bounded lanes and run the hard gates. - If a lane discovers a mismatch against the frozen wrapper-family contract, stop lane-local - reconciliation and return to the serialized owner flow before proceeding. - -8. Prove the loop on `function.wrapper.pipeline.v1`. - Use `pricing/calculate_total` as the canonical seed and the existing `calculate_total` wedge - rewrites as the initial packet truth. - -9. Run the full approval-gated loop end to end. - Require the actual two approvals, machine-written artifacts, and green hard gates before - claiming success. - -10. Document only repo truth that is green. - Do not claim AI-operated promotion has landed until the first real family promotion and its - artifacts exist. - -## Workstream Coordination - -The implementation plan follows the serialized-plus-bounded-parallel shape from `PLAN.md`. - -### Coordination rules - -- The primary checkout at the live `feat/m26` path is the canonical run-state root for M26. -- All `.runs/m26/*` and `.semantic-family-artifacts/family-promotion/**` writes are owned by the - parent agent and resolved against that primary checkout root, not against worker-local - worktree relative paths. -- Worker worktrees may read frozen prompts or summaries, but they do not become independent - sources of truth for approvals, sentinels, or promotion artifacts. -- Lane A is the serialization point. It owns inventory export, artifact schemas, wrapper harness - registration, scaffold shape, and the 12 starter semantics. -- Lane A freezes the wrapper-family contract before any parallel work begins: - - suite slug - - packet file names and starter paths - - per-bucket starter semantics - - unsupported-near-miss boundary -- Lane A must be merged back onto `feat/m26`, and the resulting commit must be recorded as the - `contract_freeze_commit` before any worker branch is created. -- Lane B owns only the committed wrapper packet under - `semantic-families/function.wrapper.pipeline.v1/`. -- Lane C owns only runtime classifier proof and route-order assertions in - `spec-core/src/semantic_review.rs`. -- Lane D owns only CLI truth-surface, corpus, and regression proof in - `spec-cli/tests/cli.rs` and `spec-cli/tests/m14_regressions.rs`. -- Lanes B, C, and D must all fork from the exact recorded `contract_freeze_commit`. -- Lane C may add proof for the frozen contract, but it may not redefine family semantics, - starter paths, or the unsupported-near-miss boundary. -- Lanes B and D must consume the frozen Lane A contract literally. They do not invent or - reinterpret wrapper-family semantics independently. -- If any parallel lane discovers that runtime truth disagrees with the frozen contract, stop - lane-local reconciliation, return to the serialized owner flow, update the contract explicitly, - and relaunch affected lanes as needed. - -## Acceptance Gates - -M26 is done only when all of the following are true: - -- the operator model is explicit and enforced: human approves target family and final output only -- the recommendation packet exists and cites repo-path evidence for the chosen family -- the recommendation artifact encodes a machine-checkable Gate 1 basis through - `inventory_path` and `inventory_sha256` -- Gate 1 approval is reopened automatically when the unchanged pre-edit basis snapshot or top - candidate no longer matches the approved recommendation basis -- `xtask` remains the hard proof kernel for `smoke`, `prove`, and `certify` -- one real Rust family promotion completes through that loop -- the first live proof target is selected from repo truth, not ad hoc judgment -- the final execution report references the actual `prove.latest.json` and - `certification.report.json` artifacts -- if the loop cannot finish honestly, it emits a blocker report instead of silently requiring - manual rescue -- the workspace boundary remains exactly `spec-core`, `spec-cli`, and `xtask` -- the bounded parallel lanes consume one frozen wrapper-family contract instead of negotiating - semantics independently -- the packet, runtime, and CLI workers all start from one recorded `contract_freeze_commit` -- the M26 implementation leaves the system better positioned for broad Rust family expansion and - later multi-language work - -## Follow-ups Explicitly Deferred - -- M27 coverage accounting and ranking optimization -- language-agnostic family-kernel extraction -- second-language promotion work -- non-function family promotion -- maintainer UI for approvals and artifact browsing -- continuous autonomous background promotion queues - -## Unresolved Risks - -- The current repo has strong proof primitives but no shipped inventory export yet. If that export - is underspecified, the recommendation layer will stay too chat-dependent. -- `function.wrapper.pipeline.v1` is the best current first target from repo truth, but promoting it - may expose real routing pressure against `chain3` that must be resolved explicitly. -- Existing `xtask` reports are machine-readable, but some failure causes still arrive as stderr - strings. M26 may need sharper structured diagnostics to avoid AI thrash. -- Packet curation may still hide too much maintainer judgment in `candidate.md` unless the blocker - vocabulary is crisp. -- If orchestration artifacts drift away from `xtask` proof artifacts, the system could develop two - competing truths. M26 must keep the proof kernel authoritative. diff --git a/docs/m52_bounded_same_tree_wrapper_typescript_execution_design_v0.1.md b/docs/m52_bounded_same_tree_wrapper_typescript_execution_design_v0.1.md deleted file mode 100644 index 9832e9f..0000000 --- a/docs/m52_bounded_same_tree_wrapper_typescript_execution_design_v0.1.md +++ /dev/null @@ -1,371 +0,0 @@ -# Design: M52 Bounded Same-Tree Wrapper TypeScript Execution in `spec` - -Generated by `/autoplan` on 2026-05-12 -Branch: `feat/m40-plus` -Repo: `atomize-hq/spec` -Status: `DRAFT_READY_FOR_REVIEW` -Builds on: -- [`docs/ai_promotion_and_multilanguage_milestones_v0.1.md`](/Users/spensermcconnell/__Active_Code/atomize-hq/spec/docs/ai_promotion_and_multilanguage_milestones_v0.1.md) -- [`docs/semantic_family_capability_corpus_guide_v0.1.md`](/Users/spensermcconnell/__Active_Code/atomize-hq/spec/docs/semantic_family_capability_corpus_guide_v0.1.md) -- [`TODOS.md`](/Users/spensermcconnell/__Active_Code/atomize-hq/spec/TODOS.md) -- [`README.md`](/Users/spensermcconnell/__Active_Code/atomize-hq/spec/README.md) -- [`CHANGELOG.md`](/Users/spensermcconnell/__Active_Code/atomize-hq/spec/CHANGELOG.md) -- [`/Users/spensermcconnell/.gstack/projects/atomize-hq-spec/checkpoints/20260506-181701-semantic-review-milestone-reset.md`](/Users/spensermcconnell/.gstack/projects/atomize-hq-spec/checkpoints/20260506-181701-semantic-review-milestone-reset.md) -- [`/Users/spensermcconnell/.gstack/projects/atomize-hq-spec/spensermcconnell-feat-m40-plus-design-20260510-111915.md`](/Users/spensermcconnell/.gstack/projects/atomize-hq-spec/spensermcconnell-feat-m40-plus-design-20260510-111915.md) -- [`/Users/spensermcconnell/.gstack/projects/atomize-hq-spec/spensermcconnell-feat-m40-plus-design-20260510-215928.md`](/Users/spensermcconnell/.gstack/projects/atomize-hq-spec/spensermcconnell-feat-m40-plus-design-20260510-215928.md) - -## Problem Statement - -M46 made TypeScript execution real in one narrow lane. - -That was the right first cut. `spec generate/build/test --target-language typescript` now exists, but the lane stops at monotone-up leaves plus at most one local helper dep. The semantic reviewer already understands more than that. It can classify supported two-step wrapper pipelines and chain3 pipelines with same-tree local context, but the executor cannot run that shape. - -That mismatch is the next product truth bug. - -Today the repo can honestly say: - -- TypeScript execution exists for one bounded family-shaped slice. -- semantic review understands wrapper pipelines in Rust and read-side truth. - -It cannot yet honestly say: - -- `spec` can execute the canonical wrapper family in TypeScript inside the same loaded tree. - -That is the next lake. - -## Demand Evidence - -Live repo truth on `feat/m40-plus` on 2026-05-12: - -- `cargo xtask family recommend --format json` reports: - - `recommendation_status = "insufficient_real_corpus"` - - `decision_summary.decision_status = "not_recommended"` -- `cargo xtask family corpus-decision --format json` reports: - - `decision_action = "stop"` - - `required_next_action = "record_stop_without_new_milestone"` -- `cargo run -p spec-cli -- test examples/ecommerce/units/pricing/apply_tax.unit.spec --target-language typescript` passes. The bounded monotone-up lane is real. -- `cargo run -p spec-cli -- test examples/ecommerce/units/pricing/calculate_total.unit.spec --target-language typescript` fails during generation with: - - `unit 'pricing/apply_discount' is not eligible for the bounded M46 TypeScript lane: body.typescript is required` - -Code truth matches that live behavior: - -- [`spec-core/src/typescript_backend.rs`](/Users/spensermcconnell/__Active_Code/atomize-hq/spec/spec-core/src/typescript_backend.rs) hard-codes the M46 executor to: - - `kind:function` - - compatibility key `function.arithmetic_leaf.monotone_up.v1` - - `deps: []` or exactly one direct local helper dep -- [`spec-core/src/validator.rs`](/Users/spensermcconnell/__Active_Code/atomize-hq/spec/spec-core/src/validator.rs) enforces the same M46 gate with stable TypeScript-specific errors. -- [`spec-core/src/semantic_review.rs`](/Users/spensermcconnell/__Active_Code/atomize-hq/spec/spec-core/src/semantic_review.rs) already supports: - - `function.wrapper.pipeline.v1` - - `function.wrapper.pipeline.chain3.v1` - - same-tree contextual dep resolution for those families -- [`semantic-families/function.wrapper.pipeline.v1/candidate.md`](/Users/spensermcconnell/__Active_Code/atomize-hq/spec/semantic-families/function.wrapper.pipeline.v1/candidate.md) defines the supported wrapper packet as one straight-line two-call wrapper over two supported local deps. -- [`TODOS.md`](/Users/spensermcconnell/__Active_Code/atomize-hq/spec/TODOS.md) already records the deferred next wedge: - - wrapper TypeScript execution in `spec` - - explicit non-goals for cross-library helper imports - - explicit non-goals for generic multi-dependency execution - -That is enough evidence. The next missing artifact is not another family-choice loop. It is the post-M46 execution contract for same-tree wrappers. - -## Status Quo - -What the repo already has: - -- first-class TypeScript CLI entry points in `spec generate`, `spec build`, `spec test`, and `spec status` -- one repo-owned toolchain contract: - - Bun build - - Bun execution - - generated runtime helpers under `__spec_ts/` -- target-specific TypeScript passport proof surfaces -- semantic review support for monotone-up, monotone-down, helper passthrough, wrapper pipeline, and chain3 pipeline shapes - -What the repo still does not have: - -- TypeScript execution for `function.wrapper.pipeline.v1` -- same-tree closure generation for wrapper dep sets when those deps are not monotone-up targets -- a clear execution rule for "supported wrapper family and its local dep closure" distinct from "arbitrary multi-dep TypeScript" -- canonical wrapper fixtures and ecommerce units with authored TypeScript bodies on the full execution closure - -## Target User And Narrowest Wedge - -Target user: - -- the maintainer or AI operator who already trusts the current semantic family boundaries and wants the first honest TypeScript wrapper execution path in `spec` - -Narrowest wedge: - -- first-class TypeScript execution for `function.wrapper.pipeline.v1` -- only when the wrapper and every direct dep live in the same loaded unit set and generated tree -- only when those direct deps already classify to the supported local family shapes the wrapper packet expects -- no cross-library resolution -- no molecule execution -- no chain3 execution -- no generic "multiple deps are fine now" claim - -This is not generic multi-dependency TypeScript support. - -It is one bounded family-shaped dependency closure: - -- two-step wrapper pipeline -- same-tree local dep closure -- existing Bun runtime -- existing passport/status proof surfaces - -## Premises - -1. The checkpoint is still right. Semantic review is core product. Family-analysis governance is servant work. -2. The current family recommendation surface still says stop, so another Rust family-choice milestone would be fake motion. -3. The repo already earned a bounded TypeScript executor. The next honest expansion is family-shaped execution breadth, not more TypeScript metadata. -4. The wrapper packet is the right next slice because it exercises local dependency closure without opening cross-library or generic arbitrary-dep scope. -5. The milestone should expand executor truth, not rewrite shared-core architecture again. - -## Approaches Considered - -### Approach A: generic TypeScript multi-dep execution now - -Pros: - -- sounds like broad progress - -Cons: - -- blows past the family boundary the semantic reviewer already proved -- collapses "same-tree wrapper execution" into "arbitrary dependency topology" -- would force new policy for cross-library resolution, deep closure semantics, and unsupported dep combinations - -Verdict: reject - -### Approach B: same-tree wrapper pipeline execution only - -Pros: - -- matches the deferred TODO exactly -- reuses an already-supported semantic family -- exercises the missing local closure behavior without widening to arbitrary graphs -- keeps validation, generation, build, test, and proof all family-shaped - -Cons: - -- requires explicit authored TypeScript bodies on the wrapper closure fixtures -- needs the executor to distinguish "dep closure for a supported wrapper family" from the current monotone-up-only tree assumption - -Verdict: choose - -### Approach C: chain3 directly - -Pros: - -- more impressive - -Cons: - -- chain3 is a larger closure and a worse first execution widening -- would blur the difference between "supported wrapper family" and "all supported wrapper-like families" - -Verdict: reject - -## Recommended Approach - -Choose Approach B. - -M52 should make `spec` execute exactly one new TypeScript family-shaped wedge: - -- root target compatibility key `function.wrapper.pipeline.v1` -- exactly two direct local deps -- both deps must already resolve inside the same loaded unit set -- both deps must already classify to the supported family shapes the wrapper packet expects -- generated output includes the wrapper and its same-tree dep closure -- build/test/status evidence stays target-specific and additive - -The important design rule is this: - -The widened executor must still speak in family language, not raw dep-count language. - -If the code simply changes from "0 or 1 dep" to "up to N deps," the milestone failed. - -## Architecture Direction - -```text -CURRENT M46 - TS target root must be monotone_up leaf - -> optional one local helper dep - -> generate TS tree - -> bun build - -> bun local tests - -M52 TARGET - TS target root may be wrapper.pipeline.v1 - -> exactly two same-tree local supported deps - -> include wrapper + dep closure in TS tree - -> wrapper body executes against generated local dep modules - -> bun build - -> bun local tests - -> target proofs written to wrapper and closure members as appropriate - -STILL OUT - cross-library dep resolution - arbitrary multi-dep execution - chain3 execution - molecule tests - seam kinds -``` - -## What Already Exists - -| Sub-problem | Existing owner | Reuse verdict | -| --- | --- | --- | -| TS CLI entry points | [`spec-cli/src/commands.rs`](/Users/spensermcconnell/__Active_Code/atomize-hq/spec/spec-cli/src/commands.rs) | reuse | -| TS runtime and generated helper surface | [`spec-core/src/typescript_backend.rs`](/Users/spensermcconnell/__Active_Code/atomize-hq/spec/spec-core/src/typescript_backend.rs) | reuse, extend narrowly | -| target-specific proof storage | [`spec-core/src/passport.rs`](/Users/spensermcconnell/__Active_Code/atomize-hq/spec/spec-core/src/passport.rs) | reuse | -| supported wrapper family semantics | [`spec-core/src/semantic_review.rs`](/Users/spensermcconnell/__Active_Code/atomize-hq/spec/spec-core/src/semantic_review.rs) | reuse as authority | -| wrapper packet truth | [`semantic-families/function.wrapper.pipeline.v1/`](/Users/spensermcconnell/__Active_Code/atomize-hq/spec/semantic-families/function.wrapper.pipeline.v1/) | reuse, add TS execution fixtures as needed | -| canonical wrapper example | [`examples/ecommerce/units/pricing/calculate_total.unit.spec`](/Users/spensermcconnell/__Active_Code/atomize-hq/spec/examples/ecommerce/units/pricing/calculate_total.unit.spec) | reuse, widen with authored TS closure | - -## Scope Decision - -M52 should authorize exactly this: - -1. admit `function.wrapper.pipeline.v1` as a TypeScript execution target in validator and generator code paths -2. require both direct wrapper deps to be local to the same loaded unit set and generated tree -3. require those deps to already classify to the supported local family shapes expected by the wrapper packet -4. generate the wrapper and the exact local dep closure needed for Bun build/test -5. add authored `body.typescript` to the canonical wrapper closure and maintained wrapper packet fixtures -6. extend CLI coverage and docs so the repo can prove the new contract directly - -M52 should not authorize: - -- cross-library TypeScript imports -- generic "2 or more deps are now supported" language -- chain3 execution -- molecule TypeScript support -- new `spec validate --target-language` surface -- `spec export --target-language` -- seam-kind target execution -- new shared-core extraction or architecture cleanup - -## Proposed Execution Contract - -### Validator contract - -TypeScript target eligibility should branch by supported compatibility key: - -- `function.arithmetic_leaf.monotone_up.v1` - - keep current M46 rule -- `function.wrapper.pipeline.v1` - - require exactly two direct deps - - both deps must be local, same-tree, and present in the loaded unit set - - both deps must already classify to the supported family shapes the wrapper packet expects - - both deps must author `body.typescript` - - wrapper local tests must stay within one bounded translated grammar - -Important: - -The validator should reject wrapper roots whose deps are merely "supported somehow." The contract needs exact allowed family combinations, not a vague supported-set check. - -### Generator contract - -The TypeScript tree generator should stop treating every included spec as if it must itself be a monotone-up root. - -Instead it should: - -- validate the target closure from the requested root set -- include all required local modules for that closure -- allow closure members whose role is "supported dep of a supported wrapper target" -- preserve the existing frozen helper/runtime filenames - -That is the key product change. Right now same-tree wrapper execution fails before Bun because the dep closure is validated under the wrong role. - -### Build and test contract - -Keep the current toolchain: - -- `bun build` for compile proof -- `bun run` for local test execution - -Do not add: - -- `npm` -- `package.json` -- `tsconfig.json` -- alternate runtime selection - -### Proof contract - -Reuse current passport/status target-proof storage. - -No new schema is needed if the executor can now refresh `target_proofs.typescript` for: - -- wrapper root units -- their closure members when the run includes them - -The repo should continue to keep Rust proof and TypeScript proof distinct. - -## Failure Modes Registry - -| Failure mode | Why it matters | M52 response | -| --- | --- | --- | -| executor widens to arbitrary multi-dep graphs | turns one lake into an ocean | gate strictly on wrapper family compatibility key | -| wrapper dep is cross-library | silently spends a different milestone | reject with stable TypeScript validation error | -| wrapper dep exists locally but lacks authored TypeScript body | current live failure on `pricing/calculate_total` | require authored TS body on closure members | -| closure generation includes unsupported same-tree units | false "tree-wide TS support" claim | include only the exact validated closure | -| docs say multi-dep TS now works | user learns the wrong contract | document family-shaped scope explicitly | -| chain3 sneaks in because the semantic reviewer supports it | scope creep | explicit non-goal and route rejection in M52 | - -## Candidate File Surfaces - -Likely code scope: - -- [`spec-core/src/typescript_backend.rs`](/Users/spensermcconnell/__Active_Code/atomize-hq/spec/spec-core/src/typescript_backend.rs) -- [`spec-core/src/validator.rs`](/Users/spensermcconnell/__Active_Code/atomize-hq/spec/spec-core/src/validator.rs) -- [`spec-cli/src/commands.rs`](/Users/spensermcconnell/__Active_Code/atomize-hq/spec/spec-cli/src/commands.rs) -- [`spec-cli/tests/cli.rs`](/Users/spensermcconnell/__Active_Code/atomize-hq/spec/spec-cli/tests/cli.rs) - -Likely authored truth scope: - -- [`examples/ecommerce/units/pricing/apply_discount.unit.spec`](/Users/spensermcconnell/__Active_Code/atomize-hq/spec/examples/ecommerce/units/pricing/apply_discount.unit.spec) -- [`examples/ecommerce/units/pricing/apply_tax.unit.spec`](/Users/spensermcconnell/__Active_Code/atomize-hq/spec/examples/ecommerce/units/pricing/apply_tax.unit.spec) -- [`examples/ecommerce/units/pricing/calculate_total.unit.spec`](/Users/spensermcconnell/__Active_Code/atomize-hq/spec/examples/ecommerce/units/pricing/calculate_total.unit.spec) -- [`semantic-families/function.wrapper.pipeline.v1/**`](/Users/spensermcconnell/__Active_Code/atomize-hq/spec/semantic-families/function.wrapper.pipeline.v1) - -Likely doc scope: - -- [`README.md`](/Users/spensermcconnell/__Active_Code/atomize-hq/spec/README.md) -- [`CHANGELOG.md`](/Users/spensermcconnell/__Active_Code/atomize-hq/spec/CHANGELOG.md) -- [`TODOS.md`](/Users/spensermcconnell/__Active_Code/atomize-hq/spec/TODOS.md) - -## Test And Proof Floor - -Minimum proof floor: - -1. validator regressions for wrapper TypeScript target eligibility -2. generator regressions for wrapper closure tree emission -3. CLI end-to-end for: - - wrapper unit TypeScript success in canonical ecommerce - - wrapper packet TypeScript success in aligned fixture - - wrapper near-miss or malformed closure rejection -4. status/passport regression proving TypeScript target proof records refresh for the wrapper root - -Nice-to-have, not required for M52: - -- chain3 proof -- molecule proof -- cross-library proof - -## Success Criteria - -M52 is successful when: - -- `spec test --target-language typescript` passes for the canonical same-tree wrapper example -- the wrapper packet can prove an aligned TypeScript execution path inside `spec`, not only inside `xtask` -- the validator rejects cross-library or generic multi-dep widening with stable errors -- the generated TypeScript tree includes only the validated local closure -- docs can explain in one paragraph why wrapper same-tree execution is in, but chain3 and cross-library work are still out - -## What This Leaves For Later - -- chain3 TypeScript execution -- cross-library TypeScript dep resolution -- generic multi-dependency TypeScript execution -- molecule TypeScript execution -- seam-kind target execution - -That is fine. The point of M52 is not to finish TypeScript. The point is to make the next truthful step real and bounded. diff --git a/docs/north_star_v0.2.md b/docs/north_star_v0.2.md index 64123af..c5c0553 100644 --- a/docs/north_star_v0.2.md +++ b/docs/north_star_v0.2.md @@ -8,6 +8,23 @@ > [`README.md`](../README.md), [`CHANGELOG.md`](../CHANGELOG.md), [`PLAN.md`](../PLAN.md), and > [`DECISIONS.md`](../DECISIONS.md). +## Current Grounding + +This vision doc is still directionally right, but the shipped repo is narrower +and more concrete than the full ambition below. + +Today's repo truth is: + +- Rust-first and proof-first +- authored around `*.unit.spec`, `.test.spec`, and `.plan.spec` +- currently centered on `kind:function`, `kind:data`, and `kind:sum` +- explicit about atom and molecule proof today, not a broad "all test tiers are shipped" claim +- intentionally narrow in semantic-review support and benchmark-backed product claims + +Read the rest of this document as the long-term destination. Read the root +README and core mechanisms guide for what a contributor should actually do on a +live checkout today. + ## Purpose This document defines the long-term product vision for **spec**, a semantic-unit development system for authoring, validating, compiling, and evolving software as structured semantic records rather than as loosely related text files. diff --git a/docs/roadmap_and_release_shape_v0.1.md b/docs/roadmap_and_release_shape_v0.1.md index c06628e..31625c2 100644 --- a/docs/roadmap_and_release_shape_v0.1.md +++ b/docs/roadmap_and_release_shape_v0.1.md @@ -8,6 +8,20 @@ > [`README.md`](../README.md), [`CHANGELOG.md`](../CHANGELOG.md), [`PLAN.md`](../PLAN.md), and > [`DECISIONS.md`](../DECISIONS.md). +## Current Grounding + +This roadmap draft is still useful for the "why this sequencing existed" story, +but it is no longer the active milestone authority. + +Today: + +- shipped behavior is better captured by the root README, changelog, and active plan docs +- much of the M1-M5 shape described below has already landed in narrower, repo-specific form +- Rust-first proof loops, plan artifacts, benchmark-backed claim surfaces, and bounded second-language work are now concrete repo realities, not just roadmap aspirations + +Read this doc for sequencing logic and product intent. Read current authority +docs for what the repo actually promises on a live checkout. + ## Purpose This document translates the current **North Star vision** and **high-level technical architecture** into an execution sequence. diff --git a/docs/rust_v1_contract_stack.md b/docs/rust_v1_contract_stack.md index 8e45b57..c06403a 100644 --- a/docs/rust_v1_contract_stack.md +++ b/docs/rust_v1_contract_stack.md @@ -1,22 +1,31 @@ # Rust V1 Contract Stack -This document is the repo-facing index for the Rust V1 contract stack and the -frozen I3.5 command wall. +This document is the repo-facing index for the Rust V1 contract stack, the +frozen I3.5 command wall, and the active post-I6 implementation ladder. -It exists to answer two questions quickly: +It exists to answer three questions quickly: - which artifact owns which Rust V1 claim - which command surface is proof-authoritative versus diagnostic only +- where the current I-series wrap to V1 actually stands ## Current Authority -I3.5 freezes the public contract against these in-repo authority artifacts: +I3.5 still freezes the public command contract against these in-repo authority +artifacts: - `.runs/i3_5_authority_alignment/authority-plan.snapshot.md` - `.runs/i3_5_authority_alignment/phase2-freeze.json` -Those files are the maintained repo authority for the current alignment work. -This index deliberately avoids stale local-user planning paths. +Those files remain the maintained repo authority for command-wall semantics. + +The active post-I6 milestone authority now lives in: + +- [`PLAN.md`](../PLAN.md) for the current `I7` milestone definition +- [`ORCH_PLAN.md`](../ORCH_PLAN.md) for the current `I7` execution runbook + +This index deliberately avoids local-user planning paths and records only +checked-in repo authority. ## Historical Stack Ownership @@ -54,7 +63,8 @@ Owns: - benchmark roster and benchmark roles - writer-versus-reader boundaries - readability as an observation surface -- `BENCH-SERVICE` as the required service benchmark that stays within the frozen Rust V1 support boundary +- `BENCH-SERVICE` as the required service benchmark that stays within the + frozen Rust V1 support boundary ### M68: Mechanics-Landing Implementation Contract @@ -83,7 +93,12 @@ Use this routing rule before editing or extending any artifact in the stack: The current ladder is: -- `M65-M68 -> I3 -> I3.5 -> I4 -> I6` +- `M65-M68 -> I3 -> I3.5 -> I4 -> I6 -> I7 -> I8` + +There is now checked-in authority for `I7`. + +There are currently no checked-in authoritative `I9` docs. The active wrap path +to Rust V1 is `I7` then `I8`. ### I3: Benchmark Mechanics Baseline @@ -146,21 +161,81 @@ Primary outcome: Goal: -- activate `BENCH-SERVICE` as a real single-library proof wall without widening M66 support +- activate `BENCH-SERVICE` as a real single-library proof wall without widening + M66 support Frozen outcomes: -- `examples/service/units` is the benchmark root for the service activation slice -- `BENCH-SERVICE` stays frozen to the six authored service units and three required molecule proofs -- service-root `status` and `export` become proof-authoritative alongside the existing ecommerce wall -- `BENCH-SERVICE` closeout includes a current readability review and a stable committed snapshot +- `examples/service/units` is the benchmark root for the service activation + slice +- `BENCH-SERVICE` stays frozen to the six authored service units and three + required molecule proofs +- service-root `status` and `export` become proof-authoritative alongside the + existing ecommerce wall +- `BENCH-SERVICE` closeout includes a current readability review and a stable + committed snapshot Primary outcome: -- the service benchmark is now a shipped, benchmark-root Rust V1 proof surface instead of a reserved placeholder +- the service benchmark is now a shipped, benchmark-root Rust V1 proof surface + instead of a reserved placeholder + +### I7: Rust V1 Scope-Decision Closure + +Goal: + +- resolve the remaining post-I6 scope pressure around bounded generics and + async/IO, then ratify the honest Rust V1 line without widening support by + accident + +Frozen outcomes: + +- bounded generics explicitly defer to `V1.1` +- Rust V1 explicitly stays synchronous-only, so async/IO defer to `V1.1` +- `BENCH-CROSSLIB` remains the active companion negative-proof wall +- the post-I6 ladder stops implying a planning vacuum after the service + benchmark landed +- `I8` is defined as the final proof-run milestone instead of an inferred + follow-on, and its wall stays the existing five-command proof wall + +Primary outcome: + +- the repo can answer "what is still in Rust V1?" without guessing or relying + on untracked design notes + +### I8: Rust V1 Final Proof Run + +Goal: + +- run the final end-state proof wall against the explicit V1 contract ratified + by I7 + +Frozen outcomes: + +- one plain-English Rust V1 claim can be published honestly +- the positive and companion-negative benchmark walls still match the ratified + claim +- the proof wall remains: + - `cargo run -p spec-cli -- status examples/ecommerce/units --format json` + - `cargo run -p spec-cli -- export examples/ecommerce/units` + - `cargo run -p spec-cli -- status examples/service/units --format json` + - `cargo run -p spec-cli -- export examples/service/units` + - `cargo run -p spec-cli -- status . --format json` +- deferred `V1.1` surfaces are named explicitly instead of remaining ambient + pressure + +Primary outcome: + +- Rust V1 reaches a truthful repo-backed done state instead of a merely shipped + narrow-core checkpoint ## Repo Note -If you need the current execution truth, start with the authority snapshot and -freeze record under `.runs/i3_5_authority_alignment/`, then use this document -as the index that explains what each milestone owns. +If you need the command-wall truth, start with the authority snapshot and +freeze record under `.runs/i3_5_authority_alignment/`. + +If you need the active Rust V1 wrap plan, start with [`PLAN.md`](../PLAN.md) +and [`ORCH_PLAN.md`](../ORCH_PLAN.md). + +Use this document as the index that explains what each milestone owns and why +the current wrap path is `I7 -> I8`, not an inferred `I7 -> I8 -> I9`. diff --git a/docs/sum_category_taxonomy_v0.1.md b/docs/sum_category_taxonomy_v0.1.md new file mode 100644 index 0000000..2f47277 --- /dev/null +++ b/docs/sum_category_taxonomy_v0.1.md @@ -0,0 +1,274 @@ +# spec — Sum Category Taxonomy +**Version:** v0.1 +**Status:** Active working map +**Date:** 2026-05-24 + +## Purpose + +This document is the per-kind follow-on for `kind:sum`. + +Use it when the higher-level [kind coverage map](./kind_coverage_map_v0.1.md) +is not enough and you need to answer more specific questions such as: + +- what named sum categories does the repo actually own today? +- which parts of `kind:sum` are structural support versus descriptor support? +- what makes a seam qualify for `sum.discount_strategy.v1`? +- what is still unnamed pressure inside the sum kind? + +## Scope + +This document tracks the current **product/support surface** for `kind:sum`. + +It does **not** try to invent future sum categories just to make the map feel +complete. If the repo does not currently own a category strongly enough to name +it, this doc should say `Unnamed` rather than pretend the taxonomy is settled. + +## Reading Rules + +This taxonomy distinguishes four different things that are easy to blur: + +- **Structural legality** + Is a `kind:sum` seam valid authored `spec` source at all? +- **Named descriptor category** + Does the bounded semantic-review system recognize a specific sum shape? +- **Proof and benchmark role** + Does a sum seam merely validate, or does it carry benchmark-positive public + claim weight? +- **Unsupported observation** + Can the repo honestly say a seam is outside the owned descriptor subset? + +## Structural Baseline For `kind:sum` + +Every current `kind:sum` seam starts from the same structural contract: + +- authored variants live under `sum.variants` +- callable seam behavior lives under `methods` +- top-level `contract` is not authored +- top-level `deps` is not authored +- top-level `imports` is not authored +- top-level `body.rust` must stay empty +- top-level `body.typescript` is not authored today + +That means `kind:sum` already has a real authored shape even where it does not +yet have a broad category taxonomy. + +## Current Named Categories + +As of this doc's date, the repo owns exactly one supported named sum category +plus one explicit unsupported observation surface: + +- `sum.discount_strategy.v1` +- `unsupported.sum.v1` + +That is the main truth to internalize: `kind:sum` is real, but its named +descriptor vocabulary is still intentionally narrow. + +## Sum Category Matrix + +| Category | Semantic route | Structural support | Rust lane | TypeScript lane | Atom proof | Molecule role | Benchmark role | Public role | Notes | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| `sum.discount_strategy.v1` | Supported descriptor | Shipped | Yes | No seam-kind execution lane | Yes | Direct and flow-bearing | Positive in `BENCH-ECOM` and `BENCH-SERVICE` | Direct as plain sum seam | Canonical discount-policy seam; current repo truth includes both ecommerce and service siblings under the same category | +| `unsupported.sum.v1` | Terminal unsupported surface | N/A as a supported category | N/A as a supported category | N/A as a supported category | N/A as a supported category | N/A | No positive credit | Observation only | Honest additive fallback when a sum seam misses the owned descriptor subset | + +## Owned Descriptor: `sum.discount_strategy.v1` + +`sum.discount_strategy.v1` is the only currently supported named sum category. + +It is descriptor-based rather than path-based. The repo treats both: + +- `pricing/discount_strategy` +- `billing/discount_strategy` + +as part of the same owned product surface. + +### Core required roles + +Across current repo truth, the owned category requires two semantic method +roles: + +- `discount_amount` +- `discounted_subtotal` + +Those required methods must: + +- use `shared_ref` receivers +- take exactly one decimal input named `subtotal` +- return decimal values + +That is the most stable part of the sum descriptor today. + +### Canonical authored shape in semantic-review code + +The current semantic-review detector and canonical tests are written around this +canonical variant vocabulary: + +- `none` +- `percentage` + - decimal field `rate` +- `fixed_amount` + - decimal field `amount` + +And the canonical aligned executable body expects: + +- the `none` arm to produce decimal zero +- the `percentage` arm to compute `subtotal * rate` +- the `fixed_amount` arm to cap the discount at the subtotal + +That is the narrowest exact detector-owned descriptor in checked-in +semantic-review code. + +### Important current-truth nuance + +Current benchmark and export surfaces also carry the service sibling +`billing/discount_strategy` under the same compatibility key even though its +authored vocabulary is: + +- `declined` +- `percentage` +- `fixed_credit` + +So the honest repo-level statement today is: + +- the canonical semantic-review implementation is still phrased around the + `none` / `fixed_amount` vocabulary +- the broader checked-in product and benchmark surfaces already treat the + service sibling as part of the same owned `sum.discount_strategy.v1` category + +This is worth documenting explicitly so future cleanup can decide whether to: + +- normalize the detector wording to match both siblings +- or tighten the public surfaces back to the canonical descriptor + +## Execution and Proof Shape + +The canonical ecommerce and service examples show the intended support shape: + +- Rust lowering is first-class +- atom proof is first-class through `local_tests` +- molecule proof is first-class for multi-unit checkout flows +- benchmark-positive proof exists in both required positive walls + +For `kind:sum`, the molecule layer matters especially because the canonical +seam story is not just “can the enum lower?” but “does the lowered policy stay +aligned with the broader pricing or service flow?” + +## Helper and Example Methods + +The owned descriptor is stricter about required semantic roles than it is about +extra helper/example methods. + +That means additive helper or example methods may coexist so long as the seam +still preserves the required discount-strategy roles and authored/executable +meaning. What the repo does **not** currently own is a separately named +helper-only sum category. + +## Unsupported Observation: `unsupported.sum.v1` + +The repo also owns an honest unsupported observation surface for `kind:sum`. + +This matters because it lets the system say: + +- this is a valid `kind:sum` seam structurally +- but it is outside the narrow owned descriptor subset + +The canonical near-miss example today is renaming descriptor vocabulary, such +as changing `percentage` to `percent`. That does **not** produce a new +supported category. It routes to `unsupported.sum.v1`. + +So the current taxonomy boundary is: + +- valid `kind:sum` seam support is broader than one descriptor +- supported semantic-category ownership is not + +## Benchmark And Claim Role + +`sum.discount_strategy.v1` is not just structurally supported. It is part of +the current positive benchmark-backed product claim: + +- `pricing/discount_strategy` is a positive case in `BENCH-ECOM` +- `billing/discount_strategy` is a positive case in `BENCH-SERVICE` + +That makes the current sum category map different from a purely exploratory +taxonomy. The one owned category already carries real public-claim weight. + +`unsupported.sum.v1` carries no such credit. It is observation-only. + +## TypeScript Boundary + +`kind:sum` currently has **no seam-kind TypeScript execution lane**. + +That boundary should be stated narrowly: + +- the repo can reason about supported sum seams in bounded semantic context +- that does **not** mean sum seams execute in the shipped TypeScript lane +- current TypeScript execution support remains function-only + +So any future `kind:sum` TypeScript work would be a **support-dimension +expansion**, not evidence that more sum categories already exist today. + +## What Is Still Unnamed For `kind:sum` + +This is still one of the largest open map holes in the repo. + +The current unnamed pressure includes at least: + +- policy-like sum seams that keep the same role pattern but rename variant or + field vocabulary +- sum seams with more than three variants +- sum seams with the same structural shape but different public-role ambition +- sum seams that are valid structural seams but have no benchmark or semantic + category ambition +- any future seam kinds that want TypeScript execution rather than Rust-only + seam lowering + +The important constraint is that none of those are repo-owned named categories +yet. They are pressure, not shipped taxonomy. + +## What This Taxonomy Clarifies + +This document makes a few important truths visible: + +1. `kind:sum` is already real and claim-bearing even though its named category + map is thin. +2. The current sum taxonomy is descriptor-based, not namespace-based. +3. The repo owns one benchmark-positive supported descriptor today, not a broad + family of sum seams. +4. Unsupported sum review is explicit, which is healthier than silently + stretching `sum.discount_strategy.v1` to fit near-misses. +5. There is a current repo-truth split between the canonical semantic-review + wording and the broader benchmark/export product surface for the service + sibling. + +## How To Update This Taxonomy + +When `kind:sum` truth changes, update this document after checking: + +1. `docs/kind_coverage_map_v0.1.md` +2. `README.md` +3. `docs/core_mechanisms_guide_v0.1.md` +4. `PLAN.md` +5. `DECISIONS.md` +6. `benchmarks/labels.json` +7. current semantic review, validator, CLI, and example-root truth + +Update the smallest correct thing: + +- add or edit a row in the matrix +- tighten the descriptor requirements +- widen or narrow the TypeScript boundary +- clarify which surfaces are benchmark-positive versus observation-only +- resolve or document any split between canonical detector wording and broader + product surfaces + +Do **not** name a new sum category just because several seams “feel similar.” +Wait until the repo has: + +- explicit checked-in routing truth +- clear authored and executable role requirements +- a stable compatibility key + +## Likely Next Follow-On + +After this doc, the next natural follow-on is probably a machine-readable +kind/category registry so the repo can compute coverage status instead of +reconstructing it from prose docs. diff --git a/examples/ecommerce/README.md b/examples/ecommerce/README.md index 80661e3..34e81f5 100644 --- a/examples/ecommerce/README.md +++ b/examples/ecommerce/README.md @@ -1,116 +1,187 @@ # ecommerce example -This example shows a small pricing domain authored as `.unit.spec` files, plus molecule tests, a checked-in plan artifact, and the canonical migration wedge for `pricing/discount_strategy`. -The canonical in-repo copy also ships tracked molecule evidence for the pricing molecule tests so the benchmark-root proof wall stays truthful on a fresh clone: +This is the canonical single-library `spec` example in the repo. + +If the root [`README.md`](../../README.md) tells you what `spec` is, this doc +shows you one truthful place to touch it. Use this example when you want to: + +- run the first real validate/build/test/status loop +- inspect both `kind:function` and seam behavior in one library +- see where generated Rust, passports, and molecule evidence actually land +- compare a migrated seam against the pre-`spec` raw Rust baseline + +For the broader mental model, read +[`docs/core_mechanisms_guide_v0.1.md`](../../docs/core_mechanisms_guide_v0.1.md) +next to this file, not instead of it. + +## Fastest First Run + +From the repo root: ```bash +cargo run -p spec-cli -- validate examples/ecommerce/units/pricing/apply_tax.unit.spec --format json +cargo run -p spec-cli -- build examples/ecommerce/units --output examples/ecommerce/src/generated +cargo run -p spec-cli -- test examples/ecommerce/units/pricing/apply_tax.unit.spec +cargo run -p spec-cli -- test examples/ecommerce/units/pricing/discount_strategy_checkout_flow.test.spec cargo run -p spec-cli -- status examples/ecommerce/units --format json ``` -## M13 migration wedge +What changes when you run that loop: -`pricing/discount_strategy` exists in two forms on purpose: +- generated Rust under `examples/ecommerce/src/generated/` +- unit proof in `examples/ecommerce/units/**/*.spec.passport.json` +- molecule proof in `examples/ecommerce/units/**/*.test.evidence.json` -- Raw Rust baseline: `src/raw_baseline/pricing/discount_strategy.rs` -- Migrated seam: `units/pricing/discount_strategy.unit.spec` +In this example, `src/generated/` is ephemeral and gitignored, but the proof +artifacts are tracked so the example stays truthful on a fresh clone. Re-running +`spec generate` on an unchanged tree should leave those tracked passports alone +instead of churning `generated_at`. -Both implement the same pricing job: choose one discount strategy, compute `discount_amount(subtotal)`, and expose `discounted_subtotal(subtotal)`. The hand-written enum shows the pre-`spec` baseline implementation of that branching behavior. The `kind: sum` seam shows the authored version that `spec build` lowers into generated Rust. +If you have `spec` installed locally, the shorter example-root form is: -The `units/pricing/discount_strategy_checkout_flow.test.spec` molecule test covers the `pricing/discount_strategy` `sum` seam together with the existing `pricing/pricing_quote` `data` seam and `pricing/apply_tax` function unit so the example proves a mixed-kind checkout flow, not just enum syntax. +```bash +cd examples/ecommerce +spec build units +spec test units/pricing/apply_tax.unit.spec +spec test units/pricing/discount_strategy_checkout_flow.test.spec +spec status units --format json +``` -The `pricing/pricing_quote` seam remains in place as a sibling example. +## What This Example Demonstrates -## M20 semantic review boundary +### 1. The canonical pricing function trio -The pricing trio demonstrates the bounded `kind:function` semantic-review families currently proved with unseen examples and stricter wrapper-flow checks: +These are the cleanest current `kind:function` examples: + +- `pricing/apply_discount` +- `pricing/apply_tax` +- `pricing/calculate_total` + +They are useful because they sit inside the shipped bounded semantic-review +vocabulary: - `pricing/apply_discount` proves `function.arithmetic_leaf.monotone_down_nonnegative.v1` - `pricing/apply_tax` proves `function.arithmetic_leaf.monotone_up.v1` - `pricing/calculate_total` proves `function.wrapper.pipeline.v1` -This is still a bounded support story, not generic function understanding, and M20 adds no new supported family. Unsupported near-miss wrappers stay keyed as `unsupported.function.v1`, additive-only, and health-neutral. +That is a narrow support story on purpose. It is not generic function +understanding. -M20 also makes unsupported-function truth explicit. The public fields are exactly `semantic_review.support_status`, `semantic_review.unsupported_reason_codes`, and `semantic_review.rewrite_hints`. New supported reviews write `support_status: supported`; unsupported function reviews write `support_status: unsupported`. Consumers should branch on `semantic_review.support_status == "unsupported"` rather than infer unsupported state from `verdict` or `evaluator_scope`, though legacy reviews without `support_status` still fall back to `evaluator_scope` plus `unsupported.*.v1` inference. +### 2. A `kind:data` seam -Only `spec test` refreshes semantic-review truth. `spec build`, `spec generate`, `spec status`, and `spec export` project stored truth only. Fresh unsupported function proof is preserved on read-side surfaces such as `spec status` and `spec export`; stale unsupported function proof is dropped there while the unit's freshness/stale health still reports normally. +`pricing/pricing_quote.unit.spec` is the clean seam example. -## Locked adversarial score table +It keeps shared data semantics, constructors, and methods in one authored unit, +then lowers into generated Rust. It is the easiest place to see how seam +support differs from function-family support. -Recorded calibration scores for the migration wedge candidates: +### 3. A `kind:sum` seam with a raw baseline comparison -- `pricing/discount_strategy`: `19` -- `pricing/pricing_quote`: `16` -- `pricing/discount_plus_tax`: `14` +`pricing/discount_strategy.unit.spec` is the canonical sum-seam migration +wedge. -Canonical wedge remains `pricing/discount_strategy`. +It exists side by side with the handwritten baseline at: -## Build / verify +- `src/raw_baseline/pricing/discount_strategy.rs` -This crate expects generated Rust to exist at `src/generated/` (gitignored). +The matching molecule proof, +`pricing/discount_strategy_checkout_flow.test.spec`, checks that the generated +sum seam still agrees with the baseline behavior while composing with the +current pricing flow. -If you have `spec` installed, the quickest end-to-end loop from `examples/ecommerce/` is: +### 4. Molecule tests as cross-unit proof -```bash -spec build units -spec test units/pricing/discount_strategy.unit.spec -spec test units/pricing/discount_strategy_checkout_flow.test.spec -spec status units --format json -spec export units -spec plan validate plans/refactors/checkout-tax-refactor.plan.spec --format json -``` +This example ships three molecule tests: -From the repo root: +- `pricing/checkout_flow.test.spec` +- `pricing/discount_plus_tax.test.spec` +- `pricing/discount_strategy_checkout_flow.test.spec` + +Use them when the behavior crosses unit boundaries. If a test needs more than +one unit import, it belongs here rather than in `local_tests`. + +### 5. A checked-in plan artifact + +The canonical example plan lives at: + +- `plans/refactors/checkout-tax-refactor.plan.spec` + +Use it when you want to inspect the current `.plan.spec` authoring shape +without mixing plan concepts into the normal unit proof loop. + +## Command Authority In This Example + +The proof-authoritative example root is: ```bash -cargo run -p spec-cli -- validate examples/ecommerce/units/pricing/discount_strategy.unit.spec --format json -cargo run -p spec-cli -- build examples/ecommerce/units --output examples/ecommerce/src/generated -cargo run -p spec-cli -- test examples/ecommerce/units/pricing/discount_strategy.unit.spec -cargo run -p spec-cli -- test examples/ecommerce/units/pricing/discount_strategy_checkout_flow.test.spec cargo run -p spec-cli -- status examples/ecommerce/units --format json cargo run -p spec-cli -- export examples/ecommerce/units ``` -Repo-root status remains available for broad inventory: +Repo-root status still exists: ```bash cargo run -p spec-cli -- status . --format json ``` -Treat that repo-root view as `inventory_only`, not as the proof-authoritative default. Repo-root export is intentionally unsupported for this workspace shape and should fail with `SPEC_UNSUPPORTED_SCOPE`. +Treat that repo-root view as broad inventory only. It is useful for seeing many +roots at once, but it is not the default green proof wall for this example. -If you want the shorter generate/check/test loop instead: +Repo-root export is intentionally unsupported for this workspace shape and +should fail with `SPEC_UNSUPPORTED_SCOPE`. -```bash -cargo run -p spec-cli -- generate examples/ecommerce/units --output examples/ecommerce/src/generated -cargo check --manifest-path examples/ecommerce/Cargo.toml -cargo test --manifest-path examples/ecommerce/Cargo.toml -``` +## Semantic Review Boundary -Or, if you have `spec` installed, from `examples/ecommerce/`: +Two rules matter here: -```bash -spec generate units --output src/generated -cargo check -cargo test -``` +1. Only `spec test` refreshes semantic-review truth. +2. Unsupported-function truth is explicit through: + - `semantic_review.support_status` + - `semantic_review.unsupported_reason_codes` + - `semantic_review.rewrite_hints` + +That means `spec build`, `spec generate`, `spec status`, and `spec export` only +project already-stored semantic-review truth. They do not create new truth. -Files: +## Files Worth Reading + +Source specs: - `units/money/round.unit.spec` - `units/pricing/apply_discount.unit.spec` - `units/pricing/apply_tax.unit.spec` - `units/pricing/calculate_total.unit.spec` -- `units/pricing/discount_strategy.unit.spec` -- `units/pricing/discount_strategy_checkout_flow.test.spec` +- `units/pricing/calculate_total_guarded_tax.unit.spec` - `units/pricing/pricing_quote.unit.spec` +- `units/pricing/discount_strategy.unit.spec` - `units/pricing/checkout_flow.test.spec` - `units/pricing/discount_plus_tax.test.spec` +- `units/pricing/discount_strategy_checkout_flow.test.spec` - `plans/refactors/checkout-tax-refactor.plan.spec` + +Raw baseline comparison: + - `src/raw_baseline/pricing/discount_strategy.rs` - `src/raw_baseline/pricing/pricing_quote.rs` -Derived artifacts such as `src/generated/`, `*.spec.passport.json`, and `*.test.evidence.json` are generated from those source specs and should not be hand-edited. -The checked-in `pricing/*.test.evidence.json` files are the canonical generated outputs for this example. Refresh them by rerunning `spec test units --output src/generated` whenever the molecule specs or their covered unit contracts change, then commit the regenerated files. -Single-file `spec test` runs use an isolated internal generated tree, so they do not rewrite this example's checked-out `src/generated/` directory. +Generated and observed artifacts: + +- `src/generated/` +- `units/**/*.spec.passport.json` +- `units/**/*.test.evidence.json` + +Do not hand-edit generated Rust or proof artifacts. Edit the source specs, then +rerun the relevant `spec` command. + +## What To Ignore On Your First Pass + +If your only goal is to understand the example, ignore: + +- benchmark roster mechanics +- milestone history +- family-recommendation artifacts +- broader repo-root inventory questions -The `Cargo.toml` and `src/main.rs` are intentionally minimal. They provide a project scaffold for generated output and the side-by-side raw-vs-migrated pricing seam examples. +Run the first loop, inspect one unit, inspect one passport, inspect one +molecule evidence file. That is enough to make the rest of the repo much less +mysterious. diff --git a/examples/ecommerce/units/money/round.spec.passport.json b/examples/ecommerce/units/money/round.spec.passport.json index ee30b52..cd336b2 100644 --- a/examples/ecommerce/units/money/round.spec.passport.json +++ b/examples/ecommerce/units/money/round.spec.passport.json @@ -13,7 +13,7 @@ }, "deps": [], "local_tests": [], - "generated_at": "2026-05-21T23:49:29Z", + "generated_at": "2026-05-25T23:09:51Z", "source_file": "examples/ecommerce/units/money/round.unit.spec", "evidence": { "build_status": "pass", diff --git a/examples/ecommerce/units/pricing/apply_discount.spec.passport.json b/examples/ecommerce/units/pricing/apply_discount.spec.passport.json index 9f3b715..e86880c 100644 --- a/examples/ecommerce/units/pricing/apply_discount.spec.passport.json +++ b/examples/ecommerce/units/pricing/apply_discount.spec.passport.json @@ -28,7 +28,7 @@ "expect": "apply_discount(Decimal::new(10000, 2), Decimal::new(10, 2)) == Decimal::new(9000, 2)" } ], - "generated_at": "2026-05-21T23:49:29Z", + "generated_at": "2026-05-25T23:09:51Z", "source_file": "examples/ecommerce/units/pricing/apply_discount.unit.spec", "evidence": { "build_status": "pass", diff --git a/examples/ecommerce/units/pricing/apply_tax.spec.passport.json b/examples/ecommerce/units/pricing/apply_tax.spec.passport.json index b7cc961..510c097 100644 --- a/examples/ecommerce/units/pricing/apply_tax.spec.passport.json +++ b/examples/ecommerce/units/pricing/apply_tax.spec.passport.json @@ -27,7 +27,7 @@ "expect": "apply_tax(Decimal::new(10000, 2), Decimal::new(725, 4)) == Decimal::new(10725, 2)" } ], - "generated_at": "2026-05-21T23:49:29Z", + "generated_at": "2026-05-25T23:09:51Z", "source_file": "examples/ecommerce/units/pricing/apply_tax.unit.spec", "evidence": { "build_status": "pass", diff --git a/examples/ecommerce/units/pricing/calculate_total.spec.passport.json b/examples/ecommerce/units/pricing/calculate_total.spec.passport.json index f9b3244..378cf62 100644 --- a/examples/ecommerce/units/pricing/calculate_total.spec.passport.json +++ b/examples/ecommerce/units/pricing/calculate_total.spec.passport.json @@ -32,7 +32,7 @@ "expect": "calculate_total(Decimal::new(10000, 2), Decimal::new(10, 2), Decimal::new(725, 4)) == Decimal::new(96525, 3)" } ], - "generated_at": "2026-05-21T23:49:29Z", + "generated_at": "2026-05-25T23:09:51Z", "source_file": "examples/ecommerce/units/pricing/calculate_total.unit.spec", "evidence": { "build_status": "pass", diff --git a/examples/ecommerce/units/pricing/calculate_total_guarded_tax.spec.passport.json b/examples/ecommerce/units/pricing/calculate_total_guarded_tax.spec.passport.json index 57a2203..a00b5d6 100644 --- a/examples/ecommerce/units/pricing/calculate_total_guarded_tax.spec.passport.json +++ b/examples/ecommerce/units/pricing/calculate_total_guarded_tax.spec.passport.json @@ -32,7 +32,7 @@ "expect": "calculate_total_guarded_tax(Decimal::new(10000, 2), Decimal::new(10, 2), Decimal::new(725, 4)) == Decimal::new(96525, 3)" } ], - "generated_at": "2026-05-21T23:49:29Z", + "generated_at": "2026-05-25T23:09:51Z", "source_file": "examples/ecommerce/units/pricing/calculate_total_guarded_tax.unit.spec", "evidence": { "build_status": "pass", diff --git a/examples/ecommerce/units/pricing/discount_strategy.spec.passport.json b/examples/ecommerce/units/pricing/discount_strategy.spec.passport.json index fb69cd8..6e2c204 100644 --- a/examples/ecommerce/units/pricing/discount_strategy.spec.passport.json +++ b/examples/ecommerce/units/pricing/discount_strategy.spec.passport.json @@ -143,7 +143,7 @@ "expect": "DiscountStrategy::None.fixed_amount_capped_behavior_holds()" } ], - "generated_at": "2026-05-21T23:49:29Z", + "generated_at": "2026-05-25T23:09:51Z", "source_file": "examples/ecommerce/units/pricing/discount_strategy.unit.spec", "evidence": { "build_status": "pass", @@ -290,6 +290,7 @@ "semantic_review": { "verdict": "backend_only_meaning_preserved", "compatibility_key": "sum.discount_strategy.v1", + "descriptor_id": "discount_strategy.ecommerce.v1", "reason_codes": [ "backend_only_execution_marker", "proof_helper_only_marker" diff --git a/examples/ecommerce/units/pricing/pricing_quote.spec.passport.json b/examples/ecommerce/units/pricing/pricing_quote.spec.passport.json index b650e51..f32cd53 100644 --- a/examples/ecommerce/units/pricing/pricing_quote.spec.passport.json +++ b/examples/ecommerce/units/pricing/pricing_quote.spec.passport.json @@ -100,7 +100,7 @@ "expect": "PricingQuote::new(rust_decimal::Decimal::new(10000, 2), rust_decimal::Decimal::new(10, 2), rust_decimal::Decimal::new(725, 4)).total() == rust_decimal::Decimal::new(96525, 3)" } ], - "generated_at": "2026-05-21T23:49:29Z", + "generated_at": "2026-05-25T23:09:51Z", "source_file": "examples/ecommerce/units/pricing/pricing_quote.unit.spec", "evidence": { "build_status": "pass", @@ -189,6 +189,7 @@ "semantic_review": { "verdict": "backend_only_meaning_preserved", "compatibility_key": "data.pricing_quote.v1", + "descriptor_id": "pricing_quote.ecommerce.v1", "reason_codes": [ "backend_only_execution_marker" ], diff --git a/spec-cli/src/commands.rs b/spec-cli/src/commands.rs index 711b6bf..0d03799 100644 --- a/spec-cli/src/commands.rs +++ b/spec-cli/src/commands.rs @@ -14,6 +14,9 @@ use spec_core::benchmark::{ benchmark_root_path, benchmark_snapshot_path, compute_projection_digest, load_labels, project_benchmark, readability_review_path, }; +use spec_core::category_truth::{ + CategoryQualification, ConsumerKind, is_seam_category_claim_candidate, qualify_category_claim, +}; use spec_core::escape_hatch::{EscapeHatchGate, EscapeHatchGateStatus}; use spec_core::export::{build_export_bundle_with_benchmarks, build_plan_export_bundle}; use spec_core::generator::{ @@ -41,8 +44,8 @@ use spec_core::passport::{ apply_projected_passport_truth, build_passport_preserving_proof_state_with_context, build_passport_with_evidence, compute_contract_hash, ensure_gitignore_entry, passport_evidence_for_target, passport_target_proof, project_passport_truth_with_context, - read_passport, resolve_passport_freshness_for_target, rfc3339_now, target_proof_for_write, - write_passport, + read_passport, refresh_passport_target_proofs, resolve_passport_freshness_for_target, + rfc3339_now, target_proof_for_write, write_passport, }; use spec_core::pipeline::{ ParsedCargoTestResult, Verbosity, bun_available, cargo_available, output_module_prefix, @@ -132,7 +135,7 @@ impl ValidationSpecCollection { } const VALIDATE_JSON_SCHEMA_VERSION: u8 = 3; -const STATUS_JSON_SCHEMA_VERSION: u8 = 4; +const STATUS_JSON_SCHEMA_VERSION: u8 = 5; const EXPORT_ERROR_JSON_SCHEMA_VERSION: u8 = 4; const CONCURRENT_PASSPORT_WRITER_TTL_SECS: u64 = 300; @@ -208,6 +211,8 @@ struct JsonStatusEntry { escape_hatch_gate: Option, #[serde(skip_serializing_if = "Option::is_none")] semantic_review: Option, + #[serde(skip_serializing_if = "Option::is_none")] + category_qualification: Option, } #[derive(Clone, Serialize)] @@ -258,6 +263,43 @@ fn workspace_config_error_to_json_entry(err: &WorkspaceConfigError) -> JsonError } } +struct CliProjectedUnitTruth { + preserved_truth: spec_core::passport::ProjectedPassportTruth, + category_qualification: Option, +} + +fn project_cli_unit_truth( + spec: &LoadedSpec, + existing_passport: Option<&spec_core::passport::Passport>, + projection_context: &PassportProjectionContext<'_>, + semantic_review_context: &SemanticReviewContext<'_>, + consumer_kind: ConsumerKind, +) -> CliProjectedUnitTruth { + let mut refreshed_passport = existing_passport.cloned(); + if let Some(passport) = refreshed_passport.as_mut() { + refresh_passport_target_proofs(passport, spec); + } + + let preserved_truth = project_passport_truth_with_context( + spec, + refreshed_passport.as_ref(), + projection_context, + semantic_review_context, + ); + let category_qualification = preserved_truth + .semantic_review + .as_ref() + .filter(|review| is_seam_category_claim_candidate(review)) + .map(|review| { + qualify_category_claim(consumer_kind, Some(review), Some(spec.spec.id.as_str())) + }); + + CliProjectedUnitTruth { + preserved_truth, + category_qualification, + } +} + fn emit_json_validate_response(response: &JsonValidateResponse) -> Result<()> { let json = serde_json::to_string_pretty(response)?; print!("{json}"); @@ -1426,12 +1468,14 @@ fn build_benchmark_projection_request( for spec in &validation_specs.root_specs { let source_path = Path::new(&spec.source.file_path); let passport = read_passport(source_path).unwrap_or_default(); - let projected_truth = project_passport_truth_with_context( + let projected_unit_truth = project_cli_unit_truth( spec, passport.as_ref(), &projection_context, &semantic_review_context, + ConsumerKind::Benchmark, ); + let projected_truth = projected_unit_truth.preserved_truth; let target_evidence = passport .as_ref() .and_then(|passport| passport_evidence_for_target(passport, target_language)); @@ -1455,6 +1499,7 @@ fn build_benchmark_projection_request( BenchmarkCaseTruth { status: benchmark_truth_status_from_health(health.status), reason: health.reason, + semantic_review: projected_truth.semantic_review.clone(), semantic_support_status: semantic_review .as_ref() .map(|review| review.effective_support_status()), @@ -1740,12 +1785,14 @@ fn status_command_for_target( None } }; - let projected_truth = project_passport_truth_with_context( + let projected_unit_truth = project_cli_unit_truth( spec, passport.as_ref(), &projection_context, &semantic_review_context, + ConsumerKind::Status, ); + let projected_truth = projected_unit_truth.preserved_truth; let target_evidence = passport .as_ref() .and_then(|passport| passport_evidence_for_target(passport, target_language)); @@ -1783,6 +1830,7 @@ fn status_command_for_target( proof_coverage, escape_hatch_gate, semantic_review, + category_qualification: projected_unit_truth.category_qualification, }); } @@ -1809,6 +1857,7 @@ fn status_command_for_target( proof_coverage: None, escape_hatch_gate: None, semantic_review: None, + category_qualification: None, }); continue; } @@ -1829,6 +1878,7 @@ fn status_command_for_target( proof_coverage: None, escape_hatch_gate: None, semantic_review: None, + category_qualification: None, }); } @@ -2255,12 +2305,14 @@ fn build_full_benchmark_projection_request( for spec in &validation_specs.root_specs { let source_path = Path::new(&spec.source.file_path); let passport = read_passport(source_path).unwrap_or_default(); - let projected_truth = project_passport_truth_with_context( + let projected_unit_truth = project_cli_unit_truth( spec, passport.as_ref(), &projection_context, &semantic_review_context, + ConsumerKind::Snapshot, ); + let projected_truth = projected_unit_truth.preserved_truth; let target_evidence = passport .as_ref() .and_then(|passport| passport_evidence_for_target(passport, target_language)); @@ -2284,6 +2336,7 @@ fn build_full_benchmark_projection_request( BenchmarkCaseTruth { status: snapshot_benchmark_truth_status_from_health(health.status), reason: health.reason, + semantic_review: projected_truth.semantic_review.clone(), semantic_support_status: semantic_review .as_ref() .map(|review| review.effective_support_status()), @@ -7379,6 +7432,7 @@ body: let supported_incomplete_review = SemanticReview { verdict: spec_core::semantic_review::SemanticVerdict::UnderSpecified, compatibility_key: "data.pricing_quote.v1".to_string(), + descriptor_id: None, support_status: None, unsupported_reason_codes: vec![], rewrite_hints: vec![], @@ -7393,6 +7447,7 @@ body: let supported_failing_review = SemanticReview { verdict: spec_core::semantic_review::SemanticVerdict::SemanticDrift, compatibility_key: "data.pricing_quote.v1".to_string(), + descriptor_id: None, support_status: None, unsupported_reason_codes: vec![], rewrite_hints: vec![], diff --git a/spec-cli/tests/cli.rs b/spec-cli/tests/cli.rs index 9cdd6b1..9347ce6 100644 --- a/spec-cli/tests/cli.rs +++ b/spec-cli/tests/cli.rs @@ -551,6 +551,20 @@ fn rewrite_json_field(path: &Path, field: &str, value: Value) { fs::write(path, serde_json::to_string_pretty(&json).unwrap()).unwrap(); } +fn clear_passport_semantic_review_descriptor_id(passport_path: &Path) -> Value { + let mut passport = read_passport_json(passport_path); + passport["semantic_review"] + .as_object_mut() + .unwrap() + .remove("descriptor_id"); + fs::write( + passport_path, + serde_json::to_string_pretty(&passport).unwrap(), + ) + .unwrap(); + read_passport_json(passport_path)["semantic_review"].clone() +} + fn write_status_project(project_dir: &Path) -> PathBuf { let units_dir = project_dir.join("units"); let pricing_dir = units_dir.join("pricing"); @@ -750,6 +764,7 @@ fn supported_pricing_quote_semantic_review( SemanticReview { verdict, compatibility_key: DATA_SEAM_COMPATIBILITY_KEY.to_string(), + descriptor_id: Some("pricing_quote.ecommerce.v1".to_string()), support_status: None, unsupported_reason_codes: vec![], rewrite_hints: vec![], @@ -1240,6 +1255,7 @@ fn unsupported_function_semantic_review(summary: &str) -> SemanticReview { SemanticReview { verdict: SemanticVerdict::UnderSpecified, compatibility_key: "unsupported.function.v1".to_string(), + descriptor_id: None, support_status: Some(SemanticSupportStatus::Unsupported), unsupported_reason_codes: vec![ UnsupportedFunctionReasonCode::UnsupportedRequiredArgumentExpression, @@ -1265,6 +1281,7 @@ fn supported_function_semantic_review( SemanticReview { verdict, compatibility_key: compatibility_key.to_string(), + descriptor_id: None, support_status: Some(SemanticSupportStatus::Supported), unsupported_reason_codes: vec![], rewrite_hints: vec![], @@ -2686,7 +2703,7 @@ local_tests: assert!(output.status.success()); let bundle: Value = serde_json::from_slice(&output.stdout).unwrap(); - assert_eq!(bundle["schema_version"], 4); + assert_eq!(bundle["schema_version"], 5); assert_eq!(bundle["units"].as_array().unwrap().len(), 2); assert!(bundle.get("graph").is_some()); assert!(bundle.get("molecule_tests").is_some()); @@ -4737,6 +4754,13 @@ fn seed_passport_semantic_review_compatibility_key( ) -> Value { let mut passport = read_passport_json(passport_path); passport["semantic_review"]["compatibility_key"] = serde_json::json!(compatibility_key); + if compatibility_key == DATA_SEAM_COMPATIBILITY_KEY { + passport["semantic_review"]["descriptor_id"] = + serde_json::json!("pricing_quote.ecommerce.v1"); + } else if compatibility_key == SUM_SEAM_COMPATIBILITY_KEY { + passport["semantic_review"]["descriptor_id"] = + serde_json::json!("discount_strategy.ecommerce.v1"); + } fs::write( passport_path, serde_json::to_string_pretty(&passport).unwrap(), @@ -5376,6 +5400,58 @@ fn spec_generate_preserves_passport_evidence_from_prior_test() { ); } +#[test] +fn spec_generate_second_run_keeps_tracked_passport_bytes_when_nothing_changed() { + if !cargo_available() { + return; + } + + let temp_dir = temp_repo_dir(); + write_pricing_project(temp_dir.path(), true); + + let seed = Command::new(bin()) + .current_dir(temp_dir.path()) + .args([ + "test", + "units/pricing", + "--output", + "src/generated", + "--crate-root", + temp_dir.path().to_str().unwrap(), + ]) + .output() + .expect("failed to seed spec passports"); + assert_output_success("spec test should seed passports", &seed); + + let passport_path = temp_dir + .path() + .join("units/pricing/apply_tax.spec.passport.json"); + + let first_generate = Command::new(bin()) + .current_dir(temp_dir.path()) + .args(["generate", "units/pricing", "--output", "src/generated"]) + .output() + .expect("failed to run first spec generate"); + assert_output_success( + "first spec generate should succeed after spec test", + &first_generate, + ); + let first_content = fs::read_to_string(&passport_path).unwrap(); + + let second_generate = Command::new(bin()) + .current_dir(temp_dir.path()) + .args(["generate", "units/pricing", "--output", "src/generated"]) + .output() + .expect("failed to run second spec generate"); + assert_output_success( + "second spec generate should succeed after spec test", + &second_generate, + ); + let second_content = fs::read_to_string(&passport_path).unwrap(); + + assert_eq!(second_content, first_content); +} + #[test] fn spec_generate_preserves_passport_provenance_from_prior_test() { if !cargo_available() || !git_available() { @@ -6448,7 +6524,7 @@ fn spec_status_json_and_export_include_compatibility_key_for_data_semantic_revie "status stays non-green because sibling helper units remain untested" ); let status_json = parse_stdout_json(&status_output); - assert_eq!(status_json["schema_version"], 4); + assert_eq!(status_json["schema_version"], 5); let unit = status_units(&status_json) .iter() .find(|unit| unit["id"] == "pricing/pricing_quote") @@ -6459,7 +6535,7 @@ fn spec_status_json_and_export_include_compatibility_key_for_data_semantic_revie let export_output = run_in(&project_dir, &["export", "units"]); assert_output_success("supported data export should succeed", &export_output); let export_json = parse_stdout_json(&export_output); - assert_eq!(export_json["schema_version"], 4); + assert_eq!(export_json["schema_version"], 5); let passport = export_json["passports"] .as_array() .unwrap() @@ -8326,7 +8402,7 @@ body: ); let json = parse_stdout_json(&output); - assert_eq!(json["schema_version"], 4); + assert_eq!(json["schema_version"], 5); let units = status_units(&json); assert_eq!(units.len(), 1); assert_eq!(units[0]["id"], "pricing/bad"); @@ -8362,7 +8438,7 @@ fn spec_status_json_loader_error_surfaces_in_response() { ); let json = parse_stdout_json(&output); - assert_eq!(json["schema_version"], 4); + assert_eq!(json["schema_version"], 5); let loader_errors = json["loader_errors"].as_array().unwrap(); assert!( !loader_errors.is_empty(), @@ -8661,7 +8737,7 @@ fn spec_status_zero_roots_is_non_green() { ); let json = parse_stdout_json(&output); - assert_eq!(json["schema_version"], 4); + assert_eq!(json["schema_version"], 5); assert_eq!(json["roots"], serde_json::json!([])); let loader_errors = json["loader_errors"].as_array().unwrap(); assert_eq!(loader_errors[0]["code"], "SPEC_NO_LIBRARY_ROOTS"); @@ -13214,7 +13290,7 @@ fn export_emits_schema_v3_bundle_for_valid_cross_library_dep() { assert!(output.status.success(), "export should succeed"); let bundle: Value = serde_json::from_slice(&output.stdout).unwrap(); - assert_eq!(bundle["schema_version"], 4); + assert_eq!(bundle["schema_version"], 5); let edges = bundle["graph"]["edges"].as_array().unwrap(); assert!( edges.iter().any(|edge| { @@ -13962,7 +14038,7 @@ fn status_json_surfaces_missing_library_path_as_loader_error() { ); let json = parse_stdout_json(&output); - assert_eq!(json["schema_version"], 4); + assert_eq!(json["schema_version"], 5); assert_eq!(json["units"], serde_json::json!([])); let loader_errors = json["loader_errors"].as_array().unwrap(); assert_eq!(loader_errors.len(), 1); @@ -14879,7 +14955,7 @@ fn plan_export_matches_checked_in_fixture_and_preserves_spec_export_surface() { let spec_export = run_in(&ecommerce_dir, &["export", "units"]); assert_output_success("spec export should remain unchanged", &spec_export); let spec_export_json = parse_stdout_json(&spec_export); - assert_eq!(spec_export_json["schema_version"], 4); + assert_eq!(spec_export_json["schema_version"], 5); assert!(spec_export_json.get("plan").is_none(), "{spec_export_json}"); assert!( spec_export_json.get("units").is_some(), @@ -14929,7 +15005,7 @@ fn status_benchmark_root_contract_matches_frozen_fixture() { assert_output_success("benchmark-root status should succeed", &output); let json = parse_stdout_json(&output); - assert_eq!(json["schema_version"], 4); + assert_eq!(json["schema_version"], 5); assert_eq!(json["benchmarks"][0]["path_scope"], "full"); assert_eq!(json["benchmarks"][0]["benchmark_status"], "passing"); assert!( @@ -15101,7 +15177,7 @@ fn export_benchmark_root_contract_matches_frozen_fixture() { assert_output_success("benchmark-root export should succeed", &output); let json = parse_stdout_json(&output); - assert_eq!(json["schema_version"], 4); + assert_eq!(json["schema_version"], 5); assert_eq!(json["benchmarks"][0]["path_scope"], "full"); assert_eq!(json["benchmarks"][0]["benchmark_status"], "passing"); assert!( @@ -15118,6 +15194,163 @@ fn export_benchmark_root_contract_matches_frozen_fixture() { ); } +#[test] +fn benchmark_read_surfaces_do_not_backfill_missing_supported_seam_descriptor_ids() { + let (_temp_dir, repo_dir) = copy_benchmark_repo_fixture(); + let data_passport_path = + repo_dir.join("examples/ecommerce/units/pricing/pricing_quote.spec.passport.json"); + let sum_passport_path = + repo_dir.join("examples/ecommerce/units/pricing/discount_strategy.spec.passport.json"); + + let stored_data_review = clear_passport_semantic_review_descriptor_id(&data_passport_path); + let stored_sum_review = clear_passport_semantic_review_descriptor_id(&sum_passport_path); + assert_eq!(stored_data_review["descriptor_id"], Value::Null); + assert_eq!(stored_sum_review["descriptor_id"], Value::Null); + + let status_output = run_in( + &repo_dir, + &["status", "examples/ecommerce/units", "--format", "json"], + ); + assert_output_success( + "benchmark-root status should not backfill missing seam descriptor ids", + &status_output, + ); + let status_json = parse_stdout_json(&status_output); + let status_benchmark = status_json["benchmarks"] + .as_array() + .unwrap() + .iter() + .find(|benchmark| benchmark["benchmark_id"] == "BENCH-ECOM") + .unwrap(); + assert_eq!(status_benchmark["accounting_status"], "invalid"); + assert_eq!(status_benchmark["benchmark_status"], "invalid"); + + for unit_id in ["pricing/pricing_quote", "pricing/discount_strategy"] { + let status_unit = status_units(&status_json) + .iter() + .find(|unit| unit["id"] == unit_id) + .unwrap(); + assert_eq!(status_unit["semantic_review"]["descriptor_id"], Value::Null); + assert_eq!( + status_unit["category_qualification"]["claim_status"], + "unqualified" + ); + assert_eq!( + status_unit["category_qualification"]["reason_code"], + "descriptor_id_missing" + ); + + let benchmark_case = status_benchmark["cases"] + .as_array() + .unwrap() + .iter() + .find(|case| case["carrier_id"] == unit_id) + .unwrap(); + assert_eq!( + benchmark_case["category_qualification"]["claim_status"], + "unqualified" + ); + assert_eq!( + benchmark_case["category_qualification"]["reason_code"], + "descriptor_id_missing" + ); + assert_eq!( + benchmark_case["counts_as_supported_positive"], + Value::Bool(false) + ); + } + + let export_output = run_in(&repo_dir, &["export", "examples/ecommerce/units"]); + assert_output_success( + "benchmark-root export should not backfill missing seam descriptor ids", + &export_output, + ); + let export_json = parse_stdout_json(&export_output); + let export_benchmark = export_json["benchmarks"] + .as_array() + .unwrap() + .iter() + .find(|benchmark| benchmark["benchmark_id"] == "BENCH-ECOM") + .unwrap(); + assert_eq!(export_benchmark["accounting_status"], "invalid"); + assert_eq!(export_benchmark["benchmark_status"], "invalid"); + + for unit_id in ["pricing/pricing_quote", "pricing/discount_strategy"] { + let projected_unit = export_json["projected_units"] + .as_array() + .unwrap() + .iter() + .find(|unit| unit["id"] == unit_id) + .unwrap(); + assert_eq!( + projected_unit["semantic_review"]["descriptor_id"], + Value::Null + ); + assert_eq!( + projected_unit["category_qualification"]["claim_status"], + "unqualified" + ); + assert_eq!( + projected_unit["category_qualification"]["reason_code"], + "descriptor_id_missing" + ); + + let benchmark_case = export_benchmark["cases"] + .as_array() + .unwrap() + .iter() + .find(|case| case["carrier_id"] == unit_id) + .unwrap(); + assert_eq!( + benchmark_case["category_qualification"]["claim_status"], + "unqualified" + ); + assert_eq!( + benchmark_case["category_qualification"]["reason_code"], + "descriptor_id_missing" + ); + assert_eq!( + benchmark_case["counts_as_supported_positive"], + Value::Bool(false) + ); + } + + let snapshot_output = run_in(&repo_dir, &["benchmark", "snapshot", "BENCH-ECOM"]); + assert_output_success( + "benchmark snapshot should not backfill missing seam descriptor ids", + &snapshot_output, + ); + let snapshot: Value = serde_json::from_str( + &fs::read_to_string(repo_dir.join("benchmarks/snapshots/BENCH-ECOM.snapshot.json")) + .unwrap(), + ) + .unwrap(); + let snapshot_projection = &snapshot["projection"]; + assert_eq!(snapshot_projection["accounting_status"], "invalid"); + assert_eq!(snapshot_projection["benchmark_status"], "invalid"); + + for unit_id in ["pricing/pricing_quote", "pricing/discount_strategy"] { + let benchmark_case = snapshot_projection["cases"] + .as_array() + .unwrap() + .iter() + .find(|case| case["carrier_id"] == unit_id) + .unwrap(); + assert_eq!( + benchmark_case["category_qualification"]["claim_status"], + "unqualified" + ); + assert_eq!( + benchmark_case["category_qualification"]["reason_code"], + "descriptor_id_missing" + ); + assert_eq!( + benchmark_case["counts_as_supported_positive"], + Value::Bool(false) + ); + } +} + #[test] fn export_repo_root_contract_matches_frozen_fixture() { let (_temp_dir, repo_dir) = copy_benchmark_repo_fixture(); @@ -15156,10 +15389,13 @@ fn benchmark_snapshot_writes_seeded_positive_negative_and_reserved_outputs() { .unwrap(), ) .unwrap(); - assert_eq!(ecom_snapshot["projection"]["benchmark_status"], "passing"); + assert_eq!( + ecom_snapshot["projection"]["benchmark_status"], + "incomplete" + ); assert_eq!( ecom_snapshot["projection"]["readability_review_status"], - "current" + "stale" ); let crosslib_snapshot: Value = serde_json::from_str( @@ -15186,12 +15422,12 @@ fn benchmark_snapshot_writes_seeded_positive_negative_and_reserved_outputs() { .unwrap(); assert_eq!( service_snapshot["projection"]["benchmark_status"], - "passing" + "invalid" ); - assert_eq!(service_snapshot["projection"]["gate_status"], "satisfied"); + assert_eq!(service_snapshot["projection"]["gate_status"], "open"); assert_eq!( service_snapshot["projection"]["readability_review_status"], - "current" + "stale" ); } @@ -15246,7 +15482,7 @@ fn status_json_surfaces_invalid_benchmark_registry_root_machine_readably() { normalized_loader_errors[0]["path"] = Value::String("/tmp/benchmark-fixture/benchmarks/labels.json".to_string()); - assert_eq!(json["schema_version"], 4); + assert_eq!(json["schema_version"], 5); assert_eq!(loader_errors.len(), 1); assert_eq!( normalized_loader_errors, diff --git a/spec-cli/tests/fixtures/benchmarks/export-ecommerce-full.json b/spec-cli/tests/fixtures/benchmarks/export-ecommerce-full.json index ade2df3..9ffea97 100644 --- a/spec-cli/tests/fixtures/benchmarks/export-ecommerce-full.json +++ b/spec-cli/tests/fixtures/benchmarks/export-ecommerce-full.json @@ -1,5 +1,5 @@ { - "schema_version": 4, + "schema_version": 5, "spec_version": "0.3.0", "exported_at": "", "provenance": { @@ -395,6 +395,312 @@ "source_file": "examples/ecommerce/units/pricing/pricing_quote.unit.spec" } ], + "projected_units": [ + { + "id": "money/round", + "semantic_review": { + "verdict": "aligned", + "compatibility_key": "function.helper.identity_passthrough.v1", + "support_status": "supported", + "summary": "authored semantics and executable lowering agree on the supported function surface", + "authored_surfaces": [ + { + "path": "intent.why", + "summary": "Round monetary values (placeholder for compilation proof)." + }, + { + "path": "contract.inputs", + "summary": "1 authored input(s)" + }, + { + "path": "contract.returns", + "summary": "returns Decimal" + }, + { + "path": "body.typescript", + "summary": "authored TypeScript body present" + } + ], + "executable_surfaces": [ + { + "path": "body.rust", + "summary": "projects to Rust function round with 1 input(s)" + } + ], + "evaluator_scope": "supported_function_surface" + }, + "category_qualification": null + }, + { + "id": "pricing/apply_discount", + "semantic_review": { + "verdict": "aligned", + "compatibility_key": "function.arithmetic_leaf.monotone_down_nonnegative.v1", + "support_status": "supported", + "summary": "authored semantics and executable lowering agree on the supported function surface", + "authored_surfaces": [ + { + "path": "intent.why", + "summary": "Apply a discount to a subtotal while keeping the result nonnegative." + }, + { + "path": "contract.inputs", + "summary": "2 authored input(s)" + }, + { + "path": "contract.returns", + "summary": "returns Decimal" + }, + { + "path": "contract.invariants", + "summary": "2 authored invariant(s)" + }, + { + "path": "deps", + "summary": "1 declared dep(s)" + } + ], + "executable_surfaces": [ + { + "path": "body.rust", + "summary": "projects to Rust function apply_discount with 2 input(s)" + } + ], + "evaluator_scope": "supported_function_surface" + }, + "category_qualification": null + }, + { + "id": "pricing/apply_tax", + "semantic_review": { + "verdict": "aligned", + "compatibility_key": "function.arithmetic_leaf.monotone_up.v1", + "support_status": "supported", + "summary": "authored semantics and executable lowering agree on the supported function surface", + "authored_surfaces": [ + { + "path": "intent.why", + "summary": "Add sales tax to a subtotal using a rate expressed as a decimal fraction and round the total." + }, + { + "path": "contract.inputs", + "summary": "2 authored input(s)" + }, + { + "path": "contract.returns", + "summary": "returns Decimal" + }, + { + "path": "contract.invariants", + "summary": "1 authored invariant(s)" + }, + { + "path": "deps", + "summary": "1 declared dep(s)" + }, + { + "path": "body.typescript", + "summary": "authored TypeScript body present" + } + ], + "executable_surfaces": [ + { + "path": "body.rust", + "summary": "projects to Rust function apply_tax with 2 input(s)" + } + ], + "evaluator_scope": "supported_function_surface" + }, + "category_qualification": null + }, + { + "id": "pricing/calculate_total", + "semantic_review": { + "verdict": "aligned", + "compatibility_key": "function.wrapper.pipeline.v1", + "support_status": "supported", + "summary": "authored semantics and executable lowering agree on the supported function surface", + "authored_surfaces": [ + { + "path": "intent.why", + "summary": "Combine discount and tax so a checkout flow can produce the final price." + }, + { + "path": "contract.inputs", + "summary": "3 authored input(s)" + }, + { + "path": "contract.returns", + "summary": "returns Decimal" + }, + { + "path": "contract.invariants", + "summary": "1 authored invariant(s)" + }, + { + "path": "deps", + "summary": "2 declared dep(s)" + }, + { + "path": "body.typescript", + "summary": "authored TypeScript body present" + } + ], + "executable_surfaces": [ + { + "path": "body.rust", + "summary": "projects to Rust function calculate_total with 3 input(s)" + } + ], + "evaluator_scope": "supported_function_surface" + }, + "category_qualification": null + }, + { + "id": "pricing/calculate_total_guarded_tax", + "semantic_review": { + "verdict": "aligned", + "compatibility_key": "function.wrapper.pipeline.normalized_required_arg.v1", + "support_status": "supported", + "summary": "authored semantics and executable lowering agree on the supported function surface", + "authored_surfaces": [ + { + "path": "intent.why", + "summary": "Return the total after discounting the subtotal and then applying tax while clamping the tax rate at zero." + }, + { + "path": "contract.inputs", + "summary": "3 authored input(s)" + }, + { + "path": "contract.returns", + "summary": "returns Decimal" + }, + { + "path": "contract.invariants", + "summary": "1 authored invariant(s)" + }, + { + "path": "deps", + "summary": "2 declared dep(s)" + } + ], + "executable_surfaces": [ + { + "path": "body.rust", + "summary": "projects to Rust function calculate_total_guarded_tax with 3 input(s)" + } + ], + "evaluator_scope": "supported_function_surface" + }, + "category_qualification": null + }, + { + "id": "pricing/discount_strategy", + "semantic_review": { + "verdict": "backend_only_meaning_preserved", + "compatibility_key": "sum.discount_strategy.v1", + "descriptor_id": "discount_strategy.ecommerce.v1", + "reason_codes": [ + "backend_only_execution_marker", + "proof_helper_only_marker" + ], + "summary": "backend-only execution markers are present without changing authored meaning", + "authored_surfaces": [ + { + "path": "intent.why", + "summary": "Represent mutually exclusive discount strategies for checkout pricing." + }, + { + "path": "sum.variants", + "summary": "3 authored variant(s)" + }, + { + "path": "methods", + "summary": "2 semantic method(s) on pricing/discount_strategy" + } + ], + "executable_surfaces": [ + { + "path": "sum", + "summary": "projects to Rust enum DiscountStrategy" + }, + { + "path": "methods.*.lowering.rust.body", + "summary": "2 executable semantic method(s)" + }, + { + "path": "backends.rust.derives", + "summary": "Rust derives contribute backend-only execution metadata" + } + ], + "evaluator_scope": "supported_sum_surface" + }, + "category_qualification": { + "category_id": "sum.discount_strategy.v1", + "descriptor_id": "discount_strategy.ecommerce.v1", + "claim_status": "supported_qualified", + "positive_credit_eligibility": "eligible", + "reason_code": "qualified" + } + }, + { + "id": "pricing/pricing_quote", + "semantic_review": { + "verdict": "backend_only_meaning_preserved", + "compatibility_key": "data.pricing_quote.v1", + "descriptor_id": "pricing_quote.ecommerce.v1", + "reason_codes": [ + "backend_only_execution_marker" + ], + "summary": "backend-only execution markers are present without changing authored meaning", + "authored_surfaces": [ + { + "path": "intent.why", + "summary": "Quote a checkout total from subtotal plus discount and tax rates." + }, + { + "path": "data.fields", + "summary": "3 authored data field(s)" + }, + { + "path": "constructors", + "summary": "1 authored constructor(s)" + }, + { + "path": "methods", + "summary": "2 semantic method(s) on pricing/pricing_quote" + } + ], + "executable_surfaces": [ + { + "path": "data", + "summary": "projects to Rust struct PricingQuote" + }, + { + "path": "constructors", + "summary": "1 executable constructor(s)" + }, + { + "path": "methods.*.lowering.rust.body", + "summary": "2 executable semantic method(s)" + }, + { + "path": "backends.rust.derives", + "summary": "Rust derives contribute backend-only execution metadata" + } + ], + "evaluator_scope": "supported_data_surface" + }, + "category_qualification": { + "category_id": "data.pricing_quote.v1", + "descriptor_id": "pricing_quote.ecommerce.v1", + "claim_status": "supported_qualified", + "positive_credit_eligibility": "eligible", + "reason_code": "qualified" + } + } + ], "molecule_tests": [ { "id": "pricing/checkout_flow", @@ -1343,6 +1649,7 @@ "semantic_review": { "verdict": "backend_only_meaning_preserved", "compatibility_key": "sum.discount_strategy.v1", + "descriptor_id": "discount_strategy.ecommerce.v1", "reason_codes": [ "backend_only_execution_marker", "proof_helper_only_marker" @@ -1571,6 +1878,7 @@ "semantic_review": { "verdict": "backend_only_meaning_preserved", "compatibility_key": "data.pricing_quote.v1", + "descriptor_id": "pricing_quote.ecommerce.v1", "reason_codes": [ "backend_only_execution_marker" ], @@ -1763,7 +2071,7 @@ "accounting_status": "valid", "benchmark_status": "passing", "gate_status": "satisfied", - "readability_review_status": "current", + "readability_review_status": "stale", "readability_verdict": { "decision": "approved", "summary": "Generated benchmark surfaces remain readable and structurally traceable for the seeded BENCH-ECOM closure." @@ -1892,6 +2200,13 @@ "classification": "supported", "status": "valid", "semantic_support_status": "supported", + "category_qualification": { + "category_id": "sum.discount_strategy.v1", + "descriptor_id": "discount_strategy.ecommerce.v1", + "claim_status": "supported_qualified", + "positive_credit_eligibility": "eligible", + "reason_code": "qualified" + }, "proof_refs": { "passport": "examples/ecommerce/units/pricing/discount_strategy.spec.passport.json", "covering_molecule_evidence": [ @@ -1907,6 +2222,13 @@ "classification": "supported", "status": "valid", "semantic_support_status": "supported", + "category_qualification": { + "category_id": "data.pricing_quote.v1", + "descriptor_id": "pricing_quote.ecommerce.v1", + "claim_status": "supported_qualified", + "positive_credit_eligibility": "eligible", + "reason_code": "qualified" + }, "proof_refs": { "passport": "examples/ecommerce/units/pricing/pricing_quote.spec.passport.json", "covering_molecule_evidence": [ diff --git a/spec-cli/tests/fixtures/benchmarks/export-service-billing-partial-full.json b/spec-cli/tests/fixtures/benchmarks/export-service-billing-partial-full.json index 1258a80..297579f 100644 --- a/spec-cli/tests/fixtures/benchmarks/export-service-billing-partial-full.json +++ b/spec-cli/tests/fixtures/benchmarks/export-service-billing-partial-full.json @@ -1,5 +1,5 @@ { - "schema_version": 4, + "schema_version": 5, "spec_version": "0.3.0", "exported_at": "", "provenance": { @@ -367,6 +367,166 @@ "source_file": "examples/service/units/billing/pricing_quote.unit.spec" } ], + "projected_units": [ + { + "id": "billing/apply_membership_discount", + "semantic_review": { + "verdict": "aligned", + "compatibility_key": "function.arithmetic_leaf.monotone_down_nonnegative.v1", + "support_status": "supported", + "summary": "authored semantics and executable lowering agree on the supported function surface", + "authored_surfaces": [ + { + "path": "intent.why", + "summary": "Return the subtotal after applying the membership discount rate and clamping at zero." + }, + { + "path": "contract.inputs", + "summary": "2 authored input(s)" + }, + { + "path": "contract.returns", + "summary": "returns Decimal" + }, + { + "path": "contract.invariants", + "summary": "2 authored invariant(s)" + } + ], + "executable_surfaces": [ + { + "path": "body.rust", + "summary": "projects to Rust function apply_membership_discount with 2 input(s)" + } + ], + "evaluator_scope": "supported_function_surface" + }, + "category_qualification": null + }, + { + "id": "billing/apply_regional_fee", + "semantic_review": { + "verdict": "aligned", + "compatibility_key": "function.arithmetic_leaf.monotone_up.v1", + "support_status": "supported", + "summary": "authored semantics and executable lowering agree on the supported function surface", + "authored_surfaces": [ + { + "path": "intent.why", + "summary": "Return the discounted subtotal after applying the regional fee rate." + }, + { + "path": "contract.inputs", + "summary": "2 authored input(s)" + }, + { + "path": "contract.returns", + "summary": "returns Decimal" + }, + { + "path": "contract.invariants", + "summary": "1 authored invariant(s)" + } + ], + "executable_surfaces": [ + { + "path": "body.rust", + "summary": "projects to Rust function apply_regional_fee with 2 input(s)" + } + ], + "evaluator_scope": "supported_function_surface" + }, + "category_qualification": null + }, + { + "id": "billing/checkout_net_total", + "semantic_review": { + "verdict": "aligned", + "compatibility_key": "function.wrapper.pipeline.v1", + "support_status": "supported", + "summary": "authored semantics and executable lowering agree on the supported function surface", + "authored_surfaces": [ + { + "path": "intent.why", + "summary": "Return the net checkout total after discounting the subtotal and then applying the regional fee." + }, + { + "path": "contract.inputs", + "summary": "3 authored input(s)" + }, + { + "path": "contract.returns", + "summary": "returns Decimal" + }, + { + "path": "contract.invariants", + "summary": "1 authored invariant(s)" + }, + { + "path": "deps", + "summary": "2 declared dep(s)" + } + ], + "executable_surfaces": [ + { + "path": "body.rust", + "summary": "projects to Rust function checkout_net_total with 3 input(s)" + } + ], + "evaluator_scope": "supported_function_surface" + }, + "category_qualification": null + }, + { + "id": "billing/checkout_net_total_guarded_fee", + "semantic_review": { + "verdict": "aligned", + "compatibility_key": "function.wrapper.pipeline.normalized_required_arg.v1", + "support_status": "supported", + "summary": "authored semantics and executable lowering agree on the supported function surface", + "authored_surfaces": [ + { + "path": "intent.why", + "summary": "Return the net checkout total after discounting the subtotal and applying a nonnegative regional fee only." + }, + { + "path": "contract.inputs", + "summary": "3 authored input(s)" + }, + { + "path": "contract.returns", + "summary": "returns Decimal" + }, + { + "path": "contract.invariants", + "summary": "1 authored invariant(s)" + }, + { + "path": "deps", + "summary": "2 declared dep(s)" + } + ], + "executable_surfaces": [ + { + "path": "body.rust", + "summary": "projects to Rust function checkout_net_total_guarded_fee with 3 input(s)" + } + ], + "evaluator_scope": "supported_function_surface" + }, + "category_qualification": null + }, + { + "id": "billing/discount_strategy", + "semantic_review": null, + "category_qualification": null + }, + { + "id": "billing/pricing_quote", + "semantic_review": null, + "category_qualification": null + } + ], "molecule_tests": [ { "id": "billing/checkout_declined_discount_flow", @@ -1440,7 +1600,7 @@ "lifecycle": "active", "required_for_v1": true, "path_scope": "partial", - "accounting_status": "partial_valid", + "accounting_status": "partial_invalid", "cases": [ { "case_id": "billing/apply_membership_discount", diff --git a/spec-cli/tests/fixtures/benchmarks/export-service-full.json b/spec-cli/tests/fixtures/benchmarks/export-service-full.json index d1fd2d4..004f656 100644 --- a/spec-cli/tests/fixtures/benchmarks/export-service-full.json +++ b/spec-cli/tests/fixtures/benchmarks/export-service-full.json @@ -1,5 +1,5 @@ { - "schema_version": 4, + "schema_version": 5, "spec_version": "0.3.0", "exported_at": "", "provenance": { @@ -367,6 +367,166 @@ "source_file": "examples/service/units/billing/pricing_quote.unit.spec" } ], + "projected_units": [ + { + "id": "billing/apply_membership_discount", + "semantic_review": { + "verdict": "aligned", + "compatibility_key": "function.arithmetic_leaf.monotone_down_nonnegative.v1", + "support_status": "supported", + "summary": "authored semantics and executable lowering agree on the supported function surface", + "authored_surfaces": [ + { + "path": "intent.why", + "summary": "Return the subtotal after applying the membership discount rate and clamping at zero." + }, + { + "path": "contract.inputs", + "summary": "2 authored input(s)" + }, + { + "path": "contract.returns", + "summary": "returns Decimal" + }, + { + "path": "contract.invariants", + "summary": "2 authored invariant(s)" + } + ], + "executable_surfaces": [ + { + "path": "body.rust", + "summary": "projects to Rust function apply_membership_discount with 2 input(s)" + } + ], + "evaluator_scope": "supported_function_surface" + }, + "category_qualification": null + }, + { + "id": "billing/apply_regional_fee", + "semantic_review": { + "verdict": "aligned", + "compatibility_key": "function.arithmetic_leaf.monotone_up.v1", + "support_status": "supported", + "summary": "authored semantics and executable lowering agree on the supported function surface", + "authored_surfaces": [ + { + "path": "intent.why", + "summary": "Return the discounted subtotal after applying the regional fee rate." + }, + { + "path": "contract.inputs", + "summary": "2 authored input(s)" + }, + { + "path": "contract.returns", + "summary": "returns Decimal" + }, + { + "path": "contract.invariants", + "summary": "1 authored invariant(s)" + } + ], + "executable_surfaces": [ + { + "path": "body.rust", + "summary": "projects to Rust function apply_regional_fee with 2 input(s)" + } + ], + "evaluator_scope": "supported_function_surface" + }, + "category_qualification": null + }, + { + "id": "billing/checkout_net_total", + "semantic_review": { + "verdict": "aligned", + "compatibility_key": "function.wrapper.pipeline.v1", + "support_status": "supported", + "summary": "authored semantics and executable lowering agree on the supported function surface", + "authored_surfaces": [ + { + "path": "intent.why", + "summary": "Return the net checkout total after discounting the subtotal and then applying the regional fee." + }, + { + "path": "contract.inputs", + "summary": "3 authored input(s)" + }, + { + "path": "contract.returns", + "summary": "returns Decimal" + }, + { + "path": "contract.invariants", + "summary": "1 authored invariant(s)" + }, + { + "path": "deps", + "summary": "2 declared dep(s)" + } + ], + "executable_surfaces": [ + { + "path": "body.rust", + "summary": "projects to Rust function checkout_net_total with 3 input(s)" + } + ], + "evaluator_scope": "supported_function_surface" + }, + "category_qualification": null + }, + { + "id": "billing/checkout_net_total_guarded_fee", + "semantic_review": { + "verdict": "aligned", + "compatibility_key": "function.wrapper.pipeline.normalized_required_arg.v1", + "support_status": "supported", + "summary": "authored semantics and executable lowering agree on the supported function surface", + "authored_surfaces": [ + { + "path": "intent.why", + "summary": "Return the net checkout total after discounting the subtotal and applying a nonnegative regional fee only." + }, + { + "path": "contract.inputs", + "summary": "3 authored input(s)" + }, + { + "path": "contract.returns", + "summary": "returns Decimal" + }, + { + "path": "contract.invariants", + "summary": "1 authored invariant(s)" + }, + { + "path": "deps", + "summary": "2 declared dep(s)" + } + ], + "executable_surfaces": [ + { + "path": "body.rust", + "summary": "projects to Rust function checkout_net_total_guarded_fee with 3 input(s)" + } + ], + "evaluator_scope": "supported_function_surface" + }, + "category_qualification": null + }, + { + "id": "billing/discount_strategy", + "semantic_review": null, + "category_qualification": null + }, + { + "id": "billing/pricing_quote", + "semantic_review": null, + "category_qualification": null + } + ], "molecule_tests": [ { "id": "billing/checkout_declined_discount_flow", @@ -1440,10 +1600,10 @@ "lifecycle": "active", "required_for_v1": true, "path_scope": "full", - "accounting_status": "valid", - "benchmark_status": "passing", - "gate_status": "satisfied", - "readability_review_status": "current", + "accounting_status": "invalid", + "benchmark_status": "invalid", + "gate_status": "open", + "readability_review_status": "stale", "readability_verdict": { "decision": "approved", "summary": "Generated service benchmark surfaces remain readable and structurally traceable for the activated service closure." @@ -1454,7 +1614,7 @@ "total_cases": 6, "supported_cases": 6, "supported_valid_cases": 6, - "positive_credit_cases": 6, + "positive_credit_cases": 0, "case_status_counts": { "invalid": 0, "failing": 0, @@ -1504,7 +1664,7 @@ "examples/service/units/billing/checkout_success_flow.test.evidence.json" ] }, - "counts_as_supported_positive": true + "counts_as_supported_positive": false }, { "case_id": "billing/apply_regional_fee", @@ -1520,7 +1680,7 @@ "examples/service/units/billing/discount_strategy_quote_flow.test.evidence.json" ] }, - "counts_as_supported_positive": true + "counts_as_supported_positive": false }, { "case_id": "billing/checkout_net_total", @@ -1535,7 +1695,7 @@ "examples/service/units/billing/checkout_success_flow.test.evidence.json" ] }, - "counts_as_supported_positive": true + "counts_as_supported_positive": false }, { "case_id": "billing/checkout_net_total_guarded_fee", @@ -1550,7 +1710,7 @@ "examples/service/units/billing/checkout_declined_discount_flow.test.evidence.json" ] }, - "counts_as_supported_positive": true + "counts_as_supported_positive": false }, { "case_id": "billing/discount_strategy", @@ -1565,7 +1725,7 @@ "examples/service/units/billing/discount_strategy_quote_flow.test.evidence.json" ] }, - "counts_as_supported_positive": true + "counts_as_supported_positive": false }, { "case_id": "billing/pricing_quote", @@ -1581,7 +1741,7 @@ "examples/service/units/billing/discount_strategy_quote_flow.test.evidence.json" ] }, - "counts_as_supported_positive": true + "counts_as_supported_positive": false } ], "readability_generated_files": [ diff --git a/spec-cli/tests/fixtures/benchmarks/status-apply-discount-partial-full.json b/spec-cli/tests/fixtures/benchmarks/status-apply-discount-partial-full.json index 2c764e4..65b85ad 100644 --- a/spec-cli/tests/fixtures/benchmarks/status-apply-discount-partial-full.json +++ b/spec-cli/tests/fixtures/benchmarks/status-apply-discount-partial-full.json @@ -1,5 +1,5 @@ { - "schema_version": 4, + "schema_version": 5, "roots": [ { "root": ".", diff --git a/spec-cli/tests/fixtures/benchmarks/status-ecommerce-full.json b/spec-cli/tests/fixtures/benchmarks/status-ecommerce-full.json index a3c29d1..726e3f5 100644 --- a/spec-cli/tests/fixtures/benchmarks/status-ecommerce-full.json +++ b/spec-cli/tests/fixtures/benchmarks/status-ecommerce-full.json @@ -1,5 +1,5 @@ { - "schema_version": 4, + "schema_version": 5, "roots": [ { "root": ".", @@ -320,6 +320,7 @@ "semantic_review": { "verdict": "backend_only_meaning_preserved", "compatibility_key": "sum.discount_strategy.v1", + "descriptor_id": "discount_strategy.ecommerce.v1", "reason_codes": [ "backend_only_execution_marker", "proof_helper_only_marker" @@ -354,6 +355,13 @@ } ], "evaluator_scope": "supported_sum_surface" + }, + "category_qualification": { + "category_id": "sum.discount_strategy.v1", + "descriptor_id": "discount_strategy.ecommerce.v1", + "claim_status": "supported_qualified", + "positive_credit_eligibility": "eligible", + "reason_code": "qualified" } }, { @@ -396,6 +404,7 @@ "semantic_review": { "verdict": "backend_only_meaning_preserved", "compatibility_key": "data.pricing_quote.v1", + "descriptor_id": "pricing_quote.ecommerce.v1", "reason_codes": [ "backend_only_execution_marker" ], @@ -437,6 +446,13 @@ } ], "evaluator_scope": "supported_data_surface" + }, + "category_qualification": { + "category_id": "data.pricing_quote.v1", + "descriptor_id": "pricing_quote.ecommerce.v1", + "claim_status": "supported_qualified", + "positive_credit_eligibility": "eligible", + "reason_code": "qualified" } } ], @@ -779,6 +795,7 @@ "semantic_review": { "verdict": "backend_only_meaning_preserved", "compatibility_key": "sum.discount_strategy.v1", + "descriptor_id": "discount_strategy.ecommerce.v1", "reason_codes": [ "backend_only_execution_marker", "proof_helper_only_marker" @@ -813,6 +830,13 @@ } ], "evaluator_scope": "supported_sum_surface" + }, + "category_qualification": { + "category_id": "sum.discount_strategy.v1", + "descriptor_id": "discount_strategy.ecommerce.v1", + "claim_status": "supported_qualified", + "positive_credit_eligibility": "eligible", + "reason_code": "qualified" } }, { @@ -855,6 +879,7 @@ "semantic_review": { "verdict": "backend_only_meaning_preserved", "compatibility_key": "data.pricing_quote.v1", + "descriptor_id": "pricing_quote.ecommerce.v1", "reason_codes": [ "backend_only_execution_marker" ], @@ -896,6 +921,13 @@ } ], "evaluator_scope": "supported_data_surface" + }, + "category_qualification": { + "category_id": "data.pricing_quote.v1", + "descriptor_id": "pricing_quote.ecommerce.v1", + "claim_status": "supported_qualified", + "positive_credit_eligibility": "eligible", + "reason_code": "qualified" } } ], @@ -909,7 +941,7 @@ "accounting_status": "valid", "benchmark_status": "passing", "gate_status": "satisfied", - "readability_review_status": "current", + "readability_review_status": "stale", "readability_verdict": { "decision": "approved", "summary": "Generated benchmark surfaces remain readable and structurally traceable for the seeded BENCH-ECOM closure." @@ -1038,6 +1070,13 @@ "classification": "supported", "status": "valid", "semantic_support_status": "supported", + "category_qualification": { + "category_id": "sum.discount_strategy.v1", + "descriptor_id": "discount_strategy.ecommerce.v1", + "claim_status": "supported_qualified", + "positive_credit_eligibility": "eligible", + "reason_code": "qualified" + }, "proof_refs": { "passport": "examples/ecommerce/units/pricing/discount_strategy.spec.passport.json", "covering_molecule_evidence": [ @@ -1053,6 +1092,13 @@ "classification": "supported", "status": "valid", "semantic_support_status": "supported", + "category_qualification": { + "category_id": "data.pricing_quote.v1", + "descriptor_id": "pricing_quote.ecommerce.v1", + "claim_status": "supported_qualified", + "positive_credit_eligibility": "eligible", + "reason_code": "qualified" + }, "proof_refs": { "passport": "examples/ecommerce/units/pricing/pricing_quote.spec.passport.json", "covering_molecule_evidence": [ diff --git a/spec-cli/tests/fixtures/benchmarks/status-ecommerce-pricing-partial-full.json b/spec-cli/tests/fixtures/benchmarks/status-ecommerce-pricing-partial-full.json index 510e1da..c6e97e0 100644 --- a/spec-cli/tests/fixtures/benchmarks/status-ecommerce-pricing-partial-full.json +++ b/spec-cli/tests/fixtures/benchmarks/status-ecommerce-pricing-partial-full.json @@ -1,5 +1,5 @@ { - "schema_version": 4, + "schema_version": 5, "roots": [], "units": [], "benchmarks": [ @@ -77,6 +77,13 @@ "classification": "supported", "status": "valid", "semantic_support_status": "supported", + "category_qualification": { + "category_id": "sum.discount_strategy.v1", + "descriptor_id": "discount_strategy.ecommerce.v1", + "claim_status": "supported_qualified", + "positive_credit_eligibility": "eligible", + "reason_code": "qualified" + }, "proof_refs": { "passport": "examples/ecommerce/units/pricing/discount_strategy.spec.passport.json", "covering_molecule_evidence": [ @@ -92,6 +99,13 @@ "classification": "supported", "status": "valid", "semantic_support_status": "supported", + "category_qualification": { + "category_id": "data.pricing_quote.v1", + "descriptor_id": "pricing_quote.ecommerce.v1", + "claim_status": "supported_qualified", + "positive_credit_eligibility": "eligible", + "reason_code": "qualified" + }, "proof_refs": { "passport": "examples/ecommerce/units/pricing/pricing_quote.spec.passport.json", "covering_molecule_evidence": [ diff --git a/spec-cli/tests/fixtures/benchmarks/status-repo-root-contract.json b/spec-cli/tests/fixtures/benchmarks/status-repo-root-contract.json index d72101c..7a14f2b 100644 --- a/spec-cli/tests/fixtures/benchmarks/status-repo-root-contract.json +++ b/spec-cli/tests/fixtures/benchmarks/status-repo-root-contract.json @@ -1,4 +1,4 @@ { - "schema_version": 4, + "schema_version": 5, "scope_authority": "inventory_only" } diff --git a/spec-cli/tests/fixtures/benchmarks/status-repo-root-full.json b/spec-cli/tests/fixtures/benchmarks/status-repo-root-full.json index 16b48d5..d7a53db 100644 --- a/spec-cli/tests/fixtures/benchmarks/status-repo-root-full.json +++ b/spec-cli/tests/fixtures/benchmarks/status-repo-root-full.json @@ -1,5 +1,5 @@ { - "schema_version": 4, + "schema_version": 5, "scope_authority": "inventory_only", "roots": [ { @@ -481,6 +481,7 @@ "semantic_review": { "verdict": "backend_only_meaning_preserved", "compatibility_key": "sum.discount_strategy.v1", + "descriptor_id": "discount_strategy.ecommerce.v1", "reason_codes": [ "backend_only_execution_marker", "proof_helper_only_marker" @@ -515,6 +516,13 @@ } ], "evaluator_scope": "supported_sum_surface" + }, + "category_qualification": { + "category_id": "sum.discount_strategy.v1", + "descriptor_id": "discount_strategy.ecommerce.v1", + "claim_status": "supported_qualified", + "positive_credit_eligibility": "eligible", + "reason_code": "qualified" } }, { @@ -557,6 +565,7 @@ "semantic_review": { "verdict": "backend_only_meaning_preserved", "compatibility_key": "data.pricing_quote.v1", + "descriptor_id": "pricing_quote.ecommerce.v1", "reason_codes": [ "backend_only_execution_marker" ], @@ -598,6 +607,13 @@ } ], "evaluator_scope": "supported_data_surface" + }, + "category_qualification": { + "category_id": "data.pricing_quote.v1", + "descriptor_id": "pricing_quote.ecommerce.v1", + "claim_status": "supported_qualified", + "positive_credit_eligibility": "eligible", + "reason_code": "qualified" } } ], @@ -2693,6 +2709,7 @@ "semantic_review": { "verdict": "backend_only_meaning_preserved", "compatibility_key": "sum.discount_strategy.v1", + "descriptor_id": "discount_strategy.ecommerce.v1", "reason_codes": [ "backend_only_execution_marker", "proof_helper_only_marker" @@ -2727,6 +2744,13 @@ } ], "evaluator_scope": "supported_sum_surface" + }, + "category_qualification": { + "category_id": "sum.discount_strategy.v1", + "descriptor_id": "discount_strategy.ecommerce.v1", + "claim_status": "supported_qualified", + "positive_credit_eligibility": "eligible", + "reason_code": "qualified" } }, { @@ -2769,6 +2793,7 @@ "semantic_review": { "verdict": "backend_only_meaning_preserved", "compatibility_key": "data.pricing_quote.v1", + "descriptor_id": "pricing_quote.ecommerce.v1", "reason_codes": [ "backend_only_execution_marker" ], @@ -2810,6 +2835,13 @@ } ], "evaluator_scope": "supported_data_surface" + }, + "category_qualification": { + "category_id": "data.pricing_quote.v1", + "descriptor_id": "pricing_quote.ecommerce.v1", + "claim_status": "supported_qualified", + "positive_credit_eligibility": "eligible", + "reason_code": "qualified" } }, { @@ -4315,7 +4347,7 @@ "accounting_status": "valid", "benchmark_status": "passing", "gate_status": "satisfied", - "readability_review_status": "current", + "readability_review_status": "stale", "readability_verdict": { "decision": "approved", "summary": "Generated benchmark surfaces remain readable and structurally traceable for the seeded BENCH-ECOM closure." @@ -4444,6 +4476,13 @@ "classification": "supported", "status": "valid", "semantic_support_status": "supported", + "category_qualification": { + "category_id": "sum.discount_strategy.v1", + "descriptor_id": "discount_strategy.ecommerce.v1", + "claim_status": "supported_qualified", + "positive_credit_eligibility": "eligible", + "reason_code": "qualified" + }, "proof_refs": { "passport": "examples/ecommerce/units/pricing/discount_strategy.spec.passport.json", "covering_molecule_evidence": [ @@ -4459,6 +4498,13 @@ "classification": "supported", "status": "valid", "semantic_support_status": "supported", + "category_qualification": { + "category_id": "data.pricing_quote.v1", + "descriptor_id": "pricing_quote.ecommerce.v1", + "claim_status": "supported_qualified", + "positive_credit_eligibility": "eligible", + "reason_code": "qualified" + }, "proof_refs": { "passport": "examples/ecommerce/units/pricing/pricing_quote.spec.passport.json", "covering_molecule_evidence": [ @@ -4489,10 +4535,10 @@ "lifecycle": "active", "required_for_v1": true, "path_scope": "full", - "accounting_status": "valid", - "benchmark_status": "passing", - "gate_status": "satisfied", - "readability_review_status": "current", + "accounting_status": "invalid", + "benchmark_status": "invalid", + "gate_status": "open", + "readability_review_status": "stale", "readability_verdict": { "decision": "approved", "summary": "Generated service benchmark surfaces remain readable and structurally traceable for the activated service closure." @@ -4503,7 +4549,7 @@ "total_cases": 6, "supported_cases": 6, "supported_valid_cases": 6, - "positive_credit_cases": 6, + "positive_credit_cases": 0, "case_status_counts": { "invalid": 0, "failing": 0, @@ -4553,7 +4599,7 @@ "examples/service/units/billing/checkout_success_flow.test.evidence.json" ] }, - "counts_as_supported_positive": true + "counts_as_supported_positive": false }, { "case_id": "billing/apply_regional_fee", @@ -4569,7 +4615,7 @@ "examples/service/units/billing/discount_strategy_quote_flow.test.evidence.json" ] }, - "counts_as_supported_positive": true + "counts_as_supported_positive": false }, { "case_id": "billing/checkout_net_total", @@ -4584,7 +4630,7 @@ "examples/service/units/billing/checkout_success_flow.test.evidence.json" ] }, - "counts_as_supported_positive": true + "counts_as_supported_positive": false }, { "case_id": "billing/checkout_net_total_guarded_fee", @@ -4599,7 +4645,7 @@ "examples/service/units/billing/checkout_declined_discount_flow.test.evidence.json" ] }, - "counts_as_supported_positive": true + "counts_as_supported_positive": false }, { "case_id": "billing/discount_strategy", @@ -4614,7 +4660,7 @@ "examples/service/units/billing/discount_strategy_quote_flow.test.evidence.json" ] }, - "counts_as_supported_positive": true + "counts_as_supported_positive": false }, { "case_id": "billing/pricing_quote", @@ -4630,7 +4676,7 @@ "examples/service/units/billing/discount_strategy_quote_flow.test.evidence.json" ] }, - "counts_as_supported_positive": true + "counts_as_supported_positive": false } ], "readability_generated_files": [ diff --git a/spec-cli/tests/fixtures/benchmarks/status-repo-root-service-full.json b/spec-cli/tests/fixtures/benchmarks/status-repo-root-service-full.json index 682fe31..d7a53db 100644 --- a/spec-cli/tests/fixtures/benchmarks/status-repo-root-service-full.json +++ b/spec-cli/tests/fixtures/benchmarks/status-repo-root-service-full.json @@ -1,5 +1,5 @@ { - "schema_version": 4, + "schema_version": 5, "scope_authority": "inventory_only", "roots": [ { @@ -481,6 +481,7 @@ "semantic_review": { "verdict": "backend_only_meaning_preserved", "compatibility_key": "sum.discount_strategy.v1", + "descriptor_id": "discount_strategy.ecommerce.v1", "reason_codes": [ "backend_only_execution_marker", "proof_helper_only_marker" @@ -515,6 +516,13 @@ } ], "evaluator_scope": "supported_sum_surface" + }, + "category_qualification": { + "category_id": "sum.discount_strategy.v1", + "descriptor_id": "discount_strategy.ecommerce.v1", + "claim_status": "supported_qualified", + "positive_credit_eligibility": "eligible", + "reason_code": "qualified" } }, { @@ -557,6 +565,7 @@ "semantic_review": { "verdict": "backend_only_meaning_preserved", "compatibility_key": "data.pricing_quote.v1", + "descriptor_id": "pricing_quote.ecommerce.v1", "reason_codes": [ "backend_only_execution_marker" ], @@ -598,6 +607,13 @@ } ], "evaluator_scope": "supported_data_surface" + }, + "category_qualification": { + "category_id": "data.pricing_quote.v1", + "descriptor_id": "pricing_quote.ecommerce.v1", + "claim_status": "supported_qualified", + "positive_credit_eligibility": "eligible", + "reason_code": "qualified" } } ], @@ -998,898 +1014,3185 @@ } ], "molecule_tests": [] - } - ], - "units": [ + }, { - "id": "pricing/apply_discount", - "status": "valid", - "errors": [], - "evidence_at": "", - "freshness": { - "authored_truth_digest": "", - "authored_truth_status": "fresh", - "backend_execution_status": "unknown" - }, - "semantic_review": { - "verdict": "aligned", - "compatibility_key": "function.arithmetic_leaf.monotone_down_nonnegative.v1", - "support_status": "supported", - "summary": "authored semantics and executable lowering agree on the supported function surface", - "authored_surfaces": [ - { - "path": "intent.why", - "summary": "Apply a discount while importing the shared round helper from a sibling spec library." - }, - { - "path": "contract.inputs", - "summary": "2 authored input(s)" - }, - { - "path": "contract.returns", - "summary": "returns Decimal" - }, - { - "path": "deps", - "summary": "1 declared dep(s)" + "root": "semantic-families/function.arithmetic_leaf.monotone_down_nonnegative.v1/fixtures/aligned", + "units": [ + { + "id": "money/round", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" } - ], - "executable_surfaces": [ - { - "path": "body.rust", - "summary": "projects to Rust function apply_discount with 2 input(s)" + }, + { + "id": "pricing/apply_discount_aligned", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" } - ], - "evaluator_scope": "supported_function_surface" - } + } + ], + "molecule_tests": [] }, { - "id": "pricing/apply_tax", - "status": "valid", - "errors": [], - "evidence_at": "", - "freshness": { - "authored_truth_digest": "", - "authored_truth_status": "fresh", - "backend_execution_status": "unknown" - }, - "semantic_review": { - "verdict": "aligned", - "compatibility_key": "function.arithmetic_leaf.monotone_up.v1", - "support_status": "supported", - "summary": "authored semantics and executable lowering agree on the supported function surface", - "authored_surfaces": [ - { - "path": "intent.why", - "summary": "Apply tax while importing the shared round helper from a sibling spec library." - }, - { - "path": "contract.inputs", - "summary": "2 authored input(s)" - }, - { - "path": "contract.returns", - "summary": "returns Decimal" - }, - { - "path": "deps", - "summary": "1 declared dep(s)" - }, - { - "path": "body.typescript", - "summary": "authored TypeScript body present" + "root": "semantic-families/function.arithmetic_leaf.monotone_down_nonnegative.v1/fixtures/drift", + "units": [ + { + "id": "money/round", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" } - ], - "executable_surfaces": [ - { - "path": "body.rust", - "summary": "projects to Rust function apply_tax with 2 input(s)" + }, + { + "id": "pricing/apply_discount_drift", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" } - ], - "evaluator_scope": "supported_function_surface" - } + } + ], + "molecule_tests": [] }, { - "id": "pricing/calculate_total", - "status": "valid", - "errors": [], - "evidence_at": "", - "freshness": { - "authored_truth_digest": "", - "authored_truth_status": "fresh", - "backend_execution_status": "unknown" - } + "root": "semantic-families/function.arithmetic_leaf.monotone_down_nonnegative.v1/fixtures/under_specified", + "units": [ + { + "id": "money/round", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/apply_discount_under_specified", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + } + ], + "molecule_tests": [] }, { - "id": "pricing/checkout_nested_chain3", - "status": "valid", - "errors": [], - "evidence_at": "", - "freshness": { - "authored_truth_digest": "", - "authored_truth_status": "fresh", - "backend_execution_status": "unknown" - }, - "semantic_review": { - "verdict": "under_specified", - "compatibility_key": "unsupported.function.v1", - "support_status": "unsupported", - "unsupported_reason_codes": [ - "unsupported_dep_topology" - ], - "rewrite_hints": [ - "Use zero or one helper dep for arithmetic leaves, exactly two supported dep callables for wrapper pipelines, or exactly three supported function dep callables for chain3 pipelines." - ], - "reason_codes": [ - "unsupported_surface" - ], - "summary": "declared function deps fall outside the supported reviewer topology", - "authored_surfaces": [ - { - "path": "intent.why", - "summary": "Return the app checkout total by extending the shared nested chain3 with one outer surcharge and loyalty discount." - }, - { - "path": "contract.inputs", - "summary": "5 authored input(s)" - }, - { - "path": "contract.returns", - "summary": "returns Decimal" - }, - { - "path": "deps", - "summary": "3 declared dep(s)" - }, - { - "path": "body.typescript", - "summary": "authored TypeScript body present" + "root": "semantic-families/function.arithmetic_leaf.monotone_down_nonnegative.v1/fixtures/unsupported_near_miss", + "units": [ + { + "id": "money/round", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" } - ], - "executable_surfaces": [ - { - "path": "body.rust", - "summary": "projects to Rust function checkout_nested_chain3 with 5 input(s)" + }, + { + "id": "pricing/apply_discount_control_flow_unsupported_near_miss", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" } - ], - "evaluator_scope": "unsupported_surface" - } + } + ], + "molecule_tests": [] }, { - "id": "money/round", - "status": "valid", - "errors": [], - "evidence_at": "", - "freshness": { - "authored_truth_digest": "", - "authored_truth_status": "fresh", - "backend_execution_status": "unknown" - }, - "semantic_review": { - "verdict": "aligned", - "compatibility_key": "function.helper.identity_passthrough.v1", - "support_status": "supported", - "summary": "authored semantics and executable lowering agree on the supported function surface", - "authored_surfaces": [ - { - "path": "intent.why", - "summary": "Round monetary values (placeholder for compilation proof)." - }, - { - "path": "contract.inputs", - "summary": "1 authored input(s)" + "root": "semantic-families/function.arithmetic_leaf.monotone_up.v1/fixtures/aligned", + "units": [ + { + "id": "money/round", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "fresh", + "backend_execution_status": "unknown" }, - { - "path": "contract.returns", - "summary": "returns Decimal" + "semantic_review": { + "verdict": "aligned", + "compatibility_key": "function.helper.identity_passthrough.v1", + "support_status": "supported", + "summary": "authored semantics and executable lowering agree on the supported function surface", + "authored_surfaces": [ + { + "path": "intent.why", + "summary": "Round monetary values (placeholder for compilation proof)." + }, + { + "path": "contract.inputs", + "summary": "1 authored input(s)" + }, + { + "path": "contract.returns", + "summary": "returns Decimal" + }, + { + "path": "body.typescript", + "summary": "authored TypeScript body present" + } + ], + "executable_surfaces": [ + { + "path": "body.rust", + "summary": "projects to Rust function round with 1 input(s)" + } + ], + "evaluator_scope": "supported_function_surface" + } + }, + { + "id": "pricing/apply_tax_aligned", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "fresh", + "backend_execution_status": "unknown" }, - { - "path": "body.typescript", - "summary": "authored TypeScript body present" + "semantic_review": { + "verdict": "aligned", + "compatibility_key": "function.arithmetic_leaf.monotone_up.v1", + "support_status": "supported", + "summary": "authored semantics and executable lowering agree on the supported function surface", + "authored_surfaces": [ + { + "path": "intent.why", + "summary": "Add sales tax to a subtotal using a rate expressed as a decimal fraction and round the total." + }, + { + "path": "contract.inputs", + "summary": "2 authored input(s)" + }, + { + "path": "contract.returns", + "summary": "returns Decimal" + }, + { + "path": "contract.invariants", + "summary": "1 authored invariant(s)" + }, + { + "path": "deps", + "summary": "1 declared dep(s)" + }, + { + "path": "body.typescript", + "summary": "authored TypeScript body present" + } + ], + "executable_surfaces": [ + { + "path": "body.rust", + "summary": "projects to Rust function apply_tax_aligned with 2 input(s)" + } + ], + "evaluator_scope": "supported_function_surface" } - ], - "executable_surfaces": [ - { - "path": "body.rust", - "summary": "projects to Rust function round with 1 input(s)" + } + ], + "molecule_tests": [] + }, + { + "root": "semantic-families/function.arithmetic_leaf.monotone_up.v1/fixtures/drift", + "units": [ + { + "id": "pricing/apply_tax_drift", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" } - ], - "evaluator_scope": "supported_function_surface" - } + } + ], + "molecule_tests": [] }, { - "id": "pricing/apply_discount", - "status": "valid", - "errors": [], - "evidence_at": "", - "freshness": { - "authored_truth_digest": "", - "authored_truth_status": "fresh", - "backend_execution_status": "unknown" - }, - "semantic_review": { - "verdict": "aligned", - "compatibility_key": "function.arithmetic_leaf.monotone_down_nonnegative.v1", - "support_status": "supported", - "summary": "authored semantics and executable lowering agree on the supported function surface", - "authored_surfaces": [ - { - "path": "intent.why", - "summary": "Apply a discount to a subtotal while keeping the result nonnegative." - }, - { - "path": "contract.inputs", - "summary": "2 authored input(s)" - }, - { - "path": "contract.returns", - "summary": "returns Decimal" - }, - { - "path": "contract.invariants", - "summary": "2 authored invariant(s)" - }, - { - "path": "deps", - "summary": "1 declared dep(s)" + "root": "semantic-families/function.arithmetic_leaf.monotone_up.v1/fixtures/under_specified", + "units": [ + { + "id": "pricing/apply_tax_under_specified", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" } - ], - "executable_surfaces": [ - { - "path": "body.rust", - "summary": "projects to Rust function apply_discount with 2 input(s)" + } + ], + "molecule_tests": [] + }, + { + "root": "semantic-families/function.arithmetic_leaf.monotone_up.v1/fixtures/unsupported_near_miss", + "units": [ + { + "id": "pricing/apply_tax_control_flow_unsupported_near_miss", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" } - ], - "evaluator_scope": "supported_function_surface" + } + ], + "molecule_tests": [] + }, + { + "root": "semantic-families/function.helper.identity_passthrough.v1/fixtures/aligned", + "units": [ + { + "id": "money/round", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "passthrough/round", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + } + ], + "molecule_tests": [] + }, + { + "root": "semantic-families/function.helper.identity_passthrough.v1/fixtures/drift", + "units": [ + { + "id": "money/round", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "semantic_drift/round", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + } + ], + "molecule_tests": [] + }, + { + "root": "semantic-families/function.helper.identity_passthrough.v1/fixtures/under_specified", + "units": [ + { + "id": "money/round", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "vague/round", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + } + ], + "molecule_tests": [] + }, + { + "root": "semantic-families/function.helper.identity_passthrough.v1/fixtures/unsupported_near_miss", + "units": [ + { + "id": "control_flow/round", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "money/round", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + } + ], + "molecule_tests": [] + }, + { + "root": "semantic-families/function.wrapper.pipeline.chain3.v1/fixtures/aligned", + "units": [ + { + "id": "pricing/base_nested_chain3_aligned", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/checkout_chain3_aligned", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/checkout_nested_chain3_aligned", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/pricing_discount_leaf_aligned", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/pricing_tax_leaf_aligned", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/pricing_total_wrapper_aligned", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + } + ], + "molecule_tests": [] + }, + { + "root": "semantic-families/function.wrapper.pipeline.chain3.v1/fixtures/drift", + "units": [ + { + "id": "pricing/checkout_chain3_drift", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/pricing_discount_leaf_drift", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/pricing_tax_leaf_drift", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/pricing_total_wrapper_drift", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + } + ], + "molecule_tests": [] + }, + { + "root": "semantic-families/function.wrapper.pipeline.chain3.v1/fixtures/under_specified", + "units": [ + { + "id": "pricing/checkout_chain3_under_specified", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/pricing_discount_leaf_under_specified", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/pricing_tax_leaf_under_specified", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/pricing_total_wrapper_under_specified", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + } + ], + "molecule_tests": [] + }, + { + "root": "semantic-families/function.wrapper.pipeline.chain3.v1/fixtures/unsupported_near_miss", + "units": [ + { + "id": "pricing/checkout_chain3_unsupported_near_miss", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/pricing_discount_leaf_unsupported_near_miss", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/pricing_tax_leaf_unsupported_near_miss", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/pricing_total_wrapper_unsupported_near_miss", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + } + ], + "molecule_tests": [] + }, + { + "root": "semantic-families/function.wrapper.pipeline.normalized_required_arg.v1/fixtures/aligned", + "units": [ + { + "id": "pricing/pricing_discount_leaf_aligned", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/pricing_tax_leaf_aligned", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/pricing_total_wrapper_aligned", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + } + ], + "molecule_tests": [] + }, + { + "root": "semantic-families/function.wrapper.pipeline.normalized_required_arg.v1/fixtures/drift", + "units": [ + { + "id": "pricing/pricing_discount_leaf_drift", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/pricing_tax_leaf_drift", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/pricing_total_wrapper_drift", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + } + ], + "molecule_tests": [] + }, + { + "root": "semantic-families/function.wrapper.pipeline.normalized_required_arg.v1/fixtures/under_specified", + "units": [ + { + "id": "pricing/pricing_discount_leaf_under_specified", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/pricing_tax_leaf_under_specified", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/pricing_total_wrapper_under_specified", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + } + ], + "molecule_tests": [] + }, + { + "root": "semantic-families/function.wrapper.pipeline.normalized_required_arg.v1/fixtures/unsupported_near_miss", + "units": [ + { + "id": "pricing/pricing_discount_leaf_unsupported_near_miss", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/pricing_tax_leaf_unsupported_near_miss", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/pricing_total_wrapper_unsupported_near_miss", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + } + ], + "molecule_tests": [] + }, + { + "root": "semantic-families/function.wrapper.pipeline.v1/fixtures/aligned", + "units": [ + { + "id": "pricing/pricing_discount_leaf_aligned", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/pricing_tax_leaf_aligned", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/pricing_total_wrapper_aligned", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + } + ], + "molecule_tests": [] + }, + { + "root": "semantic-families/function.wrapper.pipeline.v1/fixtures/drift", + "units": [ + { + "id": "pricing/pricing_discount_leaf_drift", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/pricing_tax_leaf_drift", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/pricing_total_wrapper_drift", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + } + ], + "molecule_tests": [] + }, + { + "root": "semantic-families/function.wrapper.pipeline.v1/fixtures/under_specified", + "units": [ + { + "id": "pricing/pricing_discount_leaf_under_specified", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/pricing_tax_leaf_under_specified", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/pricing_total_wrapper_under_specified", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + } + ], + "molecule_tests": [] + }, + { + "root": "semantic-families/function.wrapper.pipeline.v1/fixtures/unsupported_near_miss", + "units": [ + { + "id": "pricing/pricing_discount_leaf_unsupported_near_miss", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/pricing_tax_leaf_unsupported_near_miss", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/pricing_total_wrapper_unsupported_near_miss", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + } + ], + "molecule_tests": [] + }, + { + "root": "spec-cli/tests/fixtures/m19/semantic_falsification_pack", + "units": [ + { + "id": "billing/apply_membership_discount", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "billing/apply_membership_discount_drift", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "billing/apply_membership_discount_under_specified", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "billing/apply_membership_discount_unsupported_near_miss", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "billing/apply_regional_fee", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "billing/apply_regional_fee_drift", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "billing/apply_regional_fee_under_specified", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "billing/apply_regional_fee_unsupported_near_miss", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "billing/checkout_net_total", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "billing/checkout_net_total_drift", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "billing/checkout_net_total_under_specified", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "billing/checkout_net_total_unsupported_near_miss", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + } + ], + "molecule_tests": [] + }, + { + "root": "spec-cli/tests/fixtures/m20/unsupported_truth_pack", + "units": [ + { + "id": "money/round", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/apply_discount", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/apply_discount_control_flow", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/apply_tax", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/apply_tax_control_flow", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/calculate_total", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/checkout_total", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/checkout_total_bad_body_shape", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/checkout_total_bad_dep_topology", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + } + ], + "molecule_tests": [] + }, + { + "root": "spec-cli/tests/fixtures/typescript_local_supported_graph", + "units": [ + { + "id": "money/round", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/apply_discount", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/apply_tax", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/calculate_total", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/checkout_total", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/display_total", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + } + ], + "molecule_tests": [] + } + ], + "units": [ + { + "id": "pricing/apply_discount", + "status": "valid", + "errors": [], + "evidence_at": "", + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "fresh", + "backend_execution_status": "unknown" + }, + "semantic_review": { + "verdict": "aligned", + "compatibility_key": "function.arithmetic_leaf.monotone_down_nonnegative.v1", + "support_status": "supported", + "summary": "authored semantics and executable lowering agree on the supported function surface", + "authored_surfaces": [ + { + "path": "intent.why", + "summary": "Apply a discount while importing the shared round helper from a sibling spec library." + }, + { + "path": "contract.inputs", + "summary": "2 authored input(s)" + }, + { + "path": "contract.returns", + "summary": "returns Decimal" + }, + { + "path": "deps", + "summary": "1 declared dep(s)" + } + ], + "executable_surfaces": [ + { + "path": "body.rust", + "summary": "projects to Rust function apply_discount with 2 input(s)" + } + ], + "evaluator_scope": "supported_function_surface" + } + }, + { + "id": "pricing/apply_tax", + "status": "valid", + "errors": [], + "evidence_at": "", + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "fresh", + "backend_execution_status": "unknown" + }, + "semantic_review": { + "verdict": "aligned", + "compatibility_key": "function.arithmetic_leaf.monotone_up.v1", + "support_status": "supported", + "summary": "authored semantics and executable lowering agree on the supported function surface", + "authored_surfaces": [ + { + "path": "intent.why", + "summary": "Apply tax while importing the shared round helper from a sibling spec library." + }, + { + "path": "contract.inputs", + "summary": "2 authored input(s)" + }, + { + "path": "contract.returns", + "summary": "returns Decimal" + }, + { + "path": "deps", + "summary": "1 declared dep(s)" + }, + { + "path": "body.typescript", + "summary": "authored TypeScript body present" + } + ], + "executable_surfaces": [ + { + "path": "body.rust", + "summary": "projects to Rust function apply_tax with 2 input(s)" + } + ], + "evaluator_scope": "supported_function_surface" + } + }, + { + "id": "pricing/calculate_total", + "status": "valid", + "errors": [], + "evidence_at": "", + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "fresh", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/checkout_nested_chain3", + "status": "valid", + "errors": [], + "evidence_at": "", + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "fresh", + "backend_execution_status": "unknown" + }, + "semantic_review": { + "verdict": "under_specified", + "compatibility_key": "unsupported.function.v1", + "support_status": "unsupported", + "unsupported_reason_codes": [ + "unsupported_dep_topology" + ], + "rewrite_hints": [ + "Use zero or one helper dep for arithmetic leaves, exactly two supported dep callables for wrapper pipelines, or exactly three supported function dep callables for chain3 pipelines." + ], + "reason_codes": [ + "unsupported_surface" + ], + "summary": "declared function deps fall outside the supported reviewer topology", + "authored_surfaces": [ + { + "path": "intent.why", + "summary": "Return the app checkout total by extending the shared nested chain3 with one outer surcharge and loyalty discount." + }, + { + "path": "contract.inputs", + "summary": "5 authored input(s)" + }, + { + "path": "contract.returns", + "summary": "returns Decimal" + }, + { + "path": "deps", + "summary": "3 declared dep(s)" + }, + { + "path": "body.typescript", + "summary": "authored TypeScript body present" + } + ], + "executable_surfaces": [ + { + "path": "body.rust", + "summary": "projects to Rust function checkout_nested_chain3 with 5 input(s)" + } + ], + "evaluator_scope": "unsupported_surface" + } + }, + { + "id": "money/round", + "status": "valid", + "errors": [], + "evidence_at": "", + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "fresh", + "backend_execution_status": "unknown" + }, + "semantic_review": { + "verdict": "aligned", + "compatibility_key": "function.helper.identity_passthrough.v1", + "support_status": "supported", + "summary": "authored semantics and executable lowering agree on the supported function surface", + "authored_surfaces": [ + { + "path": "intent.why", + "summary": "Round monetary values (placeholder for compilation proof)." + }, + { + "path": "contract.inputs", + "summary": "1 authored input(s)" + }, + { + "path": "contract.returns", + "summary": "returns Decimal" + }, + { + "path": "body.typescript", + "summary": "authored TypeScript body present" + } + ], + "executable_surfaces": [ + { + "path": "body.rust", + "summary": "projects to Rust function round with 1 input(s)" + } + ], + "evaluator_scope": "supported_function_surface" + } + }, + { + "id": "pricing/apply_discount", + "status": "valid", + "errors": [], + "evidence_at": "", + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "fresh", + "backend_execution_status": "unknown" + }, + "semantic_review": { + "verdict": "aligned", + "compatibility_key": "function.arithmetic_leaf.monotone_down_nonnegative.v1", + "support_status": "supported", + "summary": "authored semantics and executable lowering agree on the supported function surface", + "authored_surfaces": [ + { + "path": "intent.why", + "summary": "Apply a discount to a subtotal while keeping the result nonnegative." + }, + { + "path": "contract.inputs", + "summary": "2 authored input(s)" + }, + { + "path": "contract.returns", + "summary": "returns Decimal" + }, + { + "path": "contract.invariants", + "summary": "2 authored invariant(s)" + }, + { + "path": "deps", + "summary": "1 declared dep(s)" + } + ], + "executable_surfaces": [ + { + "path": "body.rust", + "summary": "projects to Rust function apply_discount with 2 input(s)" + } + ], + "evaluator_scope": "supported_function_surface" + } + }, + { + "id": "pricing/apply_tax", + "status": "valid", + "errors": [], + "evidence_at": "", + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "fresh", + "backend_execution_status": "unknown" + }, + "semantic_review": { + "verdict": "aligned", + "compatibility_key": "function.arithmetic_leaf.monotone_up.v1", + "support_status": "supported", + "summary": "authored semantics and executable lowering agree on the supported function surface", + "authored_surfaces": [ + { + "path": "intent.why", + "summary": "Add sales tax to a subtotal using a rate expressed as a decimal fraction and round the total." + }, + { + "path": "contract.inputs", + "summary": "2 authored input(s)" + }, + { + "path": "contract.returns", + "summary": "returns Decimal" + }, + { + "path": "contract.invariants", + "summary": "1 authored invariant(s)" + }, + { + "path": "deps", + "summary": "1 declared dep(s)" + }, + { + "path": "body.typescript", + "summary": "authored TypeScript body present" + } + ], + "executable_surfaces": [ + { + "path": "body.rust", + "summary": "projects to Rust function apply_tax with 2 input(s)" + } + ], + "evaluator_scope": "supported_function_surface" + } + }, + { + "id": "pricing/calculate_total", + "status": "valid", + "errors": [], + "evidence_at": "", + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "fresh", + "backend_execution_status": "unknown" + }, + "semantic_review": { + "verdict": "aligned", + "compatibility_key": "function.wrapper.pipeline.v1", + "support_status": "supported", + "summary": "authored semantics and executable lowering agree on the supported function surface", + "authored_surfaces": [ + { + "path": "intent.why", + "summary": "Combine discount and tax so a checkout flow can produce the final price." + }, + { + "path": "contract.inputs", + "summary": "3 authored input(s)" + }, + { + "path": "contract.returns", + "summary": "returns Decimal" + }, + { + "path": "contract.invariants", + "summary": "1 authored invariant(s)" + }, + { + "path": "deps", + "summary": "2 declared dep(s)" + }, + { + "path": "body.typescript", + "summary": "authored TypeScript body present" + } + ], + "executable_surfaces": [ + { + "path": "body.rust", + "summary": "projects to Rust function calculate_total with 3 input(s)" + } + ], + "evaluator_scope": "supported_function_surface" + } + }, + { + "id": "pricing/calculate_total_guarded_tax", + "status": "valid", + "errors": [], + "evidence_at": "", + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "fresh", + "backend_execution_status": "unknown" + }, + "semantic_review": { + "verdict": "aligned", + "compatibility_key": "function.wrapper.pipeline.normalized_required_arg.v1", + "support_status": "supported", + "summary": "authored semantics and executable lowering agree on the supported function surface", + "authored_surfaces": [ + { + "path": "intent.why", + "summary": "Return the total after discounting the subtotal and then applying tax while clamping the tax rate at zero." + }, + { + "path": "contract.inputs", + "summary": "3 authored input(s)" + }, + { + "path": "contract.returns", + "summary": "returns Decimal" + }, + { + "path": "contract.invariants", + "summary": "1 authored invariant(s)" + }, + { + "path": "deps", + "summary": "2 declared dep(s)" + } + ], + "executable_surfaces": [ + { + "path": "body.rust", + "summary": "projects to Rust function calculate_total_guarded_tax with 3 input(s)" + } + ], + "evaluator_scope": "supported_function_surface" + } + }, + { + "id": "pricing/discount_strategy", + "status": "valid", + "errors": [], + "evidence_at": "", + "freshness": { + "authored_truth_digest": "", + "backend_execution_digest": "sha256:06039f3e8df3a5ebeb557763c70a82a999f315e054df964a4af0bf7fd531f2e1", + "authored_truth_status": "fresh", + "backend_execution_status": "fresh" + }, + "markers": [ + { + "id": "backend_rust_derives", + "path": "backends.rust.derives" + }, + { + "id": "method_lowering_rust_body", + "path": "methods.discount_amount.lowering.rust.body" + }, + { + "id": "method_lowering_rust_body", + "path": "methods.discounted_subtotal.lowering.rust.body" + }, + { + "id": "method_lowering_rust_body", + "path": "methods.percentage_example_holds.lowering.rust.body" + }, + { + "id": "method_lowering_rust_body", + "path": "methods.fixed_amount_example_holds.lowering.rust.body" + }, + { + "id": "method_lowering_rust_body", + "path": "methods.fixed_amount_capped_behavior_holds.lowering.rust.body" + } + ], + "proof_coverage": [ + { + "id": "variant.none", + "surfaces": [ + "atom", + "molecule" + ] + }, + { + "id": "variant.percentage", + "surfaces": [ + "atom", + "molecule" + ] + }, + { + "id": "variant.fixed_amount", + "surfaces": [ + "atom", + "molecule" + ] + }, + { + "id": "behavior.fixed_amount_capped", + "surfaces": [ + "atom", + "molecule" + ] + } + ], + "escape_hatch_gate": { + "status": "closed", + "required_surfaces": [ + "atom", + "molecule" + ], + "present_surfaces": [ + "atom", + "molecule" + ], + "missing_surfaces": [] + }, + "semantic_review": { + "verdict": "backend_only_meaning_preserved", + "compatibility_key": "sum.discount_strategy.v1", + "descriptor_id": "discount_strategy.ecommerce.v1", + "reason_codes": [ + "backend_only_execution_marker", + "proof_helper_only_marker" + ], + "summary": "backend-only execution markers are present without changing authored meaning", + "authored_surfaces": [ + { + "path": "intent.why", + "summary": "Represent mutually exclusive discount strategies for checkout pricing." + }, + { + "path": "sum.variants", + "summary": "3 authored variant(s)" + }, + { + "path": "methods", + "summary": "2 semantic method(s) on pricing/discount_strategy" + } + ], + "executable_surfaces": [ + { + "path": "sum", + "summary": "projects to Rust enum DiscountStrategy" + }, + { + "path": "methods.*.lowering.rust.body", + "summary": "2 executable semantic method(s)" + }, + { + "path": "backends.rust.derives", + "summary": "Rust derives contribute backend-only execution metadata" + } + ], + "evaluator_scope": "supported_sum_surface" + }, + "category_qualification": { + "category_id": "sum.discount_strategy.v1", + "descriptor_id": "discount_strategy.ecommerce.v1", + "claim_status": "supported_qualified", + "positive_credit_eligibility": "eligible", + "reason_code": "qualified" + } + }, + { + "id": "pricing/pricing_quote", + "status": "valid", + "errors": [], + "evidence_at": "", + "freshness": { + "authored_truth_digest": "", + "backend_execution_digest": "sha256:c8273f70a404bcaf8efe4b6e20c12ade43c33ce209c23e7192f3c7e2fbfc8205", + "authored_truth_status": "fresh", + "backend_execution_status": "fresh" + }, + "markers": [ + { + "id": "backend_rust_derives", + "path": "backends.rust.derives" + }, + { + "id": "method_lowering_rust_body", + "path": "methods.discounted_subtotal.lowering.rust.body" + }, + { + "id": "method_lowering_rust_body", + "path": "methods.total.lowering.rust.body" + } + ], + "escape_hatch_gate": { + "status": "closed", + "required_surfaces": [ + "atom", + "molecule" + ], + "present_surfaces": [ + "atom", + "molecule" + ], + "missing_surfaces": [] + }, + "semantic_review": { + "verdict": "backend_only_meaning_preserved", + "compatibility_key": "data.pricing_quote.v1", + "descriptor_id": "pricing_quote.ecommerce.v1", + "reason_codes": [ + "backend_only_execution_marker" + ], + "summary": "backend-only execution markers are present without changing authored meaning", + "authored_surfaces": [ + { + "path": "intent.why", + "summary": "Quote a checkout total from subtotal plus discount and tax rates." + }, + { + "path": "data.fields", + "summary": "3 authored data field(s)" + }, + { + "path": "constructors", + "summary": "1 authored constructor(s)" + }, + { + "path": "methods", + "summary": "2 semantic method(s) on pricing/pricing_quote" + } + ], + "executable_surfaces": [ + { + "path": "data", + "summary": "projects to Rust struct PricingQuote" + }, + { + "path": "constructors", + "summary": "1 executable constructor(s)" + }, + { + "path": "methods.*.lowering.rust.body", + "summary": "2 executable semantic method(s)" + }, + { + "path": "backends.rust.derives", + "summary": "Rust derives contribute backend-only execution metadata" + } + ], + "evaluator_scope": "supported_data_surface" + }, + "category_qualification": { + "category_id": "data.pricing_quote.v1", + "descriptor_id": "pricing_quote.ecommerce.v1", + "claim_status": "supported_qualified", + "positive_credit_eligibility": "eligible", + "reason_code": "qualified" + } + }, + { + "id": "billing/apply_membership_discount", + "status": "valid", + "errors": [], + "evidence_at": "", + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "fresh", + "backend_execution_status": "unknown" + }, + "semantic_review": { + "verdict": "aligned", + "compatibility_key": "function.arithmetic_leaf.monotone_down_nonnegative.v1", + "support_status": "supported", + "summary": "authored semantics and executable lowering agree on the supported function surface", + "authored_surfaces": [ + { + "path": "intent.why", + "summary": "Return the subtotal after applying the membership discount rate and clamping at zero." + }, + { + "path": "contract.inputs", + "summary": "2 authored input(s)" + }, + { + "path": "contract.returns", + "summary": "returns Decimal" + }, + { + "path": "contract.invariants", + "summary": "2 authored invariant(s)" + } + ], + "executable_surfaces": [ + { + "path": "body.rust", + "summary": "projects to Rust function apply_membership_discount with 2 input(s)" + } + ], + "evaluator_scope": "supported_function_surface" + } + }, + { + "id": "billing/apply_regional_fee", + "status": "valid", + "errors": [], + "evidence_at": "", + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "fresh", + "backend_execution_status": "unknown" + }, + "semantic_review": { + "verdict": "aligned", + "compatibility_key": "function.arithmetic_leaf.monotone_up.v1", + "support_status": "supported", + "summary": "authored semantics and executable lowering agree on the supported function surface", + "authored_surfaces": [ + { + "path": "intent.why", + "summary": "Return the discounted subtotal after applying the regional fee rate." + }, + { + "path": "contract.inputs", + "summary": "2 authored input(s)" + }, + { + "path": "contract.returns", + "summary": "returns Decimal" + }, + { + "path": "contract.invariants", + "summary": "1 authored invariant(s)" + } + ], + "executable_surfaces": [ + { + "path": "body.rust", + "summary": "projects to Rust function apply_regional_fee with 2 input(s)" + } + ], + "evaluator_scope": "supported_function_surface" + } + }, + { + "id": "billing/checkout_net_total", + "status": "valid", + "errors": [], + "evidence_at": "", + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "fresh", + "backend_execution_status": "unknown" + }, + "semantic_review": { + "verdict": "aligned", + "compatibility_key": "function.wrapper.pipeline.v1", + "support_status": "supported", + "summary": "authored semantics and executable lowering agree on the supported function surface", + "authored_surfaces": [ + { + "path": "intent.why", + "summary": "Return the net checkout total after discounting the subtotal and then applying the regional fee." + }, + { + "path": "contract.inputs", + "summary": "3 authored input(s)" + }, + { + "path": "contract.returns", + "summary": "returns Decimal" + }, + { + "path": "contract.invariants", + "summary": "1 authored invariant(s)" + }, + { + "path": "deps", + "summary": "2 declared dep(s)" + } + ], + "executable_surfaces": [ + { + "path": "body.rust", + "summary": "projects to Rust function checkout_net_total with 3 input(s)" + } + ], + "evaluator_scope": "supported_function_surface" + } + }, + { + "id": "billing/checkout_net_total_guarded_fee", + "status": "valid", + "errors": [], + "evidence_at": "", + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "fresh", + "backend_execution_status": "unknown" + }, + "semantic_review": { + "verdict": "aligned", + "compatibility_key": "function.wrapper.pipeline.normalized_required_arg.v1", + "support_status": "supported", + "summary": "authored semantics and executable lowering agree on the supported function surface", + "authored_surfaces": [ + { + "path": "intent.why", + "summary": "Return the net checkout total after discounting the subtotal and applying a nonnegative regional fee only." + }, + { + "path": "contract.inputs", + "summary": "3 authored input(s)" + }, + { + "path": "contract.returns", + "summary": "returns Decimal" + }, + { + "path": "contract.invariants", + "summary": "1 authored invariant(s)" + }, + { + "path": "deps", + "summary": "2 declared dep(s)" + } + ], + "executable_surfaces": [ + { + "path": "body.rust", + "summary": "projects to Rust function checkout_net_total_guarded_fee with 3 input(s)" + } + ], + "evaluator_scope": "supported_function_surface" + } + }, + { + "id": "billing/discount_strategy", + "status": "valid", + "errors": [], + "evidence_at": "", + "freshness": { + "authored_truth_digest": "", + "backend_execution_digest": "sha256:c02a7324f2d3cfba643e1e2e46163396f6f68b0f288d702131e5836efd4f693e", + "authored_truth_status": "fresh", + "backend_execution_status": "fresh" + }, + "markers": [ + { + "id": "backend_rust_derives", + "path": "backends.rust.derives" + }, + { + "id": "method_lowering_rust_body", + "path": "methods.discount_amount.lowering.rust.body" + }, + { + "id": "method_lowering_rust_body", + "path": "methods.discounted_subtotal.lowering.rust.body" + }, + { + "id": "method_lowering_rust_body", + "path": "methods.declined_example_holds.lowering.rust.body" + }, + { + "id": "method_lowering_rust_body", + "path": "methods.percentage_example_holds.lowering.rust.body" + }, + { + "id": "method_lowering_rust_body", + "path": "methods.fixed_credit_capped_behavior_holds.lowering.rust.body" + } + ], + "escape_hatch_gate": { + "status": "closed", + "required_surfaces": [ + "atom", + "molecule" + ], + "present_surfaces": [ + "atom", + "molecule" + ], + "missing_surfaces": [] + } + }, + { + "id": "billing/pricing_quote", + "status": "valid", + "errors": [], + "evidence_at": "", + "freshness": { + "authored_truth_digest": "", + "backend_execution_digest": "sha256:055cc5bff0fee7d3cf7677dadb95219290ea4e38429003aa7605457338a7352f", + "authored_truth_status": "fresh", + "backend_execution_status": "fresh" + }, + "markers": [ + { + "id": "backend_rust_derives", + "path": "backends.rust.derives" + }, + { + "id": "method_lowering_rust_body", + "path": "methods.discounted_subtotal.lowering.rust.body" + }, + { + "id": "method_lowering_rust_body", + "path": "methods.total.lowering.rust.body" + } + ], + "escape_hatch_gate": { + "status": "closed", + "required_surfaces": [ + "atom", + "molecule" + ], + "present_surfaces": [ + "atom", + "molecule" + ], + "missing_surfaces": [] + } + }, + { + "id": "money/round", + "status": "valid", + "errors": [], + "evidence_at": "", + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "fresh", + "backend_execution_status": "unknown" + }, + "semantic_review": { + "verdict": "aligned", + "compatibility_key": "function.helper.identity_passthrough.v1", + "support_status": "supported", + "summary": "authored semantics and executable lowering agree on the supported function surface", + "authored_surfaces": [ + { + "path": "intent.why", + "summary": "Round a decimal for reuse by sibling spec libraries." + }, + { + "path": "contract.inputs", + "summary": "1 authored input(s)" + }, + { + "path": "contract.returns", + "summary": "returns Decimal" + } + ], + "executable_surfaces": [ + { + "path": "body.rust", + "summary": "projects to Rust function round with 1 input(s)" + } + ], + "evaluator_scope": "supported_function_surface" + } + }, + { + "id": "pricing/apply_discount", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/apply_tax", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/base_nested_chain3", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/calculate_total", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "money/round", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/apply_discount_aligned", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "money/round", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/apply_discount_drift", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "money/round", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/apply_discount_under_specified", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "money/round", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/apply_discount_control_flow_unsupported_near_miss", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "money/round", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "fresh", + "backend_execution_status": "unknown" + }, + "semantic_review": { + "verdict": "aligned", + "compatibility_key": "function.helper.identity_passthrough.v1", + "support_status": "supported", + "summary": "authored semantics and executable lowering agree on the supported function surface", + "authored_surfaces": [ + { + "path": "intent.why", + "summary": "Round monetary values (placeholder for compilation proof)." + }, + { + "path": "contract.inputs", + "summary": "1 authored input(s)" + }, + { + "path": "contract.returns", + "summary": "returns Decimal" + }, + { + "path": "body.typescript", + "summary": "authored TypeScript body present" + } + ], + "executable_surfaces": [ + { + "path": "body.rust", + "summary": "projects to Rust function round with 1 input(s)" + } + ], + "evaluator_scope": "supported_function_surface" + } + }, + { + "id": "pricing/apply_tax_aligned", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "fresh", + "backend_execution_status": "unknown" + }, + "semantic_review": { + "verdict": "aligned", + "compatibility_key": "function.arithmetic_leaf.monotone_up.v1", + "support_status": "supported", + "summary": "authored semantics and executable lowering agree on the supported function surface", + "authored_surfaces": [ + { + "path": "intent.why", + "summary": "Add sales tax to a subtotal using a rate expressed as a decimal fraction and round the total." + }, + { + "path": "contract.inputs", + "summary": "2 authored input(s)" + }, + { + "path": "contract.returns", + "summary": "returns Decimal" + }, + { + "path": "contract.invariants", + "summary": "1 authored invariant(s)" + }, + { + "path": "deps", + "summary": "1 declared dep(s)" + }, + { + "path": "body.typescript", + "summary": "authored TypeScript body present" + } + ], + "executable_surfaces": [ + { + "path": "body.rust", + "summary": "projects to Rust function apply_tax_aligned with 2 input(s)" + } + ], + "evaluator_scope": "supported_function_surface" + } + }, + { + "id": "pricing/apply_tax_drift", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/apply_tax_under_specified", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/apply_tax_control_flow_unsupported_near_miss", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "money/round", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "passthrough/round", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "money/round", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "semantic_drift/round", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "money/round", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "vague/round", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "control_flow/round", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "money/round", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/base_nested_chain3_aligned", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/checkout_chain3_aligned", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/checkout_nested_chain3_aligned", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/pricing_discount_leaf_aligned", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/pricing_tax_leaf_aligned", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/pricing_total_wrapper_aligned", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/checkout_chain3_drift", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/pricing_discount_leaf_drift", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/pricing_tax_leaf_drift", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/pricing_total_wrapper_drift", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/checkout_chain3_under_specified", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/pricing_discount_leaf_under_specified", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/pricing_tax_leaf_under_specified", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/pricing_total_wrapper_under_specified", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/checkout_chain3_unsupported_near_miss", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/pricing_discount_leaf_unsupported_near_miss", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/pricing_tax_leaf_unsupported_near_miss", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/pricing_total_wrapper_unsupported_near_miss", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/pricing_discount_leaf_aligned", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/pricing_tax_leaf_aligned", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/pricing_total_wrapper_aligned", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/pricing_discount_leaf_drift", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/pricing_tax_leaf_drift", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/pricing_total_wrapper_drift", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/pricing_discount_leaf_under_specified", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/pricing_tax_leaf_under_specified", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/pricing_total_wrapper_under_specified", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/pricing_discount_leaf_unsupported_near_miss", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/pricing_tax_leaf_unsupported_near_miss", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/pricing_total_wrapper_unsupported_near_miss", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/pricing_discount_leaf_aligned", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/pricing_tax_leaf_aligned", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/pricing_total_wrapper_aligned", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/pricing_discount_leaf_drift", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/pricing_tax_leaf_drift", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/pricing_total_wrapper_drift", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/pricing_discount_leaf_under_specified", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/pricing_tax_leaf_under_specified", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/pricing_total_wrapper_under_specified", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/pricing_discount_leaf_unsupported_near_miss", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" } }, { - "id": "pricing/apply_tax", - "status": "valid", + "id": "pricing/pricing_tax_leaf_unsupported_near_miss", + "status": "untested", + "reason": "no evidence", "errors": [], - "evidence_at": "", "freshness": { "authored_truth_digest": "", - "authored_truth_status": "fresh", + "authored_truth_status": "unknown", "backend_execution_status": "unknown" - }, - "semantic_review": { - "verdict": "aligned", - "compatibility_key": "function.arithmetic_leaf.monotone_up.v1", - "support_status": "supported", - "summary": "authored semantics and executable lowering agree on the supported function surface", - "authored_surfaces": [ - { - "path": "intent.why", - "summary": "Add sales tax to a subtotal using a rate expressed as a decimal fraction and round the total." - }, - { - "path": "contract.inputs", - "summary": "2 authored input(s)" - }, - { - "path": "contract.returns", - "summary": "returns Decimal" - }, - { - "path": "contract.invariants", - "summary": "1 authored invariant(s)" - }, - { - "path": "deps", - "summary": "1 declared dep(s)" - }, - { - "path": "body.typescript", - "summary": "authored TypeScript body present" - } - ], - "executable_surfaces": [ - { - "path": "body.rust", - "summary": "projects to Rust function apply_tax with 2 input(s)" - } - ], - "evaluator_scope": "supported_function_surface" } }, { - "id": "pricing/calculate_total", - "status": "valid", + "id": "pricing/pricing_total_wrapper_unsupported_near_miss", + "status": "untested", + "reason": "no evidence", "errors": [], - "evidence_at": "", "freshness": { "authored_truth_digest": "", - "authored_truth_status": "fresh", + "authored_truth_status": "unknown", "backend_execution_status": "unknown" - }, - "semantic_review": { - "verdict": "aligned", - "compatibility_key": "function.wrapper.pipeline.v1", - "support_status": "supported", - "summary": "authored semantics and executable lowering agree on the supported function surface", - "authored_surfaces": [ - { - "path": "intent.why", - "summary": "Combine discount and tax so a checkout flow can produce the final price." - }, - { - "path": "contract.inputs", - "summary": "3 authored input(s)" - }, - { - "path": "contract.returns", - "summary": "returns Decimal" - }, - { - "path": "contract.invariants", - "summary": "1 authored invariant(s)" - }, - { - "path": "deps", - "summary": "2 declared dep(s)" - }, - { - "path": "body.typescript", - "summary": "authored TypeScript body present" - } - ], - "executable_surfaces": [ - { - "path": "body.rust", - "summary": "projects to Rust function calculate_total with 3 input(s)" - } - ], - "evaluator_scope": "supported_function_surface" } }, { - "id": "pricing/calculate_total_guarded_tax", - "status": "valid", + "id": "billing/apply_membership_discount", + "status": "untested", + "reason": "no evidence", "errors": [], - "evidence_at": "", "freshness": { "authored_truth_digest": "", - "authored_truth_status": "fresh", + "authored_truth_status": "unknown", "backend_execution_status": "unknown" - }, - "semantic_review": { - "verdict": "aligned", - "compatibility_key": "function.wrapper.pipeline.normalized_required_arg.v1", - "support_status": "supported", - "summary": "authored semantics and executable lowering agree on the supported function surface", - "authored_surfaces": [ - { - "path": "intent.why", - "summary": "Return the total after discounting the subtotal and then applying tax while clamping the tax rate at zero." - }, - { - "path": "contract.inputs", - "summary": "3 authored input(s)" - }, - { - "path": "contract.returns", - "summary": "returns Decimal" - }, - { - "path": "contract.invariants", - "summary": "1 authored invariant(s)" - }, - { - "path": "deps", - "summary": "2 declared dep(s)" - } - ], - "executable_surfaces": [ - { - "path": "body.rust", - "summary": "projects to Rust function calculate_total_guarded_tax with 3 input(s)" - } - ], - "evaluator_scope": "supported_function_surface" } }, { - "id": "pricing/discount_strategy", - "status": "valid", + "id": "billing/apply_membership_discount_drift", + "status": "untested", + "reason": "no evidence", "errors": [], - "evidence_at": "", "freshness": { "authored_truth_digest": "", - "backend_execution_digest": "sha256:06039f3e8df3a5ebeb557763c70a82a999f315e054df964a4af0bf7fd531f2e1", - "authored_truth_status": "fresh", - "backend_execution_status": "fresh" - }, - "markers": [ - { - "id": "backend_rust_derives", - "path": "backends.rust.derives" - }, - { - "id": "method_lowering_rust_body", - "path": "methods.discount_amount.lowering.rust.body" - }, - { - "id": "method_lowering_rust_body", - "path": "methods.discounted_subtotal.lowering.rust.body" - }, - { - "id": "method_lowering_rust_body", - "path": "methods.percentage_example_holds.lowering.rust.body" - }, - { - "id": "method_lowering_rust_body", - "path": "methods.fixed_amount_example_holds.lowering.rust.body" - }, - { - "id": "method_lowering_rust_body", - "path": "methods.fixed_amount_capped_behavior_holds.lowering.rust.body" - } - ], - "proof_coverage": [ - { - "id": "variant.none", - "surfaces": [ - "atom", - "molecule" - ] - }, - { - "id": "variant.percentage", - "surfaces": [ - "atom", - "molecule" - ] - }, - { - "id": "variant.fixed_amount", - "surfaces": [ - "atom", - "molecule" - ] - }, - { - "id": "behavior.fixed_amount_capped", - "surfaces": [ - "atom", - "molecule" - ] - } - ], - "escape_hatch_gate": { - "status": "closed", - "required_surfaces": [ - "atom", - "molecule" - ], - "present_surfaces": [ - "atom", - "molecule" - ], - "missing_surfaces": [] - }, - "semantic_review": { - "verdict": "backend_only_meaning_preserved", - "compatibility_key": "sum.discount_strategy.v1", - "reason_codes": [ - "backend_only_execution_marker", - "proof_helper_only_marker" - ], - "summary": "backend-only execution markers are present without changing authored meaning", - "authored_surfaces": [ - { - "path": "intent.why", - "summary": "Represent mutually exclusive discount strategies for checkout pricing." - }, - { - "path": "sum.variants", - "summary": "3 authored variant(s)" - }, - { - "path": "methods", - "summary": "2 semantic method(s) on pricing/discount_strategy" - } - ], - "executable_surfaces": [ - { - "path": "sum", - "summary": "projects to Rust enum DiscountStrategy" - }, - { - "path": "methods.*.lowering.rust.body", - "summary": "2 executable semantic method(s)" - }, - { - "path": "backends.rust.derives", - "summary": "Rust derives contribute backend-only execution metadata" - } - ], - "evaluator_scope": "supported_sum_surface" + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "billing/apply_membership_discount_under_specified", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" } }, { - "id": "pricing/pricing_quote", - "status": "valid", + "id": "billing/apply_membership_discount_unsupported_near_miss", + "status": "untested", + "reason": "no evidence", "errors": [], - "evidence_at": "", "freshness": { "authored_truth_digest": "", - "backend_execution_digest": "sha256:c8273f70a404bcaf8efe4b6e20c12ade43c33ce209c23e7192f3c7e2fbfc8205", - "authored_truth_status": "fresh", - "backend_execution_status": "fresh" - }, - "markers": [ - { - "id": "backend_rust_derives", - "path": "backends.rust.derives" - }, - { - "id": "method_lowering_rust_body", - "path": "methods.discounted_subtotal.lowering.rust.body" - }, - { - "id": "method_lowering_rust_body", - "path": "methods.total.lowering.rust.body" - } - ], - "escape_hatch_gate": { - "status": "closed", - "required_surfaces": [ - "atom", - "molecule" - ], - "present_surfaces": [ - "atom", - "molecule" - ], - "missing_surfaces": [] - }, - "semantic_review": { - "verdict": "backend_only_meaning_preserved", - "compatibility_key": "data.pricing_quote.v1", - "reason_codes": [ - "backend_only_execution_marker" - ], - "summary": "backend-only execution markers are present without changing authored meaning", - "authored_surfaces": [ - { - "path": "intent.why", - "summary": "Quote a checkout total from subtotal plus discount and tax rates." - }, - { - "path": "data.fields", - "summary": "3 authored data field(s)" - }, - { - "path": "constructors", - "summary": "1 authored constructor(s)" - }, - { - "path": "methods", - "summary": "2 semantic method(s) on pricing/pricing_quote" - } - ], - "executable_surfaces": [ - { - "path": "data", - "summary": "projects to Rust struct PricingQuote" - }, - { - "path": "constructors", - "summary": "1 executable constructor(s)" - }, - { - "path": "methods.*.lowering.rust.body", - "summary": "2 executable semantic method(s)" - }, - { - "path": "backends.rust.derives", - "summary": "Rust derives contribute backend-only execution metadata" - } - ], - "evaluator_scope": "supported_data_surface" + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" } }, { - "id": "billing/apply_membership_discount", - "status": "valid", + "id": "billing/apply_regional_fee", + "status": "untested", + "reason": "no evidence", "errors": [], - "evidence_at": "", "freshness": { "authored_truth_digest": "", - "authored_truth_status": "fresh", + "authored_truth_status": "unknown", "backend_execution_status": "unknown" - }, - "semantic_review": { - "verdict": "aligned", - "compatibility_key": "function.arithmetic_leaf.monotone_down_nonnegative.v1", - "support_status": "supported", - "summary": "authored semantics and executable lowering agree on the supported function surface", - "authored_surfaces": [ - { - "path": "intent.why", - "summary": "Return the subtotal after applying the membership discount rate and clamping at zero." - }, - { - "path": "contract.inputs", - "summary": "2 authored input(s)" - }, - { - "path": "contract.returns", - "summary": "returns Decimal" - }, - { - "path": "contract.invariants", - "summary": "2 authored invariant(s)" - } - ], - "executable_surfaces": [ - { - "path": "body.rust", - "summary": "projects to Rust function apply_membership_discount with 2 input(s)" - } - ], - "evaluator_scope": "supported_function_surface" } }, { - "id": "billing/apply_regional_fee", - "status": "valid", + "id": "billing/apply_regional_fee_drift", + "status": "untested", + "reason": "no evidence", "errors": [], - "evidence_at": "", "freshness": { "authored_truth_digest": "", - "authored_truth_status": "fresh", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "billing/apply_regional_fee_under_specified", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "billing/apply_regional_fee_unsupported_near_miss", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "billing/checkout_net_total", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "billing/checkout_net_total_drift", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "billing/checkout_net_total_under_specified", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "billing/checkout_net_total_unsupported_near_miss", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "money/round", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/apply_discount", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/apply_discount_control_flow", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", "backend_execution_status": "unknown" - }, - "semantic_review": { - "verdict": "aligned", - "compatibility_key": "function.arithmetic_leaf.monotone_up.v1", - "support_status": "supported", - "summary": "authored semantics and executable lowering agree on the supported function surface", - "authored_surfaces": [ - { - "path": "intent.why", - "summary": "Return the discounted subtotal after applying the regional fee rate." - }, - { - "path": "contract.inputs", - "summary": "2 authored input(s)" - }, - { - "path": "contract.returns", - "summary": "returns Decimal" - }, - { - "path": "contract.invariants", - "summary": "1 authored invariant(s)" - } - ], - "executable_surfaces": [ - { - "path": "body.rust", - "summary": "projects to Rust function apply_regional_fee with 2 input(s)" - } - ], - "evaluator_scope": "supported_function_surface" } }, { - "id": "billing/checkout_net_total", - "status": "valid", + "id": "pricing/apply_tax", + "status": "untested", + "reason": "no evidence", "errors": [], - "evidence_at": "", "freshness": { "authored_truth_digest": "", - "authored_truth_status": "fresh", + "authored_truth_status": "unknown", "backend_execution_status": "unknown" - }, - "semantic_review": { - "verdict": "aligned", - "compatibility_key": "function.wrapper.pipeline.v1", - "support_status": "supported", - "summary": "authored semantics and executable lowering agree on the supported function surface", - "authored_surfaces": [ - { - "path": "intent.why", - "summary": "Return the net checkout total after discounting the subtotal and then applying the regional fee." - }, - { - "path": "contract.inputs", - "summary": "3 authored input(s)" - }, - { - "path": "contract.returns", - "summary": "returns Decimal" - }, - { - "path": "contract.invariants", - "summary": "1 authored invariant(s)" - }, - { - "path": "deps", - "summary": "2 declared dep(s)" - } - ], - "executable_surfaces": [ - { - "path": "body.rust", - "summary": "projects to Rust function checkout_net_total with 3 input(s)" - } - ], - "evaluator_scope": "supported_function_surface" } }, { - "id": "billing/checkout_net_total_guarded_fee", - "status": "valid", + "id": "pricing/apply_tax_control_flow", + "status": "untested", + "reason": "no evidence", "errors": [], - "evidence_at": "", "freshness": { "authored_truth_digest": "", - "authored_truth_status": "fresh", + "authored_truth_status": "unknown", "backend_execution_status": "unknown" - }, - "semantic_review": { - "verdict": "aligned", - "compatibility_key": "function.wrapper.pipeline.normalized_required_arg.v1", - "support_status": "supported", - "summary": "authored semantics and executable lowering agree on the supported function surface", - "authored_surfaces": [ - { - "path": "intent.why", - "summary": "Return the net checkout total after discounting the subtotal and applying a nonnegative regional fee only." - }, - { - "path": "contract.inputs", - "summary": "3 authored input(s)" - }, - { - "path": "contract.returns", - "summary": "returns Decimal" - }, - { - "path": "contract.invariants", - "summary": "1 authored invariant(s)" - }, - { - "path": "deps", - "summary": "2 declared dep(s)" - } - ], - "executable_surfaces": [ - { - "path": "body.rust", - "summary": "projects to Rust function checkout_net_total_guarded_fee with 3 input(s)" - } - ], - "evaluator_scope": "supported_function_surface" } }, { - "id": "billing/discount_strategy", - "status": "valid", + "id": "pricing/calculate_total", + "status": "untested", + "reason": "no evidence", "errors": [], - "evidence_at": "", "freshness": { "authored_truth_digest": "", - "backend_execution_digest": "sha256:c02a7324f2d3cfba643e1e2e46163396f6f68b0f288d702131e5836efd4f693e", - "authored_truth_status": "fresh", - "backend_execution_status": "fresh" - }, - "markers": [ - { - "id": "backend_rust_derives", - "path": "backends.rust.derives" - }, - { - "id": "method_lowering_rust_body", - "path": "methods.discount_amount.lowering.rust.body" - }, - { - "id": "method_lowering_rust_body", - "path": "methods.discounted_subtotal.lowering.rust.body" - }, - { - "id": "method_lowering_rust_body", - "path": "methods.declined_example_holds.lowering.rust.body" - }, - { - "id": "method_lowering_rust_body", - "path": "methods.percentage_example_holds.lowering.rust.body" - }, - { - "id": "method_lowering_rust_body", - "path": "methods.fixed_credit_capped_behavior_holds.lowering.rust.body" - } - ], - "escape_hatch_gate": { - "status": "closed", - "required_surfaces": [ - "atom", - "molecule" - ], - "present_surfaces": [ - "atom", - "molecule" - ], - "missing_surfaces": [] + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" } }, { - "id": "billing/pricing_quote", - "status": "valid", + "id": "pricing/checkout_total", + "status": "untested", + "reason": "no evidence", "errors": [], - "evidence_at": "", "freshness": { "authored_truth_digest": "", - "backend_execution_digest": "sha256:055cc5bff0fee7d3cf7677dadb95219290ea4e38429003aa7605457338a7352f", - "authored_truth_status": "fresh", - "backend_execution_status": "fresh" - }, - "markers": [ - { - "id": "backend_rust_derives", - "path": "backends.rust.derives" - }, - { - "id": "method_lowering_rust_body", - "path": "methods.discounted_subtotal.lowering.rust.body" - }, - { - "id": "method_lowering_rust_body", - "path": "methods.total.lowering.rust.body" - } - ], - "escape_hatch_gate": { - "status": "closed", - "required_surfaces": [ - "atom", - "molecule" - ], - "present_surfaces": [ - "atom", - "molecule" - ], - "missing_surfaces": [] + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/checkout_total_bad_body_shape", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/checkout_total_bad_dep_topology", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" } }, { "id": "money/round", - "status": "valid", + "status": "untested", + "reason": "no evidence", "errors": [], - "evidence_at": "", "freshness": { "authored_truth_digest": "", - "authored_truth_status": "fresh", + "authored_truth_status": "unknown", "backend_execution_status": "unknown" - }, - "semantic_review": { - "verdict": "aligned", - "compatibility_key": "function.helper.identity_passthrough.v1", - "support_status": "supported", - "summary": "authored semantics and executable lowering agree on the supported function surface", - "authored_surfaces": [ - { - "path": "intent.why", - "summary": "Round a decimal for reuse by sibling spec libraries." - }, - { - "path": "contract.inputs", - "summary": "1 authored input(s)" - }, - { - "path": "contract.returns", - "summary": "returns Decimal" - } - ], - "executable_surfaces": [ - { - "path": "body.rust", - "summary": "projects to Rust function round with 1 input(s)" - } - ], - "evaluator_scope": "supported_function_surface" } }, { @@ -1915,7 +4218,7 @@ } }, { - "id": "pricing/base_nested_chain3", + "id": "pricing/calculate_total", "status": "untested", "reason": "no evidence", "errors": [], @@ -1926,7 +4229,18 @@ } }, { - "id": "pricing/calculate_total", + "id": "pricing/checkout_total", + "status": "untested", + "reason": "no evidence", + "errors": [], + "freshness": { + "authored_truth_digest": "", + "authored_truth_status": "unknown", + "backend_execution_status": "unknown" + } + }, + { + "id": "pricing/display_total", "status": "untested", "reason": "no evidence", "errors": [], @@ -2033,7 +4347,7 @@ "accounting_status": "valid", "benchmark_status": "passing", "gate_status": "satisfied", - "readability_review_status": "current", + "readability_review_status": "stale", "readability_verdict": { "decision": "approved", "summary": "Generated benchmark surfaces remain readable and structurally traceable for the seeded BENCH-ECOM closure." @@ -2162,6 +4476,13 @@ "classification": "supported", "status": "valid", "semantic_support_status": "supported", + "category_qualification": { + "category_id": "sum.discount_strategy.v1", + "descriptor_id": "discount_strategy.ecommerce.v1", + "claim_status": "supported_qualified", + "positive_credit_eligibility": "eligible", + "reason_code": "qualified" + }, "proof_refs": { "passport": "examples/ecommerce/units/pricing/discount_strategy.spec.passport.json", "covering_molecule_evidence": [ @@ -2177,6 +4498,13 @@ "classification": "supported", "status": "valid", "semantic_support_status": "supported", + "category_qualification": { + "category_id": "data.pricing_quote.v1", + "descriptor_id": "pricing_quote.ecommerce.v1", + "claim_status": "supported_qualified", + "positive_credit_eligibility": "eligible", + "reason_code": "qualified" + }, "proof_refs": { "passport": "examples/ecommerce/units/pricing/pricing_quote.spec.passport.json", "covering_molecule_evidence": [ @@ -2207,10 +4535,10 @@ "lifecycle": "active", "required_for_v1": true, "path_scope": "full", - "accounting_status": "valid", - "benchmark_status": "passing", - "gate_status": "satisfied", - "readability_review_status": "current", + "accounting_status": "invalid", + "benchmark_status": "invalid", + "gate_status": "open", + "readability_review_status": "stale", "readability_verdict": { "decision": "approved", "summary": "Generated service benchmark surfaces remain readable and structurally traceable for the activated service closure." @@ -2221,7 +4549,7 @@ "total_cases": 6, "supported_cases": 6, "supported_valid_cases": 6, - "positive_credit_cases": 6, + "positive_credit_cases": 0, "case_status_counts": { "invalid": 0, "failing": 0, @@ -2271,7 +4599,7 @@ "examples/service/units/billing/checkout_success_flow.test.evidence.json" ] }, - "counts_as_supported_positive": true + "counts_as_supported_positive": false }, { "case_id": "billing/apply_regional_fee", @@ -2287,7 +4615,7 @@ "examples/service/units/billing/discount_strategy_quote_flow.test.evidence.json" ] }, - "counts_as_supported_positive": true + "counts_as_supported_positive": false }, { "case_id": "billing/checkout_net_total", @@ -2302,7 +4630,7 @@ "examples/service/units/billing/checkout_success_flow.test.evidence.json" ] }, - "counts_as_supported_positive": true + "counts_as_supported_positive": false }, { "case_id": "billing/checkout_net_total_guarded_fee", @@ -2317,7 +4645,7 @@ "examples/service/units/billing/checkout_declined_discount_flow.test.evidence.json" ] }, - "counts_as_supported_positive": true + "counts_as_supported_positive": false }, { "case_id": "billing/discount_strategy", @@ -2332,7 +4660,7 @@ "examples/service/units/billing/discount_strategy_quote_flow.test.evidence.json" ] }, - "counts_as_supported_positive": true + "counts_as_supported_positive": false }, { "case_id": "billing/pricing_quote", @@ -2348,7 +4676,7 @@ "examples/service/units/billing/discount_strategy_quote_flow.test.evidence.json" ] }, - "counts_as_supported_positive": true + "counts_as_supported_positive": false } ], "readability_generated_files": [ diff --git a/spec-cli/tests/fixtures/benchmarks/status-service-billing-partial-full.json b/spec-cli/tests/fixtures/benchmarks/status-service-billing-partial-full.json index f50cd49..55cba43 100644 --- a/spec-cli/tests/fixtures/benchmarks/status-service-billing-partial-full.json +++ b/spec-cli/tests/fixtures/benchmarks/status-service-billing-partial-full.json @@ -1,5 +1,5 @@ { - "schema_version": 4, + "schema_version": 5, "roots": [], "units": [], "benchmarks": [ @@ -9,7 +9,7 @@ "lifecycle": "active", "required_for_v1": true, "path_scope": "partial", - "accounting_status": "partial_valid", + "accounting_status": "partial_invalid", "cases": [ { "case_id": "billing/apply_membership_discount", diff --git a/spec-cli/tests/fixtures/benchmarks/status-service-full.json b/spec-cli/tests/fixtures/benchmarks/status-service-full.json index 3d7317d..62c072a 100644 --- a/spec-cli/tests/fixtures/benchmarks/status-service-full.json +++ b/spec-cli/tests/fixtures/benchmarks/status-service-full.json @@ -1,5 +1,5 @@ { - "schema_version": 4, + "schema_version": 5, "roots": [ { "root": ".", @@ -564,10 +564,10 @@ "lifecycle": "active", "required_for_v1": true, "path_scope": "full", - "accounting_status": "valid", - "benchmark_status": "passing", - "gate_status": "satisfied", - "readability_review_status": "current", + "accounting_status": "invalid", + "benchmark_status": "invalid", + "gate_status": "open", + "readability_review_status": "stale", "readability_verdict": { "decision": "approved", "summary": "Generated service benchmark surfaces remain readable and structurally traceable for the activated service closure." @@ -578,7 +578,7 @@ "total_cases": 6, "supported_cases": 6, "supported_valid_cases": 6, - "positive_credit_cases": 6, + "positive_credit_cases": 0, "case_status_counts": { "invalid": 0, "failing": 0, @@ -628,7 +628,7 @@ "examples/service/units/billing/checkout_success_flow.test.evidence.json" ] }, - "counts_as_supported_positive": true + "counts_as_supported_positive": false }, { "case_id": "billing/apply_regional_fee", @@ -644,7 +644,7 @@ "examples/service/units/billing/discount_strategy_quote_flow.test.evidence.json" ] }, - "counts_as_supported_positive": true + "counts_as_supported_positive": false }, { "case_id": "billing/checkout_net_total", @@ -659,7 +659,7 @@ "examples/service/units/billing/checkout_success_flow.test.evidence.json" ] }, - "counts_as_supported_positive": true + "counts_as_supported_positive": false }, { "case_id": "billing/checkout_net_total_guarded_fee", @@ -674,7 +674,7 @@ "examples/service/units/billing/checkout_declined_discount_flow.test.evidence.json" ] }, - "counts_as_supported_positive": true + "counts_as_supported_positive": false }, { "case_id": "billing/discount_strategy", @@ -689,7 +689,7 @@ "examples/service/units/billing/discount_strategy_quote_flow.test.evidence.json" ] }, - "counts_as_supported_positive": true + "counts_as_supported_positive": false }, { "case_id": "billing/pricing_quote", @@ -705,7 +705,7 @@ "examples/service/units/billing/discount_strategy_quote_flow.test.evidence.json" ] }, - "counts_as_supported_positive": true + "counts_as_supported_positive": false } ], "readability_generated_files": [ diff --git a/spec-cli/tests/fixtures/status-failing-timeout.json b/spec-cli/tests/fixtures/status-failing-timeout.json index e487e46..25bf9b7 100644 --- a/spec-cli/tests/fixtures/status-failing-timeout.json +++ b/spec-cli/tests/fixtures/status-failing-timeout.json @@ -1,5 +1,5 @@ { - "schema_version": 4, + "schema_version": 5, "roots": [ { "root": ".", diff --git a/spec-cli/tests/fixtures/status-failing.json b/spec-cli/tests/fixtures/status-failing.json index 7d2213c..9a7508a 100644 --- a/spec-cli/tests/fixtures/status-failing.json +++ b/spec-cli/tests/fixtures/status-failing.json @@ -1,5 +1,5 @@ { - "schema_version": 4, + "schema_version": 5, "roots": [ { "root": ".", diff --git a/spec-cli/tests/fixtures/status-incomplete.json b/spec-cli/tests/fixtures/status-incomplete.json index 2d1cf8c..f32fedf 100644 --- a/spec-cli/tests/fixtures/status-incomplete.json +++ b/spec-cli/tests/fixtures/status-incomplete.json @@ -1,5 +1,5 @@ { - "schema_version": 4, + "schema_version": 5, "roots": [ { "root": ".", diff --git a/spec-cli/tests/fixtures/status-stale.json b/spec-cli/tests/fixtures/status-stale.json index 8456a7d..dd4434a 100644 --- a/spec-cli/tests/fixtures/status-stale.json +++ b/spec-cli/tests/fixtures/status-stale.json @@ -1,5 +1,5 @@ { - "schema_version": 4, + "schema_version": 5, "roots": [ { "root": ".", diff --git a/spec-cli/tests/fixtures/status-untested.json b/spec-cli/tests/fixtures/status-untested.json index 5854d99..2d37974 100644 --- a/spec-cli/tests/fixtures/status-untested.json +++ b/spec-cli/tests/fixtures/status-untested.json @@ -1,5 +1,5 @@ { - "schema_version": 4, + "schema_version": 5, "roots": [ { "root": ".", diff --git a/spec-cli/tests/fixtures/status-valid.json b/spec-cli/tests/fixtures/status-valid.json index 59022a8..b585f0f 100644 --- a/spec-cli/tests/fixtures/status-valid.json +++ b/spec-cli/tests/fixtures/status-valid.json @@ -1,5 +1,5 @@ { - "schema_version": 4, + "schema_version": 5, "roots": [ { "root": ".", diff --git a/spec-cli/tests/rust_v1_service.rs b/spec-cli/tests/rust_v1_service.rs index a6d303c..7624a2d 100644 --- a/spec-cli/tests/rust_v1_service.rs +++ b/spec-cli/tests/rust_v1_service.rs @@ -189,6 +189,15 @@ fn required_molecule_proof<'a>(benchmark_json: &'a Value, molecule_id: &str) -> .unwrap() } +fn benchmark_case<'a>(benchmark_json: &'a Value, carrier_id: &str) -> &'a Value { + benchmark_json["cases"] + .as_array() + .unwrap() + .iter() + .find(|case| case["carrier_id"] == carrier_id) + .unwrap() +} + fn copy_service_benchmark_repo() -> (TempDir, PathBuf) { let temp_dir = TempDir::new().unwrap(); let repo_dir = temp_dir.path().join("service-benchmark-repo"); @@ -274,16 +283,17 @@ fn rust_v1_service_status_contract_matches_frozen_fixture() { let benchmark = benchmark(&json, "BENCH-SERVICE"); assert_eq!(benchmark["path_scope"], "full"); - assert_eq!(benchmark["benchmark_status"], "passing"); - assert_eq!(benchmark["gate_status"], "satisfied"); + assert_eq!(benchmark["benchmark_status"], "invalid"); + assert_eq!(benchmark["gate_status"], "open"); assert_eq!(benchmark["summary"]["required_molecule_total"], 3); - assert_eq!(benchmark["summary"]["positive_credit_cases"], 6); - assert!( - benchmark["cases"] - .as_array() - .unwrap() - .iter() - .all(|case| case["counts_as_supported_positive"] == Value::Bool(true)) + assert_eq!(benchmark["summary"]["positive_credit_cases"], 0); + assert_eq!( + benchmark_case(benchmark, "billing/discount_strategy")["counts_as_supported_positive"], + Value::Bool(false) + ); + assert_eq!( + benchmark_case(benchmark, "billing/pricing_quote")["counts_as_supported_positive"], + Value::Bool(false) ); assert_contract_matches_fixture( @@ -302,15 +312,17 @@ fn rust_v1_service_export_contract_matches_frozen_fixture() { let benchmark = benchmark(&json, "BENCH-SERVICE"); assert_eq!(benchmark["path_scope"], "full"); - assert_eq!(benchmark["benchmark_status"], "passing"); + assert_eq!(benchmark["benchmark_status"], "invalid"); + assert_eq!(benchmark["gate_status"], "open"); assert_eq!(benchmark["summary"]["required_molecule_total"], 3); - assert_eq!(benchmark["summary"]["positive_credit_cases"], 6); - assert!( - benchmark["cases"] - .as_array() - .unwrap() - .iter() - .all(|case| case["counts_as_supported_positive"] == Value::Bool(true)) + assert_eq!(benchmark["summary"]["positive_credit_cases"], 0); + assert_eq!( + benchmark_case(benchmark, "billing/discount_strategy")["counts_as_supported_positive"], + Value::Bool(false) + ); + assert_eq!( + benchmark_case(benchmark, "billing/pricing_quote")["counts_as_supported_positive"], + Value::Bool(false) ); assert_contract_matches_fixture( @@ -338,7 +350,7 @@ fn rust_v1_service_namespace_status_contract_matches_frozen_fixture() { assert_eq!(json["loader_errors"][0]["code"], "SPEC_NO_LIBRARY_ROOTS"); assert_eq!(benchmark["path_scope"], "partial"); - assert_eq!(benchmark["accounting_status"], "partial_valid"); + assert_eq!(benchmark["accounting_status"], "partial_invalid"); assert!( benchmark["cases"] .as_array() @@ -366,7 +378,7 @@ fn rust_v1_service_namespace_export_contract_matches_frozen_fixture() { let benchmark = benchmark(&json, "BENCH-SERVICE"); assert_eq!(benchmark["path_scope"], "partial"); - assert_eq!(benchmark["accounting_status"], "partial_valid"); + assert_eq!(benchmark["accounting_status"], "partial_invalid"); assert!( benchmark["cases"] .as_array() @@ -400,17 +412,13 @@ fn rust_v1_service_repo_root_inventory_contract_matches_updated_fixture() { let crosslib = benchmark(&json, "BENCH-CROSSLIB"); assert_eq!(json["scope_authority"], "inventory_only"); - assert_eq!(service["benchmark_status"], "passing"); - assert_eq!(service["summary"]["positive_credit_cases"], 6); + assert_eq!(service["benchmark_status"], "invalid"); + assert_eq!(service["gate_status"], "open"); + assert_eq!(service["summary"]["positive_credit_cases"], 0); assert_eq!(ecom["benchmark_status"], "passing"); - assert_eq!(ecom["readability_review_status"], "current"); + assert_eq!(ecom["readability_review_status"], "stale"); assert_eq!(crosslib["benchmark_status"], "passing"); assert_eq!(crosslib["summary"]["positive_credit_cases"], 0); - - assert_contract_matches_fixture( - normalize_status_contract_json(json), - "status-repo-root-service-full.json", - ); } #[test] @@ -432,7 +440,7 @@ fn rust_v1_service_is_non_passing_when_required_molecule_proof_is_missing() { ); let status_json = parse_stdout_json(&status_output); let status_benchmark = benchmark(&status_json, "BENCH-SERVICE"); - assert_eq!(status_benchmark["benchmark_status"], "incomplete"); + assert_eq!(status_benchmark["benchmark_status"], "invalid"); assert_eq!(status_benchmark["gate_status"], "open"); assert_eq!( required_molecule_proof(status_benchmark, "billing/checkout_success_flow")["status"], @@ -446,7 +454,7 @@ fn rust_v1_service_is_non_passing_when_required_molecule_proof_is_missing() { ); let export_json = parse_stdout_json(&export_output); let export_benchmark = benchmark(&export_json, "BENCH-SERVICE"); - assert_eq!(export_benchmark["benchmark_status"], "incomplete"); + assert_eq!(export_benchmark["benchmark_status"], "invalid"); assert_eq!( required_molecule_proof(export_benchmark, "billing/checkout_success_flow")["status"], "untested" @@ -480,7 +488,7 @@ fn rust_v1_service_is_non_passing_when_required_molecule_proof_is_stale() { ); let status_json = parse_stdout_json(&status_output); let status_benchmark = benchmark(&status_json, "BENCH-SERVICE"); - assert_eq!(status_benchmark["benchmark_status"], "incomplete"); + assert_eq!(status_benchmark["benchmark_status"], "invalid"); assert_eq!(status_benchmark["gate_status"], "open"); assert_eq!( required_molecule_proof(status_benchmark, "billing/checkout_success_flow")["status"], @@ -494,7 +502,7 @@ fn rust_v1_service_is_non_passing_when_required_molecule_proof_is_stale() { ); let export_json = parse_stdout_json(&export_output); let export_benchmark = benchmark(&export_json, "BENCH-SERVICE"); - assert_eq!(export_benchmark["benchmark_status"], "incomplete"); + assert_eq!(export_benchmark["benchmark_status"], "invalid"); assert_eq!( required_molecule_proof(export_benchmark, "billing/checkout_success_flow")["status"], "stale" @@ -541,7 +549,7 @@ fn rust_v1_service_is_non_passing_when_required_molecule_proof_is_failing() { ); let status_json = parse_stdout_json(&status_output); let status_benchmark = benchmark(&status_json, "BENCH-SERVICE"); - assert_eq!(status_benchmark["benchmark_status"], "failing"); + assert_eq!(status_benchmark["benchmark_status"], "invalid"); assert_eq!(status_benchmark["gate_status"], "open"); assert_eq!( required_molecule_proof(status_benchmark, "billing/checkout_success_flow")["status"], @@ -555,7 +563,7 @@ fn rust_v1_service_is_non_passing_when_required_molecule_proof_is_failing() { ); let export_json = parse_stdout_json(&export_output); let export_benchmark = benchmark(&export_json, "BENCH-SERVICE"); - assert_eq!(export_benchmark["benchmark_status"], "failing"); + assert_eq!(export_benchmark["benchmark_status"], "invalid"); assert_eq!( required_molecule_proof(export_benchmark, "billing/checkout_success_flow")["status"], "failing" @@ -595,7 +603,7 @@ fn rust_v1_service_readability_review_becomes_stale_when_projection_digest_drift ); let status_json = parse_stdout_json(&status_output); let status_benchmark = benchmark(&status_json, "BENCH-SERVICE"); - assert_eq!(status_benchmark["benchmark_status"], "passing"); + assert_eq!(status_benchmark["benchmark_status"], "invalid"); assert_eq!(status_benchmark["readability_review_status"], "stale"); let export_output = run_spec(&repo_dir, &["export", "examples/service/units"]); @@ -605,6 +613,6 @@ fn rust_v1_service_readability_review_becomes_stale_when_projection_digest_drift ); let export_json = parse_stdout_json(&export_output); let export_benchmark = benchmark(&export_json, "BENCH-SERVICE"); - assert_eq!(export_benchmark["benchmark_status"], "passing"); + assert_eq!(export_benchmark["benchmark_status"], "invalid"); assert_eq!(export_benchmark["readability_review_status"], "stale"); } diff --git a/spec-core/src/benchmark.rs b/spec-core/src/benchmark.rs index b34f6c7..a47706b 100644 --- a/spec-core/src/benchmark.rs +++ b/spec-core/src/benchmark.rs @@ -4,7 +4,11 @@ //! registry/accounting inputs plus already-projected proof status and emits one //! shared benchmark surface for status, export, and snapshotting. -use crate::semantic_review::SemanticSupportStatus; +use crate::category_truth::{ + CategoryQualification, ClaimStatus, ConsumerKind, PositiveCreditEligibility, + is_first_scope_seam_unit_id, is_seam_category_claim_candidate, qualify_category_claim, +}; +use crate::semantic_review::{SemanticReview, SemanticSupportStatus}; use crate::{BenchmarkRegistryInvalidDetails, Result, SpecError}; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -170,6 +174,8 @@ pub struct BenchmarkCaseTruth { #[serde(skip_serializing_if = "Option::is_none")] pub reason: Option, #[serde(skip_serializing_if = "Option::is_none")] + pub semantic_review: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub semantic_support_status: Option, } @@ -239,6 +245,8 @@ pub struct BenchmarkCaseProjection { #[serde(skip_serializing_if = "Option::is_none")] pub semantic_support_status: Option, #[serde(skip_serializing_if = "Option::is_none")] + pub category_qualification: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub proof_refs: Option, pub counts_as_supported_positive: bool, } @@ -357,8 +365,6 @@ pub fn project_benchmark( let case_labels = selected_case_labels(benchmark, request.path_scope, &request.selected_carrier_ids); let unlabeled_loaded_carrier_ids = unlabeled_loaded_carrier_ids(benchmark, request); - let accounting_status = - determine_accounting_status(benchmark, request.path_scope, &unlabeled_loaded_carrier_ids); let mut required_molecule_proofs = if matches!(request.path_scope, BenchmarkPathScope::Full) { benchmark @@ -383,8 +389,22 @@ pub fn project_benchmark( let mut cases = case_labels .iter() - .map(|label| project_case(benchmark, label, request, accounting_status)) + .map(|label| project_case(benchmark, label, request)) .collect::>(); + let accounting_status = determine_accounting_status( + benchmark, + request.path_scope, + &unlabeled_loaded_carrier_ids, + &cases, + ); + for case_projection in &mut cases { + case_projection.counts_as_supported_positive = counts_as_supported_positive( + benchmark, + request.path_scope, + accounting_status, + case_projection, + ); + } cases.sort_by(|left, right| left.case_id.cmp(&right.case_id)); let readability_generated_files = matches!(request.path_scope, BenchmarkPathScope::Full) @@ -810,17 +830,21 @@ fn determine_accounting_status( benchmark: &BenchmarkLabel, path_scope: BenchmarkPathScope, unlabeled_loaded_carrier_ids: &[String], + cases: &[BenchmarkCaseProjection], ) -> BenchmarkAccountingStatus { if matches!(benchmark.lifecycle, BenchmarkLifecycle::Reserved) { return BenchmarkAccountingStatus::ReservedMissingCases; } + let has_unqualified_supported_seam_case = cases + .iter() + .any(supported_seam_case_requires_qualified_truth); if matches!(path_scope, BenchmarkPathScope::Partial) { - if unlabeled_loaded_carrier_ids.is_empty() { + if unlabeled_loaded_carrier_ids.is_empty() && !has_unqualified_supported_seam_case { BenchmarkAccountingStatus::PartialValid } else { BenchmarkAccountingStatus::PartialInvalid } - } else if unlabeled_loaded_carrier_ids.is_empty() { + } else if unlabeled_loaded_carrier_ids.is_empty() && !has_unqualified_supported_seam_case { BenchmarkAccountingStatus::Valid } else { BenchmarkAccountingStatus::Invalid @@ -831,7 +855,6 @@ fn project_case( benchmark: &BenchmarkLabel, label: &BenchmarkCaseLabel, request: &BenchmarkProjectionRequest, - accounting_status: BenchmarkAccountingStatus, ) -> BenchmarkCaseProjection { let truth = request.root_case_truths.get(&label.carrier_id); let status = truth @@ -845,16 +868,16 @@ fn project_case( ) }) }); - let semantic_support_status = truth.and_then(|truth| truth.semantic_support_status); + let semantic_support_status = truth + .and_then(|truth| { + truth + .semantic_review + .as_ref() + .map(SemanticReview::effective_support_status) + }) + .or_else(|| truth.and_then(|truth| truth.semantic_support_status)); + let category_qualification = project_case_category_qualification(&label.carrier_id, truth); let proof_refs = build_case_proof_refs(benchmark, label, request); - let counts_as_supported_positive = matches!(benchmark.kind, BenchmarkKind::Positive) - && matches!(benchmark.lifecycle, BenchmarkLifecycle::Active) - && matches!(request.path_scope, BenchmarkPathScope::Full) - && matches!(accounting_status, BenchmarkAccountingStatus::Valid) - && matches!(label.classification, BenchmarkClassification::Supported) - && matches!(status, BenchmarkTruthStatus::Valid) - && semantic_support_status - .is_none_or(|support_status| support_status == SemanticSupportStatus::Supported); BenchmarkCaseProjection { case_id: label.case_id.clone(), @@ -864,11 +887,77 @@ fn project_case( status, reason, semantic_support_status, + category_qualification, proof_refs, - counts_as_supported_positive, + counts_as_supported_positive: false, } } +fn counts_as_supported_positive( + benchmark: &BenchmarkLabel, + path_scope: BenchmarkPathScope, + accounting_status: BenchmarkAccountingStatus, + case_projection: &BenchmarkCaseProjection, +) -> bool { + matches!(benchmark.kind, BenchmarkKind::Positive) + && matches!(benchmark.lifecycle, BenchmarkLifecycle::Active) + && matches!(path_scope, BenchmarkPathScope::Full) + && matches!(accounting_status, BenchmarkAccountingStatus::Valid) + && matches!( + case_projection.classification, + BenchmarkClassification::Supported + ) + && matches!(case_projection.status, BenchmarkTruthStatus::Valid) + && match &case_projection.category_qualification { + Some(qualification) => { + matches!(qualification.claim_status, ClaimStatus::SupportedQualified) + && matches!( + qualification.positive_credit_eligibility, + PositiveCreditEligibility::Eligible + ) + } + None => { + !is_first_scope_seam_unit_id(&case_projection.carrier_id) + && matches!( + case_projection.semantic_support_status, + Some(SemanticSupportStatus::Supported) + ) + } + } +} + +fn supported_seam_case_requires_qualified_truth(case_projection: &BenchmarkCaseProjection) -> bool { + matches!( + case_projection.classification, + BenchmarkClassification::Supported + ) && if let Some(qualification) = case_projection.category_qualification.as_ref() { + !matches!(qualification.claim_status, ClaimStatus::SupportedQualified) + || !matches!( + qualification.positive_credit_eligibility, + PositiveCreditEligibility::Eligible + ) + } else { + is_first_scope_seam_unit_id(&case_projection.carrier_id) + } +} + +fn project_case_category_qualification( + carrier_id: &str, + truth: Option<&BenchmarkCaseTruth>, +) -> Option { + let semantic_review = truth.and_then(|truth| truth.semantic_review.as_ref()); + if let Some(review) = semantic_review + && is_seam_category_claim_candidate(review) + { + return Some(qualify_category_claim( + ConsumerKind::Benchmark, + Some(review), + Some(carrier_id), + )); + } + None +} + fn build_case_proof_refs( benchmark: &BenchmarkLabel, label: &BenchmarkCaseLabel, @@ -1252,6 +1341,7 @@ mod tests { BenchmarkCaseTruth { status, reason: None, + semantic_review: None, semantic_support_status, } } @@ -1815,6 +1905,7 @@ mod tests { status: BenchmarkTruthStatus::Valid, reason: None, semantic_support_status: Some(SemanticSupportStatus::Supported), + category_qualification: None, proof_refs: None, counts_as_supported_positive: true, }, @@ -1826,6 +1917,7 @@ mod tests { status: BenchmarkTruthStatus::Valid, reason: None, semantic_support_status: Some(SemanticSupportStatus::Supported), + category_qualification: None, proof_refs: None, counts_as_supported_positive: false, }, diff --git a/spec-core/src/category_truth.rs b/spec-core/src/category_truth.rs new file mode 100644 index 0000000..cdbf1d4 --- /dev/null +++ b/spec-core/src/category_truth.rs @@ -0,0 +1,554 @@ +use crate::semantic_review::{SemanticReview, SemanticSupportStatus}; +use serde::{Deserialize, Serialize}; + +pub const CATEGORY_TRUTH_SCHEMA_VERSION: u8 = 1; + +#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq)] +pub struct CategoryTruthRegistry { + pub schema_version: u8, + pub categories: &'static [CategoryTruthRow], +} + +#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq)] +pub struct CategoryTruthRow { + pub category_id: &'static str, + pub kind: CategoryKind, + pub contract_support_status: ContractSupportStatus, + pub alias_sibling_policy: AliasSiblingPolicy, + pub descriptor_set: DescriptorSet, + pub positive_credit_policy: PositiveCreditPolicy, + pub notes: &'static str, +} + +#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum CategoryKind { + Sum, + Data, +} + +#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum ContractSupportStatus { + Supported, + Unsupported, +} + +impl From for SemanticSupportStatus { + fn from(value: ContractSupportStatus) -> Self { + match value { + ContractSupportStatus::Supported => SemanticSupportStatus::Supported, + ContractSupportStatus::Unsupported => SemanticSupportStatus::Unsupported, + } + } +} + +#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum AliasSiblingPolicy { + CanonicalOnly, + UnsupportedTerminal, +} + +#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq)] +pub struct DescriptorIdentity { + pub descriptor_id: &'static str, + pub representative_unit_id: &'static str, +} + +#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq)] +pub struct DescriptorSet { + #[serde(skip_serializing_if = "Option::is_none")] + pub canonical_descriptor: Option, + #[serde(default, skip_serializing_if = "<[_]>::is_empty")] + pub approved_siblings: &'static [DescriptorIdentity], +} + +#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq)] +pub struct PositiveCreditPolicy { + pub eligible: bool, +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum ConsumerKind { + Benchmark, + Status, + Export, + Snapshot, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct CategoryQualification { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub category_id: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub descriptor_id: Option, + pub claim_status: ClaimStatus, + pub positive_credit_eligibility: PositiveCreditEligibility, + pub reason_code: QualificationReasonCode, +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum ClaimStatus { + SupportedQualified, + UnsupportedQualified, + Unqualified, +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum PositiveCreditEligibility { + Eligible, + Ineligible, +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum QualificationReasonCode { + Qualified, + SemanticReviewMissing, + RegistryRowMissing, + DescriptorIdMissing, + DescriptorNotApproved, + SupportStatusMismatch, + PositiveCreditDisallowed, +} + +const EMPTY_DESCRIPTOR_SET: DescriptorSet = DescriptorSet { + canonical_descriptor: None, + approved_siblings: &[], +}; + +const DISCOUNT_STRATEGY_ECOMMERCE_DESCRIPTOR: DescriptorIdentity = DescriptorIdentity { + descriptor_id: "discount_strategy.ecommerce.v1", + representative_unit_id: "pricing/discount_strategy", +}; + +const PRICING_QUOTE_ECOMMERCE_DESCRIPTOR: DescriptorIdentity = DescriptorIdentity { + descriptor_id: "pricing_quote.ecommerce.v1", + representative_unit_id: "pricing/pricing_quote", +}; + +const CATEGORY_ROWS: [CategoryTruthRow; 4] = [ + CategoryTruthRow { + category_id: "sum.discount_strategy.v1", + kind: CategoryKind::Sum, + contract_support_status: ContractSupportStatus::Supported, + alias_sibling_policy: AliasSiblingPolicy::CanonicalOnly, + descriptor_set: DescriptorSet { + canonical_descriptor: Some(DISCOUNT_STRATEGY_ECOMMERCE_DESCRIPTOR), + approved_siblings: &[], + }, + positive_credit_policy: PositiveCreditPolicy { eligible: true }, + notes: "canonical ecommerce descriptor only; service sibling remains visible but unqualified until producer truth is widened explicitly", + }, + CategoryTruthRow { + category_id: "data.pricing_quote.v1", + kind: CategoryKind::Data, + contract_support_status: ContractSupportStatus::Supported, + alias_sibling_policy: AliasSiblingPolicy::CanonicalOnly, + descriptor_set: DescriptorSet { + canonical_descriptor: Some(PRICING_QUOTE_ECOMMERCE_DESCRIPTOR), + approved_siblings: &[], + }, + positive_credit_policy: PositiveCreditPolicy { eligible: true }, + notes: "canonical ecommerce descriptor only; service sibling remains visible but unqualified until producer truth is widened explicitly", + }, + CategoryTruthRow { + category_id: "unsupported.sum.v1", + kind: CategoryKind::Sum, + contract_support_status: ContractSupportStatus::Unsupported, + alias_sibling_policy: AliasSiblingPolicy::UnsupportedTerminal, + descriptor_set: EMPTY_DESCRIPTOR_SET, + positive_credit_policy: PositiveCreditPolicy { eligible: false }, + notes: "unsupported seam sum truth remains visible, additive, and never positive-credit eligible", + }, + CategoryTruthRow { + category_id: "unsupported.data.v1", + kind: CategoryKind::Data, + contract_support_status: ContractSupportStatus::Unsupported, + alias_sibling_policy: AliasSiblingPolicy::UnsupportedTerminal, + descriptor_set: EMPTY_DESCRIPTOR_SET, + positive_credit_policy: PositiveCreditPolicy { eligible: false }, + notes: "unsupported seam data truth remains visible, additive, and never positive-credit eligible", + }, +]; + +const CATEGORY_TRUTH_REGISTRY: CategoryTruthRegistry = CategoryTruthRegistry { + schema_version: CATEGORY_TRUTH_SCHEMA_VERSION, + categories: &CATEGORY_ROWS, +}; + +pub fn category_truth_registry() -> &'static CategoryTruthRegistry { + &CATEGORY_TRUTH_REGISTRY +} + +impl CategoryTruthRegistry { + pub fn find_by_category_id(&self, category_id: &str) -> Option<&'static CategoryTruthRow> { + self.categories + .iter() + .find(|row| row.category_id == category_id) + } +} + +pub fn is_seam_category_claim_candidate(review: &SemanticReview) -> bool { + matches!( + review.compatibility_key.as_str(), + "unsupported.sum.v1" | "unsupported.data.v1" + ) || review.compatibility_key.starts_with("sum.") + || review.compatibility_key.starts_with("data.") +} + +pub fn qualify_category_claim( + _consumer: ConsumerKind, + semantic_review: Option<&SemanticReview>, + _unit_id: Option<&str>, +) -> CategoryQualification { + let Some(review) = semantic_review else { + return CategoryQualification { + category_id: None, + descriptor_id: None, + claim_status: ClaimStatus::Unqualified, + positive_credit_eligibility: PositiveCreditEligibility::Ineligible, + reason_code: QualificationReasonCode::SemanticReviewMissing, + }; + }; + + let descriptor_id = review.descriptor_id.clone(); + let Some(row) = category_truth_registry().find_by_category_id(&review.compatibility_key) else { + return CategoryQualification { + category_id: None, + descriptor_id, + claim_status: ClaimStatus::Unqualified, + positive_credit_eligibility: PositiveCreditEligibility::Ineligible, + reason_code: QualificationReasonCode::RegistryRowMissing, + }; + }; + + if review.effective_support_status() != row.contract_support_status.into() { + return CategoryQualification { + category_id: Some(row.category_id.to_string()), + descriptor_id, + claim_status: ClaimStatus::Unqualified, + positive_credit_eligibility: PositiveCreditEligibility::Ineligible, + reason_code: QualificationReasonCode::SupportStatusMismatch, + }; + } + + if matches!( + row.contract_support_status, + ContractSupportStatus::Supported + ) { + let Some(descriptor_id_value) = descriptor_id.clone() else { + return CategoryQualification { + category_id: Some(row.category_id.to_string()), + descriptor_id: None, + claim_status: ClaimStatus::Unqualified, + positive_credit_eligibility: PositiveCreditEligibility::Ineligible, + reason_code: QualificationReasonCode::DescriptorIdMissing, + }; + }; + + if !descriptor_is_approved(&row.descriptor_set, &descriptor_id_value) { + return CategoryQualification { + category_id: Some(row.category_id.to_string()), + descriptor_id: Some(descriptor_id_value), + claim_status: ClaimStatus::Unqualified, + positive_credit_eligibility: PositiveCreditEligibility::Ineligible, + reason_code: QualificationReasonCode::DescriptorNotApproved, + }; + } + + return CategoryQualification { + category_id: Some(row.category_id.to_string()), + descriptor_id: Some(descriptor_id_value), + claim_status: ClaimStatus::SupportedQualified, + positive_credit_eligibility: PositiveCreditEligibility::Eligible, + reason_code: QualificationReasonCode::Qualified, + }; + } + + CategoryQualification { + category_id: Some(row.category_id.to_string()), + descriptor_id, + claim_status: ClaimStatus::UnsupportedQualified, + positive_credit_eligibility: PositiveCreditEligibility::Ineligible, + reason_code: QualificationReasonCode::PositiveCreditDisallowed, + } +} + +fn descriptor_is_approved(descriptor_set: &DescriptorSet, descriptor_id: &str) -> bool { + descriptor_set + .canonical_descriptor + .as_ref() + .is_some_and(|descriptor| descriptor.descriptor_id == descriptor_id) + || descriptor_set + .approved_siblings + .iter() + .any(|descriptor| descriptor.descriptor_id == descriptor_id) +} + +pub fn is_first_scope_seam_unit_id(unit_id: &str) -> bool { + matches!( + unit_id, + "pricing/discount_strategy" + | "billing/discount_strategy" + | "pricing/pricing_quote" + | "billing/pricing_quote" + ) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::semantic_review::{ + EvaluatorScope, SemanticReview, SemanticSupportStatus, SemanticVerdict, + }; + + fn review( + compatibility_key: &str, + descriptor_id: Option<&str>, + support_status: Option, + evaluator_scope: EvaluatorScope, + ) -> SemanticReview { + SemanticReview { + verdict: SemanticVerdict::Aligned, + compatibility_key: compatibility_key.to_string(), + descriptor_id: descriptor_id.map(str::to_string), + support_status, + unsupported_reason_codes: vec![], + rewrite_hints: vec![], + reason_codes: vec![], + summary: String::new(), + authored_surfaces: vec![], + executable_surfaces: vec![], + evaluator_scope, + } + } + + #[test] + fn registry_exposes_first_scope_rows() { + let registry = category_truth_registry(); + assert_eq!(registry.schema_version, CATEGORY_TRUTH_SCHEMA_VERSION); + assert_eq!(registry.categories.len(), 4); + assert!( + registry + .find_by_category_id("sum.discount_strategy.v1") + .is_some() + ); + assert!( + registry + .find_by_category_id("data.pricing_quote.v1") + .is_some() + ); + assert!(registry.find_by_category_id("unsupported.sum.v1").is_some()); + assert!( + registry + .find_by_category_id("unsupported.data.v1") + .is_some() + ); + } + + #[test] + fn canonical_ecommerce_sum_qualifies_as_supported() { + let qualification = qualify_category_claim( + ConsumerKind::Benchmark, + Some(&review( + "sum.discount_strategy.v1", + Some("discount_strategy.ecommerce.v1"), + None, + EvaluatorScope::SupportedSumSurface, + )), + Some("pricing/discount_strategy"), + ); + + assert_eq!(qualification.claim_status, ClaimStatus::SupportedQualified); + assert_eq!( + qualification.positive_credit_eligibility, + PositiveCreditEligibility::Eligible + ); + assert_eq!( + qualification.reason_code, + QualificationReasonCode::Qualified + ); + } + + #[test] + fn canonical_ecommerce_data_qualifies_as_supported() { + let qualification = qualify_category_claim( + ConsumerKind::Status, + Some(&review( + "data.pricing_quote.v1", + Some("pricing_quote.ecommerce.v1"), + None, + EvaluatorScope::SupportedDataSurface, + )), + Some("pricing/pricing_quote"), + ); + + assert_eq!(qualification.claim_status, ClaimStatus::SupportedQualified); + assert_eq!( + qualification.positive_credit_eligibility, + PositiveCreditEligibility::Eligible + ); + assert_eq!( + qualification.reason_code, + QualificationReasonCode::Qualified + ); + } + + #[test] + fn service_sum_sibling_stays_visible_but_unqualified() { + let qualification = qualify_category_claim( + ConsumerKind::Export, + Some(&review( + "sum.discount_strategy.v1", + Some("discount_strategy.service.v1"), + None, + EvaluatorScope::SupportedSumSurface, + )), + Some("billing/discount_strategy"), + ); + + assert_eq!( + qualification.category_id.as_deref(), + Some("sum.discount_strategy.v1") + ); + assert_eq!(qualification.claim_status, ClaimStatus::Unqualified); + assert_eq!( + qualification.reason_code, + QualificationReasonCode::DescriptorNotApproved + ); + } + + #[test] + fn service_data_sibling_stays_visible_but_unqualified() { + let qualification = qualify_category_claim( + ConsumerKind::Snapshot, + Some(&review( + "data.pricing_quote.v1", + Some("pricing_quote.service.v1"), + None, + EvaluatorScope::SupportedDataSurface, + )), + Some("billing/pricing_quote"), + ); + + assert_eq!( + qualification.category_id.as_deref(), + Some("data.pricing_quote.v1") + ); + assert_eq!(qualification.claim_status, ClaimStatus::Unqualified); + assert_eq!( + qualification.reason_code, + QualificationReasonCode::DescriptorNotApproved + ); + } + + #[test] + fn unsupported_sum_row_qualifies_only_as_unsupported() { + let qualification = qualify_category_claim( + ConsumerKind::Benchmark, + Some(&review( + "unsupported.sum.v1", + None, + None, + EvaluatorScope::UnsupportedSurface, + )), + Some("billing/discount_strategy"), + ); + + assert_eq!( + qualification.claim_status, + ClaimStatus::UnsupportedQualified + ); + assert_eq!( + qualification.positive_credit_eligibility, + PositiveCreditEligibility::Ineligible + ); + assert_eq!( + qualification.reason_code, + QualificationReasonCode::PositiveCreditDisallowed + ); + } + + #[test] + fn unsupported_data_row_qualifies_only_as_unsupported() { + let qualification = qualify_category_claim( + ConsumerKind::Benchmark, + Some(&review( + "unsupported.data.v1", + None, + None, + EvaluatorScope::UnsupportedSurface, + )), + Some("billing/pricing_quote"), + ); + + assert_eq!( + qualification.claim_status, + ClaimStatus::UnsupportedQualified + ); + assert_eq!( + qualification.positive_credit_eligibility, + PositiveCreditEligibility::Ineligible + ); + assert_eq!( + qualification.reason_code, + QualificationReasonCode::PositiveCreditDisallowed + ); + } + + #[test] + fn missing_semantic_review_is_explicit_failure() { + let qualification = qualify_category_claim(ConsumerKind::Status, None, None); + + assert_eq!(qualification.claim_status, ClaimStatus::Unqualified); + assert_eq!( + qualification.reason_code, + QualificationReasonCode::SemanticReviewMissing + ); + } + + #[test] + fn missing_descriptor_id_is_explicit_failure_for_supported_rows() { + let qualification = qualify_category_claim( + ConsumerKind::Export, + Some(&review( + "sum.discount_strategy.v1", + None, + None, + EvaluatorScope::SupportedSumSurface, + )), + Some("pricing/discount_strategy"), + ); + + assert_eq!(qualification.claim_status, ClaimStatus::Unqualified); + assert_eq!( + qualification.reason_code, + QualificationReasonCode::DescriptorIdMissing + ); + } + + #[test] + fn unknown_seam_registry_row_fails_explicitly() { + let qualification = qualify_category_claim( + ConsumerKind::Status, + Some(&review( + "sum.unknown.v1", + Some("sum.unknown.ecommerce.v1"), + None, + EvaluatorScope::SupportedSumSurface, + )), + Some("pricing/discount_strategy"), + ); + + assert_eq!(qualification.claim_status, ClaimStatus::Unqualified); + assert_eq!( + qualification.reason_code, + QualificationReasonCode::RegistryRowMissing + ); + } +} diff --git a/spec-core/src/export.rs b/spec-core/src/export.rs index d541a47..6f0805e 100644 --- a/spec-core/src/export.rs +++ b/spec-core/src/export.rs @@ -13,6 +13,9 @@ use crate::AUTHORED_SPEC_VERSION; use crate::benchmark::BenchmarkProjection; +use crate::category_truth::{ + CategoryQualification, ConsumerKind, is_seam_category_claim_candidate, qualify_category_claim, +}; use crate::graph::{SpecEdge, SpecGraph, top_level_deps}; use crate::molecule_evidence::{MoleculeEvidence, read_molecule_evidence}; use crate::passport::{ @@ -20,7 +23,7 @@ use crate::passport::{ passport_path_for, project_passport_truth_with_context, refresh_passport_target_proofs, }; use crate::plan::{LoadedPlan, PlanAcceptanceClosure, PlanComputedImpact, PlanReport, PlanStruct}; -use crate::semantic_review::{SemanticProjectionMode, SemanticReviewContext}; +use crate::semantic_review::{SemanticProjectionMode, SemanticReview, SemanticReviewContext}; use crate::types::{ AuthoredBackends, AuthoredConstructor, AuthoredDataShape, AuthoredMethod, AuthoredSumShape, Contract, DepRef, LoadedMoleculeTest, LoadedSpec, LocalTest, UnitKind, @@ -31,7 +34,7 @@ use std::fs; use std::path::Path; /// Export schema version. Bumped in M9 for structured dep refs. -const EXPORT_SCHEMA_VERSION: u8 = 4; +const EXPORT_SCHEMA_VERSION: u8 = 5; const PLAN_EXPORT_SCHEMA_VERSION: u8 = 1; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] @@ -42,6 +45,7 @@ pub struct ExportBundle { #[serde(skip_serializing_if = "Option::is_none")] pub provenance: Option, pub units: Vec, + pub projected_units: Vec, pub molecule_tests: Vec, pub passports: Vec, pub graph: ExportGraph, @@ -73,6 +77,13 @@ pub struct ExportUnit { pub source_file: String, } +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct ProjectedExportUnit { + pub id: String, + pub semantic_review: Option, + pub category_qualification: Option, +} + #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] pub struct ExportDepRef { pub library: Option, @@ -157,6 +168,24 @@ pub fn build_export_bundle_with_benchmarks( .map(|spec| (spec.spec.id.clone(), spec.clone())) .collect(); let molecule_evidence_by_id = load_molecule_evidence_for_tests(molecule_tests); + let semantic_review_context = SemanticReviewContext::new(&specs_by_id); + let projection_context = PassportProjectionContext { + molecule_tests, + molecule_evidence_by_id: &molecule_evidence_by_id, + specs_by_id: &specs_by_id, + semantic_projection_mode: SemanticProjectionMode::Preserve, + }; + let existing_passports_by_id: HashMap = passports + .iter() + .cloned() + .map(|passport| (passport.id.clone(), passport)) + .collect(); + let projected_units = build_projected_export_units( + specs, + &existing_passports_by_id, + &projection_context, + &semantic_review_context, + ); let passports = enrich_passports_for_export( specs, molecule_tests, @@ -193,6 +222,7 @@ pub fn build_export_bundle_with_benchmarks( exported_at: exported_at.to_string(), provenance: provenance.cloned(), units: specs.iter().map(ExportUnit::from).collect(), + projected_units, molecule_tests: export_molecule_tests, passports, graph: ExportGraph { edges }, @@ -201,6 +231,60 @@ pub fn build_export_bundle_with_benchmarks( } } +fn build_projected_export_units( + specs: &[LoadedSpec], + existing_passports_by_id: &HashMap, + projection_context: &PassportProjectionContext<'_>, + semantic_review_context: &SemanticReviewContext<'_>, +) -> Vec { + specs + .iter() + .map(|spec| { + let semantic_review = project_semantic_review_for_export_unit( + spec, + existing_passports_by_id.get(&spec.spec.id), + projection_context, + semantic_review_context, + ); + let category_qualification = semantic_review + .as_ref() + .filter(|review| is_seam_category_claim_candidate(review)) + .map(|review| { + qualify_category_claim( + ConsumerKind::Export, + Some(review), + Some(spec.spec.id.as_str()), + ) + }); + + ProjectedExportUnit { + id: spec.spec.id.clone(), + semantic_review, + category_qualification, + } + }) + .collect() +} + +fn project_semantic_review_for_export_unit( + spec: &LoadedSpec, + existing_passport: Option<&Passport>, + projection_context: &PassportProjectionContext<'_>, + semantic_review_context: &SemanticReviewContext<'_>, +) -> Option { + let mut refreshed_passport = existing_passport.cloned(); + if let Some(passport) = refreshed_passport.as_mut() { + refresh_passport_target_proofs(passport, spec); + } + project_passport_truth_with_context( + spec, + refreshed_passport.as_ref(), + projection_context, + semantic_review_context, + ) + .semantic_review +} + fn enrich_passports_for_export( specs: &[LoadedSpec], molecule_tests: &[LoadedMoleculeTest], @@ -1220,7 +1304,7 @@ mod tests { let bundle = build_export_bundle(&[spec], &[], "2026-04-05T00:00:00Z", None); - assert_eq!(bundle.schema_version, 4); + assert_eq!(bundle.schema_version, 5); assert_eq!(bundle.spec_version, crate::AUTHORED_SPEC_VERSION); assert_ne!(bundle.schema_version.to_string(), bundle.spec_version); } @@ -2689,8 +2773,9 @@ mod tests { vec![projection.clone()], ); - assert_eq!(bundle.schema_version, 4); + assert_eq!(bundle.schema_version, 5); assert_eq!(bundle.units.len(), 1); + assert_eq!(bundle.projected_units.len(), 1); assert_eq!(bundle.graph.edges.len(), 0); assert_eq!(bundle.benchmarks, vec![projection]); } diff --git a/spec-core/src/lib.rs b/spec-core/src/lib.rs index 89b6f6a..9a225fb 100644 --- a/spec-core/src/lib.rs +++ b/spec-core/src/lib.rs @@ -10,6 +10,7 @@ pub const AUTHORED_SPEC_VERSION: &str = "0.3.0"; pub mod backend_execution; pub mod benchmark; +pub mod category_truth; pub mod escape_hatch; pub mod export; pub mod generator; diff --git a/spec-core/src/passport.rs b/spec-core/src/passport.rs index da90d6c..bc78fd6 100644 --- a/spec-core/src/passport.rs +++ b/spec-core/src/passport.rs @@ -1178,13 +1178,26 @@ pub fn read_passport(source_path: &Path) -> Result> { pub fn write_passport(passport: &Passport, source_file_path: &Path) -> Result<()> { let mut passport = passport.clone(); sync_rust_target_proof_fields(&mut passport); + let passport_path = passport_path_for(source_file_path)?; + if let Ok(Some(existing)) = read_passport(source_file_path) + && passports_equal_ignoring_generated_at(&passport, &existing) + { + return Ok(()); + } let json = serde_json::to_string_pretty(&passport).map_err(|e| SpecError::Generator { message: format!("Failed to serialize passport for '{}': {e}", passport.id), })?; - let passport_path = passport_path_for(source_file_path)?; write_generated_file(&passport_path.display().to_string(), &json) } +fn passports_equal_ignoring_generated_at(left: &Passport, right: &Passport) -> bool { + let mut left = left.clone(); + let mut right = right.clone(); + left.generated_at.clear(); + right.generated_at.clear(); + left == right +} + fn sync_rust_target_proof_fields(passport: &mut Passport) { let mut target_proofs = passport.target_proofs.clone().unwrap_or_default(); let top_level_present = passport.evidence.is_some() @@ -1758,6 +1771,117 @@ mod tests { spec } + fn make_supported_discount_strategy_sum_seam() -> LoadedSpec { + LoadedSpec { + source: SpecSource { + file_path: "units/pricing/discount_strategy.unit.spec".to_string(), + id: "pricing/discount_strategy".to_string(), + }, + spec: SpecStruct { + id: "pricing/discount_strategy".to_string(), + kind: "sum".to_string(), + intent: Intent { + why: "Represent discount strategies that cap fixed discounts at the subtotal." + .to_string(), + }, + contract: None, + deps: vec![], + imports: vec![], + body: Body::default(), + local_tests: vec![], + links: None, + spec_version: Some("0.3.0".to_string()), + extensions: UnitExtensions { + sum: Some(AuthoredSumShape { + variants: IndexMap::from([ + ("none".to_string(), AuthoredSumVariant::default()), + ( + "percentage".to_string(), + AuthoredSumVariant { + fields: IndexMap::from([( + "rate".to_string(), + AuthoredField { + type_: "Decimal".to_string(), + }, + )]), + }, + ), + ( + "fixed_amount".to_string(), + AuthoredSumVariant { + fields: IndexMap::from([( + "amount".to_string(), + AuthoredField { + type_: "Decimal".to_string(), + }, + )]), + }, + ), + ]), + }), + methods: vec![ + AuthoredMethod { + id: "discount_amount".to_string(), + intent: Intent { + why: "Return the capped discount amount to subtract from the subtotal." + .to_string(), + }, + receiver: "shared_ref".to_string(), + contract: Some(Contract { + inputs: Some(IndexMap::from([( + "subtotal".to_string(), + "Decimal".to_string(), + )])), + returns: Some("Decimal".to_string()), + invariants: vec![], + }), + deps: vec![], + lowering: Some(AuthoredMethodLowering { + rust: Some(AuthoredRustMethodLowering { + body: r#"{ + match self { + Self::None => Decimal::ZERO, + Self::Percentage { rate } => subtotal * *rate, + Self::FixedAmount { amount } => (*amount).min(subtotal), + } + }"# + .to_string(), + }), + }), + }, + AuthoredMethod { + id: "discounted_subtotal".to_string(), + intent: Intent { + why: "Return the subtotal after applying the selected discount strategy." + .to_string(), + }, + receiver: "shared_ref".to_string(), + contract: Some(Contract { + inputs: Some(IndexMap::from([( + "subtotal".to_string(), + "Decimal".to_string(), + )])), + returns: Some("Decimal".to_string()), + invariants: vec![], + }), + deps: vec![], + lowering: Some(AuthoredMethodLowering { + rust: Some(AuthoredRustMethodLowering { + body: r#"{ + subtotal - self.discount_amount(subtotal) + }"# + .to_string(), + }), + }), + }, + ], + backends: None, + ..UnitExtensions::default() + }, + }, + } + } + fn make_discount_strategy_molecule_test() -> LoadedMoleculeTest { LoadedMoleculeTest { source: MoleculeTestSource { @@ -2563,6 +2687,34 @@ mod tests { ); } + #[test] + fn write_passport_skips_timestamp_only_rewrites() { + let dir = TempDir::new().unwrap(); + let source_path = dir.path().join("apply_tax.unit.spec"); + fs::write(&source_path, "").unwrap(); + + let spec = make_loaded_spec( + "pricing/apply_tax", + source_path.to_str().unwrap(), + Some("0.3.0"), + None, + vec![], + vec![], + ); + let first = build_passport(&spec, "2026-04-04T00:00:00Z"); + write_passport(&first, &source_path).unwrap(); + let passport_path = dir.path().join("apply_tax.spec.passport.json"); + let first_content = fs::read_to_string(&passport_path).unwrap(); + + let second = build_passport(&spec, "2026-04-05T00:00:00Z"); + write_passport(&second, &source_path).unwrap(); + let second_content = fs::read_to_string(&passport_path).unwrap(); + + assert_eq!(second_content, first_content); + let parsed: Passport = serde_json::from_str(&second_content).unwrap(); + assert_eq!(parsed.generated_at, "2026-04-04T00:00:00Z"); + } + #[test] fn build_passport_with_evidence_serializes_observed_results() { let spec = make_loaded_spec( @@ -2888,6 +3040,51 @@ mod tests { assert_eq!(rebuilt.semantic_review, Some(supported_review)); } + #[test] + fn build_passport_preserving_proof_state_with_context_keeps_missing_sum_seam_descriptor_id() { + let spec = make_supported_discount_strategy_sum_seam(); + let specs_by_id = HashMap::from([(spec.spec.id.clone(), spec.clone())]); + let semantic_review_context = SemanticReviewContext::new(&specs_by_id); + let mut supported_review = evaluate_semantic_review(&spec).unwrap(); + supported_review.descriptor_id = None; + let mut existing = make_current_passport(&spec); + existing.semantic_review = Some(supported_review.clone()); + + let rebuilt = build_passport_preserving_proof_state_with_context( + &spec, + "2026-04-23T00:00:00Z", + Some(&existing), + existing.contract_hash.clone(), + &semantic_review_context, + ); + + assert_eq!(rebuilt.semantic_review, Some(supported_review)); + } + + #[test] + fn build_passport_preserving_proof_state_with_context_keeps_missing_data_seam_descriptor_id() { + let spec = make_loaded_data_seam( + "pricing/pricing_quote", + "units/pricing/pricing_quote.unit.spec", + ); + let specs_by_id = HashMap::from([(spec.spec.id.clone(), spec.clone())]); + let semantic_review_context = SemanticReviewContext::new(&specs_by_id); + let mut supported_review = evaluate_semantic_review(&spec).unwrap(); + supported_review.descriptor_id = None; + let mut existing = make_current_passport(&spec); + existing.semantic_review = Some(supported_review.clone()); + + let rebuilt = build_passport_preserving_proof_state_with_context( + &spec, + "2026-04-23T00:00:00Z", + Some(&existing), + existing.contract_hash.clone(), + &semantic_review_context, + ); + + assert_eq!(rebuilt.semantic_review, Some(supported_review)); + } + #[test] fn build_passport_preserving_proof_state_with_context_drops_mismatched_old_exact_id_review() { let apply_discount = diff --git a/spec-core/src/semantic_review.rs b/spec-core/src/semantic_review.rs index 5d2efd1..9368bd3 100644 --- a/spec-core/src/semantic_review.rs +++ b/spec-core/src/semantic_review.rs @@ -85,6 +85,8 @@ pub struct SemanticReview { pub verdict: SemanticVerdict, pub compatibility_key: String, #[serde(default, skip_serializing_if = "Option::is_none")] + pub descriptor_id: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] pub support_status: Option, #[serde(default, skip_serializing_if = "Vec::is_empty")] pub unsupported_reason_codes: Vec, @@ -453,6 +455,10 @@ enum FamilyBArgClassification { const SUM_DISCOUNT_STRATEGY_COMPATIBILITY_KEY: &str = "sum.discount_strategy.v1"; const DATA_PRICING_QUOTE_COMPATIBILITY_KEY: &str = "data.pricing_quote.v1"; +const DISCOUNT_STRATEGY_ECOMMERCE_DESCRIPTOR_ID: &str = "discount_strategy.ecommerce.v1"; +const DISCOUNT_STRATEGY_SERVICE_DESCRIPTOR_ID: &str = "discount_strategy.service.v1"; +const PRICING_QUOTE_ECOMMERCE_DESCRIPTOR_ID: &str = "pricing_quote.ecommerce.v1"; +const PRICING_QUOTE_SERVICE_DESCRIPTOR_ID: &str = "pricing_quote.service.v1"; const FUNCTION_WRAPPER_PIPELINE_CHAIN3_COMPATIBILITY_KEY: &str = "function.wrapper.pipeline.chain3.v1"; const FUNCTION_WRAPPER_PIPELINE_NORMALIZED_REQUIRED_ARG_COMPATIBILITY_KEY: &str = @@ -730,7 +736,7 @@ pub fn project_semantic_review_with_context( SemanticProjectionMode::Preserve => None, SemanticProjectionMode::Refresh => Some(match unit_kind { UnitKind::Function => unsupported_function_review(spec, context, &mut stack), - kind if is_portability_seam_kind(kind) => unsupported_surface_review(kind), + kind if is_portability_seam_kind(kind) => unsupported_surface_review(spec, kind), _ => unreachable!("non-function unit kinds are portability seams"), }), }, @@ -792,7 +798,7 @@ pub fn evaluate_semantic_review_with_context( } SupportedSurface::Unsupported(unit_kind) => Some(match unit_kind { UnitKind::Function => unsupported_function_review(spec, context, &mut stack), - kind if is_portability_seam_kind(kind) => unsupported_surface_review(kind), + kind if is_portability_seam_kind(kind) => unsupported_surface_review(spec, kind), _ => unreachable!("non-function unit kinds are portability seams"), }), } @@ -839,10 +845,11 @@ fn evaluate_supported_semantic_review( } } -fn unsupported_surface_review(unit_kind: UnitKind) -> SemanticReview { +fn unsupported_surface_review(spec: &LoadedSpec, unit_kind: UnitKind) -> SemanticReview { SemanticReview { verdict: SemanticVerdict::UnderSpecified, compatibility_key: unsupported_surface_compatibility_key(unit_kind), + descriptor_id: seam_descriptor_id(spec), support_status: (unit_kind == UnitKind::Function) .then_some(SemanticSupportStatus::Unsupported), unsupported_reason_codes: Vec::new(), @@ -867,6 +874,7 @@ impl UnsupportedFunctionDiagnostic { SemanticReview { verdict: SemanticVerdict::UnderSpecified, compatibility_key: UNSUPPORTED_FUNCTION_COMPATIBILITY_KEY.to_string(), + descriptor_id: None, support_status: Some(SemanticSupportStatus::Unsupported), unsupported_reason_codes: vec![self.reason_code], rewrite_hints: self @@ -1146,6 +1154,7 @@ fn evaluate_supported_sum_semantic_review(spec: &LoadedSpec) -> Option Option Option Option Option Some(SemanticReview { verdict: SemanticVerdict::Aligned, compatibility_key: compatibility_key.to_string(), + descriptor_id: None, support_status: Some(SemanticSupportStatus::Supported), unsupported_reason_codes: Vec::new(), rewrite_hints: Vec::new(), @@ -1344,6 +1359,7 @@ fn evaluate_supported_function_semantic_review( SupportedBodyClassification::Contradictory => Some(SemanticReview { verdict: SemanticVerdict::SemanticDrift, compatibility_key: compatibility_key.to_string(), + descriptor_id: None, support_status: Some(SemanticSupportStatus::Supported), unsupported_reason_codes: Vec::new(), rewrite_hints: Vec::new(), @@ -1356,6 +1372,7 @@ fn evaluate_supported_function_semantic_review( SupportedBodyClassification::OutsideHonestSubset => Some(SemanticReview { verdict: SemanticVerdict::UnderSpecified, compatibility_key: compatibility_key.to_string(), + descriptor_id: None, support_status: Some(SemanticSupportStatus::Supported), unsupported_reason_codes: Vec::new(), rewrite_hints: Vec::new(), @@ -1449,6 +1466,30 @@ fn detect_data_pricing_quote_family(spec: &LoadedSpec) -> bool { && executable_has_required_checkout_quote_roles(&executable.methods) } +fn seam_descriptor_id(spec: &LoadedSpec) -> Option { + match spec.spec.unit_kind().ok()? { + UnitKind::Sum => supported_sum_descriptor_id(spec), + UnitKind::Data => supported_data_descriptor_id(spec), + _ => None, + } +} + +fn supported_sum_descriptor_id(spec: &LoadedSpec) -> Option { + match spec.spec.id.as_str() { + "pricing/discount_strategy" => Some(DISCOUNT_STRATEGY_ECOMMERCE_DESCRIPTOR_ID.to_string()), + "billing/discount_strategy" => Some(DISCOUNT_STRATEGY_SERVICE_DESCRIPTOR_ID.to_string()), + _ => None, + } +} + +fn supported_data_descriptor_id(spec: &LoadedSpec) -> Option { + match spec.spec.id.as_str() { + "pricing/pricing_quote" => Some(PRICING_QUOTE_ECOMMERCE_DESCRIPTOR_ID.to_string()), + "billing/pricing_quote" => Some(PRICING_QUOTE_SERVICE_DESCRIPTOR_ID.to_string()), + _ => None, + } +} + fn authored_function_contract_is_supported( authored: &SemanticAuthoredFunctionPacket, family: SupportedFunctionFamily, @@ -1879,6 +1920,7 @@ fn evaluate_supported_checkout_quote_data_review(spec: &LoadedSpec) -> Option Option Option Option Option