@junghanacs/entwurf 0.36.1 → 0.37.1
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 +301 -0
- package/CONTRIBUTING.md +1 -1
- package/README.md +5 -0
- package/VERIFY.md +26 -17
- package/docs/setup-clean-host.md +1 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-pi-durable.js +16 -1
- package/package.json +4 -3
- package/pi/pi-durable/bootstrap.mjs +113 -11
- package/pi/pi-durable/carrier/harness-setup.js +4 -1
- package/pi/pi-durable/carrier/prompt.js +31 -2
- package/pi/pi-durable/carrier/runtime.js +11 -2
- package/pi/pi-durable/carrier/sessions.js +75 -5
- package/pi/pi-durable/carrier/tui.js +23 -0
- package/pi/pi-durable/overlay/upstream-pin.json +10 -10
- package/pi-extensions/meta-bridge-pi-durable.ts +20 -0
- package/run.sh +44 -17
- package/scripts/check-gate-qualification.ts +3 -3
- package/scripts/check-release-gate-outcomes.ts +200 -387
- package/scripts/fixtures/herdr-supply.json +11 -10
- package/scripts/lib/codex-fresh-live-protocol.ts +6 -1
- package/scripts/lib/qualification-ledger.ts +2 -2
- package/scripts/mutants/herdr-supply.json +5 -5
- package/scripts/mutants/pi-durable.json +117 -45
- package/scripts/mutants/release-gate.json +18 -52
- package/scripts/smoke-mux-lifecycle-live.ts +1 -1
- package/scripts/ci-qualify-decide.sh +0 -256
- package/scripts/fixtures/qualify-replay.json +0 -149
|
@@ -126,73 +126,39 @@
|
|
|
126
126
|
"signatureSource": "scripts/check-release-gate-outcomes.ts"
|
|
127
127
|
},
|
|
128
128
|
{
|
|
129
|
-
"claim": "RELEASE-
|
|
130
|
-
"title": "the exact-SHA CI oracle stops
|
|
129
|
+
"claim": "RELEASE-WITNESS-IS-PUSH-RUN",
|
|
130
|
+
"title": "the exact-SHA CI oracle stops asking GitHub for push runs only, so a running or red qualification dispatch at the same SHA can become the run judged",
|
|
131
131
|
"subject": ".claude/skills/entwurf-release/scripts/verify-exact-ci.sh",
|
|
132
|
-
"find": ["
|
|
133
|
-
"replace": ["
|
|
132
|
+
"find": [" --commit \"$SHA\" \\", " --event push \\"],
|
|
133
|
+
"replace": [" --commit \"$SHA\" \\"],
|
|
134
134
|
"gate": ["bash", "run.sh", "check-release-gate-outcomes"],
|
|
135
135
|
"timeoutSeconds": 180,
|
|
136
|
-
"signature": "[QK:RELEASE-
|
|
136
|
+
"signature": "[QK:RELEASE-WITNESS-IS-PUSH-RUN]",
|
|
137
137
|
"signatureSource": "scripts/check-release-gate-outcomes.ts"
|
|
138
138
|
},
|
|
139
139
|
{
|
|
140
|
-
"claim": "
|
|
141
|
-
"title": "the CI
|
|
142
|
-
"subject": "scripts/
|
|
143
|
-
"find": ["
|
|
144
|
-
"replace": ["
|
|
140
|
+
"claim": "RELEASE-ORACLE-REFUSES-NON-SUCCESS-JOB",
|
|
141
|
+
"title": "the exact-SHA CI oracle refuses a required job only when it failed, so an absent, skipped, cancelled or unconcluded job certifies the release SHA",
|
|
142
|
+
"subject": ".claude/skills/entwurf-release/scripts/verify-exact-ci.sh",
|
|
143
|
+
"find": ["if conclusion != \"success\":"],
|
|
144
|
+
"replace": ["if conclusion == \"failure\":"],
|
|
145
145
|
"gate": ["bash", "run.sh", "check-release-gate-outcomes"],
|
|
146
146
|
"timeoutSeconds": 180,
|
|
147
|
-
"signature": "[QK:
|
|
147
|
+
"signature": "[QK:RELEASE-ORACLE-REFUSES-NON-SUCCESS-JOB]",
|
|
148
148
|
"signatureSource": "scripts/check-release-gate-outcomes.ts"
|
|
149
149
|
},
|
|
150
150
|
{
|
|
151
|
-
"claim": "
|
|
152
|
-
"title": "the
|
|
153
|
-
"subject": "scripts/
|
|
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
|
-
],
|
|
151
|
+
"claim": "RELEASE-ORACLE-CLAIMS-NO-BODY",
|
|
152
|
+
"title": "the exact-SHA CI oracle prints a qualification BODY success line again, so a release PASS reads as kill-power evidence the release never measured",
|
|
153
|
+
"subject": ".claude/skills/entwurf-release/scripts/verify-exact-ci.sh",
|
|
154
|
+
"find": ["print(\"exact-ci: qualification BODY is not a release axis (#144)\")"],
|
|
157
155
|
"replace": [
|
|
158
|
-
"
|
|
156
|
+
"print(\"exact-ci: qualification BODY is not a release axis (#144)\")",
|
|
157
|
+
"print(\"exact-ci: step check/Run ./run.sh check-gate-qualification=success\")"
|
|
159
158
|
],
|
|
160
159
|
"gate": ["bash", "run.sh", "check-release-gate-outcomes"],
|
|
161
160
|
"timeoutSeconds": 180,
|
|
162
|
-
"signature": "[QK:
|
|
163
|
-
"signatureSource": "scripts/check-release-gate-outcomes.ts"
|
|
164
|
-
},
|
|
165
|
-
{
|
|
166
|
-
"claim": "QUALIFY-FILTER-REPLAYS-PAST-CATCHES",
|
|
167
|
-
"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",
|
|
168
|
-
"subject": "scripts/ci-qualify-decide.sh",
|
|
169
|
-
"find": ["\tdone <<< \"$files\""],
|
|
170
|
-
"replace": ["\tdone <<< \"${files%%$'\\n'*}\""],
|
|
171
|
-
"gate": ["bash", "run.sh", "check-release-gate-outcomes"],
|
|
172
|
-
"timeoutSeconds": 180,
|
|
173
|
-
"signature": "[QK:QUALIFY-FILTER-REPLAYS-PAST-CATCHES]",
|
|
174
|
-
"signatureSource": "scripts/check-release-gate-outcomes.ts"
|
|
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]",
|
|
161
|
+
"signature": "[QK:RELEASE-ORACLE-CLAIMS-NO-BODY]",
|
|
196
162
|
"signatureSource": "scripts/check-release-gate-outcomes.ts"
|
|
197
163
|
},
|
|
198
164
|
{
|
|
@@ -108,7 +108,7 @@ const BRIDGE_LAUNCHER = path.join(REPO, "mcp", "entwurf-bridge", "start.sh");
|
|
|
108
108
|
|
|
109
109
|
/** The two pi shapes. They differ in exactly one thing that matters here — whether the record
|
|
110
110
|
* carries `provider=entwurf`, which is what makes the resume argv re-inject the bridge (#29). */
|
|
111
|
-
const PI_NATIVE_MODEL = "openai-codex/gpt-6-
|
|
111
|
+
const PI_NATIVE_MODEL = "openai-codex/gpt-6.1-sol:low";
|
|
112
112
|
const PI_ACP_MODEL = "entwurf/claude-sonnet-5-5";
|
|
113
113
|
const CLAUDE_MODEL = "claude-sonnet-5";
|
|
114
114
|
|
|
@@ -1,256 +0,0 @@
|
|
|
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" "${1:-}" <<'PY'
|
|
32
|
-
import glob
|
|
33
|
-
import json
|
|
34
|
-
import os
|
|
35
|
-
import sys
|
|
36
|
-
import subprocess
|
|
37
|
-
|
|
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)
|
|
53
|
-
for mutant in manifest.get("mutants", []):
|
|
54
|
-
subject = mutant.get("subject")
|
|
55
|
-
if subject:
|
|
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)
|
|
61
|
-
signature_source = mutant.get("signatureSource")
|
|
62
|
-
if signature_source:
|
|
63
|
-
out.add(signature_source)
|
|
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}))
|
|
68
|
-
PY
|
|
69
|
-
}
|
|
70
|
-
|
|
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.
|
|
136
|
-
matches_qualification_surface() {
|
|
137
|
-
local file="$1"
|
|
138
|
-
local subject
|
|
139
|
-
while IFS= read -r subject; do
|
|
140
|
-
[ "$file" = "$subject" ] && { echo "manifest-signature-source"; return 0; }
|
|
141
|
-
done <<< "$SIGNATURE_PATHS"
|
|
142
|
-
case "$file" in
|
|
143
|
-
scripts/mutants/*) echo "glob:scripts/mutants/**"; return 0 ;;
|
|
144
|
-
scripts/check-*) echo "glob:scripts/check-*"; return 0 ;;
|
|
145
|
-
scripts/lib/*) echo "glob:scripts/lib/**"; return 0 ;;
|
|
146
|
-
# The replay fixture is the independent oracle input cell 8e reads. Once a
|
|
147
|
-
# claim rests on it, changing it changes what the body is qualified against.
|
|
148
|
-
scripts/fixtures/*) echo "glob:scripts/fixtures/**"; return 0 ;;
|
|
149
|
-
.github/workflows/*) echo "glob:.github/workflows/**"; return 0 ;;
|
|
150
|
-
run.sh) echo "exact:run.sh"; return 0 ;;
|
|
151
|
-
package.json) echo "exact:package.json"; return 0 ;;
|
|
152
|
-
# The decider itself: change the filter and the body runs, so a filter
|
|
153
|
-
# that stopped covering something cannot hide behind its own change.
|
|
154
|
-
scripts/ci-qualify-decide.sh) echo "exact:scripts/ci-qualify-decide.sh"; return 0 ;;
|
|
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"
|
|
170
|
-
return 1
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
decide_from_files() {
|
|
174
|
-
local files="$1"
|
|
175
|
-
local count=0
|
|
176
|
-
local hits=0
|
|
177
|
-
local file rule
|
|
178
|
-
while IFS= read -r file; do
|
|
179
|
-
[ -n "$file" ] || continue
|
|
180
|
-
count=$((count + 1))
|
|
181
|
-
if rule="$(matches_qualification_surface "$file")"; then
|
|
182
|
-
hits=$((hits + 1))
|
|
183
|
-
[ "$hits" -le 5 ] && echo "ci-qualify-decide: hit $file ($rule)" >&2
|
|
184
|
-
fi
|
|
185
|
-
done <<< "$files"
|
|
186
|
-
if [ "$hits" -gt 0 ]; then
|
|
187
|
-
echo "ci-qualify-decide: $hits of $count changed files touch the qualification surface -> run_body=true" >&2
|
|
188
|
-
echo "run_body=true"
|
|
189
|
-
else
|
|
190
|
-
echo "ci-qualify-decide: no qualification-surface path among $count changed files -> run_body=false" >&2
|
|
191
|
-
echo "run_body=false"
|
|
192
|
-
fi
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
if [ "${1:-}" = "--files-from" ]; then
|
|
196
|
-
MATCH_MODE=files
|
|
197
|
-
load_manifest_surface
|
|
198
|
-
SRC="${2:-}"
|
|
199
|
-
[ -n "$SRC" ] || { echo "ABORT: --files-from needs a path or -" >&2; exit 1; }
|
|
200
|
-
if [ "$SRC" = "-" ]; then FILES="$(cat)"; else FILES="$(cat "$SRC")"; fi || fail_open 4 "unreadable files-from input"
|
|
201
|
-
decide_from_files "$FILES"
|
|
202
|
-
exit 0
|
|
203
|
-
fi
|
|
204
|
-
|
|
205
|
-
BEFORE="${1:-}"
|
|
206
|
-
HEAD_SHA="${2:-}"
|
|
207
|
-
EVENT="${CI_EVENT_NAME:-push}"
|
|
208
|
-
FORCED="${CI_FORCED:-false}"
|
|
209
|
-
|
|
210
|
-
# 3. A human or the schedule asked for this run; there is no push range to read.
|
|
211
|
-
# `qualify` is what makes the dispatch an explicit request for the BODY. A
|
|
212
|
-
# dispatch WITHOUT it is a floor-only rerun, so it must not silently satisfy
|
|
213
|
-
# the release oracle's fourth axis -- the input would be dead configuration if
|
|
214
|
-
# every dispatch ran the body regardless of its value.
|
|
215
|
-
case "$EVENT" in
|
|
216
|
-
workflow_dispatch)
|
|
217
|
-
[ "${CI_QUALIFY:-false}" = "true" ] && fail_open 3 "event=workflow_dispatch with qualify=true asked for the body"
|
|
218
|
-
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
|
|
219
|
-
echo "run_body=false"
|
|
220
|
-
exit 0
|
|
221
|
-
;;
|
|
222
|
-
schedule) fail_open 3 "event=schedule runs the body unconditionally (drift ceiling)" ;;
|
|
223
|
-
# 5. A pull_request event carries no `before`, and GitHub compares it
|
|
224
|
-
# three-dot. Narrowing PRs would need a second comparison rule for an
|
|
225
|
-
# event this repo barely uses, so PRs always run the body.
|
|
226
|
-
pull_request) fail_open 5 "event=pull_request has no two-dot push range" ;;
|
|
227
|
-
esac
|
|
228
|
-
|
|
229
|
-
[ -n "$BEFORE" ] && [ -n "$HEAD_SHA" ] || { echo "ABORT: usage: $0 <before-sha> <head-sha>" >&2; exit 1; }
|
|
230
|
-
|
|
231
|
-
# 1. A new branch's first push: GitHub sends all-zeros, and its own documented
|
|
232
|
-
# base ("the parent of the ancestor of the deepest commit pushed") is not
|
|
233
|
-
# what a two-dot diff from zeros would give.
|
|
234
|
-
case "$BEFORE" in
|
|
235
|
-
*[!0]*) ;;
|
|
236
|
-
*) fail_open 1 "before=$BEFORE is the all-zero SHA (new branch)" ;;
|
|
237
|
-
esac
|
|
238
|
-
|
|
239
|
-
# 2. Force push. What `before` even means here is UNDOCUMENTED (#99 stage-2,
|
|
240
|
-
# "measured not"): if it is the old head, the two-dot diff runs backwards and
|
|
241
|
-
# the verdict can invert. Never guess -- run the body.
|
|
242
|
-
[ "$FORCED" = "true" ] && fail_open 2 "the push was forced; its two-dot base is undocumented"
|
|
243
|
-
|
|
244
|
-
# 4. The range is unreadable on this checkout -- a shallow clone, or an object
|
|
245
|
-
# the remote no longer has (which is also how a force push's old base
|
|
246
|
-
# disappears, so 2 and 4 can both be true and each still names itself).
|
|
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"
|
|
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"
|
|
249
|
-
|
|
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
|
|
255
|
-
|
|
256
|
-
decide_from_files "$CHANGED"
|
|
@@ -1,149 +0,0 @@
|
|
|
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
|
-
}
|