@junghanacs/entwurf 0.34.2 → 0.35.0
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/CHANGELOG.md +115 -0
- package/README.md +7 -0
- package/VERIFY.md +11 -1
- package/demo/README.md +109 -175
- package/demo/demo-baseline.sh +5 -1
- package/demo/demo.sh +6 -2
- package/demo/herdr-pi-cc-20261011.provenance.json +64 -0
- package/docs/assets/entwurf-demo.gif +0 -0
- package/package.json +4 -2
- package/run.sh +37 -0
- package/scripts/check-gate-qualification.ts +2 -1
- package/scripts/check-release-gate-outcomes.ts +159 -1
- package/scripts/ci-qualify-decide.sh +114 -17
- package/scripts/mutants/demo-reproducibility.json +155 -0
- package/scripts/mutants/release-gate.json +28 -2
- package/scripts/tsconfig.json +1 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"scene": "whole-Herdr Pi to native Claude Code",
|
|
4
|
+
"take": "take-herdr-pi-cc-2026-10-11T11-57-22-297Z",
|
|
5
|
+
"startedAt": "2026-10-11T11:57:22.299Z",
|
|
6
|
+
"stage": "SOURCE checkout, not an npm consumer",
|
|
7
|
+
"gif": {
|
|
8
|
+
"path": "docs/assets/entwurf-demo.gif",
|
|
9
|
+
"sha256": "b686739b5c68fa458dd47c844711aea0f6ab5819eb3eb5d2850449922fa084c0",
|
|
10
|
+
"bytes": 1835518,
|
|
11
|
+
"seconds": 43.5,
|
|
12
|
+
"width": 1126,
|
|
13
|
+
"height": 764,
|
|
14
|
+
"frames": 340,
|
|
15
|
+
"captions": false
|
|
16
|
+
},
|
|
17
|
+
"cast": {
|
|
18
|
+
"sha256": "bbf8a409c58f8b48e88346d232a66ee41ac0c2acf8e605a3f568fd7d40857cf2",
|
|
19
|
+
"public": false
|
|
20
|
+
},
|
|
21
|
+
"runtime": {
|
|
22
|
+
"entwurf": "0.34.2",
|
|
23
|
+
"pi": "1.1.0",
|
|
24
|
+
"herdr": "0.9.3",
|
|
25
|
+
"gitHead": "9f4c9983fed1dd0ba619bc5a50f0f5abccefeef4",
|
|
26
|
+
"gitDirty": true,
|
|
27
|
+
"measuredPiModel": "openai-codex/gpt-6.1-sol",
|
|
28
|
+
"requestedClaudeAlias": "sonnet",
|
|
29
|
+
"measuredClaudeNativeModel": "claude-sonnet-5-5"
|
|
30
|
+
},
|
|
31
|
+
"auditedOriginalSourceSha256": {
|
|
32
|
+
"demo/take-herdr-pi-cc.ts": "f9cd30f1bb5b06236559a9f1283f0e030b300f6a17858d778c40d246327a6349",
|
|
33
|
+
"demo/lib/take-recorder.ts": "d47d824b90a7265c5ab6e7c4bf313b4f81b27cef836b4ddd15f971f3de386256",
|
|
34
|
+
"demo/render-take.ts": "f6e68157d7f8fb1f0e00a359efaa04aa30eeca1112ce2571661f39aea826b55d",
|
|
35
|
+
"demo/lib/herdr-take.ts": "57089d883323b2582aadd7f54f63cfce541f3a2c584bb6940313fbcdc940d83e"
|
|
36
|
+
},
|
|
37
|
+
"render": {
|
|
38
|
+
"aggArgs": [
|
|
39
|
+
"--idle-time-limit",
|
|
40
|
+
"2",
|
|
41
|
+
"--speed",
|
|
42
|
+
"1",
|
|
43
|
+
"--last-frame-duration",
|
|
44
|
+
"8",
|
|
45
|
+
"--font-size",
|
|
46
|
+
"14",
|
|
47
|
+
"--theme",
|
|
48
|
+
"monokai"
|
|
49
|
+
],
|
|
50
|
+
"trimmedTailEvents": 2,
|
|
51
|
+
"trimmedWhat": "recorder teardown: herdr client exit (alt-screen exit + server shut down line)",
|
|
52
|
+
"renderedAt": "2026-10-11T11:58:32.828Z",
|
|
53
|
+
"aggVersion": null,
|
|
54
|
+
"ffprobeVersion": null,
|
|
55
|
+
"toolVersionEvidence": "not recorded for this original; current versions cannot fill historical fields"
|
|
56
|
+
},
|
|
57
|
+
"limits": [
|
|
58
|
+
"Original cast, manifests and native records remain private; this tree cannot replay that capture.",
|
|
59
|
+
"The tracked command is a new LIVE take recipe, not byte-identical reproduction.",
|
|
60
|
+
"Current maintainer source is amended; original source hashes are historical observations, not a complete import-closure seal.",
|
|
61
|
+
"Operator approved the rough original pixels; this is not privacy or public-readability certification.",
|
|
62
|
+
"No claim of 0.35.0 release, published-package acceptance or future Herdr plugin observation follows from this take."
|
|
63
|
+
]
|
|
64
|
+
}
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@junghanacs/entwurf",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.35.0",
|
|
4
4
|
"description": "Garden-citizen dispatch substrate and meta-bridge for pi, Claude Code, Copilot CLI, OMP, Antigravity, and Codex CLI; Claude and Snowflake Cortex Code ACP backends.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -40,6 +40,8 @@
|
|
|
40
40
|
"scripts/",
|
|
41
41
|
"prompts/",
|
|
42
42
|
"demo/README.md",
|
|
43
|
+
"demo/herdr-pi-cc-20261011.provenance.json",
|
|
44
|
+
"!demo/**/*.ts",
|
|
43
45
|
"demo/demo.sh",
|
|
44
46
|
"demo/demo-baseline.sh",
|
|
45
47
|
"docs/setup-clean-host.md",
|
|
@@ -186,7 +188,7 @@
|
|
|
186
188
|
"check:core": "pnpm run check:toolchain && pnpm run check:vitest && pnpm run check:contracts",
|
|
187
189
|
"check:toolchain": "pnpm lint && pnpm typecheck",
|
|
188
190
|
"check:vitest": "./run.sh check-mux-fresh-call && ./run.sh check-acp-sdk-surface && ./run.sh check-pi-queue-oracle && ./run.sh check-tests-beside-behavior",
|
|
189
|
-
"check:contracts": "./run.sh check-shell-quote && ./run.sh check-entwurf-session-identity && ./run.sh check-meta-session && ./run.sh check-meta-v3-record && ./run.sh check-mailbox-receipt-state && ./run.sh check-entwurf-capabilities && ./run.sh check-harness-admission-parity && ./run.sh check-capability-bundle-reach && ./run.sh check-meta-mailbox-state-write && ./run.sh check-meta-receiver-marker && ./run.sh check-meta-capability-source && ./run.sh check-model-lock && ./run.sh check-dep-versions && ./run.sh check-node-floor-coherence && ./run.sh check-claude-floor-coherence && ./run.sh check-copilot-statusline && ./run.sh check-pi-import-surface && ./run.sh check-env-namespace && ./run.sh check-pi-runtime-version && ./run.sh check-pi-preflight && ./run.sh check-project-trust-handler && ./run.sh check-entwurf-v2-contract && ./run.sh check-entwurf-v2-send && ./run.sh check-entwurf-v2-runner && ./run.sh check-control-send-receipt && ./run.sh check-compaction-send-guard && ./run.sh check-entwurf-resume-args && ./run.sh check-entwurf-v2-native-push && ./run.sh check-entwurf-v2-production && ./run.sh check-entwurf-v2-surface && ./run.sh check-entwurf-bridge-pi-free && ./run.sh check-resume-launch-identity && ./run.sh check-mux-placement && ./run.sh check-mux-launch && ./run.sh check-mux-resume-call && ./run.sh check-mux-parent-artifact && ./run.sh check-mux-launcher-fence && ./run.sh check-entwurf-v2-visible-resume && ./run.sh check-control-socket-path && ./run.sh check-meta-listing && ./run.sh check-entwurf-fact-provider && ./run.sh check-herdr-placement && ./run.sh check-herdr-fresh-call && ./run.sh check-herdr-supply && ./run.sh check-fresh-call-dispatch && ./run.sh check-entwurf-peers-surface && ./run.sh check-entwurf-self-address && ./run.sh check-entwurf-deliverability && ./run.sh check-native-push-adapter && ./run.sh check-native-push-register && ./run.sh check-auth-boundary && ./run.sh check-acp-overlay && ./run.sh check-acp-tool-surface && ./run.sh check-acp-event-mapper && ./run.sh check-acp-prompt-builder && ./run.sh check-acp-config && ./run.sh check-acp-session-store && ./run.sh check-acp-carrier-augment && ./run.sh check-acp-foreground-only && ./run.sh check-acp-vendor-rejection && ./run.sh check-codex-sender-identity && ./run.sh check-codex-native-push && ./run.sh check-pi-durable-beside",
|
|
191
|
+
"check:contracts": "./run.sh check-demo-reproducibility && ./run.sh check-shell-quote && ./run.sh check-entwurf-session-identity && ./run.sh check-meta-session && ./run.sh check-meta-v3-record && ./run.sh check-mailbox-receipt-state && ./run.sh check-entwurf-capabilities && ./run.sh check-harness-admission-parity && ./run.sh check-capability-bundle-reach && ./run.sh check-meta-mailbox-state-write && ./run.sh check-meta-receiver-marker && ./run.sh check-meta-capability-source && ./run.sh check-model-lock && ./run.sh check-dep-versions && ./run.sh check-node-floor-coherence && ./run.sh check-claude-floor-coherence && ./run.sh check-copilot-statusline && ./run.sh check-pi-import-surface && ./run.sh check-env-namespace && ./run.sh check-pi-runtime-version && ./run.sh check-pi-preflight && ./run.sh check-project-trust-handler && ./run.sh check-entwurf-v2-contract && ./run.sh check-entwurf-v2-send && ./run.sh check-entwurf-v2-runner && ./run.sh check-control-send-receipt && ./run.sh check-compaction-send-guard && ./run.sh check-entwurf-resume-args && ./run.sh check-entwurf-v2-native-push && ./run.sh check-entwurf-v2-production && ./run.sh check-entwurf-v2-surface && ./run.sh check-entwurf-bridge-pi-free && ./run.sh check-resume-launch-identity && ./run.sh check-mux-placement && ./run.sh check-mux-launch && ./run.sh check-mux-resume-call && ./run.sh check-mux-parent-artifact && ./run.sh check-mux-launcher-fence && ./run.sh check-entwurf-v2-visible-resume && ./run.sh check-control-socket-path && ./run.sh check-meta-listing && ./run.sh check-entwurf-fact-provider && ./run.sh check-herdr-placement && ./run.sh check-herdr-fresh-call && ./run.sh check-herdr-supply && ./run.sh check-fresh-call-dispatch && ./run.sh check-entwurf-peers-surface && ./run.sh check-entwurf-self-address && ./run.sh check-entwurf-deliverability && ./run.sh check-native-push-adapter && ./run.sh check-native-push-register && ./run.sh check-auth-boundary && ./run.sh check-acp-overlay && ./run.sh check-acp-tool-surface && ./run.sh check-acp-event-mapper && ./run.sh check-acp-prompt-builder && ./run.sh check-acp-config && ./run.sh check-acp-session-store && ./run.sh check-acp-carrier-augment && ./run.sh check-acp-foreground-only && ./run.sh check-acp-vendor-rejection && ./run.sh check-codex-sender-identity && ./run.sh check-codex-native-push && ./run.sh check-pi-durable-beside",
|
|
190
192
|
"check:hermetic": "./run.sh check-bridge-delivery && ./run.sh smoke-pi-attach && ./run.sh check-fresh-cut-gate && ./run.sh check-hook-launch-topology && ./run.sh check-meta-hook-session-switch && ./run.sh check-copilot-birth-hook && ./run.sh check-copilot-receive-arm && ./run.sh check-copilot-launch && ./run.sh check-pi-launch && ./run.sh check-codex-app-server-launch && ./run.sh check-meta-identity-consumers && ./run.sh check-socket-probe && ./run.sh smoke-meta-honesty && ./run.sh check-meta-doctor-oracle && ./run.sh check-agy-permission-matrix && ./run.sh smoke-agy-statusline-state && ./run.sh smoke-copilot-statusline-state && ./run.sh smoke-copilot-mcp-state && ./run.sh smoke-omp-bridge-state && ./run.sh smoke-omp-mcp-state && ./run.sh smoke-omp-receive-state && ./run.sh check-omp-birth-hook && ./run.sh check-omp-receive-arm && ./run.sh check-omp-fresh-preflight && ./run.sh smoke-agy-hooks-state && ./run.sh smoke-pi-provider-state && ./run.sh smoke-user-scope-citizen && ./run.sh smoke-meta-prune && ./run.sh smoke-meta-keyset-guard && ./run.sh check-meta-manifest-schema && ./run.sh check-entwurf-control-rpc && ./run.sh check-entwurf-bridge-boot && ./run.sh check-probe-bridge-command && ./run.sh check-meta-facts && ./run.sh check-peer-facts && ./run.sh check-herdr-plugin && ./run.sh check-herdr-plugin-profile && ./run.sh check-herdr-plugin-build && ./run.sh check-herdr-runtime-bootstrap && ./run.sh check-herdr-activation && ./run.sh check-agy-sender-identity && ./run.sh check-acp-provider-surface && ./run.sh check-acp-stop-reason && ./run.sh check-acp-prompt-lifecycle && ./run.sh check-acp-usage-accounting && ./run.sh check-acp-launch-namespace && ./run.sh check-acp-stream-hooks && ./run.sh check-acp-backend-preflight && ./run.sh check-acp-session-reuse && ./run.sh check-typing-call-fence && ./run.sh check-gate-manifests && ./run.sh check-release-gate-outcomes && ./run.sh check-probe-ordering && ./run.sh check-probe-cli-shim && ./run.sh check-acp-cortex && ./run.sh check-codex-bridge-identity && ./run.sh check-pi-bridge-sender && ./run.sh check-pi-mcp-register && ./run.sh check-pi-mcp-bridge && ./run.sh check-codex-birth-hook && ./run.sh smoke-codex-config-state && ./run.sh smoke-codex-birth && ./run.sh check-herdr-sandbox",
|
|
191
193
|
"check:package": "./run.sh check-install-surface && ./run.sh smoke-meta-install-state && ./run.sh smoke-agy-install-state && ./run.sh smoke-setup-verdict && ./run.sh check-pack-pin-matcher && ./run.sh check-pack-consumer-roots && ./run.sh check-package-source-routing && ./run.sh check-install-preflight && ./run.sh check-pack"
|
|
192
194
|
},
|
package/run.sh
CHANGED
|
@@ -134,6 +134,7 @@ Usage:
|
|
|
134
134
|
./run.sh check-omp-birth-hook # #87 gate: drives the real OMP assembler into a temp dir, imports the ASSEMBLED index.ts into a MOCK omp host and fires session_start/session_switch. tui mints one backend:"omp" record + a sender marker keyed to the host's OWN pid (the one-process join) + the garden id on the status line; print/rpc/json mint NOTHING (hasUI is true on the rpc rows, as in the vendor); switch attaches on the same native id and mints the replacement on a new one; the CERTIFIED receiver reader still finds no marker. Also the four-root joint binding: extension and omp-labeled bridge child resolve the same sessions/mailbox/senders/receivers bundle against test-built literals (never the production resolver as its own oracle), under a poisoned PI_CODING_AGENT_DIR and under four distinct ENTWURF_META_* overrides; the override grammar is absolute-or-~ and both halves plus the doctor refuse anything else by name; and a drifted provenance label selects no root policy at all. Hermetic; no omp, no model turn
|
|
135
135
|
./run.sh check-pi-durable-contact # #129 L-identity gate, CHECKOUT-ONLY (an installed package refuses it in run_ts). Opens the REAL durable app from THIS checkout's verified carrier (pi/pi-durable/carrier/ under its resolver, against the checkout's pinned pi SDK set — refused by name first) with the pi-durable contact, births the citizen and runs its tools adapter-direct through a privately emitted branch bridge (ENTWURF_PI_DURABLE_BRIDGE_ENTRY; its registry must match this checkout). Matrix roots {default, explicit} x {fresh, --continue}: one record keyed by the durable session dir, sender marker on the spawned host pid, exactly one bridge child whose /proc PPID is that host, poisoned identity carriers kept from it, its four root carriers bound to the roots written, the receiver ARMED (raw marker owned by the host pid + start key as pi-durable-host, joined to the sender marker; removed on close), self = the record and replyable, peers from an outside bridge, same garden id on reopen; the bundle's compiled owner join must admit pi-durable-host. Reaps only the host/bridges it owns on any failure. Harness-bypassed: no model turn, no ToolTask. Missing inputs → SKIP (97), never a pass
|
|
136
136
|
./run.sh check-pi-durable-send # #129 L-send FIRST CELL, CHECKOUT-ONLY. Same input as check-pi-durable-contact. One native durable turn: the app's own controller input → GenerationTask → ToolTask → the contact's entwurf_v2 → the private bridge → a seeded self-fetch target (C2 recipe, owned by a separate idle child). S = a 127.0.0.1 scripted OpenAI-compatible endpoint configured through the app's documented models.json (dummy key, PI_OFFLINE); H = the native ToolTask path; V (a vendor turn) is NOT run. Oracles: the wire (tools offered, v2 schema = the bridge's tools/list, exactly two requests, the tool result + receipt in the second), the raw mailbox (.msg 1, signal, sender = the host record), the native view (one call, its settled result, root current). Not exactly-once, not recovery, not a recipient turn. Missing inputs → SKIP (97)
|
|
137
|
+
./run.sh check-demo-reproducibility # source-checkout-only: discover offline demo Node tests; no LIVE, model, render or publication
|
|
137
138
|
./run.sh check-pi-durable-beside # #129 mutant execution coordinate (VERIFY rule 3): run_vitest over the six pi-durable beside tests (adapter, carrier/SDK-set verifier, preflight, owner join, carrier relocation/resolver/emitter/task footer, native-module file and directory ingress) — the pi-durable qualification lane's gate argv. Same files check-tests-beside-behavior already runs.
|
|
138
139
|
./run.sh check-pi-durable-receive # #129 L-receive FIRST CELL, CHECKOUT-ONLY. Same input (the bundle must carry the pi-durable-host owner join). The host driver gives the app NO input; a seeded claude-code sender (marker owned by a separate idle child, handed to a gate-owned sending bridge through ENTWURF_META_SENDER_MARKER) enqueues one message, the host's doorbell admits its notice to the ROOT conversation, and the scripted model drains it with entwurf_inbox_read. S withholds response #1 until the ENQUEUE receipt, the .delivered bytes, the signal, the host-owned receiver marker and the notice are captured. Oracles: receiver() admission, native view, raw archive (.delivered.read + lastReadAt), wire (exactly two requests, drain result = view), and after a green reap a forensic read-only SQLite copy (submission row by request id = reported id, root, input, done, placed entry = the notice). V NOT run. Missing inputs → SKIP (97)
|
|
139
140
|
./run.sh check-copilot-receive-arm # #82 RAIL 5 gate: the REAL receiver installer + the REAL extension.mjs forked with a stubbed SDK. Arms only after birth, marker owned by the WATCHER pid, self-fetch dispatch answer, doorbell carries the garden id and NOT the body, id-drift/foreign-parent refusals. Hermetic; no Copilot, no model turn
|
|
@@ -1029,6 +1030,37 @@ check_capability_bundle_reach() {
|
|
|
1029
1030
|
run_ts scripts/check-capability-bundle-reach.ts
|
|
1030
1031
|
}
|
|
1031
1032
|
|
|
1033
|
+
check_demo_reproducibility() {
|
|
1034
|
+
# Source-only Node strip-types/CLI and subprocess fixtures, not adapter Vitest tests.
|
|
1035
|
+
# Discovery lives beside demo behavior; no filename list or npm TS crossing.
|
|
1036
|
+
section "demo reproducibility (offline source fixtures, no model or recording)"
|
|
1037
|
+
case "$REPO_DIR" in */node_modules/*) fail "check-demo-reproducibility is source-checkout-only"; return 1 ;; esac
|
|
1038
|
+
local -a tests=()
|
|
1039
|
+
local f
|
|
1040
|
+
while IFS= read -r f; do [ -n "$f" ] && tests+=("$f"); done < <(cd "$REPO_DIR" && find demo -type f -name '*.test.ts' -print | LC_ALL=C sort)
|
|
1041
|
+
if [ "${#tests[@]}" -eq 0 ]; then
|
|
1042
|
+
fail "[QK:DEMO-TEST-DISCOVERY] demo test discovery is empty — no fixtures is not a passing floor"
|
|
1043
|
+
return 1
|
|
1044
|
+
fi
|
|
1045
|
+
(
|
|
1046
|
+
local sandbox name
|
|
1047
|
+
sandbox=$(mktemp -d /tmp/entwurf-demo-unit.XXXXXX)
|
|
1048
|
+
trap 'rm -rf -- "$sandbox"' EXIT
|
|
1049
|
+
while IFS= read -r name; do
|
|
1050
|
+
case "$name" in ENTWURF_*|HERDR_*|PI_SESSION_ID|PI_AGENT_ID|CLAUDECODE|CLAUDE_CODE_SESSION_ID|TMUX|TMUX_PANE|LIVE) unset "$name" ;; esac
|
|
1051
|
+
done < <(compgen -e)
|
|
1052
|
+
export HOME="$sandbox/home" PI_CODING_AGENT_DIR="$sandbox/pi"
|
|
1053
|
+
export XDG_CONFIG_HOME="$sandbox/config" XDG_DATA_HOME="$sandbox/data" XDG_STATE_HOME="$sandbox/state" XDG_CACHE_HOME="$sandbox/cache" XDG_RUNTIME_DIR="$sandbox/runtime"
|
|
1054
|
+
mkdir -m 700 "$HOME" "$PI_CODING_AGENT_DIR" "$XDG_CONFIG_HOME" "$XDG_DATA_HOME" "$XDG_STATE_HOME" "$XDG_CACHE_HOME" "$XDG_RUNTIME_DIR" "$sandbox/bin"
|
|
1055
|
+
# Take.finish records PATH versions; fixtures must not depend on an installed Pi or run it.
|
|
1056
|
+
printf '#!/bin/sh\n[ "$1" = --version ] && { printf "offline-fixture\\n"; exit 0; }\nexit 81\n' > "$sandbox/bin/pi"
|
|
1057
|
+
chmod +x "$sandbox/bin/pi"
|
|
1058
|
+
export PATH="$sandbox/bin:$PATH"
|
|
1059
|
+
cd "$REPO_DIR"
|
|
1060
|
+
node --test "${tests[@]}"
|
|
1061
|
+
)
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1032
1064
|
check_bridge_delivery() {
|
|
1033
1065
|
# demo/demo.sh scene 3, recovered as a deterministic gate. demo drove two real pi
|
|
1034
1066
|
# panes so it proved DELIVERY through the installed binary (at model cost); what
|
|
@@ -3756,6 +3788,8 @@ check_pack() {
|
|
|
3756
3788
|
# pi-extensions/ so it also covers any future allowlist entry; `plugins/` needs no
|
|
3757
3789
|
# companion negation because `^plugins/` above already refuses that whole tree.
|
|
3758
3790
|
'\.test\.[cm]?[jt]s$'
|
|
3791
|
+
# Maintainer take/renderer/helper TS is source-only, including non-test implementations.
|
|
3792
|
+
'^demo/.*\.ts$'
|
|
3759
3793
|
)
|
|
3760
3794
|
|
|
3761
3795
|
local pass=1 f pat hit
|
|
@@ -8175,6 +8209,9 @@ case "$cmd" in
|
|
|
8175
8209
|
check-pi-queue-oracle)
|
|
8176
8210
|
check_pi_queue_oracle
|
|
8177
8211
|
;;
|
|
8212
|
+
check-demo-reproducibility)
|
|
8213
|
+
check_demo_reproducibility
|
|
8214
|
+
;;
|
|
8178
8215
|
check-tests-beside-behavior)
|
|
8179
8216
|
check_tests_beside_behavior
|
|
8180
8217
|
;;
|
|
@@ -3455,6 +3455,7 @@ let manifestCount: number;
|
|
|
3455
3455
|
"codex-native": 82,
|
|
3456
3456
|
"compaction-send-guard": 7,
|
|
3457
3457
|
"control-socket-disconnect": 4,
|
|
3458
|
+
"demo-reproducibility": 12,
|
|
3458
3459
|
"copilot-birth": 19,
|
|
3459
3460
|
"copilot-launch": 14,
|
|
3460
3461
|
"pi-launch": 9,
|
|
@@ -3493,7 +3494,7 @@ let manifestCount: number;
|
|
|
3493
3494
|
"omp-fresh": 24,
|
|
3494
3495
|
"omp-receive": 11,
|
|
3495
3496
|
"probe-ordering": 1,
|
|
3496
|
-
"release-gate":
|
|
3497
|
+
"release-gate": 24,
|
|
3497
3498
|
"retired-sent-surface": 2,
|
|
3498
3499
|
"resume-args": 6,
|
|
3499
3500
|
"resume-launch-identity": 6,
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
// Pure + subprocess, no network/model — IN pnpm run check:full.
|
|
44
44
|
|
|
45
45
|
import { strict as assert } from "node:assert";
|
|
46
|
-
import { execFileSync } from "node:child_process";
|
|
46
|
+
import { execFileSync, spawnSync } from "node:child_process";
|
|
47
47
|
import { copyFileSync, globSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
48
48
|
import { tmpdir } from "node:os";
|
|
49
49
|
import { basename, join } from "node:path";
|
|
@@ -650,6 +650,164 @@ function runSubcommand(sub: string, env: Record<string, string | undefined>): {
|
|
|
650
650
|
}
|
|
651
651
|
}
|
|
652
652
|
|
|
653
|
+
// ===========================================================================
|
|
654
|
+
// 8g. #137: TOML data-subject hunks, not path identity; Markdown/JSON data and
|
|
655
|
+
// code/signatures/machinery remain file-level. Hermetic like 8f: real history is NOT
|
|
656
|
+
// in a qualification snapshot.
|
|
657
|
+
// ==========================================================================
|
|
658
|
+
{
|
|
659
|
+
const tmp = mkdtempSync(join(tmpdir(), "entwurf-qualify-hunks-"));
|
|
660
|
+
const git = (...args: string[]) =>
|
|
661
|
+
execFileSync("git", ["-c", "core.hooksPath=/dev/null", "-c", "user.email=g@e", "-c", "user.name=g", ...args], {
|
|
662
|
+
cwd: tmp,
|
|
663
|
+
stdio: "ignore",
|
|
664
|
+
});
|
|
665
|
+
const head = () => execFileSync("git", ["rev-parse", "HEAD"], { cwd: tmp, encoding: "utf8" }).trim();
|
|
666
|
+
const data = 'version = "1"\n[[panes]]\ncommand = ["node", "build.mjs"]\n# outside\n';
|
|
667
|
+
const wrong: string[] = [];
|
|
668
|
+
try {
|
|
669
|
+
git("init", "-q", "-b", "main");
|
|
670
|
+
mkdirSync(join(tmp, "scripts/mutants"), { recursive: true });
|
|
671
|
+
mkdirSync(join(tmp, "fixture"));
|
|
672
|
+
mkdirSync(join(tmp, "scripts/fixtures"));
|
|
673
|
+
copyFileSync(join(REPO_DIR, "scripts/ci-qualify-decide.sh"), join(tmp, "scripts/ci-qualify-decide.sh"));
|
|
674
|
+
const manifest = JSON.stringify({
|
|
675
|
+
mutants: [
|
|
676
|
+
{ subject: "fixture/data.toml", find: ["[[panes]]"] },
|
|
677
|
+
{ subject: "fixture/data.toml", find: ['command = ["node", "build.mjs"]'] },
|
|
678
|
+
{ subject: "fixture/data.toml", find: ["[[panes]]", 'command = ["node", "build.mjs"]'] },
|
|
679
|
+
{ subject: "fixture/code.ts", find: ["return 1"], signatureSource: "fixture/signature.md" },
|
|
680
|
+
{ subject: "fixture/signature.md", find: ["signature"] },
|
|
681
|
+
{ subject: "fixture/joined.toml", find: ["alpha = 1", "beta = 2"] },
|
|
682
|
+
{ subject: "fixture/doc.md", find: ["Legacy rows are documented: kept for history."] },
|
|
683
|
+
{ subject: "fixture/reg.json", find: ['"mode": "self-fetch",'] },
|
|
684
|
+
{ subject: "scripts/fixtures/pin.json", find: ['"pinned": 1'] },
|
|
685
|
+
{ subject: "scripts/ci-qualify-decide.sh", find: ["set -euo pipefail"] },
|
|
686
|
+
],
|
|
687
|
+
});
|
|
688
|
+
writeFileSync(join(tmp, "scripts/mutants/fixture.json"), manifest);
|
|
689
|
+
writeFileSync(join(tmp, "fixture/data.toml"), data);
|
|
690
|
+
writeFileSync(join(tmp, "fixture/code.ts"), "// outside\nfunction f() { return 1; }\n");
|
|
691
|
+
writeFileSync(join(tmp, "fixture/signature.md"), "signature\n# outside\n");
|
|
692
|
+
writeFileSync(join(tmp, "fixture/joined.toml"), "version = 1\nalpha = 1\nbeta = 2\n");
|
|
693
|
+
const doc = "# Doc\nLegacy rows are documented: kept for history.\n# outside\n";
|
|
694
|
+
const reg = '{\n"copilot": {\n"mode": "self-fetch",\n"level": 6\n}\n}\n';
|
|
695
|
+
writeFileSync(join(tmp, "fixture/doc.md"), doc);
|
|
696
|
+
writeFileSync(join(tmp, "fixture/reg.json"), reg);
|
|
697
|
+
writeFileSync(join(tmp, "scripts/fixtures/pin.json"), '{\n"version": 1,\n"pinned": 1\n}\n');
|
|
698
|
+
git("add", "-A");
|
|
699
|
+
git("commit", "-qm", "seed");
|
|
700
|
+
const base = head();
|
|
701
|
+
const nonToml: string[] = [];
|
|
702
|
+
const check = (name: string, expected: boolean, reason?: string, before = base, into = wrong) => {
|
|
703
|
+
const r = spawnSync("bash", [join(tmp, "scripts/ci-qualify-decide.sh"), before, head()], {
|
|
704
|
+
cwd: tmp,
|
|
705
|
+
encoding: "utf8",
|
|
706
|
+
env: { ...process.env, CI_EVENT_NAME: "push", CI_FORCED: "false" },
|
|
707
|
+
});
|
|
708
|
+
if (r.status !== 0 || r.stdout.trim() !== `run_body=${expected}` || (reason && !r.stderr.includes(reason)))
|
|
709
|
+
into.push(`${name}: status=${r.status}, out=${r.stdout.trim()}, err=${r.stderr.trim()}`);
|
|
710
|
+
};
|
|
711
|
+
const commit = () => {
|
|
712
|
+
git("add", "-A");
|
|
713
|
+
git("commit", "-qm", "cell");
|
|
714
|
+
};
|
|
715
|
+
const reset = () => git("reset", "--hard", base); // ONLY this test-owned throwaway repository.
|
|
716
|
+
writeFileSync(join(tmp, "fixture/data.toml"), data.replace('version = "1"', 'version = "2"'));
|
|
717
|
+
commit();
|
|
718
|
+
check("outside", false, "fixture/data.toml: no find range touched");
|
|
719
|
+
// A replay must read HEAD's manifest, not an unrelated dirty worktree's find.
|
|
720
|
+
writeFileSync(
|
|
721
|
+
join(tmp, "scripts/mutants/fixture.json"),
|
|
722
|
+
'{"mutants":[{"subject":"fixture/data.toml","find":["version"]}]}',
|
|
723
|
+
);
|
|
724
|
+
check("HEAD manifest, not dirty worktree", false);
|
|
725
|
+
reset();
|
|
726
|
+
for (const [name, content] of [
|
|
727
|
+
["panes line", data.replace("[[panes]]", "[[panes]] # changed")],
|
|
728
|
+
["command line", data.replace('command = ["node", "build.mjs"]', 'command = ["node", "build.mjs"] # changed')],
|
|
729
|
+
["moved", `# inserted above\n${data}`],
|
|
730
|
+
["ambiguous", `${data}[[panes]]\n`],
|
|
731
|
+
["missing find", data.replace("[[panes]]", "[[other]]")],
|
|
732
|
+
] as const) {
|
|
733
|
+
writeFileSync(join(tmp, "fixture/data.toml"), content);
|
|
734
|
+
commit();
|
|
735
|
+
check(name, true);
|
|
736
|
+
reset();
|
|
737
|
+
}
|
|
738
|
+
git("rm", "fixture/data.toml");
|
|
739
|
+
commit();
|
|
740
|
+
check("deleted subject", true);
|
|
741
|
+
reset();
|
|
742
|
+
git("mv", "fixture/data.toml", "fixture/renamed.toml");
|
|
743
|
+
commit();
|
|
744
|
+
check("renamed subject", true);
|
|
745
|
+
reset();
|
|
746
|
+
writeFileSync(join(tmp, "fixture/code.ts"), "// outside changed\nfunction f() { return 1; }\n");
|
|
747
|
+
commit();
|
|
748
|
+
check("code outside find remains file-level", true);
|
|
749
|
+
reset();
|
|
750
|
+
writeFileSync(join(tmp, "fixture/signature.md"), "signature\n# outside changed\n");
|
|
751
|
+
commit();
|
|
752
|
+
check("signature also data subject remains file-level", true);
|
|
753
|
+
reset();
|
|
754
|
+
writeFileSync(join(tmp, "fixture/joined.toml"), "version = 1\nalpha = 1\nbeta = 2 # changed\n");
|
|
755
|
+
commit();
|
|
756
|
+
check("only multiline find, second line touched", true);
|
|
757
|
+
reset();
|
|
758
|
+
writeFileSync(join(tmp, "scripts/fixtures/pin.json"), '{\n"version": 2,\n"pinned": 1\n}\n');
|
|
759
|
+
commit();
|
|
760
|
+
check("machinery also data subject remains file-level", true);
|
|
761
|
+
reset();
|
|
762
|
+
writeFileSync(
|
|
763
|
+
join(tmp, "scripts/ci-qualify-decide.sh"),
|
|
764
|
+
`${readFileSync(join(tmp, "scripts/ci-qualify-decide.sh"), "utf8")}\n# outside find\n`,
|
|
765
|
+
);
|
|
766
|
+
commit();
|
|
767
|
+
check("machinery also subject remains file-level", true);
|
|
768
|
+
reset();
|
|
769
|
+
// Outside an untouched exact-once span, a shorter presence literal (measured on DELIVERY.md)
|
|
770
|
+
// or a later JSON duplicate key (JSON.parse: last wins) can re-satisfy the oracle.
|
|
771
|
+
writeFileSync(join(tmp, "fixture/doc.md"), `${doc}Legacy rows are documented\n`);
|
|
772
|
+
commit();
|
|
773
|
+
check("markdown restatement outside find", true, "conservative:", base, nonToml);
|
|
774
|
+
reset();
|
|
775
|
+
writeFileSync(
|
|
776
|
+
join(tmp, "fixture/reg.json"),
|
|
777
|
+
reg.replace("6\n}\n}\n", '6\n},\n"copilot": { "level": 6, "mode": "self-fetch" }\n}\n'),
|
|
778
|
+
);
|
|
779
|
+
commit();
|
|
780
|
+
check("json duplicate key outside find", true, "conservative:", base, nonToml);
|
|
781
|
+
reset();
|
|
782
|
+
// The first quoted name must still fail open after a >pipe-buffer list:
|
|
783
|
+
// printf | grep -q can lose its match to SIGPIPE under pipefail.
|
|
784
|
+
writeFileSync(join(tmp, '000"quoted'), "x\n");
|
|
785
|
+
for (let i = 0; i < 768; i++) writeFileSync(join(tmp, "fixture", `${i}_${"x".repeat(180)}.txt`), "x\n");
|
|
786
|
+
commit();
|
|
787
|
+
check("large quoted changed-name list", true, "quoted changed path cannot be read honestly");
|
|
788
|
+
reset();
|
|
789
|
+
check("unreadable range", true, "fail-open 4", "f".repeat(40));
|
|
790
|
+
git("rm", "scripts/mutants/fixture.json");
|
|
791
|
+
commit();
|
|
792
|
+
check("missing manifests", true);
|
|
793
|
+
assert.ok(
|
|
794
|
+
wrong.length === 0,
|
|
795
|
+
"[QK:QUALIFY-FILTER-SUBJECT-HUNKS] only untouched, unmoved, exact-once TOML find ranges may skip; " +
|
|
796
|
+
"unknown ranges run, and code/signature/machinery retain file-level protection. " +
|
|
797
|
+
wrong.join("; "),
|
|
798
|
+
);
|
|
799
|
+
assert.ok(
|
|
800
|
+
nonToml.length === 0,
|
|
801
|
+
"[QK:QUALIFY-FILTER-TOML-ONLY-REFINEMENT] Markdown and JSON subjects stay file-level: an untouched exact-once " +
|
|
802
|
+
"find does not pin kill-power when a shorter presence literal or a later duplicate JSON key outside it can " +
|
|
803
|
+
"re-satisfy the oracle. " +
|
|
804
|
+
nonToml.join("; "),
|
|
805
|
+
);
|
|
806
|
+
} finally {
|
|
807
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
|
|
653
811
|
// ===========================================================================
|
|
654
812
|
// 9. Every gate a mutant manifest NAMES runs inside `pnpm run check:full`
|
|
655
813
|
//
|
|
@@ -28,37 +28,117 @@ fail_open() {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
collect_paths_from_manifests() {
|
|
31
|
-
python3 - "$REPO_DIR" <<'PY'
|
|
31
|
+
python3 - "$REPO_DIR" "${1:-}" <<'PY'
|
|
32
32
|
import glob
|
|
33
33
|
import json
|
|
34
34
|
import os
|
|
35
35
|
import sys
|
|
36
|
+
import subprocess
|
|
36
37
|
|
|
37
|
-
repo = sys.argv[1]
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
repo, rev = sys.argv[1:]
|
|
39
|
+
def git(*args):
|
|
40
|
+
return subprocess.check_output(["git", "-C", repo, *args])
|
|
41
|
+
paths = (git("ls-tree", "-r", "--name-only", rev, "scripts/mutants").decode().splitlines()
|
|
42
|
+
if rev else sorted(glob.glob(os.path.join(repo, "scripts/mutants/*.json"))))
|
|
43
|
+
paths = [p for p in paths if p.endswith(".json")]
|
|
44
|
+
if not paths:
|
|
45
|
+
raise ValueError("no mutant manifests")
|
|
46
|
+
out, signatures, subjects = set(), set(), {}
|
|
47
|
+
for path in paths:
|
|
48
|
+
if rev:
|
|
49
|
+
manifest = json.loads(git("show", f"{rev}:{path}"))
|
|
50
|
+
else:
|
|
51
|
+
with open(path, encoding="utf-8") as handle:
|
|
52
|
+
manifest = json.load(handle)
|
|
42
53
|
for mutant in manifest.get("mutants", []):
|
|
43
54
|
subject = mutant.get("subject")
|
|
44
55
|
if subject:
|
|
45
56
|
out.add(subject)
|
|
57
|
+
find = mutant.get("find")
|
|
58
|
+
# The runner joins a find ARRAY into ONE multiline occurrence.
|
|
59
|
+
needle = "\n".join(find) if isinstance(find, list) and all(isinstance(s, str) for s in find) else None
|
|
60
|
+
subjects.setdefault(subject, []).append(needle)
|
|
46
61
|
signature_source = mutant.get("signatureSource")
|
|
47
62
|
if signature_source:
|
|
48
63
|
out.add(signature_source)
|
|
49
|
-
|
|
50
|
-
|
|
64
|
+
signatures.add(signature_source)
|
|
65
|
+
if not out or any(any(c in p for c in '\n\r\t"\\') for p in out):
|
|
66
|
+
raise ValueError("empty or unrepresentable manifest paths")
|
|
67
|
+
print(json.dumps({"paths": sorted(out), "signatures": sorted(signatures), "subjects": subjects}))
|
|
51
68
|
PY
|
|
52
69
|
}
|
|
53
70
|
|
|
54
|
-
|
|
55
|
-
|
|
71
|
+
load_manifest_surface() {
|
|
72
|
+
MANIFEST_DATA="$(collect_paths_from_manifests "${1:-}")" || fail_open 6 "missing/unreadable mutant manifests"
|
|
73
|
+
MANIFEST_PATHS="$(printf '%s' "$MANIFEST_DATA" | python3 -c 'import json,sys;print("\n".join(json.load(sys.stdin)["paths"]))')" || fail_open 6 "unreadable manifest paths"
|
|
74
|
+
SIGNATURE_PATHS="$(printf '%s' "$MANIFEST_DATA" | python3 -c 'import json,sys;print("\n".join(json.load(sys.stdin)["signatures"]))')" || fail_open 6 "unreadable signature paths"
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
# Return success ONLY when every exact-once TOML find span is demonstrably
|
|
78
|
+
# untouched and unmoved on BOTH blobs. Any uncertainty leaves the body armed.
|
|
79
|
+
subject_find_untouched() {
|
|
80
|
+
python3 - "$REPO_DIR" "$BEFORE" "$HEAD_SHA" "$1" 3<<< "$MANIFEST_DATA" <<'PY'
|
|
81
|
+
import json
|
|
82
|
+
import os
|
|
83
|
+
import re
|
|
84
|
+
import subprocess
|
|
85
|
+
import sys
|
|
86
|
+
|
|
87
|
+
repo, before, head, path = sys.argv[1:]
|
|
88
|
+
def git(*args):
|
|
89
|
+
return subprocess.check_output(["git", "-C", repo, *args], stderr=subprocess.DEVNULL).decode("utf-8")
|
|
90
|
+
def span(source, needle):
|
|
91
|
+
if not needle or source.count(needle) != 1:
|
|
92
|
+
raise ValueError("find absent or ambiguous")
|
|
93
|
+
start = source[:source.index(needle)].count("\n") + 1
|
|
94
|
+
return start, start + needle.count("\n") - int(needle.endswith("\n"))
|
|
95
|
+
try:
|
|
96
|
+
finds = json.load(os.fdopen(3))["subjects"].get(path)
|
|
97
|
+
if not finds or any(not needle for needle in finds):
|
|
98
|
+
raise ValueError("missing find spans")
|
|
99
|
+
old, new = git("show", f"{before}:{path}"), git("show", f"{head}:{path}")
|
|
100
|
+
if "\0" in old or "\0" in new:
|
|
101
|
+
raise ValueError("binary subject")
|
|
102
|
+
diff = git("diff", "--no-renames", "--no-ext-diff", "--no-textconv", "--no-color", "-U0", before, head, "--", path)
|
|
103
|
+
if "Binary files " in diff or "GIT binary patch" in diff:
|
|
104
|
+
raise ValueError("binary diff")
|
|
105
|
+
hunks = []
|
|
106
|
+
for line in diff.splitlines():
|
|
107
|
+
if not line.startswith("@@"):
|
|
108
|
+
continue
|
|
109
|
+
match = re.fullmatch(r"@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(?: .*)?", line)
|
|
110
|
+
if not match:
|
|
111
|
+
raise ValueError("unreadable hunk")
|
|
112
|
+
a, b, c, d = match.groups()
|
|
113
|
+
hunks.append((int(a), int(b or 1), int(c), int(d or 1)))
|
|
114
|
+
if not hunks:
|
|
115
|
+
raise ValueError("no readable text hunks")
|
|
116
|
+
for needle in set(finds):
|
|
117
|
+
os_, oe = span(old, needle)
|
|
118
|
+
ns, ne = span(new, needle)
|
|
119
|
+
if (os_, oe) != (ns, ne):
|
|
120
|
+
raise ValueError("find range moved")
|
|
121
|
+
for a, b, c, d in hunks:
|
|
122
|
+
if (b > 0 and a <= oe and a + b - 1 >= os_) or (d > 0 and c <= ne and c + d - 1 >= ns):
|
|
123
|
+
raise ValueError("find range touched")
|
|
124
|
+
except (ValueError, KeyError, TypeError, OSError, subprocess.SubprocessError) as error:
|
|
125
|
+
print(f"ci-qualify-decide: fail-open 7 {path}: {error} -> run_body=true", file=sys.stderr)
|
|
126
|
+
sys.exit(1)
|
|
127
|
+
print(f"ci-qualify-decide: {path}: no find range touched (all exact-once spans unmoved)", file=sys.stderr)
|
|
128
|
+
PY
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
# Signature sources and machinery MUST precede subjects: many paths are BOTH.
|
|
132
|
+
# Code/unknown subjects retain file-level protection; only TOML data gets hunk
|
|
133
|
+
# refinement (#137 non-goal: no skipping code/gate/mutant/matrix changes). TOML
|
|
134
|
+
# refuses a redefined key, but a shorter Markdown presence literal or a later JSON
|
|
135
|
+
# duplicate key outside an untouched find can still re-satisfy an oracle.
|
|
56
136
|
matches_qualification_surface() {
|
|
57
137
|
local file="$1"
|
|
58
138
|
local subject
|
|
59
139
|
while IFS= read -r subject; do
|
|
60
|
-
[ "$file" = "$subject" ] && { echo "manifest-
|
|
61
|
-
done <<< "$
|
|
140
|
+
[ "$file" = "$subject" ] && { echo "manifest-signature-source"; return 0; }
|
|
141
|
+
done <<< "$SIGNATURE_PATHS"
|
|
62
142
|
case "$file" in
|
|
63
143
|
scripts/mutants/*) echo "glob:scripts/mutants/**"; return 0 ;;
|
|
64
144
|
scripts/check-*) echo "glob:scripts/check-*"; return 0 ;;
|
|
@@ -73,6 +153,20 @@ matches_qualification_surface() {
|
|
|
73
153
|
# that stopped covering something cannot hide behind its own change.
|
|
74
154
|
scripts/ci-qualify-decide.sh) echo "exact:scripts/ci-qualify-decide.sh"; return 0 ;;
|
|
75
155
|
esac
|
|
156
|
+
while IFS= read -r subject; do
|
|
157
|
+
if [ "$file" = "$subject" ]; then
|
|
158
|
+
if [ "${MATCH_MODE:-files}" = range ]; then
|
|
159
|
+
case "$file" in
|
|
160
|
+
*.toml) ;;
|
|
161
|
+
*) echo "conservative:non-toml-subject"; return 0 ;;
|
|
162
|
+
esac
|
|
163
|
+
if subject_find_untouched "$file"; then
|
|
164
|
+
return 1
|
|
165
|
+
fi
|
|
166
|
+
fi
|
|
167
|
+
echo "manifest-subject"; return 0
|
|
168
|
+
fi
|
|
169
|
+
done <<< "$MANIFEST_PATHS"
|
|
76
170
|
return 1
|
|
77
171
|
}
|
|
78
172
|
|
|
@@ -98,13 +192,12 @@ decide_from_files() {
|
|
|
98
192
|
fi
|
|
99
193
|
}
|
|
100
194
|
|
|
101
|
-
MANIFEST_PATHS="$(collect_paths_from_manifests)"
|
|
102
|
-
[ -n "$MANIFEST_PATHS" ] || { echo "ABORT: no mutant manifests read from $REPO_DIR/scripts/mutants" >&2; exit 1; }
|
|
103
|
-
|
|
104
195
|
if [ "${1:-}" = "--files-from" ]; then
|
|
196
|
+
MATCH_MODE=files
|
|
197
|
+
load_manifest_surface
|
|
105
198
|
SRC="${2:-}"
|
|
106
199
|
[ -n "$SRC" ] || { echo "ABORT: --files-from needs a path or -" >&2; exit 1; }
|
|
107
|
-
if [ "$SRC" = "-" ]; then FILES="$(cat)"; else FILES="$(cat "$SRC")"; fi
|
|
200
|
+
if [ "$SRC" = "-" ]; then FILES="$(cat)"; else FILES="$(cat "$SRC")"; fi || fail_open 4 "unreadable files-from input"
|
|
108
201
|
decide_from_files "$FILES"
|
|
109
202
|
exit 0
|
|
110
203
|
fi
|
|
@@ -154,6 +247,10 @@ esac
|
|
|
154
247
|
git -C "$REPO_DIR" cat-file -e "$BEFORE^{commit}" 2>/dev/null || fail_open 4 "before=$BEFORE is not a commit object in this checkout"
|
|
155
248
|
git -C "$REPO_DIR" cat-file -e "$HEAD_SHA^{commit}" 2>/dev/null || fail_open 4 "head=$HEAD_SHA is not a commit object in this checkout"
|
|
156
249
|
|
|
157
|
-
|
|
250
|
+
MATCH_MODE=range
|
|
251
|
+
load_manifest_surface "$HEAD_SHA"
|
|
252
|
+
CHANGED="$(git -C "$REPO_DIR" diff --no-renames --no-ext-diff --no-textconv --name-only "$BEFORE".."$HEAD_SHA" 2>/dev/null)" || fail_open 4 "git diff $BEFORE..$HEAD_SHA failed"
|
|
253
|
+
# Git quotes unrepresentable changed names; never interpret those as absent paths.
|
|
254
|
+
if grep -q '^"' <<< "$CHANGED"; then fail_open 4 "quoted changed path cannot be read honestly"; fi
|
|
158
255
|
|
|
159
256
|
decide_from_files "$CHANGED"
|