sneakoscope 8.3.2 → 8.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/README.md +1 -1
  2. package/config/architecture-map-policy.v1.json +168 -0
  3. package/crates/sks-core/Cargo.lock +1 -1
  4. package/crates/sks-core/Cargo.toml +1 -1
  5. package/dist/commands/bridge.js +19 -1
  6. package/dist/commands/doctor-profile.js +1 -1
  7. package/dist/commands/doctor.js +33 -1
  8. package/dist/config/skills-manifest.json +1 -1
  9. package/dist/core/align/align-route.js +8 -2
  10. package/dist/core/align/code-navigation-align.js +88 -32
  11. package/dist/core/architecture-map-pipeline.js +172 -0
  12. package/dist/core/architecture-map-review.js +180 -0
  13. package/dist/core/codex-app/codex-app-fast-ui-repair.js +2 -1
  14. package/dist/core/codex-app/menubar/constants.js +1 -0
  15. package/dist/core/codex-lb/codex-lb-env.js +25 -3
  16. package/dist/core/codex-lb/combined-catalog/builder.js +7 -2
  17. package/dist/core/codex-lb/combined-catalog/model-selection.js +71 -0
  18. package/dist/core/codex-lb/combined-catalog/normalize.js +20 -3
  19. package/dist/core/codex-lb/desktop-controller-v3/catalog.js +75 -4
  20. package/dist/core/codex-lb/desktop-controller-v3.js +5 -1
  21. package/dist/core/commands/triwiki-atlas-command.js +372 -0
  22. package/dist/core/commands/triwiki-command.js +12 -1
  23. package/dist/core/fsx.js +14 -0
  24. package/dist/core/git-hygiene/git-policy.js +4 -0
  25. package/dist/core/git-hygiene/gitignore-writer.js +2 -0
  26. package/dist/core/gx-renderer.js +2 -1
  27. package/dist/core/managed-paths.js +1 -0
  28. package/dist/core/perf/release-latency-slo.js +57 -11
  29. package/dist/core/pipeline-internals/pipeline-stage-builder.js +181 -0
  30. package/dist/core/pipeline-internals/runtime-core.js +13 -172
  31. package/dist/core/pipeline-internals/runtime-gates.js +12 -1
  32. package/dist/core/release/content-digest-cache.js +70 -0
  33. package/dist/core/release/gate-manifest.js +9 -0
  34. package/dist/core/release/main-push-guard.js +4 -3
  35. package/dist/core/release/package-dist-snapshot.js +18 -2
  36. package/dist/core/release/physical-release-gates.js +26 -3
  37. package/dist/core/release/release-authorization-snapshot.js +4 -0
  38. package/dist/core/release/release-gate-cache-v2.js +27 -3
  39. package/dist/core/release/release-gate-contract.js +2 -0
  40. package/dist/core/release/release-gate-dag.js +3 -0
  41. package/dist/core/release/release-inspection-memo.js +69 -0
  42. package/dist/core/release/release-pack-content-scanner.js +5 -0
  43. package/dist/core/release/release-pack-receipt.js +28 -18
  44. package/dist/core/release/release-pack-tarball.js +10 -0
  45. package/dist/core/release/release-upgrade-baseline.js +4 -0
  46. package/dist/core/routes.js +1 -1
  47. package/dist/core/safety/ssot-authority-inventory.js +114 -0
  48. package/dist/core/safety/ssot-guard.js +2 -44
  49. package/dist/core/triwiki/context-graph/architecture/baseline.js +81 -0
  50. package/dist/core/triwiki/context-graph/architecture/capsule.js +34 -0
  51. package/dist/core/triwiki/context-graph/architecture/contracts.js +67 -0
  52. package/dist/core/triwiki/context-graph/architecture/delta.js +149 -0
  53. package/dist/core/triwiki/context-graph/architecture/finding-factory.js +28 -0
  54. package/dist/core/triwiki/context-graph/architecture/findings.js +173 -0
  55. package/dist/core/triwiki/context-graph/architecture/fingerprint.js +75 -0
  56. package/dist/core/triwiki/context-graph/architecture/index.js +15 -0
  57. package/dist/core/triwiki/context-graph/architecture/input-bundle.js +66 -0
  58. package/dist/core/triwiki/context-graph/architecture/metrics.js +137 -0
  59. package/dist/core/triwiki/context-graph/architecture/policy.js +321 -0
  60. package/dist/core/triwiki/context-graph/architecture/slice.js +100 -0
  61. package/dist/core/triwiki/context-graph/architecture/ssot-analysis.js +59 -0
  62. package/dist/core/triwiki/context-graph/architecture/topology-overlay.js +106 -0
  63. package/dist/core/triwiki/context-graph/architecture/validation.js +145 -0
  64. package/dist/core/triwiki/context-graph/architecture/voxel-context.js +39 -0
  65. package/dist/core/triwiki/context-graph/extractors/index.js +4 -0
  66. package/dist/core/triwiki/context-graph/projections/index.js +2 -0
  67. package/dist/core/triwiki/context-graph/projections/mermaid/architecture-delta.js +68 -0
  68. package/dist/core/triwiki/context-graph/projections/mermaid/ast.js +49 -0
  69. package/dist/core/triwiki/context-graph/projections/mermaid/change-impact.js +43 -0
  70. package/dist/core/triwiki/context-graph/projections/mermaid/contracts.js +5 -0
  71. package/dist/core/triwiki/context-graph/projections/mermaid/escape.js +45 -0
  72. package/dist/core/triwiki/context-graph/projections/mermaid/ids.js +73 -0
  73. package/dist/core/triwiki/context-graph/projections/mermaid/index.js +113 -0
  74. package/dist/core/triwiki/context-graph/projections/mermaid/manifest.js +37 -0
  75. package/dist/core/triwiki/context-graph/projections/mermaid/module-dependency.js +21 -0
  76. package/dist/core/triwiki/context-graph/projections/mermaid/ownership-workstream.js +21 -0
  77. package/dist/core/triwiki/context-graph/projections/mermaid/project-topology.js +13 -0
  78. package/dist/core/triwiki/context-graph/projections/mermaid/public-surface.js +17 -0
  79. package/dist/core/triwiki/context-graph/projections/mermaid/reduce.js +44 -0
  80. package/dist/core/triwiki/context-graph/projections/mermaid/risk-domains.js +23 -0
  81. package/dist/core/triwiki/context-graph/projections/mermaid/runtime-control.js +20 -0
  82. package/dist/core/triwiki/context-graph/projections/mermaid/selection-ledger.js +94 -0
  83. package/dist/core/triwiki/context-graph/projections/mermaid/serializer.js +81 -0
  84. package/dist/core/triwiki/context-graph/projections/mermaid/ssot-provenance.js +17 -0
  85. package/dist/core/triwiki/context-graph/projections/mermaid/verification-coverage.js +17 -0
  86. package/dist/core/triwiki/context-graph/projections/mermaid/view-builder-common.js +1 -0
  87. package/dist/core/triwiki/context-graph/projections/mermaid/view-builder.js +173 -0
  88. package/dist/core/triwiki/context-graph/projections/mermaid/view-specs.js +33 -0
  89. package/dist/core/triwiki/context-graph/store/architecture-map-store.js +71 -0
  90. package/dist/core/triwiki/triwiki-proof-bank-index.js +26 -1
  91. package/dist/core/triwiki/triwiki-proof-bank.js +82 -2
  92. package/dist/core/version.js +1 -1
  93. package/dist/native/sks-menubar/Sources/OverviewViewController.swift +2 -2
  94. package/dist/native/sks-menubar/Sources/ProvidersModelExposure.swift +178 -0
  95. package/dist/native/sks-menubar/Sources/ProvidersViewController.swift +10 -1
  96. package/dist/scripts/architecture-map-check.js +299 -0
  97. package/dist/scripts/lib/ensure-dist-fresh.js +48 -1
  98. package/dist/scripts/release-metadata-check.js +2 -0
  99. package/package.json +6 -3
  100. package/release-gates.v2.json +204 -0
  101. package/schemas/architecture-baseline.v1.schema.json +47 -0
  102. package/schemas/architecture-map-manifest.v1.schema.json +40 -0
  103. package/schemas/architecture-map-policy.v1.schema.json +147 -0
  104. package/schemas/architecture-review.v1.schema.json +46 -0
  105. package/schemas/mermaid-projection.v1.schema.json +47 -0
@@ -0,0 +1,70 @@
1
+ import crypto from 'node:crypto';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ export const CONTENT_DIGEST_CACHE_SCHEMA = 'sks.content-digest-cache.v1';
5
+ export const CONTENT_DIGEST_CACHE_ENV = 'SKS_RELEASE_CONTENT_DIGEST_CACHE';
6
+ const processMemo = new Map();
7
+ export function contentDigestCacheEnabled() {
8
+ return process.env[CONTENT_DIGEST_CACHE_ENV] !== '0';
9
+ }
10
+ export function contentDigestCachePath(root) {
11
+ return path.join(root, '.sneakoscope', 'cache', 'release-digests.json');
12
+ }
13
+ export function statFingerprint(root, files) {
14
+ const hash = crypto.createHash('sha256');
15
+ for (const file of files) {
16
+ let stat;
17
+ try {
18
+ stat = fs.statSync(path.isAbsolute(file) ? file : path.join(root, file), { bigint: true });
19
+ }
20
+ catch {
21
+ hash.update(`${file}\0missing\0`);
22
+ continue;
23
+ }
24
+ hash.update(`${file}\0${stat.size}\0${stat.mtimeNs}\0${stat.ino}\0`);
25
+ }
26
+ return hash.digest('hex');
27
+ }
28
+ export function cachedByFingerprint(root, bucket, fingerprint, compute) {
29
+ if (!contentDigestCacheEnabled())
30
+ return compute();
31
+ const memoKey = `${path.resolve(root)}\0${bucket}`;
32
+ const memo = processMemo.get(memoKey);
33
+ if (memo?.fingerprint === fingerprint)
34
+ return structuredClone(memo.value);
35
+ const document = readCache(root);
36
+ const stored = document.entries[bucket];
37
+ if (stored && stored.fingerprint === fingerprint) {
38
+ processMemo.set(memoKey, { fingerprint, value: stored.value });
39
+ return structuredClone(stored.value);
40
+ }
41
+ const value = compute();
42
+ processMemo.set(memoKey, { fingerprint, value });
43
+ writeCache(root, bucket, fingerprint, value);
44
+ return value;
45
+ }
46
+ export function clearContentDigestProcessMemo() {
47
+ processMemo.clear();
48
+ }
49
+ function readCache(root) {
50
+ try {
51
+ const parsed = JSON.parse(fs.readFileSync(contentDigestCachePath(root), 'utf8'));
52
+ if (parsed?.schema === CONTENT_DIGEST_CACHE_SCHEMA && parsed.entries && typeof parsed.entries === 'object')
53
+ return parsed;
54
+ }
55
+ catch { }
56
+ return { schema: CONTENT_DIGEST_CACHE_SCHEMA, entries: {} };
57
+ }
58
+ function writeCache(root, bucket, fingerprint, value) {
59
+ const file = contentDigestCachePath(root);
60
+ try {
61
+ const document = readCache(root);
62
+ document.entries[bucket] = { fingerprint, value, recorded_at: new Date().toISOString() };
63
+ fs.mkdirSync(path.dirname(file), { recursive: true });
64
+ const temp = `${file}.${process.pid}.tmp`;
65
+ fs.writeFileSync(temp, `${JSON.stringify(document, null, 2)}\n`);
66
+ fs.renameSync(temp, file);
67
+ }
68
+ catch {
69
+ }
70
+ }
@@ -65,6 +65,15 @@ function costFor(id) {
65
65
  export function affectedGlobsFor(id) {
66
66
  const prefix = id.split(':')[0];
67
67
  switch (prefix) {
68
+ case 'architecture-map':
69
+ return [
70
+ 'src/core/triwiki/context-graph/architecture/**',
71
+ 'src/core/triwiki/context-graph/projections/mermaid/**',
72
+ 'src/core/triwiki/context-graph/store/architecture-map-store.ts',
73
+ 'src/scripts/architecture-map-check.ts',
74
+ 'config/architecture-map-policy.v1.json',
75
+ 'package.json'
76
+ ];
68
77
  case 'architecture':
69
78
  return ['src/core/safety/ssot-guard.ts', 'src/core/pipeline-internals/runtime-core.ts', 'src/core/pipeline-internals/runtime-gates.ts', 'src/core/commands/naruto-command.ts', 'src/scripts/release-parallel-check.ts', 'src/scripts/architecture-guard-check.ts', 'docs/architecture-ts-rust-boundary.md', 'package.json'];
70
79
  case 'core-skill':
@@ -1,11 +1,12 @@
1
1
  import path from 'node:path';
2
+ import { sameFilesystemPathSync } from '../fsx.js';
2
3
  import { readMacosMenubarProof, validateMacosMenubarProofArtifacts } from './macos-menubar-proof.js';
3
4
  import { fileSha256, gitOk, gitText, readJson, relative, unique } from './release-proof-io.js';
4
5
  import { releaseOriginIdentity } from './release-origin.js';
5
6
  import { releaseProofDir, validateLocalReleasePackBinding } from './release-pack-receipt.js';
6
7
  import { validatePhysicalReleaseGateInspection } from './physical-release-gates.js';
7
8
  import { validateFullReleaseStamp } from './release-stamp-proof.js';
8
- import { RELEASE_UPGRADE_BASELINE_SHA256, RELEASE_UPGRADE_BASELINE_VERSION, releaseUpgradeExpectedVersion, releaseUpgradeProofFilename } from './release-upgrade-baseline.js';
9
+ import { RELEASE_UPGRADE_BASELINE_SHA256, RELEASE_UPGRADE_BASELINE_VERSION, isExpectedReleaseUpgradeLaunchctlCall, releaseUpgradeExpectedVersion, releaseUpgradeProofFilename } from './release-upgrade-baseline.js';
9
10
  export const MAIN_PUSH_GUARD_SCHEMA = 'sks.release-main-push-guard.v1';
10
11
  const RELEASE_UPGRADE_SMOKE_SCHEMA = 'sks.release-upgrade-smoke.v2';
11
12
  const RELEASE_UPGRADE_COMMAND_STAGES = Object.freeze([
@@ -244,7 +245,7 @@ function validateReleaseUpgradeIsolationAndCommands(report, blockers) {
244
245
  || !path.isAbsolute(String(safety?.launchctl_log_path || ''))
245
246
  || !lexicalSubpath(String(safety?.launchctl_log_path || ''), sandbox)
246
247
  || !Array.isArray(safety?.launchctl_calls)
247
- || safety.launchctl_calls.some((call) => !/^(?:unsetenv (?:CODEX_LB_API_KEY|OPENROUTER_API_KEY)|print)$/.test(String(call)))
248
+ || safety.launchctl_calls.some((call) => !isExpectedReleaseUpgradeLaunchctlCall(call))
248
249
  || !Array.isArray(safety?.launchctl_unexpected_calls)
249
250
  || safety.launchctl_unexpected_calls.length > 0)
250
251
  blockers.push('install_safety_policy_invalid');
@@ -367,7 +368,7 @@ function validateDoctorCommandReceipt(command, stage, reportFile, isolation, tar
367
368
  || binding.matches_stdout !== true
368
369
  || binding.schema !== 'sks.doctor-status.v3'
369
370
  || binding.ok !== true
370
- || binding.root !== isolation.workspace)
371
+ || !sameFilesystemPathSync(binding.root, isolation.workspace))
371
372
  blockers.push(`doctor_report_binding_invalid:${stage}`);
372
373
  }
373
374
  else if (stage !== 'target_doctor' && binding.validation_mode === 'pinned_baseline_stdout_only') {
@@ -1,6 +1,7 @@
1
1
  import crypto from 'node:crypto';
2
2
  import fs from 'node:fs';
3
3
  import path from 'node:path';
4
+ import { cachedByFingerprint, statFingerprint } from './content-digest-cache.js';
4
5
  export function packageDistSnapshot(root, pkg) {
5
6
  const distRoot = path.join(root, 'dist');
6
7
  if (!fs.existsSync(distRoot))
@@ -11,6 +12,9 @@ export function packageDistSnapshot(root, pkg) {
11
12
  const included = files
12
13
  .filter((file) => packageFileIncluded(`dist/${path.relative(distRoot, file).split(path.sep).join('/')}`, entries))
13
14
  .sort();
15
+ return cachedByFingerprint(root, 'package-dist-snapshot', statFingerprint(root, included), () => computePackageDistSnapshot(distRoot, included));
16
+ }
17
+ function computePackageDistSnapshot(distRoot, included) {
14
18
  const hash = crypto.createHash('sha256');
15
19
  for (const file of included) {
16
20
  const rel = path.relative(distRoot, file).split(path.sep).join('/');
@@ -48,6 +52,10 @@ export function packageFilesSnapshot(root, pkg) {
48
52
  }
49
53
  }
50
54
  const files = [...candidates].filter((file) => packageFileIncluded(file, entries)).sort();
55
+ const fingerprint = statFingerprint(root, [...missingEntries.map((entry) => `missing:${entry}`), ...files]);
56
+ return cachedByFingerprint(root, 'package-files-snapshot', fingerprint, () => computePackageFilesSnapshot(root, files, missingEntries));
57
+ }
58
+ function computePackageFilesSnapshot(root, files, missingEntries) {
51
59
  const hash = crypto.createHash('sha256');
52
60
  for (const entry of missingEntries.sort()) {
53
61
  hash.update(entry);
@@ -95,6 +103,15 @@ function packageFileIncluded(file, entries) {
95
103
  }
96
104
  return included;
97
105
  }
106
+ const PATTERN_REGEXP_CACHE = new Map();
107
+ function packagePatternRegExps(normalized) {
108
+ const cached = PATTERN_REGEXP_CACHE.get(normalized);
109
+ if (cached)
110
+ return cached;
111
+ const compiled = expandBracePatterns(normalized).map((expanded) => globPatternToRegExp(expanded));
112
+ PATTERN_REGEXP_CACHE.set(normalized, compiled);
113
+ return compiled;
114
+ }
98
115
  function matchesPackagePattern(file, pattern) {
99
116
  const rel = normalizeRel(file);
100
117
  const normalized = normalizeRel(pattern);
@@ -102,8 +119,7 @@ function matchesPackagePattern(file, pattern) {
102
119
  return false;
103
120
  if (!hasGlob(normalized))
104
121
  return rel === normalized || rel.startsWith(`${normalized}/`);
105
- for (const expanded of expandBracePatterns(normalized)) {
106
- const re = globPatternToRegExp(expanded);
122
+ for (const re of packagePatternRegExps(normalized)) {
107
123
  if (re.test(rel))
108
124
  return true;
109
125
  const parts = rel.split('/');
@@ -3,6 +3,7 @@ import fs from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import { spawnSync } from 'node:child_process';
5
5
  import { desktopBridgeReleaseSourceIdentitySha256, inspectDesktopBridgeReleaseEvidence } from './desktop-bridge-release-evidence.js';
6
+ import { cloneBuffer, fileIdentity, inspectionKey, memoizeReleaseInspection, toolIdentity } from './release-inspection-memo.js';
6
7
  export const PHYSICAL_RELEASE_GATES_SCHEMA = 'sks.release-physical-gates.v2';
7
8
  export const PHYSICAL_RELEASE_GATES_INSPECTION_SCHEMA = 'sks.release-physical-gates-inspection.v2';
8
9
  export const PHYSICAL_RELEASE_EVIDENCE_ARTIFACT_MANIFEST_SCHEMA = 'sks.release-physical-evidence-artifact-manifest.v1';
@@ -375,6 +376,16 @@ function inspectPhysicalEvidenceArtifact(input) {
375
376
  };
376
377
  }
377
378
  function verifyGithubArtifactAttestation(input) {
379
+ const key = inspectionKey(input.archivePath, fileIdentity(input.archivePath), toolIdentity('gh'), input.repository, input.trustedWorkflow, input.sourceCommit, input.runId);
380
+ const verdict = memoizeReleaseInspection('github-artifact-attestation', key, () => {
381
+ const blockers = [];
382
+ const invocationUri = computeGithubArtifactAttestation({ ...input, blockers });
383
+ return { invocation_uri: invocationUri, blockers };
384
+ });
385
+ input.blockers.push(...verdict.blockers);
386
+ return verdict.invocation_uri;
387
+ }
388
+ function computeGithubArtifactAttestation(input) {
378
389
  const result = spawnSync('gh', [
379
390
  'attestation', 'verify', input.archivePath,
380
391
  '--repo', input.repository,
@@ -429,6 +440,15 @@ function readBoundArchive(file) {
429
440
  }
430
441
  }
431
442
  function listArchiveFiles(archivePath, blockers) {
443
+ const listing = memoizeReleaseInspection('physical-archive-listing', inspectionKey(archivePath, fileIdentity(archivePath), toolIdentity('tar')), () => {
444
+ const own = [];
445
+ const files = computeArchiveFiles(archivePath, own);
446
+ return { entries: files ? [...files] : null, blockers: own };
447
+ });
448
+ blockers.push(...listing.blockers);
449
+ return listing.entries ? new Map(listing.entries) : null;
450
+ }
451
+ function computeArchiveFiles(archivePath, blockers) {
432
452
  const result = spawnSync('tar', ['-tzf', archivePath], { encoding: 'utf8', maxBuffer: 16 * 1024 * 1024 });
433
453
  if (result.status !== 0) {
434
454
  blockers.push('physical_evidence_artifact_archive_unreadable');
@@ -455,12 +475,15 @@ function listArchiveFiles(archivePath, blockers) {
455
475
  return files;
456
476
  }
457
477
  function readArchiveMember(archivePath, member, label, blockers) {
458
- const result = spawnSync('tar', ['-xOzf', archivePath, member], { encoding: 'buffer', maxBuffer: 64 * 1024 * 1024 });
459
- if (result.status !== 0 || !Buffer.isBuffer(result.stdout)) {
478
+ const bytes = memoizeReleaseInspection('physical-archive-member', inspectionKey(archivePath, fileIdentity(archivePath), toolIdentity('tar'), member), () => {
479
+ const result = spawnSync('tar', ['-xOzf', archivePath, member], { encoding: 'buffer', maxBuffer: 64 * 1024 * 1024 });
480
+ return result.status !== 0 || !Buffer.isBuffer(result.stdout) ? null : result.stdout;
481
+ }, (value) => (value ? cloneBuffer(value) : null));
482
+ if (!bytes) {
460
483
  blockers.push(`${label}_unreadable`);
461
484
  return null;
462
485
  }
463
- return result.stdout;
486
+ return bytes;
464
487
  }
465
488
  function safeArchivePath(value) {
466
489
  if (!value || value.includes('\\') || value.startsWith('/') || value.endsWith('/'))
@@ -2,6 +2,7 @@ import { spawnSync } from 'node:child_process';
2
2
  import crypto from 'node:crypto';
3
3
  import fs from 'node:fs';
4
4
  import path from 'node:path';
5
+ import { cachedByFingerprint, statFingerprint } from './content-digest-cache.js';
5
6
  import { packageDistSnapshot, packageFilesSnapshot } from './package-dist-snapshot.js';
6
7
  export const RELEASE_AUTHORIZATION_SNAPSHOT_KEYS = Object.freeze([
7
8
  'git_commit',
@@ -44,6 +45,9 @@ function releaseGateHash(root, pkg) {
44
45
  }
45
46
  function releaseSourceSnapshot(root) {
46
47
  const files = gitFiles(root).filter(releaseRelevant).sort();
48
+ return cachedByFingerprint(root, 'release-source-snapshot', statFingerprint(root, files), () => computeReleaseSourceSnapshot(root, files));
49
+ }
50
+ function computeReleaseSourceSnapshot(root, files) {
47
51
  const hash = crypto.createHash('sha256');
48
52
  for (const file of files) {
49
53
  const full = path.join(root, file);
@@ -292,10 +292,14 @@ function writeReleaseCacheBridgeReport(root, row) {
292
292
  parsed = existing;
293
293
  }
294
294
  catch { }
295
+ const stamped = { ...row, recorded_at: new Date().toISOString() };
295
296
  parsed.records = [
296
- ...parsed.records.filter((item) => item.gate_id !== row.gate_id || item.cache_key !== row.cache_key || item.bridge !== row.bridge),
297
- row
298
- ].sort((a, b) => `${a.gate_id}:${a.bridge}`.localeCompare(`${b.gate_id}:${b.bridge}`));
297
+ ...parsed.records.filter((item) => item.gate_id !== row.gate_id || item.bridge !== row.bridge),
298
+ stamped
299
+ ]
300
+ .sort((a, b) => Date.parse(String(b.recorded_at || 0)) - Date.parse(String(a.recorded_at || 0)) || 0)
301
+ .slice(0, releaseCacheBridgeRecordLimit())
302
+ .sort((a, b) => `${a.gate_id}:${a.bridge}`.localeCompare(`${b.gate_id}:${b.bridge}`));
299
303
  fs.mkdirSync(path.dirname(file), { recursive: true });
300
304
  fs.writeFileSync(file, `${JSON.stringify(parsed, null, 2)}\n`);
301
305
  }
@@ -317,9 +321,29 @@ function writeCacheRecord(file, key, record) {
317
321
  parsed.schema = RELEASE_GATE_CACHE_V2_SCHEMA;
318
322
  parsed.records ||= {};
319
323
  parsed.records[key] = record;
324
+ parsed.records = pruneCacheRecords(parsed.records);
320
325
  fs.mkdirSync(path.dirname(file), { recursive: true });
321
326
  fs.writeFileSync(file, `${JSON.stringify(parsed, null, 2)}\n`);
322
327
  }
328
+ export function pruneCacheRecords(records, keep = releaseGateCacheRecordLimit()) {
329
+ const entries = Object.entries(records || {});
330
+ if (entries.length <= keep)
331
+ return records;
332
+ const ordered = entries.sort((left, right) => recordedAtMs(right[1]) - recordedAtMs(left[1]));
333
+ return Object.fromEntries(ordered.slice(0, keep));
334
+ }
335
+ export function releaseGateCacheRecordLimit() {
336
+ const configured = Number(process.env.SKS_RELEASE_GATE_CACHE_RECORD_LIMIT);
337
+ return Number.isFinite(configured) && configured > 0 ? Math.floor(configured) : 600;
338
+ }
339
+ export function releaseCacheBridgeRecordLimit() {
340
+ const configured = Number(process.env.SKS_RELEASE_CACHE_BRIDGE_RECORD_LIMIT);
341
+ return Number.isFinite(configured) && configured > 0 ? Math.floor(configured) : 600;
342
+ }
343
+ function recordedAtMs(record) {
344
+ const parsed = Date.parse(String(record?.recorded_at || ''));
345
+ return Number.isFinite(parsed) ? parsed : 0;
346
+ }
323
347
  function hashFileIfPresent(hash, file) {
324
348
  if (fs.existsSync(file) && fs.statSync(file).isFile())
325
349
  hash.update(fs.readFileSync(file));
@@ -2,6 +2,8 @@ import crypto from 'node:crypto';
2
2
  export const RELEASE_GATE_CONTRACT_SCHEMA = 'sks.release-gate-contract.v1';
3
3
  export const RELEASE_GATE_CONTRACT_IDS = Object.freeze([
4
4
  'architecture:guard',
5
+ 'architecture-map:contract',
6
+ 'architecture-map:legacy-closure',
5
7
  'codex:current:app-server-v2',
6
8
  'codex:current:binary-identity',
7
9
  'codex:current:capability',
@@ -10,6 +10,7 @@ import { RELEASE_GATE_NODE_SCHEMA, validateReleaseGateManifest } from './release
10
10
  import { countReleaseGateResources, defaultReleaseGateBudget, summarizeReleaseGateBudget } from './release-gate-resource-governor.js';
11
11
  import { selectAffectedReleaseGates } from './release-gate-affected-selector.js';
12
12
  import { computeTriWikiAffectedGraph } from '../triwiki/triwiki-affected-graph.js';
13
+ import { pruneTriWikiProofBank } from '../triwiki/triwiki-proof-bank.js';
13
14
  import { executeExtremeSchedule } from './extreme-parallel-scheduler.js';
14
15
  import { computeResourceClassBudget } from './resource-class-budget.js';
15
16
  import { guardedProcessKill, guardContextForRoute } from '../safety/mutation-guard.js';
@@ -106,6 +107,7 @@ export async function runReleaseGateDag(input) {
106
107
  throw new Error(`release_gate_selection_missing_dependencies:${[...new Set(missingSelectedDeps)].sort().join(',')}`);
107
108
  const runId = `rg-${new Date().toISOString().replace(/[:.]/g, '-')}-${process.pid}`;
108
109
  const retentionBefore = await pruneOldReleaseGateRunDirs(root);
110
+ const proofBankRetention = pruneTriWikiProofBank(root);
109
111
  await sweepSksTempDirs(root, { maxAgeHours: 24 }).catch(() => null);
110
112
  const reportDir = path.join(root, '.sneakoscope', 'reports', 'release-gates', runId);
111
113
  fs.mkdirSync(reportDir, { recursive: true });
@@ -113,6 +115,7 @@ export async function runReleaseGateDag(input) {
113
115
  const affectedGraphFile = path.join(reportDir, 'affected-gate-graph.json');
114
116
  const completionCertificateFile = path.join(reportDir, 'completion-certificate.json');
115
117
  appendReleaseGateJsonl(timeline, { event: 'retention', phase: 'before_run', ...retentionBefore, at: new Date().toISOString() });
118
+ appendReleaseGateJsonl(timeline, { event: 'proof_bank_retention', phase: 'before_run', ...proofBankRetention, at: new Date().toISOString() });
116
119
  const started = Date.now();
117
120
  const slaMs = Math.max(1, Math.floor(Number(input.slaMs || 300000)));
118
121
  const pending = new Map(selected.map((gate) => [gate.id, gate]));
@@ -0,0 +1,69 @@
1
+ import crypto from 'node:crypto';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { SksLruCache } from '../perf/lru-cache.js';
5
+ export const RELEASE_INSPECTION_MEMO_ENV = 'SKS_RELEASE_INSPECTION_MEMO';
6
+ const MAX_ENTRIES_PER_BUCKET = 64;
7
+ const buckets = new Map();
8
+ export function releaseInspectionMemoEnabled() {
9
+ return process.env[RELEASE_INSPECTION_MEMO_ENV] !== '0';
10
+ }
11
+ export function fileIdentity(file) {
12
+ try {
13
+ const stat = fs.lstatSync(file, { bigint: true });
14
+ if (!stat.isFile() || stat.isSymbolicLink())
15
+ return null;
16
+ return `${stat.dev}:${stat.ino}:${stat.size}:${stat.mtimeNs}:${stat.ctimeNs}`;
17
+ }
18
+ catch {
19
+ return null;
20
+ }
21
+ }
22
+ export function toolIdentity(command) {
23
+ const searchPath = String(process.env.PATH || '').split(path.delimiter).filter(Boolean);
24
+ for (const dir of searchPath) {
25
+ const candidate = path.join(dir, command);
26
+ let resolved;
27
+ try {
28
+ resolved = fs.realpathSync(candidate);
29
+ }
30
+ catch {
31
+ continue;
32
+ }
33
+ const identity = fileIdentity(resolved);
34
+ if (identity)
35
+ return `${candidate}>${resolved}#${identity}`;
36
+ }
37
+ return null;
38
+ }
39
+ export function inspectionKey(...parts) {
40
+ if (parts.some((part) => part === null || part === undefined))
41
+ return null;
42
+ return crypto.createHash('sha256').update(parts.map(String).join('\0')).digest('hex');
43
+ }
44
+ export function memoizeReleaseInspection(bucket, key, compute, clone = defaultClone) {
45
+ if (!key || !releaseInspectionMemoEnabled())
46
+ return compute();
47
+ let cache = buckets.get(bucket);
48
+ if (!cache) {
49
+ cache = new SksLruCache(MAX_ENTRIES_PER_BUCKET);
50
+ buckets.set(bucket, cache);
51
+ }
52
+ const hit = cache.get(key);
53
+ if (hit !== null)
54
+ return clone(hit);
55
+ const value = compute();
56
+ cache.set(key, value);
57
+ return clone(value);
58
+ }
59
+ function defaultClone(value) {
60
+ if (value === null || typeof value !== 'object')
61
+ return value;
62
+ return structuredClone(value);
63
+ }
64
+ export function cloneBuffer(value) {
65
+ return Buffer.from(value);
66
+ }
67
+ export function clearReleaseInspectionMemo() {
68
+ buckets.clear();
69
+ }
@@ -3,7 +3,12 @@ import fs from 'node:fs';
3
3
  import os from 'node:os';
4
4
  import path from 'node:path';
5
5
  import { spawnSync } from 'node:child_process';
6
+ import { fileIdentity, inspectionKey, memoizeReleaseInspection, toolIdentity } from './release-inspection-memo.js';
6
7
  export function scanTarballTextContents(input) {
8
+ const key = inspectionKey(input.temp_prefix, input.tarball, fileIdentity(input.tarball), toolIdentity('tar'), String(input.max_findings), input.include_allowlisted_count === true ? 'allowlisted' : 'plain', input.patterns.map((pattern) => `${pattern.kind}=${pattern.regex.source}/${pattern.regex.flags}`).join('\0'), String(input.allow_finding || 'no_allowlist'), String(input.finding_blocker));
9
+ return memoizeReleaseInspection('tarball-content-scan', key, () => computeTarballTextContents(input));
10
+ }
11
+ function computeTarballTextContents(input) {
7
12
  const temp = fs.mkdtempSync(path.join(os.tmpdir(), input.temp_prefix));
8
13
  const findings = [];
9
14
  const blockers = [];
@@ -6,6 +6,7 @@ import { spawnSync } from 'node:child_process';
6
6
  import { tmpdir } from '../fsx.js';
7
7
  import { DEFAULT_MAX_PACK_BYTES, DEFAULT_MAX_UNPACKED_BYTES } from './package-size-budget.js';
8
8
  import { readCurrentNpmPackGateArtifacts } from './npm-pack-proof.js';
9
+ import { inspectionKey, memoizeReleaseInspection, toolIdentity } from './release-inspection-memo.js';
9
10
  import { scanTarballTextContents } from './release-pack-content-scanner.js';
10
11
  import { RELEASE_PACK_COMPARE_SCHEMA, RELEASE_PACK_RECEIPT_SCHEMA } from './release-pack-contract.js';
11
12
  import { tarInventory, tarPackageJson, tarUnpackedBytes } from './release-pack-tarball.js';
@@ -298,12 +299,32 @@ export function inspectLocalReleaseSourceState(root) {
298
299
  else if (String(fullStatus.stdout || '').split(/\r?\n/).some((line) => line.startsWith('?? '))) {
299
300
  blockers.push('npm_pack_untracked_files_present');
300
301
  }
302
+ const inventory = memoizeReleaseInspection('npm-pack-source-inventory', inspectionKey(path.resolve(root), head, toolIdentity('git'), toolIdentity('npm'), process.env.SKS_RELEASE_NPM_CACHE || '', String(trackedStatus.status), String(trackedStatus.stdout || ''), String(fullStatus.status), String(fullStatus.stdout || '')), () => packSourceInventory(root));
303
+ const trackedFiles = inventory.tracked_files ? new Set(inventory.tracked_files) : null;
304
+ if (!trackedFiles)
305
+ blockers.push('npm_pack_tracked_file_inventory_unavailable');
306
+ const packedFiles = inventory.packed_files;
307
+ if (!packedFiles)
308
+ blockers.push('npm_pack_dry_run_file_inventory_unavailable');
309
+ const packEligibleUntracked = trackedFiles && packedFiles
310
+ ? unique(packedFiles.filter((relative) => !trackedFiles.has(relative) && !isGeneratedDistPackagePath(relative))).sort()
311
+ : [];
312
+ if (packEligibleUntracked.length > 0)
313
+ blockers.push('npm_pack_eligible_untracked_files_present');
314
+ return {
315
+ ok: blockers.length === 0,
316
+ head,
317
+ source_tree_sha256: sourceTreeSha256,
318
+ tracked_changes: trackedChanges,
319
+ pack_eligible_untracked: packEligibleUntracked,
320
+ blockers: unique(blockers)
321
+ };
322
+ }
323
+ function packSourceInventory(root) {
301
324
  const trackedFilesResult = spawnSync('git', ['ls-files', '-z'], { cwd: root, encoding: 'utf8', maxBuffer: 32 * 1024 * 1024 });
302
325
  const trackedFiles = trackedFilesResult.status === 0
303
- ? new Set(String(trackedFilesResult.stdout || '').split('\0').filter(Boolean).map(normalizePath))
326
+ ? unique(String(trackedFilesResult.stdout || '').split('\0').filter(Boolean).map(normalizePath))
304
327
  : null;
305
- if (!trackedFiles)
306
- blockers.push('npm_pack_tracked_file_inventory_unavailable');
307
328
  const dryRun = spawnSync('npm', ['pack', '--dry-run', '--ignore-scripts', '--json'], {
308
329
  cwd: root,
309
330
  encoding: 'utf8',
@@ -323,21 +344,7 @@ export function inspectLocalReleaseSourceState(root) {
323
344
  packedFiles = null;
324
345
  }
325
346
  }
326
- if (!packedFiles)
327
- blockers.push('npm_pack_dry_run_file_inventory_unavailable');
328
- const packEligibleUntracked = trackedFiles && packedFiles
329
- ? unique(packedFiles.filter((relative) => !trackedFiles.has(relative) && !isGeneratedDistPackagePath(relative))).sort()
330
- : [];
331
- if (packEligibleUntracked.length > 0)
332
- blockers.push('npm_pack_eligible_untracked_files_present');
333
- return {
334
- ok: blockers.length === 0,
335
- head,
336
- source_tree_sha256: sourceTreeSha256,
337
- tracked_changes: trackedChanges,
338
- pack_eligible_untracked: packEligibleUntracked,
339
- blockers: unique(blockers)
340
- };
347
+ return { tracked_files: trackedFiles, packed_files: packedFiles };
341
348
  }
342
349
  export function writeReleaseJson(file, value) {
343
350
  fs.mkdirSync(path.dirname(file), { recursive: true });
@@ -536,6 +543,9 @@ function gitHead(root) {
536
543
  return result.status === 0 ? String(result.stdout || '').trim() : '';
537
544
  }
538
545
  function gitTreeSha256(root, commit) {
546
+ return memoizeReleaseInspection('git-tree-sha256', inspectionKey(path.resolve(root), commit, toolIdentity('git')), () => computeGitTreeSha256(root, commit));
547
+ }
548
+ function computeGitTreeSha256(root, commit) {
539
549
  if (!/^[a-f0-9]{40}$/i.test(commit))
540
550
  return '';
541
551
  const verified = spawnSync('git', ['rev-parse', '--verify', `${commit}^{commit}`], { cwd: root, encoding: 'utf8' });
@@ -2,7 +2,11 @@ import fs from 'node:fs';
2
2
  import os from 'node:os';
3
3
  import path from 'node:path';
4
4
  import { spawnSync } from 'node:child_process';
5
+ import { fileIdentity, inspectionKey, memoizeReleaseInspection, toolIdentity } from './release-inspection-memo.js';
5
6
  export function tarInventory(tarball) {
7
+ return memoizeReleaseInspection('tar-inventory', inspectionKey(tarball, fileIdentity(tarball), toolIdentity('tar')), () => computeTarInventory(tarball));
8
+ }
9
+ function computeTarInventory(tarball) {
6
10
  const result = spawnSync('tar', ['-tzf', tarball], { encoding: 'utf8', maxBuffer: 32 * 1024 * 1024 });
7
11
  if (result.status !== 0)
8
12
  return { files: [], blockers: ['tarball_inventory_command_failed'] };
@@ -28,6 +32,9 @@ export function tarInventory(tarball) {
28
32
  return { files, blockers };
29
33
  }
30
34
  export function tarPackageJson(tarball) {
35
+ return memoizeReleaseInspection('tar-package-json', inspectionKey(tarball, fileIdentity(tarball), toolIdentity('tar')), () => computeTarPackageJson(tarball));
36
+ }
37
+ function computeTarPackageJson(tarball) {
31
38
  const result = spawnSync('tar', ['-xOzf', tarball, 'package/package.json'], { encoding: 'utf8', maxBuffer: 1024 * 1024 });
32
39
  if (result.status !== 0)
33
40
  return null;
@@ -40,6 +47,9 @@ export function tarPackageJson(tarball) {
40
47
  }
41
48
  }
42
49
  export function tarUnpackedBytes(tarball) {
50
+ return memoizeReleaseInspection('tar-unpacked-bytes', inspectionKey(tarball, fileIdentity(tarball), toolIdentity('tar')), () => computeTarUnpackedBytes(tarball));
51
+ }
52
+ function computeTarUnpackedBytes(tarball) {
43
53
  const temp = fs.mkdtempSync(path.join(os.tmpdir(), 'sks-release-pack-unpacked-'));
44
54
  try {
45
55
  const result = spawnSync('tar', ['-xzf', tarball, '-C', temp], { encoding: 'utf8', maxBuffer: 32 * 1024 * 1024 });
@@ -4,6 +4,10 @@ export const RELEASE_UPGRADE_BASELINE_SHA256 = '40a9e89f3a234dfcd32507ab7deeb950
4
4
  export function releaseUpgradeProofFilename(targetVersion) {
5
5
  return `upgrade-${RELEASE_UPGRADE_BASELINE_LABEL}-to-${targetVersion}.json`;
6
6
  }
7
+ export function isExpectedReleaseUpgradeLaunchctlCall(call) {
8
+ return /^(?:unsetenv (?:CODEX_LB_API_KEY|OPENROUTER_API_KEY)|print|bootout (?:com\.sneakoscope\.sks-menubar|com\.sneakoscope\.telegram-hub))$/
9
+ .test(String(call));
10
+ }
7
11
  export function releaseUpgradeExpectedVersion(stateName, targetVersion) {
8
12
  return stateName.startsWith('target_') ? targetVersion : RELEASE_UPGRADE_BASELINE_VERSION;
9
13
  }
@@ -647,7 +647,7 @@ export const COMMAND_CATALOG = [
647
647
  { name: 'check', usage: 'sks check --tier instant|affected|confidence|release|real-check [--sla 5m] [--changed-since auto] [--json]', description: 'Run build-once proof-bank checks: affected/confidence use incremental build and cached proof reuse; release keeps full clean proof for publish readiness.' },
648
648
  { name: 'task', usage: 'sks task run [--sla 5m] [--json]', description: 'Run the normal affected-scope, release-equivalent task verification path.' },
649
649
  { name: 'release', usage: 'sks release affected|full|background|stage [--json]', description: 'Run affected release proof, full release proof, or background release proof explicitly, or drive the staged npm publish up to the human approval step.' },
650
- { name: 'triwiki', usage: 'sks triwiki index|affected|proof-bank [--json]', description: 'Inspect TriWiki module cards, gate impact maps, affected graphs, and proof bank status.' },
650
+ { name: 'triwiki', usage: 'sks triwiki index|affected|proof-bank|graph-status|graph-lint|graph-query|atlas-status|atlas-lint|atlas-list|atlas-show|atlas-why [--json]', description: 'Inspect TriWiki module cards, gate impact maps, affected graphs, proof bank status, and Architecture Map views.' },
651
651
  { name: 'daemon', usage: 'sks daemon status|warm|stop [--json]', description: 'Inspect or warm the local SKS daemon cache state for build/proof reuse.' },
652
652
  { name: 'run', usage: 'sks run "task" [--visual|--research|--db] [--json]', description: 'Classify a plain-language task, materialize a mission, and route it through the SKS trust kernel.' },
653
653
  { name: 'plan', usage: 'sks plan "task" [--json]', description: 'Write a planning-only artifact under .sneakoscope/plans without editing code.' },