Skip to content

Fix/commit seam id uniqueness gate - #43

Open
jesseh wants to merge 2 commits into
aklos:mainfrom
ignitionworks:fix/commit-seam-id-uniqueness-gate
Open

Fix/commit seam id uniqueness gate#43
jesseh wants to merge 2 commits into
aklos:mainfrom
ignitionworks:fix/commit-seam-id-uniqueness-gate

Conversation

@jesseh

@jesseh jesseh commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Problem

Responsibilities (and nodes, links, groups) are identified by a globally-unique id, and every consumer bakes that assumption into its data structures:

  • diff::index_responsibilities (and the TS twin indexResponsibilities) build an id-keyed map with last-write-wins — a second copy of an id under a different host is silently dropped before the diff runs.
  • commit.rs:find_responsibility returns the first match and stops.

So when a responsibility is moved from host A to host B but a stale copy is left on A (a botched move, a set_model overwrite, a hand-edit), the committed model holds the same id twice. The plan diff collapses each side to a single copy, from and to compare equal, no change is emitted, nobody is ever prompted to reconcile it, and the self-healing retain in the commit fold never fires. The wrong copy sits committed and invisible indefinitely.

validate() already detects this (the "globally unique" check), but it is advisory — nothing gates on it, so the state is reported into a warnings list no writer consults.

The underlying category: consumers assume an invariant that only an advisory check enforces, and they encode the assumption as silent deduplication — so a violation is not merely unhandled, it is actively hidden.

Fix

Promote the invariant from an advisory read to a hard gate at the one seam that persists committed state.

  1. scryer-core/src/validate.rs — new pub fn structural_violations(model) -> Vec<String>: the silent-misbind subset only (duplicate node/link/group ids, duplicate property labels, per-host responsibility repeats, cross-host responsibility-id collisions). It deliberately excludes advisory findings (length caps, disconnected nodes, link legality) that are legitimate transient states, so it can guard every write without false positives.

  2. scryer-core/src/storage.rswrite_model_at (the single committed-layer writer every commit / fold / set_model rides through) now runs structural_violations on the exact bytes about to be written and returns Err, naming each violation, if any exist. A silently-misbindable duplicate can no longer reach model.scry.

  3. scryer-mcp/src/tools/read.rsvalidate_model now splits its output into a BLOCKING section (structural violations — "a commit will be REFUSED until resolved") and advisory warnings, de-duped against each other. The plan write path intentionally does not gate, so a draft can transiently hold a duplicate; this is where the agent sees it before hitting the commit refusal.

Tests

  • structural_violations: flags a cross-host collision + duplicate node id; empty on a clean model.
  • write_model_at: rejects a cross-host duplicate (nothing persisted); accepts a clean model.
  • validate_model: a draft duplicate surfaces in the BLOCKING section.

All existing tests pass; full scryer-core (171) and scryer-mcp (121) suites green on top of main.

Deliberately out of scope

No new Change variant to surface duplicates on the live canvas diff (diff.rs / planDiff.ts) — it ripples across TS, Rust, and the UI renderer for a case the seam gate now prevents at commit, and validate_model already surfaces a draft duplicate. Reasonable follow-up if earlier canvas-time warning is wanted.

HAPI added 2 commits August 29, 2026 22:32
validate::structural_violations() returns the silent-misbind subset
(duplicate node/link/group ids, duplicate property labels, per-host and
cross-host responsibility-id collisions). write_model_at -- the
committed-layer seam every commit/fold/set_model rides through -- now
rejects any model carrying one, naming each violation.

A responsibility left on two hosts by a botched move is invisible to the
plan diff: its id-keyed index keeps only one copy per id, so from and to
collapse to a single entry, compare equal, emit no change, and the stale
wrong copy sits committed indefinitely with nothing able to surface it.
Fail loud at the seam instead of letting it sit.
Surface structural invariant violations (which write_model_at now
refuses) in their own BLOCKING section, apart from advisory warnings and
de-duped against them, so an agent editing the plan sees a duplicate id
before a commit is refused at the seam. The plan write path intentionally
does not gate, so the draft can hold the duplicate; validate_model is
where it becomes visible.
@jesseh

jesseh commented Aug 29, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for this innovative tool. I think it has the potential to become central to creating a sustainable AI workflow on a complex codebase.

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