Commit39a12e53Recorded2 Sep 2026Repositorysigil-system

Gate: declare the intended divergence, and control the classifier

Message

The migration intends ONE decision change: the old engine granted the empty command, because exec:allowlist:git, split to ("git" "") and the "" entry became a permission. Nothing could match it (argv[0] is never ""), so it was unreachable authority and the migration drops it.

Byte-identity would have forced either a failing gate or a corpus with the interesting case quietly removed, and the second is how a differential comes to agree for the wrong reason. So the divergence is declared BY SHAPE, checked FOR DIRECTION, and counted: every difference must be baseline #t becoming migrated #f. A widening of the same shape still reds.

THE CLASSIFIER IS NOW THE INSTRUMENT, not diff, so it gets its own control. A classifier whose patterns were too broad would wave everything through and the gate would report agreement over a transcript full of changes. The gate now plants an UNDECLARED divergence and requires it to be rejected, alongside the existing control that diff can see a difference at all. When a check is repaired by adding a step, the new step is an instrument too.

Also fixes a bug in this gate's own shell: grep -c PRINTS the count and EXITS 1 when the count is zero, so || echo 0 appended a second zero and the integer test received "0n0". It reported a widening that did not exist. || true keeps the printed count and swallows only the status.

And the landing status now asks the question that actually matters: not "has (sigil grant) shipped" but "does THIS manifest resolve it". Those came apart for most of this branch's life.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DYn8wxSdjveEgK9NRxFcNA

Changed
 scripts/gate-system-grant-migration | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
 1 file changed, 67 insertions(+), 8 deletions(-)
Diff
scripts/gate-system-grant-migrationmodified
@@ -47,8 +47,8 @@ dump_counts() {
47
if [ -s "$SCRATCH/migrated.txt" ] || [ -s "$SCRATCH/baseline.txt" ]; then
48
say ""
49
say "--- instrument state at exit ---"
−50
say " baseline decision lines: $(grep -c '^CHK|' "$SCRATCH/baseline.txt" 2>/dev/null || echo 0)"
−51
say " migrated decision lines: $(grep -c '^CHK|' "$SCRATCH/migrated.txt" 2>/dev/null || echo 0)"
+50
say " baseline decision lines: $(grep -c '^CHK|' "$SCRATCH/baseline.txt" 2>/dev/null || true)"
+51
say " migrated decision lines: $(grep -c '^CHK|' "$SCRATCH/migrated.txt" 2>/dev/null || true)"
52
say "--- end ---"
53
fi
54
}
@@ -186,14 +186,49 @@ say " decisions compared: migrated $MIG_N / baseline $BASE_N"
186
[ "$BASE_N" -gt 0 ] || red "the baseline probe produced ZERO decisions; the differential compared nothing"
187
[ "$MIG_N" -eq "$BASE_N" ] || red "the two probes produced different numbers of decisions ($MIG_N vs $BASE_N)"
188
−189
if ! diff -u "$SCRATCH/baseline.txt" "$SCRATCH/migrated.txt" > "$SCRATCH/diff.txt" 2>&1; then
−190
say "--- divergence (baseline vs migrated) ---"
+189
# A migration may INTEND to change a decision. Byte-identity would then
+190
# force either a failing gate or a corpus with the interesting case
+191
# quietly removed, and the second is how a differential comes to agree
+192
# for the wrong reason. So intended divergences are declared BY SHAPE,
+193
# checked FOR DIRECTION, and counted.
+194
#
+195
# The one declared here: the old engine granted the EMPTY command.
+196
# `exec:allowlist:git,` split to ("git" "") and the "" entry became a
+197
# permission. Nothing could ever match it (argv[0] is never ""), so it
+198
# was unreachable authority, and the migration drops it. Every such
+199
# difference must be a NARROWING, baseline #t becoming migrated #f.
+200
# A widening of the same shape is a defect and still reds.
+201
diff -u "$SCRATCH/baseline.txt" "$SCRATCH/migrated.txt" > "$SCRATCH/diff.txt" 2>&1
+202
grep -E '^[-+]CHK\|' "$SCRATCH/diff.txt" > "$SCRATCH/diffchk.txt" 2>/dev/null || true
+203
+204
# Anything differing that is NOT the declared shape.
+205
UNEXPECTED_DIFF="$(grep -E '^[-+]CHK\|' "$SCRATCH/diff.txt" 2>/dev/null \
+206
| grep -vE '^-CHK\|[^|]*\|exec\|""\|#t$' \
+207
| grep -vE '^\+CHK\|[^|]*\|exec\|""\|#f$' || true)"
+208
if [ -n "$UNEXPECTED_DIFF" ]; then
+209
say "--- UNDECLARED divergence (baseline vs migrated) ---"
+210
echo "$UNEXPECTED_DIFF" | head -40
+211
say "--- full diff head ---"
212
head -40 "$SCRATCH/diff.txt"
−192
red "the migrated engine DISAGREES with the pre-migration engine"
+213
red "the migrated engine DISAGREES with the pre-migration engine outside the declared narrowing"
214
fi
−194
say " transcripts identical: $MIG_N decisions agree, 0 disagree"
215
−196
# A differential that agrees is meaningful only if it COULD disagree.
+216
NARROWED="$(grep -cE '^\+CHK\|[^|]*\|exec\|""\|#f$' "$SCRATCH/diff.txt" 2>/dev/null || true)"
+217
NARROWED="${NARROWED:-0}"
+218
WIDENED="$(grep -cE '^\+CHK\|[^|]*\|exec\|""\|#t$' "$SCRATCH/diff.txt" 2>/dev/null || true)"
+219
WIDENED="${WIDENED:-0}"
+220
[ "$WIDENED" -eq 0 ] || red "a declared-shape divergence WIDENED authority ($WIDENED lines). The allowance covers narrowing only."
+221
AGREED=$((MIG_N - NARROWED))
+222
say " decisions agreeing: $AGREED of $MIG_N"
+223
say " declared narrowings (exec \"\" was granted, now denied): $NARROWED, widenings: 0"
+224
+225
# A differential that agrees is meaningful only if it COULD disagree, and
+226
# the thing that decides now is the CLASSIFIER above, not `diff`. When a
+227
# check is repaired by adding a step, the new step is an instrument too
+228
# and arrives with the same defect rate and none of the scrutiny. So both
+229
# get a control.
+230
#
+231
# Control 1: `diff` itself can see a difference at all.
232
CTL="$SCRATCH/control.txt"
233
sed 's/^CHK|pty|/CHK|PLANTED|/' "$SCRATCH/migrated.txt" > "$CTL"
234
if diff -q "$SCRATCH/baseline.txt" "$CTL" >/dev/null 2>&1; then
@@ -201,6 +236,22 @@ if diff -q "$SCRATCH/baseline.txt" "$CTL" >/dev/null 2>&1; then
236
fi
237
say " comparison positive control: an altered transcript IS detected"
238
+239
# Control 2, and this is the one that matters now: an UNDECLARED
+240
# divergence must be REJECTED by the classifier. Without this, a
+241
# classifier whose patterns were too broad would wave everything through
+242
# and the gate would report agreement over a transcript full of changes.
+243
CTL2="$SCRATCH/control-undeclared.txt"
+244
sed '0,/^CHK|[^|]*|fs-read|/s//CHK|PLANTED-UNDECLARED|fs-read|/' "$SCRATCH/migrated.txt" > "$CTL2"
+245
if [ "$(diff -u "$SCRATCH/baseline.txt" "$CTL2" 2>/dev/null | grep -cE '^[-+]CHK\|')" -eq 0 ]; then
+246
setup_failed "the undeclared-divergence control did not land: the planted line produced no diff"
+247
fi
+248
CTL2_LEAK="$(diff -u "$SCRATCH/baseline.txt" "$CTL2" 2>/dev/null | grep -E '^[-+]CHK\|' \
+249
| grep -vE '^-CHK\|[^|]*\|exec\|""\|#t$' \
+250
| grep -vE '^\+CHK\|[^|]*\|exec\|""\|#f$' || true)"
+251
[ -n "$CTL2_LEAK" ] || red \
+252
"the classifier is TOO BROAD: a deliberately planted UNDECLARED divergence was accepted as declared. Every difference would pass."
+253
say " classifier positive control: an undeclared divergence IS rejected"
+254
255
say "STEP 4/5 existing test-system suite"
256
timeout "$TIMEOUT_S" "$SIGIL" test --redirects "$REDIRECTS" > "$SCRATCH/suite.log" 2>&1
257
SRC=$?
@@ -292,7 +343,15 @@ if [ -d "$MONO/.git" ]; then
343
TAGS="$(git -C "$MONO" tag --contains "$GRANT_COMMIT" 2>/dev/null | head -3)"
344
if [ -n "$TAGS" ]; then
345
say " (sigil grant) is RELEASED in: $(echo "$TAGS" | tr '\n' ' ')"
−295
say " ACTION: set the floor to the release above, then this branch is landable."
+346
# Does the declared floor actually name one of those releases? The
+347
# question is not "has it shipped" but "does THIS manifest resolve
+348
# it", and those came apart for most of this branch's life.
+349
if echo "$TAGS" | sed 's/^v//' | grep -qxF "$FLOOR"; then
+350
say " => LANDABLE. The declared floor names a release containing (sigil grant)."
+351
else
+352
say " => NOT LANDABLE YET. The floor \"$FLOOR\" does not name a release that"
+353
say " contains (sigil grant). Set it to one of the releases above."
+354
fi
355
else
356
say " (sigil grant) landed in monorepo $(git -C "$MONO" rev-parse --short "$GRANT_COMMIT") and is in NO TAG."
357
say " => NOT LANDABLE YET. Consumers resolve caret ranges against TAGS, so"