Skip to content

📝 docs(openspec): design P5-DEM public demo repositories (D-142, OQ-32) - #47

Merged
konih merged 10 commits into
mainfrom
spec-dem-demo-repos
Aug 10, 2026
Merged

📝 docs(openspec): design P5-DEM public demo repositories (D-142, OQ-32)#47
konih merged 10 commits into
mainfrom
spec-dem-demo-repos

Conversation

@konih

@konih konih commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Spec-first design for two public demo repositories, plus an assessment of how extensible the user-resolution (provider) seam actually is. Design only — no code, no schema, 14 stories decomposed but none implemented.

This work was authored in a prior session and was sitting uncommitted and untracked in the working tree. It is committed here unchanged apart from the rebase onto current main.

The question that opened it, and the honest answer

"Does there already exist a sample GitLab repository we can test with as well as a sample GitHub repository?"No. examples/repos/** are content layouts with no .assent/ tree; examples/packs/** are policy trees with no repo identity; gitlab.com/konrad.heimel/assent-lab is the operator's personal D-042 adoption-proof project, not a showcase; GitHub presence is zero because E10 is spec-only. So "everybody can see assent in action themselves" is currently false.

The constraint that shapes the epic

assent cannot run on a GitHub PR today. The design therefore commits to a two-tier demo contract, stated on each repo's front page:

  • Tier 1git clone && assent test .: forge-independent, no token, no network, no IdP. Deliverable now, on both repos, via the E6 harness. This is the "everybody can see it" claim.
  • Tier 2 — a live MR/PR: GitLab now, GitHub blocked on E10-S18.

The two repos are split by governance shape, not by tool — otherwise they demonstrate the same three archetypes twice. Repo 1 (assent-demo-platform, GitLab) is request a platform resource, distinguished by being referential: an ACL names a topic and a principal, so the decision cannot be made from the changed file alone — which is exactly what builtin/resource-owner and builtin/repo-file exist for and what no shipped example exercises end to end. Repo 2 (assent-demo-terraform, GitHub) is change infrastructure, distinguished by blast radius plus the opaque-change fallback, and doubles as E10-S18's live adoption target.

🔴 The finding that outlives this epic

(class, environment) binding routing is not wired, so a multi-class demo repo fails closed in both tiers on both forges. Found by reading the code against the design:

  • cmd/assent/run.go:493 selectBinding fails closed on any RulesetBinding with >1 binding — its own comment says the Config class-matcher "is not wired in this lane".
  • cmd/assent/test.go:366 selectBindingForTest collapses to the strictest binding (D-060) but fails closed when two bindings differ in class, packs, or require[].

Both demo repos have three classes with different require[]. Why nobody noticed: D-060 recorded the shipped packs' dev/prod split as "empirically decision-neutral for the corpus" — every shipped example is single-class with a decision-neutral environment split, so the collapse is invisible and the matcher is never missed. The demo is the first artifact needing real routing.

This lands as DEM-S00 (engine-grade · maintainer LGTM). It is wiring, not new contract — policy.Config already carries Environments/Classes as NamedMatch. Note the epic's clearest demonstration — the identical diff that APPROVEs in dev and REVIEWs in prod — is impossible in either tier today, which is why it becomes S00's sharpest acceptance test.

Reverting this PR does not close that gap. D-060 remains its standing record.

On provider extensibility

Verified verdict: the seam is genuinely good and extending it needs no core change and no fork. The wire contract is frozen and published, and extension is a two-file repo-side change read from the protected target ref — so an MR author cannot redefine their own fact semantics.

But four gaps stand between that and "easily extendable", and the spec states all four rather than demoing around them. Two are P1:

  • G1find examples -type d -name providers returns nothing. All three shipped packs declare providers: with no host declaration, so provider_host.go:83-87 silently continues and every shipped example's provider resolves to nothing. The required path is documented nowhere.
  • G2OQ-32CallHTTP sets only Content-Type; the repo-side provider schema is additionalProperties:false over {type,url,failure}; ScrubEnv/ScrubArgv refuse (?i)TOKEN|SECRET names even when explicitly configured. So no provider can call Entra ID or Keycloak directly — both need a bearer token. This is not a bug, it is ADR-0015 §7 working as designed, with a hostile-provider isolation proof resting on it. What has never been written down is the consequence: the only working shape is a broker holding the IdP credential itself. That narrows what docs/vision.md:67 promises, and OQ-32 is deliberately left unresolved — option (b), adding a credential channel, is a trust-boundary change needing its own ADR, and must not be resolved by quietly adding a header field.

The epic recommends never shipping OIDC/Keycloak/LDAP builtins (ADR-0004 §1 listed them; they never shipped): each is an unbounded auth surface entering the decision path's dependency tree for no capability HTTP lacks. Instead a four-layer ladder (L0 fixtures → L1 no-network builtins → L2 forge-groups → L3 your IdP broker) with a runnable contrib/providers/idp-groups/ reference; the L2→L3 step is two files changed, zero lines of assent rebuilt.

Operator gate

DEM-S13 is operator-gated. Creating public repositories under the PlatformRelay org is outward-facing and not covered by AGENTS.md rule 2's push grant to PlatformRelay/assent. S01–S12 are unblocked by it.

Gates

task check green locally at the branch tip (exit 0) — release-exitgate skips on PRs, so CI never runs it. Docs/spec only.

Decision rows verified intact after the rebase: D-140, D-141, D-142 all present, with #45's operator-answer closures preserved.

konih added 10 commits August 10, 2026 16:43
Two public demo repos designed spec-first (14 stories): assent-demo-platform
(GitLab, referential resources) and assent-demo-terraform (GitHub, blast
radius + opaque fallback). Two-tier demo contract: tier 1 is forge-independent
'git clone && assent test .'; tier 2 is a live MR/PR (GitLab now, GitHub
blocked on E10-S18).

Records the (class, environment) routing gap found while designing -- both
run.go selectBinding and test.go selectBindingForTest fail closed on the
multi-class shape both demo repos need -- as DEM-S00. Records OQ-32: no
provider transport can carry a credential, so no provider can call a
token-authenticated IdP directly; the broker pattern is the only working
shape and is undocumented.
… story count

The DEM epic had ZERO rows in openspec/specs/backlog.md -- every other
epic has them, and the backlog is what the loop reads to pick lanes, so
a spec with no rows is a spec nothing will execute. Same class as the
phantom E11-S06' row: spec and backlog disagreeing.

Also corrected D-142's story count: it said 14, the spec has 15 sections
(S00-S14). Found by counting rather than trusting the stated total --
the identical defect the independent review found on E11 (14 rows
against a stated 13).

Rows carry the two gates that must not be lost when someone reads the
backlog instead of the spec: DEM-S00 is engine-grade + maintainer LGTM
because (class, environment) routing is unwired and both demo repos fail
closed in both tiers without it, and DEM-S13 is operator-gated because
creating public repos under the PlatformRelay org is outward-facing and
not covered by AGENTS.md rule 2's push grant.
… of PR #47

F1 -- DEM-S00's premise 'the data already reaches these call sites --
no loader work' was FALSE at both cited sites, and the correction changes
the story's size. On assent run, selectBinding is run.go:219 but the
Config load is run.go:224-237 guarded by 'if cfg.config != ""' -- loaded
AFTER selection and only when --config is supplied, so S00 must move the
load ahead of selection AND decide the --config-absent behaviour (fail
closed, never fall back to a collapse or binding zero). On assent test
there is no Config at all: catalogue.go:124-127 says Config is
'deliberately absent; a later story that needs config-derived fields adds
it then'. S00 IS that later story, so extending catalogue.Input is a
deliberate E6 contract change, not wiring. Config.Classes has zero
production readers -- precisely parsed-and-discarded. Tier 1 is the risk:
wiring only run.go leaves assent test, the epic's primary deliverable,
failing closed while the story reads done.

F2 -- three REQs assumed a multi-file changeset. The evaluation unit is
ONE file: assent run takes one --subject file:<path> (run.go:266), diffs
it alone (:289), the fold propagates only ClassAssentPolicy and opacity,
and adoptertest.Case is singular. There is also no unmatched-EDIT
analogue of the delete escalation. REQ-DEM-S05-04 and REQ-DEM-S10-03 are
rescoped to tier 2 with explicit anti-tautology clauses -- satisfying
either with a hand-authored facts.yaml value is a test that cannot fail,
the defect class this project logged six instances of on 2026-08-07.

F3 -- the ladder claimed L1 (repo-file/resource-owner) runs at tier 1.
It cannot: test_provider_fence_test.go:69-80 fails the build if test.go
touches the live provider host, so tier-1 facts come solely from
adoptertest.MapFacts. A reader following the README would believe they
watched ownership resolve from repo files when they watched a fixture
literal -- the load-bearing claim of a demo whose premise is 'everybody
can see assent in action themselves'. Ladder corrected to 'tier 2 only'
with the fence cited, and REQ-DEM-S06-03 labelled tier-2.

F4 -- ADR-0008 s27 does not exist (the ADR has s1-4 plus two amendments);
it was cited 3x including in the permanent decision log. The substance
was right and the pointer fabricated: the implicit unclassified class is
s1 (classify.go:18-20) and the no-vouch-rule-may-match guarantee is
enforced by classify.ValidateRouting / ErrReservedClassRouting
(classify.go:127-145). Load-bearing because REQ-DEM-S10-01 instructs the
implementer to cite the two mechanisms separately.

All four verified against source before acting.
F7 -- the annotation pass is deferred, but the GAP is no longer silent.
DEM has 0 Test:/Verify:/Level: annotations against E10's 53, E11's 36
and E6's 42, so 'green' is undefined for all 15 stories -- exactly the
condition that produced this project's documented six tests that cannot
fail. Stated as a hard precondition in the epic's Executability
paragraph and on the backlog section header: no DEM story is
implementable until its REQs carry them, and DEM-S00 in particular must
not be started, being engine-grade + maintainer LGTM and the story
someone reaches for first.

F8 -- the backlog row asserted 'so task check covers it' in the PRESENT
tense, which is false: task check runs no example dogfood,
task dogfood-examples is called by nothing, and verify.yaml duplicates it
as a hardcoded pack loop. Corrected to future tense with the three facts
named, and S12 told to edit both places.

F10 -- REQ-DEM-S01-04 forward-depended on S03 (the reference broker)
while the epic orders S01 -> S02 -> S03. Resolved by preferring the
builtin-conversion arm, which keeps S01 self-contained and removes the
forward dependency rather than papering over it.

F11 -- DEM-S02 was marked [autonomous] while OQ-32 gates what its guide
may say. Row now scopes it precisely: S02 may write everything else and
must cross-link the OQ, but the vision.md/ADR-0004 amendment is
operator-gated.

F17 -- D-142 recorded the F1 and F4 corrections but not F2/F3, leaving
the row promising more than the epic now delivers. Added: repo 1's
headline referential differentiator is demonstrable only at tier 2
(DEM-S14, infra-gated), and the ladder's L1 rung does not run at tier 1
either -- so tier 1 is an honest demonstration of L0 alone.

F18 -- REQ-DEM-S07-03 still said the README shows the L0-L3 ladder with
no tier qualifier; the fence note binds it normatively but the REQ is
where an implementer looks.
@konih
konih merged commit 1debf3b into main Aug 10, 2026
7 checks passed
@konih
konih deleted the spec-dem-demo-repos branch August 10, 2026 15:19
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