Skip to content

fix(exec): warn when replaying an older owning script reverts newer work (#269) - #498

Merged
MendixMau merged 5 commits into
masterfrom
fix/restatement-guard-261-269
Oct 21, 2026
Merged

MendixMau merged 5 commits into
masterfrom
fix/restatement-guard-261-269

Conversation

@MendixMau

@MendixMau MendixMau commented Oct 21, 2026 •

Copy link
Copy Markdown
Owner

Direct lane

What changed and why (one paragraph):
When a document is created by several mdlsource/ scripts, re-running an older one silently reverted the newer one's work: exit 0, every gate green (#269). Three field apps had 99/579, 130/357 and 38/159 such documents. New project-bin/script-owners.sh maps each microflow, nanoflow, page and snippet to the scripts that create or restate it, in run order, and reads -- supersedes: / -- superseded-by: headers. Run order compares digit runs as numbers, so 2-core/ runs before 11-fixes/ and 9-a before 10-b; a leading done- is ignored. exec.sh records every kept write in a machine-local last-writer ledger, and restore-mpr.sh marks the ledger when it restores. Before a write, exec.sh prints:

  • WARN (also in the BUILD-LOG detail) only when the script would revert a newer owner's write that is live;
  • one NOTE line (not in BUILD-LOG) the first time a new owner restates a document an older script owns without a -- supersedes: header;
  • nothing when neither script has ever run on this machine (the script is new).

It never refuses, and the exit codes (0/1/2/4/5) do not change. gate-check.sh prints the co-owned count when it is above 0. The #261 part (stale-DESCRIBE baseline) is a separate PR that lands after this one; together they supersede draft #311.

Fixes #269

Behaviour change to check in your project: exec.sh output can carry WARN: restatement: … and NOTE: new owner of … lines, and the BUILD-LOG detail cell WARN reverts: <docs>. Set MXTK_RESTATE_GUARD=off to skip the check. A new machine-local file appears: .claude/loop/script-owners/written.tsv. It is not committed, because that folder carries its own * .gitignore (the same pattern as app-restart.sh's .claude/loop/app-restart/).

How "is the newer owner's write live?" is decided, cheapest first:

  1. If neither script has run, the script is new: no message and no DESCRIBE. A script has run when any of these holds:

    • it has a ledger row;
    • its file name has the done- prefix;
    • docs/BUILD-LOG.md has a row naming it in backticks whose result says applied (the done-drift-check.sh pattern).

    The last two are committed, so a fresh clone or cloud session (empty ledger) still WARNs on a replay over committed newer work. A script nobody has run yet (draft-ahead) stays quiet. None of this calls mxcli.

  2. Otherwise read the ledger's last writer of the document, ignoring rows before the last RESTORE row. If it is this script, or a script that runs before it, nothing would be reverted.

  3. Otherwise run one DESCRIBE. If the document is absent (a fresh model), there is nothing to revert.

  4. With no ./mxcli to ask, it is a WARN.

DESCRIBE is capped at 5 calls per exec; any further documents print as CONFLICT (not checked). record compacts the ledger to the last row per (script, document) plus the last RESTORE row, through a temp file and mv, while exec.sh holds the exec lock. exec.sh's own rollback passes MXTK_RESTORE_SAME_LEDGER=1, because its snapshot predates nothing recorded; every other restore writes the marker.

Impact

Row Answer
Process Nothing new blocks. One optional header (-- supersedes:). gate-check gains one information line when the co-owned count is above 0.
Dependencies Touches project-bin/exec.sh (one block after the pre-exec check, one record call after LOOK owed, one cell in the BUILD-LOG detail, one env var on its rollback), project-bin/restore-mpr.sh (the RESTORE row), bin/gate-check.sh, and bin/lib/install-manifest.sh (new installed script). New artifact: the ledger, machine-local and not committed (self-gitignored; init-project.sh does not ignore .claude/loop/, so the folder ignores itself, which also covers existing projects). The ledger's producers are exec.sh record (mandatory, after every kept write) and restore-mpr.sh; its consumer is script-owners.sh check. check also reads docs/BUILD-LOG.md (producer: exec.sh, every run) and the done- names (producer: the iterative-build-loop.md rename). The ledger cannot see Studio Pro edits or a git pull/git checkout of the .mpr; the header says so, and a post-merge/post-checkout hook is the follow-up. Branched from 88e9848; master has since moved to 9f3948c (#497) and the branch merges cleanly (git merge-tree). No new tools: bash 3.2, awk, grep, sed.
Context cost none: no routed or always-loaded file touched (render-routing.sh --check: Baseline: 73804 words (budget 80000), base and head).
Behaviour n/a: scripts only (the STOP-21 clause is in the #261 PR).
Runtime Re-review, 600 scripts, same harness: master 9f3948c 1405-1440 ms, head 292ded7 1502-1545 ms, 0 DESCRIBE calls. First pass (stub mxcli and mxbuild, wall time, 5 runs each; base master 88e9848's exec.sh on a sole-owner script): 20 scripts: base 1406-1458 ms, head 1495-1523 ms. 600 scripts: base 1382-1443 ms, head 1485-1525 ms. That is about +90 ms per exec, with zero DESCRIBE calls (fixture-asserted). gate-check count takes 345 ms on 600 scripts, once per gate run.

Field evidence: no client project was run. Before the split, exec.sh ran with real mxcli v0.26.0 (check, exec, DESCRIBE) on a scratch copy of mxcli's public testdata/pedapp model. Replaying 01 over 02 printed WARN: restatement … newer owner mdlsource/02-A.mdl — replaying this script reverts it. A fresh-model rebuild in run order printed 0 WARN lines, including with a stale ledger, because DESCRIBE answered not-found. The not-found output (microflow, nanoflow, page, snippet) and two found documents are captured verbatim from that model in tests/wave2/fixtures/restatement-guard/. A field run on a real project is still owed.

Fails on master, passes on head: tests/wave2/test-restatement-guard.sh

Subject Passed Failed
head 292ded7 58 0
previous head 2a0886b (re-review cases) 56 2
master exec.sh 14 38
this PR's previous head 8ad0d77 23 28
head with master's restore-mpr.sh (control) 49 2

On 2a0886b, the 2 failures are the new fresh-clone cases: empty ledger plus done-09 (which supersedes 05), and empty ledger plus a BUILD-LOG applied row for 09; replaying 05 must WARN in both. Also new: 09 never run, and a rolled-back BUILD-LOG row, both stay quiet. exec.sh's own rollback must write no RESTORE row; a control with that flag removed fails exactly that assertion. The 28 failures on 8ad0d77 are every review finding: the run-order cases, NOTE vs WARN, new-script quiet, compaction, the RESTORE marker, the DESCRIBE cap, and live. The control's 2 failures are exactly the RESTORE-marker case: 05, then 09 (supersedes 05), replay 05, restore, replay 05 must WARN.

Re-review checks (head 292ded7): test-restatement-guard 58/0, test-exec-gate-retry 47/0, test-restore-mpr-arg 10/0, test-exec-gate-log 24/0. One batch run showed 23/1, the same intermittent failure as before; it did not reproduce in 3 serial reruns or 12 runs in parallel with master. check-scripts, check-portability (an awk -v backslash it flagged is fixed in 292ded7), render-routing --check (73804, unchanged), the leak guard, check-pr-discipline: all clean. The client grep is 0.

Scoped checks (head 2a0886b):

  • Fixtures:

    • test-restore-mpr-arg 10/0
    • test-exec-change-class 21/0
    • test-exec-check-refusal 10/0
    • test-exec-cr-brief-warn 19/0
    • test-exec-crash-rollback 13/0
    • test-exec-gate-log 24/0 (one run showed 23/1, three reruns 24/0)
    • test-exec-gate-retry 47/0
    • test-exec-hash-verify 43/0
    • test-exec-lock-watch 84/0
    • test-exec-step-times 7/0
    • test-exec-subjects 6/0
    • test-exec-lint-autorestore 17/0
    • test-bug07-08 45/0
    • test-sync-only-bin 40/0
    • test-bug12-sync 62/0
    • test-bug03-gates 47/0
    • test-wrong-verdicts 20/0
    • test-manifest-clean-checkout 1/0
  • Guards, all clean: check-scripts, check-portability, render-routing --check, the leak guard, check-pr-discipline. The client-name grep of added lines returns 0.

  • No client data anywhere in the diff

  • Size cap: 17 files, but 10 are captures or fixtures. Non-fixture lines: 350 (+347 −3).

  • Test tier reached: T1/T2. A stub-mxcli fixture, plus real mxcli v0.26.0 on a public model before the split. No T3 field project.

  • For a new/changed instrument:

    • Golden input is captured, not hand-written.
    • Both layouts: two-tree app/mdlsource/ is asserted.
    • Both platforms: bash 3.2 only, no sort -V, grep -rlF -f, awk ENVIRON. Checked by inspection; not run on macOS or Git Bash.
  • For a new skill: n/a

  • CHANGELOG.md line, credited to MendixMau (One document, several owning scripts: re-running an older mdlsource script silently reverts later changes #269)

  • New bug entries: none

https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw


Generated by Claude Code

A script restated from a stale DESCRIBE (#261), or a re-run of an older
script that owns the same document (#269), silently reverted newer work:
exit 0, every gate green, no warning.

- project-bin/script-owners.sh: which mdlsource/ scripts create or restate
  each microflow/nanoflow/page/snippet; -- supersedes: / -- superseded-by:
  headers; a last-writer ledger exec.sh appends after every kept write, so
  an in-order rebuild stays quiet without asking mxcli.
- project-bin/describe-baseline.sh: stamp -- baseline: <Doc> sha256=... at
  draft time, verify at exec (opt-in: only stamped scripts DESCRIBE).
- exec.sh: WARN lines + BUILD-LOG detail; never a refusal, exit codes
  unchanged. Happy path: one grep over mdlsource/, zero mxcli calls.
- gate-check.sh: co-owned count (info only).
- learned-mdl-preflight.md: STOP 33 and 34.
- Fixture tests/wave2/test-restatement-guard.sh; not-found DESCRIBE output
  captured from mxcli v0.26.0.

Rebuilt from master; supersedes draft #311 without its merged #315 and
crash-rollback (#424) hunks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
…d compaction (#269)

Review of #498 (MERGE-AFTER-FIXES). This PR is now the #269 part only.

- script-owners.sh: run order pads digit runs per path component
  (2-core before 11-fixes, 9-a before 10-b); a script that has never run,
  over an owner that never ran either, is new (no message, no DESCRIBE).
- A new owner without a supersedes header gets one NOTE line on its first
  run, out of BUILD-LOG; WARN only when a newer owner's write is live.
- restore-mpr.sh appends a RESTORE row; rows before it are ignored, so a
  replay after a restore asks the model again. exec.sh's own rollback
  does not mark.
- record compacts the ledger (last row per script and document, last
  RESTORE) through a temp file and mv; the folder self-gitignores.
- At most 5 DESCRIBE calls per check; the rest print CONFLICT (not checked).
  The DESCRIBE existence check now lives in script-owners.sh (live).
- Removed: describe-baseline.sh, its exec.sh verify block and the
  preflight rows 33/34 (the #261 part moves to its own PR; STOP 34 dropped).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
@MendixMau MendixMau changed the title fix(exec): warn when a restatement reverts newer work (#261, #269) fix(exec): warn when replaying an older owning script reverts newer work (#269) Oct 21, 2026
Re-review of #498. The ledger is machine-local, so in a fresh clone or
cloud session "neither script has a ledger row" read as "S is new" and a
replay over committed newer work was silent.

- has-run now also counts a done- file name and a docs/BUILD-LOG.md row
  naming the script in backticks whose result says applied (the
  done-drift-check.sh pattern); no mxcli call. Draft-ahead stays quiet.
- Header: the ledger cannot see Studio Pro edits or a git pull/checkout
  of the .mpr.
- Fixture: three fresh-clone cases (done-09, BUILD-LOG applied row,
  never run) and a rolled-back row; exec.sh's own rollback writes no
  RESTORE row.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
MendixMau pushed a commit that referenced this pull request Oct 21, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
…-v (#269)

check-portability flagged a backslash in an awk -v value (gawk and mawk
differ on escapes).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
MendixMau pushed a commit that referenced this pull request Oct 21, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
@MendixMau
MendixMau merged commit 555feb2 into master Oct 21, 2026
2 checks passed
MendixMau pushed a commit that referenced this pull request Oct 21, 2026
#498 landed as a squash, so its commits on this branch conflicted with master in
bin/lib/install-manifest.sh and project-bin/exec.sh. Kept this branch's side of both hunks
(describe-baseline.sh in the manifest; the #261 stale-draft block and the shared note comment).
The diff against master is now exactly this PR's own change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
MendixMau added a commit that referenced this pull request Oct 21, 2026
…ow 21 (#503)

Fixes #261. Supersedes #311 (with #498).
- project-bin/describe-baseline.sh: opt-in `-- baseline:` header. `stamp` records a DESCRIBE of each restated document plus the mxcli version; `verify` compares with the live model.
- exec.sh: for a script with that header, warns on a stale draft (DRIFT into the BUILD-LOG detail; STALEHEADER warns; RESTAMP after an mxcli upgrade instead of drift). Warn-only; exit codes unchanged; no mxcli call for a script without the header.
- skills/learned-mdl-preflight.md row 21: trigger names `create or modify` / `create or replace`; Rule gains "A new owner adds `-- supersedes: <script>`; drafting ahead of unrun scripts, `bin/describe-baseline.sh stamp <script>` after the DESCRIBE (exec.sh only warns)." (+24 always-loaded words; baseline 73828/80000).

Review: independent review of 8431714, then 1858281, stacked on #498; after #498's squash the merge desk merged master in (780f0ad), keeping this branch's side of the two install-manifest/exec.sh hunks so the diff against master is exactly this PR. On the merged tree: test-describe-baseline 33/0 (3 cases fail against #498's exec.sh), test-restatement-guard 58/0, test-exec-gate-retry 52/0; render-routing in sync, baseline 73828 words; check-pr-discipline clean; client grep 0. Behaviour A/B on row 21 (66 sonnet reps, M = master c2b9bec, T = 1858281, packs differing only in row 21): the planted STOP 21 case flagged 10/10 in both arms, and all 10 T mf reps now name `-- supersedes:` and `describe-baseline.sh stamp` (0/10 in M). The first-pass `quoted` dip (two T reps at 55/61%) is a scorer artifact (it counts unquoted names inside the new header comments); on MDL code lines only, M and T both have median 91% and 8/10 reps >= 91%. Probes and all other metrics equal or within one rep. Verdict: T no worse.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
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.

One document, several owning scripts: re-running an older mdlsource script silently reverts later changes

2 participants