Repository navigation
feat(hypatia-rules): identity coherence and SHA pinning #1270
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| # SPDX-License-Identifier: MPL-2.0 | ||
| # HYP-S010 — CLADE Identity Coherence | ||
| # Fires when a repository's CLADE.a2ml identity block does not cohere. | ||
| # | ||
| # Root cause seen 2026-10-15 on metadatastician/odraz (PR #19): the repo's | ||
| # CLADE.a2ml was copied verbatim from the template, so its [identity] block | ||
| # declared rsr-template-repo (owner, canonical-name, prefixed-name, forges, | ||
| # born) while the minted uuid was recomputed for metadatastician/odraz — | ||
| # two true facts pointing at different repositories. A reviewer bot then | ||
| # aligned the uuid to the stale declaration, which was the wrong direction: | ||
| # the declaration was the residue. Every CLADE must declare THIS repository | ||
| # and carry the recomputable profile-C mint for that declaration, with | ||
| # CLAUDE.md in agreement. This rule reifies the coherence invariant so the | ||
| # direction of the fix is not a judgement call made under review pressure. | ||
|
|
||
| @rule(version="1.0"): | ||
| id: HYP-S010 | ||
| name: "CLADE identity coherence" | ||
| description: "The [identity]/[forges] block of .machine_readable/descriptiles/CLADE.a2ml must declare THIS repository, and its uuid must be the recomputable UUIDv8 profile-C mint for the declared owner/name (domain gv-clade-index)" | ||
| severity: high | ||
| category: IdentityIntegrity | ||
| auto_fixable: false # the direction of the fix (declaration vs preimage) is an owner ruling, not a rewrite | ||
| source: standards/hypatia-rules | ||
|
|
||
| @scanner(type="file-tree"): | ||
| target_repo: "any estate repo carrying .machine_readable/descriptiles/CLADE.a2ml" | ||
| reference: "docs/decisions/ADR-008-uuid-v8-estate-standard.adoc" | ||
| @end | ||
|
|
||
| @parameters: | ||
| profile_c_domains: docs/decisions/ADR-008-profile-c-domains.json | ||
| identity_domains: [gv-clade-index, julia-package-identity] | ||
| template_canonical_name: rsr-template-repo | ||
| template_repo: hyperpolymath/rsr-template-repo | ||
| @end | ||
|
|
||
| @must: | ||
| # the declaration is THIS repo, not the template it was instantiated from | ||
| - "primary-owner + canonical-name identify the repository the file lives in (canonical-name == rsr-template-repo outside hyperpolymath/rsr-template-repo is template residue)" | ||
| - "forges.github names the repository's actual home; empty rather than the template's forge" | ||
| - "born does not predate the repository's first commit" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Make the "born" and "forges" checks executable. The Define Also applies to: 51-51 🤖 Prompt for AI Agents |
||
| # the uuid is recomputable from the declaration | ||
| - "uuid == profile-C(domain=gv-clade-index, name=github.com/<primary-owner>/<canonical-name>)" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Define the preimage format and the domain handling consistently. The Also, Document the preimage construction once, in the Also applies to: 52-52 🤖 Prompt for AI Agents |
||
| - "the recorded preimage comment, the derivation note, and the uuid agree" | ||
| # both metadata documents agree | ||
| - "CLAUDE.md's identity line carries the same uuid, preimage, forge, and born as CLADE.a2ml" | ||
| @end | ||
|
|
||
| @query(engine="vcl"): | ||
| clade_identity(scan="repo-tree", file=".machine_readable/descriptiles/CLADE.a2ml") | ||
| |> where("not declares_this_repository(identity, repo)") | ||
| |> or_where("uuid != profile_c_mint('gv-clade-index', identity.primary_owner, identity.canonical_name)") | ||
| |> or_where("disagrees_with(claude_md_identity_line)") | ||
| |> emit("compliance.finding.new", severity=high) | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,46 @@ | ||||||
| # SPDX-License-Identifier: MPL-2.0 | ||||||
| # HYP-S011 — Workflow uses refs are SHA-pinned | ||||||
| # Fires when a `.github/workflows` `uses:` reference resolves to a version | ||||||
| # tag (or any moving ref) instead of a full commit SHA. | ||||||
| # | ||||||
| # Root cause seen 2026-10-15 on hyperpolymath/choreographic-types (PR #33): | ||||||
| # a governance-repair PR restored `actions/checkout@v7.0.1` — a tag — in | ||||||
| # codeql.yml and proofs.yml, while the estate's pinning policy | ||||||
| # (sha_pinning_required) and the actions.lock both speak in SHAs. A tag can | ||||||
| # be re-pointed upstream at any time, which silently changes what the | ||||||
| # workflow executes; the lock's identity checks (HYPATIA PI002/PI004) | ||||||
| # compare SHA-to-comment, so a tag ref sidesteps them entirely. | ||||||
| # | ||||||
| # Tags remain welcome as *comments* (`@3d3c42e5… # v7.0.1`) — the ref is | ||||||
| # the SHA, the comment is the label. This rule enforces the content-level | ||||||
| # invariant that PI001-PI005 assume but never state. | ||||||
|
|
||||||
| @rule(version="1.0"): | ||||||
| id: HYP-S011 | ||||||
| name: "Workflow uses refs are SHA-pinned" | ||||||
| description: "Every `uses:` ref in .github/workflows must resolve to a full commit SHA (40 hex chars); version tags and moving refs are rejected at content level" | ||||||
| severity: medium | ||||||
| category: CiResilience | ||||||
| auto_fixable: true # rewriting @vX.Y.Z to the locked SHA is mechanical when the lock carries the entry | ||||||
| source: standards/hypatia-rules | ||||||
|
|
||||||
| @scanner(type="repo-tree"): | ||||||
| dir: ".github/workflows" | ||||||
| glob: "*.{yml,yaml}" | ||||||
| @end | ||||||
|
|
||||||
| @parameters: | ||||||
| exempt_refs: [docker://] # container refs use digests, not SHAs | ||||||
| @end | ||||||
|
|
||||||
| @must: | ||||||
| - "every `uses:` ref is a full commit SHA (^[0-9a-f]{40}$), with the tag kept only as a trailing comment" | ||||||
| - "a ref matching ^v?\d+(\.\d+)*$ (a version tag) or ^[a-z]+$ that is not a SHA (a moving ref: stable, latest, main) is a finding" | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win Correct the moving-ref pattern in The pattern Proposed fix--- "a/hypatia-rules/uses-ref-sha-pinned.a2ml"
+++ "b/hypatia-rules/uses-ref-sha-pinned.a2ml"
@@ -35,7 +35,7 @@
@must:
- "every `uses:` ref is a full commit SHA (^[0-9a-f]{40}$), with the tag kept only as a trailing comment"
-- "a ref matching ^v?\d+(\.\d+)*$ (a version tag) or ^[a-z]+$ that is not a SHA (a moving ref: stable, latest, main) is a finding"
+- "any ref that is not a full 40-hex SHA (version tag, branch, or other moving ref such as stable, latest, main) is a finding"
- "the SHA agrees with the repo's actions.lock entry for the same action (defers to PI004 for the comparison itself)"
- "local reusable workflows (`./…`) are exempt — they resolve within the repo"
@end📝 Committable suggestion
Suggested change
🤖 Prompt for AI AgentsSource: Learnings |
||||||
| - "the SHA agrees with the repo's actions.lock entry for the same action (defers to PI004 for the comparison itself)" | ||||||
| - "local reusable workflows (`./…`) are exempt — they resolve within the repo" | ||||||
| @end | ||||||
|
|
||||||
| @query(engine="vcl"): | ||||||
| workflow_uses(scan="repo-tree", dir=".github/workflows") | ||||||
| |> where("ref !~ '^[0-9a-f]{40}$' and ref !~ '^\\./' and ref !~ '^docker://'") | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Fix the VCL filter: it exempts reusable workflow refs only partly, and it ignores the stated exemptions. The Reusable workflow refs such as The pattern Proposed fix--- "a/hypatia-rules/uses-ref-sha-pinned.a2ml"
+++ "b/hypatia-rules/uses-ref-sha-pinned.a2ml"
@@ -42,5 +42,5 @@
@query(engine="vcl"):
workflow_uses(scan="repo-tree", dir=".github/workflows")
- |> where("ref !~ '^[0-9a-f]{40}$' and ref !~ '^\\./' and ref !~ '^docker://'")
+ |> where("ref !~ '^[0-9a-f]{40}$' and ref !~ '^[.]/' and ref !~ '^docker://'")
|> emit("compliance.finding.new", severity=medium, fix="resolve the tag to its commit SHA via the actions.lock and pin the ref, keeping the tag as a comment")📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
| |> emit("compliance.finding.new", severity=medium, fix="resolve the tag to its commit SHA via the actions.lock and pin the ref, keeping the tag as a comment") | ||||||
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| ; SPDX-License-Identifier: MPL-2.0 | ||
| ; HYP-UUID-001 — estate UUID policy enforcement (ADR-008, amended by D320). | ||
| ; Supersedes uuid-v7-estate-conformance.a2ml: the estate standardised on v7 | ||
| ; (#1063) and was amended to strict UUIDv8 profile C by owner ruling D320 | ||
| ; (2026-10-07). The gates run strict, version 8 only. | ||
|
|
||
| (rule | ||
| (id "HYP-UUID-001") | ||
| (name "Estate UUIDs are version 8, profile C, over registered domains") | ||
| (severity high) | ||
| (category identity-integrity) | ||
| (source "docs/decisions/ADR-008-uuid-v8-estate-standard.adoc (amendment D320)") | ||
| (applies-to ("hyperpolymath" "metadatastician")) | ||
| (must | ||
| "all newly generated UUIDs are UUIDv8 profile C: bytes 0..15 of SHA-256(domain \":\" name), version nibble 8, RFC 9562 variant 10" | ||
| "the domain is registered in docs/decisions/ADR-008-profile-c-domains.json and the preimage identifies the artifact itself, never another repository" | ||
|
Comment on lines
+15
to
+16
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: #!/bin/bash
rg -n -i 'profile-c-domains|sha256|domain' scripts/check-uuid-v8.shRepository: hyperpolymath/standards Length of output: 161 The gate implementation must enforce the requirements stated in the rule, or the rule must identify the component that enforces them. The current 🤖 Prompt for AI Agents |
||
| "all UUID literals have version nibble 8 and RFC 9562 variant 10; v5 and v7 literals are rejected by the strict gate" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value 🔎 Supported by static analysis🏁 Script executed: #!/bin/bash
rg -n -C3 'v5|V5|version.*5' scripts/check-uuid-v8.sh scripts/tests/uuid-v8-test.shRepository: hyperpolymath/standards Length of output: 2538 🏁 Script executed: set -eu
printf '%s\n' '--- rule ---'
nl -ba hypatia-rules/uuid-v8-estate-conformance.a2ml | sed -n '1,40p'
printf '%s\n' '--- gate ---'
nl -ba scripts/check-uuid-v8.sh | sed -n '1,260p'
printf '%s\n' '--- tests ---'
nl -ba scripts/tests/uuid-v8-test.sh | sed -n '1,145p'Repository: hyperpolymath/standards Length of output: 15006 Add a strict-mode v5 rejection test. The gate rejects v5 UUIDs under Suggested test fresh; printf 'id = "%s"\n' "$V7_8" > "$WORK/t/a.toml"
expect 1 "a v7 UUID is rejected under --strict" --strict
names "a.toml: non-v8 UUID literal ($V7_8)" "the --strict rejection names the v7 UUID"
+fresh; printf 'id = "%s"\n' "$V5" > "$WORK/t/a.json"
+expect 1 "a v5 UUID is rejected under --strict" --strict
+names "a.json: non-v8 UUID literal ($V5)" "the --strict rejection names the v5 UUID"
fresh; printf 'id = "%s"\n' "$V8_C" > "$WORK/t/a.toml"🤖 Prompt for AI Agents |
||
| "a Julia [deps] mapping that names an estate package uses that package's own declared uuid, so every Project.toml in the repo is self-consistent" | ||
| "legacy values have an auditable migration alias and explicit type" | ||
| "unknown and unmeasured repositories are not reported as compliant") | ||
| (gate "scripts/check-uuid-v8.sh") | ||
| (remediation "re-mint from the registered domain and the artifact's own preimage; never hash-copy legacy UUIDs and never retain a preimage that identifies a different repository")) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
Update the README sections that follow the table.
The README table now lists HYP-S010, HYP-S011 and the changed HYP-UUID-001. The "Dependencies" section stops at HYP-S008. The prose sections do not describe the new rules. Readers cannot see which inputs the new rules read, such as
CLADE.a2ml,CLAUDE.mdandactions.lock. Add the new inputs to the "Dependencies" section.🤖 Prompt for AI Agents