test(npm): npm_virtual_upstream_rules rows 3/3 (S31 plan: 3/19)
Stacked MRs
| Part | MR | Scope | Reviewable LOC | Target |
|---|---|---|---|---|
| 1/3 | !2099 (merged) (merged) | schema, generated companions, and the cascade guard | 709 | main |
| 2/3 | !2100 (merged) (merged) | the catalog and DDL suite, and the sidecar's first two sections | 1,021 | main |
| 3/3 | this MR | the absences and the rows they admit | 1,357 | main |
Merge 1/3, then 2/3, then 3/3. The order is forced, not a preference. Part
2/3 asserts a table part 1/3 creates, and it also owns the fixtures part 3/3
consumes (npmVirtualRule, seedNPMVirtualRule, seedNPMVirtualRuleTree,
npmVirtualRulesInsert and the constraint-name constants), so part 3/3 does not
compile without it. Nothing flows the other way. Parts 1/3 and 2/3 have merged,
so this MR now targets main directly.
Part 1/3 additionally cannot be split from its cascade-guard companion: the
moment its migration lands, TestRepositoryStore_DeleteFKAssumption_SchemaGuard
fails until wantCascadeTargets names the new table, so separating them would
leave main red between merges.
Each part was compiled with -tags=integration and run on its own branch, not
only at the tip: 0, 19 and 40 TestNPMVirtualRules* functions respectively, all
passing, against PostgreSQL 16 with the full migration chain applied.
Parts 1/3 and 2/3 carry the figures their own MRs reported; this row is derived at the head commit. Summing the three rows as listed gives 3,087 reviewable LOC. Part 3/3 carries the overshoot and its "Diff size" section says why shrinking it would cost more than the imbalance does.
Merge-order requirement against !2033 (merged)
!2033 (merged) removes the // --- banner carve-out from
scripts/ci/check-comment-caps.sh, so a gap-separated divider segment stops
keeping its own 2-line row and joins the run below it. This file has four
banner-plus-doc runs, and under !2033 (merged)'s version of the script they measure 4, 3,
3 and 3 lines against the test-file cap of 2. Measured by running !2033 (merged)'s script
against this branch:
FAIL: ..._rowshapes_integration_test.go:18-19,21-22: test-file comment block is 4 lines (cap 2)
FAIL: ..._rowshapes_integration_test.go:38,40-43: test-file comment block is 3 lines (cap 2)
FAIL: ..._rowshapes_integration_test.go:331,333-334: test-file comment block is 3 lines (cap 2)
FAIL: ..._rowshapes_integration_test.go:700,702-705: test-file comment block is 3 lines (cap 2)The current gate passes on this branch. The two MRs share no file, so
has_conflicts stays false and neither pipeline reports the interaction.
This MR should merge before !2033 (merged). The banners are deliberately left as they
are: the merged sibling
npm_virtual_repository_upstreams_rowshapes_integration_test.go carries the same
pattern, so the tree-wide adjustment belongs with !2033 (merged) and its ratchet rather
than being pre-paid here against a gate that is still in review. If !2033 (merged) lands
first, the fix here is to fold each banner into the doc comment under it, which
costs one informative line per site.
What this does
The absences, and the row shapes those absences admit.
Three catalog arms pin the absences themselves: the columns this table declines to carry and why, that there is no fourth CHECK, and that there is no trigger, which is the half the CHECK count cannot claim on its own. The rest drive real inserts against the live table.
Enumerated columns get positive hits, not just negative ones
Every value of every enumerated column is covered as an accepted insert rather
than merely as a value the CHECK declines to reject: rule_type at both values,
target_field at all three, and the upstream repositories row at all three
kinds, all four formats and all three visibilitys. That matches the
sibling npm_virtual_repository_upstreams suite value for value, which is what
AGENTS.md guardrail 6 asks for. The pattern CHECK is covered in both
polarities with a multibyte pair that distinguishes char_length from
octet_length.
The accepted shapes are the point of the file: a duplicate rule, an allow and a
deny naming the same target_field and pattern, the same rule on two
associations, more rules than ADR-004's source cap, a rule under an already
soft-deleted upstream or virtual repository, a rule under a namespace out of
service, any id shape including a v4 and one equal to each of the other two uuid
columns, and an id reused across namespaces. Each is a recorded reading rather
than a gap. The cross-namespace refusal is attributed to the association key
rather than the namespaces key, so the test would notice if the wrong
constraint started doing the work.
The sidecar completes here
npm_virtual_upstream_rules_schema_notes.md gains the three sections that name
tests in this file: why NOT NULL is the whole defense on four columns, why all
three repositories enums get positive coverage, and why the rule count is
unbounded. Its intro now names both suites, since both exist as of this MR.
Review findings closed
A branch review raised eight warning-level findings and sixteen observations.
All are closed in a2150f0d5, which also touches
npm_virtual_upstream_rules_schema_integration_test.go for the last item.
Claims that were wrong:
- The FK-count comment said both foreign-key targets are hash-partitioned and
read
2+2*64unfiltered.namespacesis not partitioned, so it reads2+64, the 66 this table's own migration measured. The sibling carries the qualifier the adaptation dropped. Note for anyone reading the earlier review comment on this line: theconparentid = 0filter is load-bearing and must stay.conrelidexcludes referencing-side clones, but a partitioned referenced table produces child rows whoseconrelidis the parent. - The
rule_typeminus_onecase, and the same sentence in the sidecar, said a corruptrule_typeturns every read of the repository into a500.virtual.EligibleUpstreamsreturns on itsanyEligiblearm first, so it is a5xxonly when no other position is eligible. - The no-trigger message quantified over the shapes the
Acceptstests admit and omitted the lone wildcard and the 255-character ASCII pattern. - The no-fourth-CHECK message called a per-target pattern grammar impossible; a predicate on the discriminator is a legal immutable CHECK, so only the rules-per-association cap is unwritable.
- The multibyte pair excludes byte bounds at or below 509, not all of them; the ASCII pair closes 510 and 511.
- The control-character case cited a logging sink for rule patterns that nothing in the tree provides.
- The lower-bound count now says ten of this service's own columns, excluding the
vendored
river_*tables. - The
target_field"own route" claim did not follow from its premises: 0 and 1 are reached by both routes and only 2 is route-distinct.
Coverage added:
AcceptsContradictoryAllowAndDenypins the one row shape the migration catalogues that no test reached.AcceptsDuplicateRulegains the positive arbiter arm the sibling has, so the42P10is attributable to the columns named rather than to arbiter inference being unavailable.
Assertions that could have stopped testing their premise:
- The three stamping
UPDATEs go through a helper requiring one row affected. A zero-rowUPDATEis not an error, so the two soft-delete tests passed with their ids swapped. AcceptsDuplicateRulecounted by association alone, leaving "byte-identical" resting on a literal in another file. It now counts the value columns.
Both guards were checked by breaking the premise and confirming the test goes red, not by inspection.
Also: the two catalog counters name the offending constraint or trigger instead
of printing an integer; the loop-driven assertions no longer reuse one message
across iterations; three references name the file, tests and subtest they point
at; and the three !2101 forward-references that merged with part 2/3 now name
the rowshapes file in the present tense, since this change is what falsifies
them.
Four of these fixes (the stamping UPDATEs, the two counters' diagnosability and
the reused loop messages) diverge from the merged sibling suite, which has the
same shapes. That is deliberate: the alternative was leaving known-thin
assertions in place to keep the two files byte-comparable for guardrail 6's
mirror diff. The mirror still holds subtest for subtest; only these assertion
bodies differ, and the sibling is worth a follow-up.
Testing
Verified against PostgreSQL 16 on top of parts 1/3 and 2/3: 40
TestNPMVirtualRules* functions in the package and 60 subtests, all passing, up
from 19 at part 2/3, and the whole internal/datastore/migrations package passes
under -tags=integration. golangci-lint 2.12.2 with --build-tags=integration
and --max-same-issues=0 --max-issues-per-linter=0 --uniq-by-line=false reports
128 findings in the package, identical to the merge base, and 0 in any of the
three changed files. scripts/ci/check-comment-caps.sh passes.
Diff size
1,357 reviewable LOC, past the 500 docs/dev/development-model.md asks a split or a justification for, and the largest of the three parts: row-shape suite 1,272, sidecar 77 added and 2 removed, and 3 added and 3 removed in the part 2/3 schema suite for the forward-reference fix.
This part carries the overshoot because the row-shape suite is one file that
mirrors its sibling test for test. The obvious way to shrink it would be to move
its three Schema_-prefixed catalog arms into part 2/3, but the sibling
npm_virtual_repository_upstreams_rowshapes_integration_test.go holds exactly
those same three arms, and guardrail 6 asks a reviewer to diff the two suites
for dropped subtests. Moving them would break the mirror that check depends on,
so the imbalance is the lesser cost.