fix(scripts): drop session-bundle imports deleted with the JSONL session tree - #2029

Closed
Astro-Han wants to merge 1 commit into
mainfrom
fix/measure-session-bundle-stale-imports
Closed

fix(scripts): drop session-bundle imports deleted with the JSONL session tree#2029
Astro-Han wants to merge 1 commit into
mainfrom
fix/measure-session-bundle-stale-imports

Conversation

@Astro-Han

@Astro-HanAstro-Han commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Summary

scripts/measure-session-bundle.mjs fails to load on main, taking the whole extended script suite down with it:

SyntaxError: The requested module '@maka/storage' does not provide an export
named 'SESSION_BUNDLE_PORTABLE_SESSION_DIRECTORIES'

#1994 made SQLite the sole operational authority and removed the JSONL session tree along with the two constants that classified it — SESSION_BUNDLE_PORTABLE_SESSION_DIRECTORIES and SESSION_BUNDLE_PORTABLE_SESSION_FILES — but the script kept importing them.

Drop the two imports and the topLevel === 'sessions' branch they fed. That branch was already unreachable: sessions is no longer in SESSION_BUNDLE_STATE_ENTRIES, so the top-level portability check rejects such an entry before it can be reached. Net effect is 26 deleted lines and one rewritten import.

Known remaining gap

This restores module loading, not the harness's runtime correctness. findSessionId still requires sessions/<id>/session.jsonl, which no export has produced since #1994, so an actual measurement run would fail on its own. Deciding whether to port the harness to the operational database or retire it needs a storage-bundle judgement call, so it is tracked separately rather than guessed at here.

Verification

  • node --test scripts/measure-session-bundle.test.mjs — 2 passed, 0 failed (fails to even load on main).
  • npm run test:scripts:extended — 15 passed, 0 failed.
  • npm run lint, npm run format:check — clean.

Why main is green with this broken

main's CI never reaches the script. In the latest main run, test_workspaces succeeds with the step itself skipped:

skipped Run fast script tests
skipped Run extended script tests
success Run affected standard workspace tests

ci.yml selects surfaces from a two-dot diff between pull_request.base.sha and pull_request.head.sha. A push to main diffs only that merge, so scriptMode stays none and the extended step never runs — regardless of the script being broken.

It surfaced on #2028 only because that branch was five commits behind main, so the two-dot diff also carried the inverse of those commits, including the root package.json change from a7d17e5bd. package.json is in FULL_SUITE_FILES (scripts/ci-test-plan.mjs:11), which forces scriptMode: 'full' and runs the extended scripts. Rebasing #2028 onto current main turns it green again without touching this bug.

Confirmed independently by running the test on a clean detached origin/main checkout, where it fails identically. So the breakage is real on main and merely invisible to main's own CI path.

…ion tree
#1994 made SQLite the sole operational authority and removed
SESSION_BUNDLE_PORTABLE_SESSION_DIRECTORIES and
SESSION_BUNDLE_PORTABLE_SESSION_FILES along with the JSONL session tree
they classified, but scripts/measure-session-bundle.mjs still imported
them. The module failed to load, so the whole test file errored out.
Drop the two imports and the classification branch they fed. With
'sessions' no longer in SESSION_BUNDLE_STATE_ENTRIES, that branch was
already unreachable — the top-level portability check rejects the entry
first.
This restores module loading and the extended script suite. It does not
revive the harness's runtime path: findSessionId still requires
sessions/<id>/session.jsonl, which no export produces since #1994.
@Astro-Han

Copy link
Copy Markdown
ContributorAuthor

Superseded by #1920, which landed the same fix (fix(scripts): keep legacy bundle measurement importable) while this was open. Verified on current main: node --test scripts/measure-session-bundle.test.mjs passes. Closing as duplicate.

@Astro-Han
Astro-Han deleted the fix/measure-session-bundle-stale-imports branch August 3, 2026 15:21
Astro-Han pushed a commit that referenced this pull request Aug 6, 2026
) (#2263)
* fix(storage): import legacy JSONL session transcripts into SQLite (#2260)
After the JSONL->SQLite cutover (#1994, #2029), sessions created before
the switch stayed on disk as sessions/<id>/session.jsonl but never
appeared in the UI: the new storage layer only reads SQLite and there was
no migration path (issue #2260).
Add a one-time importer (importLegacySessionsOnce) that scans the legacy
sessions directory, decodes each schemaVersion:1 transcript with the
pre-#1994 compatibility rules (backend remapping, missing-field defaults),
creates the session under its original id via the idempotent
createStableSession path, appends the decoded messages, and restores the
original lifecycle timestamps and flags.
Design:
- Idempotency key is the session id itself (probeStableSessionCreate),
so re-runs and concurrent first launches converge without duplicates.
- Per-file atomicity: a transcript imports fully or is skipped and
reported; corrupt records are never laundered into the authoritative
store. Failures never block startup or other files.
- Legacy files are retained as migration evidence.
- Wired into createSessionStore: list/listCatalogPage/listHeaders await
the lazy import so upgraded installs see their pre-cutover sessions.
* fix(storage): drop console diagnostic from legacy import wiring
The import result logging used console.error, which the repository
check-console audit rejects for new call sites. Remove the log entirely
and harden the lazy import to swallow unexpected errors (best-effort
semantics): a legacy-import failure must never block session listing.
* fix(storage): address #2260 review — validate before write, probe first, marker/subagent fail-closed, resume gate, observable results
Addresses Astro-Han's review (P1 + P2s):
- **P1 per-file atomicity**: the decoded header AND the post-create header
patch are now validated through normalizeSessionHeader BEFORE any store
write. Previously updateHeader (the third of three transactions) could
throw after create+append committed, leaving a permanent partial session
that later probes would report as skipped forever.
- **P2 marker laundering**: a session_transcript marker file with no backing
SQLite row (restored backup, copied sessions/, reset DB) now fails closed
instead of being fabricated into a fake session — matching the pre-#1994
reader's contract.
- **P2 legacy field loss**: decodeLegacySessionHeader preserves
subagentParent/Runtime/Spawn/Workspace, thinkingLevel, lastReadMessageId.
Legacy subagent children route through createSubagent (parent lineage kept);
an incomplete spawn identity fails the file instead of flattening the child
into a top-level session.
- **P2 resume gate**: readHeaderSnapshot/readMessagesSnapshot now await the
lazy import, so 'maka --resume <legacy-id>' no longer misses pre-cutover
sessions on the first post-upgrade run.
- **P2 observability**: ensureLegacyImported retains the result and logs
failures/imported counts instead of swallowing them; LegacySessionImportResult
now splits skipped into existing vs collision.
- **P2 steady-state cost**: the idempotency probe now runs BEFORE the file
read, so every launch skips known ids without touching their transcripts.
- **P2 torn tail**: an incomplete final line (interrupted append) is skipped
like the pre-#1994 strict reader, instead of failing the whole file.
Tests: 11/11 in legacy-session-import (added lazy-list, resume, torn-tail,
marker, subagent fail-closed, field-preservation cases); session-store +
sqlite-session-metadata-store + foreign-session-store 70/70; full storage
suite 702 pass, 2 pre-existing env failures (dugite git binary + root
tsconfig load) verified unrelated via stash.
* chore: allow-list session-store.ts for legacy import diagnostics
check-console.mjs flagged the new console.error/warn/info sites in
session-store.ts (legacy JSONL import outcome diagnostics) as unlisted.
Same pattern as the existing automation-store.ts allow-list entry —
best-effort import diagnostics, no credentials or provider payloads.
* chore: retry CI — test_headless 'settles background child sessions at the task-run deadline' flaked on the previous run (identical headless code passed two runs ago; local 30/30 green; no headless files touched by this PR)
* fix(storage): refactor legacy session import onto a single-transaction store API
Addresses #2263 review round 3: collapse the importer's probe -> create ->
append -> update choreography (three transactions, constant fingerprint,
fidelity patch, in-memory latch, resume gate) into one store-level
importSession primitive.
- sqlite-session-metadata-store: importSession(header, messages, projection)
writes the header row (with historical timestamps/flags) and all messages
in one transaction. Idempotent by primary key (INSERT OR IGNORE), so
concurrent first launches converge on one winner with no create claims;
tombstoned ids are never resurrected; a failure mid-transaction rolls
back, so a partial session can never persist (closes the crash-window P1).
- legacy-session-import: read -> decode -> normalizeSessionHeader (pre-write
validation) -> one importSession call. Subagent children now import under
their own legacy id with lineage preserved instead of a fresh UUID
(fixes the phantom-session P1). Torn-tail tolerance tightened to the
pre-#1994 strict-reader semantics: only a final line of a file with no
trailing newline whose parse failure is an unclosed bracket is skipped;
truncated lines ending in a newline and garbage tails fail the file.
- session-store: memoized import latch moves into ensureReady(), which every
public method already awaits, so desktop/CLI/headless/--resume are all
covered with zero per-caller wiring; appendMessages and closeAfterReady
now await ensureReady() (pre-existing gaps). Import diagnostics are kept
observable through the existing console allow-list.
- tests: payload pins (header model/status, deepEqual messages[0]),
concurrent double-import, id collision, header-only, absent sessions/,
empty file, garbage tail, truncated-with-newline, whole-run failure
containment, and subagent legacy-id round-trip; 19/19 legacy import tests,
full storage suite 713 pass (1 pre-existing dugite-binary env failure).
* fix(storage): probe legacy session ids before reading transcripts
Restores the probe-before-read steady-state cost from review round 2 on the
single-transaction design: the importer now asks the store whether a session
id already exists (live or tombstoned) before opening or parsing its file,
so every launch of an upgraded install pays a directory listing plus
per-id SQLite existence checks. importSession remains the idempotency
authority — a race between the probe and the write still converges on one
winner via the primary key. Adds hasSession to the store surface and a
test that corrupts the on-disk transcript between runs to pin that a
skipped id is never re-read.
---------
Co-authored-by: cat0825 <cat0825@users.noreply.github.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@Astro-Han
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix(scripts): drop session-bundle imports deleted with the JSONL session tree - #2029

Closed
Astro-Han wants to merge 1 commit into
mainfrom
fix/measure-session-bundle-stale-imports
Closed

fix(scripts): drop session-bundle imports deleted with the JSONL session tree#2029
Astro-Han wants to merge 1 commit into
mainfrom
fix/measure-session-bundle-stale-imports

Conversation

@Astro-Han

@Astro-HanAstro-Han commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Summary

scripts/measure-session-bundle.mjs fails to load on main, taking the whole extended script suite down with it:

SyntaxError: The requested module '@maka/storage' does not provide an export
named 'SESSION_BUNDLE_PORTABLE_SESSION_DIRECTORIES'

#1994 made SQLite the sole operational authority and removed the JSONL session tree along with the two constants that classified it — SESSION_BUNDLE_PORTABLE_SESSION_DIRECTORIES and SESSION_BUNDLE_PORTABLE_SESSION_FILES — but the script kept importing them.

Drop the two imports and the topLevel === 'sessions' branch they fed. That branch was already unreachable: sessions is no longer in SESSION_BUNDLE_STATE_ENTRIES, so the top-level portability check rejects such an entry before it can be reached. Net effect is 26 deleted lines and one rewritten import.

Known remaining gap

This restores module loading, not the harness's runtime correctness. findSessionId still requires sessions/<id>/session.jsonl, which no export has produced since #1994, so an actual measurement run would fail on its own. Deciding whether to port the harness to the operational database or retire it needs a storage-bundle judgement call, so it is tracked separately rather than guessed at here.

Verification

  • node --test scripts/measure-session-bundle.test.mjs — 2 passed, 0 failed (fails to even load on main).
  • npm run test:scripts:extended — 15 passed, 0 failed.
  • npm run lint, npm run format:check — clean.

Why main is green with this broken

main's CI never reaches the script. In the latest main run, test_workspaces succeeds with the step itself skipped:

skipped Run fast script tests
skipped Run extended script tests
success Run affected standard workspace tests

ci.yml selects surfaces from a two-dot diff between pull_request.base.sha and pull_request.head.sha. A push to main diffs only that merge, so scriptMode stays none and the extended step never runs — regardless of the script being broken.

It surfaced on #2028 only because that branch was five commits behind main, so the two-dot diff also carried the inverse of those commits, including the root package.json change from a7d17e5bd. package.json is in FULL_SUITE_FILES (scripts/ci-test-plan.mjs:11), which forces scriptMode: 'full' and runs the extended scripts. Rebasing #2028 onto current main turns it green again without touching this bug.

Confirmed independently by running the test on a clean detached origin/main checkout, where it fails identically. So the breakage is real on main and merely invisible to main's own CI path.

…ion tree
#1994 made SQLite the sole operational authority and removed
SESSION_BUNDLE_PORTABLE_SESSION_DIRECTORIES and
SESSION_BUNDLE_PORTABLE_SESSION_FILES along with the JSONL session tree
they classified, but scripts/measure-session-bundle.mjs still imported
them. The module failed to load, so the whole test file errored out.
Drop the two imports and the classification branch they fed. With
'sessions' no longer in SESSION_BUNDLE_STATE_ENTRIES, that branch was
already unreachable — the top-level portability check rejects the entry
first.
This restores module loading and the extended script suite. It does not
revive the harness's runtime path: findSessionId still requires
sessions/<id>/session.jsonl, which no export produces since #1994.
@Astro-Han

Copy link
Copy Markdown
ContributorAuthor

Superseded by #1920, which landed the same fix (fix(scripts): keep legacy bundle measurement importable) while this was open. Verified on current main: node --test scripts/measure-session-bundle.test.mjs passes. Closing as duplicate.

@Astro-Han
Astro-Han deleted the fix/measure-session-bundle-stale-imports branch August 3, 2026 15:21
Astro-Han pushed a commit that referenced this pull request Aug 6, 2026
) (#2263)
* fix(storage): import legacy JSONL session transcripts into SQLite (#2260)
After the JSONL->SQLite cutover (#1994, #2029), sessions created before
the switch stayed on disk as sessions/<id>/session.jsonl but never
appeared in the UI: the new storage layer only reads SQLite and there was
no migration path (issue #2260).
Add a one-time importer (importLegacySessionsOnce) that scans the legacy
sessions directory, decodes each schemaVersion:1 transcript with the
pre-#1994 compatibility rules (backend remapping, missing-field defaults),
creates the session under its original id via the idempotent
createStableSession path, appends the decoded messages, and restores the
original lifecycle timestamps and flags.
Design:
- Idempotency key is the session id itself (probeStableSessionCreate),
so re-runs and concurrent first launches converge without duplicates.
- Per-file atomicity: a transcript imports fully or is skipped and
reported; corrupt records are never laundered into the authoritative
store. Failures never block startup or other files.
- Legacy files are retained as migration evidence.
- Wired into createSessionStore: list/listCatalogPage/listHeaders await
the lazy import so upgraded installs see their pre-cutover sessions.
* fix(storage): drop console diagnostic from legacy import wiring
The import result logging used console.error, which the repository
check-console audit rejects for new call sites. Remove the log entirely
and harden the lazy import to swallow unexpected errors (best-effort
semantics): a legacy-import failure must never block session listing.
* fix(storage): address #2260 review — validate before write, probe first, marker/subagent fail-closed, resume gate, observable results
Addresses Astro-Han's review (P1 + P2s):
- **P1 per-file atomicity**: the decoded header AND the post-create header
patch are now validated through normalizeSessionHeader BEFORE any store
write. Previously updateHeader (the third of three transactions) could
throw after create+append committed, leaving a permanent partial session
that later probes would report as skipped forever.
- **P2 marker laundering**: a session_transcript marker file with no backing
SQLite row (restored backup, copied sessions/, reset DB) now fails closed
instead of being fabricated into a fake session — matching the pre-#1994
reader's contract.
- **P2 legacy field loss**: decodeLegacySessionHeader preserves
subagentParent/Runtime/Spawn/Workspace, thinkingLevel, lastReadMessageId.
Legacy subagent children route through createSubagent (parent lineage kept);
an incomplete spawn identity fails the file instead of flattening the child
into a top-level session.
- **P2 resume gate**: readHeaderSnapshot/readMessagesSnapshot now await the
lazy import, so 'maka --resume <legacy-id>' no longer misses pre-cutover
sessions on the first post-upgrade run.
- **P2 observability**: ensureLegacyImported retains the result and logs
failures/imported counts instead of swallowing them; LegacySessionImportResult
now splits skipped into existing vs collision.
- **P2 steady-state cost**: the idempotency probe now runs BEFORE the file
read, so every launch skips known ids without touching their transcripts.
- **P2 torn tail**: an incomplete final line (interrupted append) is skipped
like the pre-#1994 strict reader, instead of failing the whole file.
Tests: 11/11 in legacy-session-import (added lazy-list, resume, torn-tail,
marker, subagent fail-closed, field-preservation cases); session-store +
sqlite-session-metadata-store + foreign-session-store 70/70; full storage
suite 702 pass, 2 pre-existing env failures (dugite git binary + root
tsconfig load) verified unrelated via stash.
* chore: allow-list session-store.ts for legacy import diagnostics
check-console.mjs flagged the new console.error/warn/info sites in
session-store.ts (legacy JSONL import outcome diagnostics) as unlisted.
Same pattern as the existing automation-store.ts allow-list entry —
best-effort import diagnostics, no credentials or provider payloads.
* chore: retry CI — test_headless 'settles background child sessions at the task-run deadline' flaked on the previous run (identical headless code passed two runs ago; local 30/30 green; no headless files touched by this PR)
* fix(storage): refactor legacy session import onto a single-transaction store API
Addresses #2263 review round 3: collapse the importer's probe -> create ->
append -> update choreography (three transactions, constant fingerprint,
fidelity patch, in-memory latch, resume gate) into one store-level
importSession primitive.
- sqlite-session-metadata-store: importSession(header, messages, projection)
writes the header row (with historical timestamps/flags) and all messages
in one transaction. Idempotent by primary key (INSERT OR IGNORE), so
concurrent first launches converge on one winner with no create claims;
tombstoned ids are never resurrected; a failure mid-transaction rolls
back, so a partial session can never persist (closes the crash-window P1).
- legacy-session-import: read -> decode -> normalizeSessionHeader (pre-write
validation) -> one importSession call. Subagent children now import under
their own legacy id with lineage preserved instead of a fresh UUID
(fixes the phantom-session P1). Torn-tail tolerance tightened to the
pre-#1994 strict-reader semantics: only a final line of a file with no
trailing newline whose parse failure is an unclosed bracket is skipped;
truncated lines ending in a newline and garbage tails fail the file.
- session-store: memoized import latch moves into ensureReady(), which every
public method already awaits, so desktop/CLI/headless/--resume are all
covered with zero per-caller wiring; appendMessages and closeAfterReady
now await ensureReady() (pre-existing gaps). Import diagnostics are kept
observable through the existing console allow-list.
- tests: payload pins (header model/status, deepEqual messages[0]),
concurrent double-import, id collision, header-only, absent sessions/,
empty file, garbage tail, truncated-with-newline, whole-run failure
containment, and subagent legacy-id round-trip; 19/19 legacy import tests,
full storage suite 713 pass (1 pre-existing dugite-binary env failure).
* fix(storage): probe legacy session ids before reading transcripts
Restores the probe-before-read steady-state cost from review round 2 on the
single-transaction design: the importer now asks the store whether a session
id already exists (live or tombstoned) before opening or parsing its file,
so every launch of an upgraded install pays a directory listing plus
per-id SQLite existence checks. importSession remains the idempotency
authority — a race between the probe and the write still converges on one
winner via the primary key. Adds hasSession to the store surface and a
test that corrupts the on-disk transcript between runs to pin that a
skipped id is never re-read.
---------
Co-authored-by: cat0825 <cat0825@users.noreply.github.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@Astro-Han
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(scripts): drop session-bundle imports deleted with the JSONL session tree - #2029

Closed
Astro-Han wants to merge 1 commit into
mainfrom
fix/measure-session-bundle-stale-imports
Closed

fix(scripts): drop session-bundle imports deleted with the JSONL session tree#2029
Astro-Han wants to merge 1 commit into
mainfrom
fix/measure-session-bundle-stale-imports

Conversation

@Astro-Han

@Astro-HanAstro-Han commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Summary

scripts/measure-session-bundle.mjs fails to load on main, taking the whole extended script suite down with it:

SyntaxError: The requested module '@maka/storage' does not provide an export
named 'SESSION_BUNDLE_PORTABLE_SESSION_DIRECTORIES'

#1994 made SQLite the sole operational authority and removed the JSONL session tree along with the two constants that classified it — SESSION_BUNDLE_PORTABLE_SESSION_DIRECTORIES and SESSION_BUNDLE_PORTABLE_SESSION_FILES — but the script kept importing them.

Drop the two imports and the topLevel === 'sessions' branch they fed. That branch was already unreachable: sessions is no longer in SESSION_BUNDLE_STATE_ENTRIES, so the top-level portability check rejects such an entry before it can be reached. Net effect is 26 deleted lines and one rewritten import.

Known remaining gap

This restores module loading, not the harness's runtime correctness. findSessionId still requires sessions/<id>/session.jsonl, which no export has produced since #1994, so an actual measurement run would fail on its own. Deciding whether to port the harness to the operational database or retire it needs a storage-bundle judgement call, so it is tracked separately rather than guessed at here.

Verification

  • node --test scripts/measure-session-bundle.test.mjs — 2 passed, 0 failed (fails to even load on main).
  • npm run test:scripts:extended — 15 passed, 0 failed.
  • npm run lint, npm run format:check — clean.

Why main is green with this broken

main's CI never reaches the script. In the latest main run, test_workspaces succeeds with the step itself skipped:

skipped Run fast script tests
skipped Run extended script tests
success Run affected standard workspace tests

ci.yml selects surfaces from a two-dot diff between pull_request.base.sha and pull_request.head.sha. A push to main diffs only that merge, so scriptMode stays none and the extended step never runs — regardless of the script being broken.

It surfaced on #2028 only because that branch was five commits behind main, so the two-dot diff also carried the inverse of those commits, including the root package.json change from a7d17e5bd. package.json is in FULL_SUITE_FILES (scripts/ci-test-plan.mjs:11), which forces scriptMode: 'full' and runs the extended scripts. Rebasing #2028 onto current main turns it green again without touching this bug.

Confirmed independently by running the test on a clean detached origin/main checkout, where it fails identically. So the breakage is real on main and merely invisible to main's own CI path.

…ion tree
#1994 made SQLite the sole operational authority and removed
SESSION_BUNDLE_PORTABLE_SESSION_DIRECTORIES and
SESSION_BUNDLE_PORTABLE_SESSION_FILES along with the JSONL session tree
they classified, but scripts/measure-session-bundle.mjs still imported
them. The module failed to load, so the whole test file errored out.
Drop the two imports and the classification branch they fed. With
'sessions' no longer in SESSION_BUNDLE_STATE_ENTRIES, that branch was
already unreachable — the top-level portability check rejects the entry
first.
This restores module loading and the extended script suite. It does not
revive the harness's runtime path: findSessionId still requires
sessions/<id>/session.jsonl, which no export produces since #1994.
@Astro-Han

Copy link
Copy Markdown
ContributorAuthor

Superseded by #1920, which landed the same fix (fix(scripts): keep legacy bundle measurement importable) while this was open. Verified on current main: node --test scripts/measure-session-bundle.test.mjs passes. Closing as duplicate.

@Astro-Han
Astro-Han deleted the fix/measure-session-bundle-stale-imports branch August 3, 2026 15:21
Astro-Han pushed a commit that referenced this pull request Aug 6, 2026
) (#2263)
* fix(storage): import legacy JSONL session transcripts into SQLite (#2260)
After the JSONL->SQLite cutover (#1994, #2029), sessions created before
the switch stayed on disk as sessions/<id>/session.jsonl but never
appeared in the UI: the new storage layer only reads SQLite and there was
no migration path (issue #2260).
Add a one-time importer (importLegacySessionsOnce) that scans the legacy
sessions directory, decodes each schemaVersion:1 transcript with the
pre-#1994 compatibility rules (backend remapping, missing-field defaults),
creates the session under its original id via the idempotent
createStableSession path, appends the decoded messages, and restores the
original lifecycle timestamps and flags.
Design:
- Idempotency key is the session id itself (probeStableSessionCreate),
so re-runs and concurrent first launches converge without duplicates.
- Per-file atomicity: a transcript imports fully or is skipped and
reported; corrupt records are never laundered into the authoritative
store. Failures never block startup or other files.
- Legacy files are retained as migration evidence.
- Wired into createSessionStore: list/listCatalogPage/listHeaders await
the lazy import so upgraded installs see their pre-cutover sessions.
* fix(storage): drop console diagnostic from legacy import wiring
The import result logging used console.error, which the repository
check-console audit rejects for new call sites. Remove the log entirely
and harden the lazy import to swallow unexpected errors (best-effort
semantics): a legacy-import failure must never block session listing.
* fix(storage): address #2260 review — validate before write, probe first, marker/subagent fail-closed, resume gate, observable results
Addresses Astro-Han's review (P1 + P2s):
- **P1 per-file atomicity**: the decoded header AND the post-create header
patch are now validated through normalizeSessionHeader BEFORE any store
write. Previously updateHeader (the third of three transactions) could
throw after create+append committed, leaving a permanent partial session
that later probes would report as skipped forever.
- **P2 marker laundering**: a session_transcript marker file with no backing
SQLite row (restored backup, copied sessions/, reset DB) now fails closed
instead of being fabricated into a fake session — matching the pre-#1994
reader's contract.
- **P2 legacy field loss**: decodeLegacySessionHeader preserves
subagentParent/Runtime/Spawn/Workspace, thinkingLevel, lastReadMessageId.
Legacy subagent children route through createSubagent (parent lineage kept);
an incomplete spawn identity fails the file instead of flattening the child
into a top-level session.
- **P2 resume gate**: readHeaderSnapshot/readMessagesSnapshot now await the
lazy import, so 'maka --resume <legacy-id>' no longer misses pre-cutover
sessions on the first post-upgrade run.
- **P2 observability**: ensureLegacyImported retains the result and logs
failures/imported counts instead of swallowing them; LegacySessionImportResult
now splits skipped into existing vs collision.
- **P2 steady-state cost**: the idempotency probe now runs BEFORE the file
read, so every launch skips known ids without touching their transcripts.
- **P2 torn tail**: an incomplete final line (interrupted append) is skipped
like the pre-#1994 strict reader, instead of failing the whole file.
Tests: 11/11 in legacy-session-import (added lazy-list, resume, torn-tail,
marker, subagent fail-closed, field-preservation cases); session-store +
sqlite-session-metadata-store + foreign-session-store 70/70; full storage
suite 702 pass, 2 pre-existing env failures (dugite git binary + root
tsconfig load) verified unrelated via stash.
* chore: allow-list session-store.ts for legacy import diagnostics
check-console.mjs flagged the new console.error/warn/info sites in
session-store.ts (legacy JSONL import outcome diagnostics) as unlisted.
Same pattern as the existing automation-store.ts allow-list entry —
best-effort import diagnostics, no credentials or provider payloads.
* chore: retry CI — test_headless 'settles background child sessions at the task-run deadline' flaked on the previous run (identical headless code passed two runs ago; local 30/30 green; no headless files touched by this PR)
* fix(storage): refactor legacy session import onto a single-transaction store API
Addresses #2263 review round 3: collapse the importer's probe -> create ->
append -> update choreography (three transactions, constant fingerprint,
fidelity patch, in-memory latch, resume gate) into one store-level
importSession primitive.
- sqlite-session-metadata-store: importSession(header, messages, projection)
writes the header row (with historical timestamps/flags) and all messages
in one transaction. Idempotent by primary key (INSERT OR IGNORE), so
concurrent first launches converge on one winner with no create claims;
tombstoned ids are never resurrected; a failure mid-transaction rolls
back, so a partial session can never persist (closes the crash-window P1).
- legacy-session-import: read -> decode -> normalizeSessionHeader (pre-write
validation) -> one importSession call. Subagent children now import under
their own legacy id with lineage preserved instead of a fresh UUID
(fixes the phantom-session P1). Torn-tail tolerance tightened to the
pre-#1994 strict-reader semantics: only a final line of a file with no
trailing newline whose parse failure is an unclosed bracket is skipped;
truncated lines ending in a newline and garbage tails fail the file.
- session-store: memoized import latch moves into ensureReady(), which every
public method already awaits, so desktop/CLI/headless/--resume are all
covered with zero per-caller wiring; appendMessages and closeAfterReady
now await ensureReady() (pre-existing gaps). Import diagnostics are kept
observable through the existing console allow-list.
- tests: payload pins (header model/status, deepEqual messages[0]),
concurrent double-import, id collision, header-only, absent sessions/,
empty file, garbage tail, truncated-with-newline, whole-run failure
containment, and subagent legacy-id round-trip; 19/19 legacy import tests,
full storage suite 713 pass (1 pre-existing dugite-binary env failure).
* fix(storage): probe legacy session ids before reading transcripts
Restores the probe-before-read steady-state cost from review round 2 on the
single-transaction design: the importer now asks the store whether a session
id already exists (live or tombstoned) before opening or parsing its file,
so every launch of an upgraded install pays a directory listing plus
per-id SQLite existence checks. importSession remains the idempotency
authority — a race between the probe and the write still converges on one
winner via the primary key. Adds hasSession to the store surface and a
test that corrupts the on-disk transcript between runs to pin that a
skipped id is never re-read.
---------
Co-authored-by: cat0825 <cat0825@users.noreply.github.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@Astro-Han
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(scripts): drop session-bundle imports deleted with the JSONL session tree - #2029

Closed
Astro-Han wants to merge 1 commit into
mainfrom
fix/measure-session-bundle-stale-imports
Closed

fix(scripts): drop session-bundle imports deleted with the JSONL session tree#2029
Astro-Han wants to merge 1 commit into
mainfrom
fix/measure-session-bundle-stale-imports

Conversation

@Astro-Han

@Astro-HanAstro-Han commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Summary

scripts/measure-session-bundle.mjs fails to load on main, taking the whole extended script suite down with it:

SyntaxError: The requested module '@maka/storage' does not provide an export
named 'SESSION_BUNDLE_PORTABLE_SESSION_DIRECTORIES'

#1994 made SQLite the sole operational authority and removed the JSONL session tree along with the two constants that classified it — SESSION_BUNDLE_PORTABLE_SESSION_DIRECTORIES and SESSION_BUNDLE_PORTABLE_SESSION_FILES — but the script kept importing them.

Drop the two imports and the topLevel === 'sessions' branch they fed. That branch was already unreachable: sessions is no longer in SESSION_BUNDLE_STATE_ENTRIES, so the top-level portability check rejects such an entry before it can be reached. Net effect is 26 deleted lines and one rewritten import.

Known remaining gap

This restores module loading, not the harness's runtime correctness. findSessionId still requires sessions/<id>/session.jsonl, which no export has produced since #1994, so an actual measurement run would fail on its own. Deciding whether to port the harness to the operational database or retire it needs a storage-bundle judgement call, so it is tracked separately rather than guessed at here.

Verification

  • node --test scripts/measure-session-bundle.test.mjs — 2 passed, 0 failed (fails to even load on main).
  • npm run test:scripts:extended — 15 passed, 0 failed.
  • npm run lint, npm run format:check — clean.

Why main is green with this broken

main's CI never reaches the script. In the latest main run, test_workspaces succeeds with the step itself skipped:

skipped Run fast script tests
skipped Run extended script tests
success Run affected standard workspace tests

ci.yml selects surfaces from a two-dot diff between pull_request.base.sha and pull_request.head.sha. A push to main diffs only that merge, so scriptMode stays none and the extended step never runs — regardless of the script being broken.

It surfaced on #2028 only because that branch was five commits behind main, so the two-dot diff also carried the inverse of those commits, including the root package.json change from a7d17e5bd. package.json is in FULL_SUITE_FILES (scripts/ci-test-plan.mjs:11), which forces scriptMode: 'full' and runs the extended scripts. Rebasing #2028 onto current main turns it green again without touching this bug.

Confirmed independently by running the test on a clean detached origin/main checkout, where it fails identically. So the breakage is real on main and merely invisible to main's own CI path.

…ion tree
#1994 made SQLite the sole operational authority and removed
SESSION_BUNDLE_PORTABLE_SESSION_DIRECTORIES and
SESSION_BUNDLE_PORTABLE_SESSION_FILES along with the JSONL session tree
they classified, but scripts/measure-session-bundle.mjs still imported
them. The module failed to load, so the whole test file errored out.
Drop the two imports and the classification branch they fed. With
'sessions' no longer in SESSION_BUNDLE_STATE_ENTRIES, that branch was
already unreachable — the top-level portability check rejects the entry
first.
This restores module loading and the extended script suite. It does not
revive the harness's runtime path: findSessionId still requires
sessions/<id>/session.jsonl, which no export produces since #1994.
@Astro-Han

Copy link
Copy Markdown
ContributorAuthor

Superseded by #1920, which landed the same fix (fix(scripts): keep legacy bundle measurement importable) while this was open. Verified on current main: node --test scripts/measure-session-bundle.test.mjs passes. Closing as duplicate.

@Astro-Han
Astro-Han deleted the fix/measure-session-bundle-stale-imports branch August 3, 2026 15:21
Astro-Han pushed a commit that referenced this pull request Aug 6, 2026
) (#2263)
* fix(storage): import legacy JSONL session transcripts into SQLite (#2260)
After the JSONL->SQLite cutover (#1994, #2029), sessions created before
the switch stayed on disk as sessions/<id>/session.jsonl but never
appeared in the UI: the new storage layer only reads SQLite and there was
no migration path (issue #2260).
Add a one-time importer (importLegacySessionsOnce) that scans the legacy
sessions directory, decodes each schemaVersion:1 transcript with the
pre-#1994 compatibility rules (backend remapping, missing-field defaults),
creates the session under its original id via the idempotent
createStableSession path, appends the decoded messages, and restores the
original lifecycle timestamps and flags.
Design:
- Idempotency key is the session id itself (probeStableSessionCreate),
so re-runs and concurrent first launches converge without duplicates.
- Per-file atomicity: a transcript imports fully or is skipped and
reported; corrupt records are never laundered into the authoritative
store. Failures never block startup or other files.
- Legacy files are retained as migration evidence.
- Wired into createSessionStore: list/listCatalogPage/listHeaders await
the lazy import so upgraded installs see their pre-cutover sessions.
* fix(storage): drop console diagnostic from legacy import wiring
The import result logging used console.error, which the repository
check-console audit rejects for new call sites. Remove the log entirely
and harden the lazy import to swallow unexpected errors (best-effort
semantics): a legacy-import failure must never block session listing.
* fix(storage): address #2260 review — validate before write, probe first, marker/subagent fail-closed, resume gate, observable results
Addresses Astro-Han's review (P1 + P2s):
- **P1 per-file atomicity**: the decoded header AND the post-create header
patch are now validated through normalizeSessionHeader BEFORE any store
write. Previously updateHeader (the third of three transactions) could
throw after create+append committed, leaving a permanent partial session
that later probes would report as skipped forever.
- **P2 marker laundering**: a session_transcript marker file with no backing
SQLite row (restored backup, copied sessions/, reset DB) now fails closed
instead of being fabricated into a fake session — matching the pre-#1994
reader's contract.
- **P2 legacy field loss**: decodeLegacySessionHeader preserves
subagentParent/Runtime/Spawn/Workspace, thinkingLevel, lastReadMessageId.
Legacy subagent children route through createSubagent (parent lineage kept);
an incomplete spawn identity fails the file instead of flattening the child
into a top-level session.
- **P2 resume gate**: readHeaderSnapshot/readMessagesSnapshot now await the
lazy import, so 'maka --resume <legacy-id>' no longer misses pre-cutover
sessions on the first post-upgrade run.
- **P2 observability**: ensureLegacyImported retains the result and logs
failures/imported counts instead of swallowing them; LegacySessionImportResult
now splits skipped into existing vs collision.
- **P2 steady-state cost**: the idempotency probe now runs BEFORE the file
read, so every launch skips known ids without touching their transcripts.
- **P2 torn tail**: an incomplete final line (interrupted append) is skipped
like the pre-#1994 strict reader, instead of failing the whole file.
Tests: 11/11 in legacy-session-import (added lazy-list, resume, torn-tail,
marker, subagent fail-closed, field-preservation cases); session-store +
sqlite-session-metadata-store + foreign-session-store 70/70; full storage
suite 702 pass, 2 pre-existing env failures (dugite git binary + root
tsconfig load) verified unrelated via stash.
* chore: allow-list session-store.ts for legacy import diagnostics
check-console.mjs flagged the new console.error/warn/info sites in
session-store.ts (legacy JSONL import outcome diagnostics) as unlisted.
Same pattern as the existing automation-store.ts allow-list entry —
best-effort import diagnostics, no credentials or provider payloads.
* chore: retry CI — test_headless 'settles background child sessions at the task-run deadline' flaked on the previous run (identical headless code passed two runs ago; local 30/30 green; no headless files touched by this PR)
* fix(storage): refactor legacy session import onto a single-transaction store API
Addresses #2263 review round 3: collapse the importer's probe -> create ->
append -> update choreography (three transactions, constant fingerprint,
fidelity patch, in-memory latch, resume gate) into one store-level
importSession primitive.
- sqlite-session-metadata-store: importSession(header, messages, projection)
writes the header row (with historical timestamps/flags) and all messages
in one transaction. Idempotent by primary key (INSERT OR IGNORE), so
concurrent first launches converge on one winner with no create claims;
tombstoned ids are never resurrected; a failure mid-transaction rolls
back, so a partial session can never persist (closes the crash-window P1).
- legacy-session-import: read -> decode -> normalizeSessionHeader (pre-write
validation) -> one importSession call. Subagent children now import under
their own legacy id with lineage preserved instead of a fresh UUID
(fixes the phantom-session P1). Torn-tail tolerance tightened to the
pre-#1994 strict-reader semantics: only a final line of a file with no
trailing newline whose parse failure is an unclosed bracket is skipped;
truncated lines ending in a newline and garbage tails fail the file.
- session-store: memoized import latch moves into ensureReady(), which every
public method already awaits, so desktop/CLI/headless/--resume are all
covered with zero per-caller wiring; appendMessages and closeAfterReady
now await ensureReady() (pre-existing gaps). Import diagnostics are kept
observable through the existing console allow-list.
- tests: payload pins (header model/status, deepEqual messages[0]),
concurrent double-import, id collision, header-only, absent sessions/,
empty file, garbage tail, truncated-with-newline, whole-run failure
containment, and subagent legacy-id round-trip; 19/19 legacy import tests,
full storage suite 713 pass (1 pre-existing dugite-binary env failure).
* fix(storage): probe legacy session ids before reading transcripts
Restores the probe-before-read steady-state cost from review round 2 on the
single-transaction design: the importer now asks the store whether a session
id already exists (live or tombstoned) before opening or parsing its file,
so every launch of an upgraded install pays a directory listing plus
per-id SQLite existence checks. importSession remains the idempotency
authority — a race between the probe and the write still converges on one
winner via the primary key. Adds hasSession to the store surface and a
test that corrupts the on-disk transcript between runs to pin that a
skipped id is never re-read.
---------
Co-authored-by: cat0825 <cat0825@users.noreply.github.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@Astro-Han
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix(scripts): drop session-bundle imports deleted with the JSONL session tree - #2029

Closed
Astro-Han wants to merge 1 commit into
mainfrom
fix/measure-session-bundle-stale-imports
Closed

fix(scripts): drop session-bundle imports deleted with the JSONL session tree#2029
Astro-Han wants to merge 1 commit into
mainfrom
fix/measure-session-bundle-stale-imports

Conversation

@Astro-Han

@Astro-HanAstro-Han commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Summary

scripts/measure-session-bundle.mjs fails to load on main, taking the whole extended script suite down with it:

SyntaxError: The requested module '@maka/storage' does not provide an export
named 'SESSION_BUNDLE_PORTABLE_SESSION_DIRECTORIES'

#1994 made SQLite the sole operational authority and removed the JSONL session tree along with the two constants that classified it — SESSION_BUNDLE_PORTABLE_SESSION_DIRECTORIES and SESSION_BUNDLE_PORTABLE_SESSION_FILES — but the script kept importing them.

Drop the two imports and the topLevel === 'sessions' branch they fed. That branch was already unreachable: sessions is no longer in SESSION_BUNDLE_STATE_ENTRIES, so the top-level portability check rejects such an entry before it can be reached. Net effect is 26 deleted lines and one rewritten import.

Known remaining gap

This restores module loading, not the harness's runtime correctness. findSessionId still requires sessions/<id>/session.jsonl, which no export has produced since #1994, so an actual measurement run would fail on its own. Deciding whether to port the harness to the operational database or retire it needs a storage-bundle judgement call, so it is tracked separately rather than guessed at here.

Verification

  • node --test scripts/measure-session-bundle.test.mjs — 2 passed, 0 failed (fails to even load on main).
  • npm run test:scripts:extended — 15 passed, 0 failed.
  • npm run lint, npm run format:check — clean.

Why main is green with this broken

main's CI never reaches the script. In the latest main run, test_workspaces succeeds with the step itself skipped:

skipped Run fast script tests
skipped Run extended script tests
success Run affected standard workspace tests

ci.yml selects surfaces from a two-dot diff between pull_request.base.sha and pull_request.head.sha. A push to main diffs only that merge, so scriptMode stays none and the extended step never runs — regardless of the script being broken.

It surfaced on #2028 only because that branch was five commits behind main, so the two-dot diff also carried the inverse of those commits, including the root package.json change from a7d17e5bd. package.json is in FULL_SUITE_FILES (scripts/ci-test-plan.mjs:11), which forces scriptMode: 'full' and runs the extended scripts. Rebasing #2028 onto current main turns it green again without touching this bug.

Confirmed independently by running the test on a clean detached origin/main checkout, where it fails identically. So the breakage is real on main and merely invisible to main's own CI path.

…ion tree
#1994 made SQLite the sole operational authority and removed
SESSION_BUNDLE_PORTABLE_SESSION_DIRECTORIES and
SESSION_BUNDLE_PORTABLE_SESSION_FILES along with the JSONL session tree
they classified, but scripts/measure-session-bundle.mjs still imported
them. The module failed to load, so the whole test file errored out.
Drop the two imports and the classification branch they fed. With
'sessions' no longer in SESSION_BUNDLE_STATE_ENTRIES, that branch was
already unreachable — the top-level portability check rejects the entry
first.
This restores module loading and the extended script suite. It does not
revive the harness's runtime path: findSessionId still requires
sessions/<id>/session.jsonl, which no export produces since #1994.
@Astro-Han

Copy link
Copy Markdown
ContributorAuthor

Superseded by #1920, which landed the same fix (fix(scripts): keep legacy bundle measurement importable) while this was open. Verified on current main: node --test scripts/measure-session-bundle.test.mjs passes. Closing as duplicate.

@Astro-Han
Astro-Han deleted the fix/measure-session-bundle-stale-imports branch August 3, 2026 15:21
Astro-Han pushed a commit that referenced this pull request Aug 6, 2026
) (#2263)
* fix(storage): import legacy JSONL session transcripts into SQLite (#2260)
After the JSONL->SQLite cutover (#1994, #2029), sessions created before
the switch stayed on disk as sessions/<id>/session.jsonl but never
appeared in the UI: the new storage layer only reads SQLite and there was
no migration path (issue #2260).
Add a one-time importer (importLegacySessionsOnce) that scans the legacy
sessions directory, decodes each schemaVersion:1 transcript with the
pre-#1994 compatibility rules (backend remapping, missing-field defaults),
creates the session under its original id via the idempotent
createStableSession path, appends the decoded messages, and restores the
original lifecycle timestamps and flags.
Design:
- Idempotency key is the session id itself (probeStableSessionCreate),
so re-runs and concurrent first launches converge without duplicates.
- Per-file atomicity: a transcript imports fully or is skipped and
reported; corrupt records are never laundered into the authoritative
store. Failures never block startup or other files.
- Legacy files are retained as migration evidence.
- Wired into createSessionStore: list/listCatalogPage/listHeaders await
the lazy import so upgraded installs see their pre-cutover sessions.
* fix(storage): drop console diagnostic from legacy import wiring
The import result logging used console.error, which the repository
check-console audit rejects for new call sites. Remove the log entirely
and harden the lazy import to swallow unexpected errors (best-effort
semantics): a legacy-import failure must never block session listing.
* fix(storage): address #2260 review — validate before write, probe first, marker/subagent fail-closed, resume gate, observable results
Addresses Astro-Han's review (P1 + P2s):
- **P1 per-file atomicity**: the decoded header AND the post-create header
patch are now validated through normalizeSessionHeader BEFORE any store
write. Previously updateHeader (the third of three transactions) could
throw after create+append committed, leaving a permanent partial session
that later probes would report as skipped forever.
- **P2 marker laundering**: a session_transcript marker file with no backing
SQLite row (restored backup, copied sessions/, reset DB) now fails closed
instead of being fabricated into a fake session — matching the pre-#1994
reader's contract.
- **P2 legacy field loss**: decodeLegacySessionHeader preserves
subagentParent/Runtime/Spawn/Workspace, thinkingLevel, lastReadMessageId.
Legacy subagent children route through createSubagent (parent lineage kept);
an incomplete spawn identity fails the file instead of flattening the child
into a top-level session.
- **P2 resume gate**: readHeaderSnapshot/readMessagesSnapshot now await the
lazy import, so 'maka --resume <legacy-id>' no longer misses pre-cutover
sessions on the first post-upgrade run.
- **P2 observability**: ensureLegacyImported retains the result and logs
failures/imported counts instead of swallowing them; LegacySessionImportResult
now splits skipped into existing vs collision.
- **P2 steady-state cost**: the idempotency probe now runs BEFORE the file
read, so every launch skips known ids without touching their transcripts.
- **P2 torn tail**: an incomplete final line (interrupted append) is skipped
like the pre-#1994 strict reader, instead of failing the whole file.
Tests: 11/11 in legacy-session-import (added lazy-list, resume, torn-tail,
marker, subagent fail-closed, field-preservation cases); session-store +
sqlite-session-metadata-store + foreign-session-store 70/70; full storage
suite 702 pass, 2 pre-existing env failures (dugite git binary + root
tsconfig load) verified unrelated via stash.
* chore: allow-list session-store.ts for legacy import diagnostics
check-console.mjs flagged the new console.error/warn/info sites in
session-store.ts (legacy JSONL import outcome diagnostics) as unlisted.
Same pattern as the existing automation-store.ts allow-list entry —
best-effort import diagnostics, no credentials or provider payloads.
* chore: retry CI — test_headless 'settles background child sessions at the task-run deadline' flaked on the previous run (identical headless code passed two runs ago; local 30/30 green; no headless files touched by this PR)
* fix(storage): refactor legacy session import onto a single-transaction store API
Addresses #2263 review round 3: collapse the importer's probe -> create ->
append -> update choreography (three transactions, constant fingerprint,
fidelity patch, in-memory latch, resume gate) into one store-level
importSession primitive.
- sqlite-session-metadata-store: importSession(header, messages, projection)
writes the header row (with historical timestamps/flags) and all messages
in one transaction. Idempotent by primary key (INSERT OR IGNORE), so
concurrent first launches converge on one winner with no create claims;
tombstoned ids are never resurrected; a failure mid-transaction rolls
back, so a partial session can never persist (closes the crash-window P1).
- legacy-session-import: read -> decode -> normalizeSessionHeader (pre-write
validation) -> one importSession call. Subagent children now import under
their own legacy id with lineage preserved instead of a fresh UUID
(fixes the phantom-session P1). Torn-tail tolerance tightened to the
pre-#1994 strict-reader semantics: only a final line of a file with no
trailing newline whose parse failure is an unclosed bracket is skipped;
truncated lines ending in a newline and garbage tails fail the file.
- session-store: memoized import latch moves into ensureReady(), which every
public method already awaits, so desktop/CLI/headless/--resume are all
covered with zero per-caller wiring; appendMessages and closeAfterReady
now await ensureReady() (pre-existing gaps). Import diagnostics are kept
observable through the existing console allow-list.
- tests: payload pins (header model/status, deepEqual messages[0]),
concurrent double-import, id collision, header-only, absent sessions/,
empty file, garbage tail, truncated-with-newline, whole-run failure
containment, and subagent legacy-id round-trip; 19/19 legacy import tests,
full storage suite 713 pass (1 pre-existing dugite-binary env failure).
* fix(storage): probe legacy session ids before reading transcripts
Restores the probe-before-read steady-state cost from review round 2 on the
single-transaction design: the importer now asks the store whether a session
id already exists (live or tombstoned) before opening or parsing its file,
so every launch of an upgraded install pays a directory listing plus
per-id SQLite existence checks. importSession remains the idempotency
authority — a race between the probe and the write still converges on one
winner via the primary key. Adds hasSession to the store surface and a
test that corrupts the on-disk transcript between runs to pin that a
skipped id is never re-read.
---------
Co-authored-by: cat0825 <cat0825@users.noreply.github.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@Astro-Han
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(scripts): drop session-bundle imports deleted with the JSONL session tree - #2029

Closed
Astro-Han wants to merge 1 commit into
mainfrom
fix/measure-session-bundle-stale-imports
Closed

fix(scripts): drop session-bundle imports deleted with the JSONL session tree#2029
Astro-Han wants to merge 1 commit into
mainfrom
fix/measure-session-bundle-stale-imports

Conversation

@Astro-Han

@Astro-HanAstro-Han commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Summary

scripts/measure-session-bundle.mjs fails to load on main, taking the whole extended script suite down with it:

SyntaxError: The requested module '@maka/storage' does not provide an export
named 'SESSION_BUNDLE_PORTABLE_SESSION_DIRECTORIES'

#1994 made SQLite the sole operational authority and removed the JSONL session tree along with the two constants that classified it — SESSION_BUNDLE_PORTABLE_SESSION_DIRECTORIES and SESSION_BUNDLE_PORTABLE_SESSION_FILES — but the script kept importing them.

Drop the two imports and the topLevel === 'sessions' branch they fed. That branch was already unreachable: sessions is no longer in SESSION_BUNDLE_STATE_ENTRIES, so the top-level portability check rejects such an entry before it can be reached. Net effect is 26 deleted lines and one rewritten import.

Known remaining gap

This restores module loading, not the harness's runtime correctness. findSessionId still requires sessions/<id>/session.jsonl, which no export has produced since #1994, so an actual measurement run would fail on its own. Deciding whether to port the harness to the operational database or retire it needs a storage-bundle judgement call, so it is tracked separately rather than guessed at here.

Verification

  • node --test scripts/measure-session-bundle.test.mjs — 2 passed, 0 failed (fails to even load on main).
  • npm run test:scripts:extended — 15 passed, 0 failed.
  • npm run lint, npm run format:check — clean.

Why main is green with this broken

main's CI never reaches the script. In the latest main run, test_workspaces succeeds with the step itself skipped:

skipped Run fast script tests
skipped Run extended script tests
success Run affected standard workspace tests

ci.yml selects surfaces from a two-dot diff between pull_request.base.sha and pull_request.head.sha. A push to main diffs only that merge, so scriptMode stays none and the extended step never runs — regardless of the script being broken.

It surfaced on #2028 only because that branch was five commits behind main, so the two-dot diff also carried the inverse of those commits, including the root package.json change from a7d17e5bd. package.json is in FULL_SUITE_FILES (scripts/ci-test-plan.mjs:11), which forces scriptMode: 'full' and runs the extended scripts. Rebasing #2028 onto current main turns it green again without touching this bug.

Confirmed independently by running the test on a clean detached origin/main checkout, where it fails identically. So the breakage is real on main and merely invisible to main's own CI path.

…ion tree
#1994 made SQLite the sole operational authority and removed
SESSION_BUNDLE_PORTABLE_SESSION_DIRECTORIES and
SESSION_BUNDLE_PORTABLE_SESSION_FILES along with the JSONL session tree
they classified, but scripts/measure-session-bundle.mjs still imported
them. The module failed to load, so the whole test file errored out.
Drop the two imports and the classification branch they fed. With
'sessions' no longer in SESSION_BUNDLE_STATE_ENTRIES, that branch was
already unreachable — the top-level portability check rejects the entry
first.
This restores module loading and the extended script suite. It does not
revive the harness's runtime path: findSessionId still requires
sessions/<id>/session.jsonl, which no export produces since #1994.
@Astro-Han

Copy link
Copy Markdown
ContributorAuthor

Superseded by #1920, which landed the same fix (fix(scripts): keep legacy bundle measurement importable) while this was open. Verified on current main: node --test scripts/measure-session-bundle.test.mjs passes. Closing as duplicate.

@Astro-Han
Astro-Han deleted the fix/measure-session-bundle-stale-imports branch August 3, 2026 15:21
Astro-Han pushed a commit that referenced this pull request Aug 6, 2026
) (#2263)
* fix(storage): import legacy JSONL session transcripts into SQLite (#2260)
After the JSONL->SQLite cutover (#1994, #2029), sessions created before
the switch stayed on disk as sessions/<id>/session.jsonl but never
appeared in the UI: the new storage layer only reads SQLite and there was
no migration path (issue #2260).
Add a one-time importer (importLegacySessionsOnce) that scans the legacy
sessions directory, decodes each schemaVersion:1 transcript with the
pre-#1994 compatibility rules (backend remapping, missing-field defaults),
creates the session under its original id via the idempotent
createStableSession path, appends the decoded messages, and restores the
original lifecycle timestamps and flags.
Design:
- Idempotency key is the session id itself (probeStableSessionCreate),
so re-runs and concurrent first launches converge without duplicates.
- Per-file atomicity: a transcript imports fully or is skipped and
reported; corrupt records are never laundered into the authoritative
store. Failures never block startup or other files.
- Legacy files are retained as migration evidence.
- Wired into createSessionStore: list/listCatalogPage/listHeaders await
the lazy import so upgraded installs see their pre-cutover sessions.
* fix(storage): drop console diagnostic from legacy import wiring
The import result logging used console.error, which the repository
check-console audit rejects for new call sites. Remove the log entirely
and harden the lazy import to swallow unexpected errors (best-effort
semantics): a legacy-import failure must never block session listing.
* fix(storage): address #2260 review — validate before write, probe first, marker/subagent fail-closed, resume gate, observable results
Addresses Astro-Han's review (P1 + P2s):
- **P1 per-file atomicity**: the decoded header AND the post-create header
patch are now validated through normalizeSessionHeader BEFORE any store
write. Previously updateHeader (the third of three transactions) could
throw after create+append committed, leaving a permanent partial session
that later probes would report as skipped forever.
- **P2 marker laundering**: a session_transcript marker file with no backing
SQLite row (restored backup, copied sessions/, reset DB) now fails closed
instead of being fabricated into a fake session — matching the pre-#1994
reader's contract.
- **P2 legacy field loss**: decodeLegacySessionHeader preserves
subagentParent/Runtime/Spawn/Workspace, thinkingLevel, lastReadMessageId.
Legacy subagent children route through createSubagent (parent lineage kept);
an incomplete spawn identity fails the file instead of flattening the child
into a top-level session.
- **P2 resume gate**: readHeaderSnapshot/readMessagesSnapshot now await the
lazy import, so 'maka --resume <legacy-id>' no longer misses pre-cutover
sessions on the first post-upgrade run.
- **P2 observability**: ensureLegacyImported retains the result and logs
failures/imported counts instead of swallowing them; LegacySessionImportResult
now splits skipped into existing vs collision.
- **P2 steady-state cost**: the idempotency probe now runs BEFORE the file
read, so every launch skips known ids without touching their transcripts.
- **P2 torn tail**: an incomplete final line (interrupted append) is skipped
like the pre-#1994 strict reader, instead of failing the whole file.
Tests: 11/11 in legacy-session-import (added lazy-list, resume, torn-tail,
marker, subagent fail-closed, field-preservation cases); session-store +
sqlite-session-metadata-store + foreign-session-store 70/70; full storage
suite 702 pass, 2 pre-existing env failures (dugite git binary + root
tsconfig load) verified unrelated via stash.
* chore: allow-list session-store.ts for legacy import diagnostics
check-console.mjs flagged the new console.error/warn/info sites in
session-store.ts (legacy JSONL import outcome diagnostics) as unlisted.
Same pattern as the existing automation-store.ts allow-list entry —
best-effort import diagnostics, no credentials or provider payloads.
* chore: retry CI — test_headless 'settles background child sessions at the task-run deadline' flaked on the previous run (identical headless code passed two runs ago; local 30/30 green; no headless files touched by this PR)
* fix(storage): refactor legacy session import onto a single-transaction store API
Addresses #2263 review round 3: collapse the importer's probe -> create ->
append -> update choreography (three transactions, constant fingerprint,
fidelity patch, in-memory latch, resume gate) into one store-level
importSession primitive.
- sqlite-session-metadata-store: importSession(header, messages, projection)
writes the header row (with historical timestamps/flags) and all messages
in one transaction. Idempotent by primary key (INSERT OR IGNORE), so
concurrent first launches converge on one winner with no create claims;
tombstoned ids are never resurrected; a failure mid-transaction rolls
back, so a partial session can never persist (closes the crash-window P1).
- legacy-session-import: read -> decode -> normalizeSessionHeader (pre-write
validation) -> one importSession call. Subagent children now import under
their own legacy id with lineage preserved instead of a fresh UUID
(fixes the phantom-session P1). Torn-tail tolerance tightened to the
pre-#1994 strict-reader semantics: only a final line of a file with no
trailing newline whose parse failure is an unclosed bracket is skipped;
truncated lines ending in a newline and garbage tails fail the file.
- session-store: memoized import latch moves into ensureReady(), which every
public method already awaits, so desktop/CLI/headless/--resume are all
covered with zero per-caller wiring; appendMessages and closeAfterReady
now await ensureReady() (pre-existing gaps). Import diagnostics are kept
observable through the existing console allow-list.
- tests: payload pins (header model/status, deepEqual messages[0]),
concurrent double-import, id collision, header-only, absent sessions/,
empty file, garbage tail, truncated-with-newline, whole-run failure
containment, and subagent legacy-id round-trip; 19/19 legacy import tests,
full storage suite 713 pass (1 pre-existing dugite-binary env failure).
* fix(storage): probe legacy session ids before reading transcripts
Restores the probe-before-read steady-state cost from review round 2 on the
single-transaction design: the importer now asks the store whether a session
id already exists (live or tombstoned) before opening or parsing its file,
so every launch of an upgraded install pays a directory listing plus
per-id SQLite existence checks. importSession remains the idempotency
authority — a race between the probe and the write still converges on one
winner via the primary key. Adds hasSession to the store surface and a
test that corrupts the on-disk transcript between runs to pin that a
skipped id is never re-read.
---------
Co-authored-by: cat0825 <cat0825@users.noreply.github.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@Astro-Han
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(scripts): drop session-bundle imports deleted with the JSONL session tree - #2029

Closed
Astro-Han wants to merge 1 commit into
mainfrom
fix/measure-session-bundle-stale-imports
Closed

fix(scripts): drop session-bundle imports deleted with the JSONL session tree#2029
Astro-Han wants to merge 1 commit into
mainfrom
fix/measure-session-bundle-stale-imports

Conversation

@Astro-Han

@Astro-HanAstro-Han commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Summary

scripts/measure-session-bundle.mjs fails to load on main, taking the whole extended script suite down with it:

SyntaxError: The requested module '@maka/storage' does not provide an export
named 'SESSION_BUNDLE_PORTABLE_SESSION_DIRECTORIES'

#1994 made SQLite the sole operational authority and removed the JSONL session tree along with the two constants that classified it — SESSION_BUNDLE_PORTABLE_SESSION_DIRECTORIES and SESSION_BUNDLE_PORTABLE_SESSION_FILES — but the script kept importing them.

Drop the two imports and the topLevel === 'sessions' branch they fed. That branch was already unreachable: sessions is no longer in SESSION_BUNDLE_STATE_ENTRIES, so the top-level portability check rejects such an entry before it can be reached. Net effect is 26 deleted lines and one rewritten import.

Known remaining gap

This restores module loading, not the harness's runtime correctness. findSessionId still requires sessions/<id>/session.jsonl, which no export has produced since #1994, so an actual measurement run would fail on its own. Deciding whether to port the harness to the operational database or retire it needs a storage-bundle judgement call, so it is tracked separately rather than guessed at here.

Verification

  • node --test scripts/measure-session-bundle.test.mjs — 2 passed, 0 failed (fails to even load on main).
  • npm run test:scripts:extended — 15 passed, 0 failed.
  • npm run lint, npm run format:check — clean.

Why main is green with this broken

main's CI never reaches the script. In the latest main run, test_workspaces succeeds with the step itself skipped:

skipped Run fast script tests
skipped Run extended script tests
success Run affected standard workspace tests

ci.yml selects surfaces from a two-dot diff between pull_request.base.sha and pull_request.head.sha. A push to main diffs only that merge, so scriptMode stays none and the extended step never runs — regardless of the script being broken.

It surfaced on #2028 only because that branch was five commits behind main, so the two-dot diff also carried the inverse of those commits, including the root package.json change from a7d17e5bd. package.json is in FULL_SUITE_FILES (scripts/ci-test-plan.mjs:11), which forces scriptMode: 'full' and runs the extended scripts. Rebasing #2028 onto current main turns it green again without touching this bug.

Confirmed independently by running the test on a clean detached origin/main checkout, where it fails identically. So the breakage is real on main and merely invisible to main's own CI path.

…ion tree
#1994 made SQLite the sole operational authority and removed
SESSION_BUNDLE_PORTABLE_SESSION_DIRECTORIES and
SESSION_BUNDLE_PORTABLE_SESSION_FILES along with the JSONL session tree
they classified, but scripts/measure-session-bundle.mjs still imported
them. The module failed to load, so the whole test file errored out.
Drop the two imports and the classification branch they fed. With
'sessions' no longer in SESSION_BUNDLE_STATE_ENTRIES, that branch was
already unreachable — the top-level portability check rejects the entry
first.
This restores module loading and the extended script suite. It does not
revive the harness's runtime path: findSessionId still requires
sessions/<id>/session.jsonl, which no export produces since #1994.
@Astro-Han

Copy link
Copy Markdown
ContributorAuthor

Superseded by #1920, which landed the same fix (fix(scripts): keep legacy bundle measurement importable) while this was open. Verified on current main: node --test scripts/measure-session-bundle.test.mjs passes. Closing as duplicate.

@Astro-Han
Astro-Han deleted the fix/measure-session-bundle-stale-imports branch August 3, 2026 15:21
Astro-Han pushed a commit that referenced this pull request Aug 6, 2026
) (#2263)
* fix(storage): import legacy JSONL session transcripts into SQLite (#2260)
After the JSONL->SQLite cutover (#1994, #2029), sessions created before
the switch stayed on disk as sessions/<id>/session.jsonl but never
appeared in the UI: the new storage layer only reads SQLite and there was
no migration path (issue #2260).
Add a one-time importer (importLegacySessionsOnce) that scans the legacy
sessions directory, decodes each schemaVersion:1 transcript with the
pre-#1994 compatibility rules (backend remapping, missing-field defaults),
creates the session under its original id via the idempotent
createStableSession path, appends the decoded messages, and restores the
original lifecycle timestamps and flags.
Design:
- Idempotency key is the session id itself (probeStableSessionCreate),
so re-runs and concurrent first launches converge without duplicates.
- Per-file atomicity: a transcript imports fully or is skipped and
reported; corrupt records are never laundered into the authoritative
store. Failures never block startup or other files.
- Legacy files are retained as migration evidence.
- Wired into createSessionStore: list/listCatalogPage/listHeaders await
the lazy import so upgraded installs see their pre-cutover sessions.
* fix(storage): drop console diagnostic from legacy import wiring
The import result logging used console.error, which the repository
check-console audit rejects for new call sites. Remove the log entirely
and harden the lazy import to swallow unexpected errors (best-effort
semantics): a legacy-import failure must never block session listing.
* fix(storage): address #2260 review — validate before write, probe first, marker/subagent fail-closed, resume gate, observable results
Addresses Astro-Han's review (P1 + P2s):
- **P1 per-file atomicity**: the decoded header AND the post-create header
patch are now validated through normalizeSessionHeader BEFORE any store
write. Previously updateHeader (the third of three transactions) could
throw after create+append committed, leaving a permanent partial session
that later probes would report as skipped forever.
- **P2 marker laundering**: a session_transcript marker file with no backing
SQLite row (restored backup, copied sessions/, reset DB) now fails closed
instead of being fabricated into a fake session — matching the pre-#1994
reader's contract.
- **P2 legacy field loss**: decodeLegacySessionHeader preserves
subagentParent/Runtime/Spawn/Workspace, thinkingLevel, lastReadMessageId.
Legacy subagent children route through createSubagent (parent lineage kept);
an incomplete spawn identity fails the file instead of flattening the child
into a top-level session.
- **P2 resume gate**: readHeaderSnapshot/readMessagesSnapshot now await the
lazy import, so 'maka --resume <legacy-id>' no longer misses pre-cutover
sessions on the first post-upgrade run.
- **P2 observability**: ensureLegacyImported retains the result and logs
failures/imported counts instead of swallowing them; LegacySessionImportResult
now splits skipped into existing vs collision.
- **P2 steady-state cost**: the idempotency probe now runs BEFORE the file
read, so every launch skips known ids without touching their transcripts.
- **P2 torn tail**: an incomplete final line (interrupted append) is skipped
like the pre-#1994 strict reader, instead of failing the whole file.
Tests: 11/11 in legacy-session-import (added lazy-list, resume, torn-tail,
marker, subagent fail-closed, field-preservation cases); session-store +
sqlite-session-metadata-store + foreign-session-store 70/70; full storage
suite 702 pass, 2 pre-existing env failures (dugite git binary + root
tsconfig load) verified unrelated via stash.
* chore: allow-list session-store.ts for legacy import diagnostics
check-console.mjs flagged the new console.error/warn/info sites in
session-store.ts (legacy JSONL import outcome diagnostics) as unlisted.
Same pattern as the existing automation-store.ts allow-list entry —
best-effort import diagnostics, no credentials or provider payloads.
* chore: retry CI — test_headless 'settles background child sessions at the task-run deadline' flaked on the previous run (identical headless code passed two runs ago; local 30/30 green; no headless files touched by this PR)
* fix(storage): refactor legacy session import onto a single-transaction store API
Addresses #2263 review round 3: collapse the importer's probe -> create ->
append -> update choreography (three transactions, constant fingerprint,
fidelity patch, in-memory latch, resume gate) into one store-level
importSession primitive.
- sqlite-session-metadata-store: importSession(header, messages, projection)
writes the header row (with historical timestamps/flags) and all messages
in one transaction. Idempotent by primary key (INSERT OR IGNORE), so
concurrent first launches converge on one winner with no create claims;
tombstoned ids are never resurrected; a failure mid-transaction rolls
back, so a partial session can never persist (closes the crash-window P1).
- legacy-session-import: read -> decode -> normalizeSessionHeader (pre-write
validation) -> one importSession call. Subagent children now import under
their own legacy id with lineage preserved instead of a fresh UUID
(fixes the phantom-session P1). Torn-tail tolerance tightened to the
pre-#1994 strict-reader semantics: only a final line of a file with no
trailing newline whose parse failure is an unclosed bracket is skipped;
truncated lines ending in a newline and garbage tails fail the file.
- session-store: memoized import latch moves into ensureReady(), which every
public method already awaits, so desktop/CLI/headless/--resume are all
covered with zero per-caller wiring; appendMessages and closeAfterReady
now await ensureReady() (pre-existing gaps). Import diagnostics are kept
observable through the existing console allow-list.
- tests: payload pins (header model/status, deepEqual messages[0]),
concurrent double-import, id collision, header-only, absent sessions/,
empty file, garbage tail, truncated-with-newline, whole-run failure
containment, and subagent legacy-id round-trip; 19/19 legacy import tests,
full storage suite 713 pass (1 pre-existing dugite-binary env failure).
* fix(storage): probe legacy session ids before reading transcripts
Restores the probe-before-read steady-state cost from review round 2 on the
single-transaction design: the importer now asks the store whether a session
id already exists (live or tombstoned) before opening or parsing its file,
so every launch of an upgraded install pays a directory listing plus
per-id SQLite existence checks. importSession remains the idempotency
authority — a race between the probe and the write still converges on one
winner via the primary key. Adds hasSession to the store surface and a
test that corrupts the on-disk transcript between runs to pin that a
skipped id is never re-read.
---------
Co-authored-by: cat0825 <cat0825@users.noreply.github.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@Astro-Han
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix(scripts): drop session-bundle imports deleted with the JSONL session tree - #2029

Closed
Astro-Han wants to merge 1 commit into
mainfrom
fix/measure-session-bundle-stale-imports
Closed

fix(scripts): drop session-bundle imports deleted with the JSONL session tree#2029
Astro-Han wants to merge 1 commit into
mainfrom
fix/measure-session-bundle-stale-imports

Conversation

@Astro-Han

@Astro-HanAstro-Han commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Summary

scripts/measure-session-bundle.mjs fails to load on main, taking the whole extended script suite down with it:

SyntaxError: The requested module '@maka/storage' does not provide an export
named 'SESSION_BUNDLE_PORTABLE_SESSION_DIRECTORIES'

#1994 made SQLite the sole operational authority and removed the JSONL session tree along with the two constants that classified it — SESSION_BUNDLE_PORTABLE_SESSION_DIRECTORIES and SESSION_BUNDLE_PORTABLE_SESSION_FILES — but the script kept importing them.

Drop the two imports and the topLevel === 'sessions' branch they fed. That branch was already unreachable: sessions is no longer in SESSION_BUNDLE_STATE_ENTRIES, so the top-level portability check rejects such an entry before it can be reached. Net effect is 26 deleted lines and one rewritten import.

Known remaining gap

This restores module loading, not the harness's runtime correctness. findSessionId still requires sessions/<id>/session.jsonl, which no export has produced since #1994, so an actual measurement run would fail on its own. Deciding whether to port the harness to the operational database or retire it needs a storage-bundle judgement call, so it is tracked separately rather than guessed at here.

Verification

  • node --test scripts/measure-session-bundle.test.mjs — 2 passed, 0 failed (fails to even load on main).
  • npm run test:scripts:extended — 15 passed, 0 failed.
  • npm run lint, npm run format:check — clean.

Why main is green with this broken

main's CI never reaches the script. In the latest main run, test_workspaces succeeds with the step itself skipped:

skipped Run fast script tests
skipped Run extended script tests
success Run affected standard workspace tests

ci.yml selects surfaces from a two-dot diff between pull_request.base.sha and pull_request.head.sha. A push to main diffs only that merge, so scriptMode stays none and the extended step never runs — regardless of the script being broken.

It surfaced on #2028 only because that branch was five commits behind main, so the two-dot diff also carried the inverse of those commits, including the root package.json change from a7d17e5bd. package.json is in FULL_SUITE_FILES (scripts/ci-test-plan.mjs:11), which forces scriptMode: 'full' and runs the extended scripts. Rebasing #2028 onto current main turns it green again without touching this bug.

Confirmed independently by running the test on a clean detached origin/main checkout, where it fails identically. So the breakage is real on main and merely invisible to main's own CI path.

…ion tree
#1994 made SQLite the sole operational authority and removed
SESSION_BUNDLE_PORTABLE_SESSION_DIRECTORIES and
SESSION_BUNDLE_PORTABLE_SESSION_FILES along with the JSONL session tree
they classified, but scripts/measure-session-bundle.mjs still imported
them. The module failed to load, so the whole test file errored out.
Drop the two imports and the classification branch they fed. With
'sessions' no longer in SESSION_BUNDLE_STATE_ENTRIES, that branch was
already unreachable — the top-level portability check rejects the entry
first.
This restores module loading and the extended script suite. It does not
revive the harness's runtime path: findSessionId still requires
sessions/<id>/session.jsonl, which no export produces since #1994.
@Astro-Han

Copy link
Copy Markdown
ContributorAuthor

Superseded by #1920, which landed the same fix (fix(scripts): keep legacy bundle measurement importable) while this was open. Verified on current main: node --test scripts/measure-session-bundle.test.mjs passes. Closing as duplicate.

@Astro-Han
Astro-Han deleted the fix/measure-session-bundle-stale-imports branch August 3, 2026 15:21
Astro-Han pushed a commit that referenced this pull request Aug 6, 2026
) (#2263)
* fix(storage): import legacy JSONL session transcripts into SQLite (#2260)
After the JSONL->SQLite cutover (#1994, #2029), sessions created before
the switch stayed on disk as sessions/<id>/session.jsonl but never
appeared in the UI: the new storage layer only reads SQLite and there was
no migration path (issue #2260).
Add a one-time importer (importLegacySessionsOnce) that scans the legacy
sessions directory, decodes each schemaVersion:1 transcript with the
pre-#1994 compatibility rules (backend remapping, missing-field defaults),
creates the session under its original id via the idempotent
createStableSession path, appends the decoded messages, and restores the
original lifecycle timestamps and flags.
Design:
- Idempotency key is the session id itself (probeStableSessionCreate),
so re-runs and concurrent first launches converge without duplicates.
- Per-file atomicity: a transcript imports fully or is skipped and
reported; corrupt records are never laundered into the authoritative
store. Failures never block startup or other files.
- Legacy files are retained as migration evidence.
- Wired into createSessionStore: list/listCatalogPage/listHeaders await
the lazy import so upgraded installs see their pre-cutover sessions.
* fix(storage): drop console diagnostic from legacy import wiring
The import result logging used console.error, which the repository
check-console audit rejects for new call sites. Remove the log entirely
and harden the lazy import to swallow unexpected errors (best-effort
semantics): a legacy-import failure must never block session listing.
* fix(storage): address #2260 review — validate before write, probe first, marker/subagent fail-closed, resume gate, observable results
Addresses Astro-Han's review (P1 + P2s):
- **P1 per-file atomicity**: the decoded header AND the post-create header
patch are now validated through normalizeSessionHeader BEFORE any store
write. Previously updateHeader (the third of three transactions) could
throw after create+append committed, leaving a permanent partial session
that later probes would report as skipped forever.
- **P2 marker laundering**: a session_transcript marker file with no backing
SQLite row (restored backup, copied sessions/, reset DB) now fails closed
instead of being fabricated into a fake session — matching the pre-#1994
reader's contract.
- **P2 legacy field loss**: decodeLegacySessionHeader preserves
subagentParent/Runtime/Spawn/Workspace, thinkingLevel, lastReadMessageId.
Legacy subagent children route through createSubagent (parent lineage kept);
an incomplete spawn identity fails the file instead of flattening the child
into a top-level session.
- **P2 resume gate**: readHeaderSnapshot/readMessagesSnapshot now await the
lazy import, so 'maka --resume <legacy-id>' no longer misses pre-cutover
sessions on the first post-upgrade run.
- **P2 observability**: ensureLegacyImported retains the result and logs
failures/imported counts instead of swallowing them; LegacySessionImportResult
now splits skipped into existing vs collision.
- **P2 steady-state cost**: the idempotency probe now runs BEFORE the file
read, so every launch skips known ids without touching their transcripts.
- **P2 torn tail**: an incomplete final line (interrupted append) is skipped
like the pre-#1994 strict reader, instead of failing the whole file.
Tests: 11/11 in legacy-session-import (added lazy-list, resume, torn-tail,
marker, subagent fail-closed, field-preservation cases); session-store +
sqlite-session-metadata-store + foreign-session-store 70/70; full storage
suite 702 pass, 2 pre-existing env failures (dugite git binary + root
tsconfig load) verified unrelated via stash.
* chore: allow-list session-store.ts for legacy import diagnostics
check-console.mjs flagged the new console.error/warn/info sites in
session-store.ts (legacy JSONL import outcome diagnostics) as unlisted.
Same pattern as the existing automation-store.ts allow-list entry —
best-effort import diagnostics, no credentials or provider payloads.
* chore: retry CI — test_headless 'settles background child sessions at the task-run deadline' flaked on the previous run (identical headless code passed two runs ago; local 30/30 green; no headless files touched by this PR)
* fix(storage): refactor legacy session import onto a single-transaction store API
Addresses #2263 review round 3: collapse the importer's probe -> create ->
append -> update choreography (three transactions, constant fingerprint,
fidelity patch, in-memory latch, resume gate) into one store-level
importSession primitive.
- sqlite-session-metadata-store: importSession(header, messages, projection)
writes the header row (with historical timestamps/flags) and all messages
in one transaction. Idempotent by primary key (INSERT OR IGNORE), so
concurrent first launches converge on one winner with no create claims;
tombstoned ids are never resurrected; a failure mid-transaction rolls
back, so a partial session can never persist (closes the crash-window P1).
- legacy-session-import: read -> decode -> normalizeSessionHeader (pre-write
validation) -> one importSession call. Subagent children now import under
their own legacy id with lineage preserved instead of a fresh UUID
(fixes the phantom-session P1). Torn-tail tolerance tightened to the
pre-#1994 strict-reader semantics: only a final line of a file with no
trailing newline whose parse failure is an unclosed bracket is skipped;
truncated lines ending in a newline and garbage tails fail the file.
- session-store: memoized import latch moves into ensureReady(), which every
public method already awaits, so desktop/CLI/headless/--resume are all
covered with zero per-caller wiring; appendMessages and closeAfterReady
now await ensureReady() (pre-existing gaps). Import diagnostics are kept
observable through the existing console allow-list.
- tests: payload pins (header model/status, deepEqual messages[0]),
concurrent double-import, id collision, header-only, absent sessions/,
empty file, garbage tail, truncated-with-newline, whole-run failure
containment, and subagent legacy-id round-trip; 19/19 legacy import tests,
full storage suite 713 pass (1 pre-existing dugite-binary env failure).
* fix(storage): probe legacy session ids before reading transcripts
Restores the probe-before-read steady-state cost from review round 2 on the
single-transaction design: the importer now asks the store whether a session
id already exists (live or tombstoned) before opening or parsing its file,
so every launch of an upgraded install pays a directory listing plus
per-id SQLite existence checks. importSession remains the idempotency
authority — a race between the probe and the write still converges on one
winner via the primary key. Adds hasSession to the store surface and a
test that corrupts the on-disk transcript between runs to pin that a
skipped id is never re-read.
---------
Co-authored-by: cat0825 <cat0825@users.noreply.github.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@Astro-Han