From 86402c0c781b56163cb4b4e8709969edc902daf9 Mon Sep 17 00:00:00 2001 From: Konrad Heimel Date: Tue, 25 Aug 2026 11:01:24 +0200 Subject: [PATCH] :memo: docs(planning): record the 2026-08-25 operator rulings and withdraw the stale rule-7 block (D-162..D-164) --- CHANGELOG.md | 1 + docs/decisions/decisions.md | 3 +++ docs/planning/rego-tier-ceiling.md | 5 ++++- openspec/specs/backlog.md | 4 ++-- openspec/specs/p5-e11-rego-backend/spec.md | 4 +++- 5 files changed, 13 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8101ee17..a392ce30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -103,6 +103,7 @@ repository still gets a decision, never by following the link; no release carrie - :memo: docs(ci): withdraw three falsifiable claims from the PR-reach reader (D-157) - :memo: docs(ci): withdraw the third copy of the retracted claim (D-157) - :memo: docs(e10): record the operator LGTM that satisfies E10-S00's GOVERNANCE marker (D-161) +- :memo: docs(planning): record the 2026-08-25 operator rulings and withdraw the stale rule-7 block (D-162..D-164) ### Features - :sparkles: feat(audit): measure REL-03/REL-07 by real mutation run, not by name diff --git a/docs/decisions/decisions.md b/docs/decisions/decisions.md index 83732db3..af23855e 100644 --- a/docs/decisions/decisions.md +++ b/docs/decisions/decisions.md @@ -166,3 +166,6 @@ project/process decisions. | D-159 | 2026-08-23 | **ORPHAN — `release-verify-test` was a gate invoked by nothing (D-124 again), and it is wired into `task check` in its three OFFLINE modes, not its default `all`.** Finding: `Taskfile.yml` defined `release-verify-test` — the **only** test of `hack/release/verify-artifacts.sh` — and no caller existed anywhere: not `check:`, not `CHECK_STAGES` in `hack/audit/exitgate_test.sh`, not `.github/workflows/**`, not the push-only `release-exitgate`. That script is not incidental: **`SECURITY.md:61` publishes it as the procedure release consumers run** to check artifact checksums, the stamped `assent version` and cosign signatures, and D-153/AUD2-F01 had just proved that published verification instructions rot silently. Measured before fixing, as the lane required: the gate was **green**, so this is a wiring defect only — nothing was being papered over. **Why not the `all` default.** `verify_test.sh` with no argument adds `test_snapshot_pass`, which shells out to `task release-snapshot`: `go install github.com/goreleaser/goreleaser/v2@…` on any machine without it (**network**, in a gate that runs before every commit), a `before: hooks: go mod tidy` that **rewrites `go.mod`/`go.sum` in the middle of `task check`**, `--clean` on the developer's `dist/`, and a five-target cross-compile (10s here with warm caches and goreleaser already installed; minutes cold). A flaky or network-bound stage is worse than a missing one, so `check:` runs `negative`, `cosign-skip-when-absent` and `readme` — REQ-E9-S12-02/03/04, all offline, together well under a second. **Consequence, stated precisely: REQ-E9-S12-01 (the real snapshot→verify round trip) is not gated per-commit in `task check`; it runs in push-only CI via `hack/release/exitgate_test.sh:33-34` (the `release-exitgate` job, `if: github.event_name != 'pull_request'`) — automated but not PR-visible, i.e. the RELSE-08 blind spot.** Those two lines are literally `task release-snapshot` then `task release-verify`, which is the `Verify:` string the spec gives the requirement (`openspec/specs/p5-e9-distribution/spec.md:441-442`), so nothing here needs a new gate built — an earlier draft of this row claimed the requirement was covered by no automation at all, which was false, and acting on it would have meant a future lane duplicating `exitgate_test.sh:33-34`. What this lane declines to do is move that L1 round trip into a per-commit gate: it cannot be, for the network/`go mod tidy`/`--clean` reasons above. Narrowing the RELSE-08 exposure for this requirement is a separate question from D-159. **Pinning.** `CHECK_STAGES` goes 20 → 21 in the same commit as the `check:` line (the AUD-S18/RELSE-08 lockstep), and `STAGE_BODY_PINS` pins the body **per mode** rather than by the bare script path — a single `bash hack/release/verify_test.sh` pin would stay green if two modes were dropped *and* if the stage were reverted to the unrunnable-in-check default. Three mutation controls prove it: deleting the `check:` line reds `check_check_wiring` by stage name, gutting one of the three modes reds `check_stage_bodies` while the other two still run, and the same deletion applied to the real tree reds the real gate at rc=1. **Also fixed, minimally, because wiring made it matter:** `verify_test.sh` wrote its captured stdout/stderr to fixed `/tmp/verify-{tamper,nosig}.{out,err}` and then **grepped** them; harmless while nothing ran it, but this repo runs several lane worktrees whose `task check` invocations overlap, and a concurrent run truncating `/tmp/verify-nosig.err` at open would fail the other run's `skip.*cosign` grep spuriously. Scratch files now live in each run's own `mktemp -d` (invisible to `verify-artifacts.sh`, which globs only `*.tar.gz`/`*.zip`) and are removed by the existing `RETURN` trap. | | D-160 | 2026-08-23 | **UNIV-COSIGN — the cosign signer-pin gate asserted an EXISTENTIAL property where the guarantee it publishes is UNIVERSAL, so it certified nothing about a file's second `cosign verify-blob`; `hack/release/install_cosign_pin_test.sh` now grades EVERY invocation in ALL THREE published files, statically and at runtime.** Substance first, and measured before anything was written: a scratch copy of each graded file with a **second, unpinned** `cosign verify-blob` spliced IN-BODY next to its real invocation (the splice shape matters — see UC-04 below) ran the gate to **exit 0** — `hack/install.sh` rc=0, `SECURITY.md` rc=0, `hack/release/verify-artifacts.sh` rc=0, the last of these being the shape a reviewer had already demonstrated on an earlier lane. **Every individual assertion in that gate was sound.** `has_flag` grepped the whole folded extraction, so a pinned sibling satisfied it; `one_value`'s `sort -u` collapsed two agreeing values to one, so the D-153 drift comparison saw a consistent file; section 0 **printed** the extracted invocation count and asserted nothing about it; and 4b/5d's stub-log checks were positive-only greps, i.e. the same existential shape at runtime, where a second unpinned call leaves the pinned call's log line intact. The conjunction of sound existential assertions is still an existential assertion. **This is the AUD2-S05 quorum defect one layer out: not a wrong assertion, a wrong quantifier**, and it predates both AUD2-S03 and AUD2-F01. **`SECURITY.md` was worse than the other two** — it fed only the drift comparison and had **no invocation-level grading at all**, so an unpinned recipe published to adopters was invisible to the gate that exists to police the recipe. **Property chosen: "every `cosign verify-blob` invocation in every graded file carries `--certificate-oidc-issuer`, `--certificate-identity-regexp` and `--bundle`, EACH WITH SECURITY.md's published value".** The value half is not decoration: `--certificate-identity-regexp ''` carries the flag and matches every Fulcio identity ever issued, and `one_value`'s `sort -u` does not catch it either, because an empty capture is not a line and the file still reports exactly one distinct value — a presence-only universal check would be the same defect one level down, so each invocation's own extracted value is compared against the published pair (reported `WRONG-VALUE[...]`, distinct from `UNPINNED[...]`). Options considered: **(a)** the remedy recorded when the finding was filed — assert the extracted invocation count is **exactly 1** per file (**REJECTED AS FACTUALLY WRONG — do not implement it in a later lane; it would red `main` on the day it lands.** `SECURITY.md` legitimately publishes **two** — one over the archive, one over `checksums.txt`, which is what covers the SBOMs listed inside it — so that assertion reds `main` on the day it lands, and the natural repair is to weaken it again); **(b)** universal quantification over invocations **(chosen)** — strictly stronger than (a) where (a) is true, and it keeps holding when a file legitimately grows a third correctly-pinned call. **Enforcement, per D-128, is one predicate**: `pin_violations ` prints one line per problem and nothing when the file is clean, so callers grade its OUTPUT and the identical function is asserted empty against the tree and non-empty against a mutant with no `set -e` games; `assert_all_pinned` is the thin failing wrapper; `log_unpinned_lines` is its runtime twin over the stub-cosign argv log. **Classification is fail-CLOSED, which is the part that carries the guarantee.** The candidate extractor is deliberately left un-narrowed (every non-comment line mentioning `cosign verify-blob`, continuations folded), because anything the *denominator* drops is a hole. A candidate is an invocation when the folded text begins with `cosign verify-blob`; the ONE waved-through non-invocation shape is a markdown inline-code mention (`` `cosign verify-blob` ``, which is how `SECURITY.md`'s capability table names it); **on a line carrying NO backticked mention, anything else — `foo && cosign verify-blob …`, a here-doc line, an un-backticked prose sentence — is reported UNCLASSIFIABLE rather than skipped**, because "this gate cannot tell whether that call is pinned" must never read as "that call is fine". **That scoping is not cosmetic, and the unscoped version of this sentence was FALSE (R3-01, caught by review on the round that added the honest banner).** The exemption tests whether the substring `` `cosign verify-blob` `` appears **anywhere on the line**, not whether the line is prose, and it is tested BEFORE the command-position and multi-token checks. So a line carrying a backticked mention AND one or more LIVE calls is exempted, neither graded nor refused: measured by splicing ``See `cosign verify-blob`: cosign verify-blob --bundle A.json a && cosign verify-blob --bundle EVIL.json evil`` into `SECURITY.md` — `occurrence_count` = 2 by the definition the banner itself states, `pin_violations` EMPTY, **whole gate rc=0**, while the banner reported that line among those "exempted as backticked prose". The **static** defect is not markdown-specific — splicing a live unpinned call with a trailing backticked mention into `hack/install.sh` gives candidates=2, invocations=1, `pin_violations` EMPTY — but the **whole-gate green is** markdown-specific, and the distinction is stated because a looser sentence here would itself be falsifiable: in that shell script the run reds at §4b for an unrelated RUNTIME reason (the spliced call executes and its bundle is absent), whereas `SECURITY.md` is a document with no runtime twin, which is why rc=0 survives there. Measured both ways rather than inferred from either. **Narrowing the exemption needs quote- and markdown-aware parsing — the machinery that produced UC-01/02/03 — so it is deliberately NOT patched here.** It is instead disclosed as residual (1) in the PASS banner, ahead of the variable/`eval`/fragment spellings, because it is the residual that is actually REACHABLE in these three files while those are not present in any of them. The banner also no longer labels an exempted line "prose": the branch it took is a fact, "prose" was an inference, and R3-01 is the case where that inference is wrong. **This is UC-07 doing its job on its own author:** the claim was falsifiable, it was checked, and the gate is now incomplete-and-honest rather than complete-and-wrong. Accepted cost, stated rather than discovered later: a *correctly pinned* invocation written off command position also reds. That is the intended direction of error for a security gate over three files carrying four invocations in one canonical style, and the message names the fix. **Anti-vacuity, because a lane whose entire deliverable is "make a gate stricter" is maximally exposed** (this repo's #1 review finding is an assertion that cannot fail — D-124, AUD-S18): section 2b runs **eight** mutants against **each** of the three files and 2c runs a ninth, and they were chosen for **branch** coverage of the new predicate rather than for variety of shape — (a) a second **unpinned** invocation must red, name the file, quote the offending command, and produce **exactly two** violations so the pinned siblings are provably not flagged; (b) a second **correctly pinned** invocation must stay green **and** must raise the extractor's invocation count by one, without which (b)'s green could mean "the extractor never saw it"; (c) an occurrence off command position, appended **fully pinned on purpose** so the only thing that can redden it is the classifier itself; (d) every invocation deleted → `NO-CANDIDATES`; (e) the continuation fold broken → `FOLD-BROKEN`, asserted **not** to also report `UNPINNED`, because conflating "the extractor truncated this" with "the pin is missing" sends the next maintainer to the wrong file; (f) every invocation displaced off command position → `NO-INVOCATIONS`; (g) a second invocation carrying both flags with an **empty** identity regexp → `WRONG-VALUE`, the hostile shape a presence-only check waves through; (h) a second invocation carrying both flags with a **foreign OIDC issuer** → `WRONG-VALUE` on the issuer half; and 2c strips the backticks from `SECURITY.md`'s prose mention and requires the same row to fail closed, proving the exemption keys on the backticks and is not a blanket bypass. The runtime twin has its own vacuity control (an unpinned line appended to a copy of the stub log must be seen). **One real defect was caught by this section during the lane and is recorded because it is a trap for the next author**: `log_unpinned_lines` was first written with `awk -v id="identity_re=${sec_identity}"`, and **awk processes escape sequences in a `-v` assignment**, so the pin's `github\.com` arrived as `github.com` and matched nothing; the values now reach awk through `ENVIRON`. **Stated limit, same posture as D-154's**: the denominator is the literal string `cosign verify-blob`, so an invocation spelled through a variable (`"$COSIGN" verify-blob …`), built by `eval`, or assembled from fragments is invisible to this gate. Widening the trigger to bare `verify-blob` was considered and **rejected** — it buys one hypothetical no file uses, and turns any future `SECURITY.md` sentence saying "verify-blob" outside backticks into a red gate, whose repair would be to loosen the classifier. **Nothing outside the gate changed**: `hack/install.sh`, `SECURITY.md` and `hack/release/verify-artifacts.sh` are byte-unchanged, and all four of their invocations were already pinned — this row closes a hole in the *checker*, not in the checked. The stage `release-install-cosign-pin-test` was already wired into `task check` and pinned in `hack/audit/exitgate_test.sh`'s `CHECK_STAGES`, so no wiring changed either. **SECOND ROUND — the first version of this fix REPRODUCED the very defect it closes, one level down, and independent review caught it before merge.** `has_flag` grepped the whole FILE; the replacement grepped the whole LINE. Both are existential; only the scope shrank. **UC-01**: one folded line was graded as at most ONE invocation, so `cosign verify-blob --bundle a.json a && cosign verify-blob --bundle EVIL.json evil` began with `cosign verify-blob` (not UNCLASSIFIABLE), carried `--bundle` (not FOLD-BROKEN), carried both flag strings (not UNPINNED) and yielded the published values (not WRONG-VALUE) — **green**, in same-line, `;`-separated and backslash-folded forms, on all three graded files. Worse, the **full gate exited 0** for `SECURITY.md`: the two shell scripts were incidentally caught by the runtime twin (`log_unpinned_lines` at §5d), but **SECURITY.md is a document and has no runtime twin**, so the hole was unmitigated precisely on the file this row calls "the copy-paste recipe adopters run by hand" — D-153's exact incident class — while the gate printed "EVERY cosign verify-blob invocation ... is pinned", false as printed. The stated-limit paragraph did not cover it: it enumerated `$COSIGN`, `eval` and fragments, not `&&` chaining, and **leading** `&&` was explicitly handled (mutant (c) → UNCLASSIFIABLE), so the operator had been considered and only one side handled. Closed by grading per **OCCURRENCE** (`occurrence_count` + a new `MULTI-OCCURRENCE` violation that fails closed on any line carrying two calls) rather than per line; counting inside an already-triggered line uses the shorter `verify-blob`, so a second call spelled `cosign verify-blob` or `"$COSIGN" verify-blob` and chained onto a pinned one is caught too, which the literal file-level trigger would miss. **UC-02**: `extract_issuer`/`extract_identity` anchor on a greedy `.*`, so the LAST value on a line wins — a hostile `--certificate-identity-regexp ''` placed FIRST was masked by a correct value placed second, **the extraction direction favoured the attacker**. Closed by `flag_value_tokens`, which returns EVERY value of every flag on the occurrence; each must equal the published one, so a repeated flag with an empty value first now reds. **UC-03 (P2, the erosion path)**: a CORRECTLY pinned call written with **double quotes** was refused as `WRONG-VALUE ... identity= ... (an empty regexp matches every Fulcio identity)` — a true refusal with a **false reason**, and the repair a maintainer reaches for on that message is widening the extractor. Both quote styles are now accepted (double quotes are correct shell for this value: it contains no `$` and no backtick), and a **bare** value is diagnosed as `UNQUOTED-VALUE`, a QUOTING defect, never as a wrong or empty one — a gate that misdiagnoses erodes faster than one that refuses. **UC-04**: this row's "rc=0, rc=0, rc=0" reproduction figure is **splice-shape-sensitive** and is stated as measured: it holds for a second unpinned call spliced **in-body** next to the real one. An EOF-appended mutant reds the two shell scripts for an unrelated runtime reason, so the figure should not be read as holding for any second unpinned call. The same sensitivity is why the UC-03 double-quoted mutant reds the two scripts through the full gate (the spliced call actually RUNS and its fixture bundle is absent) while `SECURITY.md`, having no runtime, is the clean end-to-end proof at rc=0. The classifier's fail-closed posture on off-command-position occurrences was independently adjudicated KEEP, since §2c proves the prose exemption keys on the backticks and cannot be widened silently. **The lesson, recorded because it is the whole point of the lane:** a fix for an existential-where-universal defect is itself maximally exposed to the same defect at the next scope down — file, then line, then occurrence — and the only thing that caught it was a reviewer building the chained-call mutant that neither the 20-row external matrix nor the 25 in-gate mutants contained. **THIRD ROUND (UC-05..UC-09), and the reason this row now reads the way it does.** **UC-05**: `occurrence_count` compared whitespace-delimited fields to `verify-blob`, found one, and concluded **singularity** — when all it had established is that it did not find a second. `cosign "verify-blob"`, `cosign 'verify-blob'` and `cosign verify-blob""` are the same command to the shell, and each hid a chained unpinned call from the count. Measured against the round-2 gate on **all three** files at whole-gate level: **rc=0, rc=0, rc=0** — worse than first reported, because `A && B` short-circuits and `set -e` does **not** fire on a non-final member of an AND-list, so even the two shell scripts' runtime twin missed it. Closed by stripping shell quotes before the field compare, which is **a spelling patch, not a terminator, and is labelled as one in the code**. A structural terminator was sought and **ruled out with evidence**: refusing any graded line carrying a command separator would red the REAL files, since `hack/install.sh` and `hack/release/verify-artifacts.sh` both legitimately end their genuine invocation with an or-else `die` tail. **No structural terminator exists for this gate**, so completeness is not available and must not be claimed. **UC-07 is therefore the durable fix, and it is a change of posture rather than of logic.** The `PASS:` banner was printed unconditionally and asserted "EVERY cosign verify-blob OCCURRENCE ... is pinned" plus "a second unpinned call reddens whether it sits on its own line or is chained onto a pinned one" — a **universal claim bound to no assertion**. Under UC-05 that banner did not merely overstate: on a green run it printed something **false**, which is strictly worse than printing nothing, and it is this repo's #1 defect class (an assertion that cannot fail) wearing a different hat. The banner now reports **OBSERVED** counts per file (occurrences found, classified-and-graded, prose-exempt, refused), lists **ASSERTED** properties each of which is shown to fail on a mutant, and states the **NOT ASSERTED** residual in the same breath: this gate finds calls by a literal string and counts quote-stripped words, so a call spelled through a variable, `eval`, or fragments is not seen and not graded, and the counts are what was classified — **not a proof that nothing else exists**. When a fifth spelling turns up, the gate is then **incomplete rather than wrong**, and incomplete is recoverable. **UC-06**: a TRAILING comment mentioning `verify-blob` fired `MULTI-OCCURRENCE: … has 2 verify-blob calls chained on ONE line` — fail-closed but **false as printed**, since `cosign_candidates` only drops lines whose FIRST non-space character is `#`. That is the UC-03 misdiagnosis shape recurring **inside the UC-01 fix**. The message now says TOKENS, names both possible causes, and names the correct repair; excluding trailing comments was rejected as it needs quote-aware parsing, which is the machinery that produced UC-01/02/03. **UC-08**: `shellcheck` SC1087 ×2 (error severity) on `flag_value_tokens` — `"$flag[[:space:]]…"` parses as an array subscript; `${flag}` silences it. Cosmetic: no shellcheck gate exists in this repo. **UC-09**: two stage-count figures reported during this lane ("25", then "28") were **both wrong** and neither came from a committed file — they counted `task: [` lines in a run LOG, which includes nested task invocations. Measured: the Taskfile `check:` block has **21** `- task:` entries and `CHECK_STAGES` has **21** stage names; they agree, and both files are byte-identical to `origin/main`. There is no stage-count mismatch and this lane could not have caused one. **THE PATTERN, recorded because it is the lane's most transferable output:** four P1s, three of them the same scope error at successively finer scopes — file (`has_flag`), line (round 1), then occurrence-spelling (round 2) — each found by review and none by the author's own mutant matrix, because each matrix was built from the shape the previous round had just learned. The general lesson is not "add another spelling": it is that **when a gate's denominator cannot be made provably complete, the gate must report what it classified instead of asserting what exists.** That is now enforced where it is hardest to ignore, in the banner the gate prints on every green run. Revert: restore `has_flag`, drop the per-occurrence grading and the per-invocation value comparison, and restore the two positive-only stub-log greps — which returns the gate to certifying that *some* cosign call in each file is pinned, a property that is compatible with an unpinned one sitting beside it. | | D-161 | 2026-08-24 | **E10-S00's `backlog.md:557` maintainer-LGTM (GOVERNANCE) marker is SATISFIED: the operator granted LGTM explicitly and authorized the integration of PR #85.** Recorded here because the approval was given in an agent session, and **an approval that exists only in a transcript is invisible to everyone who later asks whether the marker was honoured** — the same defect this lane's own handoff named as a standing lesson. GitHub's `reviewDecision` on #85 is empty (the maintainer is also the PR author, so GitHub refuses a self-review), which is why this D-row plus a PR comment — not a GitHub approval — is the durable record. **Integration mechanism:** #85 was 2 ahead / 24 behind with three genuine append-adjacency conflicts (`CHANGELOG.md`, `decisions.md`, `open-questions.md`). Force-push is forbidden (hard rule 2), so the branch was brought up to date by **merging `main` down into the lane** and merged with `gh pr merge --merge`, NOT by a rebase and NOT by a replacement PR — this preserves #85 and, with it, the full review history posted as its comment. Conflicts were resolved as **ordered unions** and verified **by content, not by count** (D-152..D-160 each present exactly once; OQ-31..OQ-36 each exactly once; all 11 CHANGELOG entries present); `task changelog-write` was then a byte no-op, independently corroborating the CHANGELOG union. **What the LGTM covers:** a docs/spec-only model (zero Go, zero schema bytes) whose one P1 was fixed fail-closed, so v1 GitHub comments and does not gate. **What it does NOT cover:** the same marker still guards **E10-S02/S04, E11-S02/S04/S06/S07, DEM-S00, WG-S01** — each needs its own LGTM; this row is not a blanket waiver. Revert: `git revert` the merge commit. | +| D-162 | 2026-08-25 | **P5-DEM is re-decomposed spec-first before any DEM story is implemented: all 15 stories (DEM-S00–S14) carry ZERO `Verify:` annotations, so not one of them states how it would be proven.** Operator-ruled 2026-08-25. This is a spec defect, not a per-lane gap: hard rule 4 puts specs before code, and DEM-S14 is the epic exit gate — it has nothing to aggregate if each lane invents its own gate ad hoc when it is picked up. **Rejected: annotate lane-by-lane** — cheaper per lane, but it defers the question of what the epic proves to the last story, which is precisely where this project has repeatedly found gates asserting a universal property their denominator could not establish (D-160, D-157). **Rejected: leave parked** — DEM is the adoption/demo epic and DEM-S11 is also E10-S18's live adoption target, so it is on the critical path for E10's own evidence, not optional. Scope: re-decomposition is a spec change under `openspec/` only; no DEM code lands until every DEM story has a `Verify:` annotation. Revert: restore the current 15 rows from git history; the annotations are additive and can be kept regardless. | +| D-163 | 2026-08-25 | **The recurring "dependabot reddens `main` on the CHANGELOG drift gate" failure is fixed narrowly, at the bot, NOT by enabling the drift gate on `pull_request`: OQ-30's guard STAYS and D-148 STANDS.** Operator-ruled 2026-08-25 after an initial ruling to the contrary was withdrawn on corrected premises. Incident: `cd4dac1` + `34e94cf` (codeql-action 4.37.7→4.37.8) merged green and reddened `main` in BOTH `verify` (step *changelog drift gate*) and `release-exitgate` (`task check` stage 21) — run 32817829705/32817735039; healed by PR #96. **Why not enable the gate on PRs, even though D-148's stated mechanism does not survive:** OQ-30 measured the merge-direction ordering hazard only in the lane-first-parent direction, while GitHub mints the merge ref base-first — so D-148 is defensible as PRECAUTION, not as mechanism, and this row records that explicitly rather than leaving a dead premise standing. The decisive objection is different and was missed when the question was first put: **dependabot cannot run `task changelog-write` on its own branch**, and `verify` is a REQUIRED status check with `strict=true` and `enforce_admins=true` (measured via the branch-protection API). Enabling the gate would therefore convert "main red for ~1h, healed by one regen commit" into "every dependabot PR red on a required check until a human pushes a regen commit to the bot's branch" — worse for the very incident that prompted it — while additionally exposing OQ-30's three still-untested cases (fork PRs, `pull_request_target`, force-push re-minting the merge ref) on a required check, where a false positive is unfixable by the author. **Mechanism sub-choice is deliberately NOT taken here** and is parked for the operator in `agent-context/INBOX.md`: (i) auto-regenerate on dependabot branches needs a write token on a bot-authored PR branch (`pull_request_target` or a PAT) and races dependabot's own rebase force-pushes; (ii) making the push-to-main gate tolerate bot-only `build(deps)` drift is a FAIL-OPEN in a gate, the exact species this repo has spent two waves removing, and would need both polarities plus a mutation control before it could be trusted. Until one is chosen, the standing remedy is the status quo: heal `main` with a `:memo: chore(release):` regen commit, whose subject `cliff.toml:119` skips so it does not add itself. Revert: none needed — this row changes no code; it records why the guard was not deleted. | +| D-164 | 2026-08-25 | **DOC-TRUTH — `backlog.md` rows E11-S03 and E11-S04 still said "blocked on the operator's rule-7 answer (d1/d2)" nine days after D-144 answered it, and that staleness actively misled a session.** D-144 (2026-08-16) resolves D-141's sub-question (d) to **(d1)** and ends with the words "**Unblocks E11-S03 and E11-S04**"; the two backlog rows kept their 🔴 marker and their "blocked on the operator" text regardless. Consequence, observed rather than hypothesised: the 2026-08-25 session handoff and the operator's own session-opening message both carried "the rule-7 (d1/d2) question is still parked on you" forward as fact, and the question was nearly put to the operator a second time. Same species as D-157/D-160 — a claim retained after the thing that justified it was withdrawn. Fix: both rows now cite D-144 and carry their real remaining dependency. **Generalisation deliberately not made:** no gate is added here asserting "every 🔴 row has a live blocker", because that predicate would match row TEXT, and matching text where the property is structural is the defect this project keeps re-introducing (14 P1s across two gate lanes). Revert: restore the two rows from git history. | diff --git a/docs/planning/rego-tier-ceiling.md b/docs/planning/rego-tier-ceiling.md index c65c0dbe..e910723d 100644 --- a/docs/planning/rego-tier-ceiling.md +++ b/docs/planning/rego-tier-ceiling.md @@ -603,7 +603,10 @@ they are recorded so the measurement is reproducible, not as conditions on the s | **OQ-36** | The frozen provider declaration has no object/map type, yet the authoring surface and `builtin/repo-file` together permit a mapping-valued fact and dynamic navigation into it. Is a mapping-shaped fact value in-contract? Touches B2's *second* spelling only — B2 is struck on its first spelling regardless, and Shape D needs only a flat `cardinality: set` fact. | Neither is the escalated judgment call (d) (rule-7 mechanism, (d1) vs (d2)); that question is -untouched by this record, which writes no Go and adds no dependency. +untouched by this record, which writes no Go and adds no dependency. **(d) is no longer open: +[D-144](../decisions/decisions.md) (2026-08-16) resolved it to (d1) and unblocked E11-S03/S04 — +noted here because this sentence was written after D-144 and reads as though it were still +pending (D-164).** **D-002 / rule 1.** Every rule in this document is a generated generic equivalent — topics, ACLs, partitions, service dependencies. No employer, internal system, tenant, cluster or diff --git a/openspec/specs/backlog.md b/openspec/specs/backlog.md index d2a258ec..07a0dd7e 100644 --- a/openspec/specs/backlog.md +++ b/openspec/specs/backlog.md @@ -593,8 +593,8 @@ LGTM** (published contract + the decision path itself). Independent of E10; may | E11-S00 | ⚠️ **SPIKE, do first**: does OPA expose a deterministic (non-wall-clock) eval budget? Nested throwaway module — root `go.mod` unchanged | **[autonomous · spike]** | none | if not, S06 stalls the epic *after* S02+S03 commit | | E11-S01 | ✅ **DONE (D-156)** — tier-1 (CEL) ceiling recorded: `docs/planning/rego-tier-ceiling.md` | **[autonomous]** | none | **E11 NARROWED**: cross-manifest + set-difference **struck** (both unconditional). **Two justifications survive, both unconditional**: fold/aggregate, and graph reasoning — a CEL expression's iteration count cannot be data-dependent (depth is syntactic, capped at cel-go's 250), so **unbounded** reachability has no spelling. A bounded `k`-hop check *is* writable and on a small graph is affordable and even complete — the ceiling is expressive, not performance. Cross-manifest is an *input* limit S05's identical `EvaluationInput` does not lift. Sets an S04 allowlist floor (`graph.reachable`, plus `split` as a convenience) held by **review**, not by REQ-E11-S04-02's drift-only golden. Residuals OQ-35/OQ-36 gate nothing | | E11-S02 | ⚠️ Additive `rego:` leaf in the policy schema (announced, no `apiVersion` bump) | **[autonomous · engine-grade · LGTM]** | **S00**, S01 | drift guard scoped; both polarities tested | -| E11-S03 | 🔴 Module loading from the **target ref**; compile failure is a lint hard error — **blocked on the operator's rule-7 answer (d1/d2)**: this story adds OPA to `go.mod` inside the guarded tree | **[autonomous · engine-grade · LGTM]** | S02 + operator | no second, laxer load path; transitive purity guard under (d1) | -| E11-S04 | 🔴 OPA capability sandbox — **blocked on the operator's rule-7 *mechanism* answer (d1 vs d2)**; "accept and pin" settled only the supply-chain half | **[autonomous · engine-grade · LGTM]** | S03 + operator | both purity gates are non-transitive; see D-141 | +| E11-S03 | Module loading from the **target ref**; compile failure is a lint hard error. **UNBLOCKED by [D-144](../../docs/decisions/decisions.md) (2026-08-16): rule-7 answered (d1)** — the narrowing is accepted explicitly, so this story may add OPA to `go.mod` inside the guarded tree | **[autonomous · engine-grade · LGTM]** | **S02, and REQ-E11-S04-03's transitive `go list -deps` purity check must land FIRST (D-144)** | no second, laxer load path | +| E11-S04 | OPA capability sandbox — denies `http.send`/`time.now_ns`/`rand.intn` via a golden allowlist an OPA upgrade cannot widen unnoticed. **UNBLOCKED by [D-144](../../docs/decisions/decisions.md): mechanism is (d1)**, evaluator stays in `internal/core/policy`, ADR-0011 amended to a behavioural (capability-enforced) guarantee | **[autonomous · engine-grade · LGTM]** | S03 | both purity gates are non-transitive today — see D-141/D-144 | | E11-S05 | ⚠️ Input binding to the identical `EvaluationInput` | **[autonomous · engine-grade]** | S04 | proves P3-E1-S02 neutrality empirically | | E11-S06 | ⚠️ Deterministic evaluation budget (never wall-clock) | **[autonomous · engine-grade · LGTM]** | S05 | N≥100 identical runs; budget ≠ decision | | E11-S07 | ⚠️ Violations → findings; **zero violations never proves an obligation** | **[autonomous · engine-grade · LGTM]** | S06 | the failing polarity is tested | diff --git a/openspec/specs/p5-e11-rego-backend/spec.md b/openspec/specs/p5-e11-rego-backend/spec.md index 5a3894a8..fbda255c 100644 --- a/openspec/specs/p5-e11-rego-backend/spec.md +++ b/openspec/specs/p5-e11-rego-backend/spec.md @@ -502,7 +502,9 @@ nothing about judgment call (d) — *where* the evaluator lives is untouched. - Level: L1 - **REQ-E11-S04-03** — Given judgment call (d) and the verified gap that **both purity gates are direct-import/direct-call only**, when Rego evaluation is placed, then the placement - follows the operator's answer (d1/d2/d3) and the story **fails** if it lands a green + follows the operator's answer — **RESOLVED to (d1) by [D-144](../../../docs/decisions/decisions.md), + so the (d1) branch below is the live one and the (d2) branch is retained only as the recorded + alternative (D-164)** — and the story **fails** if it lands a green `TestCorePurity` that is green only because the walk is non-transitive. Under **(d1)**: the purity guard is extended to a transitive `go list -deps` assertion over the guarded tree, with the OPA path as the single named, pinned exception, and a **mutation control** proving