You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
host: the state journal says it has not started until the first save (#2373) - #2374
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:
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)
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 }.
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"
}
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.
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.jsandcore/src/host/diagnostics.jsat T2 under the liverisk-rules.jsonat this base;core/test/**,patches/ds-bot/**andchangelog.d/**are T1,docs/subsystems/core-host.mdis T0Docs-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, andrecord()opens the writer on the first save that got pastwritable(). That leftstatus()incore/src/host/store.jsanswering for a writer that did not exist yet:so until the first save the journal claimed to be recording.
core/src/host/diagnostics.jsmapped that tostate 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 whosejournal/state.jsonldoes not replay, booted withstateJournal: trueand never saved,29355f16answered{ 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 saysstate journal: recording— a state that file was never in.What changed
core/src/host/store.js(+4/−1) —status()answers{ recording: true, opened: false }whilejournalWriteris 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), elseopened === false→not started (opens with the first save), elserecording/stopped (<reason>)as before, so an older status object withoutopenedreads 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, assertsopened: falsebefore the load and after it, that nojournal/folder exists yet, thenopened: trueafter the first save with no warning.a journal that refuses on the first save has only then stopped (#2373)pointsjournal/state.jsonlat a neighbour file: before the save the answer is still "not started", after itopened: true,recording: false,becausematchingnot 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 }(stillstate journal: recording), and from a read-only window (stillpaused (this window is read-only)), in the markdown and infacts.stateJournal.docs/subsystems/core-host.md(+1/−1) — "On start the writer replays the existing file" becomes "On the first save", andrt.journal.status()now documents{ recording, opened, because? }withopened: falseuntil a save opens the writer and thenot startedline 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 generateand nothing hand-edited:patches/ds-bot/0001-core-fixes.patch(+7/−5, thesrc/host/diagnostics.jshunk),0002-flybot-api-host.patch(+5/−2, thesrc/host/store.jshunk),0007-version-and-tooling.patch(+24/−1, thetest/diagnostics.test.jsmirror) and0014-flybot-branding.patch(+49/−1, thetest/store.test.jsmirror).client.jsandpatches/ds-bot/0008-client-bundle.patchare 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.jsincore/: 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: falseis the only difference.not ok 14 - a journal that refuses on the first save has only then stopped (#2373)—store.test.js:328: samedeepStrictEqual, messageloading 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 isstate 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 indiagnostics.test.js).Verification at this tip (
8e68d7168a6cc203da071e91e79130bda871cc33, treecc2c9b92e5176f71c5fd615217364fa64c07e30b)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:interop1 warning / 0 errors;lint:live-e2e0 / 0.ds-bot-patches checkok (10 patches apply cleanly in order),canonicalok (generate is a no-op on a second run),build-client --checkexit 0,changelog check17 fragments well formed,check-empty-catchexit 0,check-mojibakeok (0 U+FFFD in 1437 text files).Not run: the smoke test and the bench — this change touches
core/src/host/store.jsandcore/src/host/diagnostics.jsonly, and nothing inindex.jsor 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" }