Skip to content

fix(exec): warn when a script restates from a stale DESCRIBE (#261) - #503

Merged
MendixMau merged 9 commits into
masterfrom
fix/stale-describe-261
Oct 21, 2026
Merged

MendixMau merged 9 commits into
masterfrom
fix/stale-describe-261

Conversation

@MendixMau

@MendixMau MendixMau commented Oct 21, 2026 •

Copy link
Copy Markdown
Owner

Direct lane

Lands after #498. This branch was cut from #498's head (2a0886b), so until #498 merges the diff here also shows #498's commits. This PR's own changes are commits 8431714 (the instrument) and 7f91347 (row-21 trigger). #498's head 292ded7 is merged in (a merge, not a rebase). Review this PR's own diff with git diff 292ded7..1858281.

What changed and why (one paragraph):
A script drafted from a DESCRIBE taken before an earlier script of the batch ran put four old messages back, with check, mxbuild and lint green and exit 0 (#261). New project-bin/describe-baseline.sh stamp <script> writes a header from a DESCRIBE at draft time:

  • one -- baseline: <Doc> sha256=<hash> line (or absent) per document the script restates;
  • one -- baseline-mxcli: <version> line.

exec.sh runs verify only for a script that carries such a header. It prints WARN: stale draft: lines, and DRIFT also goes into the BUILD-LOG detail. It never refuses, and the exit codes (0/1/2/4/5) do not change. verify reports:

  • DRIFT, with the first lines of the DESCRIBE diff;
  • STALEHEADER when the header names a document the script no longer restates. This is now a warn (exit 1), so exec.sh prints it;
  • RESTAMP after an mxcli upgrade: the version recorded at stamp time differs, so a moved hash is not called drift.

A re-run by the script itself (it is the last recorded writer in #498's ledger) and a fresh model (document absent) read SAME.

Preflight STOP 21: its trigger now names create or modify, and the rule gains one clause. There is no new row. It adds the -- supersedes: header, describe-baseline.sh stamp when drafting ahead, and that exec.sh only warns. The DESCRIBE-visibility row (old STOP 34) is dropped.

Fixes #261
Supersedes #311 (with #498)

Behaviour change to check in your project: exec.sh output can carry WARN: stale draft: … lines, and the BUILD-LOG detail cell WARN stale baseline: <docs>. Set MXTK_RESTATE_GUARD=off to skip the check. Scripts stamped by this tool carry -- baseline: / -- baseline-mxcli: header comments.

Impact

Row Answer
Process Nothing new blocks. One optional draft-time command (bin/describe-baseline.sh stamp <script>, named in STOP 21).
Dependencies Touches project-bin/exec.sh (one block after #498's co-owner block) and bin/lib/install-manifest.sh (new installed script). New artifact read: the -- baseline: header. Its producer is stamp, which is opt-in; when there is no header, exec.sh does nothing. It reads #498's ledger through script-owners.sh last-writer, so it depends on #498 and must merge after it.
Context cost skills/learned-mdl-preflight.md (baseline-routed): +24 words in row 21 (+21 for the rule clause, +3 for the trigger), no new row. render-routing.sh --check: base Baseline: 73804 words (budget 80000) (master 9f3948c reads the same) → head Baseline: 73828 words (budget 80000). Stage 5: mdl 30345 → 30369; nothing else changes.
Behaviour NOT RUN: the A/B for the changed STOP 21 text (always-loaded) is the maintainer's to run. No agent stub changed.
Runtime Stub mxcli and mxbuild, wall time, 5 runs each, on an exec of a script with no header. 20 scripts: master 88e9848 1406-1458 ms; #498 1495-1523 ms; this PR 1458-1481 ms. 600 scripts: master 1382-1443 ms; #498 1485-1525 ms; this PR 1482-1584 ms. A script with no header gets one extra grep and zero DESCRIBE calls (fixture-asserted). A stamped script pays one DESCRIBE per baselined document plus one mxcli --version; one DESCRIBE took 0.22 s with real mxcli v0.26.0.

Field evidence: no client project was run. Before the split, exec.sh ran with real mxcli v0.26.0 on a scratch copy of mxcli's public testdata/pedapp model:

  1. Script 01 wrote 'OLD wording'.
  2. Script 03 was stamped from a DESCRIBE.
  3. Script 02 wrote 'NEW wording'.
  4. Script 03 then exited 0 and printed WARN: stale draft … Administration.ACT_Probe — changed since the script was drafted, with the real diff (- log node 'Probe' 'OLD wording'; + log node 'Probe' 'NEW wording';).

The golden captures in tests/wave2/fixtures/restatement-guard/ come from that model: the not-found output of all four kinds (shipped in #498) and the mxcli --version line (shipped here). A field run on a real project is still owed.

Fails on base, passes on head: tests/wave2/test-describe-baseline.sh

Subject Passed Failed
head 1858281 33 0
#498 head (no describe-baseline.sh) 10 24
#498's exec.sh with the new describe-baseline.sh beside it 30 3
this PR's exec.sh with the earlier 8ad0d77 describe-baseline.sh 28 5

Re-review (head 1858281): test-describe-baseline 33/0, test-restatement-guard 58/0 (8431714 fails the 2 new fresh-clone cases), test-exec-gate-retry 47/0, test-exec-gate-log 24/0, test-restore-mpr-arg 10/0. check-scripts, check-portability, the leak guard, check-pr-discipline: all clean. The client grep is 0. On 600 scripts: master 9f3948c 1405-1440 ms, this head 1480-1529 ms, 0 DESCRIBE calls.

Scoped checks (head 8431714):

  • Fixtures:

    • test-describe-baseline 33/0
    • test-restatement-guard 51/0
    • test-exec-gate-retry 47/0
    • test-exec-gate-log 24/0
    • test-exec-check-refusal 10/0
    • test-exec-crash-rollback 13/0
    • test-exec-lock-watch 84/0
    • test-exec-step-times 7/0
    • test-bug07-08 45/0
    • test-manifest-clean-checkout 1/0
  • Guards, all clean: check-scripts, check-portability, render-routing --check, the leak guard, check-no-private-citations, check-pr-discipline. The client-name grep of added lines returns 0.

  • No client data anywhere in the diff

  • Size cap: this PR's own commit touches 7 files with 226 non-fixture lines (+223 −3).

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

  • For a new/changed instrument:

  • For a new skill: n/a

  • CHANGELOG.md line, credited to AlvinnYeo ([bug] exec.sh: restating a microflow from a stale DESCRIBE silently reverts an earlier exec #261)

  • New bug entries: none


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
- project-bin/describe-baseline.sh: stamp writes -- baseline: <Doc>
  sha256=<hash> and -- baseline-mxcli: <version> from a DESCRIBE at draft
  time; verify reports DRIFT (with diff), STALEHEADER (now a warn) and
  RESTAMP after an mxcli upgrade. A re-run by the last recorded writer
  (script-owners.sh ledger) and a fresh model are SAME.
- exec.sh: verify only for a script with a baseline header; WARN lines and
  a BUILD-LOG cell, never a refusal, exit codes unchanged.
- learned-mdl-preflight.md STOP 21: one clause (+21 words).
- Fixture tests/wave2/test-describe-baseline.sh; mxcli v0.26.0 --version
  capture.

Lands after #498. With #498, supersedes draft #311.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
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
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
Re-review of #503: the row-21 trigger now reads "`DROP` + `CREATE`,
`create or modify` or `create or replace` any document another script
also edits". The rule clause is unchanged. Baseline +24 words in total
for this PR.

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

Copy link
Copy Markdown
Owner Author

Behaviour A/B for the row-21 change (RUNBOOK decision rule: T no worse than M on every scored metric)

  • M = master c2b9bec, T = this head 1858281. The packs differ only in preflight row 21 (+24 words). Fresh sonnet agent per rep; 66 reps: 10 × mf, 8 × page, 3 × each of the 5 probes, per arm. read_all = 1 in all 66.
  • The planted STOP 21 case (mf: whole-document restatement of a shared microflow) is flagged in 10/10 reps in both arms. All 10 T reps now also name -- supersedes: and describe-baseline.sh stamp (0/10 in M), so the clause is read and used as intended.
  • Quoting (mf quoted): on first scoring, two T reps came out at 55% and 61%. The cause: the T agents follow the new clause and write header comments such as -- supersedes: and DESCRIBE MICROFLOW Sales.X, and score.sh counts unquoted names inside comments. Counting only the MDL code lines, M and T are the same: median 91% in both, 8 of 10 reps at 91% or higher in both.
  • The other metrics are equal or differ by one rep: probes probe_first the same in both arms; sections, report, planted, vfb, cinloop, rinloop, pos and txtcrumb identical; page crumbs 3/3 in 5 of 8 reps in both arms (T has one 0/3, M none below 2/3); mf folder complete in 7 vs 6 reps; one T page rep wrote an extra H1.
  • Verdict: T is no worse on behaviour. Follow-up for the scorer: quoted should skip comment lines.

Generated by Claude Code

MendixMau added a commit that referenced this pull request Oct 21, 2026
…ite (#498)

Fixes #269.
- project-bin/script-owners.sh: last-writer ledger (exec.sh records after every kept write; machine-local, self-gitignored, compacted under the exec lock). Run order zero-pads digit runs per path component.
- exec.sh warns (never refuses, exit codes unchanged) when a script restates a document a newer owner wrote and that write is live; a script counts as run via a ledger row, a done- name, or an "applied" BUILD-LOG row, so a fresh clone still warns. A new owner without a `-- supersedes:` header gets a one-time NOTE outside BUILD-LOG. DESCRIBE capped at 5 per exec; zero on the happy path. MXTK_RESTATE_GUARD=off skips it.
- restore-mpr.sh appends a RESTORE marker (exec.sh's own rollback does not); gate-check prints the co-owned count.

Review: independent adversarial review over three rounds (8ad0d77, 2a0886b, 292ded7). Round 1 found byte-sort run order (11-fixes before 2-core, a forward exec read as a replay and costing DESCRIBE), a warning on every forward exec, a stale ledger after restore-mpr.sh, an unbounded ledger, uncapped DESCRIBE, an always-loaded STOP 34 whose grep hit 61/68 pedapp pages, and 544 lines over the cap; all fixed (zero-padded order, first-run NOTE, RESTORE marker, compaction, cap of 5, STOP 33/34 dropped, #261 split into #503). Round 2 found that an empty ledger (fresh clone or cloud session) silenced the replay; fixed by counting done- names and "applied" BUILD-LOG rows (no failure wording exec.sh writes contains "applied"). Verified on master c2b9bec merged with this head: test-restatement-guard 58/0 (the 2 new evidence cases fail with the round-1 script), test-exec-gate-retry 52/0, test-restore-mpr-arg 10/0, test-exec-gate-log 24/0 serially; zero DESCRIBE and no extra mxbuild on the happy path; exit codes and BUILD-LOG columns unchanged; check-pr-discipline clean; client grep 0. exec.sh timing on 600 scripts with stub mxcli/mxbuild: ~+90 ms. The occasional gate-log case C failure under parallel load reproduces on master (filed separately). Known limit: the ledger cannot see Studio Pro edits or a git pull/checkout of the .mpr (#504). No always-loaded text changed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
#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
MendixMau merged commit 96e3553 into master Oct 21, 2026
2 checks passed
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.

[bug] exec.sh: restating a microflow from a stale DESCRIBE silently reverts an earlier exec

2 participants