✨ feat(examples): EX-S07 REF-EX C5-C8 facts stubs - #64
Merged
Conversation
The pack already wires facts.quota.max_partitions into bounded-change.yaml's partition-change-bounds rule (D-071 deferral note). This gives the quota-ceiling-from-fact pattern its own discoverable, both-polarity test directory plus a facts-omitted edge case (an unresolved quota fact must fail safe to REVIEW, never APPROVE — mirrors internal/adoptertest's own hermetic unresolved-fact testdata) and an examples/archetypes/quota-ceiling/ seed.
New instance-set-allow-list rule (obligation `placement`) checks the S04 `instance_set` workload field against a stubbed facts.placement.allowed.value list. Pointer-scoped to /workloads/*/instance_set so every existing S01/S04 goldens stay vacuous (untouched). Added to bindings.yaml require: (vacuously satisfied on all pre-existing cases). Both polarities pass and earn genuine --coverage credit; examples/archetypes/placement-allow-list/ seeds the pattern.
…demo) New referenced-resource-ownership rule (obligation `resource-ownership`) checks a stubbed facts.resource_owner.owner.value (builtin/resource-owner shape, REF-GAP-1) against facts.author.groups.value whenever a topic's acl.resource names another team's resource. Pointer-scoped to /*/acl/resource so the rule is vacuous on every topic that never declares it — including S06's wildcard-grant goldens, which already populate acl.grants without acl.resource; a bare `!has(entry.acl)` guard (the spec's own shorthand) would NOT have been vacuous there, so pointer-scoping is used instead (documented in the rule file). Not a new builtin: facts.resource_owner.owner.value is a fixture literal, never resolved from the referenced resource's own file. acl.owner is carried alongside acl.resource as the entry's self-declared, untrusted claim — the predicate reads only the resolved fact. Both polarities pass and earn genuine --coverage credit; examples/archetypes/ referenced-resource-ownership/ seeds the pattern.
…n limitation) envs/prod/NOTES.md is outside infra-vars' class match (*.tfvars/*.tf); its deletion in head is caught only by the class-agnostic D-063/D-064 unmatched whole-file-DELETE fail-safe escalation, never a class-specific rule. Ran against the real engine before pinning: decision is a MEASURED REVIEW with the synthetic aggregate.unmatchedDelete finding, no obligation attached. Documented as a known limitation (not a feature) in the pack's config.yaml and examples/README.md — v1 does not correlate "delete A and append B" across files (REF-GAP-3, out of v1 engine scope). No second class added.
…t v2 backlog.md's REF-EX row and REF-GAP-1..3 rows now point at the landed EX-S07 fixtures instead of the future-tense "not started". archetype-goldens.md bumps to manifest version 2 with the three new C5-C7 archetype rows (quota-ceiling, placement-allow-list, referenced-resource-ownership).
Verified by temporarily moving quota-ceiling/ out of the tree and re-running --coverage: deleting it does not redden the gate, because bounded-change/ and bounded-change/negative/ already supply partition-change-bounds' both-polarity credit. Documented so a reader does not assume the directory is load-bearing for --coverage; facts-omitted/ remains load-bearing for the engine's fail-safe behaviour (dogfood-examples would catch a regression there).
A governed *.tfvars delete hits the SAME class-agnostic D-063/D-064 unmatched-whole-file-DELETE escalation as the ungoverned companion, not a distinct file-lifecycle rule — this pack (unlike topic-registry/ service-catalog) declares no fileEvents rule in any of its rule files. Stated as a direct reading of fileDeleteGoverned rather than a measured case: assent test cannot express a whole-file delete of a .tfvars fixture either way (inline cases.yaml refuses a non-lossless .tfvars marshal; the directory form requires the file on both sides).
…entation
REQ-EX-S07-03's Test: path said referenced-ownership/; the directory is
resource-ownership/ because assent lint's tests-per-rule hard error requires
the case name to be one of {rule.Name, obligation}. REQ-EX-S07-04's Test: path
implied a directory-form companion-delete/ case; it is an inline cases.yaml
case because the directory form cannot express a whole-file delete
(readSingleFilePair errors when head/<file> is absent). Also corrects the
REQ-EX-S07-04 Edge clause: infra-vars has no fileEvents rule at all, so a
governed *.tfvars delete hits the same class-agnostic escalation as the
ungoverned companion, not a distinct rule. Mirrors the REQ-EX-S05-05
amendment precedent (b08be34) — spec-first still means the spec must track
what was actually measured, not what was assumed before implementation.
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.
Summary
Closes REF-EX C5–C8 with stubbed facts, no live provider calls (
openspec/specs/p5-ex-complex-examples/spec.mdEX-S07).bounded-changerule already coveredfacts.quota.max_partitions; adds a discoverable named case plus afacts-omittededge case pinning the E2 fail-safe (unresolved fact → REVIEW, never APPROVE). The named case earns zero incremental--coveragecredit (verified empirically) but the fail-safe pin is genuinely load-bearing.instance-set-allow-listrule, pointer-scoped to/workloads/*/instance_set, both polarities genuinely covered.aclshape from EX-S06'swildcard-grant(additiveacl.resource/acl.ownerleaf keys, not a newaclobject — avoids opaque-diff and avoids flipping S06's goldens). Pointer-scoped vacuity guard, not the spec's literal!has(entry.acl), because S06's fixtures already populateentry.aclwithoutacl.resource.cases.yamlcase (directory form can't express deletes — verified against the harness source), measured (not assumed) REVIEW via the class-agnostic D-063/D-064 unmatched-file-delete escalation; corrects the spec's own Edge clause since infra-vars declares nofileEventsrule.Every load-bearing claim across all four patterns was independently reproduced by the reviewer against the real engine, not just read from the implementer's report.
Also amends
REQ-EX-S07-03/04in the spec to match the measured implementation.Test plan
task check— fully green, includingchangelog-verifytask coverage— 91.1% (required 91%)./bin/assent test --coverage examples/packs/topic-registry— 9/9 rules, both polarities covered./bin/assent test --coverage examples/packs/infra-vars— 6/6 rules, both polarities coveredgo test ./cmd/assent/ -run TestAssentTestNeverCallsProviderHost— passes