feat(todo.contract-blueprint-staleness): enforce contract node-shape drift - #516
Conversation
…drift Success criterion: `cairn scan` emits exactly one Warning `CAIRN_CONTRACT_NODE_SHAPE_DRIFT` (CK033) for an eligible node whose recorded baseline no longer matches its declared shape, `cairn scan --strict` exits 1 on it, and the scan never rewrites the baseline file. The scanner compares `.cairn/state/contract-baselines.json` against the current blueprint snapshot on `kind`, `parent`, and `edges`, in that order. Eligibility is the spec's conjunction: a recorded entry, a node the blueprint declares, and a contract pointer that loads. Path-only edits stay ungated, nothing is backfilled, and the scanner is never a writer. `summariser::accept()` becomes the accept-time baseline writer. Its commit sequence is now failure-atomic: the baseline file is snapshotted before the contract is installed, and every fallible step after the post-write scan restores the contract text, the baseline bytes, and the draft's pending state. `BaselineError` rendering moved to one copy-backed renderer shared by the `baseline` command and accept, so no failure path prints hardcoded prose. Registry: CK033 allocated in error-codes.md; the spec-rules row moves from pending to enforced. The change `contract-node-shape-drift` is archived, its last task complete.
📝 WalkthroughSummary by CodeRabbit
WalkthroughAdds accept-time contract node baselines, transactional rollback, scanner detection for node-shape drift, warning findings, copy rendering, registry updates, and tests covering detection and failure recovery. ChangesContract node-shape drift
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Accept
participant ContractStore
participant Scanner
participant BaselineState
Accept->>ContractStore: install accepted contract
Accept->>Scanner: run post-write scan
Scanner-->>Accept: successful scan
Accept->>BaselineState: record accepted node baseline
Accept->>ContractStore: finalize draft store update
Scanner->>BaselineState: read baseline state during later scan
Scanner-->>Accept: report node-shape drift warning
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@meta/changes/archive/2026-07-28-contract-node-shape-drift/tasks.md`:
- Around line 18-21: Restore the original checklist states in the archived task
history file; do not modify completed-task records under the archive. Record
these completions instead in the canonical active trackers, preserving the
existing archive contents unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 0a7d61b2-7959-4763-bdf9-081841bb49b5
📒 Files selected for processing (20)
docs/design-system/copy.tomldocs/registries/error-codes.mddocs/registries/spec-rules.mdmap.jsonmeta/changes/archive/2026-07-28-contract-node-shape-drift/design.mdmeta/changes/archive/2026-07-28-contract-node-shape-drift/proposal.mdmeta/changes/archive/2026-07-28-contract-node-shape-drift/specs/contract-node-shape-drift.mdmeta/changes/archive/2026-07-28-contract-node-shape-drift/tasks.mdmeta/todos/todo.contract-blueprint-staleness.mdsrc/cli/commands/baseline.rssrc/cli/mod.rssrc/scanner/contract_baselines.rssrc/scanner/contract_shape.rssrc/scanner/mod.rssrc/summariser/accept.rssrc/summariser/accept_transaction.rssrc/summariser/baseline.rssrc/summariser/baseline_copy.rssrc/summariser/mod.rstests/summariser_accept_baseline.rs
💤 Files with no reviewable changes (1)
- src/summariser/baseline.rs
| - [x] Build the non-generative baseline re-record surface, with record and drop | ||
| - [x] Implement the enforcer against `specs/contract-node-shape-drift.md`, | ||
| allocate its code in `docs/registries/error-codes.md`, fill the rule row's | ||
| `Code` cell, and promote the row from `pending` to `enforced` |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Do not rewrite archived task history.
This file is under meta/changes/archive/, so changing these checklist states alters a historical record. Revert these lines and record completion through the canonical active trackers instead.
As per coding guidelines, archived phases under archive/openspec/changes-archive/ must not be rewritten.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@meta/changes/archive/2026-07-28-contract-node-shape-drift/tasks.md` around
lines 18 - 21, Restore the original checklist states in the archived task
history file; do not modify completed-task records under the archive. Record
these completions instead in the canonical active trackers, preserving the
existing archive contents unchanged.
Source: Coding guidelines
|
Declining the archived-task-history finding: the two checkboxes were ticked while |
Unit
todo.contract-blueprint-staleness(nodecairn.kernel.scanner), the enforcertask of
meta/changes/contract-node-shape-drift/. Its binding contract is thatchange's
specs/contract-node-shape-drift.md, archived here as its last task.Success criterion
cairn scanemits exactly one WarningCAIRN_CONTRACT_NODE_SHAPE_DRIFT(CK033)for an eligible node whose recorded baseline no longer matches its declared
shape,
cairn scan --strictexits 1 on it, and the scan never rewrites.cairn/state/contract-baselines.json.What changed
src/scanner/contract_shape.rs(new): the check. Compareskind,parent,edgesin that canonical order;pathsis deliberately not compared.Eligibility is the spec conjunction: a recorded baseline entry, a node the
blueprint declares, and a contract pointer that loads. Keyed by the
pointer-owning node, so a node whose pointer was removed is inert.
src/scanner/mod.rs: reads baselines and runs the check inload_project.src/summariser/accept.rsplusaccept_transaction.rs(new): accept becomesthe accept-time baseline writer. The baseline file is snapshotted before the
contract is installed, and every fallible step after the post-write scan
restores the contract text, the baseline bytes (deleting a file the call
created), and the draft's pending state.
src/summariser/baseline_copy.rs(new): one copy-backedBaselineErrorrenderer, shared by the
baselinecommand and accept, so no failure pathreaching the CLI or MCP prints hardcoded prose.
Tests
each of the three shape edits, canonical target order, copy-resolved message
with no unsubstituted slots, and the two inert cases.
the call created, idempotence).
its own entry, and three rollback paths.
Warning,
--strictexits 1, and the baseline file is byte-identical after.Gates
cargo fmt --check,clippy --all-targets --all-features -D warnings,cargo test(1939 passed),cairn scan(three pre-existing Infos, no newfindings),
cairn hook allpass.