@junghanacs/entwurf 0.12.8 → 0.12.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +4 -4
- package/CHANGELOG.md +20 -1
- package/DELIVERY.md +1 -1
- package/README.md +122 -10
- package/demo/README.md +2 -2
- package/docs/setup-clean-host.md +14 -3
- package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +9 -15
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/models.js +12 -12
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-fact-provider.js +1 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-production.js +5 -2
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-spawn-production.js +8 -2
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-sender-identity.js +15 -4
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js +483 -34
- package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-hook.js +8 -3
- package/mcp/entwurf-bridge/dist/scripts/agy-imprint.js +14 -2
- package/mcp/entwurf-bridge/dist/scripts/meta-bridge-fresh-cut.js +155 -29
- package/mcp/entwurf-bridge/src/index.ts +10 -12
- package/package.json +7 -7
- package/pi-extensions/entwurf-control.ts +12 -12
- package/pi-extensions/lib/acp/models.ts +12 -12
- package/pi-extensions/lib/entwurf-fact-provider.ts +9 -2
- package/pi-extensions/lib/entwurf-v2-production.ts +8 -2
- package/pi-extensions/lib/entwurf-v2-spawn-production.ts +8 -2
- package/pi-extensions/lib/meta-sender-identity.ts +15 -5
- package/pi-extensions/lib/meta-session.ts +526 -38
- package/pi-extensions/meta-bridge-hook.ts +8 -2
- package/run.sh +30 -26
- package/scripts/agy-imprint.ts +15 -1
- package/scripts/check-acp-session-reuse.ts +1 -1
- package/scripts/check-acp-session-store.ts +3 -3
- package/scripts/check-agy-sender-identity.ts +83 -0
- package/scripts/check-entwurf-control-rpc.ts +2 -2
- package/scripts/check-entwurf-fact-provider.ts +9 -1
- package/scripts/check-entwurf-facts.ts +1 -1
- package/scripts/check-entwurf-mailbox-guard.ts +6 -2
- package/scripts/check-entwurf-resume-args.ts +6 -6
- package/scripts/check-entwurf-session-identity.ts +7 -6
- package/scripts/check-entwurf-v2-production.ts +4 -2
- package/scripts/check-entwurf-v2-spawn-production.ts +2 -2
- package/scripts/check-fresh-cut-gate.sh +305 -4
- package/scripts/check-meta-identity-consumers.ts +501 -1
- package/scripts/check-meta-listing.ts +91 -9
- package/scripts/check-meta-receiver-marker.ts +54 -0
- package/scripts/check-model-lock.ts +1 -1
- package/scripts/meta-bridge-fresh-cut.ts +164 -28
- package/scripts/pi_settings_io.py +65 -0
- package/scripts/register-pi-package.py +183 -37
- package/scripts/register-pi-provider.py +68 -10
- package/scripts/smoke-acp-socket-citizen-live.ts +2 -2
- package/scripts/smoke-meta-install-state.sh +1 -1
- package/scripts/smoke-pi-attach.ts +7 -2
- package/scripts/smoke-user-scope-citizen.sh +177 -0
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
# naming the explicit fresh-cut verb in both invocation forms;
|
|
10
10
|
# 3. fresh-cut REQUIRES quiescence (live OR unprovable both refuse) and archives
|
|
11
11
|
# the whole generation through one preflighted plan, opening an empty one;
|
|
12
|
-
# 4. the archive is forensic only — no runtime reads it, no restore verb
|
|
12
|
+
# 4. the archive is forensic only — no runtime reads it, no restore verb;
|
|
13
|
+
# 5. exit status reports what already moved: complete / no-move / usage /
|
|
14
|
+
# incomplete cut transition / complete-with-cleanup-residue.
|
|
13
15
|
#
|
|
14
16
|
# A–D drive the activation path. E pins the doctor and the writers to ONE
|
|
15
17
|
# certification verdict on the three defects that PARSE (drift / duplicate /
|
|
@@ -20,6 +22,7 @@
|
|
|
20
22
|
# from the record through the adapter probe the dispatch itself uses. I holds the
|
|
21
23
|
# other half of the prescription: a store that cannot be READ is not a store with
|
|
22
24
|
# defects, and the refusal must not send the operator at a cut that cannot help.
|
|
25
|
+
# J executes the #54 exit contract, including the cut-complete cleanup-failure state.
|
|
23
26
|
#
|
|
24
27
|
# There is no frozen-fixture apparatus here: fresh-cut never rewrites a byte
|
|
25
28
|
# (the archive is a rename), so the only byte claim is `archived == seeded`,
|
|
@@ -620,6 +623,165 @@ else
|
|
|
620
623
|
bad "F8 the cut refused (or kept) a marker whose owner pid is provably absent" "$cut_out"
|
|
621
624
|
fi
|
|
622
625
|
|
|
626
|
+
# THE RESIDUE ROW (#53 A, measured on a second Linux host 2026-07-25). A marker
|
|
627
|
+
# naming pid 1 is the one shape that is neither live, uncertain nor dead: init runs
|
|
628
|
+
# for the whole boot and its start-key does not change while it does, so
|
|
629
|
+
# classifyMarkerOwner answers `live` and THE ACTION THE REFUSAL PRESCRIBES cannot
|
|
630
|
+
# change that — the operator quiesces every session, as told, and the cut refuses
|
|
631
|
+
# again. Yet 0.12.8 refuses install on a pre-v3 store and names THIS cut as the only
|
|
632
|
+
# repair, so that host was stuck until the file was removed by hand. The pure
|
|
633
|
+
# classifier is right; the marker's claim is refuted by construction (no writer in
|
|
634
|
+
# this tree can mint one any more — see the isPlausibleOwnerPid cells in
|
|
635
|
+
# check-meta-receiver-marker / check-agy-sender-identity), so it is clearable
|
|
636
|
+
# residue. The recorded key must be THIS host's real key for pid 1, or the marker
|
|
637
|
+
# would classify `dead` on a key mismatch and the cell would pass while the bug
|
|
638
|
+
# lived.
|
|
639
|
+
init_key="$(node --experimental-strip-types -e '
|
|
640
|
+
import("'"$REPO"'/pi-extensions/lib/meta-session.ts").then((m) => process.stdout.write(m.processStartKey(1)));
|
|
641
|
+
')"
|
|
642
|
+
if [ -z "$init_key" ]; then
|
|
643
|
+
bad "F19 SETUP MISS: could not read this host's start key for pid 1 — the residue cells cannot run"
|
|
644
|
+
else
|
|
645
|
+
for surface in senders receivers; do
|
|
646
|
+
reset_world prevgen
|
|
647
|
+
case "$surface" in
|
|
648
|
+
senders)
|
|
649
|
+
mkdir -p "$PI_CODING_AGENT_DIR/meta-senders/claude-code"
|
|
650
|
+
ghost="$PI_CODING_AGENT_DIR/meta-senders/claude-code/1.json"
|
|
651
|
+
cat > "$ghost" <<JSON
|
|
652
|
+
{
|
|
653
|
+
"backend": "claude-code",
|
|
654
|
+
"gardenId": "20260305T000000-dddd05",
|
|
655
|
+
"nativeSessionId": "prevgen-native-1",
|
|
656
|
+
"cwd": "/tmp/prevgen",
|
|
657
|
+
"ownerPid": 1,
|
|
658
|
+
"ownerStartKey": "$init_key",
|
|
659
|
+
"updatedAt": "2026-06-10T16:03:10.000Z"
|
|
660
|
+
}
|
|
661
|
+
JSON
|
|
662
|
+
;;
|
|
663
|
+
receivers)
|
|
664
|
+
mkdir -p "$PI_CODING_AGENT_DIR/meta-receivers"
|
|
665
|
+
ghost="$PI_CODING_AGENT_DIR/meta-receivers/20260305T000000-dddd05.json"
|
|
666
|
+
cat > "$ghost" <<JSON
|
|
667
|
+
{
|
|
668
|
+
"gardenId": "20260305T000000-dddd05",
|
|
669
|
+
"backend": "claude-code",
|
|
670
|
+
"nativeSessionId": "prevgen-native-1",
|
|
671
|
+
"ownerPid": 1,
|
|
672
|
+
"ownerStartKey": "$init_key",
|
|
673
|
+
"ownerKind": "claude-code-cli",
|
|
674
|
+
"armProvenance": "session-start",
|
|
675
|
+
"updatedAt": "2026-06-10T16:03:10.000Z"
|
|
676
|
+
}
|
|
677
|
+
JSON
|
|
678
|
+
;;
|
|
679
|
+
esac
|
|
680
|
+
set +e
|
|
681
|
+
cut_out="$(node --experimental-strip-types "${FRESH_CUT[@]}" 2>&1)"; rcr=$?
|
|
682
|
+
set -e
|
|
683
|
+
if [ "$rcr" = 0 ]; then
|
|
684
|
+
ok "F19 an init-owned $surface marker does NOT block the cut (refuted, not live)"
|
|
685
|
+
else
|
|
686
|
+
bad "F19 an init-owned $surface marker still deadlocks the cut — the upgrade path is closed" "$cut_out"
|
|
687
|
+
fi
|
|
688
|
+
if [ ! -e "$ghost" ]; then
|
|
689
|
+
ok "F20 the refuted $surface marker was cleared as residue"
|
|
690
|
+
else
|
|
691
|
+
bad "F20 the refuted $surface marker survived the cut"
|
|
692
|
+
fi
|
|
693
|
+
case "$cut_out" in
|
|
694
|
+
*refuted:*) ok "F21 the cut reports it as REFUTED, distinct from a proven-dead owner ($surface)" ;;
|
|
695
|
+
*) bad "F21 the cut disguised a proof of invalidity as a proof of death ($surface)" "$cut_out" ;;
|
|
696
|
+
esac
|
|
697
|
+
done
|
|
698
|
+
# The complement, and the reason F19 is a repair rather than a gate bypass: a marker
|
|
699
|
+
# whose owner is genuinely live still refuses. C1 holds that for a real live pid; this
|
|
700
|
+
# row proves the residue rule did not widen into "any marker the cut dislikes".
|
|
701
|
+
if [ -z "$start_key" ]; then
|
|
702
|
+
bad "F22 SETUP MISS: no start key for this gate's own pid — the live-complement cell cannot run"
|
|
703
|
+
else
|
|
704
|
+
reset_world prevgen
|
|
705
|
+
mkdir -p "$PI_CODING_AGENT_DIR/meta-senders/claude-code"
|
|
706
|
+
cat > "$PI_CODING_AGENT_DIR/meta-senders/claude-code/$$.json" <<JSON
|
|
707
|
+
{
|
|
708
|
+
"backend": "claude-code",
|
|
709
|
+
"gardenId": "20260305T000000-dddd05",
|
|
710
|
+
"ownerPid": $$,
|
|
711
|
+
"ownerStartKey": "$start_key",
|
|
712
|
+
"updatedAt": "2026-03-05T00:00:00.000Z"
|
|
713
|
+
}
|
|
714
|
+
JSON
|
|
715
|
+
store_before="$(store_bytes)"
|
|
716
|
+
set +e
|
|
717
|
+
cut_out="$(node --experimental-strip-types "${FRESH_CUT[@]}" 2>&1)"; rcl=$?
|
|
718
|
+
set -e
|
|
719
|
+
if [ "$rcl" != 0 ] && [ "$store_before" = "$(store_bytes)" ]; then
|
|
720
|
+
ok "F22 a LIVE owner still blocks the cut (the residue rule did not widen)"
|
|
721
|
+
else
|
|
722
|
+
bad "F22 the residue rule widened into a live owner — a running citizen would lose its address" "$cut_out"
|
|
723
|
+
fi
|
|
724
|
+
case "$cut_out" in
|
|
725
|
+
*"LIVE sender marker"*) ok "F22b the live owner is still reported LIVE, never refuted away" ;;
|
|
726
|
+
*) bad "F22b a live owner stopped being reported as live" "$cut_out" ;;
|
|
727
|
+
esac
|
|
728
|
+
fi
|
|
729
|
+
fi
|
|
730
|
+
|
|
731
|
+
# POST-CUT CLEANUP IS REPORTED, NOT LAUNDERED. The sweep runs AFTER the archive has
|
|
732
|
+
# moved, so a failure there must not throw away the story of what DID happen — and it
|
|
733
|
+
# must not be swallowed either, because this command's own output claims the residue
|
|
734
|
+
# was `cleared`. A bare `catch {}` around the unlink read EACCES/EROFS/an immutable
|
|
735
|
+
# attribute as "raced away" (cross-review 2026-07-25, on the very loop #53 A added).
|
|
736
|
+
# ENOENT alone is already-gone; everything else is named and turns the exit nonzero
|
|
737
|
+
# while the success lines stay.
|
|
738
|
+
if [ -z "$init_key" ]; then
|
|
739
|
+
bad "F23 SETUP MISS: no start key for pid 1 — the cleanup-failure cell cannot seed its marker"
|
|
740
|
+
elif [ "$(id -u)" = 0 ]; then
|
|
741
|
+
ok "F23 vacuous under uid 0: permission bits cannot make a directory unwritable for root, so the class this row drives cannot occur here"
|
|
742
|
+
else
|
|
743
|
+
reset_world prevgen
|
|
744
|
+
mkdir -p "$PI_CODING_AGENT_DIR/meta-senders/claude-code"
|
|
745
|
+
stuck_dir="$PI_CODING_AGENT_DIR/meta-senders/claude-code"
|
|
746
|
+
stuck="$stuck_dir/1.json"
|
|
747
|
+
cat > "$stuck" <<JSON
|
|
748
|
+
{
|
|
749
|
+
"backend": "claude-code",
|
|
750
|
+
"gardenId": "20260305T000000-dddd05",
|
|
751
|
+
"ownerPid": 1,
|
|
752
|
+
"ownerStartKey": "$init_key",
|
|
753
|
+
"updatedAt": "2026-06-10T16:03:10.000Z"
|
|
754
|
+
}
|
|
755
|
+
JSON
|
|
756
|
+
# r-x: the walk can still readdir + lstat + read the marker, but the unlink needs
|
|
757
|
+
# WRITE on the parent and fails EACCES. Restored right after, or the sandbox trap
|
|
758
|
+
# (and reset_world) could not clean up.
|
|
759
|
+
chmod a-w "$stuck_dir"
|
|
760
|
+
set +e
|
|
761
|
+
cut_out="$(node --experimental-strip-types "${FRESH_CUT[@]}" 2>&1)"; rcc=$?
|
|
762
|
+
set -e
|
|
763
|
+
chmod u+w "$stuck_dir"
|
|
764
|
+
if [ "$rcc" != 0 ]; then
|
|
765
|
+
ok "F23 an unremovable marker makes the cut exit NONZERO (a partial sweep never reads as a clean one)"
|
|
766
|
+
else
|
|
767
|
+
bad "F23 the cut reported success while a file it claims to have cleared is still there" "$cut_out"
|
|
768
|
+
fi
|
|
769
|
+
case "$cut_out" in
|
|
770
|
+
*"FAIL post-cut cleanup"*) ok "F23b the failure is NAMED as post-cut cleanup, with the file and the errno" ;;
|
|
771
|
+
*) bad "F23b the unlink failure was laundered into 'raced away'" "$cut_out" ;;
|
|
772
|
+
esac
|
|
773
|
+
if [ -n "$(find "$SANDBOX" -maxdepth 1 -type d -name 'store.archive-*')" ]; then
|
|
774
|
+
ok "F23c the archive still happened and is still reported — this is NOT a half-cut"
|
|
775
|
+
else
|
|
776
|
+
bad "F23c a cleanup failure threw away the generation move" "$cut_out"
|
|
777
|
+
fi
|
|
778
|
+
case "$cut_out" in
|
|
779
|
+
*"not a half-cut"*) ok "F23d the refusal tells the operator the store is safe and what to re-run" ;;
|
|
780
|
+
*) bad "F23d the failure left the operator unable to tell a cleanup miss from a half-cut" "$cut_out" ;;
|
|
781
|
+
esac
|
|
782
|
+
rm -f "$stuck"
|
|
783
|
+
fi
|
|
784
|
+
|
|
623
785
|
# WIRING: the cut must ASK for the verdict, never compute it from a start-key. An
|
|
624
786
|
# inline `processStartKey(...) === recorded` is precisely how "" (UNKNOWN) was read
|
|
625
787
|
# as "dead" before this review — a fail-open path that must not come back.
|
|
@@ -825,10 +987,10 @@ done
|
|
|
825
987
|
set +e
|
|
826
988
|
out="$(node --experimental-strip-types "${FRESH_CUT[@]}" 2>&1)"; rcg=$?
|
|
827
989
|
set -e
|
|
828
|
-
if [ "$rcg"
|
|
829
|
-
ok "G1 an occupied archive destination REFUSES the cut"
|
|
990
|
+
if [ "$rcg" = 1 ]; then
|
|
991
|
+
ok "G1 an occupied archive destination REFUSES the cut with the NO-MOVE status (exit 1)"
|
|
830
992
|
else
|
|
831
|
-
bad "G1 the cut proceeded into an occupied destination" "$out"
|
|
993
|
+
bad "G1 the cut proceeded into an occupied destination, or refused with the wrong status (rc=$rcg, want 1)" "$out"
|
|
832
994
|
fi
|
|
833
995
|
if [ -z "$(find "$SANDBOX" -maxdepth 1 -type d -name 'store.archive-*')" ] && [ "$store_before" = "$(store_bytes)" ]; then
|
|
834
996
|
ok "G2 the collision refusal moved NOTHING — the store never left its place (no half-cut generation)"
|
|
@@ -1187,6 +1349,145 @@ case "$out" in
|
|
|
1187
1349
|
esac
|
|
1188
1350
|
rm -rf "$SANDBOX/nm"
|
|
1189
1351
|
|
|
1352
|
+
# ── J. the EXIT CONTRACT (#54): the status says WHAT ALREADY MOVED ───────────
|
|
1353
|
+
# Three world-states used to share one `1`, so the documented `fresh-cut && setup`
|
|
1354
|
+
# chain stopped identically on a refusal that changed nothing and on a cut that had
|
|
1355
|
+
# already unblocked the install. These cells pin each reachable code to the state it
|
|
1356
|
+
# names — the point is not that failures are nonzero, it is that a caller can BRANCH.
|
|
1357
|
+
echo "[check-fresh-cut-gate] J. exit contract"
|
|
1358
|
+
|
|
1359
|
+
# J1/J2 — the two states that need no host at all.
|
|
1360
|
+
set +e
|
|
1361
|
+
help_out="$(node --experimental-strip-types "${FRESH_CUT[@]}" --help 2>&1)"; rc=$?
|
|
1362
|
+
set -e
|
|
1363
|
+
if [ "$rc" = 0 ]; then ok "J1 --help exits 0"; else bad "J1 --help exited $rc" "$help_out"; fi
|
|
1364
|
+
case "$help_out" in
|
|
1365
|
+
*"EXIT CONTRACT"*) ok "J1b --help PRINTS the contract (an operator can read it where they hit it)" ;;
|
|
1366
|
+
*) bad "J1b --help never printed the exit contract" "$help_out" ;;
|
|
1367
|
+
esac
|
|
1368
|
+
set +e
|
|
1369
|
+
bad_out="$(node --experimental-strip-types "${FRESH_CUT[@]}" --wat 2>&1)"; rc=$?
|
|
1370
|
+
set -e
|
|
1371
|
+
if [ "$rc" = 2 ]; then ok "J2 an unknown argument is USAGE (exit 2), never a store verdict"; else bad "J2 unknown arg exited $rc, want 2" "$bad_out"; fi
|
|
1372
|
+
|
|
1373
|
+
# J3 — the success state, distinct from every failure below.
|
|
1374
|
+
reset_world prevgen
|
|
1375
|
+
set +e
|
|
1376
|
+
out="$(node --experimental-strip-types "${FRESH_CUT[@]}" 2>&1)"; rc=$?
|
|
1377
|
+
set -e
|
|
1378
|
+
if [ "$rc" = 0 ]; then ok "J3 a complete cut exits 0"; else bad "J3 a complete cut exited $rc" "$out"; fi
|
|
1379
|
+
|
|
1380
|
+
# J4 — quiesce refusal is NO-MOVE (1), the same code as the collision refusal in G1,
|
|
1381
|
+
# because they are the same world-state: the host is exactly what it was.
|
|
1382
|
+
reset_world prevgen
|
|
1383
|
+
start_key="$(node --experimental-strip-types -e '
|
|
1384
|
+
import("'"$REPO"'/pi-extensions/lib/meta-session.ts").then((m) => process.stdout.write(m.processStartKey(Number(process.argv[1]))));
|
|
1385
|
+
' "$$")"
|
|
1386
|
+
if [ -z "$start_key" ]; then
|
|
1387
|
+
bad "J4 could not compute a start key for the gate's own pid"
|
|
1388
|
+
else
|
|
1389
|
+
mkdir -p "$PI_CODING_AGENT_DIR/meta-senders/claude-code"
|
|
1390
|
+
cat > "$PI_CODING_AGENT_DIR/meta-senders/claude-code/$$.json" <<JSON
|
|
1391
|
+
{
|
|
1392
|
+
"backend": "claude-code",
|
|
1393
|
+
"gardenId": "20260305T000000-dddd05",
|
|
1394
|
+
"nativeSessionId": "prevgen-native-1",
|
|
1395
|
+
"cwd": "/tmp/prevgen",
|
|
1396
|
+
"ownerPid": $$,
|
|
1397
|
+
"ownerStartKey": "$start_key",
|
|
1398
|
+
"updatedAt": "2026-03-05T00:00:00.000Z"
|
|
1399
|
+
}
|
|
1400
|
+
JSON
|
|
1401
|
+
set +e
|
|
1402
|
+
out="$(node --experimental-strip-types "${FRESH_CUT[@]}" 2>&1)"; rc=$?
|
|
1403
|
+
set -e
|
|
1404
|
+
if [ "$rc" = 1 ]; then ok "J4 a quiesce refusal is NO-MOVE (exit 1) — same code as any other untouched host"; else bad "J4 quiesce refusal exited $rc, want 1" "$out"; fi
|
|
1405
|
+
rm -f "$PI_CODING_AGENT_DIR/meta-senders/claude-code/$$.json"
|
|
1406
|
+
fi
|
|
1407
|
+
|
|
1408
|
+
# J5 — THE state #54 was opened for: the cut IS done and the cleanup is not. Staged by
|
|
1409
|
+
# taking write permission off the socket surface AFTER a dead socket is parked in it:
|
|
1410
|
+
# readdir/lstat/probe all still work (r-x), the unlink does not. Root bypasses DAC, so
|
|
1411
|
+
# it cannot prove anything here and says so instead of passing vacuously.
|
|
1412
|
+
if [ "$(id -u)" = 0 ]; then
|
|
1413
|
+
echo " skip J5 post-cut cleanup failure needs a non-root uid (DAC is what stages it)"
|
|
1414
|
+
else
|
|
1415
|
+
reset_world prevgen
|
|
1416
|
+
mkdir -p "$ENTWURF_DIR"
|
|
1417
|
+
node -e '
|
|
1418
|
+
const net = require("node:net");
|
|
1419
|
+
net.createServer().listen(process.argv[1], () => process.exit(0));
|
|
1420
|
+
' "$ENTWURF_DIR/20260305T000000-dddd05.sock"
|
|
1421
|
+
if [ ! -S "$ENTWURF_DIR/20260305T000000-dddd05.sock" ]; then
|
|
1422
|
+
bad "J5 could not stage a dead socket — the cleanup-failure cells cannot run"
|
|
1423
|
+
else
|
|
1424
|
+
chmod 0500 "$ENTWURF_DIR"
|
|
1425
|
+
set +e
|
|
1426
|
+
out="$(node --experimental-strip-types "${FRESH_CUT[@]}" 2>&1)"; rc=$?
|
|
1427
|
+
set -e
|
|
1428
|
+
chmod 0700 "$ENTWURF_DIR"
|
|
1429
|
+
if [ "$rc" = 4 ]; then
|
|
1430
|
+
ok "J5 a cut whose post-cut cleanup failed exits 4 — NOT the 1 that means nothing moved"
|
|
1431
|
+
else
|
|
1432
|
+
bad "J5 post-cut cleanup failure exited $rc, want 4" "$out"
|
|
1433
|
+
fi
|
|
1434
|
+
if [ -n "$(find "$SANDBOX" -maxdepth 1 -type d -name 'store.archive-*')" ]; then
|
|
1435
|
+
ok "J5b …and the generation really WAS archived (the 4 is a claim about a cut that happened)"
|
|
1436
|
+
else
|
|
1437
|
+
bad "J5b exit 4 was reported although nothing was archived" "$out"
|
|
1438
|
+
fi
|
|
1439
|
+
if [ -d "$ENTWURF_META_SESSIONS_DIR" ] && [ -z "$(find "$ENTWURF_META_SESSIONS_DIR" -name '*.meta.json' 2>/dev/null)" ]; then
|
|
1440
|
+
ok "J5c …and the fresh generation is open and empty"
|
|
1441
|
+
else
|
|
1442
|
+
bad "J5c the fresh generation was not opened" "$out"
|
|
1443
|
+
fi
|
|
1444
|
+
case "$out" in
|
|
1445
|
+
*"FAIL post-cut cleanup"*) ok "J5d the report still names every file it could not remove" ;;
|
|
1446
|
+
*) bad "J5d the cleanup failure was silent" "$out" ;;
|
|
1447
|
+
esac
|
|
1448
|
+
# The whole point of distinguishing this state: the operator may go on.
|
|
1449
|
+
set +e
|
|
1450
|
+
out2="$("$REPO/run.sh" install "$PROJ" 2>&1)"; rc2=$?
|
|
1451
|
+
set -e
|
|
1452
|
+
if [ "$rc2" = 0 ]; then
|
|
1453
|
+
ok "J5e install PASSES after an exit-4 cut — the store no longer blocks it, which is what 4 means"
|
|
1454
|
+
else
|
|
1455
|
+
bad "J5e install still refused after a completed cut (then 4 is describing the wrong state)" "$out2"
|
|
1456
|
+
fi
|
|
1457
|
+
rm -f "$ENTWURF_DIR/20260305T000000-dddd05.sock"
|
|
1458
|
+
fi
|
|
1459
|
+
fi
|
|
1460
|
+
|
|
1461
|
+
# J6 — the INCOMPLETE-TRANSITION verdict, EXECUTED. A real post-move failure needs a
|
|
1462
|
+
# rename/open step to fail after another move succeeded, and every unprivileged way to stage that is closed by the plan
|
|
1463
|
+
# preflight (collision, unreadable surface, non-writable parent) — the remaining causes
|
|
1464
|
+
# (immutable attr, read-only mount, LSM) all need privileges this gate must not take. So
|
|
1465
|
+
# the branch calls one exported decision function and the gate drives THAT, rather than
|
|
1466
|
+
# asserting the state by grep and calling it proven.
|
|
1467
|
+
half="$(node --experimental-strip-types -e '
|
|
1468
|
+
import("'"$REPO"'/pi-extensions/lib/meta-session.ts").then((m) => {
|
|
1469
|
+
process.stdout.write([m.midCutExit(0), m.midCutExit(1), m.midCutExit(2), m.FRESH_CUT_EXIT.HALF_CUT].join(","));
|
|
1470
|
+
});
|
|
1471
|
+
')"
|
|
1472
|
+
if [ "$half" = "1,3,3,3" ]; then
|
|
1473
|
+
ok "J6 midCutExit: a first-rename failure is NO-MOVE (1), any post-move failure is CUT-TRANSITION-INCOMPLETE (3)"
|
|
1474
|
+
else
|
|
1475
|
+
bad "J6 midCutExit verdicts drifted (got '$half', want '1,3,3,3')"
|
|
1476
|
+
fi
|
|
1477
|
+
# …and the branch really routes through it, so the executed verdict is the shipped one.
|
|
1478
|
+
if grep -q 'return midCutExit(archived.length);' "$REPO/scripts/meta-bridge-fresh-cut.ts"; then
|
|
1479
|
+
ok "J6b the mid-cut branch returns midCutExit(...), not a hand-written literal"
|
|
1480
|
+
else
|
|
1481
|
+
bad "J6b the mid-cut branch no longer routes through midCutExit — J6 would be vacuous"
|
|
1482
|
+
fi
|
|
1483
|
+
# The crash path is the one place a wrong answer is invisible: it reports a status for a
|
|
1484
|
+
# state nobody inspected, so it must ask the same function rather than assume a no-op.
|
|
1485
|
+
if grep -q 'process.exit(midCutExit(archivedSoFar));' "$REPO/scripts/meta-bridge-fresh-cut.ts"; then
|
|
1486
|
+
ok "J6c the top-level crash handler answers with midCutExit too (never a flat 'nothing moved')"
|
|
1487
|
+
else
|
|
1488
|
+
bad "J6c the crash handler exits a fixed code — a post-rename crash would claim nothing moved"
|
|
1489
|
+
fi
|
|
1490
|
+
|
|
1190
1491
|
echo
|
|
1191
1492
|
echo "[check-fresh-cut-gate] passed=$PASSED failed=$FAILED"
|
|
1192
1493
|
[ "$FAILED" = 0 ] || exit 1
|