From 760e0fd9427b494d2d2ddfa0d5cb88803f58c050 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Thu, 27 Aug 2026 14:47:11 +0100 Subject: [PATCH 1/8] fix(ci): the invisible-character gate never matched anything MEASURED 2026-08-27: this gate's pattern caught 0 OF 6 invisible-character test cases. It has never detected an NBSP, zero-width space, BOM, soft hyphen, bidi override or word joiner. ROOT CAUSE: the pattern used UTF-8 BYTE sequences (\xc2\xa0) while grep -P matches CHARACTERS. Bytes c2 a0 are ONE character U+00A0; \xc2\xa0 asks for TWO characters, U+00C2 then U+00A0, which is never present. grep -P '\xc2\xa0' -> miss grep -P '\x{a0}' -> MATCH Only \x00 worked, being single-byte in both readings. FIXED: codepoint escapes; C0 control characters \x01-\x08,\x0B,\x0C,\x0E-\x1F added (TAB/LF/CR excluded); and grep -a, without which grep skips any NUL-bearing file as binary. The C0 range matters: a stray BACKSPACE byte made a workflow unparseable in developer-ecosystem, so it never ran, and this linter called it clean. Canonical fix: hyperpolymath/empty-linter#70. 1 file(s) here. VERIFIED: YAML re-parsed, and the corrected pattern was confirmed to catch a real NBSP before the change was kept. --- .github/workflows/dogfood-gate.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/dogfood-gate.yml b/.github/workflows/dogfood-gate.yml index 68e7c74..a564438 100644 --- a/.github/workflows/dogfood-gate.yml +++ b/.github/workflows/dogfood-gate.yml @@ -124,7 +124,7 @@ jobs: # Checks for: zero-width spaces, zero-width joiners, BOM, soft hyphens, # non-breaking spaces, null bytes, and other invisible Unicode in source files. set +e - PATTERNS='\xc2\xa0|\xe2\x80\x8b|\xe2\x80\x8c|\xe2\x80\x8d|\xef\xbb\xbf|\xc2\xad|\xe2\x80\x8e|\xe2\x80\x8f|\xe2\x80\xaa|\xe2\x80\xab|\xe2\x80\xac|\xe2\x80\xad|\xe2\x80\xae|\x00' + PATTERNS='\x00|[\x01-\x08\x0B\x0C\x0E-\x1F]|\x{a0}|\x{ad}|\x{200b}|\x{200c}|\x{200d}|\x{200e}|\x{200f}|\x{202a}|\x{202b}|\x{202c}|\x{202d}|\x{202e}|\x{2060}|\x{feff}' find "$GITHUB_WORKSPACE" \ -not -path '*/.git/*' -not -path '*/node_modules/*' \ -not -path '*/.deno/*' -not -path '*/target/*' \ @@ -135,7 +135,7 @@ jobs: -o -name '*.yml' -o -name '*.yaml' -o -name '*.md' -o -name '*.adoc' \ -o -name '*.idr' -o -name '*.zig' -o -name '*.v' -o -name '*.jl' \ -o -name '*.gleam' -o -name '*.hs' -o -name '*.ml' -o -name '*.sh' \) \ - -exec grep -Prl "$PATTERNS" {} \; > /tmp/empty-lint-results.txt 2>/dev/null + -exec grep -aPrl "$PATTERNS" {} \; > /tmp/empty-lint-results.txt 2>/dev/null EL_EXIT=$? set -e From 8370bab2c01d5a32d2e141e0a415efdc3052a2ce Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Fri, 28 Aug 2026 16:11:43 +0100 Subject: [PATCH 2/8] fix(ci): make invisible-character PCRE locale-independent --- .github/workflows/dogfood-gate.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dogfood-gate.yml b/.github/workflows/dogfood-gate.yml index a564438..a8544c9 100644 --- a/.github/workflows/dogfood-gate.yml +++ b/.github/workflows/dogfood-gate.yml @@ -124,7 +124,7 @@ jobs: # Checks for: zero-width spaces, zero-width joiners, BOM, soft hyphens, # non-breaking spaces, null bytes, and other invisible Unicode in source files. set +e - PATTERNS='\x00|[\x01-\x08\x0B\x0C\x0E-\x1F]|\x{a0}|\x{ad}|\x{200b}|\x{200c}|\x{200d}|\x{200e}|\x{200f}|\x{202a}|\x{202b}|\x{202c}|\x{202d}|\x{202e}|\x{2060}|\x{feff}' + PATTERNS='(*UTF)[\x00-\x08\x0B\x0C\x0E-\x1F\x{a0}\x{ad}\x{200b}-\x{200f}\x{202a}-\x{202f}\x{2060}\x{2066}-\x{2069}\x{feff}]' find "$GITHUB_WORKSPACE" \ -not -path '*/.git/*' -not -path '*/node_modules/*' \ -not -path '*/.deno/*' -not -path '*/target/*' \ From bdde2f09c8c569e396db0437c82f50fd0b5ab8bd Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Fri, 28 Aug 2026 19:56:30 +0100 Subject: [PATCH 3/8] Update .github/workflows/dogfood-gate.yml Co-authored-by: codacy-production[bot] <61871480+codacy-production[bot]@users.noreply.github.com> Signed-off-by: Jonathan D.A. Jewell <6759885+hyperpolymath@users.noreply.github.com> --- .github/workflows/dogfood-gate.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dogfood-gate.yml b/.github/workflows/dogfood-gate.yml index a8544c9..c6d4a31 100644 --- a/.github/workflows/dogfood-gate.yml +++ b/.github/workflows/dogfood-gate.yml @@ -135,7 +135,7 @@ jobs: -o -name '*.yml' -o -name '*.yaml' -o -name '*.md' -o -name '*.adoc' \ -o -name '*.idr' -o -name '*.zig' -o -name '*.v' -o -name '*.jl' \ -o -name '*.gleam' -o -name '*.hs' -o -name '*.ml' -o -name '*.sh' \) \ - -exec grep -aPrl "$PATTERNS" {} \; > /tmp/empty-lint-results.txt 2>/dev/null + -exec grep -aPl "$PATTERNS" {} + > /tmp/empty-lint-results.txt 2>/dev/null EL_EXIT=$? set -e From 5753a303480eba6fb3bd45f75292f233cf6adbb5 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Sat, 29 Aug 2026 13:24:30 +0100 Subject: [PATCH 4/8] fix(ci): make invisible scan byte-safe --- .github/workflows/dogfood-gate.yml | 100 +++++++++++++++++++++-------- 1 file changed, 73 insertions(+), 27 deletions(-) diff --git a/.github/workflows/dogfood-gate.yml b/.github/workflows/dogfood-gate.yml index c6d4a31..72545ad 100644 --- a/.github/workflows/dogfood-gate.yml +++ b/.github/workflows/dogfood-gate.yml @@ -123,38 +123,84 @@ jobs: # Inline invisible character detection (from empty-linter's core patterns). # Checks for: zero-width spaces, zero-width joiners, BOM, soft hyphens, # non-breaking spaces, null bytes, and other invisible Unicode in source files. - set +e - PATTERNS='(*UTF)[\x00-\x08\x0B\x0C\x0E-\x1F\x{a0}\x{ad}\x{200b}-\x{200f}\x{202a}-\x{202f}\x{2060}\x{2066}-\x{2069}\x{feff}]' - find "$GITHUB_WORKSPACE" \ - -not -path '*/.git/*' -not -path '*/node_modules/*' \ - -not -path '*/.deno/*' -not -path '*/target/*' \ - -not -path '*/_build/*' -not -path '*/deps/*' \ - -not -path '*/external_corpora/*' -not -path '*/.lake/*' \ - -type f \( -name '*.rs' -o -name '*.ex' -o -name '*.exs' -o -name '*.res' \ - -o -name '*.js' -o -name '*.ts' -o -name '*.json' -o -name '*.toml' \ - -o -name '*.yml' -o -name '*.yaml' -o -name '*.md' -o -name '*.adoc' \ - -o -name '*.idr' -o -name '*.zig' -o -name '*.v' -o -name '*.jl' \ - -o -name '*.gleam' -o -name '*.hs' -o -name '*.ml' -o -name '*.sh' \) \ - -exec grep -aPl "$PATTERNS" {} + > /tmp/empty-lint-results.txt 2>/dev/null - EL_EXIT=$? - set -e - - FINDINGS=$(wc -l < /tmp/empty-lint-results.txt 2>/dev/null || echo 0) - echo "findings=$FINDINGS" >> "$GITHUB_OUTPUT" - echo "exit_code=$EL_EXIT" >> "$GITHUB_OUTPUT" - echo "ready=true" >> "$GITHUB_OUTPUT" - - # Emit annotations for each file with invisible chars - while IFS= read -r filepath; do - [ -z "$filepath" ] && continue - REL_PATH="${filepath#$GITHUB_WORKSPACE/}" - echo "::warning file=${REL_PATH}::Invisible Unicode characters detected (zero-width space, BOM, NBSP, etc.)" - done < /tmp/empty-lint-results.txt + python3 - <<'PY' + import os + from pathlib import Path + + root = Path(os.environ["GITHUB_WORKSPACE"]) + skipped_dirs = { + ".git", ".deno", ".lake", "_build", "deps", + "external_corpora", "node_modules", "target", + } + source_suffixes = { + ".adoc", ".ex", ".exs", ".gleam", ".hs", ".idr", ".jl", + ".js", ".json", ".md", ".ml", ".res", ".rs", ".sh", + ".toml", ".ts", ".v", ".yaml", ".yml", ".zig", + } + invisible_codepoints = { + 0x00A0, 0x00AD, 0x2060, 0xFEFF, + *range(0x200B, 0x2010), + *range(0x202A, 0x2030), + *range(0x2066, 0x206A), + } + + def annotation_escape(value): + return str(value).replace("%", "%25").replace("\r", "%0D").replace("\n", "%0A") + + findings = [] + errors = [] + for path in root.rglob("*"): + relative = path.relative_to(root) + if ( + path.is_symlink() + or not path.is_file() + or path.suffix.lower() not in source_suffixes + or any(part in skipped_dirs for part in relative.parts[:-1]) + ): + continue + try: + data = path.read_bytes() + except OSError as error: + errors.append((relative, f"could not read file: {error}")) + continue + + reasons = set() + if data.startswith(b"\xef\xbb\xbf"): + reasons.add("leading UTF-8 BOM") + if any(byte <= 0x08 or byte in (0x0B, 0x0C) or 0x0E <= byte <= 0x1F for byte in data): + reasons.add("C0 control character") + try: + text_content = data.decode("utf-8", errors="strict") + except UnicodeDecodeError as error: + errors.append((relative, f"invalid UTF-8 at byte {error.start}")) + continue + if any(ord(character) in invisible_codepoints for character in text_content): + reasons.add("invisible Unicode code point") + if reasons: + findings.append((relative, ", ".join(sorted(reasons)))) + + with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as output: + output.write(f"findings={len(findings)}\n") + output.write(f"exit_code={2 if errors else 0}\n") + output.write("ready=true\n") + + for relative, reasons in findings: + print(f"::warning file={annotation_escape(relative)}::Invisible characters detected: {reasons}") + for relative, reason in errors: + print(f"::error file={annotation_escape(relative)}::Invisible-character scan failed: {annotation_escape(reason)}") + PY - name: Write summary run: | if [ "${{ steps.lint.outputs.ready }}" = "true" ]; then FINDINGS="${{ steps.lint.outputs.findings }}" + EXIT_CODE="${{ steps.lint.outputs.exit_code }}" + if [ "$EXIT_CODE" -ne 0 ] 2>/dev/null; then + echo "## Empty-Linter Results" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo ":x: Scanner execution failed; see error annotations above." >> "$GITHUB_STEP_SUMMARY" + exit 1 + fi if [ "$FINDINGS" -gt 0 ] 2>/dev/null; then echo "## Empty-Linter Results" >> "$GITHUB_STEP_SUMMARY" echo "" >> "$GITHUB_STEP_SUMMARY" From a703cf16e04ea349890cbed853616aa19aede35d Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Sat, 29 Aug 2026 13:47:18 +0100 Subject: [PATCH 5/8] fix(ci): enforce invisible-character findings --- .github/workflows/dogfood-gate.yml | 93 ++++++++++++++++++------------ 1 file changed, 57 insertions(+), 36 deletions(-) diff --git a/.github/workflows/dogfood-gate.yml b/.github/workflows/dogfood-gate.yml index 72545ad..21b6853 100644 --- a/.github/workflows/dogfood-gate.yml +++ b/.github/workflows/dogfood-gate.yml @@ -129,12 +129,20 @@ jobs: root = Path(os.environ["GITHUB_WORKSPACE"]) skipped_dirs = { - ".git", ".deno", ".lake", "_build", "deps", - "external_corpora", "node_modules", "target", + ".cache", ".deno", ".elixir_ls", ".git", ".lake", ".zig-cache", + "_build", "build", "coverage", "deps", "dist", "external_corpora", + "node_modules", "out", "target", "vendor", "zig-cache", "zig-out", + } + intentional_fixture_dirs = { + ("tests", "fixtures", "bom-detection"), + ("tests", "fixtures", "empty-linter"), } source_suffixes = { - ".adoc", ".ex", ".exs", ".gleam", ".hs", ".idr", ".jl", - ".js", ".json", ".md", ".ml", ".res", ".rs", ".sh", + ".adoc", ".adb", ".ads", ".agda", ".c", ".cc", ".clj", ".cljs", + ".cpp", ".erl", ".ex", ".exs", ".fs", ".fsi", ".fsx", ".gleam", + ".h", ".hh", ".hpp", ".hrl", ".hs", ".idr", ".java", ".jl", + ".js", ".json", ".kt", ".kts", ".lean", ".lua", ".md", ".ml", + ".php", ".r", ".rb", ".res", ".rs", ".scala", ".sh", ".swift", ".toml", ".ts", ".v", ".yaml", ".yml", ".zig", } invisible_codepoints = { @@ -144,40 +152,52 @@ jobs: *range(0x2066, 0x206A), } - def annotation_escape(value): + def command_escape(value): return str(value).replace("%", "%25").replace("\r", "%0D").replace("\n", "%0A") + def property_escape(value): + return command_escape(value).replace(":", "%3A").replace(",", "%2C") + + # Runtime regression for GitHub workflow-command property delimiters. + assert property_escape("docs/a,b::c.md") == "docs/a%2Cb%3A%3Ac.md" + + def intentionally_invalid_fixture(relative): + return any(relative.parts[:len(prefix)] == prefix for prefix in intentional_fixture_dirs) + findings = [] errors = [] - for path in root.rglob("*"): - relative = path.relative_to(root) - if ( - path.is_symlink() - or not path.is_file() - or path.suffix.lower() not in source_suffixes - or any(part in skipped_dirs for part in relative.parts[:-1]) - ): - continue - try: - data = path.read_bytes() - except OSError as error: - errors.append((relative, f"could not read file: {error}")) - continue - - reasons = set() - if data.startswith(b"\xef\xbb\xbf"): - reasons.add("leading UTF-8 BOM") - if any(byte <= 0x08 or byte in (0x0B, 0x0C) or 0x0E <= byte <= 0x1F for byte in data): - reasons.add("C0 control character") - try: - text_content = data.decode("utf-8", errors="strict") - except UnicodeDecodeError as error: - errors.append((relative, f"invalid UTF-8 at byte {error.start}")) - continue - if any(ord(character) in invisible_codepoints for character in text_content): - reasons.add("invisible Unicode code point") - if reasons: - findings.append((relative, ", ".join(sorted(reasons)))) + for directory, dirnames, filenames in os.walk(root, topdown=True): + dirnames[:] = [name for name in dirnames if name not in skipped_dirs] + directory_path = Path(directory) + for filename in filenames: + path = directory_path / filename + relative = path.relative_to(root) + if ( + path.is_symlink() + or path.suffix.lower() not in source_suffixes + or intentionally_invalid_fixture(relative) + ): + continue + try: + data = path.read_bytes() + except OSError as error: + errors.append((relative, f"could not read file: {error}")) + continue + + reasons = set() + if data.startswith(b"\xef\xbb\xbf"): + reasons.add("leading UTF-8 BOM") + if any(byte <= 0x08 or byte in (0x0B, 0x0C) or 0x0E <= byte <= 0x1F for byte in data): + reasons.add("C0 control character") + try: + text_content = data.decode("utf-8", errors="strict") + except UnicodeDecodeError as error: + errors.append((relative, f"invalid UTF-8 at byte {error.start}")) + continue + if any(ord(character) in invisible_codepoints for character in text_content): + reasons.add("invisible Unicode code point") + if reasons: + findings.append((relative, ", ".join(sorted(reasons)))) with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as output: output.write(f"findings={len(findings)}\n") @@ -185,9 +205,9 @@ jobs: output.write("ready=true\n") for relative, reasons in findings: - print(f"::warning file={annotation_escape(relative)}::Invisible characters detected: {reasons}") + print(f"::warning file={property_escape(relative)}::Invisible characters detected: {command_escape(reasons)}") for relative, reason in errors: - print(f"::error file={annotation_escape(relative)}::Invisible-character scan failed: {annotation_escape(reason)}") + print(f"::error file={property_escape(relative)}::Invisible-character scan failed: {command_escape(reason)}") PY - name: Write summary @@ -205,6 +225,7 @@ jobs: echo "## Empty-Linter Results" >> "$GITHUB_STEP_SUMMARY" echo "" >> "$GITHUB_STEP_SUMMARY" echo "Found **${FINDINGS}** invisible character issue(s). See annotations above." >> "$GITHUB_STEP_SUMMARY" + exit 1 else echo "## Empty-Linter Results" >> "$GITHUB_STEP_SUMMARY" echo "" >> "$GITHUB_STEP_SUMMARY" From 47c2621b112823413d7938bb8429394c86f63fe5 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Sat, 29 Aug 2026 14:02:49 +0100 Subject: [PATCH 6/8] fix(governance): update permissionless allowlist gate --- .github/workflows/governance.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/governance.yml b/.github/workflows/governance.yml index be5d5f2..aabd3eb 100644 --- a/.github/workflows/governance.yml +++ b/.github/workflows/governance.yml @@ -16,4 +16,4 @@ permissions: jobs: governance: - uses: hyperpolymath/standards/.github/workflows/governance-reusable.yml@84355587cb2a1f86e6882de83514a32db2646e7a + uses: hyperpolymath/standards/.github/workflows/governance-reusable.yml@6b38eb50104901e2fec80f9455a972bc3eced813 From 6efcf1abd141b87cdddcf4371205258d6c58ab79 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Sat, 29 Aug 2026 14:35:01 +0100 Subject: [PATCH 7/8] fix(ci): align policy gates with repository formats Ratchet-exception(.machine_readable/root-allow.txt): permit the repository's canonical AsciiDoc authority files and validation hooks at root; these are required project interfaces. --- .githooks/validate-k9.sh | 12 ++++++----- .github/workflows/dogfood-gate.yml | 5 +++-- .github/workflows/workflow-linter.yml | 21 +++++-------------- .machine_readable/root-allow.txt | 4 ++++ .../self-validating/methodology-guard.k9.ncl | 21 ++++++++++++++++++- container/deploy.k9.ncl | 2 ++ 6 files changed, 41 insertions(+), 24 deletions(-) diff --git a/.githooks/validate-k9.sh b/.githooks/validate-k9.sh index c83e290..62f3056 100755 --- a/.githooks/validate-k9.sh +++ b/.githooks/validate-k9.sh @@ -4,7 +4,7 @@ # # validate-k9.sh — K9 configuration file validation script # -# Scans for .k9 and .k9.ncl files and validates: +# Scans for Nickel K9 contractiles (.k9.ncl) and validates: # 1. K9! magic number on line 1 # 2. Pedigree block presence with required fields (name, version) # 3. Security level is one of: kennel, yard, hunt (case-insensitive) @@ -179,7 +179,8 @@ validate_k9() { # brace, depth started at 0, and the first nested block's close # prematurely terminated the validator's view of the pedigree — # making `pedigree.metadata.name` invisible. - if [[ "$line" =~ ^[[:space:]]*pedigree[[:space:]]*= ]]; then + if [[ "$line" =~ ^[[:space:]]*pedigree[[:space:]]*= ]] || \ + [[ "$line" =~ ^[[:space:]]*let[[:space:]]+[[:alnum:]_]*pedigree[[:space:]]*=[[:space:]]*\{ ]]; then has_pedigree=true in_pedigree=true pedigree_depth=0 @@ -292,11 +293,12 @@ validate_k9() { # --------------------------------------------------------------------------- echo "::group::K9 Configuration Validation" -echo "Scanning ${SCAN_PATH} for K9 files (.k9, .k9.ncl)..." +echo "Scanning ${SCAN_PATH} for Nickel K9 contractiles (.k9.ncl)..." echo "" -# Find all K9 files, excluding .git directory -mapfile -t k9_candidates < <(find "$SCAN_PATH" \( -name '*.k9' -o -name '*.k9.ncl' \) -not -path '*/.git/*' -type f | sort) +# Plain .k9 files are session-policy YAML, not Nickel K9 contractiles. +# Validate only the application/vnd.k9+nickel form documented by this repo. +mapfile -t k9_candidates < <(find "$SCAN_PATH" -name '*.k9.ncl' -not -path '*/.git/*' -type f | sort) # Apply paths-ignore filter k9_files=() diff --git a/.github/workflows/dogfood-gate.yml b/.github/workflows/dogfood-gate.yml index 21b6853..38a09c9 100644 --- a/.github/workflows/dogfood-gate.yml +++ b/.github/workflows/dogfood-gate.yml @@ -74,7 +74,8 @@ jobs: - name: Check for K9 files id: detect run: | - COUNT=$(find . \( -name '*.k9' -o -name '*.k9.ncl' \) -not -path './.git/*' | wc -l) + # Plain .k9 files are session-policy YAML; contractiles use .k9.ncl. + COUNT=$(find . -name '*.k9.ncl' -not -path './.git/*' | wc -l) CONFIG_COUNT=$(find . \( -name '*.toml' -o -name '*.yaml' -o -name '*.yml' -o -name '*.json' \) \ -not -path './.git/*' -not -path './node_modules/*' -not -path './.deno/*' \ -not -name 'package-lock.json' -not -name 'Cargo.lock' -not -name 'deno.lock' | wc -l) @@ -388,7 +389,7 @@ jobs: fi # K9 contracts present? - if find . \( -name '*.k9' -o -name '*.k9.ncl' \) -not -path './.git/*' | head -1 | grep -q .; then + if find . -name '*.k9.ncl' -not -path './.git/*' | head -1 | grep -q .; then SCORE=$((SCORE + 1)) K9_STATUS=":white_check_mark:" else diff --git a/.github/workflows/workflow-linter.yml b/.github/workflows/workflow-linter.yml index db81c19..ec55a93 100644 --- a/.github/workflows/workflow-linter.yml +++ b/.github/workflows/workflow-linter.yml @@ -63,23 +63,12 @@ jobs: echo "All workflows have permissions declared" - name: Check SHA-Pinned Actions + env: + GH_TOKEN: ${{ github.token }} run: | - echo "=== Checking Action Pinning ===" - # Find any uses: lines that don't have @SHA format - # Pattern: uses: owner/repo@<40-char-hex> - unpinned=$(grep -rnE "^[[:space:]]+uses:" .github/workflows/ | \ - grep -v "@[a-f0-9]\{40\}" | \ - grep -v "uses: \./\|uses: docker://\|uses: actions/github-script" || true) - - if [ -n "$unpinned" ]; then - echo "ERROR: Found unpinned actions:" - echo "$unpinned" - echo "" - echo "Replace version tags with SHA pins, e.g.:" - echo " uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6.0.1" - exit 1 - fi - echo "All actions are SHA-pinned" + # The lockfile is the pin authority for direct and transitive actions. + gh extension install github/gh-actions-lock --pin v0.1.6 + gh actions-lock --verify-local - name: Check for Duplicate Workflows run: | diff --git a/.machine_readable/root-allow.txt b/.machine_readable/root-allow.txt index 5b20f02..2bfa724 100644 --- a/.machine_readable/root-allow.txt +++ b/.machine_readable/root-allow.txt @@ -20,9 +20,12 @@ AFFIRMATION.adoc # dated/signed honesty snapshot (README/EXPLAINME/AFF GOVERNANCE.adoc # governance model (validator accepts root or docs/governance/) MAINTAINERS.adoc # maintainer roster CONTRIBUTING.md # REQUIRED AT ROOT by scorecard-enforcer/openssf-compliance/quality CI (test -f, no .github fallback). The fuller copy in .github/ is GitHub's auto-discovery convention; dedupe is an owner decision (would need those CI checks updated to accept .github/). +CONTRIBUTING.adoc # canonical AsciiDoc contribution guide SECURITY.md # REQUIRED AT ROOT by scorecard-enforcer CI + the security-policy contractile (test -f SECURITY.md). See CONTRIBUTING.md note re: the .github/ copy. +SECURITY.adoc # canonical AsciiDoc security policy LICENSE CHANGELOG.md +CHANGELOG.adoc # canonical AsciiDoc project change history # ─── Build entry points (must live at root for their tooling) ──────────────── Justfile # delegates phases to build/just/*.just @@ -41,6 +44,7 @@ abi.ipkg # Idris2 package for the ABI seam; sourcedir=src/inte .devcontainer/ # VS Code dev container spec; tool-required at root .git/ .github/ # CONTRIBUTING.md, CODE_OF_CONDUCT.md, SECURITY.md, workflows/ +.githooks/ # repository validation hooks LICENSES/ # REUSE/SPDX licence texts (LICENSES/MPL-2.0.txt) .machine_readable/ # AI manifests, contractiles, custom-format configs .well-known/ diff --git a/.machine_readable/self-validating/methodology-guard.k9.ncl b/.machine_readable/self-validating/methodology-guard.k9.ncl index 17f1409..4753242 100644 --- a/.machine_readable/self-validating/methodology-guard.k9.ncl +++ b/.machine_readable/self-validating/methodology-guard.k9.ncl @@ -1,3 +1,4 @@ +K9! # SPDX-License-Identifier: MPL-2.0 # Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) # @@ -64,4 +65,22 @@ let methodology_guard = { }, }, } -in methodology_guard +in { + pedigree = { + schema_version = "1.0.0", + component_type = "methodology-validator", + security = { + leash = 'Yard, + trust_level = "validated-configuration", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + }, + metadata = { + name = "methodology-guard", + version = "1.0.0", + description = "Validates declared repository methodology constraints", + }, + }, + guard = methodology_guard, +} diff --git a/container/deploy.k9.ncl b/container/deploy.k9.ncl index bf02b67..d4c643c 100644 --- a/container/deploy.k9.ncl +++ b/container/deploy.k9.ncl @@ -1,3 +1,4 @@ +K9! # SPDX-License-Identifier: MPL-2.0 # deploy.k9.ncl — Panoply deployment component (Hunt level) # @@ -39,6 +40,7 @@ let component_pedigree = { # L3: The Leash — Security # ───────────────────────────────────────────────────────────── security = { + leash = 'Hunt, trust_level = 'Hunt, allow_network = true, allow_filesystem_write = true, From 75efe90a52457a1135b0519f010c5a2af09c8f1d Mon Sep 17 00:00:00 2001 From: "coderabbitai[bot]" <136622811+coderabbitai[bot]@users.noreply.github.com> Date: Sat, 29 Aug 2026 20:09:19 +0000 Subject: [PATCH 8/8] fix: apply CodeRabbit auto-fixes Fixed 4 file(s) based on 3 failed pre-merge checks. Co-authored-by: CodeRabbit --- .githooks/validate-k9.sh | 12 +++++------ .machine_readable/root-allow.txt | 4 ---- .../self-validating/methodology-guard.k9.ncl | 21 +------------------ container/deploy.k9.ncl | 2 -- 4 files changed, 6 insertions(+), 33 deletions(-) diff --git a/.githooks/validate-k9.sh b/.githooks/validate-k9.sh index 62f3056..c83e290 100755 --- a/.githooks/validate-k9.sh +++ b/.githooks/validate-k9.sh @@ -4,7 +4,7 @@ # # validate-k9.sh — K9 configuration file validation script # -# Scans for Nickel K9 contractiles (.k9.ncl) and validates: +# Scans for .k9 and .k9.ncl files and validates: # 1. K9! magic number on line 1 # 2. Pedigree block presence with required fields (name, version) # 3. Security level is one of: kennel, yard, hunt (case-insensitive) @@ -179,8 +179,7 @@ validate_k9() { # brace, depth started at 0, and the first nested block's close # prematurely terminated the validator's view of the pedigree — # making `pedigree.metadata.name` invisible. - if [[ "$line" =~ ^[[:space:]]*pedigree[[:space:]]*= ]] || \ - [[ "$line" =~ ^[[:space:]]*let[[:space:]]+[[:alnum:]_]*pedigree[[:space:]]*=[[:space:]]*\{ ]]; then + if [[ "$line" =~ ^[[:space:]]*pedigree[[:space:]]*= ]]; then has_pedigree=true in_pedigree=true pedigree_depth=0 @@ -293,12 +292,11 @@ validate_k9() { # --------------------------------------------------------------------------- echo "::group::K9 Configuration Validation" -echo "Scanning ${SCAN_PATH} for Nickel K9 contractiles (.k9.ncl)..." +echo "Scanning ${SCAN_PATH} for K9 files (.k9, .k9.ncl)..." echo "" -# Plain .k9 files are session-policy YAML, not Nickel K9 contractiles. -# Validate only the application/vnd.k9+nickel form documented by this repo. -mapfile -t k9_candidates < <(find "$SCAN_PATH" -name '*.k9.ncl' -not -path '*/.git/*' -type f | sort) +# Find all K9 files, excluding .git directory +mapfile -t k9_candidates < <(find "$SCAN_PATH" \( -name '*.k9' -o -name '*.k9.ncl' \) -not -path '*/.git/*' -type f | sort) # Apply paths-ignore filter k9_files=() diff --git a/.machine_readable/root-allow.txt b/.machine_readable/root-allow.txt index 2bfa724..5b20f02 100644 --- a/.machine_readable/root-allow.txt +++ b/.machine_readable/root-allow.txt @@ -20,12 +20,9 @@ AFFIRMATION.adoc # dated/signed honesty snapshot (README/EXPLAINME/AFF GOVERNANCE.adoc # governance model (validator accepts root or docs/governance/) MAINTAINERS.adoc # maintainer roster CONTRIBUTING.md # REQUIRED AT ROOT by scorecard-enforcer/openssf-compliance/quality CI (test -f, no .github fallback). The fuller copy in .github/ is GitHub's auto-discovery convention; dedupe is an owner decision (would need those CI checks updated to accept .github/). -CONTRIBUTING.adoc # canonical AsciiDoc contribution guide SECURITY.md # REQUIRED AT ROOT by scorecard-enforcer CI + the security-policy contractile (test -f SECURITY.md). See CONTRIBUTING.md note re: the .github/ copy. -SECURITY.adoc # canonical AsciiDoc security policy LICENSE CHANGELOG.md -CHANGELOG.adoc # canonical AsciiDoc project change history # ─── Build entry points (must live at root for their tooling) ──────────────── Justfile # delegates phases to build/just/*.just @@ -44,7 +41,6 @@ abi.ipkg # Idris2 package for the ABI seam; sourcedir=src/inte .devcontainer/ # VS Code dev container spec; tool-required at root .git/ .github/ # CONTRIBUTING.md, CODE_OF_CONDUCT.md, SECURITY.md, workflows/ -.githooks/ # repository validation hooks LICENSES/ # REUSE/SPDX licence texts (LICENSES/MPL-2.0.txt) .machine_readable/ # AI manifests, contractiles, custom-format configs .well-known/ diff --git a/.machine_readable/self-validating/methodology-guard.k9.ncl b/.machine_readable/self-validating/methodology-guard.k9.ncl index 4753242..17f1409 100644 --- a/.machine_readable/self-validating/methodology-guard.k9.ncl +++ b/.machine_readable/self-validating/methodology-guard.k9.ncl @@ -1,4 +1,3 @@ -K9! # SPDX-License-Identifier: MPL-2.0 # Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) # @@ -65,22 +64,4 @@ let methodology_guard = { }, }, } -in { - pedigree = { - schema_version = "1.0.0", - component_type = "methodology-validator", - security = { - leash = 'Yard, - trust_level = "validated-configuration", - allow_network = false, - allow_filesystem_write = false, - allow_subprocess = false, - }, - metadata = { - name = "methodology-guard", - version = "1.0.0", - description = "Validates declared repository methodology constraints", - }, - }, - guard = methodology_guard, -} +in methodology_guard diff --git a/container/deploy.k9.ncl b/container/deploy.k9.ncl index d4c643c..bf02b67 100644 --- a/container/deploy.k9.ncl +++ b/container/deploy.k9.ncl @@ -1,4 +1,3 @@ -K9! # SPDX-License-Identifier: MPL-2.0 # deploy.k9.ncl — Panoply deployment component (Hunt level) # @@ -40,7 +39,6 @@ let component_pedigree = { # L3: The Leash — Security # ───────────────────────────────────────────────────────────── security = { - leash = 'Hunt, trust_level = 'Hunt, allow_network = true, allow_filesystem_write = true,