feat(scripts): extend §1.5 verb-canary coverage (Phase E)#215
Merged
Conversation
Tightens `scripts/hcg-policy-smoke.sh` against three classes of
verb-governance regression the original three canaries (DELETE/PUT/
PATCH on `/cartridges` and `/health`) don't catch. Single-lane HCG
tier-2 channel (`standards#91`); Phase E (`standards#100`) is the
active phase.
Added probes:
1. **`OPTIONS /cartridges`** — `global_verbs: [GET, POST]` bans
OPTIONS, but a CORS preflight auto-responder added later would
silently bypass policy. The canary fails closed against that
regression class.
2. **`DELETE /cartridge/probe/invoke`** — exercises the regex route
`^/cartridge/[A-Za-z0-9_.-]+/invoke$` under a banned verb. The
existing exact-path canaries don't catch a regex-matcher regression
where the path is accepted under any verb instead of only the verb
the rule lists.
3. **`GET /cartridges/ssg-mcp/webhook`** — the path is in the policy as
a documented public exception, but only for POST. The canary
verifies the `{path, verb}` pairing is enforced: GET on the same
path must default-deny because no rule covers it.
Deliberate omission: HEAD. Curl with `-X HEAD` (vs `--head`) waits for
a body the server will not send, which interacts badly with the
script's `--max-time 10`. HEAD enforcement remains covered by the
gateway's own unit tests; the §1.5 operator pre-check focuses on
probes that survive curl's method quirks. The reasoning is captured
inline in the script comment so a future maintainer doesn't add it
back as an oversight.
Runbook §1.5 description updated to reflect the expanded canary set;
version bump 0.4 → 0.5; status line records the extension.
Verification:
- `bash -n scripts/hcg-policy-smoke.sh` — syntax check passes.
- Synthetic always-403 mock on :18443 — PASS=31 FAIL=0 (was 28),
the three new canaries report PASS. Exits 0.
- `--help` and bad-args exit codes unchanged (64).
Out of scope:
- The `--with-backend` allow-path matrix is not extended here. The
authenticated routes the script probes in allow mode are the ones
actually wired in `BojRest.Router`; the additional policy entries
(`graphql`, `sse`, `order`, `umoja/*`, etc.) are declared-not-yet-
wired per contract §8 and would 404 from BoJ, which the
`allow_or_upstream` pattern misdiagnoses as gateway-deny. They stay
in the deny matrix until they are wired in BoJ.
- The `ssg-mcp-webhook-post` route is not added to the `--with-backend`
allow probes for the same reason: the handler is in `openapi.yaml`
but not yet in `router.ex`.
- The script does not parse `config/gateway-policy-boj.yaml` to derive
the probe matrix; the matrix stays hand-maintained and the
parity-with-policy property remains a manual maintenance discipline.
PR #210's commitment ("the script doubles as a policy-completeness
checklist") is preserved.
Channel position:
```
standards#91 (parent, open)
├── #96 Phase A — closed
├── #97 Phase B — closed
├── #98 Phase C — closed
├── #99 Phase D — closed (joint-closed via boj-server#168)
└── #100 Phase E — IN PROGRESS
├── E5 runbook draft — boj-server#128 (landed)
├── E1 loopback prereqs — boj-server#130/#131/#132/#165/#173 (landed)
├── E1 deploy spec — http-capability-gateway#38 (landed)
├── E1 live policy promotion — boj-server#208 (landed)
├── §1.5 operator pre-check smoke — boj-server#210 (landed)
├── §1.5 verb-canary expansion — THIS PR
├── E1 .ctp signing — owner follow-up
├── E2 staging cut-over — owner follow-up
├── E3 telemetry verification — owner follow-up
├── E4 production rollout — owner follow-up
└── §6.4 Trustfile flip + §6.5 joint-close — owner-only
```
Refs hyperpolymath/standards#91
Refs hyperpolymath/standards#100
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
🔍 Hypatia Security ScanFindings: 272 issues detected
View findings[
{
"reason": "Stale AI session file -- delete",
"type": "stale",
"file": "GEMINI.md",
"action": "delete",
"rule_module": "root_hygiene",
"severity": "medium"
},
{
"reason": "Issue in abi-drift.yml",
"type": "missing_timeout_minutes",
"file": "abi-drift.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in codeql.yml",
"type": "missing_timeout_minutes",
"file": "codeql.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in container-publish.yml",
"type": "missing_timeout_minutes",
"file": "container-publish.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in dogfood-gate.yml",
"type": "missing_timeout_minutes",
"file": "dogfood-gate.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in dogfood-gate.yml",
"type": "missing_timeout_minutes",
"file": "dogfood-gate.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in dogfood-gate.yml",
"type": "missing_timeout_minutes",
"file": "dogfood-gate.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in dogfood-gate.yml",
"type": "missing_timeout_minutes",
"file": "dogfood-gate.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in dogfood-gate.yml",
"type": "missing_timeout_minutes",
"file": "dogfood-gate.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in dogfood-gate.yml",
"type": "missing_timeout_minutes",
"file": "dogfood-gate.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
}
]Powered by Hypatia Neurosymbolic CI/CD Intelligence |
hyperpolymath
added a commit
that referenced
this pull request
Jun 13, 2026
…s) (#216) ## What this fixes PRs **#213** (dependabot `flake.lock`) and **#215** (scripts-only) are stuck at `mergeable_state: blocked` despite every check green (and #213 approved). Root cause: seven gates are **workflow-level path-filtered** (`on.*.paths`). When a PR touches none of a gate's paths the workflow never runs, so its **required** status check stays *"Expected"* forever → permanent block. The asymmetry this exploits: **a path-filtered workflow that never runs blocks; a job skipped via `if:` reports SUCCESS to required checks.** This converts the former into the latter. ## Change (uniform across all 7) `abi-drift`, `backend-assurance`, `e2e`, `lsp-dap-bsp`, `proofs`, `truthfulness`, `zig-test`: 1. **Drop the `on.*.paths` filter** → the workflow always runs, so the required check is always created. 2. **Add a lightweight `changes` job** that recomputes the gate's *original* path set via `git diff origin/<base>...HEAD` (the same pattern `abi-drift`/`zig-test` already use internally). 3. **Gate every heavy job** with `needs: changes` + `if: needs.changes.outputs.run == 'true'`. Nothing relevant changed → heavy job **skipped → passes** → PR unblocked *and* no wasted CI. **No branch-protection change needed** — job/check names are unchanged. `workflow_dispatch` added to all 7 for manual full runs. ## Safety - **Fail-safe toward running:** the detector defaults `run=true` and only sets `false` when a *successful* diff shows no relevant path changed (any fetch/diff error or unknown base ⇒ run). The dangerous direction — skipping a gate that *should* run — can't happen on detection failure. - Each regex **mirrors the gate's original `paths:`**, so a relevant change runs it exactly as before (verified: 77/77 unit checks — `abi/`→proofs+abi-drift, `ffi/`→zig+truthfulness+abi-drift, `SafetyLemmas.idr`→backend-assurance+proofs, etc.; #213/#215 → all skip). ## Validation ``` actionlint -shellcheck= (all 7) → EXIT 0 (structure/expressions/needs-graph valid) actionlint (all 7) → only PRE-EXISTING shellcheck infos in original run-scripts (untouched) detection regexes (77 checks) → 77/77 correct gated heavy jobs → 15/15 (abi-drift 1, backend-assurance 1, e2e 5, lsp-dap-bsp 4, proofs 2, truthfulness 1, zig-test 1) leftover path filters → 0 ``` **Self-validating:** editing a workflow file no longer self-triggers its heavy gate (kept out of each regex), so **this PR's own checks exercise the skip path** — the gates should report *skipped/success* here, demonstrating the unblock live. ## Draft — why I can't runtime-test the `run=true` branch (it needs the GH runners), and this changes how **required** gates fire, so it wants your eyes before merge. Mark ready / squash-merge when you're satisfied. After it lands, re-check #213/#215 — they should flip from `blocked` to mergeable. > Out of scope (flagged, not fixed): the pre-existing shellcheck infos in `abi-drift`/`zig-test` original scripts, and the Hypatia baseline backlog (stale `GEMINI.md`, unpinned `governance.yml` action, `missing_timeout_minutes` on several workflows) — these are non-blocking and belong in a separate hygiene pass. https://claude.ai/code/session_019tMcRS1Dm1nWjjYP4WvbJa --- _Generated by [Claude Code](https://claude.ai/code/session_019tMcRS1Dm1nWjjYP4WvbJa)_ Co-authored-by: Claude <noreply@anthropic.com>
🔍 Hypatia Security ScanFindings: 272 issues detected
View findings[
{
"reason": "Stale AI session file -- delete",
"type": "stale",
"file": "GEMINI.md",
"action": "delete",
"rule_module": "root_hygiene",
"severity": "medium"
},
{
"reason": "Issue in abi-drift.yml",
"type": "missing_timeout_minutes",
"file": "abi-drift.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in codeql.yml",
"type": "missing_timeout_minutes",
"file": "codeql.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in container-publish.yml",
"type": "missing_timeout_minutes",
"file": "container-publish.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in dogfood-gate.yml",
"type": "missing_timeout_minutes",
"file": "dogfood-gate.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in dogfood-gate.yml",
"type": "missing_timeout_minutes",
"file": "dogfood-gate.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in dogfood-gate.yml",
"type": "missing_timeout_minutes",
"file": "dogfood-gate.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in dogfood-gate.yml",
"type": "missing_timeout_minutes",
"file": "dogfood-gate.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in dogfood-gate.yml",
"type": "missing_timeout_minutes",
"file": "dogfood-gate.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in dogfood-gate.yml",
"type": "missing_timeout_minutes",
"file": "dogfood-gate.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
}
]Powered by Hypatia Neurosymbolic CI/CD Intelligence |
hyperpolymath
added a commit
that referenced
this pull request
Jun 13, 2026
…219) Documentation-only follow-up to #216 — captures the required-gate "skip-shim" as a durable convention so it can't be silently regressed, and records the work in machine-readable state. **No workflow or code changes.** ## What's added - **`docs/AI-CONVENTIONS.adoc`** — new "CI / Required Status Checks" section (the do/don't rule). Also fixes a **stale** Banned-Languages row: `TypeScript → AffineScript` (not ReScript — retired as the destination 2026-04-30, per CLAUDE.md). - **`.claude/CLAUDE.md`** — matching "CI / Required Status Checks" section for AI agents. - **`docs/wikis/CI-and-Required-Checks.adoc`** — new developer wiki page: the problem (path-filtered required check → permanent "Expected" → blocked PR), the fix, a copy-paste workflow template, a do/don't table, and the green-but-blocked diagnostic. Linked from `Home.adoc`. - **`.machine_readable/6a2/STATE.a2ml`** — 2026-06-13 session entry (#216 + merged #213/#215 + follow-ups #218/#46/#47/#48); `last-updated` bumped. - **`.machine_readable/6a2/PLAYBOOK.a2ml`** — `[ci-required-gates]` runbook section. - **`CONTRIBUTING.md`** — brief pointer note. Both a2ml files verified to still parse as TOML. Draft for your review. https://claude.ai/code/session_019tMcRS1Dm1nWjjYP4WvbJa --- _Generated by [Claude Code](https://claude.ai/code/session_019tMcRS1Dm1nWjjYP4WvbJa)_ Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Tightens
scripts/hcg-policy-smoke.shagainst three verb-governance regression classes the original three canaries (DELETE/PUT/PATCH on/cartridgesand/health) don't catch. Single-lane HCG tier-2 channel (standards#91); Phase E (standards#100) is the active phase.What this PR adds
Three new deny-mode verb-canary probes:
OPTIONS /cartridges—global_verbs: [GET, POST]bans OPTIONS, but a CORS preflight auto-responder added later would silently bypass policy. The canary fails closed against that regression class.DELETE /cartridge/probe/invoke— exercises the regex route^/cartridge/[A-Za-z0-9_.-]+/invoke$under a banned verb. The existing exact-path canaries don't catch a regex-matcher regression where the path is accepted under any verb instead of only the verb the rule lists.GET /cartridges/ssg-mcp/webhook— the path is in the policy as a documented public exception, but only for POST. The canary verifies the{path, verb}pairing is enforced: GET on the same path must default-deny because no rule covers it.Runbook §1.5 description updated to enumerate the expanded canary set; runbook version bump 0.4 → 0.5.
What this PR deliberately does NOT do
Probe HEAD. Curl with
-X HEAD(vs--head) waits for a body the server will not send, which interacts badly with the script's--max-time 10. HEAD enforcement remains covered by the gateway's own unit tests; the §1.5 operator pre-check focuses on probes that survive curl's method quirks. The reasoning is captured inline in the script comment so a future maintainer doesn't add it back as an oversight.Extend the
--with-backendallow-path matrix. The authenticated routes the script probes in allow mode are the ones actually wired inBojRest.Router; the additional policy entries (graphql,sse,order,umoja/*, etc.) are declared-not-yet-wired per contract §8 and would 404 from BoJ, which theallow_or_upstreampattern misdiagnoses as gateway-deny. They stay in the deny matrix until they are wired in BoJ. Thessg-mcp-webhook-postroute is not added to the--with-backendallow probes for the same reason.Auto-derive the probe matrix from the policy YAML. The matrix stays hand-maintained and the parity-with-policy property remains a manual maintenance discipline. PR feat(scripts): hcg-policy-smoke.sh — §1.5 operator pre-check (Phase E) #210's commitment ("the script doubles as a policy-completeness checklist") is preserved.
Close
standards#100. Per runbook §6.5 the joint-close happens after the §6.4 Trustfile flip, which itself follows the §3.3 100% production-soak window. UsingRefsper the Phase E PR convention (chore(deps): bump nixpkgs from01fbdeeto6368eda#38, feat(config): promote gateway policy example → live (Phase E §1.5) #208, feat(scripts): hcg-policy-smoke.sh — §1.5 operator pre-check (Phase E) #210).Verification
bash -n scripts/hcg-policy-smoke.sh— syntax check passes.:18443—PASS=31 FAIL=0(was 28); the three new canaries report PASS; exit 0.--helpand bad-args exit codes unchanged (64).MPL-2.0unchanged.Channel position
Refs hyperpolymath/standards#91
Refs hyperpolymath/standards#100
🤖 Generated with Claude Code
Generated by Claude Code