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.
- package/README.md +16 -3
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/crates/sks-core/src/main.rs +1 -1
- package/dist/bin/sks.js +1 -1
- package/dist/cli/command-registry.js +1 -1
- package/dist/cli/router.js +6 -1
- package/dist/commands/doctor.js +272 -127
- package/dist/core/auto-review.js +1 -1
- package/dist/core/codex/agent-config-file-repair.js +43 -2
- package/dist/core/codex-app/codex-agent-role-sync.js +4 -4
- package/dist/core/codex-control/codex-0142-capability.js +51 -6
- package/dist/core/codex-control/codex-app-server-v2-client.js +2 -2
- package/dist/core/codex-native/codex-native-feature-broker.js +50 -0
- package/dist/core/codex-native/native-capability-postcheck.js +59 -16
- package/dist/core/codex-native/native-capability-repair-matrix.js +77 -13
- package/dist/core/commands/mad-db-command.js +146 -51
- package/dist/core/commands/mad-sks-command.js +51 -61
- package/dist/core/db-safety.js +35 -37
- package/dist/core/doctor/doctor-dirty-planner.js +9 -4
- package/dist/core/doctor/doctor-native-capability-repair.js +42 -7
- package/dist/core/doctor/doctor-readiness-matrix.js +9 -5
- package/dist/core/doctor/doctor-repair-postcheck.js +10 -1
- package/dist/core/doctor/doctor-transaction.js +1 -1
- package/dist/core/doctor/supabase-mcp-repair.js +2 -2
- package/dist/core/feature-registry.js +1 -1
- package/dist/core/fsx.js +1 -1
- package/dist/core/init.js +5 -4
- package/dist/core/mad-db/mad-db-capability.js +203 -74
- package/dist/core/mad-db/mad-db-coordinator.js +287 -0
- package/dist/core/mad-db/mad-db-executor.js +156 -0
- package/dist/core/mad-db/mad-db-ledger.js +1 -1
- package/dist/core/mad-db/mad-db-lock.js +40 -0
- package/dist/core/mad-db/mad-db-operation-store.js +140 -0
- package/dist/core/mad-db/mad-db-policy-resolver.js +42 -22
- package/dist/core/mad-db/mad-db-policy.js +195 -0
- package/dist/core/mad-db/mad-db-postconditions.js +30 -0
- package/dist/core/mad-db/mad-db-recovery.js +27 -0
- package/dist/core/mad-db/mad-db-result-lifecycle.js +31 -102
- package/dist/core/mad-db/mad-db-runtime-profile.js +121 -0
- package/dist/core/mad-db/mad-db-target.js +64 -0
- package/dist/core/managed-assets/managed-assets-manifest.js +14 -4
- package/dist/core/pipeline-internals/runtime-core.js +40 -0
- package/dist/core/providers/glm/bench/glm-benchmark-runner.js +4 -3
- package/dist/core/providers/glm/bench/glm-benchmark-types.js +1 -1
- package/dist/core/release/release-gate-dag.js +6 -5
- package/dist/core/routes.js +23 -8
- package/dist/core/update/update-migration-state.js +265 -50
- package/dist/core/update-check.js +6 -6
- package/dist/core/version.js +1 -1
- package/dist/core/zellij/zellij-launcher.js +17 -5
- package/dist/core/zellij/zellij-slot-column-anchor.js +5 -1
- package/dist/scripts/check-dist-runtime.js +3 -2
- package/dist/scripts/codex-0142-manifest-check.js +2 -1
- package/dist/scripts/config-managed-merge-callsite-coverage-check.js +6 -0
- package/dist/scripts/doctor-dirty-plan-check.js +1 -1
- package/dist/scripts/doctor-transaction-engine-check.js +1 -0
- package/dist/scripts/doctor-warning-only-not-blocker-check.js +18 -1
- package/dist/scripts/loop-directive-check-lib.js +2 -1
- package/dist/scripts/mad-db-capability-check.js +13 -2
- package/dist/scripts/mad-db-command-check.js +7 -5
- package/dist/scripts/mad-db-hook-idempotency-check.js +21 -0
- package/dist/scripts/mad-db-ledger-check.js +2 -1
- package/dist/scripts/mad-db-lifecycle-hook-decision-check.js +5 -4
- package/dist/scripts/mad-db-mad-command-check.js +29 -16
- package/dist/scripts/mad-db-mcp-result-lifecycle-check.js +11 -10
- package/dist/scripts/mad-db-one-cycle-bounded-check.js +15 -18
- package/dist/scripts/mad-db-one-cycle-consumption-check.js +3 -3
- package/dist/scripts/mad-db-operation-lifecycle-blackbox.js +9 -9
- package/dist/scripts/mad-db-operation-lifecycle-ledger-check.js +6 -6
- package/dist/scripts/mad-db-parallel-lifecycle-check.js +24 -0
- package/dist/scripts/mad-db-policy-v2-check.js +20 -0
- package/dist/scripts/mad-db-priority-resolver-check.js +5 -5
- package/dist/scripts/mad-db-real-supabase-e2e.js +166 -0
- package/dist/scripts/mad-db-route-identity-check.js +28 -0
- package/dist/scripts/mad-db-runtime-profile-lifecycle-check.js +24 -0
- package/dist/scripts/mad-db-safety-conflict-matrix-check.js +3 -3
- package/dist/scripts/mad-db-skill-policy-snapshot-check.js +15 -0
- package/dist/scripts/mad-sks-zellij-launch-check.js +7 -1
- package/dist/scripts/managed-role-manifest-parity-check.js +4 -1
- package/dist/scripts/naruto-real-parallelism-blackbox.js +17 -4
- package/dist/scripts/native-capability-postcheck-check.js +1 -0
- package/dist/scripts/native-capability-repair-matrix-check.js +2 -0
- package/dist/scripts/native-chrome-web-review-repair-check.js +1 -0
- package/dist/scripts/native-computer-use-repair-check.js +1 -0
- package/dist/scripts/release-dag-full-coverage-check.js +6 -0
- package/dist/scripts/release-triwiki-first-runner-blackbox.js +5 -1
- package/dist/scripts/sks-3-1-5-directive-check-lib.js +1 -1
- package/dist/scripts/sks-401-all-feature-regression-blackbox.js +1 -1
- package/dist/scripts/update-concurrent-lock-check.js +1 -0
- package/dist/scripts/update-first-command-migration-check.js +4 -3
- package/package.json +13 -2
- package/schemas/mad-db/mad-db-capability.schema.json +92 -19
- 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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
69
|
-
|
|
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 =
|
|
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 [
|
|
96
|
-
|
|
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 (
|
|
150
|
+
if (isProjectReceiptCurrentForEpoch(receipt, epoch) && !requireReceipt) {
|
|
101
151
|
return { ...empty, ok: true, status: 'current', receipt, doctor: null, blockers: [], warnings: [] };
|
|
102
152
|
}
|
|
103
|
-
|
|
104
|
-
|
|
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', '--
|
|
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:
|
|
116
|
-
maxOutputBytes:
|
|
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:
|
|
125
|
-
warnings:
|
|
182
|
+
blockers: requiredBlockers,
|
|
183
|
+
warnings: doctor.optional_warnings
|
|
126
184
|
});
|
|
127
|
-
return { ...empty, ok: false, status: 'blocked', receipt: blocked, doctor, blockers:
|
|
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:
|
|
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:
|
|
159
|
-
maxOutputBytes:
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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:
|
|
197
|
-
maxOutputBytes:
|
|
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:
|
|
298
|
-
maxOutputBytes:
|
|
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
|
}
|
package/dist/core/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const PACKAGE_VERSION = '4.
|
|
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:
|
|
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
|
|
69
|
-
|
|
70
|
-
|
|
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
|
|
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(
|
|
67
|
-
issues.push(
|
|
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 ===
|
|
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.
|
|
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
|