Skip to content

feat(todo.contract-blueprint-staleness): enforce contract node-shape drift - #516

Merged
George-RD merged 1 commit into
mainfrom
loop/todo.contract-blueprint-staleness
Jul 28, 2026
Merged

feat(todo.contract-blueprint-staleness): enforce contract node-shape drift#516
George-RD merged 1 commit into
mainfrom
loop/todo.contract-blueprint-staleness

Conversation

@George-RD

Copy link
Copy Markdown
Collaborator

Unit

todo.contract-blueprint-staleness (node cairn.kernel.scanner), the enforcer
task of meta/changes/contract-node-shape-drift/. Its binding contract is that
change's specs/contract-node-shape-drift.md, archived here as its last task.

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
.cairn/state/contract-baselines.json.

What changed

  • src/scanner/contract_shape.rs (new): the check. Compares kind, parent,
    edges in that canonical order; paths is 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 in load_project.
  • src/summariser/accept.rs plus accept_transaction.rs (new): accept becomes
    the 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-backed BaselineError
    renderer, shared by the baseline command and accept, so no failure path
    reaching the CLI or MCP prints hardcoded prose.
  • Registries: CK033 allocated; the spec-rules row moves pending to enforced.

Tests

  • 9 unit tests over the check: no baselines, matching baseline, path-only edit,
    each of the three shape edits, canonical target order, copy-resolved message
    with no unsubstituted slots, and the two inert cases.
  • 4 unit tests over the rollback primitives (restore prior bytes, delete a file
    the call created, idempotence).
  • 6 integration tests over accept: records the reduced record, overwrites only
    its own entry, and three rollback paths.
  • 1 CLI end-to-end test: record, drift the blueprint, scan exits 0 with the
    Warning, --strict exits 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 new
findings), cairn hook all pass.

…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.
@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • Added detection for contract node-shape drift when declared kind, parent, or edges change.
    • Drift findings appear as warnings, with changed fields identified; strict scans now fail when applicable.
    • Accepted contracts record a baseline for future comparisons, while path-only changes are ignored.
  • Bug Fixes
    • Acceptance now rolls back contract and baseline changes if scanning or saving fails.
    • Improved baseline-related error messages and restore failure reporting.
  • Documentation
    • Updated specifications and registries to document the enforced contract node-shape drift rule.

Walkthrough

Adds 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.

Changes

Contract node-shape drift

Layer / File(s) Summary
Drift specification and registry contract
meta/changes/archive/2026-07-28-contract-node-shape-drift/*, docs/registries/*, docs/design-system/copy.toml, meta/todos/*
Defines the reduced baseline schema, comparison rules, warning finding, copy slots, enforced registry entry, prerequisites, and completed tracking metadata.
Scanner baseline comparison
src/scanner/*, src/cli/mod.rs, map.json
Loads baseline state during scans, compares kind, parent, and edges against the current blueprint snapshot, emits deterministic CAIRN_CONTRACT_NODE_SHAPE_DRIFT warnings, and verifies strict-mode behavior without rewriting state.
Accept-time recording and rollback
src/summariser/*, src/cli/commands/baseline.rs, tests/summariser_accept_baseline.rs, map.json
Records baselines after successful acceptance scans, restores contract and baseline bytes on later failures, centralizes baseline error rendering, and tests success, overwrite, and rollback paths.

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
Loading

Possibly related PRs

Poem

A bunny records each shape with care,
Then scans the blueprint, soft as air.
If edges hop or parents stray,
A warning marks the changed array.
Rollbacks tuck the state away.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main change: enforcing contract node-shape drift.
Description check ✅ Passed The description is clearly related to the changeset and explains the new rule, scanner behavior, accept rollback, and tests.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch loop/todo.contract-blueprint-staleness

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between a987a50 and ec26cde.

📒 Files selected for processing (20)
  • docs/design-system/copy.toml
  • docs/registries/error-codes.md
  • docs/registries/spec-rules.md
  • map.json
  • meta/changes/archive/2026-07-28-contract-node-shape-drift/design.md
  • meta/changes/archive/2026-07-28-contract-node-shape-drift/proposal.md
  • meta/changes/archive/2026-07-28-contract-node-shape-drift/specs/contract-node-shape-drift.md
  • meta/changes/archive/2026-07-28-contract-node-shape-drift/tasks.md
  • meta/todos/todo.contract-blueprint-staleness.md
  • src/cli/commands/baseline.rs
  • src/cli/mod.rs
  • src/scanner/contract_baselines.rs
  • src/scanner/contract_shape.rs
  • src/scanner/mod.rs
  • src/summariser/accept.rs
  • src/summariser/accept_transaction.rs
  • src/summariser/baseline.rs
  • src/summariser/baseline_copy.rs
  • src/summariser/mod.rs
  • tests/summariser_accept_baseline.rs
💤 Files with no reviewable changes (1)
  • src/summariser/baseline.rs

Comment on lines +18 to 21
- [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`

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 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

@George-RD

Copy link
Copy Markdown
Collaborator Author

Declining the archived-task-history finding: the two checkboxes were ticked while meta/changes/contract-node-shape-drift/ was still the active change, and cairn change apply moved the completed change into meta/changes/archive/ in the same commit. That order is what the landing procedure prescribes (complete the tracker, then archive), so the diff shows a rename plus the completion that preceded it, not a rewrite of history. The guideline cited covers archive/openspec/changes-archive/, a different tree.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant