@junghanacs/entwurf 0.18.1 → 0.19.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/AGENTS.md +2 -2
- package/CHANGELOG.md +248 -0
- package/CONTRIBUTING.md +1 -1
- package/README.md +8 -6
- package/VERIFY.md +3 -3
- package/docs/external-mcp-host.md +2 -2
- package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +18 -7
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +76 -9
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-placement.js +54 -6
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/resolve-tmux-session.js +114 -0
- package/mcp/entwurf-bridge/src/index.ts +22 -7
- package/package.json +1 -1
- package/pi-extensions/entwurf-control.ts +27 -3
- package/pi-extensions/lib/mux-fresh-call.ts +98 -11
- package/pi-extensions/lib/mux-placement.ts +65 -10
- package/pi-extensions/lib/resolve-tmux-session.ts +137 -0
- package/scripts/check-fresh-cut-gate.sh +54 -2
- package/scripts/check-gate-qualification.ts +5 -5
- package/scripts/check-mux-launch-tmux.ts +77 -2
- package/scripts/check-mux-launch.ts +17 -0
- package/scripts/check-mux-placement-tmux.ts +61 -1
- package/scripts/check-mux-placement.ts +33 -0
- package/scripts/check-release-gate-outcomes.ts +224 -9
- package/scripts/ci-qualify-decide.sh +159 -0
- package/scripts/fixtures/qualify-replay.json +149 -0
- package/scripts/mutants/fresh-cut.json +26 -0
- package/scripts/mutants/mux-boundary.json +24 -0
- package/scripts/mutants/mux-fresh-call.json +81 -0
- package/scripts/mutants/omp-birth.json +16 -3
- package/scripts/mutants/release-gate.json +45 -1
- package/scripts/omp-bridge-doctor.sh +11 -1
- package/scripts/smoke-mux-fresh-call-live.ts +175 -3
- package/scripts/smoke-omp-bridge-state.sh +5 -2
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Decide whether a push needs the qualification BODY (#103 piece 2).
|
|
3
|
+
#
|
|
4
|
+
# The body re-proves every committed mutant's kill-power and costs ~28 minutes.
|
|
5
|
+
# Across 549 CI runs it produced five reds, and replaying the real push ranges
|
|
6
|
+
# shows this filter would have run the body for all five (#99 stage-2 §1). So a
|
|
7
|
+
# push that cannot have touched the qualification surface skips it -- and the
|
|
8
|
+
# surface is READ FROM THE MANIFESTS, never copied into a list that can drift.
|
|
9
|
+
#
|
|
10
|
+
# TWO ENTRYPOINTS, ONE MATCHER:
|
|
11
|
+
# ci-qualify-decide.sh <before-sha> <head-sha> # CI and replay: a git range
|
|
12
|
+
# ci-qualify-decide.sh --files-from <file|-> # a literal changed-file list
|
|
13
|
+
# Both print `run_body=true|false` on stdout and their reasoning on stderr.
|
|
14
|
+
#
|
|
15
|
+
# Pure local git plus python3 for the manifest read: no gh, no network, so the
|
|
16
|
+
# replay fixture gate runs offline and exercises this exact code.
|
|
17
|
+
set -euo pipefail
|
|
18
|
+
|
|
19
|
+
REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
20
|
+
|
|
21
|
+
# Fail-open is numbered so a log line says WHICH one fired. Every one of these
|
|
22
|
+
# means "we cannot compute an honest diff", and the honest answer to that is to
|
|
23
|
+
# run the body, never to skip it.
|
|
24
|
+
fail_open() {
|
|
25
|
+
echo "ci-qualify-decide: fail-open $1 ($2) -> run_body=true" >&2
|
|
26
|
+
echo "run_body=true"
|
|
27
|
+
exit 0
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
collect_paths_from_manifests() {
|
|
31
|
+
python3 - "$REPO_DIR" <<'PY'
|
|
32
|
+
import glob
|
|
33
|
+
import json
|
|
34
|
+
import os
|
|
35
|
+
import sys
|
|
36
|
+
|
|
37
|
+
repo = sys.argv[1]
|
|
38
|
+
out = set()
|
|
39
|
+
for path in sorted(glob.glob(os.path.join(repo, "scripts/mutants/*.json"))):
|
|
40
|
+
with open(path, encoding="utf-8") as handle:
|
|
41
|
+
manifest = json.load(handle)
|
|
42
|
+
for mutant in manifest.get("mutants", []):
|
|
43
|
+
subject = mutant.get("subject")
|
|
44
|
+
if subject:
|
|
45
|
+
out.add(subject)
|
|
46
|
+
signature_source = mutant.get("signatureSource")
|
|
47
|
+
if signature_source:
|
|
48
|
+
out.add(signature_source)
|
|
49
|
+
for entry in sorted(out):
|
|
50
|
+
print(entry)
|
|
51
|
+
PY
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
# A changed path matters when it is a mutant's subject or signature source, or
|
|
55
|
+
# when it sits in the machinery that decides what those mutants prove.
|
|
56
|
+
matches_qualification_surface() {
|
|
57
|
+
local file="$1"
|
|
58
|
+
local subject
|
|
59
|
+
while IFS= read -r subject; do
|
|
60
|
+
[ "$file" = "$subject" ] && { echo "manifest-subject"; return 0; }
|
|
61
|
+
done <<< "$MANIFEST_PATHS"
|
|
62
|
+
case "$file" in
|
|
63
|
+
scripts/mutants/*) echo "glob:scripts/mutants/**"; return 0 ;;
|
|
64
|
+
scripts/check-*) echo "glob:scripts/check-*"; return 0 ;;
|
|
65
|
+
scripts/lib/*) echo "glob:scripts/lib/**"; return 0 ;;
|
|
66
|
+
# The replay fixture is the independent oracle input cell 8e reads. Once a
|
|
67
|
+
# claim rests on it, changing it changes what the body is qualified against.
|
|
68
|
+
scripts/fixtures/*) echo "glob:scripts/fixtures/**"; return 0 ;;
|
|
69
|
+
.github/workflows/*) echo "glob:.github/workflows/**"; return 0 ;;
|
|
70
|
+
run.sh) echo "exact:run.sh"; return 0 ;;
|
|
71
|
+
package.json) echo "exact:package.json"; return 0 ;;
|
|
72
|
+
# The decider itself: change the filter and the body runs, so a filter
|
|
73
|
+
# that stopped covering something cannot hide behind its own change.
|
|
74
|
+
scripts/ci-qualify-decide.sh) echo "exact:scripts/ci-qualify-decide.sh"; return 0 ;;
|
|
75
|
+
esac
|
|
76
|
+
return 1
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
decide_from_files() {
|
|
80
|
+
local files="$1"
|
|
81
|
+
local count=0
|
|
82
|
+
local hits=0
|
|
83
|
+
local file rule
|
|
84
|
+
while IFS= read -r file; do
|
|
85
|
+
[ -n "$file" ] || continue
|
|
86
|
+
count=$((count + 1))
|
|
87
|
+
if rule="$(matches_qualification_surface "$file")"; then
|
|
88
|
+
hits=$((hits + 1))
|
|
89
|
+
[ "$hits" -le 5 ] && echo "ci-qualify-decide: hit $file ($rule)" >&2
|
|
90
|
+
fi
|
|
91
|
+
done <<< "$files"
|
|
92
|
+
if [ "$hits" -gt 0 ]; then
|
|
93
|
+
echo "ci-qualify-decide: $hits of $count changed files touch the qualification surface -> run_body=true" >&2
|
|
94
|
+
echo "run_body=true"
|
|
95
|
+
else
|
|
96
|
+
echo "ci-qualify-decide: no qualification-surface path among $count changed files -> run_body=false" >&2
|
|
97
|
+
echo "run_body=false"
|
|
98
|
+
fi
|
|
99
|
+
}
|
|
100
|
+
|
|
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
|
+
if [ "${1:-}" = "--files-from" ]; then
|
|
105
|
+
SRC="${2:-}"
|
|
106
|
+
[ -n "$SRC" ] || { echo "ABORT: --files-from needs a path or -" >&2; exit 1; }
|
|
107
|
+
if [ "$SRC" = "-" ]; then FILES="$(cat)"; else FILES="$(cat "$SRC")"; fi
|
|
108
|
+
decide_from_files "$FILES"
|
|
109
|
+
exit 0
|
|
110
|
+
fi
|
|
111
|
+
|
|
112
|
+
BEFORE="${1:-}"
|
|
113
|
+
HEAD_SHA="${2:-}"
|
|
114
|
+
EVENT="${CI_EVENT_NAME:-push}"
|
|
115
|
+
FORCED="${CI_FORCED:-false}"
|
|
116
|
+
|
|
117
|
+
# 3. A human or the schedule asked for this run; there is no push range to read.
|
|
118
|
+
# `qualify` is what makes the dispatch an explicit request for the BODY. A
|
|
119
|
+
# dispatch WITHOUT it is a floor-only rerun, so it must not silently satisfy
|
|
120
|
+
# the release oracle's fourth axis -- the input would be dead configuration if
|
|
121
|
+
# every dispatch ran the body regardless of its value.
|
|
122
|
+
case "$EVENT" in
|
|
123
|
+
workflow_dispatch)
|
|
124
|
+
[ "${CI_QUALIFY:-false}" = "true" ] && fail_open 3 "event=workflow_dispatch with qualify=true asked for the body"
|
|
125
|
+
echo "ci-qualify-decide: dispatch without qualify=true is a floor-only rerun; the release oracle will still refuse this SHA until the body runs -> run_body=false" >&2
|
|
126
|
+
echo "run_body=false"
|
|
127
|
+
exit 0
|
|
128
|
+
;;
|
|
129
|
+
schedule) fail_open 3 "event=schedule runs the body unconditionally (drift ceiling)" ;;
|
|
130
|
+
# 5. A pull_request event carries no `before`, and GitHub compares it
|
|
131
|
+
# three-dot. Narrowing PRs would need a second comparison rule for an
|
|
132
|
+
# event this repo barely uses, so PRs always run the body.
|
|
133
|
+
pull_request) fail_open 5 "event=pull_request has no two-dot push range" ;;
|
|
134
|
+
esac
|
|
135
|
+
|
|
136
|
+
[ -n "$BEFORE" ] && [ -n "$HEAD_SHA" ] || { echo "ABORT: usage: $0 <before-sha> <head-sha>" >&2; exit 1; }
|
|
137
|
+
|
|
138
|
+
# 1. A new branch's first push: GitHub sends all-zeros, and its own documented
|
|
139
|
+
# base ("the parent of the ancestor of the deepest commit pushed") is not
|
|
140
|
+
# what a two-dot diff from zeros would give.
|
|
141
|
+
case "$BEFORE" in
|
|
142
|
+
*[!0]*) ;;
|
|
143
|
+
*) fail_open 1 "before=$BEFORE is the all-zero SHA (new branch)" ;;
|
|
144
|
+
esac
|
|
145
|
+
|
|
146
|
+
# 2. Force push. What `before` even means here is UNDOCUMENTED (#99 stage-2,
|
|
147
|
+
# "measured not"): if it is the old head, the two-dot diff runs backwards and
|
|
148
|
+
# the verdict can invert. Never guess -- run the body.
|
|
149
|
+
[ "$FORCED" = "true" ] && fail_open 2 "the push was forced; its two-dot base is undocumented"
|
|
150
|
+
|
|
151
|
+
# 4. The range is unreadable on this checkout -- a shallow clone, or an object
|
|
152
|
+
# the remote no longer has (which is also how a force push's old base
|
|
153
|
+
# disappears, so 2 and 4 can both be true and each still names itself).
|
|
154
|
+
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
|
+
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
|
+
|
|
157
|
+
CHANGED="$(git -C "$REPO_DIR" diff --name-only "$BEFORE".."$HEAD_SHA" 2>/dev/null)" || fail_open 4 "git diff $BEFORE..$HEAD_SHA failed"
|
|
158
|
+
|
|
159
|
+
decide_from_files "$CHANGED"
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
{
|
|
2
|
+
"note": "Every qualification RED this repo's CI has ever produced (#99 stage-2 SS1), with the file list of the two-dot push range GitHub actually compared. `before` is the head of the previous ci.yml run on the same branch. `files` was measured from this clone's history on 2026-09-06 and is committed BECAUSE history is not readable everywhere the gate runs: check-gate-qualification executes gates inside a snapshot with its own fresh git baseline, where these commits do not exist. `tipOnlyFiles` is the same push read the wrong way -- the tip commit alone -- which is how four of these five once looked like docs-only pushes; it is kept as the record of the misreading, not as an input.",
|
|
3
|
+
"runs": [
|
|
4
|
+
{
|
|
5
|
+
"runId": "32421631735",
|
|
6
|
+
"branch": "issue-82-copilot-citizen",
|
|
7
|
+
"before": "1da40c9994d5",
|
|
8
|
+
"head": "dff731d415d5",
|
|
9
|
+
"diedAt": "manifest validation (head)",
|
|
10
|
+
"files": [
|
|
11
|
+
"DELIVERY.md",
|
|
12
|
+
"NEXT--issue-82-copilot-citizen.md",
|
|
13
|
+
"ROADMAP.md",
|
|
14
|
+
"mcp/entwurf-bridge/tsconfig.build.json",
|
|
15
|
+
"package.json",
|
|
16
|
+
"pi-extensions/lib/meta-session.ts",
|
|
17
|
+
"pi-extensions/meta-bridge-hook-copilot.ts",
|
|
18
|
+
"pi/entwurf-capabilities.json",
|
|
19
|
+
"pi/meta-bridge-copilot/.claude-plugin/marketplace.json",
|
|
20
|
+
"pi/meta-bridge-copilot/entwurf-meta-receive-copilot/.claude-plugin/plugin.json",
|
|
21
|
+
"pi/meta-bridge-copilot/entwurf-meta-receive-copilot/hooks/hooks.json",
|
|
22
|
+
"pi/meta-bridge-copilot/entwurf-meta-receive-copilot/scripts/copilot-hook-launch.sh",
|
|
23
|
+
"run.sh",
|
|
24
|
+
"scripts/check-copilot-birth-hook.ts",
|
|
25
|
+
"scripts/check-entwurf-capabilities.ts",
|
|
26
|
+
"scripts/check-gate-qualification.ts",
|
|
27
|
+
"scripts/check-meta-doctor-oracle.sh",
|
|
28
|
+
"scripts/check-meta-manifest-schema.py",
|
|
29
|
+
"scripts/copilot-bridge-doctor.sh",
|
|
30
|
+
"scripts/copilot-bridge-install.sh",
|
|
31
|
+
"scripts/meta-bridge-hook-log.sh",
|
|
32
|
+
"scripts/mutants/copilot-birth.json",
|
|
33
|
+
"scripts/tsconfig.json",
|
|
34
|
+
"tsconfig.json"
|
|
35
|
+
],
|
|
36
|
+
"tipOnlyFiles": ["NEXT--issue-82-copilot-citizen.md"]
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"runId": "32450955048",
|
|
40
|
+
"branch": "issue-82-copilot-citizen",
|
|
41
|
+
"before": "0aed67df2f22",
|
|
42
|
+
"head": "88d0641b001e",
|
|
43
|
+
"diedAt": "lane inventory (head)",
|
|
44
|
+
"files": [
|
|
45
|
+
"AGENTS.md",
|
|
46
|
+
"NEXT--issue-82-copilot-citizen.md",
|
|
47
|
+
"VERIFY.md",
|
|
48
|
+
"docs/adding-a-harness.md",
|
|
49
|
+
"docs/external-mcp-host.md",
|
|
50
|
+
"package.json",
|
|
51
|
+
"pi-extensions/lib/meta-sender-identity.ts",
|
|
52
|
+
"pi-extensions/lib/meta-session.ts",
|
|
53
|
+
"pi-extensions/meta-bridge-hook-copilot.ts",
|
|
54
|
+
"pi/meta-bridge-copilot/entwurf-meta-receive-copilot/.claude-plugin/plugin.json",
|
|
55
|
+
"pi/meta-bridge-copilot/entwurf-meta-receive-copilot/scripts/copilot-hook-launch.sh",
|
|
56
|
+
"run.sh",
|
|
57
|
+
"scripts/check-copilot-birth-hook.ts",
|
|
58
|
+
"scripts/check-meta-session.ts",
|
|
59
|
+
"scripts/copilot-bridge-doctor.sh",
|
|
60
|
+
"scripts/copilot-mcp-bridge.sh",
|
|
61
|
+
"scripts/copilot-mcp-config.py",
|
|
62
|
+
"scripts/copilot-statusline-bridge.sh",
|
|
63
|
+
"scripts/copilot-statusline-config.py",
|
|
64
|
+
"scripts/mutants/copilot-birth.json",
|
|
65
|
+
"scripts/smoke-copilot-mcp-state.sh",
|
|
66
|
+
"scripts/smoke-copilot-statusline-state.sh"
|
|
67
|
+
],
|
|
68
|
+
"tipOnlyFiles": [
|
|
69
|
+
"AGENTS.md",
|
|
70
|
+
"NEXT--issue-82-copilot-citizen.md",
|
|
71
|
+
"docs/adding-a-harness.md",
|
|
72
|
+
"docs/external-mcp-host.md",
|
|
73
|
+
"pi-extensions/lib/meta-sender-identity.ts",
|
|
74
|
+
"pi-extensions/meta-bridge-hook-copilot.ts",
|
|
75
|
+
"pi/meta-bridge-copilot/entwurf-meta-receive-copilot/.claude-plugin/plugin.json",
|
|
76
|
+
"pi/meta-bridge-copilot/entwurf-meta-receive-copilot/scripts/copilot-hook-launch.sh",
|
|
77
|
+
"run.sh",
|
|
78
|
+
"scripts/check-copilot-birth-hook.ts",
|
|
79
|
+
"scripts/copilot-bridge-doctor.sh",
|
|
80
|
+
"scripts/mutants/copilot-birth.json"
|
|
81
|
+
]
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"runId": "33154263432",
|
|
85
|
+
"branch": "issue-87-omp-vendor-measurement",
|
|
86
|
+
"before": "7c414f55bd69",
|
|
87
|
+
"head": "9b70a4938fd0",
|
|
88
|
+
"diedAt": "lane inventory (head)",
|
|
89
|
+
"files": [
|
|
90
|
+
"NEXT.md",
|
|
91
|
+
"mcp/entwurf-bridge/src/index.ts",
|
|
92
|
+
"package.json",
|
|
93
|
+
"pi-extensions/lib/entwurf-self-address.ts",
|
|
94
|
+
"run.sh",
|
|
95
|
+
"scripts/check-entwurf-self-address.ts",
|
|
96
|
+
"scripts/mutants/self-address.json",
|
|
97
|
+
"scripts/omp-mcp-bridge.sh",
|
|
98
|
+
"scripts/omp-tool-surface.py",
|
|
99
|
+
"scripts/smoke-omp-mcp-state.sh"
|
|
100
|
+
],
|
|
101
|
+
"tipOnlyFiles": ["NEXT.md"]
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"runId": "30991752256",
|
|
105
|
+
"branch": "mux-placement",
|
|
106
|
+
"before": "e5689808c9ba",
|
|
107
|
+
"head": "e05af7294724",
|
|
108
|
+
"diedAt": "CONTROL pre-red",
|
|
109
|
+
"files": [
|
|
110
|
+
"AGENTS.md",
|
|
111
|
+
"NEXT--mux-placement.md",
|
|
112
|
+
"README.md",
|
|
113
|
+
"ROADMAP.md",
|
|
114
|
+
"VERIFY.md",
|
|
115
|
+
"docs/mux-launch-rail.md",
|
|
116
|
+
"mcp/entwurf-bridge/src/index.ts",
|
|
117
|
+
"package.json",
|
|
118
|
+
"pi-extensions/entwurf-control.ts",
|
|
119
|
+
"pi-extensions/lib/entwurf-v2-contract.ts",
|
|
120
|
+
"pi-extensions/lib/mux-fresh-call.ts",
|
|
121
|
+
"pi-extensions/lib/mux-launch.ts",
|
|
122
|
+
"pi-extensions/lib/mux-placement.ts",
|
|
123
|
+
"run.sh",
|
|
124
|
+
"scripts/check-gate-qualification.ts",
|
|
125
|
+
"scripts/check-mux-fresh-call.ts",
|
|
126
|
+
"scripts/check-mux-launch.ts",
|
|
127
|
+
"scripts/check-release-gate-outcomes.ts",
|
|
128
|
+
"scripts/mutants/mux-fresh-call.json",
|
|
129
|
+
"scripts/smoke-mux-fresh-call-live.ts",
|
|
130
|
+
"tsconfig.json"
|
|
131
|
+
],
|
|
132
|
+
"tipOnlyFiles": ["NEXT--mux-placement.md"]
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
"runId": "31062287903",
|
|
136
|
+
"branch": "mux-placement",
|
|
137
|
+
"before": "e05af7294724",
|
|
138
|
+
"head": "79edfe7738a4",
|
|
139
|
+
"diedAt": "CONTROL pre-red",
|
|
140
|
+
"files": [
|
|
141
|
+
"demo/demo-baseline.sh",
|
|
142
|
+
"demo/demo.sh",
|
|
143
|
+
"scripts/lib/mutation-qualify.ts",
|
|
144
|
+
"scripts/new-session-id.ts"
|
|
145
|
+
],
|
|
146
|
+
"tipOnlyFiles": ["demo/demo-baseline.sh", "demo/demo.sh", "scripts/new-session-id.ts"]
|
|
147
|
+
}
|
|
148
|
+
]
|
|
149
|
+
}
|
|
@@ -12,6 +12,32 @@
|
|
|
12
12
|
"timeoutSeconds": 180,
|
|
13
13
|
"signature": "[QK:FRESH-CUT-COLLISION-NO-MOVE]",
|
|
14
14
|
"signatureSource": "scripts/check-fresh-cut-gate.sh"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"claim": "FRESHCUT-SYNCAUTH-TRIPWIRE-FIRES",
|
|
18
|
+
"title": "the retired-credential tripwire goes blind again. This replants the ACTUAL defect rather than a stand-in: under pipefail a `-q` grep exits at the first match and SIGPIPEs the awk still writing ~200KB of a 433KB run.sh, so the pipeline reports 141 and the `if` takes the CLEAN branch — the cell reported no sync_auth surface EXACTLY when one existed. It was deterministic here (100/100 measured) because the producer dwarfs the pipe buffer, which is why the self-test plants its surface near the TOP of the real run.sh: a small synthetic fixture would pass under this mutant and prove nothing",
|
|
19
|
+
"subject": "scripts/check-fresh-cut-gate.sh",
|
|
20
|
+
"find": [" awk '!/^[[:space:]]*#/' \"$1\" | grep -c 'sync_auth' || true"],
|
|
21
|
+
"replace": [" awk '!/^[[:space:]]*#/' \"$1\" | grep -q 'sync_auth' && echo 1 || echo 0"],
|
|
22
|
+
"gate": ["bash", "run.sh", "check-fresh-cut-gate"],
|
|
23
|
+
"timeoutSeconds": 180,
|
|
24
|
+
"signature": "[QK:FRESHCUT-SYNCAUTH-TRIPWIRE-FIRES]",
|
|
25
|
+
"signatureSource": "scripts/check-fresh-cut-gate.sh"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"claim": "FRESHCUT-PREFLIGHT-CUT-DETECTED",
|
|
29
|
+
"title": "the cell forbidding an install from cutting a generation by itself stops SEEING the call it forbids — the context window silently narrows to the matched line, so a fresh-cut invocation one line into preflight_v3_store() reads as absent and D7 stays green. A forbidding cell that is green on a clean tree cannot tell blindness from compliance, which is what the self-test exists to separate",
|
|
30
|
+
"subject": "scripts/check-fresh-cut-gate.sh",
|
|
31
|
+
"find": [
|
|
32
|
+
" grep -n 'preflight_v3_store()' -A 40 \"$1\" | grep -cE 'run_ts scripts/meta-bridge-fresh-cut\\.ts' || true"
|
|
33
|
+
],
|
|
34
|
+
"replace": [
|
|
35
|
+
" grep -n 'preflight_v3_store()' -A 0 \"$1\" | grep -cE 'run_ts scripts/meta-bridge-fresh-cut\\.ts' || true"
|
|
36
|
+
],
|
|
37
|
+
"gate": ["bash", "run.sh", "check-fresh-cut-gate"],
|
|
38
|
+
"timeoutSeconds": 180,
|
|
39
|
+
"signature": "[QK:FRESHCUT-PREFLIGHT-CUT-DETECTED]",
|
|
40
|
+
"signatureSource": "scripts/check-fresh-cut-gate.sh"
|
|
15
41
|
}
|
|
16
42
|
]
|
|
17
43
|
}
|
|
@@ -191,6 +191,30 @@
|
|
|
191
191
|
"timeoutSeconds": 60,
|
|
192
192
|
"signature": "[QK:MUX-LAUNCH-CORE-IMPORT-FREE]",
|
|
193
193
|
"signatureSource": "scripts/check-mux-launch.ts"
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
"claim": "MUX-CLOSE-SERVER-BOUND",
|
|
197
|
+
"title": "the close-side predicate accepts a foreign SERVER — a restarted server hands out the same `@id`, and a close would kill whatever window now wears it",
|
|
198
|
+
"subject": "pi-extensions/lib/mux-placement.ts",
|
|
199
|
+
"find": ["\treturn origin.serverPid === now.serverPid;\n}"],
|
|
200
|
+
"replace": ["\treturn true;\n}"],
|
|
201
|
+
"gate": ["bash", "run.sh", "check-mux-placement"],
|
|
202
|
+
"timeoutSeconds": 120,
|
|
203
|
+
"signature": "[QK:MUX-CLOSE-SERVER-BOUND]",
|
|
204
|
+
"signatureSource": "scripts/check-mux-placement.ts"
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
"claim": "RESOLVE-TMUX-SESSION-IMPORT-FREE",
|
|
208
|
+
"title": "the session-lookup leaf reaches into the placement leaf for runTmux — the one decision leaf that must not be able to run tmux acquires exactly that power, and the injected-runner seam its whole safety argument rests on stops being the only way in",
|
|
209
|
+
"subject": "pi-extensions/lib/resolve-tmux-session.ts",
|
|
210
|
+
"find": ["export type TmuxSessionRejectReason = \"tmux-session-name-invalid\" | \"tmux-session-missing\";"],
|
|
211
|
+
"replace": [
|
|
212
|
+
"import { runTmux } from \"./mux-placement.ts\";\nvoid runTmux;\nexport type TmuxSessionRejectReason = \"tmux-session-name-invalid\" | \"tmux-session-missing\";"
|
|
213
|
+
],
|
|
214
|
+
"gate": ["bash", "run.sh", "check-mux-launch"],
|
|
215
|
+
"timeoutSeconds": 120,
|
|
216
|
+
"signature": "[QK:RESOLVE-TMUX-SESSION-IMPORT-FREE]",
|
|
217
|
+
"signatureSource": "scripts/check-mux-launch.ts"
|
|
194
218
|
}
|
|
195
219
|
]
|
|
196
220
|
}
|
|
@@ -438,6 +438,87 @@
|
|
|
438
438
|
"timeoutSeconds": 180,
|
|
439
439
|
"signature": "[QK:FRESHCALL-BACKEND-SET-SURFACE-PARITY-PI]",
|
|
440
440
|
"signatureSource": "test/fresh-call-surfaces.contract.test.ts"
|
|
441
|
+
},
|
|
442
|
+
{
|
|
443
|
+
"claim": "TMUXSESSION-LOOKUP-ENGINE",
|
|
444
|
+
"title": "the seat lookup goes back to the `-f` filter engine, which was measured to match EVERY session when the name contains a '}' — a fresh sibling then opens in whichever session tmux listed first",
|
|
445
|
+
"subject": "pi-extensions/lib/resolve-tmux-session.ts",
|
|
446
|
+
"find": ["\treturn [\"list-windows\", \"-t\", `=${name}`, \"-F\", \"#{session_id}\"];"],
|
|
447
|
+
"replace": ["\treturn [\"list-sessions\", \"-f\", `#{==:#{session_name},${name}}`, \"-F\", \"#{session_id}\"];"],
|
|
448
|
+
"gate": ["bash", "run.sh", "check-mux-fresh-call"],
|
|
449
|
+
"timeoutSeconds": 180,
|
|
450
|
+
"signature": "[QK:TMUXSESSION-LOOKUP-ENGINE]",
|
|
451
|
+
"signatureSource": "test/mux-fresh-call.test.ts"
|
|
452
|
+
},
|
|
453
|
+
{
|
|
454
|
+
"claim": "TMUXSESSION-NAME-GRAMMAR",
|
|
455
|
+
"title": "the seat name grammar is widened to 'anything non-empty' — tmux then stores a DIFFERENT name than the one requested (or splits it on '.'/':') and the seat can never be found, reported as 'missing' instead of as a shape this rail does not address",
|
|
456
|
+
"subject": "pi-extensions/lib/resolve-tmux-session.ts",
|
|
457
|
+
"find": ["const SESSION_NAME = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;"],
|
|
458
|
+
"replace": ["const SESSION_NAME = /^.+$/;"],
|
|
459
|
+
"gate": ["bash", "run.sh", "check-mux-fresh-call"],
|
|
460
|
+
"timeoutSeconds": 180,
|
|
461
|
+
"signature": "[QK:TMUXSESSION-NAME-GRAMMAR]",
|
|
462
|
+
"signatureSource": "test/mux-fresh-call.test.ts"
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
"claim": "FRESHCALL-PLACEMENT-MISSING-REJECTS",
|
|
466
|
+
"title": "an absent seat is read as success — tmux's exit code is ignored, so a missing session falls through to the caller's own session and the launch looks like the seat worked. Proof is in two halves: this mutant dies on the leaf's injected-runner assert plus the source-text ordering assert in the fast lane, and the behavioural oracle independent of that source is the real-tmux seat cell in check-mux-launch-tmux (refusal by name with a byte-identical list-windows -a before and after)",
|
|
467
|
+
"subject": "pi-extensions/lib/resolve-tmux-session.ts",
|
|
468
|
+
"find": ["\tif (result.status !== 0) return { ok: false, reason: \"tmux-session-missing\" };"],
|
|
469
|
+
"replace": [
|
|
470
|
+
"\tif (result.status !== 0 && result.stdout.length > 0) return { ok: false, reason: \"tmux-session-missing\" };"
|
|
471
|
+
],
|
|
472
|
+
"gate": ["bash", "run.sh", "check-mux-fresh-call"],
|
|
473
|
+
"timeoutSeconds": 180,
|
|
474
|
+
"signature": "[QK:FRESHCALL-PLACEMENT-MISSING-REJECTS]",
|
|
475
|
+
"signatureSource": "test/mux-fresh-call.test.ts"
|
|
476
|
+
},
|
|
477
|
+
{
|
|
478
|
+
"claim": "FRESHCALL-PLACEMENT-TARGET-ARGV",
|
|
479
|
+
"title": "the launch argv targets the caller's own session again — a seated fresh call silently opens beside the caller instead of in the operator's project session, and the receipt still looks right",
|
|
480
|
+
"subject": "pi-extensions/lib/mux-fresh-call.ts",
|
|
481
|
+
"find": ["\t\t`${targetSessionId}:{end}`,"],
|
|
482
|
+
"replace": ["\t\t`$0:{end}`,"],
|
|
483
|
+
"gate": ["bash", "run.sh", "check-mux-fresh-call"],
|
|
484
|
+
"timeoutSeconds": 180,
|
|
485
|
+
"signature": "[QK:FRESHCALL-PLACEMENT-TARGET-ARGV]",
|
|
486
|
+
"signatureSource": "test/mux-fresh-call.test.ts"
|
|
487
|
+
},
|
|
488
|
+
{
|
|
489
|
+
"claim": "FRESHCALL-PLACEMENT-BACKEND-PARITY",
|
|
490
|
+
"title": "the project seat becomes a pi capability — the grammar refusal is gated on the backend, so claude-code, copilot and omp fall past the seat check into runtime resolution and answer with the wrong reason. Every seat assertion that existed when #105 landed was written against backend pi alone, so nothing would have noticed",
|
|
491
|
+
"subject": "pi-extensions/lib/mux-fresh-call.ts",
|
|
492
|
+
"find": ["\t\tif (badSeat) return { ok: false, reason: badSeat };"],
|
|
493
|
+
"replace": ["\t\tif (badSeat && params.backend === \"pi\") return { ok: false, reason: badSeat };"],
|
|
494
|
+
"gate": ["bash", "run.sh", "check-mux-fresh-call"],
|
|
495
|
+
"timeoutSeconds": 180,
|
|
496
|
+
"signature": "[QK:FRESHCALL-PLACEMENT-BACKEND-PARITY]",
|
|
497
|
+
"signatureSource": "test/mux-fresh-call.test.ts"
|
|
498
|
+
},
|
|
499
|
+
{
|
|
500
|
+
"claim": "FRESHCALL-PLACEMENT-RECEIPT-TARGET",
|
|
501
|
+
"title": "the receipt copies the CALLER's session id again — the window is in the seat, the receipt names the caller's session, and every later close/lookup built on that receipt addresses the wrong session. Proof is in two halves: this mutant dies on the source-text assembly assert in the fast lane, and the behavioural oracle independent of that source is the real-tmux seat cell in check-mux-launch-tmux (receipt.sessionId === seatId !== callerId, read from the server's own inventory)",
|
|
502
|
+
"subject": "pi-extensions/lib/mux-fresh-call.ts",
|
|
503
|
+
"find": ["\t\t\tsessionId: targetSessionId,"],
|
|
504
|
+
"replace": ["\t\t\tsessionId: placement.sessionId,"],
|
|
505
|
+
"gate": ["bash", "run.sh", "check-mux-fresh-call"],
|
|
506
|
+
"timeoutSeconds": 180,
|
|
507
|
+
"signature": "[QK:FRESHCALL-PLACEMENT-RECEIPT-TARGET]",
|
|
508
|
+
"signatureSource": "test/mux-fresh-call.test.ts"
|
|
509
|
+
},
|
|
510
|
+
{
|
|
511
|
+
"claim": "FRESHCALL-PLACEMENT-SURFACE-PARITY",
|
|
512
|
+
"title": "one surface drops the optional seat from its schema, so opening a sibling in the operator's project session works from pi and silently cannot exist from the MCP bridge",
|
|
513
|
+
"subject": "mcp/entwurf-bridge/src/index.ts",
|
|
514
|
+
"find": [
|
|
515
|
+
"\t\tplacement: z\n\t\t\t.object({\n\t\t\t\ttmuxSession: z\n\t\t\t\t\t.string()\n\t\t\t\t\t.describe(\n\t\t\t\t\t\t\"EXACT name of an EXISTING session on this agent's own tmux server. Nothing is created: an absent session is refused as tmux-session-missing, and a name outside [A-Za-z0-9][A-Za-z0-9_-]* as tmux-session-name-invalid.\",\n\t\t\t\t\t),\n\t\t\t})\n\t\t\t.optional()\n\t\t\t.describe(\n\t\t\t\t\"Optional project seat: open the sibling in ONE EXISTING tmux session of this agent's own server instead of the caller's session. Nothing is ever created — an absent session is a refusal, not a new session. Independent of cwd; neither is inferred from the other. The receipt echoes the REQUESTED name and reports the resolved target session id.\",\n\t\t\t),\n"
|
|
516
|
+
],
|
|
517
|
+
"replace": [""],
|
|
518
|
+
"gate": ["bash", "run.sh", "check-mux-fresh-call"],
|
|
519
|
+
"timeoutSeconds": 180,
|
|
520
|
+
"signature": "[QK:FRESHCALL-PLACEMENT-SURFACE-PARITY]",
|
|
521
|
+
"signatureSource": "test/fresh-call-surfaces.contract.test.ts"
|
|
441
522
|
}
|
|
442
523
|
]
|
|
443
524
|
}
|
|
@@ -158,16 +158,29 @@
|
|
|
158
158
|
},
|
|
159
159
|
{
|
|
160
160
|
"claim": "OMP-DOCTOR-CARRIER-TRIMS",
|
|
161
|
-
"title": "treating an empty or whitespace-only PI carrier as an inherited identity makes Bundle C's deliberate tmux `-e NAME=` scrub fail the deterministic floor whenever a visible fresh omp citizen is alive",
|
|
161
|
+
"title": "treating an empty or whitespace-only PI carrier as an inherited identity makes Bundle C's deliberate tmux `-e NAME=` scrub fail the deterministic floor whenever a visible fresh omp citizen is alive. Re-anchored when the carrier scan moved from an early-exiting `grep -q` to `grep -c` (the SIGPIPE/pipefail repair); the replanted defect is unchanged — only the line it attaches to moved",
|
|
162
162
|
"subject": "scripts/omp-bridge-doctor.sh",
|
|
163
163
|
"find": [
|
|
164
|
-
"
|
|
164
|
+
" HITS=\"$(tr '\\0' '\\n' < \"$ENVIRON\" 2>/dev/null | grep -cE '^(PI_SESSION_ID|PI_AGENT_ID)=.*[^[:space:]]' || true)\""
|
|
165
|
+
],
|
|
166
|
+
"replace": [
|
|
167
|
+
" HITS=\"$(tr '\\0' '\\n' < \"$ENVIRON\" 2>/dev/null | grep -cE '^(PI_SESSION_ID|PI_AGENT_ID)=' || true)\""
|
|
165
168
|
],
|
|
166
|
-
"replace": [" if tr '\\0' '\\n' < \"$ENVIRON\" 2>/dev/null | grep -qE '^(PI_SESSION_ID|PI_AGENT_ID)='; then"],
|
|
167
169
|
"gate": ["bash", "run.sh", "smoke-omp-bridge-state"],
|
|
168
170
|
"timeoutSeconds": 300,
|
|
169
171
|
"signature": "[QK:OMP-DOCTOR-CARRIER-TRIMS]",
|
|
170
172
|
"signatureSource": "scripts/smoke-omp-bridge-state.sh"
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
"claim": "OMP-DOCTOR-CARRIER-NONBLANK-IS-RED",
|
|
176
|
+
"title": "the doctor stops flagging a live omp process that carries a NONBLANK inherited PI_SESSION_ID/PI_AGENT_ID, so a session whose MCP children would speak under the parent pi garden id reads as clean and the doctor prints PASS. The real defect was subtler than this replant, which is why the replant is written this way: `grep -q` exited at the first match, SIGPIPEd `tr` mid-environ, and pipefail turned a MATCH into a failed pipeline — a race on how far `tr` got, misreading ~15% of runs, so a `-q` replant would SURVIVE most of the time and prove nothing. This one makes the same false-clean deterministic",
|
|
177
|
+
"subject": "scripts/omp-bridge-doctor.sh",
|
|
178
|
+
"find": [" if [ \"${HITS:-0}\" -gt 0 ]; then"],
|
|
179
|
+
"replace": [" if [ \"${HITS:-0}\" -gt 99 ]; then"],
|
|
180
|
+
"gate": ["bash", "run.sh", "smoke-omp-bridge-state"],
|
|
181
|
+
"timeoutSeconds": 180,
|
|
182
|
+
"signature": "[QK:OMP-DOCTOR-CARRIER-NONBLANK-IS-RED]",
|
|
183
|
+
"signatureSource": "scripts/smoke-omp-bridge-state.sh"
|
|
171
184
|
}
|
|
172
185
|
]
|
|
173
186
|
}
|
|
@@ -114,7 +114,7 @@
|
|
|
114
114
|
},
|
|
115
115
|
{
|
|
116
116
|
"claim": "CI-FULL-FLOOR-QUALIFIED",
|
|
117
|
-
"title": "the CI check job is downgraded from the full deterministic floor back to the ≤60s everyday core — the #70 tier rename regression, where qualification still runs once on
|
|
117
|
+
"title": "the CI check job is downgraded from the full deterministic floor back to the ≤60s everyday core — the #70 tier rename regression, where qualification still runs once on the pushes the filter sends it but now certifies kill-power over a floor no candidate ships on, and the hermetic/package tiers reach machine time nowhere",
|
|
118
118
|
"subject": ".github/workflows/ci.yml",
|
|
119
119
|
"find": [" - run: pnpm run check:full"],
|
|
120
120
|
"replace": [" - run: pnpm check"],
|
|
@@ -123,6 +123,50 @@
|
|
|
123
123
|
"signature": "[QK:CI-FULL-FLOOR-QUALIFIED]",
|
|
124
124
|
"signatureSource": "scripts/check-release-gate-outcomes.ts"
|
|
125
125
|
},
|
|
126
|
+
{
|
|
127
|
+
"claim": "RELEASE-SHA-QUALIFIED-IN-CI",
|
|
128
|
+
"title": "the exact-SHA CI oracle stops requiring the qualification BODY step, so a release SHA whose body never executed is certified by three green job names",
|
|
129
|
+
"subject": ".claude/skills/entwurf-release/scripts/verify-exact-ci.sh",
|
|
130
|
+
"find": ["if qual != \"success\":"],
|
|
131
|
+
"replace": ["if False:"],
|
|
132
|
+
"gate": ["bash", "run.sh", "check-release-gate-outcomes"],
|
|
133
|
+
"timeoutSeconds": 180,
|
|
134
|
+
"signature": "[QK:RELEASE-SHA-QUALIFIED-IN-CI]",
|
|
135
|
+
"signatureSource": "scripts/check-release-gate-outcomes.ts"
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
"claim": "QUALIFY-FILTER-COVERS-SUBJECTS",
|
|
139
|
+
"title": "the CI qualification filter stops collecting signatureSource paths, so a change to a gate that owns a claim's signature no longer re-proves it in CI",
|
|
140
|
+
"subject": "scripts/ci-qualify-decide.sh",
|
|
141
|
+
"find": ["\t\tsignature_source = mutant.get(\"signatureSource\")"],
|
|
142
|
+
"replace": ["\t\tsignature_source = None"],
|
|
143
|
+
"gate": ["bash", "run.sh", "check-release-gate-outcomes"],
|
|
144
|
+
"timeoutSeconds": 180,
|
|
145
|
+
"signature": "[QK:QUALIFY-FILTER-COVERS-SUBJECTS]",
|
|
146
|
+
"signatureSource": "scripts/check-release-gate-outcomes.ts"
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
"claim": "QUALIFY-FILTER-READS-PUSH-RANGE",
|
|
150
|
+
"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",
|
|
151
|
+
"subject": "scripts/ci-qualify-decide.sh",
|
|
152
|
+
"find": ["CHANGED=\"$(git -C \"$REPO_DIR\" diff --name-only \"$BEFORE\"..\"$HEAD_SHA\" 2>/dev/null)\""],
|
|
153
|
+
"replace": ["CHANGED=\"$(git -C \"$REPO_DIR\" diff --name-only \"$HEAD_SHA~1\"..\"$HEAD_SHA\" 2>/dev/null)\""],
|
|
154
|
+
"gate": ["bash", "run.sh", "check-release-gate-outcomes"],
|
|
155
|
+
"timeoutSeconds": 180,
|
|
156
|
+
"signature": "[QK:QUALIFY-FILTER-READS-PUSH-RANGE]",
|
|
157
|
+
"signatureSource": "scripts/check-release-gate-outcomes.ts"
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
"claim": "QUALIFY-FILTER-REPLAYS-PAST-CATCHES",
|
|
161
|
+
"title": "the matcher stops after the first changed path, so a push whose first file is outside the qualification surface decides false no matter what rides behind it - invisible to every single-file check, fatal to all five historical ranges",
|
|
162
|
+
"subject": "scripts/ci-qualify-decide.sh",
|
|
163
|
+
"find": ["\tdone <<< \"$files\""],
|
|
164
|
+
"replace": ["\tdone <<< \"${files%%$'\\n'*}\""],
|
|
165
|
+
"gate": ["bash", "run.sh", "check-release-gate-outcomes"],
|
|
166
|
+
"timeoutSeconds": 180,
|
|
167
|
+
"signature": "[QK:QUALIFY-FILTER-REPLAYS-PAST-CATCHES]",
|
|
168
|
+
"signatureSource": "scripts/check-release-gate-outcomes.ts"
|
|
169
|
+
},
|
|
126
170
|
{
|
|
127
171
|
"claim": "MUX-LIFECYCLE-IS-RELEASE-MUST",
|
|
128
172
|
"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",
|
|
@@ -266,7 +266,17 @@ if [ -d /proc ]; then
|
|
|
266
266
|
case "$pid" in *[!0-9]*) continue ;; esac
|
|
267
267
|
ENVIRON="/proc/$pid/environ"
|
|
268
268
|
[ -r "$ENVIRON" ] || continue
|
|
269
|
-
|
|
269
|
+
# `grep -c`, never `grep -q`. This file runs under `set -o pipefail` (line 26), and a
|
|
270
|
+
# `-q` grep EXITS AT THE FIRST MATCH, closing the pipe while `tr` is still writing the
|
|
271
|
+
# rest of an ~12KB environ. `tr` dies of SIGPIPE (141), pipefail promotes that to the
|
|
272
|
+
# pipeline's status, and the `if` takes the CLEAN branch — so a genuinely contaminated
|
|
273
|
+
# process was reported as clean, and the doctor printed PASS. It is a RACE on how far
|
|
274
|
+
# `tr` got, which is why it read as flakiness: measured on this host, 6 misses in 40
|
|
275
|
+
# reads of one live fixture carrying PI_SESSION_ID=foreign-garden. `-c` consumes all of
|
|
276
|
+
# stdin, so there is no early close and nothing to race. The same lesson is already
|
|
277
|
+
# written at smoke-omp-mcp-state.sh:91 — it had not reached this reader.
|
|
278
|
+
HITS="$(tr '\0' '\n' < "$ENVIRON" 2>/dev/null | grep -cE '^(PI_SESSION_ID|PI_AGENT_ID)=.*[^[:space:]]' || true)"
|
|
279
|
+
if [ "${HITS:-0}" -gt 0 ]; then
|
|
270
280
|
CONTAMINATED="$CONTAMINATED $pid"
|
|
271
281
|
fi
|
|
272
282
|
done
|