refactor(gates): source the boundary resolver into skill-size-check
Why: scripts/skill-size-check.sh embedded a byte-identical 1,061-line copy
of the ADR-0020 boundary resolver only because it was also exported
through .pre-commit-hooks.yaml, whose consumers could not reach a file
inside the plugin. 4de5b6b retired that export, so the hook now runs only
in this repo and can source factory-audit's lib-boundary-resolver.sh like
validate.sh does. One copy removes the edit-one-paste-the-other hazard.
Implementation Notes:
- The hook's Python program is assembled from its own preamble, the
library's resolver and its own checks, read from quoted here-docs. The
assembled program matches the old one line for line except one comment,
and the hook's stdout, stderr and exit code are identical over every
corpus SKILL.md and the 26 differential-suite fixtures.
- The hook fails closed, naming the library, when it is missing or
defines no resolver.
- test-adr0020-contract.sh assertion 1 now pins the single copy: one
marker pair in the library, none in the hook, fail-closed on a missing
or gutted library, and a sentinel planted in a copied library that must
appear in the hook's output. 1a expects exactly one authority. 27 -> 29
passes.
- ADR-0020 and ADR-0025 carry dated amendments; gates.md and the
library, hook and mode-library comments no longer describe two copies.
- factory-audit is new on this branch, so the version-bump gate exempts
it; kyberforge is already at 2.0.0 against main's 1.6.2.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -27,6 +27,14 @@ applies the body tiers on the skill path only. The two copies must still stay by
|
||||
plugin script cannot source the root one, which is why a second copy exists at all. Read every
|
||||
"three" below as the count at the time of writing.
|
||||
|
||||
**Amended again (2026-09-16): one copy.** `scripts/skill-size-check.sh` no longer embeds the
|
||||
resolver. It sources `factory-audit/scripts/lib-boundary-resolver.sh` by path and fails closed if the
|
||||
library is missing or defines no resolver. The embedded copy had been kept only because the hook was
|
||||
also exported through `.pre-commit-hooks.yaml`, whose consumers could not reach a file inside the
|
||||
plugin; `4de5b6b` retired that export (ADR-0014), so the hook runs only inside this repo. The
|
||||
"byte-identical" sentence above is superseded: there is nothing left to keep identical, and
|
||||
`tests/test-adr0020-contract.sh` assertion 1 now pins the single copy instead of hashing a pair.
|
||||
|
||||
## Context
|
||||
|
||||
Every `file:line` citation in this ADR is against the base commit the decision was taken on,
|
||||
|
||||
@@ -10,6 +10,13 @@ below (both exported hook IDs survive) and the case 33 port no longer describe t
|
||||
deleted, and its one-plugin narrowing guard is now a property of case 32. See
|
||||
[ADR-0014's amendment](0014-vale-prefilter-ships-from-the-plugin.md#amendment-2026-09-16-the-external-hook-contract-is-retired).
|
||||
|
||||
**Amended (2026-09-16): the root hook sources the resolver.** Point 6 below records sourcing the
|
||||
resolver into `scripts/skill-size-check.sh` as refuted, because the hook was consumed through
|
||||
`.pre-commit-hooks.yaml`. That manifest is retired (above), so the reason no longer holds: the hook now
|
||||
sources `lib-boundary-resolver.sh`, the repo holds one resolver copy, and the contract test's
|
||||
assertion 1 pins that copy rather than hashing two. Assertion 1a's "exactly those two files" is now
|
||||
exactly one. Point 6 is left as the record of the decision at the time.
|
||||
|
||||
## Context
|
||||
|
||||
Every figure below was measured against the worktree on 2026-09-15. Re-derive rather than quote; the
|
||||
|
||||
@@ -410,14 +410,17 @@ skills. They are copied rather than imported because a cache-installed plugin's
|
||||
files outside their own plugin directory. `tests/test-skill-size-check.sh` asserts the copies agree,
|
||||
so drift fails CI rather than silently letting an audit bless a skill the commit hook then rejects.
|
||||
|
||||
**The shared boundary resolver is now two copies, not three** (ADR-0025). `scripts/skill-size-check.sh`
|
||||
still carries it embedded between `BEGIN`/`END ADR-0020 SHARED BOUNDARY RESOLVER` markers; the two
|
||||
plugin copies that used to sit inside `skill-audit`'s and `agent-audit`'s `validate.sh` collapsed
|
||||
into the single `factory-audit/scripts/lib-boundary-resolver.sh`, sourced by that skill's scripts.
|
||||
The two remaining copies must still stay byte-identical — a plugin script cannot source the root
|
||||
one, which is the constraint that forces a copy to exist at all.
|
||||
**The shared boundary resolver is one copy** (ADR-0025, then 2026-09-16). It lives between the
|
||||
`BEGIN`/`END ADR-0020 SHARED BOUNDARY RESOLVER` markers in `factory-audit/scripts/lib-boundary-resolver.sh`.
|
||||
ADR-0025 collapsed the two copies inside `skill-audit`'s and `agent-audit`'s `validate.sh` into that
|
||||
file. `scripts/skill-size-check.sh` kept an embedded, byte-identical third copy while it was also
|
||||
exported through `.pre-commit-hooks.yaml`, whose consumers could not reach a file inside the plugin.
|
||||
`4de5b6b` retired that export (ADR-0014), so the hook now sources the library by path and fails closed
|
||||
if the library is missing or defines no resolver.
|
||||
|
||||
`tests/test-adr0020-contract.sh` pins that arrangement, and one of its assertions was green on a
|
||||
`tests/test-adr0020-contract.sh` pins that arrangement: the library carries the only marker pair,
|
||||
the hook carries none, the hook fails closed without the library, and a sentinel planted in a copied
|
||||
library proves the hook executes the library's text. One of its assertions was green on a
|
||||
defect it named. "`validate.sh` sources the resolver in **both mode branches**" was implemented as a
|
||||
file-wide `grep -Ec … -ge 2`, which cannot see a branch at all: delete the `agent)` arm's source line
|
||||
and duplicate the `skill)` arm's, and the file-wide count is still 2 and the assertion still passes,
|
||||
@@ -425,7 +428,8 @@ with the agent path running no resolver or some other one. It is now a **per-arm
|
||||
each arm of `validate.sh`'s `case "$MODE" in` block must carry exactly one `source` line inside its
|
||||
own body, and the file must carry exactly those two — with a mutation self-test that performs that
|
||||
exact count-preserving edit on a copy and requires the check to fail on it. The suite went 25 → 28
|
||||
cases.
|
||||
cases. It stood at 27 before the 2026-09-16 change and 29 after it, which replaced the two-copy
|
||||
hash and its line-count floor with the six one-copy assertions above.
|
||||
|
||||
### `python3` and PyYAML are hard requirements
|
||||
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
#!/usr/bin/env bash
|
||||
# lib-boundary-resolver.sh — SOURCED, never executed.
|
||||
#
|
||||
# The ADR-0020 shared boundary resolver, as ONE copy for this skill. Both of
|
||||
# validate.sh's modes compose it into the Python program they run, so the
|
||||
# skill-mode and agent-mode check suites resolve boundary targets through the
|
||||
# same code rather than through two copies that can drift apart.
|
||||
# The ADR-0020 shared boundary resolver — the ONE copy in the repo. Both of
|
||||
# validate.sh's modes compose it into the Python program they run, and so does
|
||||
# the repo-root hook scripts/skill-size-check.sh, so the audit and the commit
|
||||
# hook resolve boundary targets through the same code rather than through
|
||||
# copies that can drift apart.
|
||||
#
|
||||
# The resolver is Python, and bash cannot source Python, so the block is held
|
||||
# in a shell variable filled from a QUOTED here-doc: nothing inside it is
|
||||
# expanded, substituted or rewritten, and the text between the two markers
|
||||
# below is therefore byte-identical to the copy in scripts/skill-size-check.sh
|
||||
# that tests/test-adr0020-contract.sh hashes. The markers stay on lines of
|
||||
# their own, at column 0, exactly once each, so `sed -n '/^BEGIN$/,/^END$/p'`
|
||||
# extracts the same span here as it does from the scripts the test already
|
||||
# reads. Edit one copy, then paste it over the others.
|
||||
# expanded, substituted or rewritten, and every consumer runs exactly the text
|
||||
# between the two markers below. The markers stay on lines of their own, at
|
||||
# column 0, exactly once each: tests/test-adr0020-contract.sh extracts the span
|
||||
# with `sed -n '/^BEGIN$/,/^END$/p'`, and asserts no other file carries them.
|
||||
#
|
||||
# The here-doc is consumed by the `read` BUILTIN rather than by `$(cat <<...)`.
|
||||
# This file is sourced by validate.sh before the mode-specific python3/PyYAML
|
||||
@@ -26,7 +25,7 @@
|
||||
# exactly ONE newline, never a run: blank lines at the end of a chunk are part
|
||||
# of the program text the entry script reassembles, and stripping every
|
||||
# trailing newline deleted them. The here-doc itself is unchanged: still
|
||||
# QUOTED, still byte-identical between its markers.
|
||||
# QUOTED, still verbatim between its markers.
|
||||
#
|
||||
# Self-containment (agentskills.io, skill-author/references/deployment-modes.md)
|
||||
# binds BETWEEN skills, not within one: a cache-installed plugin copies each
|
||||
@@ -34,21 +33,22 @@
|
||||
# travels with the skill and is always readable. That is why this is sourced
|
||||
# here and duplicated across skill boundaries elsewhere.
|
||||
#
|
||||
# Consumed by: validate.sh (both modes), via $KYBERFORGE_RESOLVER_PY.
|
||||
# Consumed by: validate.sh (both modes) and scripts/skill-size-check.sh, via
|
||||
# $KYBERFORGE_RESOLVER_PY. The root hook reaches into this plugin by path, which
|
||||
# is safe only because it runs solely inside this repo — 4de5b6b retired the
|
||||
# published hook manifest that once made it run elsewhere (ADR-0014).
|
||||
# shellcheck shell=bash
|
||||
# shellcheck disable=SC2034
|
||||
|
||||
IFS='' read -r -d '' KYBERFORGE_RESOLVER_PY <<'KYBERFORGE_ADR0020_RESOLVER_PY' || true
|
||||
# ===== BEGIN ADR-0020 SHARED BOUNDARY RESOLVER =====
|
||||
# ONE resolver, embedded VERBATIM in two scripts (ADR-0025 retired the third):
|
||||
# scripts/skill-size-check.sh
|
||||
# plugins/kyberforge/.apm/skills/factory-audit/scripts/lib-boundary-resolver.sh
|
||||
# The block between these markers must stay byte-identical in both. It is copied
|
||||
# rather than imported because a cache-installed plugin's scripts cannot read
|
||||
# files outside their own plugin directory, and this repo-root hook is kept fit for
|
||||
# a published hook manifest (retired; ADR-0014), where only entry[0] is rewritten --
|
||||
# so no single file is reachable by both (the same constraint that duplicates the
|
||||
# ADR-0020 constants). Edit one copy, then paste it over the other.
|
||||
# ONE resolver, and this is its only copy. Sourced from this file by:
|
||||
# plugins/kyberforge/.apm/skills/factory-audit/scripts/validate.sh (both modes)
|
||||
# scripts/skill-size-check.sh (the repo-root commit hook)
|
||||
# ADR-0025 retired the copies in the two pre-merge audit skills, and the
|
||||
# 2026-09-16 change retired the copy embedded in the root hook, which had been
|
||||
# kept only while that hook was also exported through a published hook manifest
|
||||
# (retired by 4de5b6b; ADR-0014). Edit it here; there is nothing to paste over.
|
||||
#
|
||||
# Requires: glob, os, re, yaml (imported by the host script; PyYAML is a hard
|
||||
# dependency, preflighted in bash before the interpreter starts).
|
||||
|
||||
@@ -69,9 +69,9 @@ import yaml
|
||||
# resolver block below pins the reads; this pins the writes.
|
||||
#
|
||||
# Deliberately OUTSIDE the ADR-0020 shared boundary resolver block: the two
|
||||
# validate.sh copies print findings, skill-size-check.sh has its own top-level
|
||||
# equivalent, and tests/test-adr0020-contract.sh hashes that block for
|
||||
# byte-identity across all three.
|
||||
# validate.sh modes print findings, skill-size-check.sh has its own top-level
|
||||
# equivalent, and the block is one sourced copy all three share, so each
|
||||
# consumer's own startup stays in its own preamble.
|
||||
for _stream in (sys.stdout, sys.stderr):
|
||||
try:
|
||||
_stream.reconfigure(encoding='utf-8')
|
||||
@@ -145,9 +145,11 @@ COPILOT_BODY_LIMIT = 30000
|
||||
# every session exactly like a skill's, so agents take the SAME description
|
||||
# gates. These two constants are DUPLICATED in three places:
|
||||
# scripts/skill-size-check.sh, lib-checks-skill.sh beside this file, and here.
|
||||
# The repo-root hook's copy cannot be shared with this skill — a cache-installed
|
||||
# plugin's scripts cannot read files outside their own plugin directory, and the
|
||||
# hook cannot reach inside the plugin. The two copies INSIDE this skill could be
|
||||
# The repo-root hook's copy cannot be sourced FROM this skill — a cache-installed
|
||||
# plugin's scripts cannot read files outside their own plugin directory. (The
|
||||
# hook could now read these from the plugin, as it already sources
|
||||
# lib-boundary-resolver.sh, but they sit in its Python preamble; hoisting them
|
||||
# is a separate change.) The two copies INSIDE this skill could be
|
||||
# shared (ADR-0025: two files in one skill may source a third), and are not only
|
||||
# because each mode library is a verbatim lift of the pre-merge suite whose
|
||||
# constants sit in its Python preamble; hoisting them is a separate change.
|
||||
|
||||
@@ -68,9 +68,9 @@ import yaml
|
||||
# resolver block below pins the reads; this pins the writes.
|
||||
#
|
||||
# Deliberately OUTSIDE the ADR-0020 shared boundary resolver block: the two
|
||||
# validate.sh copies print findings, skill-size-check.sh has its own top-level
|
||||
# equivalent, and tests/test-adr0020-contract.sh hashes that block for
|
||||
# byte-identity across all three.
|
||||
# validate.sh modes print findings, skill-size-check.sh has its own top-level
|
||||
# equivalent, and the block is one sourced copy all three share, so each
|
||||
# consumer's own startup stays in its own preamble.
|
||||
for _stream in (sys.stdout, sys.stderr):
|
||||
try:
|
||||
_stream.reconfigure(encoding='utf-8')
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,22 +3,20 @@
|
||||
# itself. None of them was pinned anywhere before this file, and each one fails
|
||||
# silently — which is the whole reason they need a test rather than a comment:
|
||||
#
|
||||
# 1. "ONE resolver, embedded VERBATIM in two scripts." The block between the
|
||||
# BEGIN/END markers is copied, not imported, because a cache-installed
|
||||
# plugin's scripts cannot read files outside their own plugin directory.
|
||||
# ADR-0025 merged skill-audit and agent-audit, which dropped the count from
|
||||
# three copies to two: factory-audit now holds ONE copy in a sourced
|
||||
# lib-boundary-resolver.sh, and scripts/skill-size-check.sh keeps its
|
||||
# embedded copy because it is a repo-root hook kept fit for a published
|
||||
# hook manifest (retired; ADR-0014), where entry[0] is the only token
|
||||
# pre-commit rewrites — it could not reach a file inside the plugin at a
|
||||
# path any consumer has. Nothing but this file asserts the two copies are still identical, and
|
||||
# a one-line edit to a single copy is invisible: every constant-agreement
|
||||
# assertion in tests/test-skill-size-check.sh still passes, because the
|
||||
# CONSTANTS are not what drifted.
|
||||
# 1a. The resolver's two copies are the ONLY two, and validate.sh sources
|
||||
# factory-audit's in both mode branches — the same authority checks 1b
|
||||
# makes for the parser, which byte-identity alone cannot make.
|
||||
# 1. "ONE resolver, and both of its consumers run it." The ADR-0020 boundary
|
||||
# resolver has exactly one copy, factory-audit's sourced
|
||||
# lib-boundary-resolver.sh. scripts/skill-size-check.sh used to embed a
|
||||
# second, byte-identical copy, because it was also exported through a
|
||||
# published hook manifest whose consumers could not reach a file inside the
|
||||
# plugin; 4de5b6b retired that export (ADR-0014), so the hook now sources
|
||||
# the library too. What must not fail silently: the hook growing its own
|
||||
# copy back, the library being gutted, or the hook no longer running the
|
||||
# library's text at all. So this asserts the library is real content, the
|
||||
# hook carries no marker pair, the hook fails closed without the library,
|
||||
# and — by a sentinel planted in a copied library — that the text the hook
|
||||
# executes IS the library's.
|
||||
# 1a. The library is the ONLY authority, and validate.sh sources it in both
|
||||
# mode branches — the same authority checks 1b makes for the parser.
|
||||
# 1b. The same claim, one directory over, for the Contributing-files parser.
|
||||
# That one was worse: it was embedded in both validate-provenance.sh copies,
|
||||
# the agent-audit copy's docstring ASSERTED it was kept behaviourally
|
||||
@@ -49,8 +47,8 @@ REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
HOOK="$REPO_ROOT/scripts/skill-size-check.sh"
|
||||
FACTORY_AUDIT="$REPO_ROOT/plugins/kyberforge/.apm/skills/factory-audit"
|
||||
# ADR-0025: one auto-detecting entry point, and one sourced copy of the resolver
|
||||
# behind it. The entry point is what the preflight assertions run; the library is
|
||||
# what the byte-identity assertion hashes.
|
||||
# behind it, which the root hook sources as well. The entry point is what the
|
||||
# preflight assertions run; the library is what assertion 1 inspects.
|
||||
FACTORY_VALIDATE="$FACTORY_AUDIT/scripts/validate.sh"
|
||||
FACTORY_RESOLVER="$FACTORY_AUDIT/scripts/lib-boundary-resolver.sh"
|
||||
PASS=0
|
||||
@@ -66,70 +64,111 @@ BEGIN_MARKER='# ===== BEGIN ADR-0020 SHARED BOUNDARY RESOLVER ====='
|
||||
END_MARKER='# ===== END ADR-0020 SHARED BOUNDARY RESOLVER ====='
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 1. The shared resolver block is byte-identical in both scripts
|
||||
# 1. The resolver has one copy, and the root hook runs it
|
||||
# ---------------------------------------------------------------------------
|
||||
# Two copies, not three, since ADR-0025 merged skill-audit and agent-audit:
|
||||
# scripts/skill-size-check.sh (embedded — sourcing the plugin's copy was
|
||||
# considered and refuted, see the header) and factory-audit's single sourced
|
||||
# lib-boundary-resolver.sh. The claim is unchanged and so is the check: the
|
||||
# span between the markers must be byte-identical wherever it appears.
|
||||
# One copy since the 2026-09-16 change: factory-audit's lib-boundary-resolver.sh.
|
||||
# scripts/skill-size-check.sh sources it rather than embedding a second copy, so
|
||||
# there is no pair left to hash. What replaces the hash is the set of ways a
|
||||
# single sourced copy can still fail quietly.
|
||||
echo ""
|
||||
echo "--- the ADR-0020 shared resolver block is byte-identical in both scripts ---"
|
||||
echo "--- the ADR-0020 resolver has one copy, and skill-size-check.sh runs it ---"
|
||||
|
||||
# Marker discipline first. An unbalanced or duplicated marker pair makes the
|
||||
# extraction below silently measure the wrong span — a sed range that never
|
||||
# closes swallows the rest of the file, and one that opens twice concatenates
|
||||
# two spans. Both would still compare "equal" if both were mangled the
|
||||
# same way, so the shape is asserted before the contents.
|
||||
MARKERS_OK=true
|
||||
for f in "$HOOK" "$FACTORY_RESOLVER"; do
|
||||
if [[ ! -f "$f" ]]; then
|
||||
fail "script not found: $f"
|
||||
MARKERS_OK=false
|
||||
continue
|
||||
fi
|
||||
b="$(grep -cFx "$BEGIN_MARKER" "$f" || true)"
|
||||
e="$(grep -cFx "$END_MARKER" "$f" || true)"
|
||||
if [[ "$b" == "1" && "$e" == "1" ]]; then
|
||||
pass "${f#"$REPO_ROOT/"} carries exactly one BEGIN and one END marker"
|
||||
else
|
||||
fail "${f#"$REPO_ROOT/"} has $b BEGIN and $e END markers, expected 1 and 1"
|
||||
MARKERS_OK=false
|
||||
fi
|
||||
done
|
||||
|
||||
if ! $MARKERS_OK; then
|
||||
fail "skipping the byte-identity comparison — the marker pairs are not well-formed, so any extraction would measure the wrong span"
|
||||
# (i) The library carries exactly one well-formed marker pair around real
|
||||
# content. An unbalanced pair makes every extraction measure the wrong span, and
|
||||
# an emptied-out block would still "load" while enforcing nothing. The resolver
|
||||
# is ~1,060 lines; 100 is a floor low enough never to need maintenance and high
|
||||
# enough that a gutted block cannot sneak past.
|
||||
if [[ ! -f "$FACTORY_RESOLVER" ]]; then
|
||||
fail "resolver library not found: ${FACTORY_RESOLVER#"$REPO_ROOT/"}"
|
||||
else
|
||||
HASHES=()
|
||||
LINECOUNTS=()
|
||||
for f in "$HOOK" "$FACTORY_RESOLVER"; do
|
||||
out="$TMPDIR_T/block-$(echo "$f" | md5sum | cut -c1-8).txt"
|
||||
sed -n "/^${BEGIN_MARKER}\$/,/^${END_MARKER}\$/p" "$f" > "$out"
|
||||
HASHES+=("$(md5sum < "$out" | cut -d' ' -f1)")
|
||||
LINECOUNTS+=("$(wc -l < "$out" | tr -d ' ')")
|
||||
done
|
||||
if [[ "${HASHES[0]}" == "${HASHES[1]}" ]]; then
|
||||
pass "both copies hash to ${HASHES[0]} (${LINECOUNTS[0]} lines) — agreement by construction, not by coincidence"
|
||||
b="$(grep -cFx "$BEGIN_MARKER" "$FACTORY_RESOLVER" || true)"
|
||||
e="$(grep -cFx "$END_MARKER" "$FACTORY_RESOLVER" || true)"
|
||||
if [[ "$b" == "1" && "$e" == "1" ]]; then
|
||||
pass "${FACTORY_RESOLVER#"$REPO_ROOT/"} carries exactly one BEGIN and one END marker"
|
||||
span="$(sed -n "/^${BEGIN_MARKER}\$/,/^${END_MARKER}\$/p" "$FACTORY_RESOLVER" | wc -l | tr -d ' ')"
|
||||
if [[ "$span" -gt 100 ]]; then
|
||||
pass "the resolver span is $span lines — real content, not an empty block"
|
||||
else
|
||||
fail "the resolver span is only $span lines — a gutted block would load and resolve nothing"
|
||||
fi
|
||||
else
|
||||
fail "the shared resolver has DRIFTED: skill-size-check=${HASHES[0]} (${LINECOUNTS[0]} lines), factory-audit/scripts/lib-boundary-resolver.sh=${HASHES[1]} (${LINECOUNTS[1]} lines). Edit one copy, then paste it over the other."
|
||||
fail "${FACTORY_RESOLVER#"$REPO_ROOT/"} has $b BEGIN and $e END markers, expected 1 and 1"
|
||||
fi
|
||||
# A block that has been emptied out would hash equal in both and pass the
|
||||
# comparison above while enforcing nothing. The resolver is ~1,060 lines; 100
|
||||
# is a floor low enough never to need maintenance and high enough that a
|
||||
# gutted block cannot sneak past.
|
||||
if [[ "${LINECOUNTS[0]}" -gt 100 ]]; then
|
||||
pass "the extracted block is ${LINECOUNTS[0]} lines — the comparison is over real content, not an empty span"
|
||||
fi
|
||||
|
||||
# (ii) The hook carries no copy of its own. A column-0 marker line in the hook
|
||||
# is the shape the old embedded copy had, and the shape a paste-back would have.
|
||||
if [[ ! -f "$HOOK" ]]; then
|
||||
fail "hook not found: ${HOOK#"$REPO_ROOT/"}"
|
||||
else
|
||||
hb="$(grep -cFx "$BEGIN_MARKER" "$HOOK" || true)"
|
||||
he="$(grep -cFx "$END_MARKER" "$HOOK" || true)"
|
||||
if [[ "$hb" == "0" && "$he" == "0" ]]; then
|
||||
pass "${HOOK#"$REPO_ROOT/"} carries no resolver marker lines — it has not grown its own copy back"
|
||||
else
|
||||
fail "the extracted shared block is only ${LINECOUNTS[0]} lines — two identical empty spans would compare equal and assert nothing"
|
||||
fail "${HOOK#"$REPO_ROOT/"} carries $hb BEGIN and $he END marker lines — a second copy of the resolver is back in the hook"
|
||||
fi
|
||||
fi
|
||||
|
||||
# (iii) and (iv) run the hook from a scratch tree that mirrors the two paths it
|
||||
# depends on, so the real library is never touched. The scratch hook is a copy
|
||||
# of the real one; its library is either absent, gutted, or the real library
|
||||
# with a sentinel planted inside the resolver block.
|
||||
SSC_TREE="$TMPDIR_T/ssc-tree"
|
||||
SSC_LIB_DIR="$SSC_TREE/plugins/kyberforge/.apm/skills/factory-audit/scripts"
|
||||
mkdir -p "$SSC_TREE/scripts" "$SSC_LIB_DIR" "$TMPDIR_T/ssc-skill/probe-skill"
|
||||
cp "$HOOK" "$SSC_TREE/scripts/skill-size-check.sh"
|
||||
printf -- '---\nname: probe-skill\ndescription: Use when probing the resolver wiring.\nmetadata:\n version: "0.1.0"\n---\n\n## Step 1\n\nDo the thing.\n' \
|
||||
> "$TMPDIR_T/ssc-skill/probe-skill/SKILL.md"
|
||||
PROBE="$TMPDIR_T/ssc-skill/probe-skill/SKILL.md"
|
||||
|
||||
run_scratch_hook() {
|
||||
local rc=0
|
||||
SSC_OUT="$(bash "$SSC_TREE/scripts/skill-size-check.sh" "$PROBE" 2>&1)" || rc=$?
|
||||
SSC_RC=$rc
|
||||
}
|
||||
|
||||
# (iii) Fail closed: no library, then a library that defines nothing.
|
||||
rm -f "$SSC_LIB_DIR/lib-boundary-resolver.sh"
|
||||
run_scratch_hook
|
||||
if [[ "$SSC_RC" -ne 0 && "$SSC_OUT" == *"boundary resolver library was not found"* ]]; then
|
||||
pass "with the library missing, the hook exits $SSC_RC and names the missing library — not a vacuous pass"
|
||||
else
|
||||
fail "with the library missing, the hook exited $SSC_RC without naming it: $SSC_OUT"
|
||||
fi
|
||||
printf '# gutted\n' > "$SSC_LIB_DIR/lib-boundary-resolver.sh"
|
||||
run_scratch_hook
|
||||
if [[ "$SSC_RC" -ne 0 && "$SSC_OUT" == *"did not define the ADR-0020 boundary resolver"* ]]; then
|
||||
pass "with a library that defines no resolver, the hook exits $SSC_RC and says so"
|
||||
else
|
||||
fail "with a gutted library, the hook exited $SSC_RC without saying so: $SSC_OUT"
|
||||
fi
|
||||
|
||||
# (iv) The text the hook executes IS the library's. A sentinel print planted
|
||||
# just after the BEGIN marker of a copied library must appear in the hook's
|
||||
# output. Without this, a hook that sourced the library but ran some other
|
||||
# program would pass (i)-(iii).
|
||||
SENTINEL="ADR0020-RESOLVER-SENTINEL-$$"
|
||||
if [[ -f "$FACTORY_RESOLVER" ]]; then
|
||||
awk -v m="$BEGIN_MARKER" -v s="$SENTINEL" '{ print } $0 == m { print "print(\"" s "\")" }' \
|
||||
"$FACTORY_RESOLVER" > "$SSC_LIB_DIR/lib-boundary-resolver.sh"
|
||||
if [[ "$(grep -cF "$SENTINEL" "$SSC_LIB_DIR/lib-boundary-resolver.sh" || true)" -ne 1 ]]; then
|
||||
fail "fixture check: the sentinel was not planted exactly once in the copied library — the case below would prove nothing"
|
||||
else
|
||||
run_scratch_hook
|
||||
if [[ "$SSC_RC" -eq 0 && "$SSC_OUT" == *"$SENTINEL"* ]]; then
|
||||
pass "a sentinel planted in the library's resolver block runs inside the hook — the hook executes the library's text"
|
||||
else
|
||||
fail "the hook did not run the library's resolver text (rc=$SSC_RC, sentinel absent from output): $SSC_OUT"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 1a. The resolver copies are the ONLY two, and validate.sh sources its one
|
||||
# 1a. The resolver library is the ONLY authority, and validate.sh sources it
|
||||
# ---------------------------------------------------------------------------
|
||||
# Byte-identity between two named files says nothing about a THIRD copy, and
|
||||
# nothing about whether factory-audit's copy is the one that runs. Assertion 1b
|
||||
# Assertion 1 says nothing about a copy somewhere else in the tree, and nothing
|
||||
# about whether validate.sh runs the library. Assertion 1b
|
||||
# pins both of those for the Contributing-files parser; the resolver is the same
|
||||
# defect class and gets the same two checks:
|
||||
#
|
||||
@@ -137,16 +176,14 @@ fi
|
||||
# branches — asserted inside each arm of `case "$MODE" in`, not by counting
|
||||
# source lines file-wide, because a count cannot see a branch. A library
|
||||
# that is identical, unique and never sourced is a copy that has quietly
|
||||
# been replaced by an inline one — and the byte-identity check above would
|
||||
# stay green over it.
|
||||
# been replaced by an inline one — and assertion 1 would stay green over it.
|
||||
# b. Nothing has re-inlined it. The BEGIN marker and a def unique to the
|
||||
# resolver (`_authoring_root`) appear in exactly the two authorities —
|
||||
# scripts/skill-size-check.sh and lib-boundary-resolver.sh — and nowhere
|
||||
# else under the tree. A mode library that grows a "just this once" copy
|
||||
# would otherwise escape assertion 1 entirely, because 1 hashes only the
|
||||
# two files it names.
|
||||
# resolver (`_authoring_root`) appear in exactly one file,
|
||||
# lib-boundary-resolver.sh, and nowhere else under the tree. A mode library
|
||||
# or a root script that grows a "just this once" copy would otherwise
|
||||
# escape assertion 1 entirely, because 1 inspects only the files it names.
|
||||
echo ""
|
||||
echo "--- the ADR-0020 resolver has exactly two authorities, and validate.sh sources factory-audit's ---"
|
||||
echo "--- the ADR-0020 resolver has exactly one authority, and validate.sh sources it ---"
|
||||
|
||||
# Deployed and vendored trees are generated copies, not authorities: .claude/ is
|
||||
# apm install output, apm_modules/ is resolved dependencies, build/ is release
|
||||
@@ -303,18 +340,18 @@ PY
|
||||
fi
|
||||
fi
|
||||
|
||||
# (b) Exactly the two authorities, for both spellings of a copy.
|
||||
EXPECTED_RESOLVERS="$(printf '%s\n' "$HOOK" "$FACTORY_RESOLVER" | sort)"
|
||||
# (b) Exactly the one authority, for both spellings of a copy.
|
||||
EXPECTED_RESOLVERS="$FACTORY_RESOLVER"
|
||||
check_resolver_authorities() {
|
||||
local label="$1" needle="$2"
|
||||
local found
|
||||
found="$(tree_scan "$needle")"
|
||||
if [[ "$found" == "$EXPECTED_RESOLVERS" ]]; then
|
||||
pass "$label appears in exactly the two resolver authorities and nowhere else"
|
||||
pass "$label appears in exactly the one resolver authority and nowhere else"
|
||||
elif [[ -z "$found" ]]; then
|
||||
fail "$label was found in NO file at all — the scan is looking for the wrong text"
|
||||
else
|
||||
fail "$label appears in an unexpected set of files, so the resolver has been re-inlined or lost: $(echo "$found" | tr '\n' ' ')— expected exactly ${HOOK#"$REPO_ROOT/"} and ${FACTORY_RESOLVER#"$REPO_ROOT/"}"
|
||||
fail "$label appears in an unexpected set of files, so the resolver has been re-inlined or lost: $(echo "$found" | tr '\n' ' ')— expected exactly ${FACTORY_RESOLVER#"$REPO_ROOT/"}"
|
||||
fi
|
||||
}
|
||||
check_resolver_authorities "the resolver's BEGIN marker" "$BEGIN_MARKER"
|
||||
@@ -325,7 +362,7 @@ check_resolver_authorities "a 'def _authoring_root' definition" "def _authoring_
|
||||
# ---------------------------------------------------------------------------
|
||||
# Same defect class, one directory over. parse_contributing_files() used to be
|
||||
# embedded in both validate-provenance.sh copies for the same reason the resolver
|
||||
# is embedded twice, and until this assertion existed the agent-audit copy's
|
||||
# was once embedded in several scripts, and until this assertion existed the agent-audit copy's
|
||||
# docstring merely CLAIMED it was "kept behaviourally identical to skill-audit's
|
||||
# copy" — an invariant nothing checked, and the two did drift into different
|
||||
# spellings of the bullet loop at 484357a. That drift happened to be
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
# Regression test for the two ways an ADR-0020 gate can be made to check NOTHING
|
||||
# while still exiting 0. Both were live defects, both were silent, and both sit
|
||||
# in the shared resolver block that all three scripts embed verbatim — so every
|
||||
# case below runs against all three.
|
||||
# in the shared resolver block that all three entry points run — one sourced
|
||||
# copy since 2026-09-16 — so every case below runs against all three.
|
||||
#
|
||||
# 1. THE FRONTMATTER BLOCKER. The frontmatter matcher used to be `^---\n`. A
|
||||
# UTF-8 BOM, a leading blank line, a trailing space after either marker, or
|
||||
|
||||
Reference in New Issue
Block a user