@chrono-meta/fh-gate 2.15.1 → 3.1.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/.claude/regression/probes_live.yaml +137 -0
- package/.claude/rules/.residency-patterns.defaults +7 -0
- package/.claude/rules/fh_4axis_gate.md +88 -1
- package/.claude-plugin/marketplace.json +8 -2
- package/AGENTS.md +27 -0
- package/CATALOG.md +17 -0
- package/CLAUDE.md +13 -3
- package/README.ja.md +51 -7
- package/README.ko.md +48 -7
- package/README.md +43 -6
- package/README.zh.md +45 -8
- package/docs/STANDARDS_ALIGNMENT.md +61 -0
- package/docs/USER_GUIDE.md +3 -0
- package/docs/USE_CASES.md +50 -0
- package/docs/model_tier_expectations.md +60 -0
- package/knowledge/shared/harness-core/fh_three_layer_canon.md +77 -10
- package/knowledge/shared/harness-core/field_verdict_crossfamily_gate.md +18 -0
- package/knowledge/shared/harness-core/harness_incubator_doctrine.md +9 -0
- package/knowledge/shared/harness-core/iso_ai_standards_crosswalk.md +139 -0
- package/knowledge/shared/harness-core/measurement-integrity-checklist.md +35 -1
- package/knowledge/shared/harness-core/ship_readiness_gate.md +19 -2
- package/knowledge/shared/learnings/subagent_invocations_log.yaml +489 -3
- package/package.json +48 -2
- package/plugins/fh-commons/.claude-plugin/plugin.json +1 -1
- package/plugins/fh-commons/skills/preprep/README.md +4 -1
- package/plugins/fh-commons/skills/preprep/SKILL.md +99 -3
- package/plugins/fh-commons/skills/preprep/diagram_from_json.py +154 -0
- package/plugins/fh-commons/skills/preprep/fixtures/fixture_R3_negative.pptx +0 -0
- package/plugins/fh-commons/skills/preprep/fixtures/fixture_R3_positive.pptx +0 -0
- package/plugins/fh-commons/skills/preprep/fixtures/mk_slide_fixtures.py +179 -0
- package/plugins/fh-commons/skills/preprep/interslide_deps.py +98 -9
- package/plugins/fh-commons/skills/preprep/lane_adjacent_dup.py +4 -1
- package/plugins/fh-commons/skills/preprep/lane_diagram.py +95 -0
- package/plugins/fh-commons/skills/preprep/lane_geometry.py +181 -0
- package/plugins/fh-commons/skills/preprep/lane_promise.py +4 -1
- package/plugins/fh-commons/skills/preprep/lane_slide_refs.py +134 -0
- package/plugins/fh-commons/skills/preprep/lane_slide_relations.py +300 -0
- package/plugins/fh-commons/skills/preprep/preprep.py +129 -8
- package/plugins/fh-commons/skills/preprep/preprep_wire.py +210 -0
- package/plugins/fh-commons/skills/preprep/presentation_checklist.md +4 -0
- package/plugins/fh-commons/skills/preprep/surfaces.example.yaml +18 -0
- package/plugins/fh-commons/skills/preprep/test_preprep_lanes_rp.py +184 -0
- package/plugins/fh-meta/.claude-plugin/plugin.json +1 -1
- package/plugins/fh-meta/CHANGELOG.md +67 -0
- package/plugins/fh-meta/skills/auto-decorrelation/SKILL.md +87 -2
- package/plugins/fh-meta/skills/frontier-digest/SKILL.md +2 -2
- package/plugins/fh-meta/skills/frontier-digest/SKILL_detail.md +25 -5
- package/plugins/fh-meta/skills/hub-cc-pr-reviewer/SKILL.md +11 -0
- package/plugins/fh-meta/skills/hub-cc-pr-reviewer/SKILL_detail.md +3 -0
- package/plugins/fh-qp/.claude-plugin/plugin.json +22 -0
- package/plugins/fh-qp/README.md +71 -0
- package/plugins/fh-qp/fixtures/evidence_known_clean.txt +3 -0
- package/plugins/fh-qp/fixtures/evidence_known_dirty.txt +6 -0
- package/plugins/fh-qp/fixtures/reach_known_partial.tsv +4 -0
- package/plugins/fh-qp/fixtures/reach_known_reached.tsv +4 -0
- package/plugins/fh-qp/fixtures/reach_known_wall.tsv +3 -0
- package/plugins/fh-qp/fixtures/verdicts_known_bad_branch.tsv +2 -0
- package/plugins/fh-qp/fixtures/verdicts_known_bad_vacuous_machine.tsv +2 -0
- package/plugins/fh-qp/fixtures/verdicts_known_good.tsv +4 -0
- package/plugins/fh-qp/fixtures/verdicts_verify_only.tsv +3 -0
- package/plugins/fh-qp/qp_profile.example.yaml +29 -0
- package/plugins/fh-qp/scripts/qp_tools.sh +216 -0
- package/plugins/fh-qp/skills/qp/SKILL.md +65 -0
- package/plugins/fh-qp/skills/qp-plan/SKILL.md +42 -0
- package/plugins/fh-qp/skills/qp-regress/SKILL.md +45 -0
- package/plugins/fh-qp/skills/qp-run/SKILL.md +49 -0
- package/scripts/backtick_guard.sh +194 -0
- package/scripts/chamber_run.sh +14 -5
- package/scripts/com.forge-harness.live-eval.plist +84 -0
- package/scripts/compaction_probe.sh +9 -35
- package/scripts/context_continuity_score.sh +49 -7
- package/scripts/directional_diff_gate.sh +14 -2
- package/scripts/fh-gate.sh +3 -3
- package/scripts/files_manifest_shipping_check.sh +19 -0
- package/scripts/frontier_digest_autopilot.sh +4 -1
- package/scripts/gate_pathspec_check.sh +1 -1
- package/scripts/map_postprocess.py +90 -0
- package/scripts/outbound_query_guard.sh +131 -0
- package/scripts/outbound_query_hook.sh +373 -0
- package/scripts/package_coverage_check.sh +79 -18
- package/scripts/pipe_verdict_guard.sh +41 -1
- package/scripts/probe_live_eval.sh +240 -0
- package/scripts/probe_live_eval_lib.py +579 -0
- package/scripts/proposal_hook.sh +192 -0
- package/scripts/public_surface_scan_files.sh +11 -2
- package/scripts/push_zone_check.sh +78 -0
- package/scripts/residency_closure_scan.py +252 -0
- package/scripts/revert_probe.sh +250 -0
- package/scripts/selfcheck.sh +106 -3
- package/scripts/session_close_check.sh +100 -0
- package/scripts/sim_isolated_run.sh +195 -9
- package/scripts/test_action_yml_lanes.sh +97 -0
- package/scripts/test_backtick_guard_lanes.sh +115 -0
- package/scripts/test_degrade_scan_shell_probes.sh +7 -7
- package/scripts/test_fh_qp_lanes.sh +105 -0
- package/scripts/test_files_manifest_shipping_lanes.sh +5 -5
- package/scripts/test_gate_two_verdicts_lanes.sh +139 -0
- package/scripts/test_heavy_classifier_lanes.sh +1 -1
- package/scripts/test_lane_runner_lanes.sh +59 -33
- package/scripts/test_map_postprocess_lanes.sh +143 -0
- package/scripts/test_mapped_tracks_lanes.sh +1 -1
- package/scripts/test_marker_affected_lanes.sh +93 -0
- package/scripts/test_marker_crossfamily_lanes.sh +90 -6
- package/scripts/test_marker_oracle_lanes.sh +136 -0
- package/scripts/test_marker_soul_check_lanes.sh +24 -0
- package/scripts/test_node_check_lanes.sh +34 -34
- package/scripts/test_outbound_query_hook_lanes.sh +433 -0
- package/scripts/test_outbound_query_lanes.sh +87 -0
- package/scripts/test_package_coverage_lanes.sh +53 -27
- package/scripts/test_pipe_verdict_guard_lanes.sh +31 -5
- package/scripts/test_precommit_pointer_index_lanes.sh +33 -0
- package/scripts/test_preprep_diagram_lanes.sh +87 -0
- package/scripts/test_preprep_drift_anchor.sh +16 -7
- package/scripts/test_preprep_drift_anchor_lanes.sh +23 -0
- package/scripts/test_preprep_slide_refs_lanes.sh +169 -0
- package/scripts/test_probe_live_eval_lanes.sh +437 -0
- package/scripts/test_proposal_hook_lanes.sh +57 -0
- package/scripts/test_push_zone_lanes.sh +304 -0
- package/scripts/test_residency_closure_lanes.sh +70 -0
- package/scripts/test_revert_probe_lanes.sh +146 -0
- package/scripts/test_session_close_lanes.sh +3 -5
- package/scripts/test_sim_isolated_run_lanes.sh +136 -0
- package/scripts/test_utterance_intake_lanes.sh +414 -0
- package/scripts/test_worktree_reclaim_lanes.sh +70 -0
- package/scripts/transcript_utterances.py +222 -0
- package/scripts/utterance_intake.sh +424 -0
- package/scripts/utterance_landing_check.sh +2 -2
- package/scripts/validate_yaml.sh +27 -0
- package/scripts/worktree_reclaim.sh +95 -0
- package/templates/.git-hooks/pre-commit +380 -5
- package/templates/.git-hooks/pre-push +91 -0
- package/templates/RED_TEAM_REPORT.md +49 -0
- package/templates/settings.PreToolUse.snippet.json +101 -1
- package/plugins/fh-commons/README.md +0 -38
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# test_fh_qp_lanes.sh — known-pair lanes for plugins/fh-qp (QP · Quality Platform).
|
|
3
|
+
#
|
|
4
|
+
# What it proves: the MECHANICAL half (plugins/fh-qp/scripts/qp_tools.sh) discriminates on fixtures
|
|
5
|
+
# shipped with the plugin — three negatives from the chamber oracle (profile-required target → refuse ·
|
|
6
|
+
# adapter absent → HARNESS_ERROR · dirty evidence → masked, residue 0), surface_reach four states,
|
|
7
|
+
# the verdict-record contract (mtm-check), the state-changing-verb floor, and a residency scan of the
|
|
8
|
+
# whole scaffold (fail-closed when the scanner cannot run — exit 10 is never read as clean).
|
|
9
|
+
# What it does NOT prove: that a session FOLLOWS the SKILL prose (that is the floor-tier sim's job),
|
|
10
|
+
# or that any verdict label is TRUE (judgment, deliberately unmechanised).
|
|
11
|
+
# Exit: 0 all lanes pass · 1 any lane fails · 10 harness error (plugin/tool missing).
|
|
12
|
+
set -uo pipefail
|
|
13
|
+
FH="$(cd "$(dirname "$0")/.." && pwd)"
|
|
14
|
+
T="$FH/plugins/fh-qp/scripts/qp_tools.sh"
|
|
15
|
+
X="$FH/plugins/fh-qp/fixtures"
|
|
16
|
+
[ -f "$T" ] || { echo "HARNESS-ERROR: $T missing"; exit 10; }
|
|
17
|
+
[ -d "$X" ] || { echo "HARNESS-ERROR: fixtures dir missing"; exit 10; }
|
|
18
|
+
TMP="$(mktemp -d "${TMPDIR:-/tmp}/fhqp.XXXXXX")"; trap 'rm -rf "$TMP"' EXIT
|
|
19
|
+
PASS=0; FAIL=0
|
|
20
|
+
lane() { # $1 name · $2 expected rc · $3 expected stdout regex · rest = command
|
|
21
|
+
local name="$1" want_rc="$2" want_re="$3"; shift 3
|
|
22
|
+
local out rc; out="$("$@" 2>&1)"; rc=$?
|
|
23
|
+
if [ "$rc" = "$want_rc" ] && printf '%s' "$out" | /usr/bin/grep -qE "$want_re"; then
|
|
24
|
+
PASS=$((PASS+1)); printf ' ✅ %-34s rc=%s %s\n' "$name" "$rc" "$(printf '%s' "$out" | head -1 | cut -c1-70)"
|
|
25
|
+
else
|
|
26
|
+
FAIL=$((FAIL+1)); printf ' ❌ %-34s rc=%s (want %s /%s/)\n %s\n' "$name" "$rc" "$want_rc" "$want_re" "$out"
|
|
27
|
+
fi
|
|
28
|
+
}
|
|
29
|
+
echo "── fh-qp lanes ──"
|
|
30
|
+
# ── negative ① profile-required → refuse (and the positive pair: public passes, profile unlocks) ──
|
|
31
|
+
lane N1-private-host-refused 4 '^PROFILE_REQUIRED kind=web' bash "$T" target-class https://private.example.internal/x
|
|
32
|
+
lane N1-rfc1918-refused 4 '^PROFILE_REQUIRED' bash "$T" target-class http://10.1.2.3/login
|
|
33
|
+
lane N1-desktop-needs-profile 4 '^PROFILE_REQUIRED kind=desktop' bash "$T" target-class app:SomeApp
|
|
34
|
+
lane N1-public-passes 0 '^PUBLIC kind=web host=example\.com' bash "$T" target-class https://example.com/
|
|
35
|
+
printf 'targets:\n web:\n - base_url: https://private.example.internal\n' > "$TMP/profile.yaml"
|
|
36
|
+
lane N1-profile-unlocks 0 '^PROFILE_OK' bash "$T" target-class https://private.example.internal/x --profile "$TMP/profile.yaml"
|
|
37
|
+
lane N1-bad-form-unknown 10 '^UNKNOWN' bash "$T" target-class ftp://x
|
|
38
|
+
# ── negative ② adapter absent → HARNESS_ERROR (pair: present → ADAPTER=) ──
|
|
39
|
+
lane N2-no-tools-harness-error 10 '^HARNESS_ERROR reason=tool-list-not-supplied' bash "$T" adapter-probe --need web
|
|
40
|
+
lane N2-wrong-kind-harness-error 10 '^HARNESS_ERROR reason=no-desktop-adapter' bash "$T" adapter-probe --need desktop --tools "mcp__playwright__browser_navigate,Read"
|
|
41
|
+
lane N2-web-adapter-found 0 '^ADAPTER=playwright-mcp evidence=dom' bash "$T" adapter-probe --need web --tools "Read,mcp__playwright__browser_navigate"
|
|
42
|
+
lane N2-desktop-adapter-found 0 '^ADAPTER=computer-use-mcp evidence=pixel' bash "$T" adapter-probe --need desktop --tools "mcp__computer-use__screenshot"
|
|
43
|
+
# ── negative ③ dirty evidence → masked, residue 0 (pair: clean stays clean; raw never survives) ──
|
|
44
|
+
lane N3-dirty-masked 0 '^MASKED residue=0' bash "$T" mask "$X/evidence_known_dirty.txt" "$TMP/masked.txt"
|
|
45
|
+
if /usr/bin/grep -qE 'qa\.tester@|hunter2|eyJhbGci|abcdefghijklmnop' "$TMP/masked.txt"; then FAIL=$((FAIL+1)); echo " ❌ N3-raw-survived-in-output"; else PASS=$((PASS+1)); echo " ✅ N3-raw-absent-in-output"; fi
|
|
46
|
+
lane N3-clean-stays-clean 0 '^MASKED residue=0' bash "$T" mask "$X/evidence_known_clean.txt" "$TMP/masked2.txt"
|
|
47
|
+
lane N3-missing-input-harness 10 '^HARNESS_ERROR' bash "$T" mask "$X/does_not_exist.txt" "$TMP/m3.txt"
|
|
48
|
+
# ── surface_reach four states ──
|
|
49
|
+
lane SR-reached 0 '^REACHED tcs_beyond_entry=2 tcs_total=2' bash "$T" surface-reach "$X/reach_known_reached.tsv"
|
|
50
|
+
lane SR-partial 5 '^PARTIAL tcs_beyond_entry=1 tcs_total=2' bash "$T" surface-reach "$X/reach_known_partial.tsv"
|
|
51
|
+
lane SR-wall 5 '^NOT_REACHED tcs_beyond_entry=0 tcs_total=3' bash "$T" surface-reach "$X/reach_known_wall.tsv"
|
|
52
|
+
: > "$TMP/reach_empty.tsv" # generated here — an empty file cannot travel in a diff (PATCH.diff dropped it; measured 2026-09-05)
|
|
53
|
+
lane SR-empty-unmeasured 10 '^UNMEASURED reason=evidence-empty' bash "$T" surface-reach "$TMP/reach_empty.tsv"
|
|
54
|
+
lane SR-missing-unmeasured 10 '^UNMEASURED reason=evidence-missing' bash "$T" surface-reach "$X/no_such_file.tsv"
|
|
55
|
+
# ── verdict record contract ──
|
|
56
|
+
lane MTM-good 0 '^OK mtm=UNAVAILABLE rows=3 machine_closed=2 judgment_left=1' bash "$T" mtm-check "$X/verdicts_known_good.tsv"
|
|
57
|
+
lane MTM-branch-needs-active 10 'DIFFERS_FROM_PLAN-needs-mtm-ACTIVE' bash "$T" mtm-check "$X/verdicts_known_bad_branch.tsv"
|
|
58
|
+
lane MTM-vacuous-machine 10 'MACHINE-closure-without-assertion' bash "$T" mtm-check "$X/verdicts_known_bad_vacuous_machine.tsv"
|
|
59
|
+
printf '#mtm: ACTIVE\nTC-1\tFAIL\tDIFFERS_FROM_PLAN\tMACHINE\tverify\ttext-visible:x\tINVENTORY\n' > "$TMP/v_src.tsv"
|
|
60
|
+
lane MTM-branch-source-binding 10 'DIFFERS_FROM_PLAN-needs-expected_source=PLAN_DOC' bash "$T" mtm-check "$TMP/v_src.tsv"
|
|
61
|
+
printf 'TC-1\tPASS\tAS_PLANNED\tMACHINE\tclick\tx\tINVENTORY\n' > "$TMP/v_nomtm.tsv"
|
|
62
|
+
lane MTM-state-line-required 10 'mtm-state-missing' bash "$T" mtm-check "$TMP/v_nomtm.tsv"
|
|
63
|
+
lane VERBS-good 0 '^OK state_changing_verbs=2' bash "$T" run-verbs "$X/verdicts_known_good.tsv"
|
|
64
|
+
lane VERBS-verify-only 5 '^VERIFY_ONLY' bash "$T" run-verbs "$X/verdicts_verify_only.tsv"
|
|
65
|
+
# ── screen-id: same content with different ref tokens → same id; different content → different id ──
|
|
66
|
+
printf -- '- heading "Home" [level=1] [ref=e2]\n- link "About" [ref=e7]\n' > "$TMP/s1.md"
|
|
67
|
+
printf -- '- heading "Home" [level=1] [ref=f3e2]\n- link "About" [ref=f3e7]\n' > "$TMP/s2.md"
|
|
68
|
+
printf -- '- heading "About" [level=1] [ref=e2]\n' > "$TMP/s3.md"
|
|
69
|
+
id1=$(bash "$T" screen-id "$TMP/s1.md"); id2=$(bash "$T" screen-id "$TMP/s2.md"); id3=$(bash "$T" screen-id "$TMP/s3.md")
|
|
70
|
+
if [ "$id1" = "$id2" ] && [ "$id1" != "$id3" ] && [ ${#id1} -eq 12 ]; then PASS=$((PASS+1)); echo " ✅ SID-content-hash-stable-across-refs ($id1)"; else FAIL=$((FAIL+1)); echo " ❌ SID-content-hash ($id1 / $id2 / $id3)"; fi
|
|
71
|
+
# ── residency: the whole scaffold, fail-closed ──
|
|
72
|
+
FILES="$(cd "$FH" && find plugins/fh-qp scripts/test_fh_qp_lanes.sh -type f | sort)"
|
|
73
|
+
# Worktrees do not carry the gitignored operator pattern file — fall back to the main checkout's copy
|
|
74
|
+
# (git-common-dir), else honour RESIDENCY_PATTERNS, else the scanner exits 10 and this lane FAILS (fail-closed).
|
|
75
|
+
if [ -z "${RESIDENCY_PATTERNS:-}" ] && [ ! -f "$FH/.claude/rules/.residency-patterns" ]; then
|
|
76
|
+
_common="$(cd "$FH" && git rev-parse --git-common-dir 2>/dev/null)"
|
|
77
|
+
[ -n "$_common" ] && [ -f "$_common/../.claude/rules/.residency-patterns" ] && export RESIDENCY_PATTERNS="$(cd "$_common/.." && pwd)/.claude/rules/.residency-patterns"
|
|
78
|
+
fi
|
|
79
|
+
if [ -f "$FH/scripts/residency_closure_scan.py" ]; then
|
|
80
|
+
# shellcheck disable=SC2086
|
|
81
|
+
out="$(cd "$FH" && python3 scripts/residency_closure_scan.py --files $FILES 2>&1)"; rc=$?
|
|
82
|
+
case "$rc" in
|
|
83
|
+
0) PASS=$((PASS+1)); echo " ✅ RES-closure-scan CLEAN ($(printf '%s' "$FILES" | wc -l | tr -d ' ') files)" ;;
|
|
84
|
+
10) if printf '%s' "$out" | grep -q '운영자 패턴 파일이 없다'; then
|
|
85
|
+
# CI and consumer clones structurally lack the operator override (gitignored). That is
|
|
86
|
+
# UNMEASURED, not a failure of the shipped files — the generic RES-zero-constant-grep lane
|
|
87
|
+
# below is the guarantee that travels; this lane measures only where the override exists.
|
|
88
|
+
echo " ⚪ RES-closure-scan SKIPPED — operator patterns file absent (UNMEASURED, not clean; the generic grep lane still runs)"
|
|
89
|
+
else
|
|
90
|
+
FAIL=$((FAIL+1)); echo " ❌ RES-closure-scan HARNESS (rc=10 — defaults missing or pattern compile error)"; echo "$out" | tail -3
|
|
91
|
+
fi ;;
|
|
92
|
+
*) FAIL=$((FAIL+1)); echo " ❌ RES-closure-scan TAINTED (rc=$rc)"; echo "$out" | tail -5 ;;
|
|
93
|
+
esac
|
|
94
|
+
else
|
|
95
|
+
FAIL=$((FAIL+1)); echo " ❌ RES-closure-scan scanner missing (fail-closed)"
|
|
96
|
+
fi
|
|
97
|
+
# generic zero-constant grep (no scanner needed): private hosts / secrets shapes must not appear in shipped files
|
|
98
|
+
# except inside the profile EXAMPLE and fixtures, which exist to show the shapes.
|
|
99
|
+
# The lane itself is the instrument (its known-negative literals are the point) — scanned by residency above, excluded here.
|
|
100
|
+
SHIPPED="$(printf '%s' "$FILES" | /usr/bin/grep -vE 'fixtures/|qp_profile\.example\.yaml|test_fh_qp_lanes\.sh')"
|
|
101
|
+
# shellcheck disable=SC2086
|
|
102
|
+
hits="$(cd "$FH" && /usr/bin/grep -nE '\b(10|192\.168)\.[0-9]+\.[0-9]+\.[0-9]+\b|eyJ[A-Za-z0-9_-]{8,}\.|AKIA[A-Z0-9]{8,}' $SHIPPED 2>/dev/null | /usr/bin/grep -vE 'qp_tools\.sh:.*(_MASK_|case|10\.\*)' || true)"
|
|
103
|
+
if [ -z "$hits" ]; then PASS=$((PASS+1)); echo " ✅ RES-zero-constant-grep (0 hits in shipped files)"; else FAIL=$((FAIL+1)); echo " ❌ RES-zero-constant-grep"; echo "$hits"; fi
|
|
104
|
+
echo "── fh-qp lanes: $PASS passed, $FAIL failed ──"
|
|
105
|
+
[ "$FAIL" -eq 0 ]
|
|
@@ -46,9 +46,9 @@ trap 'rm -rf "$LANE2_DIR"' EXIT
|
|
|
46
46
|
cp package.json "$LANE2_DIR/package.json.orig"
|
|
47
47
|
node -e '
|
|
48
48
|
const fs = require("fs");
|
|
49
|
-
const pkg = JSON.parse(fs.readFileSync("package.json", "utf8"));
|
|
49
|
+
const pkg = JSON.parse(fs.readFileSync("package.json", "utf8")); // # portability-noqa: cwd is REPO_ROOT (line 18) -- this is the subject under tests own manifest, not a foreign fixture
|
|
50
50
|
pkg.files.push("scripts/__test_fixture_does_not_exist__.sh");
|
|
51
|
-
fs.writeFileSync("package.json", JSON.stringify(pkg, null, 2) + "\n");
|
|
51
|
+
fs.writeFileSync("package.json", JSON.stringify(pkg, null, 2) + "\n"); // # portability-noqa: same as above -- writes back to the same repo-relative manifest it read
|
|
52
52
|
'
|
|
53
53
|
restore_pkg() { cp "$LANE2_DIR/package.json.orig" package.json; }
|
|
54
54
|
|
|
@@ -132,7 +132,7 @@ D6="$(fixture_dir)"
|
|
|
132
132
|
mkdir -p "$D6/.git"
|
|
133
133
|
node -e '
|
|
134
134
|
const fs = require("fs");
|
|
135
|
-
const pkg = JSON.parse(fs.readFileSync("package.json", "utf8"));
|
|
135
|
+
const pkg = JSON.parse(fs.readFileSync("package.json", "utf8")); // # portability-noqa: cwd is REPO_ROOT -- seeds the fixture from the subject own real manifest, then mutates a copy
|
|
136
136
|
pkg.files = ".";
|
|
137
137
|
fs.writeFileSync(process.argv[1], JSON.stringify(pkg, null, 2) + "\n");
|
|
138
138
|
' "$D6/package.json"
|
|
@@ -152,7 +152,7 @@ D7="$(fixture_dir)"
|
|
|
152
152
|
mkdir -p "$D7/.git"
|
|
153
153
|
node -e '
|
|
154
154
|
const fs = require("fs");
|
|
155
|
-
const pkg = JSON.parse(fs.readFileSync("package.json", "utf8"));
|
|
155
|
+
const pkg = JSON.parse(fs.readFileSync("package.json", "utf8")); // # portability-noqa: cwd is REPO_ROOT -- seeds the fixture from the subject own real manifest, then mutates a copy
|
|
156
156
|
pkg.files.push("missing\nPASS\\t999 entries checked, 0 phantom, 0 unpacked");
|
|
157
157
|
fs.writeFileSync(process.argv[1], JSON.stringify(pkg, null, 2) + "\n");
|
|
158
158
|
' "$D7/package.json"
|
|
@@ -175,7 +175,7 @@ mkdir -p "$D8/.git" "$D8/scripts_dummy"
|
|
|
175
175
|
echo x > "$D8/scripts_dummy/foo.sh"
|
|
176
176
|
node -e '
|
|
177
177
|
const fs = require("fs");
|
|
178
|
-
const pkg = JSON.parse(fs.readFileSync("package.json", "utf8"));
|
|
178
|
+
const pkg = JSON.parse(fs.readFileSync("package.json", "utf8")); // # portability-noqa: cwd is REPO_ROOT -- seeds the fixture from the subject own real manifest, then mutates a copy
|
|
179
179
|
pkg.files = ["scripts_dummy"];
|
|
180
180
|
delete pkg.scripts.prepare;
|
|
181
181
|
delete pkg.scripts.prepublishOnly;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# test_gate_two_verdicts_lanes.sh — pre-commit 의 `_acceptance_evidence_line` 계약을 고정한다.
|
|
3
|
+
#
|
|
4
|
+
# WHY (2026-09-06, frontier-digest 답습 · arXiv:2609.04167 «SWE-Gate»): 그 논문의 실측은
|
|
5
|
+
# 기능 테스트를 통과한 수리 644 건 중 **221 건(34%)** 이 실제 PR 리뷰 코멘트에서 뽑은 제약을
|
|
6
|
+
# 어겼다는 것이고, 처방은 «기능 정확성과 리뷰 제약 충족을 따로 채점하라» 다. 이 훅도 같은
|
|
7
|
+
# 형태였다 — 축이 다 통과하면 «ALL AXES PASSED» 라는 **합성 판정 하나**만 찍혔고, 마커의
|
|
8
|
+
# `crossfamily: DEGRADED_PANEL_UNUSED` 같은 값은 출력 어디에도 안 나왔다.
|
|
9
|
+
#
|
|
10
|
+
# SCOPE — 채널이지 판정이 아니다(§Mechanization Boundary): 이 함수는 «어떤 값이 적혔나」만
|
|
11
|
+
# 찍는다. 값의 진위를 판정하지 않고, 어떤 경우에도 종료코드를 바꾸지 않는다. 그래서 레인도
|
|
12
|
+
# «막나」가 아니라 «가려지나」를 잰다 — 특히 DEGRADED/UNKNOWN 이 조용히 사라지지 않는지.
|
|
13
|
+
#
|
|
14
|
+
# 종료코드: 0 = 계약대로 · 1 = 회귀
|
|
15
|
+
set -uo pipefail
|
|
16
|
+
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
17
|
+
HOOK="$REPO_ROOT/templates/.git-hooks/pre-commit"
|
|
18
|
+
T=$(mktemp -d); trap 'rm -rf "$T"' EXIT
|
|
19
|
+
[ -f "$HOOK" ] || { echo "ⓘ $HOOK absent — subject missing when we looked (NOT a pass)"; exit 2; }
|
|
20
|
+
|
|
21
|
+
sed -n '/^_acceptance_evidence_line()/,/^}/p' "$HOOK" > "$T/fn.sh"
|
|
22
|
+
# 계기 보정: 추출이 비면 아래 픽스처가 «아무것도 아닌 것」을 재고 전부 통과한다.
|
|
23
|
+
if ! grep -q '_acceptance_evidence_line' "$T/fn.sh"; then
|
|
24
|
+
echo "❌ HARNESS-ERROR — _acceptance_evidence_line 이 $HOOK 에서 추출되지 않았다."
|
|
25
|
+
echo " 픽스처가 빈 함수를 잴 참이었다. 초록을 보고하지 않고 중단한다."
|
|
26
|
+
exit 1
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
FAIL=0
|
|
30
|
+
run() { bash -c 'set -uo pipefail; . "$1"; _acceptance_evidence_line "$2"' _ "$T/fn.sh" "${1:-}" 2>&1; }
|
|
31
|
+
lane() { # $1=id $2=marker body ('' = 마커 없음) $3..=출력에 반드시 있어야 하는 조각들
|
|
32
|
+
local id="$1" body="$2"; shift 2
|
|
33
|
+
local path="" out n rc
|
|
34
|
+
if [ -n "$body" ]; then path="$T/m.marker"; printf '%s\n' "$body" > "$path"; fi
|
|
35
|
+
out=$(run "$path"); rc=$?
|
|
36
|
+
if [ $rc -ne 0 ]; then
|
|
37
|
+
printf ' ❌ %-34s 함수가 rc=%s — 보고 전용인데 실패했다\n %s\n' "$id" "$rc" "$out"; FAIL=1; return
|
|
38
|
+
fi
|
|
39
|
+
for n in "$@"; do
|
|
40
|
+
if ! printf '%s' "$out" | grep -qF -- "$n"; then
|
|
41
|
+
printf ' ❌ %-34s «%s» 가 출력에 없다\n %s\n' "$id" "$n" "$out"; FAIL=1; return
|
|
42
|
+
fi
|
|
43
|
+
done
|
|
44
|
+
printf ' ✅ %-34s %s\n' "$id" "$(printf '%s' "$out" | head -1)"
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
echo "== 수용-근거 줄: 값이 그대로 드러나는가 =="
|
|
48
|
+
|
|
49
|
+
# L1 known-positive — 네 필드가 다 있으면 네 값이 다 나온다
|
|
50
|
+
lane L1-all-four "crossfamily: panel(codex,gemini) — 2라운드
|
|
51
|
+
standpoint: tier2(qasp) — 명령과 출력을 댔다
|
|
52
|
+
thirdparty: checked — 상류 소스 독해
|
|
53
|
+
oracle: known-pair — 양성/음성 픽스처" \
|
|
54
|
+
"crossfamily=panel(codex,gemini)" "standpoint=tier2(qasp)" "thirdparty=checked" "oracle=known-pair"
|
|
55
|
+
|
|
56
|
+
# L2 🟥 하중 레인 — DEGRADED 가 «통과» 뒤에 숨지 않는다 (이 레인 스위트의 존재 이유)
|
|
57
|
+
lane L2-degraded-visible "crossfamily: DEGRADED_PANEL_UNUSED — 패널 미사용
|
|
58
|
+
standpoint: not-applicable — Q0 ⓒ
|
|
59
|
+
oracle: none — 잴 값이 없다" \
|
|
60
|
+
"crossfamily=DEGRADED_PANEL_UNUSED" "standpoint=not-applicable"
|
|
61
|
+
|
|
62
|
+
# L3 UNKNOWN 도 마찬가지 — «안 봤다»가 «봤는데 괜찮다»로 안 접힌다
|
|
63
|
+
lane L3-unknown-visible "crossfamily: UNKNOWN — 안 봤다
|
|
64
|
+
standpoint: UNKNOWN — 안 봤다" \
|
|
65
|
+
"crossfamily=UNKNOWN" "standpoint=UNKNOWN"
|
|
66
|
+
|
|
67
|
+
# L4 부재는 «(없음)» 으로 — 빈 문자열이면 필드가 있었는지조차 안 보인다 (미측정≠0)
|
|
68
|
+
lane L4-absent-is-typed "axis2-model: opus" \
|
|
69
|
+
"crossfamily=(없음)" "standpoint=(없음)" "thirdparty=(없음)" "oracle=(없음)"
|
|
70
|
+
|
|
71
|
+
# L5 마커 자체가 없는 경로(경량 게이트) — 근거가 «있다»고 주장하지 않는다
|
|
72
|
+
lane L5-no-marker "" "n/a" "경량 게이트"
|
|
73
|
+
|
|
74
|
+
# L6 들여쓴 필드도 읽는다(마커는 손으로 쓰는 파일이다)
|
|
75
|
+
lane L6-indented " crossfamily: declined — 근거 있음" "crossfamily=declined"
|
|
76
|
+
|
|
77
|
+
# L7 첫 줄만 — 같은 키가 둘이면 앞의 것을 찍고 조용히 합치지 않는다
|
|
78
|
+
lane L7-first-wins "crossfamily: UNKNOWN — 안 봤다
|
|
79
|
+
crossfamily: panel(codex) — 나중 줄" "crossfamily=UNKNOWN"
|
|
80
|
+
|
|
81
|
+
# L8 🟥 첫 실사용이 잡은 결함 — 괄호 «안»에 공백이 있으면 값이 잘려 문법도 깨진 문자열이 찍혔다
|
|
82
|
+
# (`thirdparty: checked(확인한 것 = …)` → `checked(확인한`). 판별자는 «괄호가 닫혔는가» 하나.
|
|
83
|
+
lane L8-open-paren-trimmed "thirdparty: checked(확인한 것 = 초록 원문 대조)" "thirdparty=checked"
|
|
84
|
+
# L9 반대편 — 닫힌 괄호는 값의 일부라 그대로 살아야 한다(L8 의 수리가 이걸 깨면 안 된다)
|
|
85
|
+
lane L9-closed-paren-kept "crossfamily: panel(codex,gemini) — 2라운드" "crossfamily=panel(codex,gemini)"
|
|
86
|
+
lane L9b-tier-paren-kept "standpoint: tier2(qasp) — 명령과 출력" "standpoint=tier2(qasp)"
|
|
87
|
+
|
|
88
|
+
# L8b 잘린 값이 «그대로» 새어나오지 않는지 직접 확인 (needle 부재를 단언)
|
|
89
|
+
printf 'thirdparty: checked(확인한 것 = x)\n' > "$T/p.marker"
|
|
90
|
+
if run "$T/p.marker" | grep -qF 'checked(확인한'; then
|
|
91
|
+
echo " ❌ L8b 잘린 괄호 문자열이 아직 출력에 있다"; FAIL=1
|
|
92
|
+
else
|
|
93
|
+
echo " ✅ L8b 잘린 괄호 문자열은 출력에 없다"
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
# ── 컨트롤 A (판별력): 값이 다르면 출력도 달라야 한다 ─────────────────────
|
|
97
|
+
printf 'crossfamily: panel(codex) — x\n' > "$T/a.marker"
|
|
98
|
+
printf 'crossfamily: DEGRADED_SINGLE_FAMILY — y\n' > "$T/b.marker"
|
|
99
|
+
OA=$(run "$T/a.marker"); OB=$(run "$T/b.marker")
|
|
100
|
+
if [ "$OA" != "$OB" ] && printf '%s' "$OB" | grep -qF 'DEGRADED_SINGLE_FAMILY'; then
|
|
101
|
+
echo " ✅ CTRL-A 판별력: 서로 다른 값이 서로 다른 줄을 낸다"
|
|
102
|
+
else
|
|
103
|
+
echo " ❌ CTRL-A 판별력 없음 — 두 마커가 같은 줄을 냈다(계기가 값을 안 읽는다)"; FAIL=1
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
# ── 컨트롤 B (되돌림): 값 추출을 죽인 뮤턴트에서 L2 가 빨개지는가 ──────────
|
|
107
|
+
# 이게 없으면 «필드를 안 읽는 함수»도 이 스위트를 통과한다.
|
|
108
|
+
sed 's/^ for f in crossfamily.*/ for f in ; do :; done; out="";/' "$T/fn.sh" > "$T/mut.sh"
|
|
109
|
+
if ! grep -q 'out=""' "$T/mut.sh"; then
|
|
110
|
+
echo " ❌ CTRL-B 뮤턴트가 적용되지 않았다 — 되돌림 프로브가 장식이다"; FAIL=1
|
|
111
|
+
else
|
|
112
|
+
MOUT=$(bash -c 'set -uo pipefail; . "$1"; _acceptance_evidence_line "$2"' _ "$T/mut.sh" "$T/b.marker" 2>&1 || true)
|
|
113
|
+
if printf '%s' "$MOUT" | grep -qF 'DEGRADED_SINGLE_FAMILY'; then
|
|
114
|
+
echo " ❌ CTRL-B 뮤턴트인데도 값이 나온다 — 레인이 실물을 안 재고 있다"; FAIL=1
|
|
115
|
+
else
|
|
116
|
+
echo " ✅ CTRL-B 되돌림: 추출을 죽이면 값이 사라진다(레인이 실패할 수 있음을 증명)"
|
|
117
|
+
fi
|
|
118
|
+
fi
|
|
119
|
+
|
|
120
|
+
# ── 컨트롤 C (배선): 훅의 PASS 배너가 이 함수를 실제로 부르는가 ───────────
|
|
121
|
+
# 함수만 있고 호출부가 없으면 산문이다([[feedback_built_but_not_wired]]).
|
|
122
|
+
if grep -q '_acceptance_evidence_line "\${MARKER:-}"' "$HOOK" \
|
|
123
|
+
&& grep -q 'ALL AXES PASSED' "$HOOK"; then
|
|
124
|
+
echo " ✅ CTRL-C 배선: PASS 배너가 이 함수를 부른다"
|
|
125
|
+
else
|
|
126
|
+
echo " ❌ CTRL-C 배선 없음 — 정의만 있고 PASS 배너가 안 부른다"; FAIL=1
|
|
127
|
+
fi
|
|
128
|
+
|
|
129
|
+
# ── 컨트롤 D (무해): 이 함수는 종료코드를 바꾸지 않는다 (채널이지 게이트가 아니다) ──
|
|
130
|
+
run "" >/dev/null 2>&1; RC_NONE=$?
|
|
131
|
+
run "$T/b.marker" >/dev/null 2>&1; RC_DEG=$?
|
|
132
|
+
if [ "$RC_NONE" -eq 0 ] && [ "$RC_DEG" -eq 0 ]; then
|
|
133
|
+
echo " ✅ CTRL-D 채널: 마커 부재도 DEGRADED 도 rc=0 (막지 않는다)"
|
|
134
|
+
else
|
|
135
|
+
echo " ❌ CTRL-D 이 함수가 종료코드를 바꾼다 (none=$RC_NONE degraded=$RC_DEG) — 채널이 아니라 게이트가 됐다"; FAIL=1
|
|
136
|
+
fi
|
|
137
|
+
|
|
138
|
+
echo "── gate-two-verdicts lanes: $([ $FAIL -eq 0 ] && echo 'all green' || echo 'FAILED')"
|
|
139
|
+
exit "$FAIL"
|
|
@@ -103,7 +103,7 @@ check carveout "README.md" "npm-shipped surface — first-scre
|
|
|
103
103
|
check carveout "README.ko.md" "language variants route the same way"
|
|
104
104
|
check carveout "README.zh-CN.md" "🟥 5-char locale forms too — cross-family caught [a-z]{2} missing these"
|
|
105
105
|
check uncovered ".claude/registry/README.md" "🟥 STILL UNCOVERED — in files[] but not root; named, not closed"
|
|
106
|
-
check uncovered "package.json" "files[] / version changes are unclassified"
|
|
106
|
+
check uncovered "package.json" "files[] / version changes are unclassified" # portability-noqa: "package.json" is a string literal fed to classify()'s regex test, never opened from disk — a ported repo without this exact file still exercises the same classifier logic
|
|
107
107
|
check uncovered ".github/workflows/validate.yml" "🟥 CI definition — changing what CI runs is unclassified"
|
|
108
108
|
check uncovered "scripts/memory_link_check.py" "🟥 .py is not .sh — shipped python is unclassified"
|
|
109
109
|
|
|
@@ -30,18 +30,32 @@ mkfixture() { # $1 = dir
|
|
|
30
30
|
printf '#!/usr/bin/env bash\nbash scripts/lane_runner_check.sh\n' > "$d/scripts/selfcheck.sh"
|
|
31
31
|
printf '#!/usr/bin/env bash\n: orphan lane suite\n' > "$d/scripts/test_orphan_lanes.sh"
|
|
32
32
|
}
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
# ONE execution per fixture, stdout AND rc from the SAME run — never two separate invocations.
|
|
34
|
+
# Until 2026-09-03 this was split into run()+rcof(), each running lane_runner_check.sh on its own.
|
|
35
|
+
# lane_runner_check.sh reads live filesystem/git state on every invocation (its own mktemp, a
|
|
36
|
+
# `.git` up-chain walk, `git ls-files -z`, a python3 subprocess) and is not guaranteed to answer
|
|
37
|
+
# identically twice in a row under CI resource pressure — so a call site combining `$(run "$D")`
|
|
38
|
+
# with a SEPARATE `$(rcof "$D")` could silently pair stdout from one run with the exit code of a
|
|
39
|
+
# DIFFERENT run. That produced an impossible-looking CI flake: rc=0 with the literal substring
|
|
40
|
+
# "declared debt" absent from $out, even though every rc=0 exit path in lane_runner_check.sh
|
|
41
|
+
# unconditionally prints that substring (traced exhaustively — see
|
|
42
|
+
# tracks/_meta/armC_armC-wt2_report2_2026-09-03.md). Sets LRC_OUT/LRC_RC as globals: this file is
|
|
43
|
+
# flat top-level (no `local` scoping across call sites), and bash functions cannot return two
|
|
44
|
+
# values through their exit status alone.
|
|
45
|
+
run_check() { # $1 = fixture dir
|
|
46
|
+
LRC_OUT="$(cd "$1" && bash scripts/lane_runner_check.sh 2>&1)"; LRC_RC=$?
|
|
47
|
+
}
|
|
35
48
|
|
|
36
49
|
# ── L1 known-POSITIVE: an undeclared orphan suite must FAIL ───────────────────────────────────
|
|
37
50
|
# Without this arm every lane below proves nothing: a checker that passed unconditionally would
|
|
38
51
|
# satisfy all the "declaration suppresses it" lanes.
|
|
39
52
|
D="$T/l1"; mkfixture "$D"
|
|
40
|
-
|
|
53
|
+
run_check "$D"; out="$LRC_OUT"; rc="$LRC_RC"
|
|
41
54
|
if [ "$rc" = "1" ] && printf '%s' "$out" | grep -q 'test_orphan_lanes.sh'; then
|
|
42
55
|
ok "L1 known-positive: undeclared orphan suite → rc=1 and it is named"
|
|
43
56
|
else
|
|
44
57
|
bad "L1 known-positive did not fire (rc=$rc) — every lane below is meaningless without it"
|
|
58
|
+
printf '%s\n' "$out" | sed 's/^/ │ /'
|
|
45
59
|
fi
|
|
46
60
|
|
|
47
61
|
# ── IDX7 the FAIL path must EXECUTE the provenance note, not merely contain a call to it ──────
|
|
@@ -55,7 +69,7 @@ fi
|
|
|
55
69
|
# It asserts the SHAPE of provenance, not the exact sentence: the fixture is a non-git dir, so the
|
|
56
70
|
# honest answer there is the DISK one, and pinning the index wording would make the lane wrong.
|
|
57
71
|
D="$T/idx7"; mkfixture "$D"
|
|
58
|
-
|
|
72
|
+
run_check "$D"; out="$LRC_OUT"
|
|
59
73
|
if printf '%s' "$out" | grep -qE 'unbound variable|command not found|syntax error'; then
|
|
60
74
|
bad "IDX7 the FAIL path errored: $(printf '%s' "$out" | grep -E 'unbound|not found|syntax' | head -1)"
|
|
61
75
|
elif printf '%s' "$out" | grep -qE 'ⓘ|🟥'; then
|
|
@@ -67,41 +81,45 @@ fi
|
|
|
67
81
|
# ── L2 NO-OP: no declaration file → behaviour is exactly the L1 behaviour ─────────────────────
|
|
68
82
|
# This is the arm that protects THIS repo, which ships no company/ directory.
|
|
69
83
|
D="$T/l2"; mkfixture "$D"
|
|
70
|
-
|
|
84
|
+
run_check "$D"; out="$LRC_OUT"
|
|
71
85
|
if ! printf '%s' "$out" | grep -q 'lane_declarations'; then
|
|
72
86
|
ok "L2 no-op: absent declaration file is silent (no mention in output)"
|
|
73
87
|
else
|
|
74
88
|
bad "L2 no-op: output mentions the declaration file when none exists"
|
|
89
|
+
printf '%s\n' "$out" | sed 's/^/ │ /'
|
|
75
90
|
fi
|
|
76
91
|
|
|
77
92
|
# ── L3 org EXEMPT suppresses the orphan ───────────────────────────────────────────────────────
|
|
78
93
|
D="$T/l3"; mkfixture "$D"; mkdir -p "$D/company"
|
|
79
94
|
printf 'exempt:\n - test_orphan_lanes.sh # org-owned, runs in the org CI only\n' > "$D/company/lane_declarations.yaml"
|
|
80
|
-
|
|
95
|
+
run_check "$D"; out="$LRC_OUT"; rc="$LRC_RC"
|
|
81
96
|
if [ "$rc" = "0" ] && printf '%s' "$out" | grep -q '1 exempt'; then
|
|
82
97
|
ok "L3 org exempt: declared suite no longer fails the check (rc=0, counted exempt)"
|
|
83
98
|
else
|
|
84
|
-
bad "L3 org exempt did not suppress (rc=$rc)
|
|
99
|
+
bad "L3 org exempt did not suppress (rc=$rc)"
|
|
100
|
+
printf '%s\n' "$out" | sed 's/^/ │ /'
|
|
85
101
|
fi
|
|
86
102
|
|
|
87
103
|
# ── L4 org DEBT suppresses AND is counted as debt ─────────────────────────────────────────────
|
|
88
104
|
D="$T/l4"; mkfixture "$D"; mkdir -p "$D/company"
|
|
89
105
|
printf 'debt:\n - test_orphan_lanes.sh\n' > "$D/company/lane_declarations.yaml"
|
|
90
|
-
|
|
106
|
+
run_check "$D"; out="$LRC_OUT"; rc="$LRC_RC"
|
|
91
107
|
if [ "$rc" = "0" ] && printf '%s' "$out" | grep -q 'declared debt'; then
|
|
92
108
|
ok "L4 org debt: declared suite suppressed and reported as debt"
|
|
93
109
|
else
|
|
94
110
|
bad "L4 org debt did not land (rc=$rc)"
|
|
111
|
+
printf '%s\n' "$out" | sed 's/^/ │ /'
|
|
95
112
|
fi
|
|
96
113
|
|
|
97
114
|
# ── L5 VISIBILITY: an org-declared list must never look like this repo's own zero ─────────────
|
|
98
115
|
D="$T/l5"; mkfixture "$D"; mkdir -p "$D/company"
|
|
99
116
|
printf 'debt:\n - test_orphan_lanes.sh\n' > "$D/company/lane_declarations.yaml"
|
|
100
|
-
|
|
117
|
+
run_check "$D"; out="$LRC_OUT"
|
|
101
118
|
if printf '%s' "$out" | grep -q 'org-declared'; then
|
|
102
119
|
ok "L5 visibility: output names the org file and its counts"
|
|
103
120
|
else
|
|
104
121
|
bad "L5 visibility: org declarations are invisible in the output"
|
|
122
|
+
printf '%s\n' "$out" | sed 's/^/ │ /'
|
|
105
123
|
fi
|
|
106
124
|
|
|
107
125
|
# ── L6 FAIL-CLOSED on an unparseable file ─────────────────────────────────────────────────────
|
|
@@ -109,41 +127,46 @@ fi
|
|
|
109
127
|
# render UNMEASURED as ZERO, and it would do so on the arm where a fork is relying on the file.
|
|
110
128
|
D="$T/l6"; mkfixture "$D"; mkdir -p "$D/company"
|
|
111
129
|
printf 'exempt:\n this line is not an entry\n' > "$D/company/lane_declarations.yaml"
|
|
112
|
-
|
|
130
|
+
run_check "$D"; out="$LRC_OUT"; rc="$LRC_RC"
|
|
113
131
|
if [ "$rc" = "2" ] && printf '%s' "$out" | grep -q 'UNMEASURED'; then
|
|
114
132
|
ok "L6 fail-closed: unparseable declaration file → rc=2 and says UNMEASURED"
|
|
115
133
|
else
|
|
116
134
|
bad "L6 fail-closed FAILED (rc=$rc) — a malformed file was read as 'no declarations'"
|
|
135
|
+
printf '%s\n' "$out" | sed 's/^/ │ /'
|
|
117
136
|
fi
|
|
118
137
|
|
|
119
138
|
# ── L6b control for L6: the SAME orphan tree with a WELL-FORMED file must not exit 2 ──────────
|
|
120
139
|
# Without this, L6 would also pass on a checker that exits 2 for any declaration file at all.
|
|
121
140
|
D="$T/l6b"; mkfixture "$D"; mkdir -p "$D/company"
|
|
122
141
|
printf 'exempt:\n - test_orphan_lanes.sh\n' > "$D/company/lane_declarations.yaml"
|
|
123
|
-
|
|
142
|
+
run_check "$D"; out="$LRC_OUT"; rc="$LRC_RC"
|
|
143
|
+
if [ "$rc" != "2" ]; then
|
|
124
144
|
ok "L6b control: a well-formed file does NOT trip the fail-closed arm"
|
|
125
145
|
else
|
|
126
146
|
bad "L6b control: every declaration file exits 2 — L6 proved nothing"
|
|
147
|
+
printf '%s\n' "$out" | sed 's/^/ │ /'
|
|
127
148
|
fi
|
|
128
149
|
|
|
129
150
|
# ── L7 unknown section is a parse failure, not a silent skip ──────────────────────────────────
|
|
130
151
|
D="$T/l7"; mkfixture "$D"; mkdir -p "$D/company"
|
|
131
152
|
printf 'allowed:\n - test_orphan_lanes.sh\n' > "$D/company/lane_declarations.yaml"
|
|
132
|
-
|
|
133
|
-
if [ "$
|
|
153
|
+
run_check "$D"; out="$LRC_OUT"; rc="$LRC_RC"
|
|
154
|
+
if [ "$rc" = "2" ] && printf '%s' "$out" | grep -q "unknown section"; then
|
|
134
155
|
ok "L7 unknown section → rc=2 and names the section"
|
|
135
156
|
else
|
|
136
|
-
bad "L7 unknown section was tolerated — a typo'd header would silently declare nothing"
|
|
157
|
+
bad "L7 unknown section was tolerated — a typo'd header would silently declare nothing (rc=$rc)"
|
|
158
|
+
printf '%s\n' "$out" | sed 's/^/ │ /'
|
|
137
159
|
fi
|
|
138
160
|
|
|
139
161
|
# ── L8 the same name in both sections is ambiguous, not last-wins ─────────────────────────────
|
|
140
162
|
D="$T/l8"; mkfixture "$D"; mkdir -p "$D/company"
|
|
141
163
|
printf 'exempt:\n - test_orphan_lanes.sh\ndebt:\n - test_orphan_lanes.sh\n' > "$D/company/lane_declarations.yaml"
|
|
142
|
-
|
|
143
|
-
if [ "$
|
|
164
|
+
run_check "$D"; out="$LRC_OUT"; rc="$LRC_RC"
|
|
165
|
+
if [ "$rc" = "2" ] && printf '%s' "$out" | grep -q "both exempt and debt"; then
|
|
144
166
|
ok "L8 same suite in both sections → rc=2 (ambiguous, not silently resolved)"
|
|
145
167
|
else
|
|
146
|
-
bad "L8 double declaration was silently resolved"
|
|
168
|
+
bad "L8 double declaration was silently resolved (rc=$rc)"
|
|
169
|
+
printf '%s\n' "$out" | sed 's/^/ │ /'
|
|
147
170
|
fi
|
|
148
171
|
|
|
149
172
|
# ── L9 a stale entry WARNS but does not block (same voice as upstream DEBT hygiene) ───────────
|
|
@@ -151,20 +174,23 @@ fi
|
|
|
151
174
|
# would train forks to delete the file, which costs more than a stale line.
|
|
152
175
|
D="$T/l9"; mkfixture "$D"; mkdir -p "$D/company"
|
|
153
176
|
printf 'exempt:\n - test_orphan_lanes.sh\n - test_deleted_lanes.sh\n' > "$D/company/lane_declarations.yaml"
|
|
154
|
-
|
|
177
|
+
run_check "$D"; out="$LRC_OUT"; rc="$LRC_RC"
|
|
155
178
|
if [ "$rc" = "0" ] && printf '%s' "$out" | grep -q 'test_deleted_lanes.sh'; then
|
|
156
179
|
ok "L9 stale entry: warns and names it, does not block (rc=0)"
|
|
157
180
|
else
|
|
158
181
|
bad "L9 stale entry handling wrong (rc=$rc)"
|
|
182
|
+
printf '%s\n' "$out" | sed 's/^/ │ /'
|
|
159
183
|
fi
|
|
160
184
|
|
|
161
185
|
# ── L10 comments and blank lines are not entries ──────────────────────────────────────────────
|
|
162
186
|
D="$T/l10"; mkfixture "$D"; mkdir -p "$D/company"
|
|
163
187
|
printf '# org lane declarations\n\nexempt:\n\n - test_orphan_lanes.sh # reason lives here\n' > "$D/company/lane_declarations.yaml"
|
|
164
|
-
|
|
188
|
+
run_check "$D"; out="$LRC_OUT"; rc="$LRC_RC"
|
|
189
|
+
if [ "$rc" = "0" ]; then
|
|
165
190
|
ok "L10 comments/blank lines tolerated (a file people can annotate)"
|
|
166
191
|
else
|
|
167
|
-
bad "L10 a commented, spaced file failed to parse"
|
|
192
|
+
bad "L10 a commented, spaced file failed to parse (rc=$rc)"
|
|
193
|
+
printf '%s\n' "$out" | sed 's/^/ │ /'
|
|
168
194
|
fi
|
|
169
195
|
|
|
170
196
|
# ── L11/L12 embedded --self-test subjects (found→extend, 2026-08-14) ──────────────────────────
|
|
@@ -186,7 +212,7 @@ mkfixture_clean() { # $1 = dir
|
|
|
186
212
|
# L11 known-POSITIVE: an unwired --self-test subject is reported, advisory (rc stays 0).
|
|
187
213
|
D="$T/l11"; mkfixture_clean "$D"
|
|
188
214
|
printf '#!/usr/bin/env bash\n[ "${1:-}" = "--self-test" ] && { echo ok; exit 0; }\n' > "$D/scripts/orphan_probe.sh"
|
|
189
|
-
|
|
215
|
+
run_check "$D"; out="$LRC_OUT"; rc="$LRC_RC"
|
|
190
216
|
if [ "$rc" = "0" ] && printf '%s' "$out" | grep -q 'orphan_probe'; then
|
|
191
217
|
ok "L11 known-positive: unwired --self-test subject named, advisory (rc=0)"
|
|
192
218
|
else
|
|
@@ -206,7 +232,7 @@ for _subj in orphan_probe; do
|
|
|
206
232
|
bash "scripts/$_subj.sh" --self-test
|
|
207
233
|
done
|
|
208
234
|
SC
|
|
209
|
-
|
|
235
|
+
run_check "$D"; out="$LRC_OUT"
|
|
210
236
|
if ! printf '%s' "$out" | grep -q 'orphan_probe'; then
|
|
211
237
|
ok "L12 known-negative: --self-test subject wired via _subj for-loop is not reported"
|
|
212
238
|
else
|
|
@@ -228,7 +254,7 @@ bash scripts/lane_runner_check.sh
|
|
|
228
254
|
bash scripts/test_ok_lanes.sh
|
|
229
255
|
bash scripts/orphan_probe.sh --self-test >/dev/null 2>&1
|
|
230
256
|
SC
|
|
231
|
-
|
|
257
|
+
run_check "$D"; out="$LRC_OUT"
|
|
232
258
|
if ! printf '%s' "$out" | grep -q 'orphan_probe'; then
|
|
233
259
|
ok "L13 known-negative: --self-test subject wired via direct dispatch is not reported"
|
|
234
260
|
else
|
|
@@ -262,7 +288,7 @@ cat > "$D/scripts/orphan_probe.sh" <<'SUBJ'
|
|
|
262
288
|
# bash scripts/orphan_probe.sh --self-test # known-pair calibration
|
|
263
289
|
[ "${1:-}" = "--self-test" ] && { echo ok; exit 0; }
|
|
264
290
|
SUBJ
|
|
265
|
-
|
|
291
|
+
run_check "$D"; out="$LRC_OUT"; rc="$LRC_RC"
|
|
266
292
|
if [ "$rc" = "0" ] && printf '%s' "$out" | grep -q 'orphan_probe'; then
|
|
267
293
|
ok "L14 known-positive: subject's own usage comment is not a caller — still reported unwired"
|
|
268
294
|
else
|
|
@@ -280,7 +306,7 @@ bash scripts/lane_runner_check.sh
|
|
|
280
306
|
bash scripts/test_ok_lanes.sh
|
|
281
307
|
# historical note: we used to run `bash scripts/orphan_probe.sh --self-test` here, removed 2026-01-01
|
|
282
308
|
SC
|
|
283
|
-
|
|
309
|
+
run_check "$D"; out="$LRC_OUT"; rc="$LRC_RC"
|
|
284
310
|
if [ "$rc" = "0" ] && printf '%s' "$out" | grep -q 'orphan_probe'; then
|
|
285
311
|
ok "L15 known-positive: a commented-out dispatch in another file is not a caller"
|
|
286
312
|
else
|
|
@@ -302,7 +328,7 @@ usage() { echo "run: bash scripts/orphan_probe.sh --self-test"; }
|
|
|
302
328
|
[ "${1:-}" = "--self-test" ] && { echo ok; exit 0; }
|
|
303
329
|
usage
|
|
304
330
|
SUBJ
|
|
305
|
-
|
|
331
|
+
run_check "$D"; out="$LRC_OUT"; rc="$LRC_RC"
|
|
306
332
|
if [ "$rc" = "0" ] && printf '%s' "$out" | grep -q 'orphan_probe'; then
|
|
307
333
|
ok "L16 known-positive: a subject's own NON-comment self-reference is not a caller"
|
|
308
334
|
else
|
|
@@ -323,36 +349,36 @@ fi
|
|
|
323
349
|
# ([[feedback_not_found_is_not_zero_family]]).
|
|
324
350
|
_ISN=$(mktemp); sed -n '/^index_source_note()/,/^}/p' "$CHECK" > "$_ISN"
|
|
325
351
|
if ! grep -q 'UNMEASURED' "$_ISN"; then
|
|
326
|
-
|
|
352
|
+
bad "IDX-harness — index_source_note did not extract from $CHECK (lanes would measure nothing)"
|
|
327
353
|
else
|
|
328
354
|
# shellcheck disable=SC1090
|
|
329
355
|
. "$_ISN"
|
|
330
356
|
o=$(index_source_note "index")
|
|
331
357
|
printf '%s' "$o" | grep -q "INDEX 기준" && printf '%s' "$o" | grep -q "스테이징" \
|
|
332
358
|
&& ok "IDX1 index mode names the index AND the staging remedy" \
|
|
333
|
-
||
|
|
359
|
+
|| bad "IDX1 index mode note missing/incomplete: $o"
|
|
334
360
|
|
|
335
361
|
o=$(index_source_note "nongit")
|
|
336
362
|
printf '%s' "$o" | grep -q "DISK" && ok "IDX2 nongit mode says DISK, not index" \
|
|
337
|
-
||
|
|
363
|
+
|| bad "IDX2 nongit: $o"
|
|
338
364
|
|
|
339
365
|
o=$(index_source_note "UNMEASURED")
|
|
340
366
|
printf '%s' "$o" | grep -q "못 셌다" && ok "IDX3 UNMEASURED says «could not count», not «none»" \
|
|
341
|
-
||
|
|
367
|
+
|| bad "IDX3 UNMEASURED: $o"
|
|
342
368
|
|
|
343
369
|
# 🟥 CONTROL — an unknown/empty mode must stay SILENT. Inventing a source line for a state the
|
|
344
370
|
# check does not recognise is worse than saying nothing, and without this lane the function
|
|
345
371
|
# could pass IDX1–IDX3 while emitting the index sentence for every input.
|
|
346
372
|
o=$(index_source_note "")
|
|
347
373
|
[ -z "$o" ] && ok "IDX4 control — empty mode is silent (no invented provenance)" \
|
|
348
|
-
||
|
|
374
|
+
|| bad "IDX4 control — empty mode spoke: $o"
|
|
349
375
|
|
|
350
376
|
# 🟥 CONTROL 2 — the three states must be DISTINGUISHABLE from each other, not merely non-empty.
|
|
351
377
|
a=$(index_source_note "index"); b=$(index_source_note "nongit"); c=$(index_source_note "UNMEASURED")
|
|
352
378
|
if [ "$a" != "$b" ] && [ "$b" != "$c" ] && [ "$a" != "$c" ]; then
|
|
353
379
|
ok "IDX5 control — the three states produce three different sentences"
|
|
354
380
|
else
|
|
355
|
-
|
|
381
|
+
bad "IDX5 control — states collapsed into the same sentence"
|
|
356
382
|
fi
|
|
357
383
|
fi
|
|
358
384
|
rm -f "$_ISN"
|
|
@@ -362,7 +388,7 @@ if sed -n '/lane suite(s) with no runner and no declaration/,/Fix by ONE of/p' "
|
|
|
362
388
|
| grep -q 'index_source_note'; then
|
|
363
389
|
ok "IDX6 the note is CALLED on the FAIL path (not just defined)"
|
|
364
390
|
else
|
|
365
|
-
|
|
391
|
+
bad "IDX6 index_source_note is defined but the FAIL path does not call it"
|
|
366
392
|
fi
|
|
367
393
|
|
|
368
394
|
echo "----"
|