sneakoscope 4.0.1 → 4.0.3
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 +7 -7
- 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 -0
- package/dist/cli/global-mode-router.js +25 -0
- package/dist/cli/router.js +12 -0
- package/dist/commands/codex-app.js +10 -1
- package/dist/commands/codex.js +15 -1
- package/dist/core/build/build-once-runner.js +49 -16
- package/dist/core/codex-app/glm-model-profile.js +2 -0
- package/dist/core/codex-app/glm-profile-installer.js +61 -0
- package/dist/core/codex-app/glm-profile-schema.js +24 -0
- package/dist/core/codex-control/codex-0141-capability.js +95 -0
- package/dist/core/commands/daemon-command.js +1 -1
- package/dist/core/commands/glm-command.js +5 -0
- package/dist/core/daemon/sksd-client.js +7 -2
- package/dist/core/daemon/sksd-ipc.js +13 -3
- package/dist/core/daemon/sksd.js +62 -3
- package/dist/core/doctor/doctor-dirty-planner.js +97 -7
- package/dist/core/doctor/doctor-transaction.js +21 -5
- package/dist/core/fsx.js +1 -1
- package/dist/core/providers/glm/glm-52-profile.js +30 -0
- package/dist/core/providers/glm/glm-52-request.js +34 -0
- package/dist/core/providers/glm/glm-52-response-guard.js +34 -0
- package/dist/core/providers/glm/glm-52-settings.js +26 -0
- package/dist/core/providers/glm/glm-mad-mode.js +242 -0
- package/dist/core/providers/openrouter/openrouter-client.js +44 -0
- package/dist/core/providers/openrouter/openrouter-error.js +37 -0
- package/dist/core/providers/openrouter/openrouter-secret-store.js +113 -0
- package/dist/core/providers/openrouter/openrouter-types.js +2 -0
- package/dist/core/release/extreme-parallel-scheduler.js +124 -11
- package/dist/core/release/gate-pack-assertion.js +58 -0
- package/dist/core/release/gate-pack-fixture-cache.js +38 -2
- package/dist/core/release/gate-pack-manifest.js +2 -2
- package/dist/core/release/gate-pack-runner.js +9 -93
- package/dist/core/release/release-gate-cache-v2.js +71 -0
- package/dist/core/release/release-gate-dag.js +50 -5
- package/dist/core/release/release-gate-node.js +2 -0
- package/dist/core/release/release-gate-resource-governor.js +2 -0
- package/dist/core/release/resource-class-budget.js +1 -0
- package/dist/core/results.js +2 -0
- package/dist/core/secret-redaction.js +4 -0
- package/dist/core/security/redact-secrets.js +15 -0
- package/dist/core/triwiki/triwiki-affected-graph.js +35 -3
- package/dist/core/triwiki/triwiki-gate-impact-map.js +2 -0
- package/dist/core/triwiki/triwiki-proof-bank.js +12 -2
- package/dist/core/triwiki/triwiki-sla-certificate.js +3 -0
- package/dist/core/version.js +1 -1
- package/dist/scripts/release-4002-required-gates.js +14 -0
- package/dist/scripts/release-gate-dag-runner.js +2 -1
- package/dist/scripts/release-gate-existence-audit.js +1 -2
- package/package.json +16 -3
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { redactOpenRouterKey } from '../../security/redact-secrets.js';
|
|
6
|
+
export const OPENROUTER_KEY_ENV_NAMES = ['OPENROUTER_API_KEY', 'SKS_OPENROUTER_API_KEY'];
|
|
7
|
+
export function openRouterSecretPaths(env = process.env) {
|
|
8
|
+
const sksHome = path.resolve(env.SKS_HOME || path.join(env.HOME || os.homedir(), '.sneakoscope'));
|
|
9
|
+
const secretDir = path.join(sksHome, 'secrets');
|
|
10
|
+
return {
|
|
11
|
+
sksHome,
|
|
12
|
+
secretDir,
|
|
13
|
+
keyPath: path.join(secretDir, 'openrouter-api-key'),
|
|
14
|
+
metadataPath: path.join(secretDir, 'openrouter-api-key.json')
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export async function resolveOpenRouterApiKey(input = {}) {
|
|
18
|
+
const env = input.env || process.env;
|
|
19
|
+
for (const name of OPENROUTER_KEY_ENV_NAMES) {
|
|
20
|
+
const value = String(env[name] || '').trim();
|
|
21
|
+
if (value) {
|
|
22
|
+
return {
|
|
23
|
+
key: value,
|
|
24
|
+
source: 'env',
|
|
25
|
+
env_var: name,
|
|
26
|
+
key_preview: redactOpenRouterKey(value),
|
|
27
|
+
blockers: [],
|
|
28
|
+
warnings: name === 'OPENROUTER_API_KEY' ? [] : ['using_sks_openrouter_api_key_env']
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const stored = await readStoredOpenRouterKey(input.paths || openRouterSecretPaths(env));
|
|
33
|
+
if (stored) {
|
|
34
|
+
return {
|
|
35
|
+
key: stored,
|
|
36
|
+
source: 'user-secret-store',
|
|
37
|
+
key_preview: redactOpenRouterKey(stored),
|
|
38
|
+
blockers: [],
|
|
39
|
+
warnings: []
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
key: null,
|
|
44
|
+
source: null,
|
|
45
|
+
key_preview: null,
|
|
46
|
+
blockers: ['glm_missing_openrouter_key'],
|
|
47
|
+
warnings: []
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
export async function readStoredOpenRouterKey(paths) {
|
|
51
|
+
try {
|
|
52
|
+
const text = await fs.readFile(paths.keyPath, 'utf8');
|
|
53
|
+
const key = text.trim();
|
|
54
|
+
return key || null;
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
export async function writeStoredOpenRouterKey(value, input = {}) {
|
|
61
|
+
const key = value.trim();
|
|
62
|
+
if (!key)
|
|
63
|
+
throw new Error('OpenRouter key is empty.');
|
|
64
|
+
const paths = input.paths || openRouterSecretPaths();
|
|
65
|
+
const nowIso = input.nowIso || (() => new Date().toISOString());
|
|
66
|
+
const timestamp = nowIso();
|
|
67
|
+
const previous = input.previousRecord ?? await readOpenRouterKeyRecord(paths);
|
|
68
|
+
await ensureSecretDir(paths.secretDir);
|
|
69
|
+
const tmp = `${paths.keyPath}.${process.pid}.${crypto.randomBytes(3).toString('hex')}.tmp`;
|
|
70
|
+
try {
|
|
71
|
+
const handle = await fs.open(tmp, 'w', 0o600);
|
|
72
|
+
try {
|
|
73
|
+
await handle.writeFile(`${key}\n`, 'utf8');
|
|
74
|
+
await handle.sync().catch(() => undefined);
|
|
75
|
+
}
|
|
76
|
+
finally {
|
|
77
|
+
await handle.close().catch(() => undefined);
|
|
78
|
+
}
|
|
79
|
+
await fs.chmod(tmp, 0o600).catch(() => undefined);
|
|
80
|
+
await fs.rename(tmp, paths.keyPath);
|
|
81
|
+
await fs.chmod(paths.keyPath, 0o600).catch(() => undefined);
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
await fs.rm(tmp, { force: true }).catch(() => undefined);
|
|
85
|
+
throw err;
|
|
86
|
+
}
|
|
87
|
+
const record = {
|
|
88
|
+
schema: 'sks.openrouter-key.v1',
|
|
89
|
+
created_at: previous?.created_at || timestamp,
|
|
90
|
+
updated_at: timestamp,
|
|
91
|
+
key_hash: crypto.createHash('sha256').update(key).digest('hex'),
|
|
92
|
+
key_preview: redactOpenRouterKey(key)
|
|
93
|
+
};
|
|
94
|
+
await fs.writeFile(paths.metadataPath, `${JSON.stringify(record, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 });
|
|
95
|
+
await fs.chmod(paths.metadataPath, 0o600).catch(() => undefined);
|
|
96
|
+
return record;
|
|
97
|
+
}
|
|
98
|
+
export async function readOpenRouterKeyRecord(paths) {
|
|
99
|
+
try {
|
|
100
|
+
const parsed = JSON.parse(await fs.readFile(paths.metadataPath, 'utf8'));
|
|
101
|
+
if (parsed.schema !== 'sks.openrouter-key.v1' || !parsed.key_hash || !parsed.key_preview)
|
|
102
|
+
return null;
|
|
103
|
+
return parsed;
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
async function ensureSecretDir(secretDir) {
|
|
110
|
+
await fs.mkdir(secretDir, { recursive: true, mode: 0o700 });
|
|
111
|
+
await fs.chmod(secretDir, 0o700).catch(() => undefined);
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=openrouter-secret-store.js.map
|
|
@@ -2,6 +2,8 @@ import { buildGatePackManifest } from './gate-pack-manifest.js';
|
|
|
2
2
|
import { executeGatePack } from './gate-pack-runner.js';
|
|
3
3
|
import { buildCriticalPathLedger, writeCriticalPathLedger } from './critical-path-ledger.js';
|
|
4
4
|
import { computeResourceClassBudget } from './resource-class-budget.js';
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
5
7
|
export const EXTREME_PARALLEL_SCHEDULER_SCHEMA = 'sks.extreme-parallel-scheduler.v1';
|
|
6
8
|
export function planExtremeParallelSchedule(root, graph, budget = computeResourceClassBudget()) {
|
|
7
9
|
const manifest = buildGatePackManifest(root);
|
|
@@ -20,7 +22,7 @@ export function planExtremeParallelSchedule(root, graph, budget = computeResourc
|
|
|
20
22
|
const sequential = packs.reduce((sum, pack) => sum + pack.estimated_ms, 0);
|
|
21
23
|
const critical = batches.reduce((max, batch) => Math.max(max, batch.estimated_ms), 0);
|
|
22
24
|
const ratio = sequential <= 0 ? 1 : critical / sequential;
|
|
23
|
-
const blockers = ratio <= 0.3 || packs.length
|
|
25
|
+
const blockers = ratio <= 0.3 || packs.length < 4 ? [] : ['critical_path_reduction_below_70_percent'];
|
|
24
26
|
return {
|
|
25
27
|
schema: EXTREME_PARALLEL_SCHEDULER_SCHEMA,
|
|
26
28
|
ok: blockers.length === 0,
|
|
@@ -36,18 +38,66 @@ export async function executeExtremeSchedule(input) {
|
|
|
36
38
|
const planned = planExtremeParallelSchedule(input.root, input.graph, input.budget);
|
|
37
39
|
const started = Date.now();
|
|
38
40
|
const runId = `xs-${new Date().toISOString().replace(/[:.]/g, '-')}-${process.pid}`;
|
|
41
|
+
const manifest = buildGatePackManifest(input.root);
|
|
42
|
+
const packById = new Map(manifest.packs.map((pack) => [pack.id, pack]));
|
|
43
|
+
const queuedAt = new Map();
|
|
44
|
+
const pending = planned.batches.flatMap((batch) => batch.packs).filter((packId, index, all) => all.indexOf(packId) === index);
|
|
45
|
+
for (const packId of pending)
|
|
46
|
+
queuedAt.set(packId, Date.now());
|
|
47
|
+
const used = emptyUsedResources();
|
|
48
|
+
const waitingMs = {};
|
|
49
|
+
const timeline = [];
|
|
50
|
+
const running = new Map();
|
|
39
51
|
const reports = [];
|
|
40
52
|
let reused = 0;
|
|
41
53
|
let executedGates = 0;
|
|
42
54
|
let failedPacks = 0;
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
packId
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
55
|
+
while (pending.length || running.size) {
|
|
56
|
+
let launched = false;
|
|
57
|
+
for (let i = 0; i < pending.length;) {
|
|
58
|
+
const packId = pending[i];
|
|
59
|
+
const resources = resourceKeysForPack(packById.get(packId)?.resource_classes || ['cpu-light']);
|
|
60
|
+
if (!canClaim(used, input.budget, resources)) {
|
|
61
|
+
i += 1;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
pending.splice(i, 1);
|
|
65
|
+
const waitMs = Math.max(0, Date.now() - (queuedAt.get(packId) || Date.now()));
|
|
66
|
+
for (const resource of resources)
|
|
67
|
+
waitingMs[resource] = (waitingMs[resource] || 0) + waitMs;
|
|
68
|
+
claim(used, resources);
|
|
69
|
+
timeline.push({ event: 'claim', pack_id: packId, at: new Date().toISOString(), resources, used: { ...used }, wait_ms: waitMs });
|
|
70
|
+
launched = true;
|
|
71
|
+
const promise = executeGatePack({
|
|
72
|
+
root: input.root,
|
|
73
|
+
packId,
|
|
74
|
+
mode: 'execute',
|
|
75
|
+
maxParallel: Math.max(1, Math.min(4, input.budget.cpu_light))
|
|
76
|
+
}).then((report) => {
|
|
77
|
+
release(used, resources);
|
|
78
|
+
timeline.push({ event: 'release', pack_id: packId, at: new Date().toISOString(), resources, used: { ...used }, ok: report.ok });
|
|
79
|
+
return report;
|
|
80
|
+
});
|
|
81
|
+
running.set(packId, promise);
|
|
82
|
+
}
|
|
83
|
+
if (!running.size) {
|
|
84
|
+
if (!launched)
|
|
85
|
+
await sleep(5);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (launched && pending.some((packId) => canClaim(used, input.budget, resourceKeysForPack(packById.get(packId)?.resource_classes || ['cpu-light'])))) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
const report = await Promise.race([...running.values()]);
|
|
92
|
+
running.delete(report.pack_id);
|
|
93
|
+
reports.push(report);
|
|
94
|
+
reused += report.reused_proof_count || report.reused || 0;
|
|
95
|
+
executedGates += report.executed_gate_count || report.executed || 0;
|
|
96
|
+
if (!report.ok)
|
|
97
|
+
failedPacks += 1;
|
|
98
|
+
}
|
|
99
|
+
for (const report of await Promise.all([...running.values()])) {
|
|
100
|
+
if (!reports.some((row) => row.pack_id === report.pack_id)) {
|
|
51
101
|
reports.push(report);
|
|
52
102
|
reused += report.reused_proof_count || report.reused || 0;
|
|
53
103
|
executedGates += report.executed_gate_count || report.executed || 0;
|
|
@@ -56,15 +106,16 @@ export async function executeExtremeSchedule(input) {
|
|
|
56
106
|
}
|
|
57
107
|
}
|
|
58
108
|
const wallMs = Math.max(0, Date.now() - started);
|
|
59
|
-
const criticalPathMs = reports.
|
|
109
|
+
const criticalPathMs = Math.max(...reports.map((report) => report.critical_path_ms || 0), planned.critical_path_ms);
|
|
60
110
|
const sequentialMs = reports.reduce((sum, report) => sum + (report.critical_path_ms || 0), planned.sequential_ms);
|
|
111
|
+
const timelineFile = writeResourceClaimTimeline(input.root, runId, timeline);
|
|
61
112
|
writeCriticalPathLedger(input.root, buildCriticalPathLedger({
|
|
62
113
|
run_id: runId,
|
|
63
114
|
sequential_ms: sequentialMs,
|
|
64
115
|
critical_path_ms: criticalPathMs,
|
|
65
116
|
wall_ms: wallMs,
|
|
66
117
|
parallelism_gain: wallMs > 0 ? Number((sequentialMs / wallMs).toFixed(2)) : 1,
|
|
67
|
-
resource_wait_ms:
|
|
118
|
+
resource_wait_ms: waitingMs,
|
|
68
119
|
top_blockers: reports.filter((report) => !report.ok).map((report) => ({ id: report.pack_id, wait_ms: 0, run_ms: report.critical_path_ms || 0 }))
|
|
69
120
|
}));
|
|
70
121
|
return {
|
|
@@ -77,10 +128,72 @@ export async function executeExtremeSchedule(input) {
|
|
|
77
128
|
executed_gate_count: executedGates,
|
|
78
129
|
failed_pack_count: failedPacks,
|
|
79
130
|
wall_ms: wallMs,
|
|
131
|
+
resource_wait_ms: waitingMs,
|
|
132
|
+
resource_claim_timeline: timelineFile,
|
|
80
133
|
critical_path_ms: criticalPathMs,
|
|
81
134
|
sequential_ms: sequentialMs,
|
|
82
135
|
pack_reports: reports,
|
|
83
136
|
blockers: [...planned.blockers, ...(wallMs > input.slaMs ? ['sla_actual_exceeds_budget'] : []), ...(failedPacks ? ['pack_execution_failed'] : [])]
|
|
84
137
|
};
|
|
85
138
|
}
|
|
139
|
+
function emptyUsedResources() {
|
|
140
|
+
return {
|
|
141
|
+
cpu_light: 0,
|
|
142
|
+
cpu_heavy: 0,
|
|
143
|
+
io_light: 0,
|
|
144
|
+
io_heavy: 0,
|
|
145
|
+
fs_read: 0,
|
|
146
|
+
network: 0,
|
|
147
|
+
remote_model_real: 0,
|
|
148
|
+
zellij_real: 0,
|
|
149
|
+
browser_real: 0,
|
|
150
|
+
secret_sensitive: 0
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
function resourceKeysForPack(classes) {
|
|
154
|
+
const keys = classes.map((value) => {
|
|
155
|
+
if (value === 'cpu-light')
|
|
156
|
+
return 'cpu_light';
|
|
157
|
+
if (value === 'cpu-heavy')
|
|
158
|
+
return 'cpu_heavy';
|
|
159
|
+
if (value === 'io-light')
|
|
160
|
+
return 'io_light';
|
|
161
|
+
if (value === 'io-heavy')
|
|
162
|
+
return 'io_heavy';
|
|
163
|
+
if (value === 'fs-read')
|
|
164
|
+
return 'fs_read';
|
|
165
|
+
if (value === 'remote-model-real')
|
|
166
|
+
return 'remote_model_real';
|
|
167
|
+
if (value === 'zellij-real')
|
|
168
|
+
return 'zellij_real';
|
|
169
|
+
if (value === 'browser-real')
|
|
170
|
+
return 'browser_real';
|
|
171
|
+
if (value === 'secret-sensitive' || value === 'secret')
|
|
172
|
+
return 'secret_sensitive';
|
|
173
|
+
if (value === 'network')
|
|
174
|
+
return 'network';
|
|
175
|
+
return 'cpu_light';
|
|
176
|
+
});
|
|
177
|
+
return [...new Set(keys)];
|
|
178
|
+
}
|
|
179
|
+
function canClaim(used, budget, resources) {
|
|
180
|
+
return resources.every((resource) => (used[resource] || 0) < Number(budget[resource] || 1));
|
|
181
|
+
}
|
|
182
|
+
function claim(used, resources) {
|
|
183
|
+
for (const resource of resources)
|
|
184
|
+
used[resource] = (used[resource] || 0) + 1;
|
|
185
|
+
}
|
|
186
|
+
function release(used, resources) {
|
|
187
|
+
for (const resource of resources)
|
|
188
|
+
used[resource] = Math.max(0, (used[resource] || 0) - 1);
|
|
189
|
+
}
|
|
190
|
+
function writeResourceClaimTimeline(root, runId, timeline) {
|
|
191
|
+
const file = path.join(root, '.sneakoscope', 'reports', 'resource-claim-timeline.json');
|
|
192
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
193
|
+
fs.writeFileSync(file, `${JSON.stringify({ schema: 'sks.resource-claim-timeline.v1', run_id: runId, events: timeline }, null, 2)}\n`);
|
|
194
|
+
return file;
|
|
195
|
+
}
|
|
196
|
+
function sleep(ms) {
|
|
197
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
198
|
+
}
|
|
86
199
|
//# sourceMappingURL=extreme-parallel-scheduler.js.map
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
export const GATE_PACK_SHARED_ARTIFACT_SCHEMA = 'sks.gate-pack-shared-artifact.v1';
|
|
4
|
+
export function writeGatePackSharedArtifact(input) {
|
|
5
|
+
const artifact = {
|
|
6
|
+
schema: GATE_PACK_SHARED_ARTIFACT_SCHEMA,
|
|
7
|
+
pack_id: input.pack.id,
|
|
8
|
+
fixture_path: input.fixturePath,
|
|
9
|
+
setup_once: true,
|
|
10
|
+
assertions: {
|
|
11
|
+
gate_count: input.pack.gate_ids.length,
|
|
12
|
+
resource_classes: input.pack.resource_classes,
|
|
13
|
+
...sharedAssertionsForPack(input.root, input.pack),
|
|
14
|
+
...(input.assertions || {})
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
const file = path.join(input.fixturePath, 'gate-pack-shared-artifact.json');
|
|
18
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
19
|
+
fs.writeFileSync(file, `${JSON.stringify(artifact, null, 2)}\n`);
|
|
20
|
+
return file;
|
|
21
|
+
}
|
|
22
|
+
export function readGatePackSharedArtifact(file) {
|
|
23
|
+
try {
|
|
24
|
+
const json = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
25
|
+
return json.schema === GATE_PACK_SHARED_ARTIFACT_SCHEMA ? json : null;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export function assertGateFromSharedArtifact(file, gateId) {
|
|
32
|
+
const artifact = readGatePackSharedArtifact(file);
|
|
33
|
+
if (!artifact)
|
|
34
|
+
return { ok: false, blockers: ['shared_artifact_missing_or_invalid'], artifact: null };
|
|
35
|
+
const gateIds = artifact.assertions.gate_ids;
|
|
36
|
+
if (Array.isArray(gateIds) && !gateIds.map(String).includes(gateId)) {
|
|
37
|
+
return { ok: false, blockers: [`gate_not_in_shared_artifact:${gateId}`], artifact };
|
|
38
|
+
}
|
|
39
|
+
return { ok: true, blockers: [], artifact };
|
|
40
|
+
}
|
|
41
|
+
function sharedAssertionsForPack(root, pack) {
|
|
42
|
+
const packageJson = readJson(path.join(root, 'package.json'));
|
|
43
|
+
return {
|
|
44
|
+
gate_ids: pack.gate_ids,
|
|
45
|
+
package_version: typeof packageJson?.version === 'string' ? packageJson.version : null,
|
|
46
|
+
release_manifest_present: fs.existsSync(path.join(root, 'release-gates.v2.json')),
|
|
47
|
+
proof_bank_present: fs.existsSync(path.join(root, '.sneakoscope', 'triwiki', 'proof-bank'))
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function readJson(file) {
|
|
51
|
+
try {
|
|
52
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=gate-pack-assertion.js.map
|
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { hashJson } from '../triwiki/triwiki-cache-key.js';
|
|
3
4
|
export const GATE_PACK_FIXTURE_SCHEMA = 'sks.gate-pack-fixture.v1';
|
|
4
5
|
export async function prepareGatePackFixture(input) {
|
|
5
6
|
const base = path.join(input.root, '.sneakoscope', 'fixture-cache', 'gate-packs', safe(input.packId), safe(input.fixtureVersion));
|
|
6
|
-
const
|
|
7
|
+
const expectedHash = hashJson({ schema: GATE_PACK_FIXTURE_SCHEMA, pack_id: input.packId, fixture_version: input.fixtureVersion });
|
|
8
|
+
const reusedBase = validateBaseFixture(base, input.packId, input.fixtureVersion, expectedHash);
|
|
9
|
+
if (!reusedBase)
|
|
10
|
+
fs.rmSync(base, { recursive: true, force: true });
|
|
7
11
|
fs.mkdirSync(base, { recursive: true });
|
|
8
12
|
if (!reusedBase) {
|
|
9
|
-
fs.writeFileSync(path.join(base, 'fixture.json'), `${JSON.stringify({ schema: GATE_PACK_FIXTURE_SCHEMA, pack_id: input.packId, fixture_version: input.fixtureVersion, created_at: new Date().toISOString() }, null, 2)}\n`);
|
|
13
|
+
fs.writeFileSync(path.join(base, 'fixture.json'), `${JSON.stringify({ schema: GATE_PACK_FIXTURE_SCHEMA, pack_id: input.packId, fixture_version: input.fixtureVersion, base_fixture_hash: expectedHash, created_at: new Date().toISOString() }, null, 2)}\n`);
|
|
10
14
|
}
|
|
11
15
|
const runPath = path.join(input.root, '.sneakoscope', 'fixture-cache', 'runs', `${safe(input.packId)}-${process.pid}-${Date.now()}`);
|
|
12
16
|
fs.mkdirSync(path.dirname(runPath), { recursive: true });
|
|
17
|
+
cleanupOldRuns(path.dirname(runPath), 20);
|
|
13
18
|
copyDir(base, runPath);
|
|
14
19
|
return {
|
|
15
20
|
schema: GATE_PACK_FIXTURE_SCHEMA,
|
|
@@ -18,6 +23,7 @@ export async function prepareGatePackFixture(input) {
|
|
|
18
23
|
fixture_version: input.fixtureVersion,
|
|
19
24
|
base_path: base,
|
|
20
25
|
run_path: runPath,
|
|
26
|
+
base_fixture_hash: expectedHash,
|
|
21
27
|
reused_base: reusedBase,
|
|
22
28
|
setup_count: reusedBase ? 0 : 1
|
|
23
29
|
};
|
|
@@ -29,6 +35,13 @@ function copyDir(from, to) {
|
|
|
29
35
|
const dst = path.join(to, entry.name);
|
|
30
36
|
if (entry.isDirectory())
|
|
31
37
|
copyDir(src, dst);
|
|
38
|
+
else if (entry.isSymbolicLink()) {
|
|
39
|
+
const target = fs.readlinkSync(src);
|
|
40
|
+
const resolved = path.resolve(path.dirname(src), target);
|
|
41
|
+
if (!resolved.startsWith(path.resolve(from) + path.sep))
|
|
42
|
+
throw new Error(`gate_pack_fixture_symlink_outside_root:${src}`);
|
|
43
|
+
fs.symlinkSync(target, dst);
|
|
44
|
+
}
|
|
32
45
|
else if (entry.isFile())
|
|
33
46
|
fs.copyFileSync(src, dst);
|
|
34
47
|
}
|
|
@@ -36,4 +49,27 @@ function copyDir(from, to) {
|
|
|
36
49
|
function safe(value) {
|
|
37
50
|
return value.replace(/[^a-zA-Z0-9._-]+/g, '_');
|
|
38
51
|
}
|
|
52
|
+
function validateBaseFixture(base, packId, fixtureVersion, expectedHash) {
|
|
53
|
+
try {
|
|
54
|
+
const file = path.join(base, 'fixture.json');
|
|
55
|
+
const json = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
56
|
+
return json.schema === GATE_PACK_FIXTURE_SCHEMA && json.pack_id === packId && json.fixture_version === fixtureVersion && json.base_fixture_hash === expectedHash;
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function cleanupOldRuns(runsDir, keep) {
|
|
63
|
+
if (!fs.existsSync(runsDir))
|
|
64
|
+
return;
|
|
65
|
+
const runs = fs.readdirSync(runsDir, { withFileTypes: true })
|
|
66
|
+
.filter((entry) => entry.isDirectory())
|
|
67
|
+
.map((entry) => {
|
|
68
|
+
const dir = path.join(runsDir, entry.name);
|
|
69
|
+
return { dir, mtimeMs: fs.statSync(dir).mtimeMs };
|
|
70
|
+
})
|
|
71
|
+
.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
72
|
+
for (const run of runs.slice(Math.max(0, keep)))
|
|
73
|
+
fs.rmSync(run.dir, { recursive: true, force: true });
|
|
74
|
+
}
|
|
39
75
|
//# sourceMappingURL=gate-pack-fixture-cache.js.map
|
|
@@ -106,11 +106,11 @@ function estimatedMsForPack(id) {
|
|
|
106
106
|
}
|
|
107
107
|
function resourceClassesForPack(id) {
|
|
108
108
|
if (id === 'secret')
|
|
109
|
-
return ['fs-read'];
|
|
109
|
+
return ['secret-sensitive', 'fs-read'];
|
|
110
110
|
if (id === 'zellij')
|
|
111
111
|
return ['zellij-real'];
|
|
112
112
|
if (id === 'qa-research-image')
|
|
113
|
-
return ['cpu-heavy', 'io-heavy'];
|
|
113
|
+
return ['browser-real', 'cpu-heavy', 'io-heavy'];
|
|
114
114
|
if (id === 'codex-0140')
|
|
115
115
|
return ['remote-model-real'];
|
|
116
116
|
return ['cpu-light', 'fs-read'];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { spawn
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { computeTriWikiCacheKey } from '../triwiki/triwiki-cache-key.js';
|
|
@@ -6,97 +6,12 @@ import { createTriWikiProofCard } from '../triwiki/triwiki-proof-card.js';
|
|
|
6
6
|
import { readReusableTriWikiProofCard, writeTriWikiProofCard } from '../triwiki/triwiki-proof-bank.js';
|
|
7
7
|
import { buildGatePackManifest } from './gate-pack-manifest.js';
|
|
8
8
|
import { prepareGatePackFixture } from './gate-pack-fixture-cache.js';
|
|
9
|
+
import { writeGatePackSharedArtifact } from './gate-pack-assertion.js';
|
|
9
10
|
import { loadPackageScripts, loadReleaseGateManifest } from '../triwiki/triwiki-gate-impact-map.js';
|
|
10
11
|
export const GATE_PACK_RUNNER_SCHEMA = 'sks.gate-pack-runner.v1';
|
|
11
12
|
export function runGatePack(input) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
if (!pack) {
|
|
15
|
-
return { schema: GATE_PACK_RUNNER_SCHEMA, ok: false, root: input.root, pack_id: input.packId, mode: input.execute ? 'execute' : 'plan', reused: 0, executed: 0, failed: 0, proof_paths: [], blockers: ['pack_missing'] };
|
|
16
|
-
}
|
|
17
|
-
const gates = loadReleaseGateManifest(input.root).gates.filter((gate) => pack.gate_ids.includes(gate.id));
|
|
18
|
-
const scripts = loadPackageScripts(input.root);
|
|
19
|
-
const blockers = [];
|
|
20
|
-
const proofPaths = [];
|
|
21
|
-
let reused = 0;
|
|
22
|
-
let executed = 0;
|
|
23
|
-
let failed = 0;
|
|
24
|
-
for (const gate of gates) {
|
|
25
|
-
if (!input.execute)
|
|
26
|
-
continue;
|
|
27
|
-
const cacheKey = computeTriWikiCacheKey({
|
|
28
|
-
root: input.root,
|
|
29
|
-
id: gate.id,
|
|
30
|
-
inputs: gate.cache.inputs,
|
|
31
|
-
implementationFiles: [`src/scripts/${scriptFileForCommand(gate.command) || ''}`].filter(Boolean),
|
|
32
|
-
envAllowlist: ['CI', 'SKS_FAST_MODE', 'SKS_RELEASE_PRESET'],
|
|
33
|
-
fixtureVersion: 'sks-4.0.0'
|
|
34
|
-
});
|
|
35
|
-
const hit = readReusableTriWikiProofCard({ root: input.root, subjectId: gate.id, cacheKey: cacheKey.key });
|
|
36
|
-
if (hit.hit) {
|
|
37
|
-
reused += 1;
|
|
38
|
-
if (hit.path)
|
|
39
|
-
proofPaths.push(hit.path);
|
|
40
|
-
continue;
|
|
41
|
-
}
|
|
42
|
-
const scriptName = scriptNameForCommand(gate.command);
|
|
43
|
-
if (!scriptName || !scripts[scriptName]) {
|
|
44
|
-
failed += 1;
|
|
45
|
-
blockers.push(`script_missing:${gate.id}`);
|
|
46
|
-
continue;
|
|
47
|
-
}
|
|
48
|
-
const started = Date.now();
|
|
49
|
-
const run = spawnSync('npm', ['run', scriptName, '--silent'], {
|
|
50
|
-
cwd: input.root,
|
|
51
|
-
encoding: 'utf8',
|
|
52
|
-
maxBuffer: 1024 * 1024 * 10,
|
|
53
|
-
env: { ...process.env, ...(input.env || {}), CI: process.env.CI || 'true' }
|
|
54
|
-
});
|
|
55
|
-
executed += 1;
|
|
56
|
-
const passed = run.status === 0;
|
|
57
|
-
if (!passed) {
|
|
58
|
-
failed += 1;
|
|
59
|
-
blockers.push(`gate_failed:${gate.id}`);
|
|
60
|
-
}
|
|
61
|
-
const card = createTriWikiProofCard({
|
|
62
|
-
subject_type: 'gate',
|
|
63
|
-
subject_id: gate.id,
|
|
64
|
-
cache_key: cacheKey.key,
|
|
65
|
-
input_hash: cacheKey.input_hash,
|
|
66
|
-
implementation_hash: cacheKey.implementation_hash,
|
|
67
|
-
gate_impl_hash: cacheKey.implementation_hash,
|
|
68
|
-
package_lock_hash: cacheKey.package_lock_hash,
|
|
69
|
-
release_gates_hash: cacheKey.release_gates_hash,
|
|
70
|
-
env_allowlist_hash: cacheKey.env_allowlist_hash,
|
|
71
|
-
tool_versions: cacheKey.tool_versions,
|
|
72
|
-
tool_version: cacheKey.tool_version,
|
|
73
|
-
fixture_version: cacheKey.fixture_version,
|
|
74
|
-
result: passed ? 'passed' : 'failed',
|
|
75
|
-
reusable: passed,
|
|
76
|
-
duration_ms: Math.max(0, Date.now() - started),
|
|
77
|
-
evidence: {
|
|
78
|
-
status: run.status,
|
|
79
|
-
stdout_tail: tail(String(run.stdout || '')),
|
|
80
|
-
stderr_tail: tail(String(run.stderr || ''))
|
|
81
|
-
},
|
|
82
|
-
invalidation_reasons: passed ? [] : ['gate_failed']
|
|
83
|
-
});
|
|
84
|
-
proofPaths.push(writeTriWikiProofCard(input.root, card));
|
|
85
|
-
}
|
|
86
|
-
const report = {
|
|
87
|
-
schema: GATE_PACK_RUNNER_SCHEMA,
|
|
88
|
-
ok: blockers.length === 0,
|
|
89
|
-
root: input.root,
|
|
90
|
-
pack_id: input.packId,
|
|
91
|
-
mode: input.execute ? 'execute' : 'plan',
|
|
92
|
-
reused,
|
|
93
|
-
executed,
|
|
94
|
-
failed,
|
|
95
|
-
proof_paths: proofPaths,
|
|
96
|
-
blockers
|
|
97
|
-
};
|
|
98
|
-
writeGatePackReport(input.root, report);
|
|
99
|
-
return report;
|
|
13
|
+
void input;
|
|
14
|
+
throw new Error('gate_pack_legacy_sync_runner_removed');
|
|
100
15
|
}
|
|
101
16
|
export async function executeGatePack(input) {
|
|
102
17
|
const mode = input.mode || 'execute';
|
|
@@ -109,7 +24,8 @@ export async function executeGatePack(input) {
|
|
|
109
24
|
if (mode === 'plan') {
|
|
110
25
|
return { schema: GATE_PACK_RUNNER_SCHEMA, ok: true, root: input.root, pack_id: input.packId, mode: 'plan', reused: 0, executed: 0, failed: 0, proof_paths: [], blockers: [], shared_setup_count: 0, parallelism_gain: 1, critical_path_ms: pack.estimated_ms, reused_proof_count: 0, executed_gate_count: 0 };
|
|
111
26
|
}
|
|
112
|
-
const fixture = await prepareGatePackFixture({ root: input.root, packId: input.packId, fixtureVersion: 'sks-4.0.
|
|
27
|
+
const fixture = await prepareGatePackFixture({ root: input.root, packId: input.packId, fixtureVersion: 'sks-4.0.2' });
|
|
28
|
+
const artifactPath = writeGatePackSharedArtifact({ root: input.root, pack, fixturePath: fixture.run_path });
|
|
113
29
|
const scripts = loadPackageScripts(input.root);
|
|
114
30
|
const blockers = [];
|
|
115
31
|
const proofPaths = [];
|
|
@@ -152,7 +68,7 @@ export async function executeGatePack(input) {
|
|
|
152
68
|
continue;
|
|
153
69
|
}
|
|
154
70
|
const started = Date.now();
|
|
155
|
-
const run = await spawnNpmScript(input.root, scriptName, input.env);
|
|
71
|
+
const run = await spawnNpmScript(input.root, scriptName, { ...(input.env || {}), SKS_GATE_PACK_ARTIFACT: artifactPath });
|
|
156
72
|
const duration = Math.max(0, Date.now() - started);
|
|
157
73
|
sumMs += duration;
|
|
158
74
|
criticalPathMs = Math.max(criticalPathMs, duration);
|
|
@@ -178,7 +94,7 @@ export async function executeGatePack(input) {
|
|
|
178
94
|
result: passed ? 'passed' : 'failed',
|
|
179
95
|
reusable: passed,
|
|
180
96
|
duration_ms: duration,
|
|
181
|
-
evidence: { status: run.status, stdout_tail: tail(run.stdout), stderr_tail: tail(run.stderr), fixture_path: fixture.run_path },
|
|
97
|
+
evidence: { status: run.status, stdout_tail: tail(run.stdout), stderr_tail: tail(run.stderr), fixture_path: fixture.run_path, shared_artifact_path: artifactPath },
|
|
182
98
|
invalidation_reasons: passed ? [] : ['gate_failed']
|
|
183
99
|
});
|
|
184
100
|
proofPaths.push(writeTriWikiProofCard(input.root, card));
|
|
@@ -198,7 +114,7 @@ export async function executeGatePack(input) {
|
|
|
198
114
|
fixture_version: fixture.fixture_version,
|
|
199
115
|
result: failed === 0 ? 'passed' : 'failed',
|
|
200
116
|
reusable: failed === 0,
|
|
201
|
-
evidence: { gate_count: gates.length, reused, executed, failed },
|
|
117
|
+
evidence: { gate_count: gates.length, reused, executed, failed, shared_artifact_path: artifactPath },
|
|
202
118
|
invalidation_reasons: failed === 0 ? [] : ['pack_gate_failed']
|
|
203
119
|
});
|
|
204
120
|
proofPaths.push(writeTriWikiProofCard(input.root, packCard));
|