@ccoalm/ccl-skills 0.14.0 → 0.15.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/dist/assets/marketplace/plugins/ccl-skills/packages/opencode-plugin/ccl-skills.ts +80 -4
- package/dist/assets/marketplace/plugins/ccl-skills/packages/opencode-plugin/commands/ccl-install-skills.md +16 -4
- package/dist/assets/marketplace/plugins/ccl-skills/scripts/owner-dispatch/owner-dispatch.sh +13 -2
- package/dist/assets/marketplace/plugins/ccl-skills/scripts/owner-dispatch/test.sh +53 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/SKILL.md +19 -24
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/references/client-routing.md +32 -32
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/references/manual-invocation-and-prompts.md +16 -14
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/references/staged-review-contract.md +24 -26
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/AGENTS.md +11 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/claude_review.sh +60 -209
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/init_policy_matrix.py +114 -367
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/parse_probe_result.py +52 -672
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/review_gate.py +17 -3
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/runtime-surface-verification-design.md +4 -2
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_claude_review_probe.sh +77 -444
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_init_policy_matrix.sh +33 -98
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_parse_probe_result.sh +57 -173
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_review_gate.sh +65 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/defect-diagnosis/SKILL.md +1 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/grill-me/SKILL.md +1 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/miniapp-product-dev/SKILL.md +1 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-observability/SKILL.md +5 -5
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-observability/references/alerting-and-on-call.md +8 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-release-engineering/SKILL.md +1 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/SKILL.md +14 -14
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/dual-sidecar-and-traffic-config-center.md +1 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/grpc-authority-workaround.md +40 -83
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/mesh-architecture.md +2 -2
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/retry-timeout-circuit-breaker.md +44 -37
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/service-discovery-recipe.md +1 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/SKILL.md +2 -2
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/delivery-lifecycle.md +1 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/rd-standards-doc-family-checklist.md +2 -2
- package/dist/assets/marketplace/plugins/ccl-skills/skills/requirement-baseline/SKILL.md +1 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/requirement-doc-writer/SKILL.md +1 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/requirement-scope/SKILL.md +9 -6
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/eval-routing.md +6 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/external-practice-controls.md +4 -4
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/source-register.md +52 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/eval-golden-trace.rb +31 -7
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/eval-routing-bank.rb +62 -3
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/impact-chain-gate.rb +188 -13
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/review_ledger_binding.py +36 -13
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/skill-behavior-eval.py +103 -21
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_impact_chain_refscripts.sh +261 -14
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_regressions.sh +2 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_eval_routing_bank_resolution.sh +253 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_eval_runtime.py +428 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_impact_chain_gate_verdict_differential.sh +49 -25
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_review_ledger_binding.sh +63 -5
- package/dist/assets/release.json +76 -56
- package/dist/claude-adapter.js +14 -7
- package/dist/codex-host.d.ts +1 -3
- package/dist/codex-host.js +6 -9
- package/dist/host-probe.d.ts +11 -0
- package/dist/host-probe.js +27 -0
- package/dist/opencode-adapter.js +24 -19
- package/dist/unified.js +11 -9
- package/package.json +1 -1
|
@@ -48,6 +48,10 @@ printf '# fixture slug-named reference\n\nNeutral placeholder for the inner-rena
|
|
|
48
48
|
# and isolate the prose-only guard from the reproduction check.
|
|
49
49
|
printf '# fixture slug mention: platform-observability\n' >> "$REPO/skills/product-rd-workflow/scripts/check-agent-contract-coverage.sh"
|
|
50
50
|
printf '\nFixture eligible sibling mention: platform-observability.\n' >> "$REPO/skills/product-rd-workflow/references/adr-convention.md"
|
|
51
|
+
# A real table baseline distinguishes a changed definition from an old anchor
|
|
52
|
+
# surviving an unrelated source-link edit on the same line.
|
|
53
|
+
TABLE_REL="skills/product-rd-workflow/references/zz-fixture-definition.md"
|
|
54
|
+
printf '# Definitions\n\n| Metric | Definition | Source |\n| --- | --- | --- |\n| Incident deployment ratio | Deployments needing later work | old-source |\n' > "$REPO/$TABLE_REL"
|
|
51
55
|
git -C "$REPO" add -A
|
|
52
56
|
git -C "$REPO" commit -qm "seed throwaway upstream reference"
|
|
53
57
|
git -C "$REPO" branch fixture-base HEAD
|
|
@@ -108,6 +112,15 @@ run_gate() {
|
|
|
108
112
|
rc=$?
|
|
109
113
|
set -e
|
|
110
114
|
}
|
|
115
|
+
# Same gate, explicit base: for fixtures whose claim is about WHICH base the
|
|
116
|
+
# verdict was taken against, pinning it beats inferring it from the upstream.
|
|
117
|
+
run_gate_with_base() { # <base ref>
|
|
118
|
+
gate_runs=$((gate_runs + 1))
|
|
119
|
+
set +e
|
|
120
|
+
out="$(env -u ALIAS_AUDIT_CMD CCL_SKILL_BASE_REF="$1" ruby "$GATE_SCRIPT" "$REPO" 2>&1)"
|
|
121
|
+
rc=$?
|
|
122
|
+
set -e
|
|
123
|
+
}
|
|
111
124
|
|
|
112
125
|
# Case 1: an upstream owner's REFERENCE changed with NO impact-chain row -> block.
|
|
113
126
|
new_case case-ref-no-row
|
|
@@ -145,6 +158,75 @@ run_gate
|
|
|
145
158
|
assert_not_contains "impact_chain_firing_path_missing" "$out" "a never-phrased normative rule should satisfy the firing-path gate"
|
|
146
159
|
assert_rc "$rc" 0 "a never-phrased normative list rule must be accepted"
|
|
147
160
|
|
|
161
|
+
# Definitions are executable guidance even when their Markdown surface is a
|
|
162
|
+
# table instead of a normative list. Negative controls keep anchors bound to
|
|
163
|
+
# new, rendered data cells in the correct owner and round.
|
|
164
|
+
definition_table_case() {
|
|
165
|
+
local label="$1" expected="$2" anchor='Unplanned deployments caused by production incidents'
|
|
166
|
+
local firing_path="$TABLE_REL"
|
|
167
|
+
new_case "case-ref-definition-table-$label"
|
|
168
|
+
case "$label" in
|
|
169
|
+
surviving-anchor)
|
|
170
|
+
anchor='Deployments needing later work'
|
|
171
|
+
perl -pi -e 's/old-source/new-source/' "$REPO/$TABLE_REL" ;;
|
|
172
|
+
unchanged-table)
|
|
173
|
+
anchor='Deployments needing later work'
|
|
174
|
+
printf '\nChanged neighboring prose.\n' >> "$REPO/$TABLE_REL" ;;
|
|
175
|
+
*)
|
|
176
|
+
perl -pi -e 's/Deployments needing later work/Unplanned deployments caused by production incidents/' "$REPO/$TABLE_REL" ;;
|
|
177
|
+
esac
|
|
178
|
+
case "$label" in
|
|
179
|
+
foreign-owner)
|
|
180
|
+
firing_path='skills/terminal-cli-dev/references/zz-fixture-definition.md'
|
|
181
|
+
cp "$REPO/$TABLE_REL" "$REPO/$firing_path" ;;
|
|
182
|
+
duplicate) printf '\n%s\n' "$anchor" >> "$REPO/$TABLE_REL" ;;
|
|
183
|
+
inline-comment) perl -pi -e 's/Unplanned deployments caused by production incidents/<!-- Unplanned deployments caused by production incidents -->/' "$REPO/$TABLE_REL" ;;
|
|
184
|
+
multiline-comment) perl -0777 -pi -e 's/\n\| Metric/\n<!--\n| Metric/; $_ .= "-->\n"' "$REPO/$TABLE_REL" ;;
|
|
185
|
+
backtick-fence) perl -0777 -pi -e 's/\n\| Metric/\n```markdown\n| Metric/; $_ .= "```\n"' "$REPO/$TABLE_REL" ;;
|
|
186
|
+
tilde-fence) perl -0777 -pi -e 's/\n\| Metric/\n~~~markdown\n| Metric/; $_ .= "~~~\n"' "$REPO/$TABLE_REL" ;;
|
|
187
|
+
raw-html) perl -0777 -pi -e 's/\n\| Metric/\n<script>\n\n| Metric/; $_ .= "<\/script>\n"' "$REPO/$TABLE_REL" ;;
|
|
188
|
+
raw-html-open-line) perl -0777 -pi -e 's/\n\| Metric/\n<script\n| Metric/; $_ .= "<\/script>\n"' "$REPO/$TABLE_REL" ;;
|
|
189
|
+
processing-instruction) perl -0777 -pi -e 's/\n\| Metric/\n<?xml\n| Metric/; $_ .= "?>\n"' "$REPO/$TABLE_REL" ;;
|
|
190
|
+
cdata) perl -0777 -pi -e 's/\n\| Metric/\n<![CDATA[\n| Metric/; $_ .= "]]>\n"' "$REPO/$TABLE_REL" ;;
|
|
191
|
+
declaration) perl -0777 -pi -e 's/\n\| Metric/\n<!DOCTYPE\n| Metric/; $_ .= ">\n"' "$REPO/$TABLE_REL" ;;
|
|
192
|
+
closed-cdata) perl -0777 -pi -e 's/\n\| Metric/\n<![CDATA[\nclosed example\n]]>\n\n| Metric/' "$REPO/$TABLE_REL" ;;
|
|
193
|
+
html-block) perl -0777 -pi -e 's/\n\| Metric/\n<div>\n| Metric/; $_ .= "<\/div>\n"' "$REPO/$TABLE_REL" ;;
|
|
194
|
+
indented-code) perl -pi -e 's/^\|/ |/' "$REPO/$TABLE_REL" ;;
|
|
195
|
+
no-header) perl -ni -e 'print unless /^\| (Metric|---)/' "$REPO/$TABLE_REL" ;;
|
|
196
|
+
header-anchor) perl -0777 -pi -e 's/\| Metric \| Definition \| Source \|/| Unplanned deployments caused by production incidents | Definition | Source |/; s/\n\| Incident deployment ratio[^\n]*\n/\n/' "$REPO/$TABLE_REL" ;;
|
|
197
|
+
no-red) : ;;
|
|
198
|
+
esac
|
|
199
|
+
local status='RED-baseline' observed='yes'
|
|
200
|
+
if [ "$label" = no-red ]; then status='semantic-control'; observed='no'; fi
|
|
201
|
+
printf '| Fixture definition table %s | `downstream-executor` | behavioral-evidence: %s; observed-failure: %s; result-class: failure; firing-path: file:%s#%s | `updated` | `product-rd-workflow/SKILL.md` definition correction |\n' "$label" "$status" "$observed" "$firing_path" "$anchor" >> "$REGISTER"
|
|
202
|
+
commit_case "definition table $label"
|
|
203
|
+
run_gate
|
|
204
|
+
assert_rc "$rc" "$expected" "definition table $label"
|
|
205
|
+
if [ "$expected" = 1 ]; then
|
|
206
|
+
if [ "$label" = no-red ]; then
|
|
207
|
+
assert_contains 'impact_chain_behavior_evidence_missing' "$out" "definition table $label: $out"
|
|
208
|
+
else
|
|
209
|
+
assert_contains 'impact_chain_firing_path_missing' "$out" "definition table $label: $out"
|
|
210
|
+
fi
|
|
211
|
+
fi
|
|
212
|
+
}
|
|
213
|
+
definition_table_case accepted 0
|
|
214
|
+
definition_table_case closed-cdata 0
|
|
215
|
+
for definition_control in surviving-anchor unchanged-table foreign-owner duplicate inline-comment multiline-comment backtick-fence tilde-fence raw-html raw-html-open-line processing-instruction cdata declaration html-block indented-code no-header header-anchor no-red; do
|
|
216
|
+
definition_table_case "$definition_control" 1
|
|
217
|
+
done
|
|
218
|
+
|
|
219
|
+
# Evidence is not its own firing surface. Keep the anchor only in the locator
|
|
220
|
+
# itself so uniqueness cannot accidentally reject the self-certifying row.
|
|
221
|
+
new_case case-ref-definition-table-self-register
|
|
222
|
+
printf '\nFixture bookkeeping note, with no new enforcing rule.\n' >> "$REPO/skills/skill-extraction-workflow/references/validation-and-landing.md"
|
|
223
|
+
printf '\n| Rule | Owner | Behavior | Status | Evidence |\n| --- | --- | --- | --- | --- |\n' >> "$REGISTER"
|
|
224
|
+
printf '| Fixture self-citing table | `downstream-executor` | behavioral-evidence: RED-baseline; observed-failure: yes; result-class: failure; firing-path: file:skills/skill-extraction-workflow/references/source-register.md#SELF-REGISTER-DEFINITION-ANCHOR | updated | `skill-extraction-workflow/SKILL.md` bookkeeping edit |\n' >> "$REGISTER"
|
|
225
|
+
commit_case "source register cannot certify its own firing path"
|
|
226
|
+
run_gate
|
|
227
|
+
assert_rc "$rc" 1 "source register must not be its own firing surface"
|
|
228
|
+
assert_contains 'impact_chain_firing_path_missing' "$out" "self-register refusal must name the firing-path boundary"
|
|
229
|
+
|
|
148
230
|
# A purely DESCRIPTIVE list line ("always exposes" — no imperative/prohibitive
|
|
149
231
|
# verb) is not an enforcing rule; the widened verb list must not admit it.
|
|
150
232
|
new_case case-ref-descriptive-always-firing-path
|
|
@@ -1194,12 +1276,13 @@ run_gate
|
|
|
1194
1276
|
assert_rc "$rc" 1 "a duplicated-then-dropped row must not vouch for new owner bytes"
|
|
1195
1277
|
assert_contains "impact_chain_gate_missing" "$out" "the net-zero ledger leaves the owner undeclared"
|
|
1196
1278
|
|
|
1197
|
-
# Round scoping 6: the partition's central claim is that
|
|
1198
|
-
#
|
|
1199
|
-
# so a linear-only fixture set proves nothing about the shape
|
|
1200
|
-
# on.
|
|
1201
|
-
#
|
|
1202
|
-
# cases pin that behaviour instead of asserting
|
|
1279
|
+
# Round scoping 6: the partition's central claim is that a MERGED worktree round is
|
|
1280
|
+
# judged exactly as its branch was — and this repo's integration branch is nothing
|
|
1281
|
+
# but merge commits, so a linear-only fixture set proves nothing about the shape
|
|
1282
|
+
# the gate actually runs on. A merge git rebuilds from its parents is expanded
|
|
1283
|
+
# into the branch's own rounds; work committed on the integration line after it
|
|
1284
|
+
# is a round of its own. These two cases pin that behaviour instead of asserting
|
|
1285
|
+
# it in a comment.
|
|
1203
1286
|
new_case case-round-scope-merged-worktree-round
|
|
1204
1287
|
git -C "$REPO" switch -q -c feature-round-merged
|
|
1205
1288
|
printf '\n- Never skip the fixture merged-round rule for this gate.\n' >> "$REPO/skills/platform-observability/SKILL.md"
|
|
@@ -1270,24 +1353,188 @@ run_gate
|
|
|
1270
1353
|
assert_rc "$rc" 0 "a pre-rename round declared against its own round head must pass"
|
|
1271
1354
|
assert_not_contains "impact_chain_evidence_missing_file" "$out" "a row citing the name that existed at its round head is valid evidence"
|
|
1272
1355
|
|
|
1273
|
-
# Round scoping 8: the
|
|
1274
|
-
# append lands BEFORE the owner work
|
|
1275
|
-
#
|
|
1276
|
-
#
|
|
1277
|
-
#
|
|
1278
|
-
#
|
|
1356
|
+
# Round scoping 8: the merged view IS the branch view. On the branch the ledger
|
|
1357
|
+
# append lands BEFORE the owner work, so as a pull request the branch is refused:
|
|
1358
|
+
# the work sits in a rowless later round. Collapsing the merged branch to one
|
|
1359
|
+
# boundary used to accept the same bytes — what a pull request could not land,
|
|
1360
|
+
# its merge could, and the verdict moved after it landed. A merge git rebuilds
|
|
1361
|
+
# from its parents is now expanded into the branch's own rounds, so the two views
|
|
1362
|
+
# agree; both are run on the same fixture so the equality is a checked fact.
|
|
1279
1363
|
new_case case-round-scope-merged-row-before-work
|
|
1280
1364
|
git -C "$REPO" switch -q -c feature-round-row-first
|
|
1365
|
+
git -C "$REPO" branch --set-upstream-to=fixture-base feature-round-row-first >/dev/null 2>&1
|
|
1281
1366
|
printf '| Fixture row-first merged round | `downstream-executor` | behavioral-evidence: RED-baseline; observed-failure: yes; result-class: failure; bank-evidence: downscoped:REFSCRIPTS-FIXTURE-NO-BANK; firing-path: file:skills/platform-observability/SKILL.md#Never skip the fixture row-first rule | `updated` | `platform-observability/SKILL.md` merged round |\n' >> "$REGISTER"
|
|
1282
1367
|
routing_surface_downscope
|
|
1283
1368
|
commit_case "worktree round: ledger append first"
|
|
1284
1369
|
printf '\n- Never skip the fixture row-first rule for this gate.\n' >> "$REPO/skills/platform-observability/SKILL.md"
|
|
1285
1370
|
routing_surface_downscope
|
|
1286
1371
|
commit_case "worktree round: owner work after the append"
|
|
1372
|
+
run_gate
|
|
1373
|
+
branch_rc="$rc"
|
|
1374
|
+
assert_rc "$rc" 1 "as a pull request, owner work after the ledger append sits in a rowless round"
|
|
1375
|
+
assert_contains "impact_chain_gate_missing" "$out" "the branch view names the undeclared work"
|
|
1287
1376
|
git -C "$REPO" switch -q case-round-scope-merged-row-before-work
|
|
1288
1377
|
git -C "$REPO" merge -q --no-ff -m "Merge branch 'feature-round-row-first': one worktree round" feature-round-row-first
|
|
1289
1378
|
run_gate
|
|
1290
|
-
assert_rc "$rc"
|
|
1379
|
+
assert_rc "$rc" "$branch_rc" "the merge of that branch is judged exactly as the branch was"
|
|
1380
|
+
assert_contains "impact_chain_gate_missing" "$out" "the merged view names the same undeclared work"
|
|
1381
|
+
|
|
1382
|
+
# Round scoping 13: the observed failure. On the branch, round 1 adds a body rule
|
|
1383
|
+
# with its row and round 2 changes ONLY the description with a `#description`
|
|
1384
|
+
# row — as a pull request that is round scoping 2 and passes. Collapsed to one
|
|
1385
|
+
# boundary the owner's merged diff is body plus description, the routing-surface
|
|
1386
|
+
# class refuses the anchor, and a row that was valid when it landed reads
|
|
1387
|
+
# `impact_chain_firing_path_missing` on every post-merge evaluation (observed on
|
|
1388
|
+
# the integration branch's push build and on the promotion pull request, with
|
|
1389
|
+
# nothing about the row changed). Expanded, round 2 is judged on its own bytes.
|
|
1390
|
+
new_case case-round-scope-merged-description-round
|
|
1391
|
+
git -C "$REPO" switch -q -c feature-round-description
|
|
1392
|
+
git -C "$REPO" branch --set-upstream-to=fixture-base feature-round-description >/dev/null 2>&1
|
|
1393
|
+
printf '\n- Never skip the fixture merged-description body rule for this gate.\n' >> "$REPO/skills/product-rd-workflow/SKILL.md"
|
|
1394
|
+
printf '| Fixture merged body round | `downstream-executor` | behavioral-evidence: RED-baseline; observed-failure: yes; result-class: failure; bank-evidence: downscoped:REFSCRIPTS-FIXTURE-NO-BANK; firing-path: file:skills/product-rd-workflow/SKILL.md#Never skip the fixture merged-description body rule | `updated` | `product-rd-workflow/SKILL.md` body round |\n' >> "$REGISTER"
|
|
1395
|
+
routing_surface_downscope
|
|
1396
|
+
commit_case "worktree round 1: body rule added to the entrypoint"
|
|
1397
|
+
perl -0pi -e 's/^(description: .+)$/$1 Fixture merged round-scope routing clause./m' "$REPO/skills/product-rd-workflow/SKILL.md"
|
|
1398
|
+
printf '| Fixture merged description round | `downstream-executor` | behavioral-evidence: RED-baseline; observed-failure: yes; result-class: failure; bank-evidence: downscoped:REFSCRIPTS-FIXTURE-NO-BANK; firing-path: file:skills/product-rd-workflow/SKILL.md#description | `updated` | `product-rd-workflow/SKILL.md` description-only round |\n' >> "$REGISTER"
|
|
1399
|
+
routing_surface_downscope
|
|
1400
|
+
commit_case "worktree round 2: description-only edit to the same owner"
|
|
1401
|
+
run_gate
|
|
1402
|
+
branch_rc="$rc"
|
|
1403
|
+
assert_rc "$rc" 0 "as a pull request, the description-only round keeps its locator after the body round"
|
|
1404
|
+
git -C "$REPO" switch -q case-round-scope-merged-description-round
|
|
1405
|
+
git -C "$REPO" merge -q --no-ff -m "Merge branch 'feature-round-description': two worktree rounds" feature-round-description
|
|
1406
|
+
run_gate
|
|
1407
|
+
assert_rc "$rc" "$branch_rc" "the merge of that branch is judged exactly as the branch was"
|
|
1408
|
+
assert_not_contains "impact_chain_firing_path_missing" "$out" "the description anchor binds in its own round after the merge too"
|
|
1409
|
+
|
|
1410
|
+
# Round scoping 14: expansion is bounded by what git can rebuild. A merge whose
|
|
1411
|
+
# tree is not the automatic merge of its parents carries content that came from
|
|
1412
|
+
# neither branch; expanding it would leave that content in no round, so such a
|
|
1413
|
+
# merge keeps the single boundary at the merge. The row-before-work branch from
|
|
1414
|
+
# round scoping 8, merged with an extra owner edit inside the merge commit, is
|
|
1415
|
+
# therefore judged as one span — which accepts it, as it always did, because that
|
|
1416
|
+
# span holds both the row and the work. The review-ledger binder refuses a
|
|
1417
|
+
# non-automatic merge on a promotion chain, so the hand-carried content is still
|
|
1418
|
+
# caught where promotion is decided; this fixture pins only that the expansion
|
|
1419
|
+
# does not silently widen past git's own reconstruction.
|
|
1420
|
+
new_case case-round-scope-hand-resolved-merge
|
|
1421
|
+
git -C "$REPO" switch -q -c feature-round-hand-resolved
|
|
1422
|
+
printf '| Fixture hand-resolved merged round | `downstream-executor` | behavioral-evidence: RED-baseline; observed-failure: yes; result-class: failure; bank-evidence: downscoped:REFSCRIPTS-FIXTURE-NO-BANK; firing-path: file:skills/platform-observability/SKILL.md#Never skip the fixture hand-resolved rule | `updated` | `platform-observability/SKILL.md` merged round |\n' >> "$REGISTER"
|
|
1423
|
+
routing_surface_downscope
|
|
1424
|
+
commit_case "worktree round: ledger append first"
|
|
1425
|
+
printf '\n- Never skip the fixture hand-resolved rule for this gate.\n' >> "$REPO/skills/platform-observability/SKILL.md"
|
|
1426
|
+
routing_surface_downscope
|
|
1427
|
+
commit_case "worktree round: owner work after the append"
|
|
1428
|
+
git -C "$REPO" switch -q case-round-scope-hand-resolved-merge
|
|
1429
|
+
git -C "$REPO" merge -q --no-ff --no-commit feature-round-hand-resolved >/dev/null 2>&1
|
|
1430
|
+
printf '\nFixture line carried by the merge commit itself.\n' >> "$REPO/skills/platform-observability/references/platform-observability-playbook.md"
|
|
1431
|
+
commit_case "Merge branch 'feature-round-hand-resolved' with a hand-carried edit"
|
|
1432
|
+
run_gate
|
|
1433
|
+
assert_rc "$rc" 0 "a merge git cannot rebuild from its parents keeps one boundary and is not expanded"
|
|
1434
|
+
|
|
1435
|
+
# Round scoping 15: the depth bound fails closed. Nine nested worktree rounds —
|
|
1436
|
+
# each branch cut from the previous one and merged back in turn, every merge the
|
|
1437
|
+
# automatic one — nest expansions past ROUND_WALK_MAX_DEPTH. The walk must stop
|
|
1438
|
+
# with its own diagnostic rather than fall back to a collapsed span, because a
|
|
1439
|
+
# fallback here would be the lenient verdict on exactly the history an author
|
|
1440
|
+
# could construct to reach it.
|
|
1441
|
+
new_case case-round-scope-nesting-depth
|
|
1442
|
+
prev=case-round-scope-nesting-depth
|
|
1443
|
+
for level in 1 2 3 4 5 6 7 8 9; do
|
|
1444
|
+
git -C "$REPO" switch -q -c "feature-nest-$level" "$prev"
|
|
1445
|
+
prev="feature-nest-$level"
|
|
1446
|
+
done
|
|
1447
|
+
printf '\n- Never skip the fixture nested rule for this gate.\n' >> "$REPO/skills/platform-observability/SKILL.md"
|
|
1448
|
+
printf '| Fixture nested round | `downstream-executor` | behavioral-evidence: RED-baseline; observed-failure: yes; result-class: failure; bank-evidence: downscoped:REFSCRIPTS-FIXTURE-NO-BANK; firing-path: file:skills/platform-observability/SKILL.md#Never skip the fixture nested rule | `updated` | `platform-observability/SKILL.md` nested round |\n' >> "$REGISTER"
|
|
1449
|
+
routing_surface_downscope
|
|
1450
|
+
commit_case "deepest worktree round: owner work with its row"
|
|
1451
|
+
for level in 9 8 7 6 5 4 3 2 1; do
|
|
1452
|
+
if [ "$level" -gt 1 ]; then outer="feature-nest-$((level - 1))"; else outer=case-round-scope-nesting-depth; fi
|
|
1453
|
+
git -C "$REPO" switch -q "$outer"
|
|
1454
|
+
git -C "$REPO" merge -q --no-ff -m "Merge branch 'feature-nest-$level'" "feature-nest-$level"
|
|
1455
|
+
done
|
|
1456
|
+
run_gate
|
|
1457
|
+
assert_rc "$rc" 1 "merges nested past the depth bound must fail closed, not fall back to a collapsed span"
|
|
1458
|
+
assert_contains "impact_chain_round_walk_too_deep" "$out" "the depth refusal names itself"
|
|
1459
|
+
|
|
1460
|
+
# Round scoping 16 and 17: git failures inside the expansion decision fail closed.
|
|
1461
|
+
# A `git` shim on PATH delegates everything to the real git except the one
|
|
1462
|
+
# subcommand under test, which it makes fail with an operational status. If the
|
|
1463
|
+
# gate swallowed that status it would silently take the collapsed-span path — the
|
|
1464
|
+
# lenient verdict — so both lookups must abort with impact_chain_git_failed.
|
|
1465
|
+
GIT_REAL="$(command -v git)"
|
|
1466
|
+
SHIM_DIR="$(mktemp -d "${TMPDIR:-/tmp}/icshim.XXXXXX")"
|
|
1467
|
+
make_git_shim() { # <subcommand-to-break> <exit-status>
|
|
1468
|
+
cat > "$SHIM_DIR/git" <<SHIM
|
|
1469
|
+
#!/usr/bin/env bash
|
|
1470
|
+
real="$GIT_REAL"
|
|
1471
|
+
args=("\$@")
|
|
1472
|
+
sub=""
|
|
1473
|
+
i=0
|
|
1474
|
+
while [ \$i -lt \${#args[@]} ]; do
|
|
1475
|
+
case "\${args[\$i]}" in
|
|
1476
|
+
-C) i=\$((i + 2)); continue ;;
|
|
1477
|
+
-*) i=\$((i + 1)); continue ;;
|
|
1478
|
+
*) sub="\${args[\$i]}"; break ;;
|
|
1479
|
+
esac
|
|
1480
|
+
done
|
|
1481
|
+
if [ "\$sub" = "$1" ]; then
|
|
1482
|
+
case "$1" in
|
|
1483
|
+
merge-base) case " \${args[*]} " in *" --is-ancestor "*|*" HEAD "*) exec "\$real" "\$@" ;; esac ;;
|
|
1484
|
+
esac
|
|
1485
|
+
echo "shim: $1 unavailable" >&2
|
|
1486
|
+
exit $2
|
|
1487
|
+
fi
|
|
1488
|
+
exec "\$real" "\$@"
|
|
1489
|
+
SHIM
|
|
1490
|
+
chmod +x "$SHIM_DIR/git"
|
|
1491
|
+
}
|
|
1492
|
+
git -C "$REPO" switch -q case-round-scope-merged-description-round
|
|
1493
|
+
make_git_shim merge-tree 129
|
|
1494
|
+
PATH="$SHIM_DIR:$PATH" run_gate
|
|
1495
|
+
assert_rc "$rc" 1 "an unavailable merge-tree must abort the walk, not skip the expansion"
|
|
1496
|
+
assert_contains "impact_chain_git_failed: git merge-tree --write-tree" "$out" "the refusal names the merge-tree lookup"
|
|
1497
|
+
assert_contains "exited 129" "$out" "the refusal carries the shim status, so it is this lookup and not another git read"
|
|
1498
|
+
make_git_shim merge-base 128
|
|
1499
|
+
PATH="$SHIM_DIR:$PATH" run_gate
|
|
1500
|
+
assert_rc "$rc" 1 "a failing fork-point lookup must abort the walk, not skip the expansion"
|
|
1501
|
+
assert_contains "impact_chain_git_failed: git merge-base" "$out" "the refusal names the fork-point lookup"
|
|
1502
|
+
assert_contains "exited 128" "$out" "the refusal carries the shim status, so it is this lookup (the only two-commit merge-base in the gate) and not the base resolution, which passes HEAD"
|
|
1503
|
+
rm -rf "$SHIM_DIR"
|
|
1504
|
+
|
|
1505
|
+
# Round scoping 18: a sync of the TARGET inside a branch is a sync on that
|
|
1506
|
+
# branch's own line. The target advances under a feature that already carries
|
|
1507
|
+
# owner work; the feature merges the target (automatically) and only then
|
|
1508
|
+
# appends its row. As a pull request the branch passes: the sync brings nothing
|
|
1509
|
+
# new and work plus row share a round. Promoted and judged against the older
|
|
1510
|
+
# base, that sync's second parent is not below the outer base — expanding it
|
|
1511
|
+
# would cut the branch at the sync and strand the work in a rowless span, so the
|
|
1512
|
+
# sync test is made against the line being walked, not the outer base alone.
|
|
1513
|
+
new_case case-round-scope-branch-synced-target
|
|
1514
|
+
git -C "$REPO" switch -q -c feature-synced-target
|
|
1515
|
+
git -C "$REPO" branch --set-upstream-to=case-round-scope-branch-synced-target feature-synced-target >/dev/null 2>&1
|
|
1516
|
+
git -C "$REPO" switch -q case-round-scope-branch-synced-target
|
|
1517
|
+
mkdir -p "$REPO/specs/fixture-refscripts"
|
|
1518
|
+
printf 'Fixture target note unrelated to any owner.\n' >> "$REPO/specs/fixture-refscripts/target-note.md"
|
|
1519
|
+
commit_case "target advances with an unrelated change"
|
|
1520
|
+
SYNC_TARGET_T="$(git -C "$REPO" rev-parse HEAD)"
|
|
1521
|
+
SYNC_TARGET_O="$(git -C "$REPO" rev-parse fixture-base)"
|
|
1522
|
+
git -C "$REPO" switch -q feature-synced-target
|
|
1523
|
+
printf '\n- Never skip the fixture synced-target rule for this gate.\n' >> "$REPO/skills/platform-observability/SKILL.md"
|
|
1524
|
+
routing_surface_downscope
|
|
1525
|
+
commit_case "worktree round: owner work before the sync"
|
|
1526
|
+
git -C "$REPO" merge -q --no-ff -m "Merge branch 'case-round-scope-branch-synced-target' into feature-synced-target" case-round-scope-branch-synced-target
|
|
1527
|
+
printf '| Fixture synced-target round | `downstream-executor` | behavioral-evidence: RED-baseline; observed-failure: yes; result-class: failure; bank-evidence: downscoped:REFSCRIPTS-FIXTURE-NO-BANK; firing-path: file:skills/platform-observability/SKILL.md#Never skip the fixture synced-target rule | `updated` | `platform-observability/SKILL.md` synced round |\n' >> "$REGISTER"
|
|
1528
|
+
routing_surface_downscope
|
|
1529
|
+
commit_case "worktree round: its ledger append after the sync"
|
|
1530
|
+
run_gate_with_base "$SYNC_TARGET_T"
|
|
1531
|
+
branch_rc="$rc"
|
|
1532
|
+
assert_rc "$rc" 0 "as a pull request (base: the advanced target), a branch that synced the target keeps its work and row in one round"
|
|
1533
|
+
git -C "$REPO" switch -q case-round-scope-branch-synced-target
|
|
1534
|
+
git -C "$REPO" merge -q --no-ff -m "Merge branch 'feature-synced-target': one worktree round" feature-synced-target
|
|
1535
|
+
run_gate_with_base "$SYNC_TARGET_O"
|
|
1536
|
+
assert_rc "$rc" "$branch_rc" "promoted against the older base (pinned: the commit the target advanced from), the target sync inside the branch is still a sync and the verdict is the branch's"
|
|
1537
|
+
assert_not_contains "impact_chain_gate_missing" "$out" "the branch's pre-sync work is not stranded by expanding the target sync"
|
|
1291
1538
|
|
|
1292
1539
|
# Round scoping 9: a TRANSIENT intermediate rename hop must not launder work.
|
|
1293
1540
|
# X renames to Y (declared), a later round substantively changes Y with no row,
|
|
@@ -1409,6 +1656,6 @@ assert_rc "$rc" 1 "a directory masquerading as SKILL.md must not read as a prese
|
|
|
1409
1656
|
assert_contains "platform-observability/SKILL.md" "$out" "the masqueraded owner must be named"
|
|
1410
1657
|
|
|
1411
1658
|
assert_rc "$full_check_runs" 1 "fixture suite must retain exactly one full checker wiring case"
|
|
1412
|
-
assert_rc "$gate_runs"
|
|
1659
|
+
assert_rc "$gate_runs" 118 "all remaining impact-chain fixtures must run the standalone gate"
|
|
1413
1660
|
|
|
1414
1661
|
echo "test_check_ccl_impact_chain_refscripts: ok"
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
# - test_check_ccl_register_pending_exclusion.sh
|
|
25
25
|
# - test_eval_routing_bank_grader_diagnostics.sh
|
|
26
26
|
# - test_eval_routing_bank_surface_binding.sh
|
|
27
|
+
# - test_eval_routing_bank_resolution.sh
|
|
27
28
|
# - test_eval_routing_prose_target.sh
|
|
28
29
|
# - test_validate_skill_credential_cwd.sh
|
|
29
30
|
# - test_validate_skill_root_depth.sh
|
|
@@ -145,6 +146,7 @@ fast_tests=(
|
|
|
145
146
|
test_impact_chain_round_attribution.sh
|
|
146
147
|
test_eval_routing_bank_grader_diagnostics.sh
|
|
147
148
|
test_eval_routing_bank_surface_binding.sh
|
|
149
|
+
test_eval_routing_bank_resolution.sh
|
|
148
150
|
test_eval_routing_prose_target.sh
|
|
149
151
|
test_validate_skill_credential_cwd.sh
|
|
150
152
|
test_validate_skill_root_depth.sh
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Regression test for the bank runner's screening-vs-action resolution signal.
|
|
3
|
+
#
|
|
4
|
+
# A report taken below the action floor locates candidates; it does not license
|
|
5
|
+
# a description edit. The runner says so on stdout and in the JSON report, and
|
|
6
|
+
# this test pins BOTH sides of the number — the reference that states the rule
|
|
7
|
+
# and the executable that enforces it — so they cannot drift apart the way the
|
|
8
|
+
# description-length thresholds once did.
|
|
9
|
+
#
|
|
10
|
+
# Uses a fake `claude` earlier in PATH: never invokes a real CLI or account.
|
|
11
|
+
set -euo pipefail
|
|
12
|
+
|
|
13
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)"
|
|
14
|
+
EVAL_SCRIPT="$SCRIPT_DIR/eval-routing-bank.rb"
|
|
15
|
+
DOC="$SCRIPT_DIR/../references/eval-routing.md"
|
|
16
|
+
[ -f "$EVAL_SCRIPT" ] || { echo "FAIL: eval script not found: $EVAL_SCRIPT" >&2; exit 1; }
|
|
17
|
+
[ -f "$DOC" ] || { echo "FAIL: reference not found: $DOC" >&2; exit 1; }
|
|
18
|
+
|
|
19
|
+
fail() { echo "FAIL: $*" >&2; exit 1; }
|
|
20
|
+
assert_contains() { case "$2" in *"$1"*) : ;; *) fail "expected output to contain: $1${3:+ ($3)}";; esac; }
|
|
21
|
+
assert_absent() { case "$2" in *"$1"*) fail "expected output NOT to contain: $1${3:+ ($3)}";; *) : ;; esac; }
|
|
22
|
+
|
|
23
|
+
TMP="$(mktemp -d "${TMPDIR:-/tmp}/eval-routing-bank-resolution.XXXXXX")"
|
|
24
|
+
trap 'rm -rf "$TMP"' EXIT
|
|
25
|
+
|
|
26
|
+
REPO="$TMP/repo"
|
|
27
|
+
FAKE_BIN="$TMP/bin"
|
|
28
|
+
mkdir -p "$REPO/skills/testing-strategy" "$REPO/skills/tighten-doc" "$REPO/eval" "$FAKE_BIN"
|
|
29
|
+
|
|
30
|
+
git -C "$TMP" init -q repo
|
|
31
|
+
git -C "$REPO" config user.email test@example.invalid
|
|
32
|
+
git -C "$REPO" config user.name "Test User"
|
|
33
|
+
|
|
34
|
+
cat > "$REPO/skills/testing-strategy/SKILL.md" <<'EOF'
|
|
35
|
+
---
|
|
36
|
+
description: Use when choosing test layers and regression evidence.
|
|
37
|
+
---
|
|
38
|
+
# Testing Strategy
|
|
39
|
+
EOF
|
|
40
|
+
|
|
41
|
+
# Keeps the outcome space larger than {expected, none} so the fixture is not
|
|
42
|
+
# vacuous: a grader that always answered the only skill would pass trivially.
|
|
43
|
+
cat > "$REPO/skills/tighten-doc/SKILL.md" <<'EOF'
|
|
44
|
+
---
|
|
45
|
+
description: Use when polishing document wording.
|
|
46
|
+
---
|
|
47
|
+
# Tighten Doc
|
|
48
|
+
EOF
|
|
49
|
+
|
|
50
|
+
# TWO cases, deliberately: with one case a per-case verdict and a report-wide
|
|
51
|
+
# conjunction are indistinguishable, so every assertion below would also pass
|
|
52
|
+
# against the defect they exist to catch. The degraded fixture fails only the
|
|
53
|
+
# first utterance, which leaves one case short of the floor and one clear of it.
|
|
54
|
+
cat > "$REPO/eval/routing-tasks.jsonl" <<'EOF'
|
|
55
|
+
{"id":"resolution-fixture","utterance":"补一个回归测试","expected_skill":"testing-strategy","frozen_at_sha":""}
|
|
56
|
+
{"id":"resolution-fixture-b","utterance":"帮我润色这段文档","expected_skill":"tighten-doc","frozen_at_sha":""}
|
|
57
|
+
EOF
|
|
58
|
+
|
|
59
|
+
git -C "$REPO" add -A
|
|
60
|
+
git -C "$REPO" commit -qm "fixture"
|
|
61
|
+
|
|
62
|
+
cat > "$FAKE_BIN/claude" <<'EOF'
|
|
63
|
+
#!/usr/bin/env bash
|
|
64
|
+
prompt="$(cat)"
|
|
65
|
+
case "$prompt" in
|
|
66
|
+
*润色*) printf '{"selected_skill":"tighten-doc","clarify":false,"confidence":0.9,"rationale_short":"fixture"}\n' ;;
|
|
67
|
+
*) printf '{"selected_skill":"testing-strategy","clarify":false,"confidence":0.9,"rationale_short":"fixture"}\n' ;;
|
|
68
|
+
esac
|
|
69
|
+
EOF
|
|
70
|
+
chmod +x "$FAKE_BIN/claude"
|
|
71
|
+
export PATH="$FAKE_BIN:$PATH"
|
|
72
|
+
|
|
73
|
+
# --- (1) below the floor: banner printed, report says action_resolution false --
|
|
74
|
+
out_lo="$(ruby "$EVAL_SCRIPT" "$REPO" --replicas 3 --json "$TMP/lo.json" 2>&1)" \
|
|
75
|
+
|| fail "runner exited non-zero below the floor:\n$out_lo"
|
|
76
|
+
assert_contains "screening_resolution_only" "$out_lo" "sub-floor run must announce screening resolution"
|
|
77
|
+
assert_contains "replicas=3" "$out_lo" "banner must name the observed replica count"
|
|
78
|
+
assert_contains "floor 10" "$out_lo" "banner must name the required floor"
|
|
79
|
+
assert_contains "valid observations" "$out_lo" "banner must report the weakest task's valid-observation count, not the request alone"
|
|
80
|
+
grep -q '"action_resolution": false' "$TMP/lo.json" \
|
|
81
|
+
|| fail "sub-floor report must carry action_resolution:false — a consumer cannot read a printed banner"
|
|
82
|
+
|
|
83
|
+
# --- (2) at the floor: no banner, report says action_resolution true -----------
|
|
84
|
+
out_hi="$(ruby "$EVAL_SCRIPT" "$REPO" --replicas 10 --json "$TMP/hi.json" 2>&1)" \
|
|
85
|
+
|| fail "runner exited non-zero at the floor:\n$out_hi"
|
|
86
|
+
assert_absent "screening_resolution_only" "$out_hi" "a run at the floor must not be labelled screening-only"
|
|
87
|
+
grep -q '"action_resolution": true' "$TMP/hi.json" \
|
|
88
|
+
|| fail "at-floor report must carry action_resolution:true"
|
|
89
|
+
|
|
90
|
+
# --- (2b) a nominal at-floor run with an invalid observation is NOT actionable -
|
|
91
|
+
# The floor is on valid observations. A grader that fails one call leaves the
|
|
92
|
+
# task below the floor while `--replicas` still reads 10, and a report that
|
|
93
|
+
# trusted the request would license an edit its evidence cannot support.
|
|
94
|
+
# The runner gives each unparsable verdict ONE retry, treating it as a sampling
|
|
95
|
+
# accident, so a fixture that fails a single call is repaired and records no
|
|
96
|
+
# error. The failure has to persist across the retry to leave the task short of
|
|
97
|
+
# the floor -- which is exactly the real shape this guards: a grader that is
|
|
98
|
+
# reliably unable to answer one utterance, not a stray quote.
|
|
99
|
+
cat > "$FAKE_BIN/claude" <<'EOF'
|
|
100
|
+
#!/usr/bin/env bash
|
|
101
|
+
prompt="$(cat)"
|
|
102
|
+
case "$prompt" in
|
|
103
|
+
*润色*) printf '{"selected_skill":"tighten-doc","clarify":false,"confidence":0.9,"rationale_short":"fixture"}\n'; exit 0 ;;
|
|
104
|
+
esac
|
|
105
|
+
echo x >> "$RESOLUTION_COUNT_FILE"
|
|
106
|
+
n=$(wc -l < "$RESOLUTION_COUNT_FILE" | tr -d ' ')
|
|
107
|
+
if [ "$n" = "3" ] || [ "$n" = "4" ]; then printf 'not json at all\n'; exit 0; fi
|
|
108
|
+
printf '{"selected_skill":"testing-strategy","clarify":false,"confidence":0.9,"rationale_short":"fixture"}\n'
|
|
109
|
+
EOF
|
|
110
|
+
chmod +x "$FAKE_BIN/claude"
|
|
111
|
+
: > "$TMP/count"
|
|
112
|
+
out_deg="$(RESOLUTION_COUNT_FILE="$TMP/count" ruby "$EVAL_SCRIPT" "$REPO" --replicas 10 --json "$TMP/deg.json" 2>&1)" \
|
|
113
|
+
|| fail "runner exited non-zero on the degraded run:\n$out_deg"
|
|
114
|
+
assert_contains "screening_resolution_only" "$out_deg" "a nominal 10-replica run with an invalid observation must not be actionable"
|
|
115
|
+
grep -q '"action_resolution": false' "$TMP/deg.json" \
|
|
116
|
+
|| fail "a 10-replica run with only 9 valid observations must report action_resolution:false"
|
|
117
|
+
grep -q '"min_valid_observations": 9' "$TMP/deg.json" \
|
|
118
|
+
|| fail "the report must expose the weakest task's valid-observation count"
|
|
119
|
+
|
|
120
|
+
# --- (2c) the verdict is per case, and says nothing about a case not measured --
|
|
121
|
+
# A report-wide boolean alone is unsound in the licensing direction: a subset run
|
|
122
|
+
# over one case would otherwise read as licence for an edit to a case the run
|
|
123
|
+
# never graded. Each result carries its own actionable and valid_observations.
|
|
124
|
+
grep -q '"actionable": true' "$TMP/hi.json" \
|
|
125
|
+
|| fail "an at-floor case must carry its own actionable:true"
|
|
126
|
+
grep -q '"valid_observations": 10' "$TMP/hi.json" \
|
|
127
|
+
|| fail "each case must expose its own valid-observation count"
|
|
128
|
+
grep -q '"action_resolution_scope"' "$TMP/hi.json" \
|
|
129
|
+
|| fail "the report must say its top-level verdict covers only the cases it measured"
|
|
130
|
+
grep -q '"actionable": false' "$TMP/deg.json" \
|
|
131
|
+
|| fail "a case left short by an invalid observation must carry actionable:false"
|
|
132
|
+
# The split is the point: the degraded case is short, the other one is not, and a
|
|
133
|
+
# report-wide conjunction substituted for the per-case field would mark both false.
|
|
134
|
+
python3 - "$TMP/deg.json" <<'PYEOF' || fail "the healthy case must stay actionable while the degraded one does not"
|
|
135
|
+
import json,sys
|
|
136
|
+
d=json.load(open(sys.argv[1]))
|
|
137
|
+
by={r["id"]:r for r in d["results"]}
|
|
138
|
+
ok = (by["resolution-fixture"]["actionable"] is False
|
|
139
|
+
and by["resolution-fixture-b"]["actionable"] is True
|
|
140
|
+
and by["resolution-fixture-b"]["valid_observations"] == 10
|
|
141
|
+
and d["action_resolution"] is False)
|
|
142
|
+
sys.exit(0 if ok else 1)
|
|
143
|
+
PYEOF
|
|
144
|
+
|
|
145
|
+
# --- (2d) shapes where a result could carry no usable verdict at all ----------
|
|
146
|
+
# The resolution loop reads every result's verdict list. Review could not check
|
|
147
|
+
# from the bounded packet that a list is always present, so the shapes that would
|
|
148
|
+
# expose an absent one are pinned here: a single-replica run, and a task whose
|
|
149
|
+
# every replica fails. Neither may crash, and neither may report itself
|
|
150
|
+
# actionable.
|
|
151
|
+
: > "$TMP/count"
|
|
152
|
+
out_one="$(RESOLUTION_COUNT_FILE="$TMP/count" ruby "$EVAL_SCRIPT" "$REPO" --replicas 1 --json "$TMP/one.json" 2>&1)" \
|
|
153
|
+
|| fail "runner exited non-zero on a single-replica run:\n$out_one"
|
|
154
|
+
grep -q '"action_resolution": false' "$TMP/one.json" \
|
|
155
|
+
|| fail "a single-replica run is below the floor and must not be actionable"
|
|
156
|
+
python3 - "$TMP/one.json" <<'PYEOF' || fail "every result of a single-replica run must carry its own verdict list and count"
|
|
157
|
+
import json,sys
|
|
158
|
+
d=json.load(open(sys.argv[1]))
|
|
159
|
+
ok = bool(d["results"]) and all(
|
|
160
|
+
isinstance(r.get("verdicts"), list) and len(r["verdicts"]) == 1
|
|
161
|
+
and r["valid_observations"] <= 1 and r["actionable"] is False
|
|
162
|
+
for r in d["results"])
|
|
163
|
+
sys.exit(0 if ok else 1)
|
|
164
|
+
PYEOF
|
|
165
|
+
|
|
166
|
+
cat > "$FAKE_BIN/claude" <<'EOF'
|
|
167
|
+
#!/usr/bin/env bash
|
|
168
|
+
cat >/dev/null
|
|
169
|
+
printf 'never json\n'
|
|
170
|
+
EOF
|
|
171
|
+
chmod +x "$FAKE_BIN/claude"
|
|
172
|
+
# Every task fails every replica: the runner reports the wholly-unmeasured run and
|
|
173
|
+
# exits 3 by its documented contract, and the report must still be well formed.
|
|
174
|
+
# `set -e` would kill the suite on the non-zero exit before it could be read, so
|
|
175
|
+
# the status is captured in the same command that produces it.
|
|
176
|
+
rc=0
|
|
177
|
+
ruby "$EVAL_SCRIPT" "$REPO" --replicas 2 --json "$TMP/allfail.json" >/dev/null 2>&1 || rc=$?
|
|
178
|
+
[ "$rc" = "3" ] || fail "a wholly failed run must exit 3 by the runner's contract, got $rc"
|
|
179
|
+
python3 - "$TMP/allfail.json" <<'PYEOF' || fail "a wholly failed run must still report zero valid observations and no actionable case"
|
|
180
|
+
import json,sys
|
|
181
|
+
d=json.load(open(sys.argv[1]))
|
|
182
|
+
ok = bool(d["results"]) and d["action_resolution"] is False and d["min_valid_observations"] == 0 \
|
|
183
|
+
and all(r["valid_observations"] == 0 and r["actionable"] is False and isinstance(r["verdicts"], list)
|
|
184
|
+
for r in d["results"])
|
|
185
|
+
sys.exit(0 if ok else 1)
|
|
186
|
+
PYEOF
|
|
187
|
+
|
|
188
|
+
# --- (2e) a parseable answer naming no catalog skill is not an observation ------
|
|
189
|
+
# Review could not see from the packet whether a well-formed JSON verdict whose
|
|
190
|
+
# selected_skill is not in the catalog counts toward the floor. It must not: it
|
|
191
|
+
# is absence of evidence, not evidence against the route, so it is an ERROR
|
|
192
|
+
# verdict and the case it belongs to stays short of the floor.
|
|
193
|
+
cat > "$FAKE_BIN/claude" <<'EOF'
|
|
194
|
+
#!/usr/bin/env bash
|
|
195
|
+
cat >/dev/null
|
|
196
|
+
printf '{"selected_skill":"skill-that-does-not-exist","clarify":false,"confidence":0.9,"rationale_short":"fixture"}\n'
|
|
197
|
+
EOF
|
|
198
|
+
chmod +x "$FAKE_BIN/claude"
|
|
199
|
+
rc=0
|
|
200
|
+
ruby "$EVAL_SCRIPT" "$REPO" --replicas 10 --json "$TMP/badsel.json" >/dev/null 2>&1 || rc=$?
|
|
201
|
+
python3 - "$TMP/badsel.json" <<'PYEOF' || fail "a parseable verdict naming a non-catalog skill must count as an error, not a usable observation"
|
|
202
|
+
import json,sys
|
|
203
|
+
d=json.load(open(sys.argv[1]))
|
|
204
|
+
ok = d["action_resolution"] is False and d["min_valid_observations"] == 0 \
|
|
205
|
+
and all(r["valid_observations"] == 0 and r["actionable"] is False for r in d["results"]) \
|
|
206
|
+
and all(v["status"] == "ERROR" for r in d["results"] for v in r["verdicts"])
|
|
207
|
+
sys.exit(0 if ok else 1)
|
|
208
|
+
PYEOF
|
|
209
|
+
|
|
210
|
+
# --- (2f) a skill the prompt never offered is not selectable ---------------------
|
|
211
|
+
# A directory with a SKILL.md but no usable description is filtered out of the
|
|
212
|
+
# prompt catalog. If the selectable-name list were built separately it could still
|
|
213
|
+
# admit that name, and a verdict naming it would count. Both must come from one
|
|
214
|
+
# filtered list, so selecting the unoffered skill is an ERROR like any other
|
|
215
|
+
# non-catalog name.
|
|
216
|
+
mkdir -p "$REPO/skills/ghost-skill"
|
|
217
|
+
printf -- '---\ndescription: ""\n---\n# Ghost\n' > "$REPO/skills/ghost-skill/SKILL.md"
|
|
218
|
+
git -C "$REPO" add -A && git -C "$REPO" commit -qm "ghost skill with empty description"
|
|
219
|
+
cat > "$FAKE_BIN/claude" <<'EOF'
|
|
220
|
+
#!/usr/bin/env bash
|
|
221
|
+
cat >/dev/null
|
|
222
|
+
printf '{"selected_skill":"ghost-skill","clarify":false,"confidence":0.9,"rationale_short":"fixture"}\n'
|
|
223
|
+
EOF
|
|
224
|
+
chmod +x "$FAKE_BIN/claude"
|
|
225
|
+
rc=0
|
|
226
|
+
ruby "$EVAL_SCRIPT" "$REPO" --replicas 10 --json "$TMP/ghost.json" >/dev/null 2>&1 || rc=$?
|
|
227
|
+
python3 - "$TMP/ghost.json" <<'PYEOF' || fail "a verdict naming a skill the prompt never offered must be an error, not a usable observation"
|
|
228
|
+
import json,sys
|
|
229
|
+
d=json.load(open(sys.argv[1]))
|
|
230
|
+
ok = d["action_resolution"] is False and all(v["status"]=="ERROR" for r in d["results"] for v in r["verdicts"])
|
|
231
|
+
sys.exit(0 if ok else 1)
|
|
232
|
+
PYEOF
|
|
233
|
+
|
|
234
|
+
# --- (3) the two sides of the number must agree -------------------------------
|
|
235
|
+
# The rule is only as good as the agreement between the reference that states it
|
|
236
|
+
# and the executable that enforces it. Compare the numbers themselves rather
|
|
237
|
+
# than pinning one literal in two places.
|
|
238
|
+
exe_min="$(grep -oE 'ACTION_RESOLUTION_MIN_REPLICAS = [0-9]+' "$EVAL_SCRIPT" | grep -oE '[0-9]+$' | sort -u)"
|
|
239
|
+
[ -n "$exe_min" ] || fail "could not read ACTION_RESOLUTION_MIN_REPLICAS from $EVAL_SCRIPT"
|
|
240
|
+
[ "$(printf '%s\n' "$exe_min" | wc -l | tr -d ' ')" = "1" ] \
|
|
241
|
+
|| fail "ACTION_RESOLUTION_MIN_REPLICAS is defined with more than one value: $exe_min"
|
|
242
|
+
doc_min="$(grep -oE 'replicas < [0-9]+' "$DOC" | grep -oE '[0-9]+$' | sort -u)"
|
|
243
|
+
[ -n "$doc_min" ] || fail "$DOC does not state the replicas floor as \`replicas < N\`"
|
|
244
|
+
[ "$doc_min" = "$exe_min" ] \
|
|
245
|
+
|| fail "doc states floor $doc_min but the runner enforces $exe_min — they must be one number"
|
|
246
|
+
|
|
247
|
+
# The prose obligation (">=N valid observations before acting") must name the
|
|
248
|
+
# same number too, so a reader following the sentence and a consumer reading the
|
|
249
|
+
# report are held to one bar.
|
|
250
|
+
grep -qF "≥${exe_min} 个有效观测" "$DOC" \
|
|
251
|
+
|| fail "$DOC must state the per-case obligation with the same floor (≥${exe_min} 个有效观测)"
|
|
252
|
+
|
|
253
|
+
echo "PASS: eval-routing-bank resolution signal (banner, report field, doc/executor agreement)"
|