@aiwayds/dsh-tui-pi 2.0.1 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -40,7 +40,7 @@ dsh plugin --profile tui add @aiwayds/dsh-dcp # optional
40
40
  dsh --profile tui # launch (or: dsh-tui-pi)
41
41
  ```
42
42
 
43
- The `dsh-tui-pi` launcher runs a silent preflight before booting dsh: it migrates legacy `session_projcache` records that are missing `identity.isSeeded`/`identity.inheritedEventCount` (records written before dsh 0.1.2-alpha.4 would otherwise crash the boot). It is idempotent already-migrated records are never touched and a preflight failure never blocks startup.
43
+ Legacy `session_projcache` records (missing `identity.isSeeded`/`identity.inheritedEventCount`, written before dsh 0.1.2-alpha.4) are migrated at the profile layer: the bundle patch replaces the stock `session-projection-cache` row with a wrapper (`@aiwayds/dsh-tui-pi/projcache`) that backfills the records while its module loads — strictly before the stock plugin could open the domain and crash the boot — so every `dsh --profile tui` start is covered, launcher or not. Migration is idempotent, backs up every rewritten file next to the original, and never blocks startup. The `dsh-tui-pi` launcher additionally runs the same migration as a CLI preflight before `exec dsh`.
44
44
 
45
45
  Everything that used to need manual patching — the canvas background, the `@deepseek-ai` module closure, the compaction backend — now happens automatically. Upgrade an existing profile after a release:
46
46
 
@@ -1,108 +1,26 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * Preflight migration for the dsh `session_projcache` storage domain.
3
+ * Preflight migration for the dsh `session_projcache` storage domain — CLI
4
+ * shell around the shared core (lib/preflight-projcache.js, built from
5
+ * src/preflight-projcache.ts). The `dsh-tui-pi` launcher (bin/dsh-tui-pi)
6
+ * runs this before `exec dsh`. This is one of two mount points for the
7
+ * migration; the other is lib/projcache.js, the wrapper module the bundle
8
+ * patch mounts in place of the stock session-projection-cache row, so plain
9
+ * `dsh --profile tui` boots are covered without the launcher.
4
10
  *
5
- * Background: dsh 0.1.2-alpha.4 extends the session-projection-cache record
6
- * schema with `identity.isSeeded: boolean` and `identity.inheritedEventCount:
7
- * number`. The boot plugin tree opens the storage domain (allSettled, no
8
- * migrate hook) and fail-fast validates those fields records written by
9
- * older dsh builds (0.1.1-rc.2 era) lack them, so the whole dsh process dies
10
- * with a bare stack trace before the TUI ever mounts. This script runs from
11
- * the launcher (bin/dsh-tui-pi) before `exec dsh` and backfills the two
12
- * fields on every legacy record under
13
- *
14
- * <DSH_HOME>/storages/session_projcache/sessions/session-*.json
15
- *
16
- * - records that already carry both fields are left byte-identical (no IO);
17
- * - every changed record is backed up next to the original, then rewritten
18
- * atomically (tmp file + rename);
19
- * - the script NEVER blocks startup: any unexpected error is swallowed with
20
- * a one-line stderr warning and a 0 exit code.
11
+ * Contract (unchanged since 2.0.1):
12
+ * - scans <DSH_HOME||~/.dsh>/storages/session_projcache/sessions/session-*.json
13
+ * - backfills identity.isSeeded:false / identity.inheritedEventCount:0
14
+ * - records that already carry both fields are left byte-identical (no IO);
15
+ * - every changed record is backed up next to the original, then rewritten
16
+ * atomically (tmp file + rename);
17
+ * - per-record problems warn on stderr and never abort the scan;
18
+ * - ALWAYS exits 0: a preflight failure must never block startup.
21
19
  */
22
20
 
23
21
  import fs from 'node:fs'
24
- import os from 'node:os'
25
- import path from 'node:path'
26
22
  import { fileURLToPath } from 'node:url'
27
-
28
- const BACKFILL = { isSeeded: false, inheritedEventCount: 0 }
29
-
30
- function isPlainObject(value) {
31
- return typeof value === 'object' && value !== null && !Array.isArray(value)
32
- }
33
-
34
- function backupPathFor(file) {
35
- const stamp = new Date().toISOString()
36
- let candidate = `${file}.bak-preflight-${stamp}`
37
- for (let n = 2; fs.existsSync(candidate); n++) {
38
- candidate = `${file}.bak-preflight-${stamp}-${n}`
39
- }
40
- return candidate
41
- }
42
-
43
- /**
44
- * Backfill missing identity fields on every session-*.json record under dir.
45
- * Returns { checked, fixed } — `fixed` counts records rewritten (or, in
46
- * check mode, records that would be rewritten). Unparsable records are
47
- * warned about on stderr and skipped.
48
- */
49
- export function preflightProjcache(dir, { check = false } = {}) {
50
- const result = { checked: 0, fixed: 0 }
51
- if (!fs.existsSync(dir)) return result
52
-
53
- for (const name of fs.readdirSync(dir)) {
54
- if (!/^session-.*\.json$/.test(name)) continue
55
- const file = path.join(dir, name)
56
- if (!fs.statSync(file).isFile()) continue
57
- result.checked++
58
-
59
- let text
60
- try {
61
- text = fs.readFileSync(file, 'utf8')
62
- } catch (err) {
63
- process.stderr.write(`[preflight-projcache] unreadable ${name}: ${err?.message ?? err}\n`)
64
- continue
65
- }
66
- let obj
67
- try {
68
- obj = JSON.parse(text)
69
- } catch {
70
- process.stderr.write(`[preflight-projcache] skipping unparsable ${name}\n`)
71
- continue
72
- }
73
- if (!isPlainObject(obj)) continue
74
-
75
- let identity = obj.identity
76
- if (identity === undefined) {
77
- identity = {}
78
- obj.identity = identity
79
- }
80
- if (!isPlainObject(identity)) continue
81
-
82
- let changed = false
83
- for (const [field, value] of Object.entries(BACKFILL)) {
84
- if (identity[field] === undefined) {
85
- identity[field] = value
86
- changed = true
87
- }
88
- }
89
- if (!changed) continue
90
-
91
- result.fixed++
92
- if (check) continue
93
- try {
94
- fs.writeFileSync(backupPathFor(file), text)
95
- const migrated = JSON.stringify(obj, null, 2) + (text.endsWith('\n') ? '\n' : '')
96
- const tmp = `${file}.tmp-preflight-${process.pid}`
97
- fs.writeFileSync(tmp, migrated)
98
- fs.renameSync(tmp, file)
99
- } catch (err) {
100
- result.fixed--
101
- process.stderr.write(`[preflight-projcache] failed to rewrite ${name}: ${err?.message ?? err}\n`)
102
- }
103
- }
104
- return result
105
- }
23
+ import { preflightProjcache, projcacheSessionsDir } from '../lib/preflight-projcache.js'
106
24
 
107
25
  function isMainEntry() {
108
26
  try {
@@ -113,19 +31,13 @@ function isMainEntry() {
113
31
  }
114
32
  }
115
33
 
116
- function main() {
117
- const home = process.env.DSH_HOME || path.join(os.homedir(), '.dsh')
118
- const dir = path.join(home, 'storages', 'session_projcache', 'sessions')
119
- const check = process.argv.includes('--check')
120
- const { fixed } = preflightProjcache(dir, { check })
121
- if (check) process.stdout.write(`${fixed} session_projcache record(s) need migration\n`)
122
- }
123
-
124
34
  // Run the CLI shell only when executed directly — importing this module
125
- // (from the tests) must have no side effects.
35
+ // must have no side effects.
126
36
  if (isMainEntry()) {
127
37
  try {
128
- main()
38
+ const check = process.argv.includes('--check')
39
+ const { fixed } = preflightProjcache(projcacheSessionsDir(), { check })
40
+ if (check) process.stdout.write(`${fixed} session_projcache record(s) need migration\n`)
129
41
  } catch (err) {
130
42
  // Never block startup: a preflight failure is a warning, not an error.
131
43
  process.stderr.write(`[preflight-projcache] skipped: ${err?.message ?? err}\n`)
package/cordis.patch.yml CHANGED
@@ -14,9 +14,36 @@
14
14
  # the bundle to be wired into the model-facing tool catalog. Without this
15
15
  # insert, the `ask_user_question` tool never registers with the model and
16
16
  # the user-questions seam has no visible surface in this profile.
17
+ #
18
+ # The stock session-projection-cache row is REPLACED (disable + wrapper), not
19
+ # extended: the alpha.4 record schema fail-fasts at storage-open on records
20
+ # written by 0.1.1-rc.2-era hosts (missing identity.isSeeded /
21
+ # identity.inheritedEventCount), and the crash happens inside the stock
22
+ # plugin's own Service.init — the loader collects it from the parallel
23
+ # allSettled and tears down the whole boot before any later-initializing
24
+ # plugin could repair the data. The wrapper entry below (tui-pi-projcache)
25
+ # mounts @aiwayds/dsh-tui-pi/projcache, which backfills the legacy fields at
26
+ # module-evaluation time — the one hook the loader reaches strictly before
27
+ # Service.init — and otherwise re-exports the stock module unchanged.
28
+ #
29
+ # The `name` field on the disable patch is a guard, not an override: if a
30
+ # future host renames or drops the row, the patch is skipped with a loader
31
+ # warning instead of silently disabling an unrelated entry.
32
+
33
+ - id: session-projection-cache
34
+ name: '@deepseek-ai/dsh-session-projection-cache'
35
+ disabled: true
17
36
 
18
37
  - insert:
19
38
  - id: tui-pi
20
39
  name: '@aiwayds/dsh-tui-pi'
21
40
  - id: tool-ask-user
22
41
  name: '@deepseek-ai/dsh-tool-ask-user'
42
+ # Config mirrors the stock row's values (the plugin's Config schema
43
+ # requires both fields); a later upstream retune of the base row does not
44
+ # propagate through a disabled entry, so revisit on host upgrades.
45
+ - id: tui-pi-projcache
46
+ name: '@aiwayds/dsh-tui-pi/projcache'
47
+ config:
48
+ writeEveryEvents: 200
49
+ writeIntervalMs: 5000
@@ -0,0 +1,39 @@
1
+ /**
2
+ * session_projcache record migration, shared by both mount points:
3
+ *
4
+ * - `bin/preflight-projcache.mjs` — the CLI shell the `dsh-tui-pi` launcher
5
+ * runs before `exec dsh` (covers users starting through the launcher);
6
+ * - `src/projcache.ts` — the wrapper module the bundle patch mounts in place
7
+ * of the stock `session-projection-cache` row (covers every `dsh --profile
8
+ * tui` boot, launcher or not).
9
+ *
10
+ * The dsh 0.1.2-alpha.4 projection-cache schema hard-requires
11
+ * `identity.isSeeded: boolean` and `identity.inheritedEventCount: number`;
12
+ * records written by 0.1.1-rc.2-era hosts lack those fields and fail zod
13
+ * validation at storage-open time (`invalid-record`), which crashes the
14
+ * whole boot. The migration backfills exactly the missing fields, backs up
15
+ * every rewritten file next to the original, and always fails open — any
16
+ * error is a stderr warning, never a startup blocker.
17
+ */
18
+ export interface PreflightResult {
19
+ checked: number;
20
+ fixed: number;
21
+ }
22
+ export interface PreflightOptions {
23
+ /** Report what would change without touching anything. */
24
+ check?: boolean;
25
+ }
26
+ /**
27
+ * The per-record sessions directory the stock plugin's storage domain opens
28
+ * at boot: `<DSH_HOME || ~/.dsh>/storages/session_projcache/sessions`.
29
+ */
30
+ export declare function projcacheSessionsDir(home?: string): string;
31
+ /**
32
+ * Backfill missing identity fields on every `session-*.json` record in
33
+ * `dir`. Already-migrated records are left byte-identical (no backup, no
34
+ * rewrite); unparsable or unreadable records are warned about and skipped;
35
+ * rewrite failures roll that record's count back and warn. Never throws for
36
+ * per-record problems — only a catastrophic `dir` scan failure propagates,
37
+ * and both callers catch it.
38
+ */
39
+ export declare function preflightProjcache(dir: string, { check }?: PreflightOptions): PreflightResult;
@@ -0,0 +1,116 @@
1
+ /**
2
+ * session_projcache record migration, shared by both mount points:
3
+ *
4
+ * - `bin/preflight-projcache.mjs` — the CLI shell the `dsh-tui-pi` launcher
5
+ * runs before `exec dsh` (covers users starting through the launcher);
6
+ * - `src/projcache.ts` — the wrapper module the bundle patch mounts in place
7
+ * of the stock `session-projection-cache` row (covers every `dsh --profile
8
+ * tui` boot, launcher or not).
9
+ *
10
+ * The dsh 0.1.2-alpha.4 projection-cache schema hard-requires
11
+ * `identity.isSeeded: boolean` and `identity.inheritedEventCount: number`;
12
+ * records written by 0.1.1-rc.2-era hosts lack those fields and fail zod
13
+ * validation at storage-open time (`invalid-record`), which crashes the
14
+ * whole boot. The migration backfills exactly the missing fields, backs up
15
+ * every rewritten file next to the original, and always fails open — any
16
+ * error is a stderr warning, never a startup blocker.
17
+ */
18
+ import fs from 'node:fs';
19
+ import os from 'node:os';
20
+ import path from 'node:path';
21
+ const BACKFILL = { isSeeded: false, inheritedEventCount: 0 };
22
+ function isPlainObject(value) {
23
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
24
+ }
25
+ function errorMessage(err) {
26
+ return err instanceof Error ? err.message : String(err);
27
+ }
28
+ function warn(message) {
29
+ process.stderr.write(`[preflight-projcache] ${message}\n`);
30
+ }
31
+ /**
32
+ * The per-record sessions directory the stock plugin's storage domain opens
33
+ * at boot: `<DSH_HOME || ~/.dsh>/storages/session_projcache/sessions`.
34
+ */
35
+ export function projcacheSessionsDir(home = process.env.DSH_HOME || path.join(os.homedir(), '.dsh')) {
36
+ return path.join(home, 'storages', 'session_projcache', 'sessions');
37
+ }
38
+ function backupPathFor(file) {
39
+ const stamp = new Date().toISOString();
40
+ let candidate = `${file}.bak-preflight-${stamp}`;
41
+ for (let n = 2; fs.existsSync(candidate); n++) {
42
+ candidate = `${file}.bak-preflight-${stamp}-${n}`;
43
+ }
44
+ return candidate;
45
+ }
46
+ /**
47
+ * Backfill missing identity fields on every `session-*.json` record in
48
+ * `dir`. Already-migrated records are left byte-identical (no backup, no
49
+ * rewrite); unparsable or unreadable records are warned about and skipped;
50
+ * rewrite failures roll that record's count back and warn. Never throws for
51
+ * per-record problems — only a catastrophic `dir` scan failure propagates,
52
+ * and both callers catch it.
53
+ */
54
+ export function preflightProjcache(dir, { check = false } = {}) {
55
+ const result = { checked: 0, fixed: 0 };
56
+ if (!fs.existsSync(dir))
57
+ return result;
58
+ for (const name of fs.readdirSync(dir)) {
59
+ if (!/^session-.*\.json$/.test(name))
60
+ continue;
61
+ const file = path.join(dir, name);
62
+ if (!fs.statSync(file).isFile())
63
+ continue;
64
+ result.checked++;
65
+ let text;
66
+ try {
67
+ text = fs.readFileSync(file, 'utf8');
68
+ }
69
+ catch (err) {
70
+ warn(`unreadable ${name}: ${errorMessage(err)}`);
71
+ continue;
72
+ }
73
+ let obj;
74
+ try {
75
+ obj = JSON.parse(text);
76
+ }
77
+ catch {
78
+ warn(`skipping unparsable ${name}`);
79
+ continue;
80
+ }
81
+ if (!isPlainObject(obj))
82
+ continue;
83
+ let identity = obj.identity;
84
+ if (identity === undefined) {
85
+ identity = {};
86
+ obj.identity = identity;
87
+ }
88
+ if (!isPlainObject(identity))
89
+ continue;
90
+ let changed = false;
91
+ for (const [field, value] of Object.entries(BACKFILL)) {
92
+ if (identity[field] === undefined) {
93
+ identity[field] = value;
94
+ changed = true;
95
+ }
96
+ }
97
+ if (!changed)
98
+ continue;
99
+ result.fixed++;
100
+ if (check)
101
+ continue;
102
+ try {
103
+ fs.writeFileSync(backupPathFor(file), text);
104
+ const migrated = JSON.stringify(obj, null, 2) + (text.endsWith('\n') ? '\n' : '');
105
+ const tmp = `${file}.tmp-preflight-${process.pid}`;
106
+ fs.writeFileSync(tmp, migrated);
107
+ fs.renameSync(tmp, file);
108
+ }
109
+ catch (err) {
110
+ result.fixed--;
111
+ warn(`failed to rewrite ${name}: ${errorMessage(err)}`);
112
+ }
113
+ }
114
+ return result;
115
+ }
116
+ //# sourceMappingURL=preflight-projcache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preflight-projcache.js","sourceRoot":"","sources":["../src/preflight-projcache.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export * from '@deepseek-ai/dsh-session-projection-cache';
2
+ export { default } from '@deepseek-ai/dsh-session-projection-cache';
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Boot-time replacement for the stock `session-projection-cache` bundle row.
3
+ *
4
+ * cordis.patch.yml disables the stock row and inserts an entry mounting this
5
+ * module instead. The alpha.4 schema fail-fasts at storage-open on records
6
+ * written by 0.1.1-rc.2-era hosts, and that happens inside the stock
7
+ * plugin's own Service.init — no later-initializing plugin can intercept it,
8
+ * the loader tears down the whole boot. Module evaluation is the only hook
9
+ * the loader reaches strictly before Service.init, so the migration runs
10
+ * here, at import time, backfilling the legacy records on disk before the
11
+ * stock code opens its storage domain.
12
+ *
13
+ * The stock module itself has no import-time side effects (class and zod
14
+ * schema definitions only), so the hoisted re-export below evaluating first
15
+ * is harmless. Migration is fail-open: any error warns on stderr and leaves
16
+ * the boot exactly where it would have been without the wrapper.
17
+ *
18
+ * Consumers of the stock package's named exports import
19
+ * `@deepseek-ai/dsh-session-projection-cache` directly and share this same
20
+ * module instance; only the loader reaches the plugin through here.
21
+ */
22
+ import { preflightProjcache, projcacheSessionsDir } from './preflight-projcache.js';
23
+ try {
24
+ preflightProjcache(projcacheSessionsDir());
25
+ }
26
+ catch (err) {
27
+ const message = err instanceof Error ? err.message : String(err);
28
+ process.stderr.write(`[preflight-projcache] skipped: ${message}\n`);
29
+ }
30
+ export * from '@deepseek-ai/dsh-session-projection-cache';
31
+ export { default } from '@deepseek-ai/dsh-session-projection-cache';
32
+ //# sourceMappingURL=projcache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"projcache.js","sourceRoot":"","sources":["../src/projcache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAA;AAEnF,IAAI,CAAC;IACH,kBAAkB,CAAC,oBAAoB,EAAE,CAAC,CAAA;AAC5C,CAAC;AAAC,OAAO,GAAG,EAAE,CAAC;IACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;IAChE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,kCAAkC,OAAO,IAAI,CAAC,CAAA;AACrE,CAAC;AAED,cAAc,2CAA2C,CAAA;AACzD,OAAO,EAAE,OAAO,EAAE,MAAM,2CAA2C,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiwayds/dsh-tui-pi",
3
- "version": "2.0.1",
3
+ "version": "2.0.2",
4
4
  "description": "pi-style terminal UI for DeepSeek Harness (dsh) — pi-tui look & feel, dsh slash commands, GitHub light/dark themes, powerline footer",
5
5
  "repository": {
6
6
  "type": "git",
@@ -19,6 +19,18 @@
19
19
  "type": "module",
20
20
  "main": "lib/index.js",
21
21
  "types": "lib/index.d.ts",
22
+ "exports": {
23
+ ".": {
24
+ "types": "./lib/index.d.ts",
25
+ "default": "./lib/index.js"
26
+ },
27
+ "./projcache": {
28
+ "types": "./lib/projcache.d.ts",
29
+ "default": "./lib/projcache.js"
30
+ },
31
+ "./cordis.patch.yml": "./cordis.patch.yml",
32
+ "./package.json": "./package.json"
33
+ },
22
34
  "bin": {
23
35
  "dsh-tui-pi": "bin/dsh-tui-pi"
24
36
  },
@@ -8,10 +8,15 @@
8
8
  // dsh.profile.bundles entry (same shape as the user's real profiles)
9
9
  // 3. pnpm install
10
10
  // 4. `dsh --profile smoke --dump-config` must compose the plugin into the
11
- // tree (mount/patch-layer proof)
12
- // 5. a real boot under a timeout must load the plugin tree without a
11
+ // tree (mount/patch-layer proof), disable the stock projection-cache
12
+ // row and mount the projcache wrapper in its place
13
+ // 5. a legacy session_projcache record (the 0.1.1-rc.2 shape that the
14
+ // alpha.4 schema fail-fasts on) is seeded into the scratch home; the
15
+ // boot below must migrate it, not crash on it
16
+ // 6. a real boot under a timeout must load the plugin tree without a
13
17
  // loader error (a healthy boot is silent and survives to the kill
14
- // signal; a broken plugin dies within ~1s with the loader error)
18
+ // signal; a broken plugin dies within ~1s with the loader error), and
19
+ // the seeded record must come out backfilled + backed up
15
20
  //
16
21
  // The boot runs piped (no TTY). Verified empirically: the TUI plugin's
17
22
  // apply() tolerates a non-terminal (pi-tui guards raw mode), so the piped
@@ -21,7 +26,7 @@
21
26
  // removed on success.
22
27
 
23
28
  import { spawnSync } from 'node:child_process'
24
- import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs'
29
+ import { mkdtempSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs'
25
30
  import { readFile } from 'node:fs/promises'
26
31
  import { tmpdir } from 'node:os'
27
32
  import path from 'node:path'
@@ -75,12 +80,32 @@ if (install.status !== 0 || install.error) fail('pnpm install in the scratch pro
75
80
 
76
81
  const dshEnv = { ...process.env, DSH_HOME: home, TERM: process.env.TERM ?? 'xterm-256color' }
77
82
 
78
- // Phase 1 — mount proof: the composed tree must include the plugin.
83
+ // Phase 1 — mount proof: the composed tree must include the plugin, disable
84
+ // the stock projection-cache row and mount the projcache wrapper instead.
79
85
  const dump = spawnSync('dsh', ['--profile', 'smoke', '--dump-config'], { cwd: profile, encoding: 'utf8', env: dshEnv })
80
86
  if (dump.status !== 0 || dump.error) fail('dsh --dump-config failed on the scratch profile', `${dump.stdout}\n${dump.stderr}`)
81
87
  if (!dump.stdout.includes(ownName)) {
82
88
  fail(`the composed profile tree does not contain ${ownName} — the bundle patch insert is broken`, dump.stdout)
83
89
  }
90
+ if (!/id: session-projection-cache[\s\S]*?disabled: true/.test(dump.stdout)) {
91
+ fail('the stock session-projection-cache row is not disabled — the wrapper would race a second mount', dump.stdout)
92
+ }
93
+ if (!dump.stdout.includes('tui-pi-projcache')) {
94
+ fail('the projcache wrapper entry is missing from the composed tree — legacy records would crash the boot', dump.stdout)
95
+ }
96
+
97
+ // Phase 1.5 — seed a legacy projection-cache record: the 0.1.1-rc.2 shape
98
+ // lacks the identity fields the alpha.4 schema requires. The boot in phase 2
99
+ // must migrate it (lib/projcache.js, module-evaluation time) instead of
100
+ // dying on it at storage-open.
101
+ const sessionsDir = path.join(home, 'storages', 'session_projcache', 'sessions')
102
+ mkdirSync(sessionsDir, { recursive: true })
103
+ const legacyName = 'session-smoke-legacy.json'
104
+ const legacyOriginal = JSON.stringify({
105
+ identity: { createdAt: 1756000000000, cwd: '/tmp/smoke' },
106
+ events: [{ seq: 1 }],
107
+ }, null, 2)
108
+ writeFileSync(path.join(sessionsDir, legacyName), legacyOriginal)
84
109
 
85
110
  // Phase 2 — boot proof: the plugin tree must LOAD without a loader error.
86
111
  const bootSeconds = 25
@@ -95,6 +120,7 @@ const output = `${boot.stdout ?? ''}\n${boot.stderr ?? ''}`
95
120
  const loaderErrors = [
96
121
  /plugin tree failed to load/,
97
122
  /failed to apply loader entry/,
123
+ /does not match its schema/,
98
124
  /cannot get property ".*" without inject/,
99
125
  /cannot get required service/,
100
126
  /Cannot find (package|module)/,
@@ -113,5 +139,29 @@ if (!survived && boot.status !== 0) {
113
139
  fail(`dsh exited early with code ${boot.status} and no loader error — unexpected`, output)
114
140
  }
115
141
 
116
- console.log(`smoke-boot: PASS${ownName} composed into the scratch profile tree and booted clean in real dsh (${survived ? `survived the ${bootSeconds}s boot window` : `exited ${boot.status}`})`)
142
+ // Phase 3 rescue proof: the seeded legacy record must have been backfilled
143
+ // by the wrapper before the stock plugin could open the domain, with the
144
+ // original bytes backed up next to it.
145
+ const legacyNow = path.join(sessionsDir, legacyName)
146
+ const backups = (() => {
147
+ try {
148
+ return readdirSync(sessionsDir).filter((n) => n.startsWith(`${legacyName}.bak-preflight-`))
149
+ } catch {
150
+ return []
151
+ }
152
+ })()
153
+ if (!backups.length) {
154
+ fail('the seeded legacy record was never migrated — the wrapper did not run at module-evaluation time', output)
155
+ }
156
+ let migrated
157
+ try {
158
+ migrated = JSON.parse(readFileSync(legacyNow, 'utf8'))
159
+ } catch (err) {
160
+ fail(`the migrated legacy record is unparsable: ${err.message}`)
161
+ }
162
+ if (migrated.identity?.isSeeded !== false || migrated.identity?.inheritedEventCount !== 0) {
163
+ fail('the seeded legacy record survived the boot without being backfilled', JSON.stringify(migrated.identity))
164
+ }
165
+
166
+ console.log(`smoke-boot: PASS — ${ownName} composed into the scratch profile tree and booted clean in real dsh (${survived ? `survived the ${bootSeconds}s boot window` : `exited ${boot.status}`}); the seeded legacy projection-cache record was migrated with a backup`)
117
167
  rmSync(work, { recursive: true, force: true })