From 96405e3264d457c699786d874c9589b13996e2b0 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 14 Oct 2026 13:35:19 +0000 Subject: [PATCH] feat(manifest-check): per-validator ignore lists, forwarded by the reusable audit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The manifests check runs the DEED and K9 validators over the caller's whole tree, and a caller has had no way to name a file that carries a manifest extension but is not a manifest. Measured on hyperpolymath/januskey 2026-10-14: audits/assail-classifications.a2ml, which is panic-attack's suppression registry (read from /audits/assail-classifications.a2ml), fails the DEED identity check, so `manifests` stays red. The file is neither under .machine_readable/, which the validator exempts, nor movable, because panic-attack reads that path. Both validators already take `paths-ignore`. manifest-check could not pass it on. actions/manifest-check: - New inputs `deed-paths-ignore` and `k9-paths-ignore`. They are separate because the two validators have different default lists; K9's skips coordination.k9 and the session files. One shared list would replace both sets of defaults, so ignoring a DEED path would start K9-scanning coordination.k9. - Each validator is called once, with or without the caller's list. Composite `with:` always passes a key, and both validators read an empty paths-ignore as "skip nothing", so forwarding an empty input would widen the scan. With both inputs empty, behaviour is unchanged. .github/workflows/main-estate-audit.yml: - workflow_call inputs `manifest-deed-paths-ignore` and `manifest-k9-paths-ignore` (default empty), passed to manifest-check. On this repository's own push and pull_request runs they are empty. - The gate pins stay at 1aa6bfb in this commit. Until the follow-up repin to this change's merge commit, manifest-check at 1aa6bfb does not declare the new inputs. GitHub reports them as unexpected inputs (a warning) and ignores them, and tests/lock-transitive-closure.sh fails its "serves the current actions/ tree" check by design. This is the house #48 → #49 pattern. code-hygiene-self-test.yml: three controls on the existing bad fixtures. A DEED-ignored bad DEED file passes. A K9-ignored bad K9 file passes. A DEED ignore list leaves K9 validation on, so the bad K9 file still fails. The existing negatives without a list still fail. All six outcomes reproduced locally against deed-ecosystem f9d999b and k9-ecosystem 2155aa2. README: how a caller names such a file, and why the defaults must be repeated. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Kjdf4Mf4Z41bNQh2TrsEp9 --- .github/workflows/code-hygiene-self-test.yml | 34 +++++++++++++++- .github/workflows/main-estate-audit.yml | 19 +++++++++ README.adoc | 29 ++++++++++++++ actions/manifest-check/action.yml | 41 ++++++++++++++++++++ 4 files changed, 121 insertions(+), 2 deletions(-) diff --git a/.github/workflows/code-hygiene-self-test.yml b/.github/workflows/code-hygiene-self-test.yml index 3198517..59977af 100644 --- a/.github/workflows/code-hygiene-self-test.yml +++ b/.github/workflows/code-hygiene-self-test.yml @@ -141,13 +141,43 @@ path: "${{ steps.fixtures.outputs.root }}/bad-k9", }, }, + # The ignore lists: each must lift exactly its own validator's finding. + # The two positives use the same bad fixtures as the negatives above, + # so they pass only because the named file is skipped. { - name: "Verify both negative controls failed", + name: "An invalid DEED file on the caller's DEED ignore list must pass", + uses: "$/actions/manifest-check", + with: { + path: "${{ steps.fixtures.outputs.root }}/bad-deed", + deed-paths-ignore: "control.deed", + }, + }, + { + name: "An invalid K9 file on the caller's K9 ignore list must pass", + uses: "$/actions/manifest-check", + with: { + path: "${{ steps.fixtures.outputs.root }}/bad-k9", + k9-paths-ignore: "control.k9.ncl", + }, + }, + { + name: "A DEED ignore list must leave K9 validation on", + id: "deed-ignore-k9-on", + continue-on-error: true, + uses: "$/actions/manifest-check", + with: { + path: "${{ steps.fixtures.outputs.root }}/bad-k9", + deed-paths-ignore: "control.k9.ncl", + }, + }, + { + name: "Verify the negative controls failed", env: { DEED_OUTCOME: "${{ steps.bad-deed.outcome }}", K9_OUTCOME: "${{ steps.bad-k9.outcome }}", + CROSS_OUTCOME: "${{ steps.deed-ignore-k9-on.outcome }}", }, - run: "test \"$DEED_OUTCOME\" = failure && test \"$K9_OUTCOME\" = failure", + run: "test \"$DEED_OUTCOME\" = failure && test \"$K9_OUTCOME\" = failure && test \"$CROSS_OUTCOME\" = failure", }, # formatting-check reads `git ls-files` in the workspace, so its controls # run on this checkout: clean it must pass, and with one planted diff --git a/.github/workflows/main-estate-audit.yml b/.github/workflows/main-estate-audit.yml index 98bb68c..8019d36 100755 --- a/.github/workflows/main-estate-audit.yml +++ b/.github/workflows/main-estate-audit.yml @@ -49,6 +49,22 @@ on: pull_request: branches: [ "main" ] workflow_call: + inputs: + # Forwarded to manifest-check. For files that carry a manifest extension + # but are not manifests (panic-attack's suppression registry at + # audits/assail-classifications.a2ml, for one). A value REPLACES that + # validator's default ignore list; empty keeps the defaults. On this + # repository's own push and pull_request runs both are empty. + manifest-deed-paths-ignore: + description: 'Newline-separated path fragments the DEED validator skips (replaces its defaults; empty keeps them).' + type: string + required: false + default: '' + manifest-k9-paths-ignore: + description: 'Newline-separated path fragments the K9 validator skips (replaces its defaults; empty keeps them).' + type: string + required: false + default: '' permissions: contents: read @@ -114,6 +130,9 @@ jobs: - name: Manifest Validation Gate if: ${{ !cancelled() && steps.checkout.outcome == 'success' }} uses: hyperpolymath/cicd-suite/actions/manifest-check@1aa6bfb657f8240db089a8eef1ad4a7ac198bcf0 # 2026-10-09 (#48) + with: + deed-paths-ignore: ${{ inputs.manifest-deed-paths-ignore }} + k9-paths-ignore: ${{ inputs.manifest-k9-paths-ignore }} - name: Recipes Set Validation Gate if: ${{ !cancelled() && steps.checkout.outcome == 'success' }} uses: hyperpolymath/cicd-suite/actions/recipes-set-check@1aa6bfb657f8240db089a8eef1ad4a7ac198bcf0 # 2026-10-09 (#48) diff --git a/README.adoc b/README.adoc index a132235..f389f19 100644 --- a/README.adoc +++ b/README.adoc @@ -182,6 +182,35 @@ because `+GITHUB_TOKEN+` may not write under `+.github/workflows/+`. `+rollout_estate.sh+` does all three for every local estate clone. It writes files and does not commit them. +If a file carries a manifest extension but is not a manifest, the +`+manifests+` check will fail on it. Panic-attack's suppression registry +at `+audits/assail-classifications.a2ml+` is one such file. Name it in the +caller rather than editing or moving it: + +[source,yaml] +---- +jobs: + estate: + uses: hyperpolymath/cicd-suite/.github/workflows/main-estate-audit.yml@<40-hex sha> # cicd-suite main + with: + manifest-deed-paths-ignore: | + vendor/ + vendored/ + verified-container-spec/ + .audittraining/ + integration/fixtures/ + test/fixtures/ + tests/fixtures/ + audits/assail-classifications.a2ml +---- + +The list goes to the DEED validator as its `+paths-ignore+` and +*replaces* its defaults, so repeat the defaults you still want, as above. +`+manifest-k9-paths-ignore+` does the same for the K9 validator. That +validator has a different default list, which is why the two lists are +separate. Each fragment is matched as a substring of the file's path. +Leave both unset and each validator keeps its own defaults. + === Licence Code MPL-2.0, prose CC-BY-SA-4.0. See link:LICENSE[`+LICENSE+`]. diff --git a/actions/manifest-check/action.yml b/actions/manifest-check/action.yml index 8de4188..29c3d30 100755 --- a/actions/manifest-check/action.yml +++ b/actions/manifest-check/action.yml @@ -10,19 +10,60 @@ inputs: description: 'Promote validator warnings to failures.' required: false default: 'false' + deed-paths-ignore: + description: >- + Newline-separated path fragments the DEED validator skips, passed through + as its own `paths-ignore`. Use it for files that carry a manifest + extension but are not manifests, such as panic-attack's suppression + registry at audits/assail-classifications.a2ml. A value REPLACES the + validator's default list (vendor/, vendored/, verified-container-spec/, + .audittraining/, integration/fixtures/, test/fixtures/, tests/fixtures/), + so repeat those you still want skipped. Empty, the default, leaves the + validator's defaults in place. + required: false + default: '' + k9-paths-ignore: + description: >- + The same for the K9 validator, whose default list differs (it also skips + coordination.k9, session/custom-checks.k9 and + self-validating/methodology-guard.k9.ncl). Kept separate from + deed-paths-ignore so that ignoring a DEED path never replaces K9's + defaults. Empty, the default, leaves the validator's defaults in place. + required: false + default: '' runs: using: 'composite' + # Each validator is called once, with or without the caller's list. A + # `with:` key is always passed, even when empty, and both validators read an + # empty paths-ignore as "skip nothing", so forwarding an empty input would + # widen the scan rather than leave it alone. steps: - name: Validate DEED manifests + if: ${{ inputs.deed-paths-ignore == '' }} uses: hyperpolymath/deed-ecosystem/validate-action@f9d999b60cb5f383679ea19912bcdc49c944973a # main @ 2026-09-17 — SHA-pinned: sha_pinning_required + audit de-onboarded; lock SHA keys cover the onboarded self-test path with: path: ${{ inputs.path }} strict: ${{ inputs.strict }} + - name: Validate DEED manifests (caller's ignore list) + if: ${{ inputs.deed-paths-ignore != '' }} + uses: hyperpolymath/deed-ecosystem/validate-action@f9d999b60cb5f383679ea19912bcdc49c944973a # main @ 2026-09-17 — SHA-pinned: ditto + with: + path: ${{ inputs.path }} + strict: ${{ inputs.strict }} + paths-ignore: ${{ inputs.deed-paths-ignore }} - name: Validate K9 manifests + if: ${{ inputs.k9-paths-ignore == '' }} + uses: hyperpolymath/k9-ecosystem/validate-action@2155aa26a21758f2ba119f61bc7e0e1981c106fb # main @ 2026-09-17 — SHA-pinned: ditto + with: + path: ${{ inputs.path }} + strict: ${{ inputs.strict }} + - name: Validate K9 manifests (caller's ignore list) + if: ${{ inputs.k9-paths-ignore != '' }} uses: hyperpolymath/k9-ecosystem/validate-action@2155aa26a21758f2ba119f61bc7e0e1981c106fb # main @ 2026-09-17 — SHA-pinned: ditto with: path: ${{ inputs.path }} strict: ${{ inputs.strict }} + paths-ignore: ${{ inputs.k9-paths-ignore }} # These validators check manifest structure. Executable Nickel contracts need # a separate Nickel evaluation step; neither an empty-field grep nor this # structural check establishes that a contract evaluates or a proof holds.