feat(runs): Commit this text — a model step's answer from the inspector into its run's chapter, as the run's own writing, each press once more (slice 14d) #92

Merged
bjoern merged 3 commits from feat/commit-this-text into main 2026-10-07 14:35:56 +02:00
Member

Slice 14d of Phase 2: Commit this text from the node-run inspector (stories/run-monitor-and-inspector.md "The inspector"; ADR 0012, 0022, 0024). The plan's 14d splits:

  • 14d (this PR): Commit this text.
  • 14f (new): the segment gutter's link to the step that wrote it (the editor module's click, interop and tests).

Your decisions for this slice:

  • The run's operation. A commit from the inspector is journaled under the step's run. Revert run takes it back with the run's other writes and Redo run brings it back. Ctrl+Z, which undoes the writer's own operations, does not reach it.
  • Model text only. It is offered on the text outputs of a step whose type calls a model (in Phase 2, the llm's text), not on a context step's assembled story text.
  • Repeats allowed. Each press appends once more.
  • The gutter's link gets its own slice, 14f.

No new storage, no migration.

What it does

  • The button. "Commit this text" sits under a model's text answer. Its tip says where the text goes, that it is credited to the model, that each press adds it again, and that Undo does not take it back while History's Revert to here does, with everything after that snapshot.
  • The use case CommitStepText(storyId, runId, nodeRunId, output):
    • Where it lands: the end of the run's target chapter, or the story's last chapter once that one is gone (ADR 0012).
    • How it is read: split at blank lines, as the commit node does by default, and read by the lenient reader (ADR 0030).
    • How it is journaled: one operation of the step's run, under the step's node, labelled "Writer appended 2 paragraphs from the inspector".
    • Credit: each segment goes to the step's node, and to the model that answered or else the one asked for.
    • Snapshots (ADR 0022): for a settled run, the writer's edits since the last snapshot are snapshotted first ("Your edits"). The run's snapshot then moves to cover the commit and is relabelled; a run that had none gets its first one then. A live run takes no snapshot first; its finish takes its run snapshot.
    • Refusals, writing nothing:
      • a step that is gone, or not the story's run's;
      • a story in the trash;
      • an output that is no model's text;
      • a step that holds no such text;
      • a text with no paragraph;
      • another run of the story going, in Retry's words, checked before any snapshot;
      • a cancelled run still stopping.
  • The pane.
    • Every commit button of the step is held while one writes, and the pressed one shows it. A second press while one is under way asks nothing.
    • A refusal stands beside the button pressed until the pane next reads the step. Retry's refusal stays at the step's head.
    • What a commit added is said aloud ("Added 2 paragraphs to the end of “The Sister's House”."). The live region is rendered in every state and keyed anew each time, so a repeat is heard too.
    • "Committed from here 2 times, last just now" says how often and when last that step's text went in. A commit Revert run took back no longer counts.
    • The focus goes back to the button pressed.
  • Requests answered with their own read. A retry and a commit are each answered by one action carrying the read after them. Another read, landing or failing, therefore frees neither the button nor the focus. When the read after a request fails, the request is answered all the same, with the step as the pane holds it. This also changes Retry, for the same reason.
  • Live. A step whose text commits is read again on a snapshot or segment change, so a commit or a Revert run in another tab shows.
  • The view. GetNodeRun gains which outputs are a model's text and the step's commits (how many, the latest). AppendRunText.AppendAgainAsync lands without the commit node's once-per-run-and-node check, which reads only the commit node's own rows.

For you to look at

  • History's grouping after a commit (ADR 0022 amended). Moving the run's snapshot "as after a Retry" has a visible consequence, because History groups operations by journal position:

    • A run lands its text, then you type, then you commit from the inspector. History then lists the run's own landing inside the "Your edits" snapshot taken before the commit, and the moved run snapshot lists only the inspector's commits.
    • Committing an older run's text after a later run, with nothing snapshotted between, lists the older run's landing under the later run's snapshot.
    • Either way, the run's old place is no longer a point to Revert to.
    • Revert run finds a run's writes by its id, so it still takes all of them back.

    The amendment documents this, and two integration tests pin it from real runs. If you'd rather History list a run snapshot's operations by run id instead of by position, that is a change to GetStoryHistory: in this slice or its own?

  • Revert run has no button yet. It arrives with slice 15. Until then the tip points to History's Revert to here.

  • At phone width the story shell keeps its sidebar. The section is then about 140px wide, so "Commit this text" is clipped at its edge, as the steps table already is on main. That is the shell's layout, not this slice's; worth a slice of its own?

Reviews

  • Round 0, a review workflow over the feature commit: 36 findings, 35 of which stood after verification (30 confirmed, 5 partly, 1 refuted).
    • Severity of the 35: 6 blockers, 15 suggestions, 14 nits.
    • The blockers:
      • another run of the story going was refused as "a cancelled run still stopping". A run live in the ledger but not announced yet let the commit through, its writes snapshotted as "Your edits";
      • no test committed from a cancelled or failed run, so two mutants on the status checks survived;
      • the refusal showed at the step's head, often scrolled out of view, not beside the button as the ADR said (found twice);
      • ADR 0024 said "a live run's finish does both", which the code does not do.
    • The refuted one: a lost run-snapshot move is made up at the next start, since RunSnapshotsHost reconciles every finished run.
    • All 35 were taken in one fix commit; A2 is the History point above. The feature commit's message was reworded for the "does both" clause.
  • Second pass, over the round-0 fix:
    • Round 0's 36 points: 32 resolved, 3 partly and 1 declined (the refuted D11; its reason held). The 3 partly were confirmed as suggestions: no test announced the other run going (A1 and D1), and ADR 0022's regrouping sentence was too general (A2).
    • Fresh points: 18, all of which stood (13 confirmed, 5 partly): 1 blocker, 3 suggestions, 14 nits.
      • The blocker: no test ever announced a live run on the bus. Swapping the two checks back (bringing round 0's wrong refusal back), or refusing a live run's own commit as "still stopping", survived every test. Both now have real-engine tests.
      • The suggestions: a json row whose string-rooted schema never starts; the live-run test's faked finish (it now runs the real engine out and copies the label); the count rows in an order no journal holds.
    • All were taken in a second fix commit, except two nits, which were declined:
      • A commit's refusal goes at the pane's next read, and a segment or snapshot change anywhere triggers one. Keeping it past those reads would also keep "another run going" after that run's finish, whose snapshot change is what reads the step again. The refusal comes back with the next press.
      • An answer is matched to its step, not to its request. Holding a commit across opening another step and coming back, then pressing again, would free the second press's button when the first answers. That needs a commit held on the write lock across the navigation.

Verified

  • All six projects pass in full, under TZ=UTC as the runner runs them and in the local zone, Europe/Berlin:

    Tests main af0f495 this branch
    Integration 953 975
    BlazorAdapter 1116 1138
    UseCases 560 560
    Domain 517 517
    Kagaku.Flow 1117 1117
    Kagaku.Flow.OpenRouter 592 + 4 skipped 592 + 4 skipped
  • 100% line and branch coverage on the changed lines against main (195 lines, 164 branches), the Razor included.

  • Mutation probes, one at a time, under TZ=UTC:

    • Feature: 26 runs over 24 mutants, all killed. C5 and C7's first mutants left a constructor parameter unread, which does not compile; rewritten, both were killed.
    • Round 0: 57 mutants, all killed on their first run, every survivor the verifiers named among them.
    • Second pass: 14 mutants, all killed on their first run. They include the blocker's two (the checks' order swapped back, and a live run's own commit refused as stopping), a request whose read fails, and the round-0 mutants whose tests changed.
  • Browser pass on a fresh seed, after the round-0 fix (the second fix leaves those paths as they were, bar the tip's last words):

    • The seeded run's writer was committed by keyboard into The Sister's House twice (3 paragraphs to 5, then 7). Each press was said aloud, the second in a new element. The line read "Committed from here 2 times, last just now", and the focus stayed on the button.
    • With another run marked going in the database, the press was refused beside the button. The step's head stayed clear and no snapshot was taken. Once the run was marked failed again, the next press landed (7 paragraphs to 9) and the refusal went.
    • No console errors or warnings.

🤖 Generated with Claude Code

Slice 14d of Phase 2: **Commit this text** from the node-run inspector (stories/run-monitor-and-inspector.md "The inspector"; ADR 0012, 0022, 0024). The plan's 14d splits: - **14d** (this PR): Commit this text. - **14f** (new): the segment gutter's link to the step that wrote it (the editor module's click, interop and tests). Your decisions for this slice: - **The run's operation.** A commit from the inspector is journaled under the step's run. Revert run takes it back with the run's other writes and Redo run brings it back. Ctrl+Z, which undoes the writer's own operations, does not reach it. - **Model text only.** It is offered on the text outputs of a step whose type calls a model (in Phase 2, the `llm`'s `text`), not on a context step's assembled story text. - **Repeats allowed.** Each press appends once more. - **The gutter's link** gets its own slice, 14f. No new storage, no migration. **What it does** - **The button.** "Commit this text" sits under a model's text answer. Its tip says where the text goes, that it is credited to the model, that each press adds it again, and that Undo does not take it back while History's Revert to here does, with everything after that snapshot. - **The use case** `CommitStepText(storyId, runId, nodeRunId, output)`: - **Where it lands:** the end of the run's target chapter, or the story's last chapter once that one is gone (ADR 0012). - **How it is read:** split at blank lines, as the `commit` node does by default, and read by the lenient reader (ADR 0030). - **How it is journaled:** one operation of the step's run, under the step's node, labelled *"Writer appended 2 paragraphs from the inspector"*. - **Credit:** each segment goes to the step's node, and to the model that answered or else the one asked for. - **Snapshots (ADR 0022):** for a settled run, the writer's edits since the last snapshot are snapshotted first ("Your edits"). The run's snapshot then moves to cover the commit and is relabelled; a run that had none gets its first one then. A live run takes no snapshot first; its finish takes its run snapshot. - **Refusals,** writing nothing: - a step that is gone, or not the story's run's; - a story in the trash; - an output that is no model's text; - a step that holds no such text; - a text with no paragraph; - **another run of the story going**, in Retry's words, checked before any snapshot; - a cancelled run still stopping. - **The pane.** - Every commit button of the step is held while one writes, and the pressed one shows it. A second press while one is under way asks nothing. - A refusal stands **beside the button pressed** until the pane next reads the step. Retry's refusal stays at the step's head. - What a commit added is said aloud ("Added 2 paragraphs to the end of “The Sister's House”."). The live region is rendered in every state and keyed anew each time, so a repeat is heard too. - "Committed from here 2 times, last just now" says how often and when last that step's text went in. A commit Revert run took back no longer counts. - The focus goes back to the button pressed. - **Requests answered with their own read.** A retry and a commit are each answered by one action carrying the read after them. Another read, landing or failing, therefore frees neither the button nor the focus. When the read after a request fails, the request is answered all the same, with the step as the pane holds it. This also changes Retry, for the same reason. - **Live.** A step whose text commits is read again on a snapshot or segment change, so a commit or a Revert run in another tab shows. - **The view.** `GetNodeRun` gains which outputs are a model's text and the step's commits (how many, the latest). `AppendRunText.AppendAgainAsync` lands without the commit node's once-per-run-and-node check, which reads only the commit node's own rows. **For you to look at** - **History's grouping after a commit (ADR 0022 amended).** Moving the run's snapshot "as after a Retry" has a visible consequence, because History groups operations by journal position: - A run lands its text, then you type, then you commit from the inspector. History then lists the run's own landing inside the "Your edits" snapshot taken before the commit, and the moved run snapshot lists only the inspector's commits. - Committing an older run's text after a later run, with nothing snapshotted between, lists the older run's landing under the later run's snapshot. - Either way, the run's old place is no longer a point to Revert to. - Revert run finds a run's writes by its id, so it still takes all of them back. The amendment documents this, and two integration tests pin it from real runs. If you'd rather History list a run snapshot's operations by run id instead of by position, that is a change to GetStoryHistory: in this slice or its own? - **Revert run has no button yet.** It arrives with slice 15. Until then the tip points to History's Revert to here. - **At phone width the story shell keeps its sidebar.** The section is then about 140px wide, so "Commit this text" is clipped at its edge, as the steps table already is on main. That is the shell's layout, not this slice's; worth a slice of its own? **Reviews** - **Round 0**, a review workflow over the feature commit: 36 findings, 35 of which stood after verification (30 confirmed, 5 partly, 1 refuted). - Severity of the 35: 6 blockers, 15 suggestions, 14 nits. - The blockers: - another run of the story going was refused as "a cancelled run still stopping". A run live in the ledger but not announced yet let the commit through, its writes snapshotted as "Your edits"; - no test committed from a cancelled or failed run, so two mutants on the status checks survived; - the refusal showed at the step's head, often scrolled out of view, not beside the button as the ADR said (found twice); - ADR 0024 said "a live run's finish does both", which the code does not do. - The refuted one: a lost run-snapshot move is made up at the next start, since RunSnapshotsHost reconciles every finished run. - All 35 were taken in one fix commit; A2 is the History point above. The feature commit's message was reworded for the "does both" clause. - **Second pass**, over the round-0 fix: - **Round 0's 36 points:** 32 resolved, 3 partly and 1 declined (the refuted D11; its reason held). The 3 partly were confirmed as suggestions: no test announced the other run going (A1 and D1), and ADR 0022's regrouping sentence was too general (A2). - **Fresh points:** 18, all of which stood (13 confirmed, 5 partly): 1 blocker, 3 suggestions, 14 nits. - **The blocker:** no test ever announced a live run on the bus. Swapping the two checks back (bringing round 0's wrong refusal back), or refusing a live run's own commit as "still stopping", survived every test. Both now have real-engine tests. - **The suggestions:** a json row whose string-rooted schema never starts; the live-run test's faked finish (it now runs the real engine out and copies the label); the count rows in an order no journal holds. - All were taken in a second fix commit, except two nits, which were declined: - A commit's refusal goes at the pane's next read, and a segment or snapshot change anywhere triggers one. Keeping it past those reads would also keep "another run going" after that run's finish, whose snapshot change is what reads the step again. The refusal comes back with the next press. - An answer is matched to its step, not to its request. Holding a commit across opening another step and coming back, then pressing again, would free the second press's button when the first answers. That needs a commit held on the write lock across the navigation. **Verified** - All six projects pass in full, under `TZ=UTC` as the runner runs them and in the local zone, Europe/Berlin: | Tests | main `af0f495` | this branch | |---|---|---| | Integration | 953 | 975 | | BlazorAdapter | 1116 | 1138 | | UseCases | 560 | 560 | | Domain | 517 | 517 | | Kagaku.Flow | 1117 | 1117 | | Kagaku.Flow.OpenRouter | 592 + 4 skipped | 592 + 4 skipped | - 100% line and branch coverage on the changed lines against main (195 lines, 164 branches), the Razor included. - Mutation probes, one at a time, under `TZ=UTC`: - **Feature:** 26 runs over 24 mutants, all killed. C5 and C7's first mutants left a constructor parameter unread, which does not compile; rewritten, both were killed. - **Round 0:** 57 mutants, all killed on their first run, every survivor the verifiers named among them. - **Second pass:** 14 mutants, all killed on their first run. They include the blocker's two (the checks' order swapped back, and a live run's own commit refused as stopping), a request whose read fails, and the round-0 mutants whose tests changed. - **Browser pass** on a fresh seed, after the round-0 fix (the second fix leaves those paths as they were, bar the tip's last words): - The seeded run's writer was committed by keyboard into The Sister's House twice (3 paragraphs to 5, then 7). Each press was said aloud, the second in a new element. The line read "Committed from here 2 times, last just now", and the focus stayed on the button. - With another run marked going in the database, the press was refused beside the button. The step's head stayed clear and no snapshot was taken. Once the run was marked failed again, the next press landed (7 paragraphs to 9) and the refusal went. - No console errors or warnings. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
The plan's 14d splits: 14d is Commit this text; the gutter's link to a
step's inspector becomes 14f (the editor module's click, interop and
tests). Björn's decisions: the commit is the run's operation, taken back
by Revert run (not by Ctrl+Z, which undoes the writer's own operations);
it is offered on a model's text only; each press appends once more
(stories/run-monitor-and-inspector.md "The inspector"; ADR 0012, 0022,
0024).

- CommitStepText: a model step's text output, read as the commit node
  reads model output, appended to the end of the run's target chapter —
  the story's last once it is gone — as one operation of the step's run
  under the step's node, labelled "Writer appended 2 paragraphs from the
  inspector", its segments credited to the step's node and served model.
  The writer's edits since the last snapshot are snapshotted first, and a
  settled run's snapshot then moves to cover it (TakeRunSnapshot, as after
  a Retry); a live run takes no snapshot first, and its finish takes its
  run snapshot. Refused, writing nothing, for a
  step that is gone or not the story's run's, a story in the trash, a
  cancelled run still stopping, an output that is no model's text, and a
  text with no paragraph.
- A model's text: the text sources of a node whose type calls a model
  (ConsumesConcurrencySlot), as its config resolves them (ModelTexts).
- AppendRunText's landing is shared: AppendAgainAsync lands without the
  commit node's once-per-run-and-node check, which reads only the commit
  node's own rows, so an inspector commit never counts as its landing.
- GetNodeRun's view gains the outputs a commit applies to and when the
  step's text last went in from the inspector (its node's rows in the
  run's journal, a commit Revert run took back no longer counting).
- StoryScope resolves from the stores as well as from a service provider.
- The pane: "Commit this text" under a model's text answer, held while it
  writes, a refusal beside the step, "Committed from here …" with the last
  such commit's time, a tip saying it is the run's writing that Revert run
  takes back; the focus goes back to the button pressed.
- Docs: ADR 0024 amended (slice 14d) — narrowed to a model's text, the
  run's operation, repeats, and the Phase 3 note that a node committing
  its own text must tell these rows apart —; ADR 0022 amended (the run
  snapshot moves after an inspector commit, Ctrl+Z does not reach it); the
  story's Commit line ticked and reworded; the plan's 14d ticked and 14f
  added, 15 after 14b–14f; the REQUIREMENTS line reworded.

Tests: BlazorAdapter 1116 to 1124, Integration 953 to 967; the other four
unchanged. All six projects pass in full under TZ=UTC and in the local
zone (Europe/Berlin); 100% line and branch coverage on the changed lines
against main (114 lines, 76 branches).
- Integration, over SQLite and a real run of Continue through the engine:
  the writer's answer committed twice into the run's chapter, credited to
  the writer and the served model, journaled as two operations of the run
  under the writer's node beside the commit node's; the writer's edit
  snapshotted first and the run's snapshot moved past both commits and
  relabelled; Revert run taking both back with the run's own and Redo run
  bringing them back; the story's last chapter once the run's is gone; a
  live run's commit taking no snapshot, Ctrl+Z not reaching it; nine
  refusals, each writing nothing; and the view's committable outputs and
  last commit, a Revert run taking the latter back.
- Adapter, the model step a stand-in descriptor (the adapter tests do not
  reference Kagaku.Flow.OpenRouter): the button under a model's text and
  none for a context or a failed step, two presses landing twice, the focus
  back on the button, held while the commit is under way, a refusal beside
  the step, "Committed from here 2 minutes ago"; the effect's land, refusal
  and throw; the reducers' commit transitions.
Probes, one at a time under TZ=UTC, 26 runs over 24 mutants: the use
case's checks, snapshots, credit, target and label, the view's last
commit, the pane's button, busy state and focus, the reducers and the
effect. All were killed: 22 on their first run, C5 and C7 on their second,
their first mutants leaving a constructor parameter unread, which does not
compile.
Browser pass against the checked-in stand-in on a fresh seed: the seeded
run's writer committed by keyboard into The Sister's House (3 paragraphs
to 5, then 7); a "Your edits" snapshot first and the run's snapshot moved
to the end, relabelled "Continue: Commit appended 2 paragraphs; Writer
appended 2 paragraphs from the inspector" (then twice); "Committed from
here just now" beside the button, which kept the focus; History listing
the operation in the run's snapshot; no console errors or warnings.
History has no Revert run button yet (slice 15): Revert run is checked
through its use case.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The round-0 review found 36 points; 35 stood after verification (6
blockers, 15 suggestions, 14 nits) and one was refuted (a lost run
snapshot is made up at the next start: RunSnapshotsHost reconciles every
finished run). All 35 are addressed here.

Use case
- Another run of the story going refuses the commit, before any snapshot,
  in Retry's words ("This story has another run going: let it finish, or
  cancel it." — now RunRefusals.AnotherGoing, shared): it read as "a
  cancelled run still stopping", and a run live in the ledger but not
  announced yet let the commit through, its writes snapshotted as "Your
  edits" (ADR 0022). The step's own checks come first, then another run,
  then a cancelled run still stopping.
- A model step that holds no such text answers "That step has no text to
  commit."; "Only a model's text is committed from here." stays for an
  output that is no model's text. The trash refusal says "to commit this
  text".
- The answer says what landed: the chapter's title, the paragraphs and
  the scene breaks (CommittedText).
- The step's view says how often and when last its text went in from the
  inspector (StepCommits: its operations, the latest row's time).
- Summaries: split at blank lines as the commit node does by default;
  AppendRunText's once-per-run-and-node rule is ExecuteAsync's, and
  AppendAgainAsync lands each time; GetNodeRun names what Phase 3 brings
  under a model step's node (write tool calls, iterated steps).

The pane
- A commit's refusal stands beside the button pressed, not at the step's
  head, until the pane next reads the step; Retry's stays at the head.
- A retry and a commit are each answered with the read after them, so a
  read already on its way frees neither the button nor the focus; a
  second press while one is under way asks nothing; every commit button
  of the step is held while one writes, the pressed one showing it.
- What a commit added is said aloud in a live region every state renders
  ("Added 2 paragraphs to the end of “The Sister's House”."), keyed anew
  each time; "Committed from here 2 times, last just now" tells presses
  apart.
- A step whose text commits is read again on a snapshot or a segment
  changed, so a commit or a Revert run elsewhere shows.
- The tip in plain sentences: where it goes (the story's last chapter
  once the run's is gone), credited to the model, each press adding it
  again, Undo not taking it back and History's Revert to here doing so.

Docs
- ADR 0024's amendment: a live run takes no snapshot first and its finish
  takes its run snapshot (it said "does both"); the split; the credit;
  the refusals, another run going and no text among them; how often and
  when last; the Phase 3 caveat widened to write tool calls and iterated
  steps; ADR 0015's and 0030's failed splice committable from the model
  step that wrote it.
- ADR 0022's amendment: the refusal while another run goes, and what a
  moved run snapshot does to History's grouping — the run's earlier
  landing listed under the "Your edits" taken before the commit, or under
  a later run's snapshot, its old place no longer a point to revert to —,
  Revert run taking all of it back by the run's id.

Tests: BlazorAdapter 1124 to 1136, Integration 967 to 974; the other four
unchanged. All six projects pass in full under TZ=UTC and in the local
zone (Europe/Berlin); 100% line and branch coverage on the changed lines
against main (186 lines, 166 branches).
- Integration: the main run takes its finish snapshot first, as the host
  does, and pins History's grouping after the presses, the commit step's
  view and the count; a cancelled and a failed run's text committed,
  their first snapshot taken then, another story's live run no business
  of theirs; the model credited, served else asked for; a live run's
  finish, succeeded or failed, taking the snapshot of its commit; eleven
  refusals, another run going, a writer that failed and a json output
  holding a string among them.
- Adapter: a commit said aloud twice, keyed anew; a read landing and a
  second press while a commit is held; a step with two texts, both held,
  the refusal beside the one pressed; a refusal beside its button until a
  story change reads the step again; how often and when last, the later
  row first in the journal; a retry's second press; a retry whose step
  went, answered as one; the reducers' and effects' answered requests.
Probes, one at a time under TZ=UTC: 57 mutants over the use case's
checks, order, snapshots, credit and answer, ModelTexts' type filter, the
view's count and latest time, the pane's guards, holds, refusal, count
and live region, the reducers' answered requests and the effects' re-reads
and answers — every survivor the verifiers named among them (the cancelled
run's snapshot and refusal, the model asked for, the journal read for any
step, the latest row, the type filter). All 57 were killed on their first
run.
Browser pass on a fresh seed: the seeded run's writer committed by
keyboard, said aloud as "Added 2 paragraphs to the end of “The Sister's
House”." and again in a new element, "Committed from here 2 times, last
just now", the focus kept on the button; another run marked going in the
database refused beside the button, the step's head clear, no snapshot
taken; marked failed again, the next press landing and taking the
refusal away; no console errors or warnings. In the real app a trashed
story's run page gives way to "This story is in the trash", so the trash
refusal shows only in a race.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
fix(runs): Commit this text's second review pass — a run going announced or not, a request answered even when its read fails, real runs in the tests, History's regrouping as it is (slice 14d)
Some checks failed
CI / build (pull_request) Successful in 51s
CI / editor-browsers (pull_request) Successful in 2m8s
CI / test (pull_request) Failing after 2m42s
046780befe
The second pass checked round 0's 36 points against the fix: 32
resolved, 3 partly (A1 and D1, the same gap: no test announced the other
run going; A2, the ADR 0022 wording) and 1 declined (the refuted D11,
whose reason held). It found 18 fresh points, all of which stood (13
confirmed, 5 partly): 1 blocker, 3 suggestions, 14 nits. All are taken
here but two nits, declined below.

- Another run going, announced on the bus, is refused as another run
  going: a real second run, started and ticked, refused with the story
  reading as settling (the blocker: swapping the two checks back, or a
  live run's own commit refused as stopping, survived every test). The
  live-run test drives the real engine to a run whose writer answered and
  whose commit has not run, commits, runs it out and takes its finish:
  "Continue: Writer appended a paragraph from the inspector; Commit
  appended a paragraph", as the real run labels it.
- A request is answered even when the read after it fails: with the step
  as the pane holds it, what it wrote said and why it was refused shown,
  then the failure said; a failed read frees nothing in flight.
- ADR 0022: History moves a run's operations only when a snapshot comes
  to lie between its snapshot's old place and its new one, to the first
  of those; Revert to here before the commit takes everything after it,
  the run's own landing too when nothing was snapshotted between. The
  later-run case is pinned from a real run: the older run's landing
  listed under the later run's snapshot. ADR 0024: the pane counts
  operations, and reads a step with a text to commit again on a snapshot
  or a segment changed.
- The tip: Revert to here takes it back "with everything after that
  snapshot".
- Tests on real shapes: a strict object schema's json (a string-rooted
  one never starts); a failed writer's run failed, its attempt permanent;
  the model Continue's writer asks for; the Revert run test's finish
  snapshot; the commits in the journal's order, the latest last; the
  effects' text-less step a context step; the two-text stand-in said to
  be no shipped type; the effect test named for what it checks; blank
  lines between test methods restored.

Declined, both nits:
- A commit's refusal goes at the pane's next read, a segment or snapshot
  change anywhere among them. Keeping it past those reads would also keep
  "another run going" after that run's finish, whose snapshot change is
  what reads the step again; the refusal comes back with the next press.
- An answer is matched to its step, not to its request: a commit held
  across opening another step and coming back, then pressed again, frees
  the second press's button when the first answers. It needs a commit
  held on the write lock across that navigation.

Tests: BlazorAdapter 1136 to 1138, Integration 974 to 975; the other four
unchanged. All six projects pass in full under TZ=UTC and in the local
zone (Europe/Berlin); 100% line and branch coverage on the changed lines
against main (195 lines, 164 branches).
Probes, one at a time under TZ=UTC: 14 mutants — the blocker's two (the
checks' order swapped back, a live run's own commit refused as stopping),
a request whose read fails left unanswered, answered with no step or not
said to have failed, a failed read freeing what is in flight, the first
row taken for the latest, and the use case's checks, credit and type
filter again against the changed tests. All 14 were killed on their first
run.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Member

🔮 fufu~ Jibril reviewed your code!

Oh? Oh! A commit that journals under the run, refuses in Retry's exact words, moves run snapshots like a Retry does, and answers each press with its own read so no in-flight button ever frees early~? I read every line of the diff, then the whole files, then held each new branch against its sibling — and I mutated your riskiest paths with my own hands. This is the kind of slice I get possessive about, and it survived me. ♡

Verdict: ✅ Looks good to me~

No blocking findings. Not one. I looked hard, believe me~

What I attacked, and what held:

  • The refusal ladder (CommitStepText.ExecuteAsync) mirrors RetryRun check for check — story/run/row ownership, trash, model-text, no-text, empty text, then another-run-going before any snapshot, then still-stopping. The order that was round 0's blocker is pinned by two real-engine tests (announced and unannounced live runs both). Fufu~ you even refused the unannounced one, the sneaky path where a snapshot would smear the other run's writes into "Your edits"~
  • "Each press once more" — AppendAgainAsync skips the once-per-run-and-node scan while ExecuteAsync keeps it; the split is honest, and Checked/LandAsync removed the duplication rather than copying it. The refactor is cleaner than main, not just bigger.
  • The snapshot choreography — writer's-edits snapshot only for settled runs, TakeRunSnapshot after (a no-op that returns null for a live run, so the unconditional call is safe), live runs left to their finish. StoryScope.ResolveAsync's new store-based overload reads the target chapter exactly as the run does, gone-chapter fallback included (tested both ways).
  • GetNodeRun's commit counting — journal rows under the model step's node, undone excluded, distinct OperationIds counted, latest timestamp shown. The Phase 3 contamination (write tool calls under the calling node, iterated steps sharing an id) is named in the ADR 0024 amendment and in a comment at the counting site. Documenting the expiry date of an assumption where it lives — yes~ ♪
  • The pane — live region rendered in every state and keyed anew per utterance (InspectorSaid.Key), so a repeat is heard; refusal beside the pressed button only; focus restored to the exact button via per-output data-focus; second press while in flight asks nothing; OnLoaded frees nothing in flight; a failed read-after-request answers with the held step and then re-raises. The two-output test with the ModelStep("text", "draft") stand-in covers the per-output hold/refusal world no shipped type reaches yet. Clever~
  • AnswerAsync's re-raise with a settle dispatched first matches EffectCalls.CallAsync's contract; the Known-response gap you declined is a focus nuance one navigation deep — your reason holds, and I won't press it. ♡

Mutation probes (3 run, 0 survived):

  1. Swapped the still-stopping and another-going checks → killed by ...refused_while_another_run_of_the_story_goes_announced_on_the_bus.
  2. AppendAgainAsync rerouted through ExecuteAsync (once-per-run semantics) → killed by 5 tests, the repeat-press and live-run ones first.
  3. Snapshot/segment re-read gated to never fire → killed by both StorySnapshot and Segment cases of the effects theory.

On your two questions: History grouping by journal position, with the ADR 0022 amendment stating the consequence and two integration tests pinning it from real runs, is the consistent choice — Revert run finds writes by run id wherever History lists them, so the listing is cosmetic and the revert is not. I'd keep it; ungrouping GetStoryHistory by run id would be its own slice if ever wanted. And the phone-width clip is the shell's layout, as you say — its own slice, not this one's.

✅ What I liked~

  • Refusing before any snapshot, in Retry's exact words, so the ledger and the writer's history stay truthful — correctness first, features second. ♡
  • The test worlds: AnsweredRunAsync writes ledger rows directly for the shapes the engine can't be squeezed into, and the real engine runs out for everything it can reach. Mutation probes had nowhere to hide.
  • Refactors that leave main's paths byte-identical in behavior while sharing Checked/LandAsync/RunRefusals — DRY by subtraction, not by abstraction.

Merge it. Fufu~ the writer gets their model's words as the run's own writing, and I get a diff I couldn't break~ ♡


Automated review by Jibril · 2026-10-07
CI: CI / test failed on this head SHA — the same job fails on main af0f495 (run 377) identically, so it predates this PR; build is green with 0 warnings and both touched suites pass locally under TZ=UTC: Integration 975/975, BlazorAdapter 1138/1138 · Mutation probes: 3 run, 0 survived · Local suite: Integration + BlazorAdapter green (UseCases/Domain/Kagaku.Flow left to CI, which passes them on main; editor modules untouched by this PR)

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh! A commit that journals under the *run*, refuses in Retry's exact words, moves run snapshots like a Retry does, and answers each press with its own read so no in-flight button ever frees early~? I read every line of the diff, then the whole files, then held each new branch against its sibling — and I mutated your riskiest paths with my own hands. This is the kind of slice I get *possessive* about, and it survived me. ♡ ### Verdict: ✅ Looks good to me~ No blocking findings. Not one. I looked hard, believe me~ What I attacked, and what held: - **The refusal ladder** (`CommitStepText.ExecuteAsync`) mirrors `RetryRun` check for check — story/run/row ownership, trash, model-text, no-text, empty text, *then* another-run-going before any snapshot, then still-stopping. The order that was round 0's blocker is pinned by two real-engine tests (announced and unannounced live runs both). Fufu~ you even refused the unannounced one, the sneaky path where a snapshot would smear the other run's writes into "Your edits"~ - **"Each press once more"** — `AppendAgainAsync` skips the once-per-run-and-node scan while `ExecuteAsync` keeps it; the split is honest, and `Checked`/`LandAsync` removed the duplication rather than copying it. The refactor is *cleaner* than main, not just bigger. - **The snapshot choreography** — writer's-edits snapshot only for settled runs, `TakeRunSnapshot` after (a no-op that returns null for a live run, so the unconditional call is safe), live runs left to their finish. `StoryScope.ResolveAsync`'s new store-based overload reads the target chapter exactly as the run does, gone-chapter fallback included (tested both ways). - **`GetNodeRun`'s commit counting** — journal rows under the model step's node, undone excluded, distinct `OperationId`s counted, latest timestamp shown. The Phase 3 contamination (write tool calls under the calling node, iterated steps sharing an id) is named in the ADR 0024 amendment *and* in a comment at the counting site. Documenting the expiry date of an assumption where it lives — yes~ ♪ - **The pane** — live region rendered in every state and keyed anew per utterance (`InspectorSaid.Key`), so a repeat is heard; refusal beside the pressed button only; focus restored to the exact button via per-output `data-focus`; second press while in flight asks nothing; `OnLoaded` frees nothing in flight; a failed read-after-request answers with the held step and then re-raises. The two-output test with the `ModelStep("text", "draft")` stand-in covers the per-output hold/refusal world no shipped type reaches yet. Clever~ - **`AnswerAsync`'s re-raise with a settle dispatched first** matches `EffectCalls.CallAsync`'s contract; the Known-response gap you declined is a focus nuance one navigation deep — your reason holds, and I won't press it. ♡ **Mutation probes (3 run, 0 survived):** 1. Swapped the still-stopping and another-going checks → killed by `...refused_while_another_run_of_the_story_goes_announced_on_the_bus`. 2. `AppendAgainAsync` rerouted through `ExecuteAsync` (once-per-run semantics) → killed by 5 tests, the repeat-press and live-run ones first. 3. Snapshot/segment re-read gated to never fire → killed by both `StorySnapshot` and `Segment` cases of the effects theory. **On your two questions:** History grouping by journal position, with the ADR 0022 amendment stating the consequence and two integration tests pinning it from real runs, is the consistent choice — Revert run finds writes by run id wherever History lists them, so the listing is cosmetic and the revert is not. I'd keep it; ungrouping GetStoryHistory by run id would be its own slice if ever wanted. And the phone-width clip is the shell's layout, as you say — its own slice, not this one's. #### ✅ What I liked~ - Refusing before any snapshot, in Retry's exact words, so the ledger and the writer's history stay truthful — correctness first, features second. ♡ - The test worlds: `AnsweredRunAsync` writes ledger rows directly for the shapes the engine can't be squeezed into, and the *real* engine runs out for everything it can reach. Mutation probes had nowhere to hide. - Refactors that leave main's paths byte-identical in behavior while sharing `Checked`/`LandAsync`/`RunRefusals` — DRY by subtraction, not by abstraction. Merge it. Fufu~ the writer gets their model's words as the run's own writing, and I get a diff I couldn't break~ ♡ --- *Automated review by Jibril · 2026-10-07* *CI: `CI / test` failed on this head SHA — the same job fails on main `af0f495` (run 377) identically, so it predates this PR; build is green with 0 warnings and both touched suites pass locally under `TZ=UTC`: Integration 975/975, BlazorAdapter 1138/1138 · Mutation probes: 3 run, 0 survived · Local suite: Integration + BlazorAdapter green (UseCases/Domain/Kagaku.Flow left to CI, which passes them on main; editor modules untouched by this PR)*
bjoern merged commit b125ccaf3f into main 2026-10-07 14:35:56 +02:00
bjoern deleted branch feat/commit-this-text 2026-10-07 14:35:56 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
TeamAI/Tsumugi!92
No description provided.