sneakoscope 4.1.0 → 4.2.0

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.
Files changed (94) hide show
  1. package/README.md +16 -3
  2. package/crates/sks-core/Cargo.lock +1 -1
  3. package/crates/sks-core/Cargo.toml +1 -1
  4. package/crates/sks-core/src/main.rs +1 -1
  5. package/dist/bin/sks.js +1 -1
  6. package/dist/cli/command-registry.js +1 -1
  7. package/dist/cli/router.js +6 -1
  8. package/dist/commands/doctor.js +272 -127
  9. package/dist/core/auto-review.js +1 -1
  10. package/dist/core/codex/agent-config-file-repair.js +43 -2
  11. package/dist/core/codex-app/codex-agent-role-sync.js +4 -4
  12. package/dist/core/codex-control/codex-0142-capability.js +51 -6
  13. package/dist/core/codex-control/codex-app-server-v2-client.js +2 -2
  14. package/dist/core/codex-native/codex-native-feature-broker.js +50 -0
  15. package/dist/core/codex-native/native-capability-postcheck.js +59 -16
  16. package/dist/core/codex-native/native-capability-repair-matrix.js +77 -13
  17. package/dist/core/commands/mad-db-command.js +146 -51
  18. package/dist/core/commands/mad-sks-command.js +51 -61
  19. package/dist/core/db-safety.js +35 -37
  20. package/dist/core/doctor/doctor-dirty-planner.js +9 -4
  21. package/dist/core/doctor/doctor-native-capability-repair.js +42 -7
  22. package/dist/core/doctor/doctor-readiness-matrix.js +9 -5
  23. package/dist/core/doctor/doctor-repair-postcheck.js +10 -1
  24. package/dist/core/doctor/doctor-transaction.js +1 -1
  25. package/dist/core/doctor/supabase-mcp-repair.js +2 -2
  26. package/dist/core/feature-registry.js +1 -1
  27. package/dist/core/fsx.js +1 -1
  28. package/dist/core/init.js +5 -4
  29. package/dist/core/mad-db/mad-db-capability.js +203 -74
  30. package/dist/core/mad-db/mad-db-coordinator.js +287 -0
  31. package/dist/core/mad-db/mad-db-executor.js +156 -0
  32. package/dist/core/mad-db/mad-db-ledger.js +1 -1
  33. package/dist/core/mad-db/mad-db-lock.js +40 -0
  34. package/dist/core/mad-db/mad-db-operation-store.js +140 -0
  35. package/dist/core/mad-db/mad-db-policy-resolver.js +42 -22
  36. package/dist/core/mad-db/mad-db-policy.js +195 -0
  37. package/dist/core/mad-db/mad-db-postconditions.js +30 -0
  38. package/dist/core/mad-db/mad-db-recovery.js +27 -0
  39. package/dist/core/mad-db/mad-db-result-lifecycle.js +31 -102
  40. package/dist/core/mad-db/mad-db-runtime-profile.js +121 -0
  41. package/dist/core/mad-db/mad-db-target.js +64 -0
  42. package/dist/core/managed-assets/managed-assets-manifest.js +14 -4
  43. package/dist/core/pipeline-internals/runtime-core.js +40 -0
  44. package/dist/core/providers/glm/bench/glm-benchmark-runner.js +4 -3
  45. package/dist/core/providers/glm/bench/glm-benchmark-types.js +1 -1
  46. package/dist/core/release/release-gate-dag.js +6 -5
  47. package/dist/core/routes.js +23 -8
  48. package/dist/core/update/update-migration-state.js +265 -50
  49. package/dist/core/update-check.js +6 -6
  50. package/dist/core/version.js +1 -1
  51. package/dist/core/zellij/zellij-launcher.js +17 -5
  52. package/dist/core/zellij/zellij-slot-column-anchor.js +5 -1
  53. package/dist/scripts/check-dist-runtime.js +3 -2
  54. package/dist/scripts/codex-0142-manifest-check.js +2 -1
  55. package/dist/scripts/config-managed-merge-callsite-coverage-check.js +6 -0
  56. package/dist/scripts/doctor-dirty-plan-check.js +1 -1
  57. package/dist/scripts/doctor-transaction-engine-check.js +1 -0
  58. package/dist/scripts/doctor-warning-only-not-blocker-check.js +18 -1
  59. package/dist/scripts/loop-directive-check-lib.js +2 -1
  60. package/dist/scripts/mad-db-capability-check.js +13 -2
  61. package/dist/scripts/mad-db-command-check.js +7 -5
  62. package/dist/scripts/mad-db-hook-idempotency-check.js +21 -0
  63. package/dist/scripts/mad-db-ledger-check.js +2 -1
  64. package/dist/scripts/mad-db-lifecycle-hook-decision-check.js +5 -4
  65. package/dist/scripts/mad-db-mad-command-check.js +29 -16
  66. package/dist/scripts/mad-db-mcp-result-lifecycle-check.js +11 -10
  67. package/dist/scripts/mad-db-one-cycle-bounded-check.js +15 -18
  68. package/dist/scripts/mad-db-one-cycle-consumption-check.js +3 -3
  69. package/dist/scripts/mad-db-operation-lifecycle-blackbox.js +9 -9
  70. package/dist/scripts/mad-db-operation-lifecycle-ledger-check.js +6 -6
  71. package/dist/scripts/mad-db-parallel-lifecycle-check.js +24 -0
  72. package/dist/scripts/mad-db-policy-v2-check.js +20 -0
  73. package/dist/scripts/mad-db-priority-resolver-check.js +5 -5
  74. package/dist/scripts/mad-db-real-supabase-e2e.js +166 -0
  75. package/dist/scripts/mad-db-route-identity-check.js +28 -0
  76. package/dist/scripts/mad-db-runtime-profile-lifecycle-check.js +24 -0
  77. package/dist/scripts/mad-db-safety-conflict-matrix-check.js +3 -3
  78. package/dist/scripts/mad-db-skill-policy-snapshot-check.js +15 -0
  79. package/dist/scripts/mad-sks-zellij-launch-check.js +7 -1
  80. package/dist/scripts/managed-role-manifest-parity-check.js +4 -1
  81. package/dist/scripts/naruto-real-parallelism-blackbox.js +17 -4
  82. package/dist/scripts/native-capability-postcheck-check.js +1 -0
  83. package/dist/scripts/native-capability-repair-matrix-check.js +2 -0
  84. package/dist/scripts/native-chrome-web-review-repair-check.js +1 -0
  85. package/dist/scripts/native-computer-use-repair-check.js +1 -0
  86. package/dist/scripts/release-dag-full-coverage-check.js +6 -0
  87. package/dist/scripts/release-triwiki-first-runner-blackbox.js +5 -1
  88. package/dist/scripts/sks-3-1-5-directive-check-lib.js +1 -1
  89. package/dist/scripts/sks-401-all-feature-regression-blackbox.js +1 -1
  90. package/dist/scripts/update-concurrent-lock-check.js +1 -0
  91. package/dist/scripts/update-first-command-migration-check.js +4 -3
  92. package/package.json +13 -2
  93. package/schemas/mad-db/mad-db-capability.schema.json +92 -19
  94. package/schemas/update-migration.schema.json +13 -1
@@ -1,7 +1,9 @@
1
1
  import fsp from 'node:fs/promises';
2
2
  import path from 'node:path';
3
- import { ensureDir, exists, globalSksRoot, nowIso, packageRoot, PACKAGE_VERSION, projectRoot, readJson, runProcess, which, writeJsonAtomic } from '../fsx.js';
4
- export const UPDATE_MIGRATION_SCHEMA = 'sks.update-migration.v1';
3
+ import { ensureDir, exists, globalSksRoot, nowIso, packageRoot, PACKAGE_VERSION, projectRoot, readJson, runProcess, sha256, which, writeJsonAtomic } from '../fsx.js';
4
+ import { MANAGED_ASSET_VERSION } from '../managed-assets/managed-assets-manifest.js';
5
+ export const UPDATE_MIGRATION_SCHEMA = 'sks.project-migration-receipt.v2';
6
+ export const INSTALLATION_EPOCH_SCHEMA = 'sks.installation-epoch.v1';
5
7
  const ALLOWLIST_COMMANDS = new Set([
6
8
  'doctor',
7
9
  'postinstall',
@@ -12,16 +14,38 @@ const ALLOWLIST_COMMANDS = new Set([
12
14
  'commands',
13
15
  'usage',
14
16
  'root',
15
- 'rollback'
17
+ 'rollback',
18
+ 'status',
19
+ 'paths',
20
+ 'codex',
21
+ 'zellij'
16
22
  ]);
23
+ export function installationEpochPath() {
24
+ return path.join(globalSksRoot(), 'update', 'installation-epoch.json');
25
+ }
17
26
  export function pendingUpdateMigrationPath() {
18
- return path.join(globalSksRoot(), 'update', 'pending-migration.json');
27
+ return installationEpochPath();
19
28
  }
20
29
  export function projectUpdateMigrationReceiptPath(root) {
21
30
  return path.join(root, '.sneakoscope', 'update', 'migration-receipt.json');
22
31
  }
23
32
  export async function readPendingUpdateMigration() {
24
- return readJson(pendingUpdateMigrationPath(), null).catch(() => null);
33
+ const epoch = await readInstallationEpoch();
34
+ if (!epoch)
35
+ return null;
36
+ return {
37
+ schema: UPDATE_MIGRATION_SCHEMA,
38
+ status: 'pending_project_receipt',
39
+ sks_version: epoch.sks_version,
40
+ root: globalSksRoot(),
41
+ source: epoch.source,
42
+ generated_at: epoch.installed_at,
43
+ pending_marker_path: installationEpochPath(),
44
+ installation_epoch_path: installationEpochPath(),
45
+ installation_epoch_sha256: installationEpochSha256(epoch),
46
+ blockers: [],
47
+ warnings: []
48
+ };
25
49
  }
26
50
  export async function readProjectUpdateMigrationReceipt(root) {
27
51
  return readJson(projectUpdateMigrationReceiptPath(root), null).catch(() => null);
@@ -30,11 +54,25 @@ export function isUpdateMigrationReceiptCurrent(receipt) {
30
54
  return receipt?.schema === UPDATE_MIGRATION_SCHEMA
31
55
  && receipt.status === 'current'
32
56
  && receipt.sks_version === PACKAGE_VERSION
57
+ && typeof receipt.installation_epoch_sha256 === 'string'
33
58
  && Array.isArray(receipt.blockers)
34
- && receipt.blockers.length === 0;
59
+ && receipt.blockers.length === 0
60
+ && (!Array.isArray(receipt.required_blockers) || receipt.required_blockers.length === 0);
61
+ }
62
+ export async function readInstallationEpoch() {
63
+ return readJson(installationEpochPath(), null).catch(() => null);
64
+ }
65
+ export async function ensureInstallationEpoch(source = 'runtime') {
66
+ const current = await buildInstallationEpoch(source);
67
+ const existing = await readInstallationEpoch();
68
+ if (existing && isInstallationEpochCurrent(existing, current))
69
+ return existing;
70
+ await writeJsonAtomic(installationEpochPath(), current);
71
+ return current;
35
72
  }
36
73
  export async function writePendingUpdateMigration(input) {
37
- const pendingPath = pendingUpdateMigrationPath();
74
+ const epoch = await ensureInstallationEpoch(input.source);
75
+ const pendingPath = installationEpochPath();
38
76
  const receipt = {
39
77
  schema: UPDATE_MIGRATION_SCHEMA,
40
78
  status: 'pending_project_receipt',
@@ -43,18 +81,25 @@ export async function writePendingUpdateMigration(input) {
43
81
  source: input.source,
44
82
  generated_at: nowIso(),
45
83
  pending_marker_path: pendingPath,
84
+ installation_epoch_path: pendingPath,
85
+ installation_epoch_sha256: installationEpochSha256(epoch),
46
86
  doctor: input.doctor || null,
87
+ required_blockers: input.blockers || [],
88
+ optional_warnings: input.warnings || [],
47
89
  blockers: input.blockers || [],
48
90
  warnings: input.warnings || []
49
91
  };
50
- await writeJsonAtomic(pendingPath, receipt);
51
92
  return receipt;
52
93
  }
53
94
  export async function clearPendingUpdateMigration() {
54
- await fsp.rm(pendingUpdateMigrationPath(), { force: true }).catch(() => undefined);
95
+ // v2 keeps a persistent installation epoch; project receipts are compared
96
+ // independently and one project must not consume global migration state.
55
97
  }
56
98
  export async function writeProjectUpdateMigrationReceipt(input) {
57
99
  const receiptPath = projectUpdateMigrationReceiptPath(input.root);
100
+ const epoch = await ensureInstallationEpoch(input.source);
101
+ const requiredBlockers = input.blockers || [];
102
+ const optionalWarnings = input.warnings || [];
58
103
  const receipt = {
59
104
  schema: UPDATE_MIGRATION_SCHEMA,
60
105
  status: input.status || 'current',
@@ -62,15 +107,19 @@ export async function writeProjectUpdateMigrationReceipt(input) {
62
107
  root: input.root,
63
108
  source: input.source,
64
109
  generated_at: nowIso(),
65
- pending_marker_path: pendingUpdateMigrationPath(),
110
+ project_root_hash: projectRootHash(input.root),
111
+ installation_epoch_sha256: installationEpochSha256(epoch),
112
+ project_semantic_hash: await projectSemanticHash(input.root),
113
+ pending_marker_path: installationEpochPath(),
114
+ installation_epoch_path: installationEpochPath(),
66
115
  doctor: input.doctor || null,
67
116
  update_stages: input.updateStages || [],
68
- blockers: input.blockers || [],
69
- warnings: input.warnings || []
117
+ required_blockers: requiredBlockers,
118
+ optional_warnings: optionalWarnings,
119
+ blockers: requiredBlockers,
120
+ warnings: optionalWarnings
70
121
  };
71
122
  await writeJsonAtomic(receiptPath, receipt);
72
- if (isUpdateMigrationReceiptCurrent(receipt))
73
- await clearPendingUpdateMigration();
74
123
  return receipt;
75
124
  }
76
125
  export async function ensureCurrentMigrationBeforeCommand(input) {
@@ -78,13 +127,14 @@ export async function ensureCurrentMigrationBeforeCommand(input) {
78
127
  const command = input.command;
79
128
  const root = await projectRoot(input.cwd || process.cwd()).catch(() => path.resolve(input.cwd || process.cwd()));
80
129
  const receiptPath = projectUpdateMigrationReceiptPath(root);
81
- const pendingPath = pendingUpdateMigrationPath();
130
+ const pendingPath = installationEpochPath();
82
131
  const empty = {
83
132
  schema: 'sks.update-migration-gate.v1',
84
133
  root,
85
134
  command,
86
135
  receipt_path: receiptPath,
87
- pending_marker_path: pendingPath
136
+ pending_marker_path: pendingPath,
137
+ installation_epoch_path: pendingPath
88
138
  };
89
139
  if (env.SKS_UPDATE_MIGRATION_GATE_DISABLED === '1') {
90
140
  return { ...empty, ok: true, status: 'skipped', receipt: null, doctor: null, blockers: [], warnings: ['gate_disabled_by_env'] };
@@ -92,49 +142,57 @@ export async function ensureCurrentMigrationBeforeCommand(input) {
92
142
  if (ALLOWLIST_COMMANDS.has(command)) {
93
143
  return { ...empty, ok: true, status: 'skipped', receipt: null, doctor: null, blockers: [], warnings: [`allowlisted_command:${command}`] };
94
144
  }
95
- const [pending, receipt] = await Promise.all([
96
- readPendingUpdateMigration(),
145
+ const [epoch, receipt] = await Promise.all([
146
+ ensureInstallationEpoch('first-command-gate'),
97
147
  readProjectUpdateMigrationReceipt(root)
98
148
  ]);
99
149
  const requireReceipt = env.SKS_REQUIRE_UPDATE_MIGRATION_RECEIPT === '1';
100
- if (!pending && isUpdateMigrationReceiptCurrent(receipt) && !requireReceipt) {
150
+ if (isProjectReceiptCurrentForEpoch(receipt, epoch) && !requireReceipt) {
101
151
  return { ...empty, ok: true, status: 'current', receipt, doctor: null, blockers: [], warnings: [] };
102
152
  }
103
- if (!pending && !requireReceipt) {
104
- return { ...empty, ok: true, status: 'skipped', receipt: receipt || null, doctor: null, blockers: [], warnings: ['no_pending_update_migration'] };
105
- }
153
+ const recheck = requireReceipt
154
+ ? undefined
155
+ : async () => {
156
+ const fresh = await readProjectUpdateMigrationReceipt(root);
157
+ if (isProjectReceiptCurrentForEpoch(fresh, epoch)) {
158
+ return { ...empty, ok: true, status: 'current', receipt: fresh, doctor: null, blockers: [], warnings: [] };
159
+ }
160
+ return null;
161
+ };
106
162
  return withUpdateMigrationLock(root, empty, async () => {
163
+ const reportFile = path.join(root, '.sneakoscope', 'update', `doctor-migration-${Date.now()}.json`);
107
164
  const doctor = await runPackageLocalDoctor({
108
165
  root,
109
- args: ['doctor', '--fix', '--json'],
166
+ args: ['doctor', '--fix', '--yes', '--profile', 'migration', '--machine-only', '--report-file', reportFile],
110
167
  env: {
111
168
  ...env,
112
169
  SKS_UPDATE_MIGRATION_GATE_DISABLED: '1',
113
170
  SKS_DISABLE_UPDATE_CHECK: '1'
114
171
  },
115
- timeoutMs: 10 * 60 * 1000,
116
- maxOutputBytes: 128 * 1024
172
+ timeoutMs: 15_000,
173
+ maxOutputBytes: 32 * 1024
117
174
  });
118
175
  if (!doctor.ok) {
176
+ const requiredBlockers = doctor.required_blockers.length ? doctor.required_blockers : ['doctor_migration_profile_failed'];
119
177
  const blocked = await writeProjectUpdateMigrationReceipt({
120
178
  root,
121
179
  source: 'first-command-gate',
122
180
  status: 'blocked',
123
181
  doctor,
124
- blockers: ['update_migration_doctor_failed'],
125
- warnings: pending?.warnings || []
182
+ blockers: requiredBlockers,
183
+ warnings: doctor.optional_warnings
126
184
  });
127
- return { ...empty, ok: false, status: 'blocked', receipt: blocked, doctor, blockers: ['update_migration_doctor_failed'], warnings: [] };
185
+ return { ...empty, ok: false, status: 'blocked', receipt: blocked, doctor, blockers: requiredBlockers, warnings: doctor.optional_warnings };
128
186
  }
129
187
  const current = await writeProjectUpdateMigrationReceipt({
130
188
  root,
131
189
  source: 'first-command-gate',
132
190
  doctor,
133
191
  blockers: [],
134
- warnings: pending?.warnings || []
192
+ warnings: doctor.optional_warnings
135
193
  });
136
194
  return { ...empty, ok: true, status: 'repaired', receipt: current, doctor, blockers: [], warnings: [] };
137
- });
195
+ }, recheck ? { recheck } : {});
138
196
  }
139
197
  export async function runPostinstallGlobalDoctorAndMarkPending(input = {}) {
140
198
  const env = input.env || process.env;
@@ -148,15 +206,15 @@ export async function runPostinstallGlobalDoctorAndMarkPending(input = {}) {
148
206
  }
149
207
  const doctor = await runPackageLocalDoctor({
150
208
  root: globalSksRoot(),
151
- args: ['doctor', '--fix', '--json'],
209
+ args: ['doctor', '--fix', '--yes', '--profile', 'migration', '--machine-only', '--report-file', path.join(globalSksRoot(), 'update', `postinstall-doctor-${Date.now()}.json`)],
152
210
  env: {
153
211
  ...env,
154
212
  SKS_UPDATE_MIGRATION_GATE_DISABLED: '1',
155
213
  SKS_DISABLE_UPDATE_CHECK: '1',
156
214
  SKS_POSTINSTALL_NO_BOOTSTRAP: '1'
157
215
  },
158
- timeoutMs: 10 * 60 * 1000,
159
- maxOutputBytes: 128 * 1024
216
+ timeoutMs: 15_000,
217
+ maxOutputBytes: 32 * 1024
160
218
  });
161
219
  const pending = await writePendingUpdateMigration({
162
220
  source: 'postinstall',
@@ -186,6 +244,8 @@ export async function runPackageLocalDoctor(input = {}) {
186
244
  args,
187
245
  exit_code: null,
188
246
  parsed_ok: null,
247
+ required_blockers: ['missing_package_local_sks_entrypoint'],
248
+ optional_warnings: [],
189
249
  stdout_tail: '',
190
250
  stderr_tail: '',
191
251
  error: `missing package-local sks entrypoint: ${entrypoint}`
@@ -206,9 +266,14 @@ export async function runPackageLocalDoctor(input = {}) {
206
266
  stdout: '',
207
267
  stderr: err?.message || String(err)
208
268
  }));
209
- const parsed = parseDoctorJson(result.stdout);
269
+ const reportFile = reportFileFromArgs(args);
270
+ const parsed = reportFile
271
+ ? await readJson(reportFile, null).catch(() => null)
272
+ : parseDoctorJson(result.stdout);
210
273
  const parsedOk = typeof parsed?.ok === 'boolean' ? parsed.ok : null;
211
- const ok = result.code === 0 && parsedOk !== false;
274
+ const ok = result.code === 0 && (reportFile ? parsedOk === true : parsedOk !== false);
275
+ const requiredBlockers = extractRequiredBlockers(parsed, ok);
276
+ const optionalWarnings = extractOptionalWarnings(parsed);
212
277
  return {
213
278
  schema: 'sks.package-local-doctor-run.v1',
214
279
  ok,
@@ -218,9 +283,11 @@ export async function runPackageLocalDoctor(input = {}) {
218
283
  args,
219
284
  exit_code: result.code ?? null,
220
285
  parsed_ok: parsedOk,
286
+ required_blockers: requiredBlockers,
287
+ optional_warnings: optionalWarnings,
221
288
  stdout_tail: tail(result.stdout || ''),
222
289
  stderr_tail: tail(result.stderr || ''),
223
- error: ok ? null : tail(result.stderr || result.stdout || 'doctor failed')
290
+ error: ok ? null : tail(result.stderr || result.stdout || requiredBlockers.join(', ') || 'doctor failed')
224
291
  };
225
292
  }
226
293
  export async function resolveInstalledSksEntrypoint(input = {}) {
@@ -235,27 +302,145 @@ export async function resolveInstalledSksEntrypoint(input = {}) {
235
302
  }
236
303
  return which('sks');
237
304
  }
238
- async function withUpdateMigrationLock(root, base, fn) {
305
+ const MIGRATION_LOCK_WAIT_MS = 20_000;
306
+ const MIGRATION_LOCK_POLL_MS = 150;
307
+ function delay(ms) {
308
+ return new Promise((resolve) => setTimeout(resolve, ms));
309
+ }
310
+ async function withUpdateMigrationLock(root, base, fn, options = {}) {
239
311
  const lockPath = path.join(root, '.sneakoscope', 'update', 'migration.lock');
240
312
  await ensureDir(path.dirname(lockPath));
241
- let handle = null;
242
- try {
243
- handle = await fsp.open(lockPath, 'wx');
244
- await handle.writeFile(JSON.stringify({ pid: process.pid, created_at: nowIso(), version: PACKAGE_VERSION }) + '\n', 'utf8');
245
- return await fn();
246
- }
247
- catch (err) {
248
- if (err?.code === 'EEXIST') {
313
+ const recheck = options.recheck ?? null;
314
+ const deadline = Date.now() + (options.maxWaitMs ?? MIGRATION_LOCK_WAIT_MS);
315
+ let reapedStale = false;
316
+ for (;;) {
317
+ let handle = null;
318
+ try {
319
+ handle = await fsp.open(lockPath, 'wx');
320
+ }
321
+ catch (err) {
322
+ if (err?.code !== 'EEXIST') {
323
+ return { ...base, ok: false, status: 'blocked', receipt: null, doctor: null, blockers: [`update_migration_lock_error:${err?.message || String(err)}`], warnings: [] };
324
+ }
325
+ // The lock is held by a concurrent process. Cooperate instead of failing fast:
326
+ // 1) a sibling may have already completed the migration we need.
327
+ if (recheck) {
328
+ const done = await recheck();
329
+ if (done)
330
+ return done;
331
+ }
332
+ // 2) reap a genuinely stale lock (dead holder or older than the stale threshold).
333
+ if (!reapedStale && await removeStaleMigrationLock(lockPath)) {
334
+ reapedStale = true;
335
+ continue;
336
+ }
337
+ // 3) wait for the in-flight holder to finish, then retry acquisition.
338
+ if (Date.now() < deadline) {
339
+ await delay(MIGRATION_LOCK_POLL_MS);
340
+ continue;
341
+ }
342
+ // 4) gave up waiting on a live holder.
249
343
  return { ...base, ok: false, status: 'blocked', receipt: null, doctor: null, blockers: ['update_migration_lock_held'], warnings: [] };
250
344
  }
251
- return { ...base, ok: false, status: 'blocked', receipt: null, doctor: null, blockers: [`update_migration_lock_error:${err?.message || String(err)}`], warnings: [] };
252
- }
253
- finally {
254
- await handle?.close().catch(() => undefined);
255
- if (handle)
345
+ try {
346
+ await handle.writeFile(JSON.stringify({ pid: process.pid, created_at: nowIso(), version: PACKAGE_VERSION }) + '\n', 'utf8');
347
+ return await fn();
348
+ }
349
+ catch (err) {
350
+ return { ...base, ok: false, status: 'blocked', receipt: null, doctor: null, blockers: [`update_migration_lock_error:${err?.message || String(err)}`], warnings: [] };
351
+ }
352
+ finally {
353
+ await handle.close().catch(() => undefined);
256
354
  await fsp.rm(lockPath, { force: true }).catch(() => undefined);
355
+ }
257
356
  }
258
357
  }
358
+ async function removeStaleMigrationLock(lockPath) {
359
+ const raw = await fsp.readFile(lockPath, 'utf8').catch(() => '');
360
+ let parsed = null;
361
+ try {
362
+ parsed = raw.trim() ? JSON.parse(raw) : null;
363
+ }
364
+ catch {
365
+ parsed = null;
366
+ }
367
+ const pid = Number(parsed?.pid || 0);
368
+ const createdMs = parsed?.created_at ? Date.parse(parsed.created_at) : 0;
369
+ const ageMs = Number.isFinite(createdMs) && createdMs > 0 ? Date.now() - createdMs : Number.POSITIVE_INFINITY;
370
+ const stale = !pidAlive(pid) || ageMs > 120_000;
371
+ if (!stale)
372
+ return false;
373
+ await fsp.rm(lockPath, { force: true }).catch(() => undefined);
374
+ return true;
375
+ }
376
+ function pidAlive(pid) {
377
+ if (!Number.isInteger(pid) || pid <= 0)
378
+ return false;
379
+ try {
380
+ process.kill(pid, 0);
381
+ return true;
382
+ }
383
+ catch (err) {
384
+ return err?.code === 'EPERM';
385
+ }
386
+ }
387
+ async function buildInstallationEpoch(source) {
388
+ const root = packageRoot();
389
+ const realpath = await fsp.realpath(root).catch(() => root);
390
+ return {
391
+ schema: INSTALLATION_EPOCH_SCHEMA,
392
+ sks_version: PACKAGE_VERSION,
393
+ package_realpath: realpath,
394
+ build_sha256: await packageBuildSha256(root),
395
+ managed_asset_version: MANAGED_ASSET_VERSION,
396
+ installed_at: nowIso(),
397
+ source
398
+ };
399
+ }
400
+ function isInstallationEpochCurrent(existing, current) {
401
+ return existing.schema === INSTALLATION_EPOCH_SCHEMA
402
+ && existing.sks_version === current.sks_version
403
+ && existing.package_realpath === current.package_realpath
404
+ && existing.build_sha256 === current.build_sha256
405
+ && existing.managed_asset_version === current.managed_asset_version;
406
+ }
407
+ async function packageBuildSha256(root) {
408
+ const candidates = [
409
+ path.join(root, 'dist', 'build-manifest.json'),
410
+ path.join(root, 'package.json')
411
+ ];
412
+ const rows = await Promise.all(candidates.map(async (file) => {
413
+ const text = await fsp.readFile(file, 'utf8').catch(() => '');
414
+ return { file: path.relative(root, file), sha256: text ? sha256(text) : 'missing' };
415
+ }));
416
+ return sha256(JSON.stringify(rows));
417
+ }
418
+ function installationEpochSha256(epoch) {
419
+ return sha256(JSON.stringify({
420
+ schema: epoch.schema,
421
+ sks_version: epoch.sks_version,
422
+ package_realpath: epoch.package_realpath,
423
+ build_sha256: epoch.build_sha256,
424
+ managed_asset_version: epoch.managed_asset_version
425
+ }));
426
+ }
427
+ function isProjectReceiptCurrentForEpoch(receipt, epoch) {
428
+ return isUpdateMigrationReceiptCurrent(receipt)
429
+ && receipt?.installation_epoch_sha256 === installationEpochSha256(epoch);
430
+ }
431
+ function projectRootHash(root) {
432
+ return sha256(path.resolve(root));
433
+ }
434
+ async function projectSemanticHash(root) {
435
+ const configPath = path.join(root, '.codex', 'config.toml');
436
+ const config = await fsp.readFile(configPath, 'utf8').catch(() => '');
437
+ return sha256(JSON.stringify({
438
+ root: projectRootHash(root),
439
+ sks_version: PACKAGE_VERSION,
440
+ managed_asset_version: MANAGED_ASSET_VERSION,
441
+ codex_config_sha256: config ? sha256(config) : 'missing'
442
+ }));
443
+ }
259
444
  function parseDoctorJson(text) {
260
445
  const trimmed = String(text || '').trim();
261
446
  if (!trimmed)
@@ -273,6 +458,36 @@ function parseDoctorJson(text) {
273
458
  }
274
459
  return null;
275
460
  }
461
+ function reportFileFromArgs(args) {
462
+ const index = args.indexOf('--report-file');
463
+ return index >= 0 && args[index + 1] ? String(args[index + 1]) : null;
464
+ }
465
+ function extractRequiredBlockers(parsed, ok) {
466
+ if (ok)
467
+ return [];
468
+ const candidates = [
469
+ parsed?.ready?.blockers,
470
+ parsed?.ready?.repair_readiness?.blockers,
471
+ parsed?.doctor_fix_postcheck?.required_blockers,
472
+ parsed?.doctor_fix_postcheck?.blockers,
473
+ parsed?.blockers
474
+ ];
475
+ for (const value of candidates) {
476
+ if (Array.isArray(value) && value.length)
477
+ return [...new Set(value.map(String).filter(Boolean))];
478
+ }
479
+ return [];
480
+ }
481
+ function extractOptionalWarnings(parsed) {
482
+ const candidates = [
483
+ parsed?.ready?.warnings,
484
+ parsed?.ready?.repair_readiness?.warnings,
485
+ parsed?.doctor_fix_postcheck?.optional_warnings,
486
+ parsed?.doctor_native_capability?.optional_warnings,
487
+ parsed?.warnings
488
+ ];
489
+ return [...new Set(candidates.flatMap((value) => Array.isArray(value) ? value.map(String) : []).filter(Boolean))];
490
+ }
276
491
  function tail(text, max = 4096) {
277
492
  const raw = String(text || '');
278
493
  return raw.length <= max ? raw : raw.slice(raw.length - max);
@@ -191,10 +191,10 @@ export async function runSksUpdateNow(options = {}) {
191
191
  }
192
192
  const oldVersionDoctor = await runPackageLocalDoctor({
193
193
  root: projectReceiptRoot,
194
- args: ['doctor', '--json'],
194
+ args: ['doctor', '--fix', '--yes', '--profile', 'migration', '--machine-only', '--report-file', path.join(projectReceiptRoot, '.sneakoscope', 'update', `old-version-doctor-${Date.now()}.json`)],
195
195
  env,
196
- timeoutMs: options.timeoutMs ?? 10 * 60 * 1000,
197
- maxOutputBytes: options.maxOutputBytes ?? 128 * 1024
196
+ timeoutMs: 15_000,
197
+ maxOutputBytes: 32 * 1024
198
198
  });
199
199
  stage('old_version_doctor_preflight', oldVersionDoctor.ok, oldVersionDoctor.status, { entrypoint: oldVersionDoctor.entrypoint, exit_code: oldVersionDoctor.exit_code });
200
200
  if (!oldVersionDoctor.ok && env.SKS_UPDATE_SKIP_OLD_DOCTOR_PREFLIGHT !== '1') {
@@ -292,10 +292,10 @@ export async function runSksUpdateNow(options = {}) {
292
292
  newVersionDoctor = await runPackageLocalDoctor({
293
293
  root: globalSksRootPath(),
294
294
  entrypoint: newBinary,
295
- args: ['doctor', '--json'],
295
+ args: ['doctor', '--fix', '--yes', '--profile', 'migration', '--machine-only', '--report-file', path.join(globalSksRootPath(), 'update', `new-version-doctor-${Date.now()}.json`)],
296
296
  env,
297
- timeoutMs: options.timeoutMs ?? 10 * 60 * 1000,
298
- maxOutputBytes: options.maxOutputBytes ?? 128 * 1024
297
+ timeoutMs: 15_000,
298
+ maxOutputBytes: 32 * 1024
299
299
  });
300
300
  stage('new_version_global_doctor', newVersionDoctor.ok, newVersionDoctor.status, { entrypoint: newBinary, exit_code: newVersionDoctor.exit_code });
301
301
  }
@@ -1,2 +1,2 @@
1
- export const PACKAGE_VERSION = '4.1.0';
1
+ export const PACKAGE_VERSION = '4.2.0';
2
2
  //# sourceMappingURL=version.js.map
@@ -47,7 +47,7 @@ export async function launchZellijLayout(opts = {}) {
47
47
  && capability.status === 'ok'
48
48
  && process.env.SKS_ZELLIJ_KEEP_SESSION !== '1';
49
49
  const sessionReset = resetSession
50
- ? await runZellij(['kill-session', sessionName], { cwd: opts.cwd || root, timeoutMs: 5000, optional: true })
50
+ ? await runZellij(['kill-session', sessionName], { cwd: opts.cwd || root, timeoutMs: 200, optional: true })
51
51
  : null;
52
52
  const launch = opts.dryRun === true || capability.status !== 'ok'
53
53
  ? null
@@ -65,10 +65,21 @@ export async function launchZellijLayout(opts = {}) {
65
65
  };
66
66
  if (layout.main_pane_kind === 'codex_interactive')
67
67
  paneProofOpts.expectedMainCommandIncludes = 'codex';
68
- const paneProof = await writeZellijPaneProof(root, paneProofOpts).catch((err) => ({
69
- ok: false,
70
- blockers: [`zellij_pane_proof_exception:${err?.message || String(err)}`]
71
- }));
68
+ const strictPaneProof = opts.requireZellij === true || opts.dryRun === true || process.env.SKS_ZELLIJ_STRICT_PANE_PROOF === '1';
69
+ const paneProof = strictPaneProof
70
+ ? await writeZellijPaneProof(root, paneProofOpts).catch((err) => ({
71
+ ok: false,
72
+ blockers: [`zellij_pane_proof_exception:${err?.message || String(err)}`]
73
+ }))
74
+ : {
75
+ ok: true,
76
+ status: 'deferred_background',
77
+ blockers: [],
78
+ warnings: ['zellij_pane_proof_deferred_until_after_attach']
79
+ };
80
+ if (!strictPaneProof && opts.dryRun !== true && capability.status === 'ok') {
81
+ void writeZellijPaneProof(root, paneProofOpts).catch(() => undefined);
82
+ }
72
83
  if (launch?.create_background) {
73
84
  launch.create_background = normalizeExistingZellijSession(sessionName, launch.create_background);
74
85
  }
@@ -113,6 +124,7 @@ export async function launchZellijLayout(opts = {}) {
113
124
  clipboard_mouse_mode: clipboard.mouse_mode,
114
125
  pane_proof_path: path.join(root, '.sneakoscope', 'missions', missionId, 'zellij-pane-proof.json'),
115
126
  pane_proof: paneProof,
127
+ pane_proof_background: !strictPaneProof,
116
128
  dry_run: opts.dryRun === true,
117
129
  capability,
118
130
  launch,
@@ -115,7 +115,11 @@ function renderTelemetrySlotRows(snapshot) {
115
115
  });
116
116
  }
117
117
  function isMadDbActive(capability) {
118
- if (!capability || capability.enabled !== true || capability.consumed === true)
118
+ if (!capability)
119
+ return false;
120
+ if (capability.schema === 'sks.mad-db-capability.v2' && !['transport_ready', 'active'].includes(String(capability.status || '')))
121
+ return false;
122
+ if (capability.schema !== 'sks.mad-db-capability.v2' && (capability.enabled !== true || capability.consumed === true))
119
123
  return false;
120
124
  const expires = Date.parse(capability.expires_at || '');
121
125
  return Number.isFinite(expires) && expires > Date.now();
@@ -8,6 +8,7 @@ import { sourceSnapshot } from './lib/ensure-dist-fresh.js';
8
8
  const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
9
9
  const distRoot = path.join(root, 'dist');
10
10
  const issues = [];
11
+ const contractOnlyMarker = 'contract' + '_only';
11
12
  if (!fs.existsSync(distRoot))
12
13
  issues.push('dist_missing');
13
14
  requiredFile('dist/bin/sks.js');
@@ -63,8 +64,8 @@ if (fs.existsSync(distRoot)) {
63
64
  if (!rel.endsWith('.js'))
64
65
  continue;
65
66
  const text = fs.readFileSync(file, 'utf8');
66
- if (text.includes('contract_only'))
67
- issues.push(`contract_only:${rel}`);
67
+ if (text.includes(contractOnlyMarker))
68
+ issues.push(`${contractOnlyMarker}:${rel}`);
68
69
  if (/from\s+['"][^'"]+\.mjs['"]|import\(\s*['"][^'"]+\.mjs['"]\s*\)/.test(text)) {
69
70
  issues.push(`imports_mjs:${rel}`);
70
71
  }
@@ -8,11 +8,12 @@ const manifest = parity.manifest;
8
8
  const dep = pkg.dependencies?.['@openai/codex-sdk'];
9
9
  const lockSdk = lock.packages?.['node_modules/@openai/codex-sdk']?.version;
10
10
  const lockCli = lock.packages?.['node_modules/@openai/codex']?.version;
11
+ const lockRootVersion = lock.packages?.['']?.version || lock.version;
11
12
  assertGate(parity.ok, 'Codex release manifest TS/JSON parity must hold', parity);
12
13
  assertGate(dep === manifest.sdkVersion, 'package.json must pin @openai/codex-sdk exactly to manifest sdkVersion', { dep, sdkVersion: manifest.sdkVersion });
13
14
  assertGate(lockSdk === manifest.sdkVersion, 'package-lock must resolve @openai/codex-sdk to manifest sdkVersion', { lockSdk, sdkVersion: manifest.sdkVersion });
14
15
  assertGate(lockCli === manifest.requiredCliVersion, 'package-lock must resolve @openai/codex to manifest requiredCliVersion', { lockCli, requiredCliVersion: manifest.requiredCliVersion });
15
- assertGate(pkg.version === '4.1.0', 'package version must be 4.1.0', { version: pkg.version });
16
+ assertGate(pkg.version === lockRootVersion, 'package version must match package-lock root version', { version: pkg.version, lockRootVersion });
16
17
  emitGate('codex:0142:manifest', {
17
18
  manifest_sha256: parity.manifest_sha256,
18
19
  target_tag: manifest.targetTag,
@@ -113,6 +113,12 @@ const ALLOWLIST = [
113
113
  pattern: /writeJsonAtomic|writeTextAtomic/,
114
114
  reason: 'migration journal writes hashes and rollback metadata, not raw secret config values',
115
115
  expires: '3.2.0'
116
+ },
117
+ {
118
+ file: 'src/core/providers/glm/naruto/glm-naruto-trace.ts',
119
+ pattern: /mission-result\.json|sanitizeArtifact/,
120
+ reason: 'GLM Naruto trace writer persists sanitized mission-result proof artifacts, not raw env secret files',
121
+ expires: '4.2.0'
116
122
  }
117
123
  ];
118
124
  const sources = listSourceFiles().map((file) => ({
@@ -4,6 +4,6 @@ import { importDist } from './sks-1-18-gate-lib.js';
4
4
  const tmp = await makeTempRoot('doctor-dirty-plan-');
5
5
  const mod = await importDist('core/doctor/doctor-dirty-planner.js');
6
6
  const plan = mod.planDoctorDirtyRepair(tmp, ['setup', 'context7_repair']);
7
- assertGate(plan.schema === 'sks.doctor-dirty-plan.v1' && plan.dirty_count === 2, 'dirty planner must mark missing markers dirty', plan);
7
+ assertGate(plan.schema === 'sks.doctor-dirty-plan.v2' && plan.dirty_count === 2, 'dirty planner must mark missing markers dirty', plan);
8
8
  emitGate('doctor:dirty-plan', { dirty: plan.dirty_count });
9
9
  //# sourceMappingURL=doctor-dirty-plan-check.js.map
@@ -27,5 +27,6 @@ const postcheck = doctorRepairPostcheck(tx);
27
27
  assertGate(rollbackCalled && tx.rollback_performed === true, 'doctor transaction runner must execute rollback hooks for failed phases', tx);
28
28
  assertGate(tx.ok === false && postcheck.ok === false, 'required failed phase must block readiness', { tx, postcheck });
29
29
  assertGate(tx.phases.find((phase) => phase.id === 'optional_manual')?.required_for_ready === false, 'optional manual phase must be explicitly marked', tx);
30
+ assertGate(!postcheck.blockers.includes('operator_action_required') && postcheck.optional_warnings.includes('optional:operator_action_required'), 'optional blockers must stay out of required blocker output', { tx, postcheck });
30
31
  emitGate('doctor:transaction-engine', { phases: tx.phases.length });
31
32
  //# sourceMappingURL=doctor-transaction-engine-check.js.map