sneakoscope 4.0.0 → 4.0.1

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 (33) hide show
  1. package/README.md +2 -2
  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/core/build/build-once-runner.js +5 -1
  7. package/dist/core/commands/check-command.js +3 -1
  8. package/dist/core/commands/mad-sks-command.js +7 -2
  9. package/dist/core/daemon/sksd-ipc.js +9 -0
  10. package/dist/core/daemon/sksd.js +37 -5
  11. package/dist/core/doctor/doctor-dirty-planner.js +70 -5
  12. package/dist/core/fsx.js +1 -1
  13. package/dist/core/probes/probe-memoization.js +40 -2
  14. package/dist/core/release/critical-path-ledger.js +12 -0
  15. package/dist/core/release/extreme-parallel-scheduler.js +53 -0
  16. package/dist/core/release/gate-pack-fixture-cache.js +39 -0
  17. package/dist/core/release/gate-pack-runner.js +145 -1
  18. package/dist/core/release/release-gate-cache-v2.js +6 -1
  19. package/dist/core/release/release-gate-dag.js +9 -2
  20. package/dist/core/triwiki/triwiki-affected-graph.js +63 -7
  21. package/dist/core/triwiki/triwiki-cache-key.js +27 -7
  22. package/dist/core/triwiki/triwiki-gate-impact-map.js +47 -2
  23. package/dist/core/triwiki/triwiki-invalidation.js +81 -0
  24. package/dist/core/triwiki/triwiki-module-card.js +48 -11
  25. package/dist/core/triwiki/triwiki-proof-bank.js +82 -4
  26. package/dist/core/triwiki/triwiki-proof-card.js +34 -3
  27. package/dist/core/triwiki/triwiki-sla-certificate.js +25 -2
  28. package/dist/core/version.js +1 -1
  29. package/dist/core/zellij/zellij-launcher.js +13 -0
  30. package/dist/core/zellij/zellij-worker-pane-manager.js +68 -17
  31. package/dist/scripts/release-4001-required-gates.js +13 -0
  32. package/dist/scripts/sizecheck.js +4 -2
  33. package/package.json +11 -2
@@ -1,10 +1,11 @@
1
- import { spawnSync } from 'node:child_process';
1
+ import { spawn, spawnSync } 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';
5
5
  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
+ import { prepareGatePackFixture } from './gate-pack-fixture-cache.js';
8
9
  import { loadPackageScripts, loadReleaseGateManifest } from '../triwiki/triwiki-gate-impact-map.js';
9
10
  export const GATE_PACK_RUNNER_SCHEMA = 'sks.gate-pack-runner.v1';
10
11
  export function runGatePack(input) {
@@ -63,6 +64,11 @@ export function runGatePack(input) {
63
64
  cache_key: cacheKey.key,
64
65
  input_hash: cacheKey.input_hash,
65
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,
66
72
  tool_version: cacheKey.tool_version,
67
73
  fixture_version: cacheKey.fixture_version,
68
74
  result: passed ? 'passed' : 'failed',
@@ -92,6 +98,130 @@ export function runGatePack(input) {
92
98
  writeGatePackReport(input.root, report);
93
99
  return report;
94
100
  }
101
+ export async function executeGatePack(input) {
102
+ const mode = input.mode || 'execute';
103
+ const manifest = buildGatePackManifest(input.root);
104
+ const pack = manifest.packs.find((candidate) => candidate.id === input.packId);
105
+ if (!pack) {
106
+ return { schema: GATE_PACK_RUNNER_SCHEMA, ok: false, root: input.root, pack_id: input.packId, mode: mode === 'plan' ? 'plan' : 'execute', reused: 0, executed: 0, failed: 0, proof_paths: [], blockers: ['pack_missing'] };
107
+ }
108
+ const gates = loadReleaseGateManifest(input.root).gates.filter((gate) => pack.gate_ids.includes(gate.id));
109
+ if (mode === 'plan') {
110
+ 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
+ }
112
+ const fixture = await prepareGatePackFixture({ root: input.root, packId: input.packId, fixtureVersion: 'sks-4.0.1' });
113
+ const scripts = loadPackageScripts(input.root);
114
+ const blockers = [];
115
+ const proofPaths = [];
116
+ let reused = 0;
117
+ let executed = 0;
118
+ let failed = 0;
119
+ let sumMs = 0;
120
+ let criticalPathMs = 0;
121
+ const pending = gates.slice();
122
+ const maxParallel = Math.max(1, Math.min(input.maxParallel || 4, pending.length || 1));
123
+ const workers = Array.from({ length: maxParallel }, async () => {
124
+ while (pending.length) {
125
+ const gate = pending.shift();
126
+ if (!gate)
127
+ return;
128
+ const cacheKey = computeTriWikiCacheKey({
129
+ root: input.root,
130
+ id: gate.id,
131
+ inputs: gate.cache.inputs,
132
+ implementationFiles: [`src/scripts/${scriptFileForCommand(gate.command) || ''}`].filter(Boolean),
133
+ envAllowlist: ['CI', 'SKS_FAST_MODE', 'SKS_RELEASE_PRESET'],
134
+ fixtureVersion: fixture.fixture_version
135
+ });
136
+ const hit = readReusableTriWikiProofCard({ root: input.root, subjectId: gate.id, cacheKey: cacheKey.key });
137
+ if (hit.hit) {
138
+ reused += 1;
139
+ if (hit.path)
140
+ proofPaths.push(hit.path);
141
+ continue;
142
+ }
143
+ if (mode === 'assert-only') {
144
+ failed += 1;
145
+ blockers.push(`proof_missing:${gate.id}`);
146
+ continue;
147
+ }
148
+ const scriptName = scriptNameForCommand(gate.command);
149
+ if (!scriptName || !scripts[scriptName]) {
150
+ failed += 1;
151
+ blockers.push(`script_missing:${gate.id}`);
152
+ continue;
153
+ }
154
+ const started = Date.now();
155
+ const run = await spawnNpmScript(input.root, scriptName, input.env);
156
+ const duration = Math.max(0, Date.now() - started);
157
+ sumMs += duration;
158
+ criticalPathMs = Math.max(criticalPathMs, duration);
159
+ executed += 1;
160
+ const passed = run.status === 0;
161
+ if (!passed) {
162
+ failed += 1;
163
+ blockers.push(`gate_failed:${gate.id}`);
164
+ }
165
+ const card = createTriWikiProofCard({
166
+ subject_type: 'gate',
167
+ subject_id: gate.id,
168
+ cache_key: cacheKey.key,
169
+ input_hash: cacheKey.input_hash,
170
+ implementation_hash: cacheKey.implementation_hash,
171
+ gate_impl_hash: cacheKey.implementation_hash,
172
+ package_lock_hash: cacheKey.package_lock_hash,
173
+ release_gates_hash: cacheKey.release_gates_hash,
174
+ env_allowlist_hash: cacheKey.env_allowlist_hash,
175
+ tool_versions: cacheKey.tool_versions,
176
+ tool_version: cacheKey.tool_version,
177
+ fixture_version: cacheKey.fixture_version,
178
+ result: passed ? 'passed' : 'failed',
179
+ reusable: passed,
180
+ duration_ms: duration,
181
+ evidence: { status: run.status, stdout_tail: tail(run.stdout), stderr_tail: tail(run.stderr), fixture_path: fixture.run_path },
182
+ invalidation_reasons: passed ? [] : ['gate_failed']
183
+ });
184
+ proofPaths.push(writeTriWikiProofCard(input.root, card));
185
+ }
186
+ });
187
+ await Promise.all(workers);
188
+ const packCard = createTriWikiProofCard({
189
+ subject_type: 'gate-pack',
190
+ subject_id: input.packId,
191
+ cache_key: `pack:${input.packId}:${gates.map((gate) => gate.id).sort().join(',')}`,
192
+ input_hash: computeTriWikiCacheKey({ root: input.root, id: `pack:${input.packId}`, inputs: gates.flatMap((gate) => gate.cache.inputs), fixtureVersion: fixture.fixture_version }).input_hash,
193
+ gate_impl_hash: `pack-runner:${GATE_PACK_RUNNER_SCHEMA}`,
194
+ package_lock_hash: computeTriWikiCacheKey({ root: input.root, id: `pack:${input.packId}:meta`, inputs: [] }).package_lock_hash,
195
+ release_gates_hash: computeTriWikiCacheKey({ root: input.root, id: `pack:${input.packId}:meta`, inputs: [] }).release_gates_hash,
196
+ env_allowlist_hash: computeTriWikiCacheKey({ root: input.root, id: `pack:${input.packId}:meta`, inputs: [], envAllowlist: ['CI', 'SKS_FAST_MODE'] }).env_allowlist_hash,
197
+ tool_versions: { sks: computeTriWikiCacheKey({ root: input.root, id: `pack:${input.packId}:meta`, inputs: [] }).tool_version },
198
+ fixture_version: fixture.fixture_version,
199
+ result: failed === 0 ? 'passed' : 'failed',
200
+ reusable: failed === 0,
201
+ evidence: { gate_count: gates.length, reused, executed, failed },
202
+ invalidation_reasons: failed === 0 ? [] : ['pack_gate_failed']
203
+ });
204
+ proofPaths.push(writeTriWikiProofCard(input.root, packCard));
205
+ const report = {
206
+ schema: GATE_PACK_RUNNER_SCHEMA,
207
+ ok: blockers.length === 0,
208
+ root: input.root,
209
+ pack_id: input.packId,
210
+ mode: 'execute',
211
+ reused,
212
+ executed,
213
+ failed,
214
+ proof_paths: proofPaths,
215
+ blockers,
216
+ shared_setup_count: fixture.setup_count,
217
+ parallelism_gain: criticalPathMs > 0 ? Number((sumMs / criticalPathMs).toFixed(2)) : 1,
218
+ critical_path_ms: criticalPathMs,
219
+ reused_proof_count: reused,
220
+ executed_gate_count: executed
221
+ };
222
+ writeGatePackReport(input.root, report);
223
+ return report;
224
+ }
95
225
  function writeGatePackReport(root, report) {
96
226
  const file = path.join(root, '.sneakoscope', 'reports', 'gate-pack-runner.json');
97
227
  fs.mkdirSync(path.dirname(file), { recursive: true });
@@ -110,4 +240,18 @@ function scriptFileForCommand(command) {
110
240
  function tail(value, limit = 2000) {
111
241
  return value.length > limit ? value.slice(-limit) : value;
112
242
  }
243
+ function spawnNpmScript(root, scriptName, env) {
244
+ return new Promise((resolve) => {
245
+ const child = spawn('npm', ['run', scriptName, '--silent'], {
246
+ cwd: root,
247
+ env: { ...process.env, ...(env || {}), CI: process.env.CI || 'true' },
248
+ stdio: ['ignore', 'pipe', 'pipe']
249
+ });
250
+ let stdout = '';
251
+ let stderr = '';
252
+ child.stdout?.on('data', (chunk) => { stdout = tail(stdout + String(chunk)); });
253
+ child.stderr?.on('data', (chunk) => { stderr = tail(stderr + String(chunk)); });
254
+ child.on('close', (status) => resolve({ status, stdout, stderr }));
255
+ });
256
+ }
113
257
  //# sourceMappingURL=gate-pack-runner.js.map
@@ -171,7 +171,7 @@ export function writeReleaseGateCacheHit(root, gate, durationMs = 0) {
171
171
  inputs: gate.cache.inputs,
172
172
  implementationFiles: ['release-gates.v2.json', `src/scripts/${gate.id.replace(/[:]/g, '-')}-check.ts`],
173
173
  envAllowlist: ['CI', 'SKS_FAST_MODE', 'SKS_RELEASE_PRESET'],
174
- fixtureVersion: 'sks-4.0.0',
174
+ fixtureVersion: 'sks-4.0.1',
175
175
  salt: key
176
176
  });
177
177
  writeTriWikiProofCard(root, createTriWikiProofCard({
@@ -180,6 +180,11 @@ export function writeReleaseGateCacheHit(root, gate, durationMs = 0) {
180
180
  cache_key: key,
181
181
  input_hash: triKey.input_hash,
182
182
  implementation_hash: triKey.implementation_hash,
183
+ gate_impl_hash: triKey.implementation_hash,
184
+ package_lock_hash: triKey.package_lock_hash,
185
+ release_gates_hash: triKey.release_gates_hash,
186
+ env_allowlist_hash: triKey.env_allowlist_hash,
187
+ tool_versions: triKey.tool_versions,
183
188
  tool_version: triKey.tool_version,
184
189
  fixture_version: triKey.fixture_version,
185
190
  result: 'passed',
@@ -8,6 +8,7 @@ import { readReleaseGateCacheRecord, releaseGateProofBankFile, writeReleaseGateC
8
8
  import { RELEASE_GATE_NODE_SCHEMA, validateReleaseGateManifest } from './release-gate-node.js';
9
9
  import { countReleaseGateResources, defaultReleaseGateBudget, summarizeReleaseGateBudget } from './release-gate-resource-governor.js';
10
10
  import { selectAffectedReleaseGates } from './release-gate-affected-selector.js';
11
+ import { computeTriWikiAffectedGraph } from '../triwiki/triwiki-affected-graph.js';
11
12
  import { guardedProcessKill, guardContextForRoute } from '../safety/mutation-guard.js';
12
13
  import { createRequestedScopeContract } from '../safety/requested-scope-contract.js';
13
14
  import { rmrf } from '../fsx.js';
@@ -25,7 +26,10 @@ export async function runReleaseGateDag(input) {
25
26
  const preset = input.preset || 'release';
26
27
  const manifest = loadReleaseGateManifest(root);
27
28
  const presetGates = selectReleaseGatePreset(manifest, preset);
28
- const affected = (preset === 'affected' || preset === 'fast') && input.full !== true
29
+ const triwikiGraph = input.triwiki !== false && (preset === 'affected' || preset === 'fast' || preset === 'confidence') && input.full !== true
30
+ ? computeTriWikiAffectedGraph({ root, tier: preset === 'fast' ? 'affected' : 'confidence', changedSince: input.changedSince || 'auto' })
31
+ : null;
32
+ const affected = (preset === 'affected' || preset === 'fast' || preset === 'confidence') && input.full !== true
29
33
  ? selectAffectedReleaseGates(root, manifest, presetGates, { changedSince: input.changedSince || 'auto', preset })
30
34
  : selectAffectedReleaseGates(root, manifest, presetGates, { full: true, preset });
31
35
  const selected = affected.gates;
@@ -105,7 +109,8 @@ export async function runReleaseGateDag(input) {
105
109
  report_dir: reportDir,
106
110
  failures,
107
111
  affected_graph: affectedGraph,
108
- completion_certificate: completionCertificate
112
+ completion_certificate: completionCertificate,
113
+ triwiki_affected_graph: triwikiGraph
109
114
  };
110
115
  if (!finished) {
111
116
  snapshot.in_progress = true;
@@ -114,6 +119,8 @@ export async function runReleaseGateDag(input) {
114
119
  }
115
120
  writeReleaseGateJson(path.join(reportDir, 'summary.json'), snapshot);
116
121
  writeReleaseGateJson(affectedGraphFile, affectedGraph);
122
+ if (triwikiGraph)
123
+ writeReleaseGateJson(path.join(reportDir, 'triwiki-affected-graph.json'), triwikiGraph);
117
124
  writeReleaseGateJson(completionCertificateFile, completionCertificate);
118
125
  if (finished) {
119
126
  writeReleaseGateJson(path.join(root, '.sneakoscope', 'reports', 'affected-gate-graph.json'), affectedGraph);
@@ -1,20 +1,27 @@
1
1
  import { spawnSync } from 'node:child_process';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
2
4
  import { DEFAULT_TRIWIKI_MODULE_CARDS, moduleIdsForPath } from './triwiki-module-card.js';
3
5
  import { buildTriWikiGateImpactMap } from './triwiki-gate-impact-map.js';
4
6
  export const TRIWIKI_AFFECTED_GRAPH_SCHEMA = 'sks.triwiki-affected-graph.v1';
5
7
  export function computeTriWikiAffectedGraph(input) {
6
8
  const cards = input.cards || DEFAULT_TRIWIKI_MODULE_CARDS;
7
- const changedFiles = normalizeChangedFiles(input.changedFiles && input.changedFiles.length ? input.changedFiles : gitChangedFiles(input.root));
9
+ const changedFiles = normalizeChangedFiles(resolveChangedFiles(input));
8
10
  const affectedModules = new Set();
9
11
  let conservativeReason = null;
12
+ if (input.full) {
13
+ for (const card of cards)
14
+ affectedModules.add(card.module_id);
15
+ conservativeReason = 'full_release_requested';
16
+ }
10
17
  for (const file of changedFiles) {
11
18
  const ids = moduleIdsForPath(file, cards);
12
19
  for (const id of ids)
13
20
  affectedModules.add(id);
14
- if (ids.includes('unknown'))
21
+ if (!input.full && ids.includes('unknown'))
15
22
  conservativeReason = 'unknown_changed_file';
16
23
  }
17
- if (!changedFiles.length)
24
+ if (!input.full && !changedFiles.length)
18
25
  affectedModules.add('release');
19
26
  if (changedFiles.some((file) => file === 'package.json' || file === 'package-lock.json' || file === 'release-gates.v2.json')) {
20
27
  conservativeReason = conservativeReason || 'root_release_surface_changed';
@@ -22,7 +29,7 @@ export function computeTriWikiAffectedGraph(input) {
22
29
  affectedModules.add(card.module_id);
23
30
  }
24
31
  const impactMap = buildTriWikiGateImpactMap(input.root, cards);
25
- const selected = impactMap.impacts.filter((impact) => impact.modules.some((moduleId) => affectedModules.has(moduleId)) || conservativeReason === 'root_release_surface_changed');
32
+ const selected = impactMap.impacts.filter((impact) => input.full || impact.modules.some((moduleId) => affectedModules.has(moduleId)) || conservativeReason === 'root_release_surface_changed');
26
33
  const gatePacks = new Set();
27
34
  for (const impact of selected)
28
35
  gatePacks.add(impact.gate_pack);
@@ -37,9 +44,30 @@ export function computeTriWikiAffectedGraph(input) {
37
44
  gates: selected.map((impact) => impact.gate_id).sort(),
38
45
  release_equivalent_within_scope: tier !== 'instant',
39
46
  confidence: tier === 'release' ? 'full-release' : tier === 'instant' ? 'instant' : 'affected-release-equivalent',
40
- conservative_reason: conservativeReason
47
+ conservative_reason: conservativeReason,
48
+ reused_proofs: [],
49
+ invalidated_proofs: selected.map((impact) => impact.gate_id).sort(),
50
+ required_new_proofs: selected.map((impact) => impact.gate_id).sort()
41
51
  };
42
52
  }
53
+ function resolveChangedFiles(input) {
54
+ if (input.full)
55
+ return ['*full-release*'];
56
+ if (input.changedFiles && input.changedFiles.length)
57
+ return input.changedFiles;
58
+ if (input.baseRef && input.headRef) {
59
+ const diff = gitDiffNameStatus(input.root, input.baseRef, input.headRef);
60
+ if (diff.length)
61
+ return diff;
62
+ }
63
+ if (input.changedSince && input.changedSince !== 'auto') {
64
+ const diff = gitDiffNameStatus(input.root, input.changedSince, 'HEAD');
65
+ if (diff.length)
66
+ return diff;
67
+ }
68
+ const status = gitChangedFiles(input.root);
69
+ return status.length ? status : [];
70
+ }
43
71
  function normalizeChangedFiles(files) {
44
72
  return [...new Set(files.map((file) => file.trim().replace(/\\/g, '/')).filter(Boolean))].sort();
45
73
  }
@@ -49,8 +77,36 @@ function gitChangedFiles(root) {
49
77
  return [];
50
78
  return String(result.stdout || '')
51
79
  .split('\n')
52
- .map((line) => line.slice(3).trim())
80
+ .flatMap((line) => parseGitStatusPathLine(line.slice(3).trim()))
53
81
  .filter(Boolean)
54
- .map((line) => line.includes(' -> ') ? line.split(' -> ').pop() || line : line);
82
+ .filter((file) => file !== '*full-release*');
83
+ }
84
+ function gitDiffNameStatus(root, base, head) {
85
+ const result = spawnSync('git', ['diff', '--name-status', '--find-renames', base, head], { cwd: root, encoding: 'utf8' });
86
+ if (result.status !== 0)
87
+ return [];
88
+ return String(result.stdout || '').split('\n').flatMap((line) => {
89
+ const parts = line.trim().split(/\t+/).filter(Boolean);
90
+ if (!parts.length)
91
+ return [];
92
+ const status = parts[0] || '';
93
+ if (status.startsWith('R') && parts.length >= 3)
94
+ return [parts[1], parts[2]];
95
+ if (status === 'D' && parts[1])
96
+ return [previousPathOrConservative(root, parts[1])];
97
+ return parts[1] ? [parts[1]] : [];
98
+ }).filter(Boolean);
99
+ }
100
+ function parseGitStatusPathLine(line) {
101
+ if (!line)
102
+ return [];
103
+ if (line.includes(' -> ')) {
104
+ const [oldPath, newPath] = line.split(' -> ');
105
+ return [oldPath, newPath].filter((value) => Boolean(value));
106
+ }
107
+ return [line];
108
+ }
109
+ function previousPathOrConservative(root, file) {
110
+ return fs.existsSync(path.join(root, file)) ? file : file;
55
111
  }
56
112
  //# sourceMappingURL=triwiki-affected-graph.js.map
@@ -16,9 +16,12 @@ export function computeTriWikiCacheKey(input) {
16
16
  const sourceFiles = collectInputFiles(root, input.inputs);
17
17
  const implementationFiles = collectInputFiles(root, input.implementationFiles || []);
18
18
  const packageLock = hashPathIfPresent(root, 'package-lock.json');
19
- const envHash = hashJson(envFingerprint(input.envAllowlist || []));
19
+ const releaseGates = hashPathIfPresent(root, 'release-gates.v2.json');
20
+ const env = envFingerprint(input.envAllowlist || []);
21
+ const envHash = hashJson(env.records);
20
22
  const fixtureVersion = input.fixtureVersion || 'fixture-v1';
21
23
  const toolVersion = input.toolVersion || readPackageVersion(root);
24
+ const toolVersions = { sks: toolVersion, ...(input.toolVersions || {}) };
22
25
  const inputHash = hashJson(sourceFiles.records);
23
26
  const implementationHash = hashJson(implementationFiles.records);
24
27
  const key = hashJson({
@@ -27,9 +30,10 @@ export function computeTriWikiCacheKey(input) {
27
30
  input_hash: inputHash,
28
31
  implementation_hash: implementationHash,
29
32
  package_lock_hash: packageLock.hash,
33
+ release_gates_hash: releaseGates.hash,
30
34
  env_allowlist_hash: envHash,
31
35
  fixture_version: fixtureVersion,
32
- tool_version: toolVersion,
36
+ tool_versions: toolVersions,
33
37
  salt: input.salt || ''
34
38
  });
35
39
  return {
@@ -39,11 +43,15 @@ export function computeTriWikiCacheKey(input) {
39
43
  input_hash: inputHash,
40
44
  implementation_hash: implementationHash,
41
45
  package_lock_hash: packageLock.hash,
46
+ release_gates_hash: releaseGates.hash,
42
47
  env_allowlist_hash: envHash,
43
48
  fixture_version: fixtureVersion,
44
49
  tool_version: toolVersion,
50
+ tool_versions: toolVersions,
45
51
  file_count: sourceFiles.records.length,
46
- missing_inputs: [...sourceFiles.missing, ...packageLock.missing]
52
+ missing_inputs: [...sourceFiles.missing, ...packageLock.missing, ...releaseGates.missing],
53
+ redacted_env_keys: env.redacted_keys,
54
+ unsupported_globs: [...sourceFiles.unsupported, ...implementationFiles.unsupported]
47
55
  };
48
56
  }
49
57
  export function hashText(value) {
@@ -55,7 +63,12 @@ export function hashJson(value) {
55
63
  export function collectInputFiles(root, patterns) {
56
64
  const files = new Set();
57
65
  const missing = [];
66
+ const unsupported = [];
58
67
  for (const pattern of patterns) {
68
+ if (/[{}]/.test(pattern)) {
69
+ unsupported.push(`unsupported_brace_glob:${pattern}`);
70
+ continue;
71
+ }
59
72
  const matches = expandInputPattern(root, pattern);
60
73
  if (!matches.length) {
61
74
  const literal = path.resolve(root, pattern);
@@ -77,7 +90,7 @@ export function collectInputFiles(root, patterns) {
77
90
  else if (hashed.record)
78
91
  records.push(hashed.record);
79
92
  }
80
- return { records, missing: [...new Set(missing)].sort() };
93
+ return { records, missing: [...new Set(missing)].sort(), unsupported: [...new Set(unsupported)].sort() };
81
94
  }
82
95
  function expandInputPattern(root, pattern) {
83
96
  const normalized = normalizePattern(pattern);
@@ -158,7 +171,9 @@ function hashPathIfPresent(root, rel) {
158
171
  }
159
172
  if (stat.isSymbolicLink()) {
160
173
  const target = fs.readlinkSync(absolute);
161
- const hash = hashJson({ rel, target, mode });
174
+ const resolved = path.resolve(path.dirname(absolute), target);
175
+ const outsideRoot = !resolved.startsWith(path.resolve(root) + path.sep);
176
+ const hash = hashJson({ rel, target, mode, outside_root: outsideRoot });
162
177
  return { hash, missing: [], record: { path: normalizePattern(rel), hash, size: target.length, mode } };
163
178
  }
164
179
  const hash = hashFileChunked(absolute, stat.size);
@@ -188,14 +203,19 @@ function hashFileChunked(file, size) {
188
203
  return h.digest('hex');
189
204
  }
190
205
  function envFingerprint(keys) {
191
- return [...new Set(keys)].sort().map((key) => {
206
+ const redacted = [];
207
+ const records = [...new Set(keys)].sort().map((key) => {
192
208
  const value = process.env[key];
209
+ const secret = /SECRET|TOKEN|PASSWORD|KEY|CREDENTIAL/i.test(key);
210
+ if (secret)
211
+ redacted.push(key);
193
212
  return {
194
213
  key,
195
214
  present: value !== undefined,
196
- fingerprint: value === undefined ? 'missing' : hashText(`${key}:${value}`)
215
+ fingerprint: value === undefined ? 'missing' : secret ? 'redacted-secret' : hashText(`${key}:${hashText(value)}`)
197
216
  };
198
217
  });
218
+ return { records, redacted_keys: redacted };
199
219
  }
200
220
  function readPackageVersion(root) {
201
221
  try {
@@ -14,17 +14,27 @@ export function buildTriWikiGateImpactMap(root, cards = DEFAULT_TRIWIKI_MODULE_C
14
14
  gate_pack: gatePackForGate(gate, modules, cards),
15
15
  cache_inputs: gate.cache.inputs,
16
16
  resources: gate.resource,
17
+ semantic_dependencies: semanticDependenciesForGate(gate),
18
+ fixture_dependencies: fixtureDependenciesForGate(gate),
19
+ resource_class: gate.resource,
20
+ expected_duration_ms_p50: expectedDurationForGate(gate, 0.5),
21
+ expected_duration_ms_p95: expectedDurationForGate(gate, 0.95),
17
22
  orphan: !scriptExistsForGateCommand(gate.command, scripts),
18
23
  command: gate.command
19
24
  };
20
25
  });
21
- return {
26
+ const packageScriptOrphans = findPackageScriptOrphans(manifest, scripts);
27
+ const map = {
22
28
  schema: TRIWIKI_GATE_IMPACT_MAP_SCHEMA,
23
29
  root,
24
30
  gate_count: impacts.length,
25
31
  orphan_count: impacts.filter((impact) => impact.orphan).length,
26
- impacts
32
+ package_script_orphan_count: packageScriptOrphans.length,
33
+ impacts,
34
+ package_script_orphans: packageScriptOrphans
27
35
  };
36
+ writeImpactMap(root, map);
37
+ return map;
28
38
  }
29
39
  export function loadReleaseGateManifest(root) {
30
40
  const manifestPath = path.join(root, 'release-gates.v2.json');
@@ -51,6 +61,9 @@ export function modulesForGate(gate, cards = DEFAULT_TRIWIKI_MODULE_CARDS) {
51
61
  return [...modules].sort();
52
62
  }
53
63
  export function gatePackForGate(gate, modules, cards = DEFAULT_TRIWIKI_MODULE_CARDS) {
64
+ const explicitPack = gate.x_sks_pack;
65
+ if (explicitPack)
66
+ return explicitPack;
54
67
  if (gate.id.startsWith('triwiki:'))
55
68
  return 'triwiki';
56
69
  if (gate.id.startsWith('gate-pack:') || gate.id.startsWith('release:'))
@@ -76,4 +89,36 @@ function inputMatches(pattern, input) {
76
89
  const cleanInput = input.replace(/\/\*\*.*$/, '');
77
90
  return cleanInput === cleanPattern || cleanInput.startsWith(`${cleanPattern}/`);
78
91
  }
92
+ function semanticDependenciesForGate(gate) {
93
+ const deps = new Set(gate.deps || []);
94
+ for (const input of gate.cache.inputs || []) {
95
+ if (input.includes('package'))
96
+ deps.add('package-metadata');
97
+ if (input.includes('release-gates'))
98
+ deps.add('release-gate-manifest');
99
+ if (input.includes('src/core/triwiki'))
100
+ deps.add('triwiki-runtime');
101
+ }
102
+ return [...deps].sort();
103
+ }
104
+ function fixtureDependenciesForGate(gate) {
105
+ return (gate.cache.inputs || []).filter((input) => input.includes('fixture') || input.includes('test/')).sort();
106
+ }
107
+ function expectedDurationForGate(gate, quantile) {
108
+ const resources = new Set(gate.resource || []);
109
+ const base = resources.has('cpu-heavy') ? 45_000 : resources.has('remote-model-real') ? 90_000 : 12_000;
110
+ return quantile === 0.95 ? Math.round(base * 2.5) : base;
111
+ }
112
+ function findPackageScriptOrphans(manifest, scripts) {
113
+ const releaseScripts = new Set(manifest.gates.map((gate) => gate.command.match(/^npm run ([^ ]+)/)?.[1]).filter((value) => Boolean(value)));
114
+ return Object.keys(scripts)
115
+ .filter((name) => /^(triwiki|gate-pack|scheduler|release|doctor|legacy|orphan|sks:401|certificate|build-once|sksd|probes):/.test(name))
116
+ .filter((name) => !releaseScripts.has(name))
117
+ .sort();
118
+ }
119
+ function writeImpactMap(root, map) {
120
+ const file = path.join(root, '.sneakoscope', 'reports', 'triwiki-gate-impact-map.json');
121
+ fs.mkdirSync(path.dirname(file), { recursive: true });
122
+ fs.writeFileSync(file, `${JSON.stringify(map, null, 2)}\n`);
123
+ }
79
124
  //# sourceMappingURL=triwiki-gate-impact-map.js.map
@@ -0,0 +1,81 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { markTriWikiProofInvalidated, triWikiProofBankDir } from './triwiki-proof-bank.js';
4
+ export const TRIWIKI_PROOF_INVALIDATION_SCHEMA = 'sks.triwiki-proof-invalidation.v1';
5
+ export async function invalidateTriWikiProofsForChange(input) {
6
+ const broad = input.changedFiles.some((file) => ['package.json', 'package-lock.json', 'release-gates.v2.json'].includes(file));
7
+ const affectedGates = new Set(input.affectedGates);
8
+ const affectedPacks = new Set();
9
+ const invalidated = [];
10
+ let unaffected = 0;
11
+ for (const file of walkProofs(input.root)) {
12
+ const parsed = readProofIdentity(file);
13
+ if (!parsed)
14
+ continue;
15
+ const hit = broad || affectedGates.has(parsed.subject_id) || input.affectedModules.includes(parsed.subject_id);
16
+ if (!hit) {
17
+ unaffected += 1;
18
+ continue;
19
+ }
20
+ if (parsed.subject_type === 'gate-pack')
21
+ affectedPacks.add(parsed.subject_id);
22
+ if (markTriWikiProofInvalidated(input.root, parsed.subject_id, parsed.proof_id, input.reason, pluralSubject(parsed.subject_type))) {
23
+ invalidated.push(parsed.proof_id);
24
+ }
25
+ }
26
+ const report = {
27
+ schema: TRIWIKI_PROOF_INVALIDATION_SCHEMA,
28
+ root: input.root,
29
+ reason: input.reason,
30
+ broad,
31
+ affected_gates: [...affectedGates].sort(),
32
+ affected_packs: [...affectedPacks].sort(),
33
+ invalidated_proofs: invalidated.sort(),
34
+ unaffected_proofs: unaffected
35
+ };
36
+ const out = path.join(input.root, '.sneakoscope', 'reports', 'triwiki-proof-invalidation.json');
37
+ fs.mkdirSync(path.dirname(out), { recursive: true });
38
+ fs.writeFileSync(out, `${JSON.stringify(report, null, 2)}\n`);
39
+ return report;
40
+ }
41
+ function walkProofs(root) {
42
+ const base = triWikiProofBankDir(root);
43
+ if (!fs.existsSync(base))
44
+ return [];
45
+ const out = [];
46
+ const stack = [base];
47
+ while (stack.length) {
48
+ const dir = stack.pop();
49
+ if (!dir)
50
+ continue;
51
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
52
+ const absolute = path.join(dir, entry.name);
53
+ if (entry.isDirectory())
54
+ stack.push(absolute);
55
+ else if (entry.isFile() && entry.name.endsWith('.json'))
56
+ out.push(absolute);
57
+ }
58
+ }
59
+ return out;
60
+ }
61
+ function readProofIdentity(file) {
62
+ try {
63
+ const json = JSON.parse(fs.readFileSync(file, 'utf8'));
64
+ if (!json.subject_type || !json.subject_id || !json.proof_id)
65
+ return null;
66
+ return { subject_type: json.subject_type, subject_id: json.subject_id, proof_id: json.proof_id };
67
+ }
68
+ catch {
69
+ return null;
70
+ }
71
+ }
72
+ function pluralSubject(value) {
73
+ if (value === 'gate')
74
+ return 'gates';
75
+ if (value === 'gate-pack')
76
+ return 'gate-packs';
77
+ if (value === 'module')
78
+ return 'modules';
79
+ return 'pipelines';
80
+ }
81
+ //# sourceMappingURL=triwiki-invalidation.js.map