Skip to content
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,11 @@ repository still gets a decision, never by following the link; no release carrie

### Documentation
- :memo: docs(decisions): record D-126 (D-031 domain deferred) and D-127 (SEC-05 PAT rotation scheduled)
- :memo: docs(decisions): unlock E10 + E11 (D-140/D-141) and record ADR-0021
- :memo: docs(openspec): decompose E10 and E11 into INVEST stories
- :memo: docs(openspec): fix two P0 design defects found by adversarial review
- :memo: docs(openspec): correct the E10 story count to 19 after adding S00
- :memo: docs(openspec): fix 3 P1 design defects found by independent review of PR #45

### Testing
- :white_check_mark: test(release): anchor the D-120 note check on its header sentence, not the bare token
Expand Down
255 changes: 255 additions & 0 deletions docs/adr/0021-multi-adapter-forge-seam.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions docs/adr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,4 @@ supersessions by ADR-0016/0017 are noted on each ADR's status line (not full
| [0018](0018-policy-lifecycle-phase-profile-comparison.md) | Policy lifecycle — phase, profiles, comparison | Accepted (D-030) |
| [0019](0019-publication-marker-reconciliation-protocol.md) | Publication marker + reconciliation protocol (database-free) | Accepted (D-030) — **one MUST unmet: doctor omits `duplicate_prevention:`** |
| [0020](0020-forge-snapshot-changed-file-completeness.md) | Forge snapshot changed-file completeness contract | Accepted (D-119) |
| [0021](0021-multi-adapter-forge-seam.md) | Multi-adapter forge seam — `forge.RunPort`, neutral capabilities, transport policy | Proposed (D-140) — governs E10 |
2 changes: 2 additions & 0 deletions docs/decisions/decisions.md

Large diffs are not rendered by default.

33 changes: 32 additions & 1 deletion docs/planning/design-notes/e10-forge-port-lift.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,39 @@
# Design note: forge port lift (pre-GitHub-adapter) — seeds E10

Status: note only (no decision taken; decide via ADR when the GitHub adapter epic opens).
Status: **SUPERSEDED as the design authority by ADR-0021** (2026-08-10), which took the
decision this note deferred. E10 opened with D-140; the epic is
`openspec/specs/p5-e10-github-forge/spec.md`. This note is retained as the record of the
pre-AUD-S15 problem and of steps 1–2, which shipped.
Trigger: ARCH-02, PROJECT-AUDIT-2026-08-06.

> **This note is INCOMPLETE as an epic scope — that was a finding, not an omission you should
> work around.** The 2026-08-09 audit recorded ARCH-18/ARCH-19: this note "under-scopes the
> epic by two design buckets, and the conformance suite cannot be run by a second adapter
> because all ~1,155 lines live in `_test.go` files Go cannot import."
>
> The original ARCH-18/ARCH-19 finding text is **not in the repo** — only the one-line summary
> at `agent-context/PROJECT-AUDIT-2026-08-09.md:412` survives. The two buckets were therefore
> **re-derived** during the 2026-08-10 design session, and are recorded as a re-derivation,
> not as a citation:
>
> - **Bucket A — capability model.** `docs/planning/forge-dossier-github.md` §4 enumerates
> eleven capability flags the port needs; `probeCapabilities` reads three project fields,
> and `capabilityGap` is computed in GitLab terms. Arming (ADR-0015 §4) hangs off that
> vocabulary, so a second adapter would restate it or silently arm under a different meaning
> of "capable". ADR-0021 §3 resolves this: a port-owned capability enum, `supported |
> absent | unknown`, gap computed at the port, and **`unknown` never arms**.
> - **Bucket B — transport and auth policy.** Bounded reads and pagination caps (AUD-S10),
> idempotent-GET retry/backoff and deadlines (AUD-S11) were built into
> `internal/forge/gitlab`. GitHub additionally needs a **GraphQL** client (thread resolution
> is GraphQL-only, dossier §4) and **two auth shapes** (PAT, App installation token). Left
> at the adapter, the forges' availability and fail-closed behaviour diverge undetected.
> ADR-0021 §4 makes these port requirements with conformance cases, while leaving protocol
> and auth as adapter-internal freedom.
>
> The unimportable conformance suite is tracked separately as **E10-S01, story zero**
> (ADR-0021 §2) — until it is fixed, no adapter can be developed against an executable
> contract and D-084's `github-deferred` catalog rows are unflippable by construction.

Progress: **steps 1 and 2 below shipped in AUD-S15** (`internal/forge/port.go`,
`internal/forge/port_test.go`, the ARCH-02 section of `hack/lint/depguard_test.sh`). Steps
3–5 remain open for E10. The "Problem" paragraph therefore describes the PRE-AUD-S15 state
Expand Down
9 changes: 6 additions & 3 deletions docs/planning/meta-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,12 @@ Follow-on epics cut during Phase 5, outside the E1–E9 sequence: **EFE**
(`p5-pcs-policy-comparison`, full comparison-suite runner), **AUD**
(`p5-aud-audit-remediation`, post-release audit remediation).

Deferred tiers keep their own numbers and unlock only with a named consumer (D-012):
**E10** GitHub adapter, **E11** Rego backend, **E12** `serve` (HTTP API), **E13** remote
packs — see the feature-maturity table in `README.md`.
Deferred tiers keep their own numbers. **E10** GitHub adapter (**unlocked D-140**, spec
`p5-e10-github-forge`, governed by ADR-0021) and **E11** Rego backend (**implementation
unlocked D-141**, spec `p5-e11-rego-backend`) are decomposed and executable; **E12** `serve`
(contract-unlocked by D-017, not decomposed) and **E13** remote packs (**still Locked** under
D-012 — the named-consumer requirement is untouched for that tier) are not. See the
feature-maturity table in `README.md`.

Ordering constraint: E7 starts early (alongside E1) because every later epic's exit gate
depends on it.
Expand Down
65 changes: 64 additions & 1 deletion openspec/specs/backlog.md
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,69 @@ table. Every one of the **37** 2026-08-06 finding IDs is dispositioned in
> check (it is skipped on PRs today, so a regression is detected only post-merge — which is
> exactly what happened at `49ba1ad`).

## P5-E10 — GitHub forge adapter + Actions entrypoint (**UNLOCKED D-140**)

Spec: [p5-e10-github-forge/spec.md](p5-e10-github-forge/spec.md) · ADR: **0021** (the seam) ·
Dossier: [forge-dossier-github.md](../../docs/planning/forge-dossier-github.md).
**Ordering is normative — S00 before any code, and the seam (S01–S05) before the first GitHub
API call.** An adversarial review of the first draft (2026-08-10) found **two P0 representation
defects** by reading the port against the code: the port addresses head content by branch name
in one project, so **every GitHub fork PR would mint a fabricated whole-file DELETE**
(`run.go:274` → `fileAtRefOrAbsent` → `OneSidedLifecycle`); and `$defs.pins` is
`additionalProperties:false` with a **single-string** `capabilityGap` required iff
`mergeResultDigest` is null, so an eleven-capability report **has nowhere valid to be
recorded**. Both are decided in ADR-0021 (items 5–8) and gated by S00. S00/S02/S04 are
core-contract and require **maintainer LGTM** (GOVERNANCE); `/agent-loop-auto` must surface
them rather than auto-merge.

| ID | Story | Execution | Depends on | Gate contribution |
| --- | --- | --- | --- | --- |
| E10-S00 | ⚠️ GitHub addressing & representation model (4 questions, ~1 page) | **[autonomous · design · LGTM]** | none | **do first** — kills both P0s before the port freezes |
| E10-S01 | Extract the conformance suite into an importable package + observation surface | **[autonomous]** | S00 | first **code** story; no assertion may be weakened |
| E10-S02 | ⚠️ `forge.RunPort` + **neutral factory** + MR-relative addressing + identity | **[autonomous · engine-grade · LGTM]** | S00, S01 | one neutral seam; ARCH-02 cannot recur |
| E10-S03 | ⚠️ Collapse `SyntheticDigest` onto `Snapshot.Heads.MergeResultDigest` | **[autonomous · engine-grade]** | S02 | digest scheme adapter-owned; allowlist emptied |
| E10-S04 | ⚠️ Neutral capability model — `unknown` never arms | **[autonomous · engine-grade · LGTM]** | S02 | one fail-closed guarantee, not two |
| E10-S05 | Port-level transport requirements (bounded reads, caps, GET-only retry, deadlines) | **[autonomous]** | S01, S04 | availability behaviour can't diverge per adapter |
| E10-S06 | GitHub client: REST + GraphQL, PAT + App installation auth | **[autonomous]** | S05 | adapter foundation; no secret in any fixture |
| E10-S07 | GitHub Snapshot (MRInfo, ADR-0020 changed-file completeness, merge-result pin) | **[autonomous]** | S06 | absent-means-trusted closed on fork detection |
| E10-S08 | ⚠️ GitHub Resolve → `ApprovalEvidence` (author/bot excluded, dismissal-aware) | **[autonomous · engine-grade]** | S06 | unprovable eligibility ⇒ unsatisfiable |
| E10-S09 | ⚠️ GitHub capability report (11 flags; unverified ⇒ `unknown`) | **[autonomous · engine-grade]** | S04, S06 | honest gaps; exhaustiveness enforced |
| E10-S10 | ⚠️ GitHub Reconcile writes (ADR-0019 parity, GraphQL thread resolution) | **[autonomous · engine-grade]** | S07–S09 | same engine, second adapter |
| E10-S11 | ⚠️ SHA-guarded merge + deferred arming + revoke-on-push | **[autonomous · engine-grade]** | S10 | ADR-0015 §2 on GitHub |
| E10-S12 | ⚠️ Capability gaps fail closed — `merges == 0` **and paired `merges == 1`** | **[autonomous · engine-grade]** | S11 | positive control mandatory; else vacuous |
| E10-S13 | Forge selection in `run`/`doctor`; ambiguity fails closed | **[autonomous]** | S12 | no default-to-GitLab |
| E10-S14 | Conformance parity — **every** row needs an adapter disposition, not just deferrals | **[autonomous]** | S13 | else GitHub ships with 0 trust-boundary cases proven |
| E10-S15 | Docs & maturity truth (README tier, C4, `--forge`, dossier items) | **[autonomous]** | S14 | no doc claims an `unknown` capability |
| E10-S16 | Actions entrypoint (`action.yml`, pinned binary, base-ref trust) | **[autonomous]** | S15 | ✅ **operator-answered 2026-08-10: stays in E10**, last + independently droppable |
| E10-S17 | Exit gate | **[autonomous]** | S01–S16 | **the E10 exit gate** |
| E10-S18 | Live GitHub adoption proof on a real repo (mirrors D-042) | **[infra-gated · operator]** | S17 + infra | D-012-grade evidence; not an autonomous blocker |

## P5-E11 — Complex-rule backend: Rego predicate tier (**IMPLEMENTATION UNLOCKED D-141**)

Spec: [p5-e11-rego-backend/spec.md](p5-e11-rego-backend/spec.md) · ADR: **0002 v2** (governing).
**Two traps recorded in D-141**: E11 is the **first epic whose DoD is `git diff schemas/` != 0**
(announced additive `rego:` leaf; `schemas/decision/**` still frozen), and a **wall-clock
evaluation timeout would itself violate rule 7** — the budget must be machine-independent and
exceeding it is a process error, never a policy outcome. S02/S04/S06/S07 require **maintainer
LGTM** (published contract + the decision path itself). Independent of E10; may run in parallel.

| ID | Story | Execution | Depends on | Gate contribution |
| --- | --- | --- | --- | --- |
| 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 | Record the tier-1 (CEL) ceiling with concrete exceeding rules | **[autonomous]** | none | **do first** — a CEL-expressible shape is struck from scope |
| 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-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 |
| E11-S08 | ⚠️ Aggregation boundary — module cannot set effect/points/phase | **[autonomous · engine-grade]** | S07 | ADR-0002 v2 / ADR-0007 held structurally |
| E11-S09 | `assent lint` hard errors + faithful catalogue entries | **[autonomous]** | S08 | E3 parity for the second backend |
| E11-S10 | `assent test` support + both-polarity coverage | **[autonomous]** | S08 | ADR-0014 unchanged |
| E11-S11 | Remove the `# locked: D-012` quarantine; **update** the P3-E3-S04 guard | **[autonomous]** | S10 | only E11's lane may do this |
| E11-S12 | Docs & maturity truth; retire ADR-0002's "pluggable half unbuilt" line | **[autonomous]** | S11 | nothing still calls Rego locked |
| E11-S13 | Exit gate | **[autonomous]** | S00–S12 | **the E11 exit gate** |

## Phases 3–5

Epic paragraphs (goal, ADR constraints, exit gate, story seeds) in
Expand All @@ -551,7 +614,7 @@ Epic paragraphs (goal, ADR constraints, exit gate, story seeds) in
| --- | --- | --- |
| 3 — Contracts first | P3-E1 schemas + contract fixture (incl. ApprovalEvidence + named-consumer fixture) · P3-E2 versioning/compat spec · P3-E3 example migration · P3-E4 lifecycle: phase/profiles/comparison (ADR-0018) · P3-E5 publication reconciliation protocol (ADR-0019) | strict end-to-end contract fixture validates (ADR-0017 §8, D-016); new ADRs 0018/0019 accepted at the freeze review |
| 4 — Walking skeleton | P4-E1 (+ rerun-idempotence gate, D-017) · **P2-E4-NS (OQ-24 timed run)** · holdout adjudication (OQ-25) | L3 skeleton green + **one real repo on live MRs** (D-012); north-star wording only after timed run |
| 5 — Implementation | E1–E7 **DONE**; **E7 AUTONOMOUS COMPLETE** (S01–S05+S08, D-087); **E8 AUTONOMOUS COMPLETE** ([p5-e8-renderer/spec.md](p5-e8-renderer/spec.md), S01–S14, D-098); **E9 AUTONOMOUS COMPLETE** ([p5-e9-distribution/spec.md](p5-e9-distribution/spec.md), S01–S13, D-099–D-111 CLOSED; Homebrew Formula live; PAT rotate optional); **PCS AUTONOMOUS COMPLETE** ([p5-pcs-policy-comparison/spec.md](p5-pcs-policy-comparison/spec.md), S01–S09, **D-057 closed**, D-118); E11/E12 **unlocked** (D-017); E14 gated on Spike D; E10/E13 **locked** (D-012) | per-epic; E9 exit = tagged signed release + docs live + brew Formula (D-111); PAT rotate optional |
| 5 — Implementation | E1–E7 **DONE**; **E7 AUTONOMOUS COMPLETE** (S01–S05+S08, D-087); **E8 AUTONOMOUS COMPLETE** ([p5-e8-renderer/spec.md](p5-e8-renderer/spec.md), S01–S14, D-098); **E9 AUTONOMOUS COMPLETE** ([p5-e9-distribution/spec.md](p5-e9-distribution/spec.md), S01–S13, D-099–D-111 CLOSED; Homebrew Formula live; PAT rotate optional); **PCS AUTONOMOUS COMPLETE** ([p5-pcs-policy-comparison/spec.md](p5-pcs-policy-comparison/spec.md), S01–S09, **D-057 closed**, D-118); **E10 UNLOCKED + DECOMPOSED** (D-140, [p5-e10-github-forge/spec.md](p5-e10-github-forge/spec.md), 19 stories, ADR-0021); **E11 IMPLEMENTATION UNLOCKED + DECOMPOSED** (D-141, [p5-e11-rego-backend/spec.md](p5-e11-rego-backend/spec.md), 14 stories); E12 **contract-unlocked** (D-017), not decomposed; E14 gated on Spike D; **E13 still locked** (D-012) | per-epic; E9 exit = tagged signed release + docs live + brew Formula (D-111); PAT rotate optional |

Named-consumer disposition (what unlocked, what stayed locked, and why):
[docs/planning/named-consumer-compat.md](../../docs/planning/named-consumer-compat.md).
Expand Down
28 changes: 23 additions & 5 deletions openspec/specs/later-phases.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,12 +257,30 @@ schema drift guard; **D-112–D-117 cited.** **9 stories (9 autonomous)** — E1
without compare debt.
Seeds: `examples/comparison/**`, `hack/compare/exitgate_test.sh`.

### E10 — GitHub adapter + Actions entrypoint — **Locked (D-012)**
Unlocks with a named consumer. Seam kept honest by the P1-E3-S03 dossier (REQUEST_CHANGES +
conversation-resolution parity, merge queue as merge-result pin, base-ref workflow trust)
and by the conformance suite being forge-neutral (E7). No frozen contract until unlock.
### E10 — GitHub adapter + Actions entrypoint — **UNLOCKED (D-140, 2026-08-10)** — spec: [p5-e10-github-forge](p5-e10-github-forge/spec.md)
Was Locked (D-012), reaffirmed locked by D-017 and D-019; unlocked by direct operator
instruction (D-140), not by the named-consumer trigger. Seam kept honest by the P1-E3-S03
dossier (REQUEST_CHANGES + conversation-resolution parity, merge queue as merge-result pin,
base-ref workflow trust) and by the conformance suite being forge-neutral (E7).
**19 stories (S00–S17 autonomous, S18 infra-gated)** governed by **ADR-0021**, which decides
the three things the audit found under-scoped (ARCH-18/ARCH-19): the named `forge.RunPort`
composite port, an **importable** conformance suite (today all 1,155 lines are in `_test.go`
files a second adapter cannot import), a neutral capability model where `unknown` never arms,
and port-level transport/auth policy. Ordering is normative: the seam (S01–S05) lands before
the first GitHub API call. v1 target is behavioural parity for the **gate**, with capability
gaps failing closed.

### E11 — Complex-rule backend (Rego) — **Unlocked (D-017), implementation after Phase 4**
### E11 — Complex-rule backend (Rego) — **IMPLEMENTATION UNLOCKED (D-141, 2026-08-10)** — spec: [p5-e11-rego-backend](p5-e11-rego-backend/spec.md)
Contract unlocked by D-017; implementation was gated twice — "after Phase 4" (satisfied, the
adoption gate closed with D-042) and D-017's **per-rule evidence** gate, which D-141 lifts by
operator instruction. The design need survives the lift: E11-S01 still writes the tier-1
ceiling document, and any shape found CEL-expressible is struck from scope. **14 stories
(S00–S13), all autonomous.** Two constraints found during design: E11 is the **first epic whose DoD is
`git diff schemas/` != 0** (the `rego:` leaf is an announced additive change to
`merge-policy.schema.json` per `API_STABILITY.md`; `EvaluationInput` is untouched, so
P3-E1-S02's neutrality guarantee holds), and bounding evaluation with a **wall-clock timeout
would itself violate rule 7** — the budget must be machine-independent and exceeding it is a
process error, never a policy outcome. Original framing, unchanged:
The named consumer's multi-pass / cross-manifest / set-difference / graph-relationship
checks are the consumer D-012 required. Contract committed in Phase 3 (EvaluationInput
stays backend-neutral, P3-E1); implementation only in the named-consumer expansion, and
Expand Down
Loading