📝 docs(openspec): design P5-DEM public demo repositories (D-142, OQ-32) - #47
Merged
Conversation
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.
…lane # Conflicts: # CHANGELOG.md
… 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.
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.
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.
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-labis 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:
git 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.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 whatbuiltin/resource-ownerandbuiltin/repo-fileexist 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:493selectBindingfails 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:366selectBindingForTestcollapses to the strictest binding (D-060) but fails closed when two bindings differ inclass,packs, orrequire[].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.Configalready carriesEnvironments/ClassesasNamedMatch. Note the epic's clearest demonstration — the identical diff that APPROVEs indevand REVIEWs inprod— 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:
find examples -type d -name providersreturns nothing. All three shipped packs declareproviders:with no host declaration, soprovider_host.go:83-87silentlycontinues and every shipped example's provider resolves to nothing. The required path is documented nowhere.CallHTTPsets onlyContent-Type; the repo-side provider schema isadditionalProperties:falseover{type,url,failure};ScrubEnv/ScrubArgvrefuse(?i)TOKEN|SECRETnames 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 whatdocs/vision.md:67promises, 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
PlatformRelayorg is outward-facing and not covered by AGENTS.md rule 2's push grant toPlatformRelay/assent. S01–S12 are unblocked by it.Gates
task checkgreen locally at the branch tip (exit 0) —release-exitgateskips 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.