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.
Files changed (54) hide show
  1. package/README.md +7 -7
  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 -0
  7. package/dist/cli/global-mode-router.js +25 -0
  8. package/dist/cli/router.js +12 -0
  9. package/dist/commands/codex-app.js +10 -1
  10. package/dist/commands/codex.js +15 -1
  11. package/dist/core/build/build-once-runner.js +49 -16
  12. package/dist/core/codex-app/glm-model-profile.js +2 -0
  13. package/dist/core/codex-app/glm-profile-installer.js +61 -0
  14. package/dist/core/codex-app/glm-profile-schema.js +24 -0
  15. package/dist/core/codex-control/codex-0141-capability.js +95 -0
  16. package/dist/core/commands/daemon-command.js +1 -1
  17. package/dist/core/commands/glm-command.js +5 -0
  18. package/dist/core/daemon/sksd-client.js +7 -2
  19. package/dist/core/daemon/sksd-ipc.js +13 -3
  20. package/dist/core/daemon/sksd.js +62 -3
  21. package/dist/core/doctor/doctor-dirty-planner.js +97 -7
  22. package/dist/core/doctor/doctor-transaction.js +21 -5
  23. package/dist/core/fsx.js +1 -1
  24. package/dist/core/providers/glm/glm-52-profile.js +30 -0
  25. package/dist/core/providers/glm/glm-52-request.js +34 -0
  26. package/dist/core/providers/glm/glm-52-response-guard.js +34 -0
  27. package/dist/core/providers/glm/glm-52-settings.js +26 -0
  28. package/dist/core/providers/glm/glm-mad-mode.js +242 -0
  29. package/dist/core/providers/openrouter/openrouter-client.js +44 -0
  30. package/dist/core/providers/openrouter/openrouter-error.js +37 -0
  31. package/dist/core/providers/openrouter/openrouter-secret-store.js +113 -0
  32. package/dist/core/providers/openrouter/openrouter-types.js +2 -0
  33. package/dist/core/release/extreme-parallel-scheduler.js +124 -11
  34. package/dist/core/release/gate-pack-assertion.js +58 -0
  35. package/dist/core/release/gate-pack-fixture-cache.js +38 -2
  36. package/dist/core/release/gate-pack-manifest.js +2 -2
  37. package/dist/core/release/gate-pack-runner.js +9 -93
  38. package/dist/core/release/release-gate-cache-v2.js +71 -0
  39. package/dist/core/release/release-gate-dag.js +50 -5
  40. package/dist/core/release/release-gate-node.js +2 -0
  41. package/dist/core/release/release-gate-resource-governor.js +2 -0
  42. package/dist/core/release/resource-class-budget.js +1 -0
  43. package/dist/core/results.js +2 -0
  44. package/dist/core/secret-redaction.js +4 -0
  45. package/dist/core/security/redact-secrets.js +15 -0
  46. package/dist/core/triwiki/triwiki-affected-graph.js +35 -3
  47. package/dist/core/triwiki/triwiki-gate-impact-map.js +2 -0
  48. package/dist/core/triwiki/triwiki-proof-bank.js +12 -2
  49. package/dist/core/triwiki/triwiki-sla-certificate.js +3 -0
  50. package/dist/core/version.js +1 -1
  51. package/dist/scripts/release-4002-required-gates.js +14 -0
  52. package/dist/scripts/release-gate-dag-runner.js +2 -1
  53. package/dist/scripts/release-gate-existence-audit.js +1 -2
  54. 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
@@ -0,0 +1,2 @@
1
+ export const OPENROUTER_CHAT_COMPLETIONS_URL = 'https://openrouter.ai/api/v1/chat/completions';
2
+ //# sourceMappingURL=openrouter-types.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 <= 1 ? [] : ['critical_path_reduction_below_70_percent'];
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
- for (const batch of planned.batches) {
44
- const results = await Promise.all(batch.packs.map((packId) => executeGatePack({
45
- root: input.root,
46
- packId,
47
- mode: 'execute',
48
- maxParallel: Math.max(1, Math.min(4, input.budget.cpu_light))
49
- })));
50
- for (const report of results) {
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.reduce((max, report) => Math.max(max, report.critical_path_ms || 0), planned.critical_path_ms);
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 reusedBase = fs.existsSync(path.join(base, 'fixture.json'));
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, spawnSync } from 'node:child_process';
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
- const manifest = buildGatePackManifest(input.root);
13
- const pack = manifest.packs.find((candidate) => candidate.id === input.packId);
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.1' });
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));