Skip to content

📝 docs: unlock E10 (GitHub adapter) + E11 (Rego) and decompose both spec-first - #45

Merged
konih merged 8 commits into
mainfrom
design-e10-e11-unlock
Aug 10, 2026
Merged

📝 docs: unlock E10 (GitHub adapter) + E11 (Rego) and decompose both spec-first#45
konih merged 8 commits into
mainfrom
design-e10-e11-unlock

Conversation

@konih

@konih konih commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

What this is

A design session, not an implementation lane. The ask was "implement the GitHub adapter
and consider the Rego parser unblocked — are the proper stories and specs in place?"

They were not. Neither E10 nor E11 had a spec directory or a single backlog story; both
existed only as ~5-line paragraphs in later-phases.md. E10 was moreover still Locked
(D-012, reaffirmed by D-017 and D-019), so the instruction is a new unlock event rather than
the exercise of an existing one. Per /agent-loop-auto's own fallback, this ran the design
session instead of the loop. No engine code is touched.

Contents

Artifact What it does
D-140 Unlocks E10; records authority, v1 scope, and what it does not authorize (E13 stays locked)
D-141 Unlocks E11 implementation; lifts D-017's per-rule evidence gate, keeps the design need
ADR-0021 (Proposed) The multi-adapter forge seam: forge.RunPort, importable conformance suite, neutral capability model, transport policy, and the addressing/representation decisions
p5-e10-github-forge/spec.md 19 stories (S00–S17 autonomous, S18 infra-gated)
p5-e11-rego-backend/spec.md 13 stories + a blocking spike
Backlog / later-phases.md / meta-plan.md / ADR index / E10 design note Propagated so nothing contradicts the new rows

Independent adversarial review ran, and changed the design

A roaster subagent that never saw the author's reasoning reviewed the first draft and returned
2 × P0. Both were verified against the code before acting, and both are fixed here:

  1. Fork PRs would mint a fabricated whole-file DELETE. run.go:274 reads head content by
    branch name inside one project, and forge.MRInfo has no source-repository field. A
    GitHub fork PR 404s → fileAtRefOrAbsent maps to nilOneSidedLifecycle returns
    KindDelete. Every fork PR would be evaluated as a deletion the contributor never made.
  2. An eleven-capability report has nowhere valid to be recorded. $defs.pins is
    additionalProperties:false with a single-string capabilityGap, required iff
    mergeResultDigest is null. ADR-0021 scopes the report to doctor/refusal reasons and
    states the audit-trail cost, rather than widening a frozen schema or hiding a
    safety-bearing field in an unvalidated one.

Also fixed: S02 could not close (main.go:72,83 call gitlab.New, and
depguard_test.sh:356-363 hard-fails unless it sees that call-site — while task lint was
S02's own Verify); every merges == 0 assertion gained a paired merges == 1 positive control;
S14 now requires an adapter disposition on every catalog row, not only deferred ones.

Two false claims are retracted in place rather than quietly edited away: the
github-deferred catalog rows are level: L3, package: test/e2e — live-infra proofs that
importability cannot unblock — and capabilityGap never modelled the general absent-capability
case.

🔴 Two blocking operator questions (details in the INBOX entry)

  1. E11-S04 — adopting OPA narrows rule 7's mechanism, and both purity gates would miss it.
    Verified: internal/core/purity_test.go flags only each file's own imports/selectors,
    and .golangci.yml's pure-tree depguard is deny-only over direct imports. Neither is
    transitive — so importing OPA into the guarded tree passes both gates green while
    transitively linking net/http. Options d1/d2/d3 are in D-141; d1 needs an ADR amendment.
  2. E10-S16 — does the Actions entrypoint stay in E10's scope? Recommendation: keep it last
    and independently droppable.

Gates

task check green locally at the final commit (exit 0), including changelog-verify after
regeneration. Note RELSE-08: release-exitgate skips on PRs, so PR-green is not
task check-green — the local run is the real evidence.

Not merging autonomously

/agent-loop-auto's stop conditions require surfacing core-contract changes for a human look
even when gates pass, and two blocking questions are open. Review requested.

konih added 8 commits August 10, 2026 15:23
E10 was Locked under D-012 and reaffirmed locked by D-017 and D-019, so the
operator instruction to implement the GitHub adapter is a new unlock event, not
the exercise of an existing one; it is recorded before any spec text exists
(AGENTS.md rule 6). D-141 lifts D-017's per-rule evidence gate on the Rego
backend without waiving the design need behind it.

ADR-0021 decides the three things the 2026-08-09 audit found under-scoped
(ARCH-18/ARCH-19): the named forge.RunPort composite port, an importable
conformance suite, a neutral capability model in which unknown never arms, and
port-level transport policy. The original finding text is not in the repo, so
the two design buckets are recorded as a re-derivation, not a citation.
E10 gets 18 stories (S01-S17 autonomous, S18 infra-gated) with normative
ordering: the seam lands before the first GitHub API call, because all ~1,166
lines of the conformance suite live in _test.go files Go cannot import, so an
adapter written today would have no executable contract and D-084's
github-deferred catalog rows would be unflippable by construction.

E11 gets 13 stories. Two constraints found during design are recorded in the
spec rather than discovered during implementation: it is the first epic whose
DoD is 'git diff schemas/' != 0 (the rego: leaf is an announced additive change
to merge-policy.schema.json; EvaluationInput is untouched, so P3-E1-S02's
neutrality guarantee holds), and bounding evaluation with a wall-clock timeout
would itself violate rule 7, so the budget must be machine-independent and
exceeding it is a process error rather than a policy outcome.
An independent roast of the first draft read the port against the code and
found two representation defects that the endpoint-level GitHub dossier could
not have surfaced.

Fork-PR head addressing: run.go:274 reads head content by branch name inside
one project and forge.MRInfo carries no source-repository identifier, so a
GitHub fork PR 404s, fileAtRefOrAbsent maps that to nil, and OneSidedLifecycle
returns KindDelete -- every fork PR would be evaluated as a whole-file deletion
the contributor never made. The port now addresses content relative to the
merge request and a conformance case pins it.

Capability record surface: $defs.pins is additionalProperties:false with a
single-string capabilityGap required iff mergeResultDigest is null, so an
eleven-capability report has nowhere valid to live. ADR-0021 scopes the report
to doctor output and arming-refusal reasons and states the audit-trail cost
rather than widening a frozen schema or hiding the field in an unvalidated one.

Also corrected: S02 could not close (cmd/assent constructs gitlab.New, and
depguard_test.sh hard-fails unless it sees that call-site, while task lint is
S02's own Verify) -- the neutral factory moves from S13 into S02 and the
scanner's positive control is replaced rather than deleted. Every merges==0
assertion gains a paired merges==1 positive control, because an adapter that
never arms would otherwise satisfy all of them. S14 now requires an adapter
disposition on every catalog row, not only the deferred ones, which would
otherwise have shipped GitHub with zero trust-boundary cases proven. Two false
claims are retracted in place: the github-deferred rows are L3 live-infra proofs
that importability cannot unblock, and capabilityGap never modelled the general
absent-capability case.
… of PR #45

F1 -- the fork-PR P0 fix was half-landed. ADR-0021 item 1 still froze
FileAtRef(project, path, ref) as the port's content accessor while item 5
said the port stops addressing by (project, branch-name); REQ-E10-S02-01
and -05 inherited the contradiction, so an implementer could satisfy -01
literally and preserve the fabricated-DELETE defect with a green build.
Reconciled by stating what the narrowing actually binds: the GOVERNED
SUBJECT (run.go:270,274) moves to FileAtBase/FileAtHead; the POLICY loads
(run.go:203,211,253) deliberately keep FileAtRef because ADR-0015 s1
requires them to read the protected target ref by name -- migrating those
would be a trust-boundary regression, not a cleanup. Both directions are
now asserted.

F2 -- E11's ordering was stated three incompatible ways and the backlog
carried an E11-S06' row with no story section, no DoD and no REQ IDs
(14 rows against a stated 13 stories). Promoted to a real story E11-S00,
the deterministic-budget feasibility spike, built in a nested throwaway
module so it answers the question without adding OPA to the root go.mod
(no go.work exists, so Go excludes it from ./...). E11 is now 14 stories,
S00-S13, and S02 is blocked on S00 because an observe-only rego: leaf is
a different published contract from a gating one.

F3 -- the spec said E11-S01..S03 were unblocked while judgment call (d)
was pending. False, and the most consequential of the three: S03 is the
story that EFFECTS the narrowing (d) governs -- it adds OPA to go.mod and
its test paths sit in the D-123 guarded tree. Because neither purity gate
is transitive, S03 would have landed green while converting rule 7's
guarantee from link-enforced to capability-enforced, i.e. narrowing a
hard rule by merging a story. S03 is now blocked on (d) alongside S04,
and gains REQ-E11-S03-04 requiring the transitive purity guard under (d1).

Also: recorded the operator's two 2026-08-10 answers in D-140/D-141
rather than leaving them only in the gitignored INBOX (rule 6), and made
explicit that 'accept and pin OPA' settles (d)'s supply-chain half only --
(d2) also accepts and pins OPA, so the mechanism half is still open and
is what blocks S03. Corrected Verify commands naming gates that do not
exist (task scrub), packages that do not exist (internal/core/{lint,
catalogue,testharness}), a shell drift guard that is actually Go
(internal/schemadrift), a guard task check never runs
(check-migration-invariants.sh), and two false counts (1,166 -> 1,155
conformance lines; 15 -> 14 non-deferred catalog rows).
@konih
konih merged commit be592b3 into main Aug 10, 2026
7 checks passed
konih added a commit that referenced this pull request Aug 10, 2026
… of PR #45

F1 -- the fork-PR P0 fix was half-landed. ADR-0021 item 1 still froze
FileAtRef(project, path, ref) as the port's content accessor while item 5
said the port stops addressing by (project, branch-name); REQ-E10-S02-01
and -05 inherited the contradiction, so an implementer could satisfy -01
literally and preserve the fabricated-DELETE defect with a green build.
Reconciled by stating what the narrowing actually binds: the GOVERNED
SUBJECT (run.go:270,274) moves to FileAtBase/FileAtHead; the POLICY loads
(run.go:203,211,253) deliberately keep FileAtRef because ADR-0015 s1
requires them to read the protected target ref by name -- migrating those
would be a trust-boundary regression, not a cleanup. Both directions are
now asserted.

F2 -- E11's ordering was stated three incompatible ways and the backlog
carried an E11-S06' row with no story section, no DoD and no REQ IDs
(14 rows against a stated 13 stories). Promoted to a real story E11-S00,
the deterministic-budget feasibility spike, built in a nested throwaway
module so it answers the question without adding OPA to the root go.mod
(no go.work exists, so Go excludes it from ./...). E11 is now 14 stories,
S00-S13, and S02 is blocked on S00 because an observe-only rego: leaf is
a different published contract from a gating one.

F3 -- the spec said E11-S01..S03 were unblocked while judgment call (d)
was pending. False, and the most consequential of the three: S03 is the
story that EFFECTS the narrowing (d) governs -- it adds OPA to go.mod and
its test paths sit in the D-123 guarded tree. Because neither purity gate
is transitive, S03 would have landed green while converting rule 7's
guarantee from link-enforced to capability-enforced, i.e. narrowing a
hard rule by merging a story. S03 is now blocked on (d) alongside S04,
and gains REQ-E11-S03-04 requiring the transitive purity guard under (d1).

Also: recorded the operator's two 2026-08-10 answers in D-140/D-141
rather than leaving them only in the gitignored INBOX (rule 6), and made
explicit that 'accept and pin OPA' settles (d)'s supply-chain half only --
(d2) also accepts and pins OPA, so the mechanism half is still open and
is what blocks S03. Corrected Verify commands naming gates that do not
exist (task scrub), packages that do not exist (internal/core/{lint,
catalogue,testharness}), a shell drift guard that is actually Go
(internal/schemadrift), a guard task check never runs
(check-migration-invariants.sh), and two false counts (1,166 -> 1,155
conformance lines; 15 -> 14 non-deferred catalog rows).
@konih
konih deleted the design-e10-e11-unlock branch August 10, 2026 14:37
konih added a commit that referenced this pull request Aug 10, 2026
G1 -- REQ-E11-S00-01's isolation guard rewritten to
'! go list ./... | grep -q spikes/rego'. NOTE the review's stated
mechanism did not reproduce: in a default shell a pipeline's exit status
is the LAST command's, so 'grep -c ... | grep -qx 0' actually exited 0
when isolated and 1 when leaked -- the correct polarity. It inverts to a
false red only under 'set -o pipefail'. Verified both polarities under
bash -o pipefail before and after. Conclusion right, reasoning wrong;
fixed because a guard that flips meaning with a shell option is not a
guard, and this one protects the 'no OPA in go.mod while (d) is open'
invariant.

G2 -- REQ-E11-S03-04's Verify used '-run TestPurity', which matches NO
test (the real names are TestCorePurity*). Confirmed vacuous: it printed
'no tests to run' and exited 0. That was the verification for the single
REQ enforcing the (d1) hard-rule narrowing -- exactly the
tests-that-cannot-fail class this repo keeps re-finding, and it would
have read as governed while checking nothing. Now -run TestCorePurity,
confirmed to execute.

G3 -- the retained-FileAtRef enumeration was incomplete and one line
number was wrong: actual ref-addressed policy loads are run.go:203
(MergePolicy), :211 (RulesetBinding), :230 (Config) and :249 (pack);
:253 is LoadPack, not a FileAtRef call. The omitted :230 is the most
damaging to get wrong -- .assent/config.yaml declares the provider
hosts, so migrating it to an MR-relative accessor would let a fork's
head redefine its own fact semantics, the precise trust-boundary
regression the F1 fix exists to prevent.

G4 -- renamed the spike doc to spike-e-rego-budget.md; 'Spike D' is
already the Kubernetes CRD feasibility spike that E14 is gated on.

G5 -- E11-S13's story dependencies corrected to S00-S12 to match the
backlog row.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant