docs(plans): record !2411, correct S04-A's ordering rule, Files lists and seams
What this does
Six edits to the per-format credential columns plan.
Records Step 2's MR. Row 2 was the last empty cell among the three
add-columns steps; Steps 1, 6 and 9 are already filled. It now points at
!2411 (merged) (:908).
Corrects the migration ordering rule. Step 2's Shared seams paragraph said the requirement over the six migration steps is "mutual exclusion rather than a sequence: any order is fine". That is false, and !2411 (merged) is the counterexample.
internal/datastore/migrations/migrations.go sets
goose.WithAllowOutofOrder(false). A step whose migration versions sort below
a version the database has already applied is refused outright rather than
applied late: Up returns a missing-migration error and applies nothing,
Runner.Start calls it at pod startup, and Runner.Check stays pending on
failure. So the versions are ordered, not merely mutually exclusive, and
whichever of the six lands second has to renumber above the head at that
moment rather than only re-deriving knownHeadVersion.
Mutual exclusion over knownHeadVersion is still required; the ordering
obligation is added beside it.
Both halves are recorded once, in ## Dependencies (:767-814), the shape
this plan already uses for Row counts (:739-747), the lock window
(:748-766) and the two merged-before gates (:827 and :838). The six
steps the rule binds cite that line from their Shared seams field
(:351, :519, :548, :621, :639, :706), and Step 2 keeps only its
own knownHeadVersion seam. The paragraph introducing the mermaid diagram
carried the same claim and pointed readers at Shared seams for the rule;
it now names the two constraints mermaid cannot draw and cites the same line
(:274).
Says what CI does not enforce. "Either way it is caught before deploy"
held for the two cases the bullet names, a pipeline created before a sibling
merged and a sibling that has already merged, and not for two of the six
riding one merge train. Both halves of lint:migration-ordering's comparison
read the target branch, target_latest from a git ls-tree -r of it
(.gitlab-ci.yml:530-533) and the added set from a three-dot
git diff <target>...HEAD (:539), so on the second car's pipeline the car
ahead is invisible twice: its timestamp is not in target_latest, and its
migration file lands inside the second car's own added set. Every timestamp
there sorts after the target's latest, the loop at :548-553 finds nothing,
and both cars go green and merge out of order. lint:comment-caps already
records the same blindness for its own base selection (:870-872), so the
clause has a citation in the repo rather than only a derivation. It lands in
both places the fact lives, the bullet (:796-807) and the graph note
(:275-278), so a reader who finds only one does not relax the constraint on
the grounds that CI would catch the violation.
Drops a borrowed justification that argued the other way. "That is the
conclusion the package-id FK-check index plan reached on the same two files"
attached to both the mutual exclusion and the second-lander rule, and that
plan reached neither: it answered the same overlap by serializing, "they run
serially rather than in parallel" (:332-333) and "Step 2 opens after Step 1
merges" (:499-500). Serialization fixes one sequence, which this plan's
graph note says these constraints do not do, so the borrow argued the
opposite of what it was cited for. The passage now says what that plan did
and why this rule is weaker (:772-781), and the genuine support was already
two sentences later in AllowOutofOrder(false). The two-files half of the
claim held and is kept: structure.sql, and the migrations_checksum_test.go
that carries knownHeadVersion. The sibling borrow in the ADR-007 gate
(:838) was checked too and stands, so it is left alone.
Completes Steps 4 and 7's Files lists. Step 4 named
internal/datastore/maven_remote_repositories_errors.md but not the .go
beside it, and Step 7 had the same gap for Container. Both classifiers carry
the code half of their sidecar's PgError.Detail rule, and the step that
encrypts the columns is what falsifies that reason, since the failing row no
longer echoes plaintext credentials. Step 10 already listed npm's pair
(:665-672), so Maven (:434-441) and Container (:573-582) now read the
same way and no format is left split. Container's entry says one site rather
than Maven's two: pgErrorCarriesRowData is declared in
npm_remote_repositories_errors.go and described in
maven_remote_repositories_errors.go, and the container file carries neither.
Moves the CountRowsForVersion residual to Step 3. Step 4 carried an
entry for internal/datastore/namespace_encryption_keys.go whose whole
content was a residual, "what this step owns is the comment's claim about
what the function reports", and the split was empty. Guardrail 17
(AGENTS.md:516) obliges Step 3 to leave the comment true when it changes
the body, so the rewrite that survives Step 3, "counts registered tables", is
still true once Step 4's writes create the first Maven rows. The one Step 3
rewrite that would leave a residual is a present-tense "still reports 0"
clause, and A Claim Time Will Falsify Names What It Is True At rules that
shape out. Step 3's entry (:372-387) now covers the body and the comment's
claim about what the function reports; Step 4 lists the file nowhere.
Both Shared seams fields name the path: Step 3's (:401-405) as the only step
in this plan that edits it, Step 4's (:471-478) to say it edits none of it,
since that is what a reviewer of either MR opens the field for and the
Depends on: Step 3 edge gives the mechanism without the path.
The Files-list and seam edits ride in this MR rather than on separate branches
touching the same file: guardrail 4 (AGENTS.md:387) gives collision between
parallel edits as the reason the Status table takes a single writer, and
guardrail 23 (AGENTS.md:565) says an overlapping file conflicts whichever
lands second with no pipeline reporting it. !2411 (merged) sits on this plan's Status
row and this MR is unmerged, so any further branch would be the next one
queued behind them.
Plan line numbers are measured at 54749e159. The
namespace_encryption_keys.go citations inside the plan stay at ee9e5063c,
the basis its Research Findings pins; the .gitlab-ci.yml and AGENTS.md
lines cited here are identical at both.
How this was reached
Step 6 merged with 20260904140000 and 20260904140100 while Step 2 sat on an
older base carrying 20260904131400 and 20260904131500. Following the plan
as written, Step 2 would have re-derived the constant, resolved the one
conflicting line, and gone red on the merge train. It renumbered instead.
lint:migration-ordering is what catches it, outside the merge-train blind
spot. The job fetches the target branch and reads its latest
migration timestamp when it runs (.gitlab-ci.yml:525 and :530-533), and
the added-file set comes from a three-dot diff against the target (:539), so
the merged-results setting is not what makes the check current. A pipeline
created before a sibling merged still passes, and the violation appears on the
next pipeline the branch gets: push or re-run after the sibling lands and it is
a red job before review; leave it, and the merge train's own pipeline is the
first to see it, as a dropped train. The local suites see nothing, since every
suite migrates from zero, where version order is self-consistent, and
scripts/ci/check-migration-immutability.sh reads in-place edits rather than
ordering. Re-running the job after a sibling lands, and renumbering above the
head it reports, is what avoids the round trip.
Steps 5, 8 and 11 each drop a plaintext column and carry the same obligation, which is why this belongs in the plan rather than only in !2411 (merged)'s description.
Scope
Plan text only. No code, no schema, no behaviour. 113 insertions and 25
deletions against the merge base b5287abe3, all in the one plan file.
Related to #417 (closed)