Skip to content

Commit cfa7f92

Browse files
feat: add paths-ignore input; default-skip vendored/fixture content (#7)
* feat: add paths-ignore input; default-skip vendored/fixture content Adopts the provenance-aware suppression pattern established by hyperpolymath/hypatia#243 and matched in hyperpolymath/a2ml-validate-action#7 for the K9 validator. Content-pattern validators must distinguish a target file from a vendored / fixture / training-corpus file that legitimately contains the very pattern being checked. This action was firing "Pedigree block missing 'name' field" on every K9 file in vendored project trees (e.g. verified-container-spec/ consumed by stapeln). The vendored files have their own pedigree declarations in their upstream context. New input `paths-ignore` (newline-separated, substring match), default-on with the canonical RSR vendored / fixture path set. Pass '' to disable. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix: count `pedigree = {` opening brace; nested-block close no longer terminates view When the validator detected the pedigree block start, it `continue`d before counting that line's `{`. So depth started at 0 instead of 1, and the next nested block's closing brace took depth back to 0 — prematurely setting `in_pedigree=false`. Any field (name, version, leash, signature) defined AFTER an inner block close was therefore invisible to the validator, even when present in the file. Repro: any K9 file where `metadata = { name = "…", …, }` is the LAST top-level field in pedigree (the canonical RSR template shape). All 6 templates / examples in hyperpolymath/stapeln#32 hit this — the `security = { … },` block prematurely closed pedigree before `metadata` was reached. Fix: drop the `continue` so the `pedigree = {` line falls through to the brace counter. Depth now starts at 1 and tracks correctly. Verified by mental-trace on `pedigree = { security = {…}, metadata = { name = … } }`: pedigree-line: depth = 1, in_pedigree=true security {: depth = 2 security }: depth = 1, line has `}` but depth > 0 → stay in pedigree metadata {: depth = 2 name = …: captured at depth 2 inside pedigree → has_pedigree_name metadata }: depth = 1 pedigree }: depth = 0 + `}` → in_pedigree=false Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 7b8244b commit cfa7f92

3 files changed

Lines changed: 92 additions & 3 deletions

File tree

README.adoc

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ jobs:
4242
with:
4343
path: '.' # Directory to scan (default: repo root)
4444
strict: 'false' # Promote warnings to errors (default: false)
45+
# paths-ignore: defaults to vendored / fixture patterns; override
46+
# via newline-separated string. Use '' to disable.
4547
----
4648

4749
=== Inputs
@@ -57,8 +59,27 @@ jobs:
5759
| `strict`
5860
| `false`
5961
| When `true`, warnings become errors and the action fails on any issue
62+
63+
| `paths-ignore`
64+
| _vendored & fixture defaults_
65+
| Newline-separated path fragments to skip. Substring match against each
66+
file path. Default set: `vendor/`, `vendored/`, `verified-container-spec/`,
67+
`.audittraining/`, `integration/fixtures/`, `test/fixtures/`,
68+
`tests/fixtures/`. Pass an empty string (`paths-ignore: ''`) to disable
69+
and scan everything. See https://github.com/hyperpolymath/hypatia/pull/243
70+
for the architectural rationale (content-pattern validators must
71+
distinguish targets from fixtures / vendored / training-corpus files
72+
that legitimately contain the very pattern being checked).
6073
|===
6174

75+
==== Why default-on path exemptions?
76+
77+
K9 files inside vendored projects (e.g. `verified-container-spec/`) carry
78+
their own pedigree declarations in their upstream context — flagging every
79+
such file as "Pedigree block missing 'name' field" is provenance noise.
80+
The defaults match the canonical RSR vendored-content paths; override for
81+
project-specific carve-outs.
82+
6283
=== Outputs
6384

6485
[cols="1,3"]

action.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,25 @@ inputs:
3030
will fail on any validation issue. Defaults to false.
3131
required: false
3232
default: 'false'
33+
paths-ignore:
34+
description: >-
35+
Newline-separated path fragments to skip. Each line is matched as a
36+
substring against the file's path. Defaults to common vendored /
37+
training-corpus / fixture patterns so consumers don't have to repeat
38+
this carve-out in every repo. Pass an empty string to disable.
39+
Pattern follows hyperpolymath/hypatia#243 — validators that scan
40+
content patterns must distinguish a target file from a fixture /
41+
vendored / training-corpus file that legitimately contains the
42+
pattern being checked.
43+
required: false
44+
default: |
45+
vendor/
46+
vendored/
47+
verified-container-spec/
48+
.audittraining/
49+
integration/fixtures/
50+
test/fixtures/
51+
tests/fixtures/
3352
3453
outputs:
3554
files-scanned:
@@ -51,5 +70,6 @@ runs:
5170
env:
5271
INPUT_PATH: ${{ inputs.path }}
5372
INPUT_STRICT: ${{ inputs.strict }}
73+
INPUT_PATHS_IGNORE: ${{ inputs.paths-ignore }}
5474
run: |
5575
"${GITHUB_ACTION_PATH}/validate-k9.sh"

validate-k9.sh

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,30 @@ set -euo pipefail
2727

2828
SCAN_PATH="${INPUT_PATH:-.}"
2929
STRICT="${INPUT_STRICT:-false}"
30+
PATHS_IGNORE_RAW="${INPUT_PATHS_IGNORE:-}"
31+
32+
# Parse paths-ignore: newline-separated fragments, blank lines and # comments
33+
# stripped. Each fragment is a substring match against the file path. Pattern
34+
# adopted from hyperpolymath/hypatia#243 — content-pattern validators must
35+
# distinguish a target from a vendored / fixture file that legitimately
36+
# contains the very pattern being checked.
37+
PATHS_IGNORE=()
38+
while IFS= read -r _frag; do
39+
# Strip leading and trailing whitespace (canonical bash idiom).
40+
_frag="${_frag#"${_frag%%[![:space:]]*}"}"
41+
_frag="${_frag%"${_frag##*[![:space:]]}"}"
42+
[[ -z "$_frag" || "$_frag" == \#* ]] && continue
43+
PATHS_IGNORE+=("$_frag")
44+
done <<< "$PATHS_IGNORE_RAW"
45+
46+
# Returns 0 if path should be skipped (matches any ignore fragment)
47+
path_ignored() {
48+
local p="$1" frag
49+
for frag in "${PATHS_IGNORE[@]}"; do
50+
[[ "$p" == *"$frag"* ]] && return 0
51+
done
52+
return 1
53+
}
3054

3155
# Counters
3256
FILES_SCANNED=0
@@ -144,12 +168,21 @@ validate_k9() {
144168
while IFS= read -r line; do
145169
line_num=$((line_num + 1))
146170

147-
# Detect pedigree block start
171+
# Detect pedigree block start. Note: do NOT `continue` here — the
172+
# `pedigree = {` line itself contains the opening brace that
173+
# establishes the block. Falling through to the brace counter
174+
# below makes depth start at 1, so a subsequent `security = {…},`
175+
# closing brace correctly takes depth to 1 (not 0), keeping us
176+
# inside the pedigree block when later fields (name/version/leash)
177+
# are checked. Previously the `continue` skipped this opening
178+
# brace, depth started at 0, and the first nested block's close
179+
# prematurely terminated the validator's view of the pedigree —
180+
# making `pedigree.metadata.name` invisible.
148181
if [[ "$line" =~ ^[[:space:]]*pedigree[[:space:]]*= ]]; then
149182
has_pedigree=true
150183
in_pedigree=true
151184
pedigree_depth=0
152-
continue
185+
# fall through
153186
fi
154187

155188
if [[ "$in_pedigree" == "true" ]]; then
@@ -250,7 +283,22 @@ echo "Scanning ${SCAN_PATH} for K9 files (.k9, .k9.ncl)..."
250283
echo ""
251284

252285
# Find all K9 files, excluding .git directory
253-
mapfile -t k9_files < <(find "$SCAN_PATH" \( -name '*.k9' -o -name '*.k9.ncl' \) -not -path '*/.git/*' -type f | sort)
286+
mapfile -t k9_candidates < <(find "$SCAN_PATH" \( -name '*.k9' -o -name '*.k9.ncl' \) -not -path '*/.git/*' -type f | sort)
287+
288+
# Apply paths-ignore filter
289+
k9_files=()
290+
SKIPPED=0
291+
for _f in "${k9_candidates[@]}"; do
292+
if path_ignored "$_f"; then
293+
SKIPPED=$((SKIPPED + 1))
294+
continue
295+
fi
296+
k9_files+=("$_f")
297+
done
298+
299+
if [[ $SKIPPED -gt 0 ]]; then
300+
echo "::notice::Skipped ${SKIPPED} file(s) matching paths-ignore"
301+
fi
254302

255303
if [[ ${#k9_files[@]} -eq 0 ]]; then
256304
echo "::notice::No K9 files found in ${SCAN_PATH}"

0 commit comments

Comments
 (0)