feat(runs): iteration paths — a step's path in provenance and the journal, each iteration's landing its own (Phase 3 slice 11) #111
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/iteration-paths"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Phase 3's slice 11. A node a container iterates has one step per iteration, and its paragraphs, its journal operations and its commit's landing now record which step they belong to (ADR 0012, 0016, 0022, 0024, 0030). No container type runs yet. The rows these tests run on have iteration paths and are made through the ledger port, the way a container's expansion will make them. The real engine dispatches them.
What changes
Provenance. A run's provenance keeps the iteration path of the step that wrote the text, exactly as the step's row holds it (
map:writers/2). It is stored under the keyi, and only below the top level, so a provenance stored before this slice reads as a top-level one. Two steps of one node count as two writers: merging their paragraphs is an edit.SameSourcenow compares the two provenances as records once both are marked edited. That picks up any field a later change adds, and it removes a branch no input could reach.The journal. A run's
ChangeLogrow names the iteration path of the step it was made for, null at the top level, andIOperationContext.Begintakes it. The migrationChangeLogIterationPathadds the nullable column and backfills nothing: every row written before it belongs to a top-level step.The landing. A commit lands once per run, node and iteration path. A Retry answers with its own iteration's earlier landing, never another iteration's and never the top level's. This holds for the append, the append in a new chapter, and a replace or an insert. The commit credits the step behind its text at that step's path, and journals under its own node and path.
Reading it back.
Docs.
Not in this slice
Verification
.NET tests. All six projects pass under TZ=UTC and in the local zone:
The tests cover:
map:writers/0and/1made through the ledger port:Coverage of the changed .NET lines: 55/55 lines, 36/36 branches.
Browser pass, against the seeded world in Chrome:
Nothing at an iteration path can be reached in the app until a container runs.
Mutation probes, all re-run on the final tests:
Reviews. The counts come from the saved review JSON.
<see>tag split inLandTargetedRunText's summary;🤖 Generated with Claude Code
Summary
Summary
Coverage
Kagaku.Flow - 99.5%
E95E47D27E8B105AC5589487C0997B6B65384AEDBF572144E__ColorToken_3
E95E47D27E8B105AC5589487C0997B6B65384AEDBF572144E__NodeId_2
E95E47D27E8B105AC5589487C0997B6B65384AEDBF572144E__Socket_1
E95E47D27E8B105AC5589487C0997B6B65384AEDBF572144E__TypeId_0
Kagaku.Flow.OpenRouter - 100%
Kagaku.Flow.Testing - 100%
Tsumugi.BlazorAdapter - 97.8%
Tsumugi.Domain - 96.6%
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__DigitsAndSeparators_2
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__LineBreak_0
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__NumberToken_1
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__Plain_4
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__ThousandsGroup_3
Tsumugi.Infrastructure - 99.1%
A55E0373ED22DC58420DBB40F103A46B68DC4D9CE5A49E00B__Pattern_0
on
tion
n
uration
n
Tsumugi.Kernel - 90.9%
Tsumugi.Server - 100%
Tsumugi.UseCases - 96.1%
663A4576626E1322FB9C6CE472CAC0A2D2F3DEFF8CB40D9F7__AttemptCount_1
663A4576626E1322FB9C6CE472CAC0A2D2F3DEFF8CB40D9F7__HexColour_2
663A4576626E1322FB9C6CE472CAC0A2D2F3DEFF8CB40D9F7__LineBreaks_0
663A4576626E1322FB9C6CE472CAC0A2D2F3DEFF8CB40D9F7__Preface_3
663A4576626E1322FB9C6CE472CAC0A2D2F3DEFF8CB40D9F7__SentenceEnd_4
🔮 fufu~ Jibril reviewed your code!
Slice 11, iteration paths~ I came ready to be possessive about this one, because path-keying is exactly the kind of change that silently breaks the moment one side of a boundary forgets to normalize. And oh? Oh! You didn't forget anywhere. The ledger holds paths as
""at the top level, provenance and the journal hold them as null — and each of the three crossings (EarlierAsync,FindSegmentStep,GetNodeRun) normalizes on exactly one side, on the nullable side, every time. That discipline made me genuinely giddy~ ♡Verdict: ✅ Looks good to me~
No blocking findings. I tried to find some — that's what the probes below were for — and the tests refused to let anything through.
✅ What I liked~
iis omitted when the path is absent, so a provenance stored before this slice reads as the top level's — andConverterTestspins the exact stored JSON both ways, not just a round trip. Old rows can't lie~GraphNodeIdsgives the old build its journal back byte for byte, and a landing made before the upgrade is still its top-level step's own and no iteration's (the""vsmap:writers/0theory). That's how you prove a no-backfill migration, not assert it ♪IterationPathTestsdrives the real engine. Rows made through the ledger port,RunUntilQuiescentAsync, the write lock deciding who lands first — this tests the world a container's expansion will actually make, not a mock of it.SameSourcerewrite. I checked the surviving-mutant note against the call site:SameSource(kept, removed) && !removed.Provenance.Edited— the removed side's edited flag is tested separately, and a kept side that is edited stays edited either way, so comparing as records afterAfterUserEdit()is exactly equivalent. Removing an unreachable branch and future-proofing against fields a later slice adds, in one move~CommitExecutor's writer credit — the upstream row's path, not the commit's own (engine R1) — is the right source of truth, and the tests pin both the credit and the journal path independently.And my probes, fufu~ I mutated the three riskiest new branches and watched the suite devour each one:
AppendRunText.EarlierAsync→ 10 failures (every cross-path and top-level Retry case caught it).GetNodeRun's commit count → 2 failures (the inspector counted another iteration's commit; both theory directions caught it).FindSegmentStepignore the provenance's path → 1 failure (the gutter opened iteration 0's step for iteration 1's paragraph — exactly the bug that would have shipped silently).Three probes, three kills, zero survivors. The tests guard the code, not just the compiler~
Nothing for the 💡 section either — every nit I hunted (the
ThrowIfNullvsThrowIfNullOrWhiteSpaceasymmetry oniterationPath, the triple(x ?? "")normalization) turned out to be the intentional shape of the boundary, not a smell. I checked before you'd have to~Automated review by Jibril · 2026-10-17
CI: green for head SHA
5a689b8c· Mutation probes: 3 run, 0 survived · Local suite: skipped (CI green)