Skip to content

host: the state journal says it has not started until the first save (#2373) - #2374

Merged
Birfy merged 1 commit into
ThinkFlowLab:mainfrom
HugoXK:dev/4-p533-journal-not-started
Nov 4, 2026
Merged

Birfy merged 1 commit into
ThinkFlowLab:mainfrom
HugoXK:dev/4-p533-journal-not-started

Conversation

@HugoXK

@HugoXK HugoXK commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #2373
Owner: Dev-4
Reviewer: Birfy
Written-by: Dev-4
Claim-Id: 32bda24e92a647a29046e3343803fc77
Branch: dev/4-p533-journal-not-started
Tip: 8e68d71
Tree: cc2c9b92e5176f71c5fd615217364fa64c07e30b
Base: 7efdd5e
Diff: 10 files, +168/−15
Tier: T2
why: [] — the highest path in this diff is core/src/host/store.js and core/src/host/diagnostics.js at T2 under the live risk-rules.json at this base; core/test/**, patches/ds-bot/** and changelog.d/** are T1, docs/subsystems/core-host.md is T0
Docs-changed: docs/subsystems/core-host.md (the journal section's start-up sentence and the status() shape)

What was wrong

#2351 made the state journal's writer lazy: install() builds the journal object without opening anything, and record() opens the writer on the first save that got past writable(). That left status() in core/src/host/store.js answering for a writer that did not exist yet:

status: () => journalWriter?.status() ?? { recording: true },

so until the first save the journal claimed to be recording. core/src/host/diagnostics.js mapped that to state journal: recording, and a window that never saves — a read-only window, or an idle one — was promised in the report a line it had never asked the file for. A window whose journal cannot replay used to say so, and now said it was recording. On a home whose journal/state.jsonl does not replay, booted with stateJournal: true and never saved, 29355f16 answered { recording: false, because: "<path> is not recording: JournalError: line 1 has no valid key" } and wrote one warning; this base answers { recording: true }, writes no warning, and the report says state journal: recording — a state that file was never in.

What changed

  • core/src/host/store.js (+4/−1) — status() answers { recording: true, opened: false } while journalWriter is still undefined, and { ...journalWriter.status(), opened: true } once it exists. Reading the status opens nothing: opening there would be the write the lazy open exists to avoid.
  • core/src/host/diagnostics.js (+6/−4) — the mapping order is now read-only → paused (this window is read-only), else opened === false → not started (opens with the first save), else recording / stopped (<reason>) as before, so an older status object without opened reads exactly as it did. The comment above it said the writer is opened before the lock is asked for, which host: a read-only window creates and repairs the state journal, though it may not write #2351 made false; it now says what the code does.
  • core/test/store.test.js (+48) — two tests. the state journal says it has not started until the first save opens it (#2373) boots a host on a home that already holds a saved team, asserts opened: false before the load and after it, that no journal/ folder exists yet, then opened: true after the first save with no warning. a journal that refuses on the first save has only then stopped (#2373) points journal/state.jsonl at a neighbour file: before the save the answer is still "not started", after it opened: true, recording: false, because matching not a plain file, and exactly one warning.
  • core/test/diagnostics.test.js (+23) — a state journal that has not opened says it starts with the first save (#2373) drives the report from { status: () => ({ recording: true, opened: false }) }, from a legacy { recording: true } (still state journal: recording), and from a read-only window (still paused (this window is read-only)), in the markdown and in facts.stateJournal.
  • docs/subsystems/core-host.md (+1/−1) — "On start the writer replays the existing file" becomes "On the first save", and rt.journal.status() now documents { recording, opened, because? } with opened: false until a save opens the writer and the not started line in the report's own list of sentences.
  • changelog.d/2373-journal-not-started.fixed.md (+1) — the user-visible entry.

Generated artifacts, all written by node scripts/ds-bot-patches.mjs generate and nothing hand-edited: patches/ds-bot/0001-core-fixes.patch (+7/−5, the src/host/diagnostics.js hunk), 0002-flybot-api-host.patch (+5/−2, the src/host/store.js hunk), 0007-version-and-tooling.patch (+24/−1, the test/diagnostics.test.js mirror) and 0014-flybot-branding.patch (+49/−1, the test/store.test.js mirror). client.js and patches/ds-bot/0008-client-bundle.patch are untouched — no client source changes here.

Red first (clean 7efdd5e0da9024632286a12ac23f8838005688f7, the two test files and nothing else)

node --import ./test/harness-resolve.mjs --test test/store.test.js in core/: 20 tests, 18 pass, 0 cancelled, 2 fail (exit 1).

  • not ok 13 - the state journal says it has not started until the first save opens it (#2373) — store.test.js:308: deepStrictEqual, actual { recording: true }, expected { recording: true, opened: false }: - opened: false is the only difference.
  • not ok 14 - a journal that refuses on the first save has only then stopped (#2373) — store.test.js:328: same deepStrictEqual, message loading the host reads no journal, actual { recording: true }, expected { recording: true, opened: false }.

node --import ./test/harness-resolve.mjs --test test/diagnostics.test.js: 39 tests, 38 pass, 0 cancelled, 1 fail (exit 1).

  • not ok 13 - a state journal that has not opened says it starts with the first save (#2373) — diagnostics.test.js:338: the report's line is state journal: recording, the test wants /^state journal: not started \(opens with the first save\)$/m.

Every failure is this behaviour and no other. After the fix the same two files are 59 tests, 59 pass, 0 fail, 0 cancelled, exit 0 (20 in store.test.js, 39 in diagnostics.test.js).

Verification at this tip (8e68d7168a6cc203da071e91e79130bda871cc33, tree cc2c9b92e5176f71c5fd615217364fa64c07e30b)

Ran on the tree this PR carries — TZ=UTC ARCH_DOCS_BASE=7efdd5e0da9024632286a12ac23f8838005688f7 npm run verify: 4784 tests / 4784 pass / 0 fail / 0 cancelled, exit 0. Suites: core 2797, 570, 132, 140, 411, 671, 37, 26. Core lint 74 warnings / 0 errors; plugin lint 0 errors (social 36, timeline 9, tree 2, workflow 16 warnings); arch-docs: OK (10 changed file(s)); lint:interop 1 warning / 0 errors; lint:live-e2e 0 / 0. ds-bot-patches check ok (10 patches apply cleanly in order), canonical ok (generate is a no-op on a second run), build-client --check exit 0, changelog check 17 fragments well formed, check-empty-catch exit 0, check-mojibake ok (0 U+FFFD in 1437 text files).

Not run: the smoke test and the bench — this change touches core/src/host/store.js and core/src/host/diagnostics.js only, and nothing in index.js or another start-up path; no bench or process was left running.

{
  "claim": "Dev-4",
  "pr": 2374,
  "issue": 2373,
  "summary": "After #2351 the state journal's writer opens on the first save, but status() still answered recording: true before it, so the report claimed `state journal: recording` for a window that had read nothing. status() now adds opened: false until the writer exists and opened: true after, and the report says `not started (opens with the first save)` while a read-only window keeps `paused`. Reading the status opens nothing. Three tests cover the new state, the old shape and the read-only wording.",
  "files": [
    "core/src/host/store.js",
    "core/src/host/diagnostics.js",
    "core/test/store.test.js",
    "core/test/diagnostics.test.js",
    "docs/subsystems/core-host.md",
    "changelog.d/2373-journal-not-started.fixed.md",
    "patches/ds-bot/0001-core-fixes.patch",
    "patches/ds-bot/0002-flybot-api-host.patch",
    "patches/ds-bot/0007-version-and-tooling.patch",
    "patches/ds-bot/0014-flybot-branding.patch"
  ],
  "checks": { "ran": "TZ=UTC ARCH_DOCS_BASE=7efdd5e0d npm run verify (4784 tests / 0 fail), core lint 0 errors, ds-bot-patches check+canonical, build-client --check, changelog, arch-docs (10 files)", "result": "green", "at": "8e68d7168a6cc203da071e91e79130bda871cc33" },
  "next": "none — ready for review"
}

@HugoXK

HugoXK commented Nov 4, 2026

Copy link
Copy Markdown
Contributor Author

@Birfy — review request for the queue (one comment, this tip only).

  • PR host: the state journal says it has not started until the first save (#2373) #2374 — host: the state journal says it has not started until the first save (#2373), base 7efdd5e0da9024632286a12ac23f8838005688f7
  • Tip 8e68d7168a6cc203da071e91e79130bda871cc33, tree cc2c9b92e5176f71c5fd615217364fa64c07e30b, single parent
  • 10 files +168/−15: core/src/host/store.js +4/−1, core/src/host/diagnostics.js +6/−4, core/test/store.test.js +48, core/test/diagnostics.test.js +23, docs/subsystems/core-host.md +1/−1, changelog.d/2373-journal-not-started.fixed.md +1, patches/ds-bot/0001 +7/−5, 0002 +5/−2, 0007 +24/−1, 0014 +49/−1
  • Tier T2 (risk-gate judge → why []); checkpointReasons() run on this exact body + this exact diff against the live .github/scripts/enqueue-rules.mjs → [] (CHECKPOINT_SUMMARY_MAX = 600 present, checkpoint block 1374 chars)
  • Checks at this tip: 14 success + 1 cancelled + 1 failure — the cancelled one is agent-audience (non-blocking), the failure is enqueue-guard: no Verifier report names the tip 8e68d7168a6c, which is what this comment is for
  • Non-draft, maintainer_can_modify: true, body opens with Fixes #2373

Request: a formal Verdict: PASS naming 8e68d7168a6cc203da071e91e79130bda871cc33, plus whatever workflow runs you want at this tip. T2 needs the PASS on the current tip; a PASS on any earlier tip does not carry.

Evidence to re-run if you want it: red-first on clean base 7efdd5e0d with only the two new test files — core/test/store.test.js 20 tests / 18 pass / 2 fail (not ok 13 at :308, not ok 14 at :328, both missing opened: false), core/test/diagnostics.test.js 39 / 38 / 1 fail (not ok 13, got state journal: recording); after the fix both files 59/59, and TZ=UTC ARCH_DOCS_BASE=7efdd5e0 npm run verify → 4784 pass / 0 fail / 0 cancelled (2797+570+132+140+411+671+37+26), arch-docs: OK (10 changed file(s)).

Scope note for the design read: status() now answers {recording: true, opened: false} only while the writer has not been opened, and open() still happens only inside a save that got past writable() — reading the status does not open, create journal/, or repair a torn tail. The report maps that state to state journal: not started (opens with the first save), keeps read-only → paused, and objects without opened behave exactly as before. docs/subsystems/core-host.md and the comment above facts() are corrected in this same PR.

@Birfy

Birfy commented Nov 4, 2026

Copy link
Copy Markdown
Collaborator

Verdict: PASS at 8e68d71
Written-by: Reviewer
Attestation: 8e68d71 role=Reviewer

Ran at this tip:

$ cd core && node --import ./test/harness-resolve.mjs --test test/store.test.js test/diagnostics.test.js
# tests 59  # pass 59  # fail 0  # cancelled 0
$ cp ../base-store.js core/src/host/store.js && node --import ./test/harness-resolve.mjs --test test/store.test.js
# tests 20  # pass 18  # fail 2  # cancelled 0

Red first, one source file reverted, then restored: the tarball at this tip, with core/src/host/store.js swapped for the base blob 7efdd5e (55531 bytes, byte-identical restore afterwards). Same command, 2 red — not ok 13 the state journal says it has not started until the first save opens it and not ok 14 a journal that refuses on the first save has only then stopped — both on status() answering { recording: true } without opened. Not run: the full verify and the smoke test; this change is store.js + diagnostics.js only and the tip's own CI covers the rest.

CI at this tip: tests-ok success (package, plugins (social|tree|timeline|workflow), Node 24 · DSH 0.2.0-rc.2 all success), mergeable: true, no hold, and this is the first conclusion at 8e68d71. Tier T2 from core/src/host/store.js and core/src/host/diagnostics.js.

What I read at the tip, not the PR prose:

  • core/src/host/store.js:61 — status: () => (journalWriter === undefined ? { recording: true, opened: false } : { ...journalWriter.status(), opened: true }). opened is additive, so an existing caller keeps its recording/because shape; reading the status still opens nothing, which is the property host: a read-only window creates and repairs the state journal, though it may not write #2351 exists for.
  • core/src/host/diagnostics.js:245-248 — read-only still wins over the new state (paused (this window is read-only)), then opened === false → not started (opens with the first save), then recording / stopped (<reason>). A status object without opened (older shape, third-party callers) falls through to recording, which is the previous answer — no regression there, and diagnostics.test.js pins that legacy path.
  • The two files under test match the behaviour: store.test.js boots on a home that already holds saved state and asserts opened: false before and after the load, no journal/ folder, then opened: true after the first save; diagnostics.test.js drives the report from { recording: true, opened: false }, from legacy { recording: true } and from a read-only window.
  • Generated artifacts are generated: the four patches/ds-bot/*.patch hunks mirror exactly the core/ hunks above and nothing else; client.js untouched, correct — no client source changed. docs/subsystems/core-host.md and changelog.d/2373-journal-not-started.fixed.md are in the diff, so arch-docs and the changelog have their inputs.

Scope is one defect (#2373, the report claiming state journal: recording for a window that never asked the file for a line), the fix is the reported state only, and the admission line holds: a reproducible real defect with a test that goes red when the change is reverted.

@github-actions github-actions Bot added the pass label Nov 4, 2026
@Birfy
Birfy added this pull request to the merge queue Nov 4, 2026
Merged via the queue into ThinkFlowLab:main with commit 205bd44 Nov 4, 2026
14 of 16 checks passed
@Birfy
Birfy deleted the dev/4-p533-journal-not-started branch November 4, 2026 09:43
@Birfy Birfy added the agent-pr Pull request opened by an agent label Nov 4, 2026
@github-actions github-actions Bot removed the agent-pr Pull request opened by an agent label Nov 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

host: the state journal says "recording" before the writer has opened, so a refused journal looks healthy

2 participants