Repository navigation
refactor(test): give the architecture and tenant guards a loader seam - #1067
Conversation
Scanners take a ModuleLedger instead of a path. RealModuleLedger.Value is the only reader of the real ledger. Fixtures build records and pass them through ModuleLedger.Validate instead of writing JSON. Parse keeps the JSON-shape errors; Validate holds every rule about the values. Refs #859
|
Reviewed d2d8ab4 against e63ad81 for #859 S1. No code findings or real-tree behavior regression found. Two P3 documentation corrections remain.
The decision record belongs in S1, as approved plan §7 explicitly requires. Its storage rationale, slice boundaries and preservation of the Verification:
The five changed error multisets are the worker's synthetic The reader audit found only Applied pstack:thermo-nuclear-code-quality-review and ponytail-review, scoped to S1 through P3. No actionable complexity finding. No file crosses 1000 lines; Independent reproduction covered the four requested matrix mutations and four additional checks above. The other matrix rows and the 92-ledger captures were audited from the worker's saved evidence. No full solution or browser run was needed for this test-infrastructure slice. Detailed reviews and reproduction logs are saved locally under |
|
Both P3 doc fixes are in 4f4509d (docs only,
Verification: the Documentation tests passed 20 of 20. The tracked-file guards ( |
TL;DR
Step 1, slice S1 of #859: the loader seam. The architecture and tenant-bypass guards now take rules as objects, not file paths, and nothing moves or is deleted. The real rules still load from
module-ledger.json,tenant-bypass-allowlist.jsonandfilter-free-set-sites.tsv, through exactly three accessors. S2 and S3 can then swap each accessor's source for C# rows without touching a scanner or a fixture.No rule, #632 key or hash, reason text or real-tree diagnostic changes. The proof is below.
Refs #859
Change map
Architecture/ModuleLedger.csLoadsplits intoParse(path)(JSON shape only) andValidate(ModuleLedger)(every rule about the values, same messages and labels)Validatethe real ledger usesArchitecture/RealModuleLedger.cs(new)Value = Validate(Parse(module-ledger.json))ModuleLedgerScanner,AdapterReachScanner(ScanandScanPeers),AdapterTierScanner,CompatibilityExceptionScannerModuleLedgerinstead of a path*RealTreeTests,TableOwnerRealModelTests,ModuleContractRealAssemblyTests,AccessOwnershipClaimTests)RealModuleLedger.ValueModuleLedgerTests,AdapterReachTests,AdapterTierTests,CompatibilityExceptionTests,PeerContractTests,CouplingMatrixTestsValidateinstead of writing JSONParsewithout touching themTableOwnerTestsValidate(Parse(path))and stay until S2. Two new facts check thatValidaterejects a blank table name and a blank FK name[null]and"name": 1JSON casesTenantBypass/GuardScanner.csScan(srcRoot, IReadOnlyList<AllowListEntry>)TenantBypass/BypassAllowList.cs,FilterFreeSetSites.cs(new)Entries(the sameAllowList.Load, with its blank-field filter) andAll(TSV rows asFilterFreeSetSiterecords)TenantBypassRealTreeTestsReason.Trim()and the blank-reason check stay at runtimestring.Join("\t", Symbol, Set, Signature), as beforeTenantBypassWalkTestsTenantBypass/, so it always ran with no entries (plan §2)TenantBypassAllowListTestsAllowListEntryobjectsdocs/decisions/859-typed-rule-registries.md,README.mdReview order. Read
ModuleLedger.csfirst (the split), then any one fixture file. The rest is mechanical.Proof that behaviour is unchanged
Counts. The Architecture and TenantBypass scope was measured at base
e63ad81d: 429 passed. At head: 431 passed, which is 429 plus the 2 newValidatefacts. A name diff shows only those 2 additions, and 22 theory cases whose arguments now show plain values instead of JSON fragments. Documentation tests (20) and the tracked-file guards (PostgresImagePin,RedisImagePin,CommittedSchemaDocs_CarryNoEnvironmentSpecificContent) pass with the branch staged.Loader equivalence (local, uncommitted harness). A verbatim copy of the old
Loadand the newValidate(Parse(path))read 115 inputs: every ledger file the 429-test suite writes (93), the real ledger, a missing path, and 20 hand-written malformed files.Fixture equivalence. Every ledger the suite builds was serialised, records plus sorted errors, at base (in
Load) and at head (inValidate). All 92 distinct base ledgers occur at head. The only 2 extra head ledgers are the two new facts. So the record-built fixtures reproduce exactly the inputs the JSON fixtures produced.Mutation matrix (plan §5). Each mutation was applied to the
bin/data or tosrc/and then restored. Base and head match on every row: same red test, same failure count, same message.src/Domain.Sales.DiscountCeilingModuleLedgerRealTreeTests: "undeclared cross-owner edge Finance -> Commerce"ListAccountsCliCommand.RunAsyncAdapterReachRealTreeTests: "undeclared adapter reach Cluckwork.Api.Cli.ListAccounts…"src/IgnoreQueryFiltersin Infrastructure…AllBypassesAreAllowListed: "unexcused bypass"src/AccountScopedUserValidator's classifieddb.Userspredicate…FilterFreeSetSitesAreStableAndClassified: "unclassified filter-free-set candidates"…AllBypassesAreAllowListed: "unexcused bypass"…FilterFreeSetSitesAreStableAndClassified: "unclassified…"TableOwnerRealModelTests: "undeclared cross-owner foreign key FK_AspNetUsers_Accounts_AccountId"CompatibilityExceptionRealTreeTests: "undeclared compatibility exception"AdapterTierRealTreeTests: 2 of 3 red, includingMcpTierRow_IsDeclaredExpenseCategoriesTableOwnerRealModelTests: "table 'ExpenseCategories' … has no owner"AspNetRoleClaimsTableOwnerRealModelTests: "… disagrees with CLR namespace owner "IFinanceModulefrom Finance's contractAdapterReachRealTreeTests: "IFinanceModule at src/Cluckwork.Api/Endpoints/Expenses/ExpenseEndpoints.cs"ModuleLedgerRealTreeTests: "undeclared cross-owner edge Access -> Commerce"persistenceForbiddenNamespacesAdapterReachRealTreeTests: "adapter registry error: adapterRoots declares no persistenceForbiddenNamespaces"The data was restored after each mutation. The full scope then ran green at head (431).
What does differ (all JSON-only, all gone in S2)
The harness above found these. None is reachable from the real files or from any test.
kind(owner or edge) now readskind '', where it readkind '<missing>'. A record cannot tell missing from empty.Contains, and the failure text shows up to 10 errors.tablesowner per JSON key. This difference is closed inParse, so the error multiset is equal. The harness found it on 4 fixture inputs: JSON reports once per key, while records report once per owner.Key\tReason), not the raw line. They differ only by trailing tabs on a short row.Scan. The same test goes red either way.Not in this PR
TableOwnerTests.MalformedRowscases are semantic and stay JSON here: unknown table owner, blank FK reason and missing override reason. S2 converts them to records when it deletesParse.AllowListEntrykeeps itsrequiredJSON shape until S3 (plan §1).deleteWhen: "#859"is untouched. Removing it is still [C] #514 slice 17: assembly split — conditional on Track A/B evidence #859's job.