@junghanacs/entwurf 0.34.1 → 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.
@@ -17,6 +17,18 @@ MKT_NAME="meta-bridge-local"
17
17
  PLUGIN="entwurf-meta-receive"
18
18
  CLAUDE_CFG="${CLAUDE_CONFIG_DIR:-$HOME/.claude}"
19
19
  REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
20
+ # #136: the one argument. A Herdr plugin activation wires Claude with ABSOLUTE commands under the
21
+ # stable runtime; the mode is normally derived from the activation ledger (see [expectation mode]),
22
+ # and this override exists so a checkout's doctor can name that root. It must agree with the ledger.
23
+ PLUGIN_RUNTIME_OVERRIDE=""
24
+ while [ $# -gt 0 ]; do
25
+ case "$1" in
26
+ --plugin-runtime)
27
+ [ -n "${2:-}" ] || { echo "meta-bridge-doctor: --plugin-runtime requires a value" >&2; exit 2; }
28
+ PLUGIN_RUNTIME_OVERRIDE="$2"; shift 2 ;;
29
+ *) echo "usage: meta-bridge-doctor.sh [--plugin-runtime <stable active runtime root>]" >&2; exit 2 ;;
30
+ esac
31
+ done
20
32
  # The live artifact lives under the XDG data dir (dev clone and installed package
21
33
  # alike — never the checkout). Prefer the path RECORDED in install-state so a doctor
22
34
  # run under a different XDG_DATA_HOME than install still hashes the REAL assembled
@@ -110,6 +122,44 @@ if command -v node >/dev/null; then
110
122
  if [ "${MJ:-0}" -ge 24 ]; then ok "node $NV (>= 24, supported axis)"; else bad "node $NV too old (need >= 24; there is no Node 22 lane)"; fi
111
123
  else bad "node not on PATH"; fi
112
124
 
125
+ echo "[expectation mode]"
126
+ # #136: which expectations the three desired-state calls below compute. The judgement is the
127
+ # activation owner's (`resolveDoctorPluginRuntime`, herdr-activation.mjs) reached through one narrow
128
+ # call, so no second reader of the ledger exists. A refusal is NOT a fallback to bare expectations:
129
+ # each call site then reports UNKNOWN, and every other section still runs (Hard Rule 13).
130
+ PLUGIN_ARGS=()
131
+ MODE_ERR=""
132
+ if ! command -v node >/dev/null; then
133
+ MODE_ERR="node not on PATH, so the activation ledger could not be read"
134
+ bad "expectation mode is UNKNOWN: $MODE_ERR"
135
+ else
136
+ MODE_STDERR="$(mktemp -t entwurf-doctor-mode.XXXXXX)"
137
+ set +e
138
+ if [ -n "$PLUGIN_RUNTIME_OVERRIDE" ]; then
139
+ MODE_ROOT="$(node "$REPO/scripts/herdr-doctor-mode.mjs" "$REPO" "$PLUGIN_RUNTIME_OVERRIDE" 2>"$MODE_STDERR")"
140
+ else
141
+ MODE_ROOT="$(node "$REPO/scripts/herdr-doctor-mode.mjs" "$REPO" 2>"$MODE_STDERR")"
142
+ fi
143
+ MODE_RC=$?
144
+ set -e
145
+ MODE_DETAIL="$(tr '\n' ' ' < "$MODE_STDERR" | cut -c1-400)"
146
+ rm -f "$MODE_STDERR"
147
+ if [ "$MODE_RC" -eq 3 ]; then
148
+ MODE_ERR="$MODE_DETAIL"
149
+ bad "expectation mode refused: $MODE_ERR"
150
+ elif [ "$MODE_RC" -ne 0 ]; then
151
+ MODE_ERR="herdr-doctor-mode.mjs exited $MODE_RC: $MODE_DETAIL"
152
+ bad "expectation mode is UNKNOWN: $MODE_ERR"
153
+ elif [ -n "$MODE_ROOT" ]; then
154
+ PLUGIN_ARGS=(--plugin-runtime "$MODE_ROOT")
155
+ ok "mode: plugin-runtime $MODE_ROOT (activation ledger)"
156
+ elif [ "$LIB_EXT" = "js" ]; then
157
+ ok "mode: installed (no activation ledger, or this package is not the plugin runtime)"
158
+ else
159
+ ok "mode: clone (this checkout is not the plugin runtime)"
160
+ fi
161
+ fi
162
+
113
163
  echo "[managed config state]"
114
164
  if command -v python3 >/dev/null; then
115
165
  # state.py check is the effect-based keyset-survival guard: it asserts every
@@ -123,7 +173,9 @@ if command -v python3 >/dev/null; then
123
173
  # which key drifted — silent instead of fail-loud. As an `if` condition the
124
174
  # substitution's nonzero status is consumed (set -e suspended), so we always
125
175
  # reach the detail. stderr (drift detail) is captured via `2>&1 >/dev/null`.
126
- if CHECK_ERR="$(python3 "$REPO/scripts/meta-bridge-state.py" check --repo "$REPO" --asm "$ASM" 2>&1 >/dev/null)"; then
176
+ if [ -n "$MODE_ERR" ]; then
177
+ bad "managed settings/MCP keyset is UNKNOWN: the expectation mode was not established ($MODE_ERR)"
178
+ elif CHECK_ERR="$(python3 "$REPO/scripts/meta-bridge-state.py" check --repo "$REPO" --asm "$ASM" ${PLUGIN_ARGS[@]+"${PLUGIN_ARGS[@]}"} 2>&1 >/dev/null)"; then
127
179
  ok "state file present and managed settings/MCP keyset survives intact"
128
180
  else
129
181
  bad "managed settings/MCP keyset missing or overwritten — another consumer may have clobbered a pi-owned key. Re-run ./run.sh install-meta-bridge. Drift detail:"
@@ -565,25 +617,29 @@ except Exception:
565
617
  print('')
566
618
  PY
567
619
  )"
568
- EXPECTED_STATUSLINE="$(python3 "$REPO/scripts/meta-bridge-state.py" desired-statusline --repo "$REPO" | python3 -c 'import json,sys; print(json.load(sys.stdin).get("command", ""))')"
569
- if [ "$STATUSLINE_CMD" = "$EXPECTED_STATUSLINE" ]; then ok "statusLine.command matches expected (dev: checkout path; installed: entwurf-statusline bin shim): $STATUSLINE_CMD"; else bad "statusLine.command drifted (got '$STATUSLINE_CMD', expected '$EXPECTED_STATUSLINE')"; fi
570
- if [[ "$EXPECTED_STATUSLINE" == */* ]]; then
571
- if [ -x "$EXPECTED_STATUSLINE" ]; then ok "statusline executable"; else bad "statusline script not executable: $EXPECTED_STATUSLINE"; fi
572
- elif command -v "$EXPECTED_STATUSLINE" >/dev/null 2>&1; then
573
- ok "statusline bin resolves on PATH: $(command -v "$EXPECTED_STATUSLINE")"
620
+ if [ -n "$MODE_ERR" ]; then
621
+ bad "statusLine expectation is UNKNOWN: the expectation mode was not established ($MODE_ERR)"
574
622
  else
575
- bad "statusline bin not on PATH: $EXPECTED_STATUSLINE"
623
+ EXPECTED_STATUSLINE="$(python3 "$REPO/scripts/meta-bridge-state.py" desired-statusline --repo "$REPO" ${PLUGIN_ARGS[@]+"${PLUGIN_ARGS[@]}"} | python3 -c 'import json,sys; print(json.load(sys.stdin).get("command", ""))')"
624
+ if [ "$STATUSLINE_CMD" = "$EXPECTED_STATUSLINE" ]; then ok "statusLine.command matches expected (dev: checkout path; installed: entwurf-statusline bin shim): $STATUSLINE_CMD"; else bad "statusLine.command drifted (got '$STATUSLINE_CMD', expected '$EXPECTED_STATUSLINE')"; fi
625
+ if [[ "$EXPECTED_STATUSLINE" == */* ]]; then
626
+ if [ -x "$EXPECTED_STATUSLINE" ]; then ok "statusline executable"; else bad "statusline script not executable: $EXPECTED_STATUSLINE"; fi
627
+ elif command -v "$EXPECTED_STATUSLINE" >/dev/null 2>&1; then
628
+ ok "statusline bin resolves on PATH: $(command -v "$EXPECTED_STATUSLINE")"
629
+ else
630
+ bad "statusline bin not on PATH: $EXPECTED_STATUSLINE"
631
+ fi
632
+ SAMPLE_STATUSLINE_OUT="$(printf '%s' '{"session_id":"doctor-no-record","workspace":{"current_dir":"/tmp"},"model":{"id":"claude-sonnet-5"},"context_window":{"context_window_size":200000,"used_percentage":1,"current_usage":{"input_tokens":1}}}' | "$EXPECTED_STATUSLINE" 2>/dev/null || true)"
633
+ # The identity row may carry the #98 B unread badge between `cc` and the model letter:
634
+ # ` ✉N` when mail is waiting, nothing when the mailbox is empty, ` ✉?` when the count
635
+ # could not be taken. All three are healthy renders, so the shape test admits any of them.
636
+ if [ "$(printf '%s\n' "$SAMPLE_STATUSLINE_OUT" | wc -l | tr -d ' ')" = "2" ] && printf '%s\n' "$SAMPLE_STATUSLINE_OUT" | sed -n '1p' | grep -q 'tmp' && printf '%s\n' "$SAMPLE_STATUSLINE_OUT" | sed -n '2p' | grep -q '🪛' && printf '%s\n' "$SAMPLE_STATUSLINE_OUT" | sed -n '2p' | grep -qE ' cc( ✉([0-9]+|\?))? \| s'; then ok "statusline synthetic execution emits two rows (work context + identity)"; else bad "statusline synthetic execution failed or omitted two-row work/identity marker"; fi
637
+ # The synthetic feeds a session_id with no meta-record, so the garden id does not
638
+ # resolve and there is no mailbox to count. `✉?` is the required answer: a statusline
639
+ # that drew nothing there would be claiming "no mail" about a mailbox it never opened —
640
+ # the exact silence #98 was opened to remove.
641
+ if printf '%s\n' "$SAMPLE_STATUSLINE_OUT" | sed -n '2p' | grep -q '✉?'; then ok "statusline badge distinguishes 'could not count' (✉?) from zero on the no-record synthetic"; else bad "statusline badge omitted ✉? for the no-record synthetic (a false zero would be indistinguishable from an empty mailbox)"; fi
576
642
  fi
577
- SAMPLE_STATUSLINE_OUT="$(printf '%s' '{"session_id":"doctor-no-record","workspace":{"current_dir":"/tmp"},"model":{"id":"claude-sonnet-5"},"context_window":{"context_window_size":200000,"used_percentage":1,"current_usage":{"input_tokens":1}}}' | "$EXPECTED_STATUSLINE" 2>/dev/null || true)"
578
- # The identity row may carry the #98 B unread badge between `cc` and the model letter:
579
- # ` ✉N` when mail is waiting, nothing when the mailbox is empty, ` ✉?` when the count
580
- # could not be taken. All three are healthy renders, so the shape test admits any of them.
581
- if [ "$(printf '%s\n' "$SAMPLE_STATUSLINE_OUT" | wc -l | tr -d ' ')" = "2" ] && printf '%s\n' "$SAMPLE_STATUSLINE_OUT" | sed -n '1p' | grep -q 'tmp' && printf '%s\n' "$SAMPLE_STATUSLINE_OUT" | sed -n '2p' | grep -q '🪛' && printf '%s\n' "$SAMPLE_STATUSLINE_OUT" | sed -n '2p' | grep -qE ' cc( ✉([0-9]+|\?))? \| s'; then ok "statusline synthetic execution emits two rows (work context + identity)"; else bad "statusline synthetic execution failed or omitted two-row work/identity marker"; fi
582
- # The synthetic feeds a session_id with no meta-record, so the garden id does not
583
- # resolve and there is no mailbox to count. `✉?` is the required answer: a statusline
584
- # that drew nothing there would be claiming "no mail" about a mailbox it never opened —
585
- # the exact silence #98 was opened to remove.
586
- if printf '%s\n' "$SAMPLE_STATUSLINE_OUT" | sed -n '2p' | grep -q '✉?'; then ok "statusline badge distinguishes 'could not count' (✉?) from zero on the no-record synthetic"; else bad "statusline badge omitted ✉? for the no-record synthetic (a false zero would be indistinguishable from an empty mailbox)"; fi
587
643
  else
588
644
  bad "cannot validate statusline without python3"
589
645
  fi
@@ -669,9 +725,12 @@ PY
669
725
  [ -n "$LIVE_MCP_DESC" ] || LIVE_MCP_DESC="(unprintable live command)"
670
726
  # Ownership axis. `|| true` on the pipeline only: a state.py failure must read as
671
727
  # UNKNOWN-and-loud below, never abort the doctor before the delivery drive.
672
- DESIRED_MCP="$( { python3 "$REPO/scripts/meta-bridge-state.py" desired-mcp --repo "$REPO" \
728
+ DESIRED_MCP=""
729
+ [ -n "$MODE_ERR" ] || DESIRED_MCP="$( { python3 "$REPO/scripts/meta-bridge-state.py" desired-mcp --repo "$REPO" ${PLUGIN_ARGS[@]+"${PLUGIN_ARGS[@]}"} \
673
730
  | python3 -c 'import json,sys; print(json.dumps(json.load(sys.stdin), sort_keys=True))'; } 2>/dev/null || true)"
674
- if [ -z "$DESIRED_MCP" ]; then
731
+ if [ -n "$MODE_ERR" ]; then
732
+ bad "live-vs-desired MCP ownership is UNKNOWN: the expectation mode was not established ($MODE_ERR)"
733
+ elif [ -z "$DESIRED_MCP" ]; then
675
734
  bad "could not compute the desired MCP entry (meta-bridge-state.py desired-mcp failed) — live-vs-desired ownership is UNKNOWN"
676
735
  elif [ "$LIVE_MCP" = "$DESIRED_MCP" ]; then
677
736
  ok "live mcpServers.entwurf-bridge equals desired_mcp() for this mode (installed: bin shim; clone: this checkout's start.sh)"
@@ -0,0 +1,155 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "lane": "demo-reproducibility",
4
+ "mutants": [
5
+ {
6
+ "claim": "DEMO-BASELINE-ARCHIVED",
7
+ "title": "the archived baseline resumes launching the retired scene and can overwrite the approved GIF",
8
+ "subject": "demo/demo-baseline.sh",
9
+ "find": ["exit 2"],
10
+ "replace": [":"],
11
+ "gate": ["bash", "run.sh", "check-demo-reproducibility"],
12
+ "timeoutSeconds": 120,
13
+ "signature": "[QK:DEMO-BASELINE-ARCHIVED]",
14
+ "signatureSource": "demo/maintenance-guards.test.ts"
15
+ },
16
+ {
17
+ "claim": "DEMO-PEER-ARCHIVED",
18
+ "title": "the archived peer scene becomes a second runnable v1 route again",
19
+ "subject": "demo/demo.sh",
20
+ "find": ["exit 2"],
21
+ "replace": [":"],
22
+ "gate": ["bash", "run.sh", "check-demo-reproducibility"],
23
+ "timeoutSeconds": 120,
24
+ "signature": "[QK:DEMO-PEER-ARCHIVED]",
25
+ "signatureSource": "demo/maintenance-guards.test.ts"
26
+ },
27
+ {
28
+ "claim": "DEMO-LIVE-OPT-IN",
29
+ "title": "the Herdr scene reaches preflight without the explicit LIVE grant",
30
+ "subject": "demo/take-herdr-pi-cc.ts",
31
+ "find": ["if (process.env.LIVE !== \"1\")"],
32
+ "replace": ["if (false)"],
33
+ "gate": ["bash", "run.sh", "check-demo-reproducibility"],
34
+ "timeoutSeconds": 120,
35
+ "signature": "[QK:DEMO-LIVE-OPT-IN]",
36
+ "signatureSource": "demo/maintenance-guards.test.ts"
37
+ },
38
+ {
39
+ "claim": "DEMO-PUBLISHED-PROVENANCE",
40
+ "title": "the tracked provenance names another GIF digest while the approved pixels stay unchanged",
41
+ "subject": "demo/herdr-pi-cc-20261011.provenance.json",
42
+ "find": ["\t\t\"sha256\": \"b686739b5c68fa458dd47c844711aea0f6ab5819eb3eb5d2850449922fa084c0\","],
43
+ "replace": ["\t\t\"sha256\": \"0000000000000000000000000000000000000000000000000000000000000000\","],
44
+ "gate": ["bash", "run.sh", "check-demo-reproducibility"],
45
+ "timeoutSeconds": 120,
46
+ "signature": "[QK:DEMO-PUBLISHED-PROVENANCE]",
47
+ "signatureSource": "demo/maintenance-guards.test.ts"
48
+ },
49
+ {
50
+ "claim": "DEMO-PACK-SOURCE-ONLY",
51
+ "title": "a directory allowlist ships maintainer TypeScript as if it were an installed entrypoint",
52
+ "subject": "package.json",
53
+ "find": [
54
+ "\t\t\"demo/README.md\",",
55
+ "\t\t\"demo/herdr-pi-cc-20261011.provenance.json\",",
56
+ "\t\t\"!demo/**/*.ts\","
57
+ ],
58
+ "replace": ["\t\t\"demo/\","],
59
+ "gate": ["bash", "run.sh", "check-demo-reproducibility"],
60
+ "timeoutSeconds": 120,
61
+ "signature": "[QK:DEMO-PACK-SOURCE-ONLY]",
62
+ "signatureSource": "demo/maintenance-guards.test.ts"
63
+ },
64
+ {
65
+ "claim": "DEMO-PUBLICATION-DESTINATION",
66
+ "title": "a live-pass renderer can overwrite publication assets through a direct or aliased destination",
67
+ "subject": "demo/render-take.ts",
68
+ "find": ["\tif (isPublicationOutput(out))"],
69
+ "replace": ["\tif (false)"],
70
+ "gate": ["bash", "run.sh", "check-demo-reproducibility"],
71
+ "timeoutSeconds": 120,
72
+ "signature": "[QK:DEMO-PUBLICATION-DESTINATION]",
73
+ "signatureSource": "demo/maintenance-guards.test.ts"
74
+ },
75
+ {
76
+ "claim": "DEMO-TMUX-SUBJECT-TAIL",
77
+ "title": "the tmux tail trimmer drops real subject text merged with the terminal teardown",
78
+ "subject": "demo/render-take.ts",
79
+ "find": ["\t\tisOnlyCsi(teardown[2])"],
80
+ "replace": ["\t\ttrue"],
81
+ "gate": ["bash", "run.sh", "check-demo-reproducibility"],
82
+ "timeoutSeconds": 120,
83
+ "signature": "[QK:DEMO-TMUX-SUBJECT-TAIL]",
84
+ "signatureSource": "demo/render-take.test.ts"
85
+ },
86
+ {
87
+ "claim": "DEMO-RECORDING-REQUIRED",
88
+ "title": "no recorder means no assertion, so an empty capture again certifies live-pass",
89
+ "subject": "demo/lib/take-recorder.ts",
90
+ "find": [
91
+ "\t\tthis.step(",
92
+ "\t\t\t\"the recording ran until the take ended and the recorder stopped cleanly (no cut, no signal, exit 0)\","
93
+ ],
94
+ "replace": [
95
+ "\t\tif (recorder) this.step(",
96
+ "\t\t\t\"the recording ran until the take ended and the recorder stopped cleanly (no cut, no signal, exit 0)\","
97
+ ],
98
+ "gate": ["bash", "run.sh", "check-demo-reproducibility"],
99
+ "timeoutSeconds": 120,
100
+ "signature": "[QK:DEMO-RECORDING-REQUIRED]",
101
+ "signatureSource": "demo/lib/take-recorder.test.ts"
102
+ },
103
+ {
104
+ "claim": "DEMO-TEST-DISCOVERY",
105
+ "title": "a dead discovery glob produces no tests but the demo floor is still invoked",
106
+ "subject": "run.sh",
107
+ "find": [
108
+ " 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)"
109
+ ],
110
+ "replace": [
111
+ " while IFS= read -r f; do [ -n \"$f\" ] && tests+=(\"$f\"); done < <(cd \"$REPO_DIR\" && find demo -type f -name '*.test.NO-MATCH' -print | LC_ALL=C sort)"
112
+ ],
113
+ "gate": ["bash", "run.sh", "check-demo-reproducibility"],
114
+ "timeoutSeconds": 120,
115
+ "signature": "[QK:DEMO-TEST-DISCOVERY]",
116
+ "signatureSource": "run.sh"
117
+ },
118
+ {
119
+ "claim": "DEMO-NO-EARLY-RESOURCES",
120
+ "title": "a Take allocated before missing-LIVE refusal writes resources without calling any fake tool",
121
+ "subject": "demo/take-pi-cc.ts",
122
+ "find": ["if (process.env.LIVE !== \"1\") {"],
123
+ "replace": [
124
+ "if (process.env.LIVE !== \"1\") {",
125
+ "\tnew Take({ label: LABEL, repo: REPO, cols: 132, rows: 38 });"
126
+ ],
127
+ "gate": ["bash", "run.sh", "check-demo-reproducibility"],
128
+ "timeoutSeconds": 120,
129
+ "signature": "[QK:DEMO-NO-EARLY-RESOURCES]",
130
+ "signatureSource": "demo/maintenance-guards.test.ts"
131
+ },
132
+ {
133
+ "claim": "DEMO-ORIGINAL-RENDER-INPUT",
134
+ "title": "rerendering replaces the original approved GIF's exact agg input inside the take directory",
135
+ "subject": "demo/render-take.ts",
136
+ "find": ["\tconst renderCast = path.join(path.dirname(out), `.${path.basename(out)}.${tag}.render.cast`);"],
137
+ "replace": ["\tconst renderCast = path.join(takeDir, \"take.render.cast\");"],
138
+ "gate": ["bash", "run.sh", "check-demo-reproducibility"],
139
+ "timeoutSeconds": 120,
140
+ "signature": "[QK:DEMO-ORIGINAL-RENDER-INPUT]",
141
+ "signatureSource": "demo/render-take.test.ts"
142
+ },
143
+ {
144
+ "claim": "DEMO-RAIL-POLICY",
145
+ "title": "the pi-durable scene stops refusing an OpenRouter base-URL redirect, so LIVE=1 could spend a sibling turn through OpenRouter",
146
+ "subject": "demo/take-pi-durable.ts",
147
+ "find": ["const routedVia = openRouterOverride(process.env);"],
148
+ "replace": ["const routedVia = null;"],
149
+ "gate": ["bash", "run.sh", "check-demo-reproducibility"],
150
+ "timeoutSeconds": 120,
151
+ "signature": "[QK:DEMO-RAIL-POLICY]",
152
+ "signatureSource": "demo/maintenance-guards.test.ts"
153
+ }
154
+ ]
155
+ }
@@ -351,6 +351,77 @@
351
351
  "timeoutSeconds": 300,
352
352
  "signature": "[QK:HAC-LEDGER-PATH-KIND]",
353
353
  "signatureSource": "scripts/check-herdr-activation.ts"
354
+ },
355
+ {
356
+ "claim": "DOCTOR-MODE-DERIVED",
357
+ "title": "a resolver that never answers plugin mode leaves the doctor computing bare-bin expectations on a plugin-activated host, so the activation's correct absolute wiring is reported as drift and the supported route can never be certified (#136)",
358
+ "subject": "scripts/herdr-activation.mjs",
359
+ "find": ["\treturn activeDir;\n}"],
360
+ "replace": ["\treturn null;\n}"],
361
+ "gate": ["bash", "run.sh", "check-herdr-activation"],
362
+ "timeoutSeconds": 300,
363
+ "signature": "[QK:DOCTOR-MODE-DERIVED]",
364
+ "signatureSource": "scripts/check-herdr-activation.ts"
365
+ },
366
+ {
367
+ "claim": "DOCTOR-MODE-CHECKOUT-NEVER-DERIVES",
368
+ "title": "dropping the runtime-package precondition lets a checkout's doctor read the plugin ledger and adopt its absolute wiring as its own expectations, so the checkout certifies a runtime it does not own (#136)",
369
+ "subject": "scripts/herdr-activation.mjs",
370
+ "find": ["\t\tif (!same) return null;"],
371
+ "replace": ["\t\tvoid same;"],
372
+ "gate": ["bash", "run.sh", "check-herdr-activation"],
373
+ "timeoutSeconds": 300,
374
+ "signature": "[QK:DOCTOR-MODE-CHECKOUT-NEVER-DERIVES]",
375
+ "signatureSource": "scripts/check-herdr-activation.ts"
376
+ },
377
+ {
378
+ "claim": "DOCTOR-MODE-UNCERTIFIED-DIES",
379
+ "title": "swallowing the reader's refusal turns an uncertified ledger into 'not plugin mode', so a doctor silently falls back to bare expectations over a record nobody can vouch for (#136)",
380
+ "subject": "scripts/herdr-activation.mjs",
381
+ "find": [
382
+ "\tconst ledger = readCertifiedLedger(resolveActivationLayout(env));\n\tif (ledger === null) {\n\t\tif (override === undefined) return null;"
383
+ ],
384
+ "replace": [
385
+ "\tlet ledger = null;\n\ttry {\n\t\tledger = readCertifiedLedger(resolveActivationLayout(env));\n\t} catch {\n\t\tledger = null;\n\t}\n\tif (ledger === null) {\n\t\tif (override === undefined || override !== undefined) return null;"
386
+ ],
387
+ "gate": ["bash", "run.sh", "check-herdr-activation"],
388
+ "timeoutSeconds": 300,
389
+ "signature": "[QK:DOCTOR-MODE-UNCERTIFIED-DIES]",
390
+ "signatureSource": "scripts/check-herdr-activation.ts"
391
+ },
392
+ {
393
+ "claim": "DOCTOR-MODE-OVERRIDE-AGREES",
394
+ "title": "an override that need not agree with the ledger lets any caller-named root become the doctor's expectation, so the doctor would certify absolute commands pointing anywhere (#136)",
395
+ "subject": "scripts/herdr-activation.mjs",
396
+ "find": ["\tif (override !== undefined && override !== activeDir) {"],
397
+ "replace": ["\tif (false) {"],
398
+ "gate": ["bash", "run.sh", "check-herdr-activation"],
399
+ "timeoutSeconds": 300,
400
+ "signature": "[QK:DOCTOR-MODE-OVERRIDE-AGREES]",
401
+ "signatureSource": "scripts/check-herdr-activation.ts"
402
+ },
403
+ {
404
+ "claim": "DOCTOR-PLUGIN-MODE-GREEN",
405
+ "title": "a doctor that resolves plugin mode but never passes it to its desired-state calls still expects bare bins, so the plugin activation's absolute wiring stays falsely red (#136). One mutant for the shared wire on purpose: omitting the mode at any single one of the three call sites also turns P1 red, but no per-site mutant proves that",
406
+ "subject": "scripts/meta-bridge-doctor.sh",
407
+ "find": [" PLUGIN_ARGS=(--plugin-runtime \"$MODE_ROOT\")"],
408
+ "replace": [" : \"$MODE_ROOT\""],
409
+ "gate": ["bash", "run.sh", "check-meta-doctor-oracle"],
410
+ "timeoutSeconds": 600,
411
+ "signature": "[QK:DOCTOR-PLUGIN-MODE-GREEN]",
412
+ "signatureSource": "scripts/check-meta-doctor-oracle.sh",
413
+ "build": { "argv": ["bash", "scripts/build-bridge.sh"], "output": "mcp/entwurf-bridge/dist" }
414
+ },
415
+ {
416
+ "claim": "DOCTOR-MODE-ADMISSION-CLAUSES",
417
+ "title": "dropping the phase clause lets a ledger caught mid-activation or mid-teardown admit plugin mode, so the doctor certifies absolute wiring the activation has not finished writing or is already removing (#136)",
418
+ "subject": "scripts/herdr-activation.mjs",
419
+ "find": ["\tif (ledger.phase !== \"active\") refuse(`activation ledger phase is ${ledger.phase}, not active`);"],
420
+ "replace": ["\tvoid refuse;"],
421
+ "gate": ["bash", "run.sh", "check-herdr-activation"],
422
+ "timeoutSeconds": 300,
423
+ "signature": "[QK:DOCTOR-MODE-ADMISSION-CLAUSES]",
424
+ "signatureSource": "scripts/check-herdr-activation.ts"
354
425
  }
355
426
  ]
356
427
  }
@@ -151,8 +151,12 @@
151
151
  "claim": "QUALIFY-FILTER-READS-PUSH-RANGE",
152
152
  "title": "the filter reads the tip commit instead of the two-dot push range - the exact misreading that once concluded four of the five historical qualification reds would have been skipped",
153
153
  "subject": "scripts/ci-qualify-decide.sh",
154
- "find": ["CHANGED=\"$(git -C \"$REPO_DIR\" diff --name-only \"$BEFORE\"..\"$HEAD_SHA\" 2>/dev/null)\""],
155
- "replace": ["CHANGED=\"$(git -C \"$REPO_DIR\" diff --name-only \"$HEAD_SHA~1\"..\"$HEAD_SHA\" 2>/dev/null)\""],
154
+ "find": [
155
+ "CHANGED=\"$(git -C \"$REPO_DIR\" diff --no-renames --no-ext-diff --no-textconv --name-only \"$BEFORE\"..\"$HEAD_SHA\" 2>/dev/null)\""
156
+ ],
157
+ "replace": [
158
+ "CHANGED=\"$(git -C \"$REPO_DIR\" diff --no-renames --no-ext-diff --no-textconv --name-only \"$HEAD_SHA~1\"..\"$HEAD_SHA\" 2>/dev/null)\""
159
+ ],
156
160
  "gate": ["bash", "run.sh", "check-release-gate-outcomes"],
157
161
  "timeoutSeconds": 180,
158
162
  "signature": "[QK:QUALIFY-FILTER-READS-PUSH-RANGE]",
@@ -169,6 +173,28 @@
169
173
  "signature": "[QK:QUALIFY-FILTER-REPLAYS-PAST-CATCHES]",
170
174
  "signatureSource": "scripts/check-release-gate-outcomes.ts"
171
175
  },
176
+ {
177
+ "claim": "QUALIFY-FILTER-SUBJECT-HUNKS",
178
+ "title": "data-subject hunk refinement collapses back to file identity, so a version-only pin repeats the unchanged mutant body",
179
+ "subject": "scripts/ci-qualify-decide.sh",
180
+ "find": ["\t\t\t\tif subject_find_untouched \"$file\"; then"],
181
+ "replace": ["\t\t\t\tif false; then"],
182
+ "gate": ["bash", "run.sh", "check-release-gate-outcomes"],
183
+ "timeoutSeconds": 180,
184
+ "signature": "[QK:QUALIFY-FILTER-SUBJECT-HUNKS]",
185
+ "signatureSource": "scripts/check-release-gate-outcomes.ts"
186
+ },
187
+ {
188
+ "claim": "QUALIFY-FILTER-TOML-ONLY-REFINEMENT",
189
+ "title": "Markdown/JSON data re-enter hunk refinement, so an out-of-find presence restatement or later duplicate JSON key skips a body whose mutant now survives",
190
+ "subject": "scripts/ci-qualify-decide.sh",
191
+ "find": ["\t\t\t\t\t*.toml) ;;"],
192
+ "replace": ["\t\t\t\t\t*.toml|*.json|*.md) ;;"],
193
+ "gate": ["bash", "run.sh", "check-release-gate-outcomes"],
194
+ "timeoutSeconds": 180,
195
+ "signature": "[QK:QUALIFY-FILTER-TOML-ONLY-REFINEMENT]",
196
+ "signatureSource": "scripts/check-release-gate-outcomes.ts"
197
+ },
172
198
  {
173
199
  "claim": "MUX-LIFECYCLE-IS-RELEASE-MUST",
174
200
  "title": "the integrated mux lifecycle stays listed but bypasses run_live_step, so a missing prerequisite returns SKIP into a branch that never classifies it and the cut reads green",
@@ -27,6 +27,7 @@
27
27
  "include": [
28
28
  "./**/*.ts",
29
29
  "../test/**/*.ts",
30
+ "../demo/**/*.ts",
30
31
  "../pi-extensions/lib/**/*.ts",
31
32
  // The behavior-adjacent tests the root config just excluded (#119 V2). Listed at
32
33
  // pi-extensions/** rather than pi-extensions/lib/** so a test beside an extension