sneakoscope 8.3.2 → 8.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/config/architecture-map-policy.v1.json +168 -0
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/dist/commands/bridge.js +19 -1
- package/dist/commands/doctor-profile.js +1 -1
- package/dist/commands/doctor.js +97 -16
- package/dist/config/skills-manifest.json +1 -1
- package/dist/core/align/align-route.js +8 -2
- package/dist/core/align/code-navigation-align.js +88 -32
- package/dist/core/architecture-map-pipeline.js +173 -0
- package/dist/core/architecture-map-review.js +180 -0
- package/dist/core/codex/agent-config-file-repair.js +17 -3
- package/dist/core/codex/codex-config-guard.js +3 -1
- package/dist/core/codex/codex-feature-flags.js +10 -0
- package/dist/core/codex/codex-project-config-policy.js +54 -30
- package/dist/core/codex/codex-startup-config-postcheck.js +5 -0
- package/dist/core/codex-app/codex-app-fast-ui-repair.js +2 -1
- package/dist/core/codex-app/menubar/constants.js +1 -0
- package/dist/core/codex-hooks/codex-hook-state-writer.js +2 -0
- package/dist/core/codex-lb/codex-lb-env.js +25 -3
- package/dist/core/codex-lb/combined-catalog/builder.js +7 -2
- package/dist/core/codex-lb/combined-catalog/model-selection.js +71 -0
- package/dist/core/codex-lb/combined-catalog/normalize.js +20 -3
- package/dist/core/codex-lb/desktop-bridge/security.js +11 -8
- package/dist/core/codex-lb/desktop-controller-v3/catalog.js +75 -4
- package/dist/core/codex-lb/desktop-controller-v3.js +5 -1
- package/dist/core/codex-lb/request-route-resolver.js +21 -18
- package/dist/core/commands/triwiki-atlas-command.js +372 -0
- package/dist/core/commands/triwiki-command.js +12 -1
- package/dist/core/doctor/doctor-dirty-planner.js +16 -1
- package/dist/core/doctor/doctor-idempotence.js +11 -0
- package/dist/core/doctor/doctor-repair-postcheck.js +49 -0
- package/dist/core/doctor/doctor-transaction.js +47 -2
- package/dist/core/doctor/legacy-global-hook-cleanup.js +1 -1
- package/dist/core/fsx.js +14 -0
- package/dist/core/git-hygiene/git-policy.js +4 -0
- package/dist/core/git-hygiene/gitignore-writer.js +2 -0
- package/dist/core/gx-renderer.js +2 -1
- package/dist/core/init.js +40 -17
- package/dist/core/managed-paths.js +1 -0
- package/dist/core/perf/release-latency-slo.js +57 -11
- package/dist/core/pipeline-internals/pipeline-stage-builder.js +181 -0
- package/dist/core/pipeline-internals/runtime-core.js +18 -172
- package/dist/core/pipeline-internals/runtime-gates.js +12 -1
- package/dist/core/release/content-digest-cache.js +70 -0
- package/dist/core/release/gate-manifest.js +9 -0
- package/dist/core/release/main-push-guard.js +4 -3
- package/dist/core/release/package-dist-snapshot.js +18 -2
- package/dist/core/release/physical-release-gates.js +26 -3
- package/dist/core/release/release-authorization-snapshot.js +4 -0
- package/dist/core/release/release-gate-cache-v2.js +27 -3
- package/dist/core/release/release-gate-contract.js +2 -0
- package/dist/core/release/release-gate-dag.js +3 -0
- package/dist/core/release/release-inspection-memo.js +69 -0
- package/dist/core/release/release-pack-content-scanner.js +5 -0
- package/dist/core/release/release-pack-receipt.js +28 -18
- package/dist/core/release/release-pack-tarball.js +10 -0
- package/dist/core/release/release-upgrade-baseline.js +4 -0
- package/dist/core/routes.js +1 -1
- package/dist/core/safety/ssot-authority-inventory.js +114 -0
- package/dist/core/safety/ssot-guard.js +2 -44
- package/dist/core/subagents/agent-catalog.js +4 -2
- package/dist/core/subagents/official-subagent-config.js +92 -15
- package/dist/core/subagents/official-subagent-preparation.js +1 -0
- package/dist/core/triwiki/context-graph/architecture/baseline.js +81 -0
- package/dist/core/triwiki/context-graph/architecture/capsule.js +34 -0
- package/dist/core/triwiki/context-graph/architecture/contracts.js +67 -0
- package/dist/core/triwiki/context-graph/architecture/delta.js +149 -0
- package/dist/core/triwiki/context-graph/architecture/finding-factory.js +28 -0
- package/dist/core/triwiki/context-graph/architecture/findings.js +173 -0
- package/dist/core/triwiki/context-graph/architecture/fingerprint.js +75 -0
- package/dist/core/triwiki/context-graph/architecture/index.js +15 -0
- package/dist/core/triwiki/context-graph/architecture/input-bundle.js +66 -0
- package/dist/core/triwiki/context-graph/architecture/metrics.js +137 -0
- package/dist/core/triwiki/context-graph/architecture/policy.js +321 -0
- package/dist/core/triwiki/context-graph/architecture/slice.js +100 -0
- package/dist/core/triwiki/context-graph/architecture/ssot-analysis.js +59 -0
- package/dist/core/triwiki/context-graph/architecture/topology-overlay.js +106 -0
- package/dist/core/triwiki/context-graph/architecture/validation.js +145 -0
- package/dist/core/triwiki/context-graph/architecture/voxel-context.js +39 -0
- package/dist/core/triwiki/context-graph/extractors/index.js +4 -0
- package/dist/core/triwiki/context-graph/projections/index.js +2 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/architecture-delta.js +68 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/ast.js +49 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/change-impact.js +43 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/contracts.js +5 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/escape.js +45 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/ids.js +73 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/index.js +113 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/manifest.js +37 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/module-dependency.js +21 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/ownership-workstream.js +21 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/project-topology.js +13 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/public-surface.js +17 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/reduce.js +44 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/risk-domains.js +23 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/runtime-control.js +20 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/selection-ledger.js +94 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/serializer.js +81 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/ssot-provenance.js +17 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/verification-coverage.js +17 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/view-builder-common.js +1 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/view-builder.js +173 -0
- package/dist/core/triwiki/context-graph/projections/mermaid/view-specs.js +33 -0
- package/dist/core/triwiki/context-graph/store/architecture-map-store.js +71 -0
- package/dist/core/triwiki/triwiki-proof-bank-index.js +26 -1
- package/dist/core/triwiki/triwiki-proof-bank.js +82 -2
- package/dist/core/version.js +1 -1
- package/dist/native/sks-menubar/Sources/OverviewViewController.swift +2 -2
- package/dist/native/sks-menubar/Sources/ProvidersModelExposure.swift +178 -0
- package/dist/native/sks-menubar/Sources/ProvidersViewController.swift +10 -1
- package/dist/scripts/architecture-map-check.js +299 -0
- package/dist/scripts/lib/ensure-dist-fresh.js +48 -1
- package/dist/scripts/release-metadata-check.js +2 -0
- package/package.json +6 -3
- package/release-gates.v2.json +204 -0
- package/schemas/architecture-baseline.v1.schema.json +47 -0
- package/schemas/architecture-map-manifest.v1.schema.json +40 -0
- package/schemas/architecture-map-policy.v1.schema.json +147 -0
- package/schemas/architecture-review.v1.schema.json +46 -0
- package/schemas/mermaid-projection.v1.schema.json +47 -0
|
@@ -38,7 +38,10 @@ import { NARUTO_GATE_FILENAME, NARUTO_SUMMARY_FILENAME, SUBAGENT_PLAN_FILENAME,
|
|
|
38
38
|
import { buildSubagentEvidence, readSubagentEvents, recordSubagentEvent, SUBAGENT_EVIDENCE_FILENAME, SUBAGENT_EVENT_LOG_FILENAME, SUBAGENT_PARENT_SUMMARY_FILENAME } from '../subagents/subagent-evidence.js';
|
|
39
39
|
import { effectiveSubagentTarget, normalizeLegacySubagentCountFields, subagentCountContractBlockers } from '../subagents/wave-lifecycle.js';
|
|
40
40
|
import { officialSubagentEvidenceReady } from '../subagents/terminal-subagent-state.js';
|
|
41
|
+
import { createArchitectureMapPlanBinding, maybeSeedArchitectureMapForPlan, planStagesArchitectureMap } from '../architecture-map-pipeline.js';
|
|
42
|
+
import { BLOCKING_GATE_LIMITS, LIGHTWEIGHT_ROUTES, GATE_PROFILE_STAGES, STAGE_BLOCKING_GATE, selectPipelineLane, buildPipelineStages, planVerification, pipelineInvariants } from './pipeline-stage-builder.js';
|
|
41
43
|
export { routePrompt };
|
|
44
|
+
export { LIGHTWEIGHT_ROUTES, GATE_PROFILE_STAGES, STAGE_BLOCKING_GATE, BLOCKING_GATE_LIMITS };
|
|
42
45
|
export const PIPELINE_PLAN_ARTIFACT = 'pipeline-plan.json';
|
|
43
46
|
export const PIPELINE_PLAN_SCHEMA_VERSION = 1;
|
|
44
47
|
const SKILL_DREAM_HOT_PATH_COUNTERS = new Map();
|
|
@@ -60,44 +63,6 @@ const HARD_BLOCKER_ARTIFACT = 'hard-blocker.json';
|
|
|
60
63
|
const DEFAULT_COMPLIANCE_LOOP_LIMIT = 3;
|
|
61
64
|
const CLARIFICATION_BYPASS_ROUTES = new Set(['Answer', 'DFix', 'Help', 'Wiki', 'ComputerUse', 'Goal']);
|
|
62
65
|
const QUESTION_GATE_ROUTES = new Set(['QALoop', 'PPT']);
|
|
63
|
-
const LIGHTWEIGHT_ROUTES = new Set(['Answer', 'DFix', 'Help', 'Wiki', 'Goal']);
|
|
64
|
-
const GATE_PROFILE_STAGES = Object.freeze({
|
|
65
|
-
none: [],
|
|
66
|
-
minimal: ['route_classification', 'listed_verification'],
|
|
67
|
-
scoped: ['route_classification', 'ownership', 'listed_verification', 'honest_summary'],
|
|
68
|
-
full: ['route_classification', 'ambiguity_gate', 'safety_gate', 'ownership', 'listed_verification', 'honest_summary']
|
|
69
|
-
});
|
|
70
|
-
const STAGE_BLOCKING_GATE = Object.freeze({
|
|
71
|
-
ambiguity_gate: 'scope',
|
|
72
|
-
safety_gate: 'safety',
|
|
73
|
-
ssot_guard: 'safety',
|
|
74
|
-
context7_evidence: 'safety',
|
|
75
|
-
mistake_recall: 'safety',
|
|
76
|
-
ownership: 'ownership',
|
|
77
|
-
pipeline_plan: 'ownership',
|
|
78
|
-
focused_implementation: 'ownership',
|
|
79
|
-
triwiki_use_first: 'ownership',
|
|
80
|
-
subagent_plan: 'ownership',
|
|
81
|
-
official_subagent_execution: 'ownership',
|
|
82
|
-
parent_integration: 'ownership',
|
|
83
|
-
route_materialization: 'ownership',
|
|
84
|
-
work_order_coverage: 'ownership',
|
|
85
|
-
engineering_sanity_check: 'verification',
|
|
86
|
-
listed_verification: 'verification',
|
|
87
|
-
triwiki_validate_before_final: 'verification',
|
|
88
|
-
completion_proof: 'verification',
|
|
89
|
-
reflection: 'verification',
|
|
90
|
-
honest_summary: 'verification'
|
|
91
|
-
});
|
|
92
|
-
const BLOCKING_GATE_LIMITS = Object.freeze({
|
|
93
|
-
passthrough: 0,
|
|
94
|
-
answer: 0,
|
|
95
|
-
'tiny-change': 1,
|
|
96
|
-
'bounded-work': 2,
|
|
97
|
-
'parallel-read': 2,
|
|
98
|
-
'parallel-write': 3,
|
|
99
|
-
'high-risk': 4
|
|
100
|
-
});
|
|
101
66
|
function reflectionInstructionText(commandPrefix = 'sks') {
|
|
102
67
|
return `Post-route reflection: full routes load \`reflection\` after work/tests and before final; DFix/Answer/Help/Wiki/SKS discovery are exempt. Write ${REFLECTION_ARTIFACT}; record only real misses/gaps, or no_issue_acknowledged. For lessons, append TriWiki claim rows to ${REFLECTION_MEMORY_PATH}. Run "${commandPrefix} wiki refresh" or pack, validate, then pass ${REFLECTION_GATE}.`;
|
|
103
68
|
}
|
|
@@ -182,6 +147,8 @@ export function buildPipelinePlan(input = {}) {
|
|
|
182
147
|
verification_budget: verificationBudget,
|
|
183
148
|
verification,
|
|
184
149
|
engineering_sanity_review: null,
|
|
150
|
+
architecture_map: null,
|
|
151
|
+
architecture_map_unavailable: null,
|
|
185
152
|
invariants,
|
|
186
153
|
proof_field: proof,
|
|
187
154
|
ssot_guard: buildSsotGuard({ route: route?.id || 'SKS', mode: route?.mode || 'SKS', task }),
|
|
@@ -219,6 +186,16 @@ export async function writePipelinePlan(dir, input = {}) {
|
|
|
219
186
|
changed_scope_base: resolveChangedScopeBase(root)
|
|
220
187
|
};
|
|
221
188
|
}
|
|
189
|
+
if (planStagesArchitectureMap(plan)) {
|
|
190
|
+
plan.architecture_map = createArchitectureMapPlanBinding();
|
|
191
|
+
const seeded = await maybeSeedArchitectureMapForPlan({
|
|
192
|
+
root, dir, plan, missionId: input.missionId, taskProfile
|
|
193
|
+
});
|
|
194
|
+
if (seeded && seeded.ok === false) {
|
|
195
|
+
plan.architecture_map = null;
|
|
196
|
+
plan.architecture_map_unavailable = { reason: seeded.reason || 'architecture_map_baseline_unavailable' };
|
|
197
|
+
}
|
|
198
|
+
}
|
|
222
199
|
await writeJsonAtomic(path.join(dir, PIPELINE_PLAN_ARTIFACT), plan);
|
|
223
200
|
if (plan.engineering_sanity_review) {
|
|
224
201
|
const report = await writeCodeStructureReport(root, dir, {
|
|
@@ -244,7 +221,8 @@ export function pipelinePlanState(plan) {
|
|
|
244
221
|
pipeline_plan_ready: validatePipelinePlan(plan).ok,
|
|
245
222
|
pipeline_plan_path: PIPELINE_PLAN_ARTIFACT,
|
|
246
223
|
engineering_sanity_required: Boolean(sanity),
|
|
247
|
-
engineering_sanity_scope_base: sanity?.changed_scope_base || null
|
|
224
|
+
engineering_sanity_scope_base: sanity?.changed_scope_base || null,
|
|
225
|
+
architecture_map_required: Boolean(plan?.architecture_map)
|
|
248
226
|
};
|
|
249
227
|
}
|
|
250
228
|
export async function writeRequestIntakeArtifact(dir, input = {}) {
|
|
@@ -488,130 +466,6 @@ function routeEconomyPlan(proof = {}) {
|
|
|
488
466
|
deletion_policy: 'do_not_delete_or_skip_pipeline_stages_until_report_only_metrics_are_calibrated'
|
|
489
467
|
};
|
|
490
468
|
}
|
|
491
|
-
function selectPipelineLane(route, task, proof, taskProfile = classifyTaskProfile(task)) {
|
|
492
|
-
if (proof.attached && proof.lane) {
|
|
493
|
-
return {
|
|
494
|
-
lane: proof.lane,
|
|
495
|
-
source: 'proof_field',
|
|
496
|
-
fast_lane_allowed: Boolean(proof.fast_lane_allowed),
|
|
497
|
-
reason: proof.fast_lane_allowed ? 'Proof Field allowed the fast lane.' : `Proof Field selected ${proof.lane}.`,
|
|
498
|
-
blockers: proof.blockers || [],
|
|
499
|
-
skip_when_fast: proof.fast_lane_allowed ? SPEED_LANE_POLICY.skip_when_fast : [],
|
|
500
|
-
keep: proof.keep || SPEED_LANE_POLICY.always_keep
|
|
501
|
-
};
|
|
502
|
-
}
|
|
503
|
-
if (taskProfile === 'passthrough' || taskProfile === 'answer')
|
|
504
|
-
return { lane: 'no_pipeline', source: 'task_profile', fast_lane_allowed: true, reason: 'Light conversation does not create an execution pipeline.', blockers: [], skip_when_fast: [], keep: [] };
|
|
505
|
-
if (route?.id === 'ComputerUse')
|
|
506
|
-
return { lane: 'computer_use_fast_lane', source: 'route_policy', fast_lane_allowed: true, reason: 'Computer Use route is intentionally direct and defers wiki/honest checks to closeout.', blockers: [], skip_when_fast: ['planning_debate'], keep: ['focused_implementation', 'triwiki_validate_before_final', 'honest_mode'] };
|
|
507
|
-
if (taskProfile === 'tiny-change')
|
|
508
|
-
return { lane: 'minimal_change_lane', source: 'task_profile', fast_lane_allowed: true, reason: 'Tiny change uses one blocking gate and one focused check at most.', blockers: [], skip_when_fast: SPEED_LANE_POLICY.skip_when_fast, keep: ['listed_verification'] };
|
|
509
|
-
if (LIGHTWEIGHT_ROUTES.has(route?.id))
|
|
510
|
-
return { lane: `${String(route.id).toLowerCase()}_lightweight_lane`, source: 'route_policy', fast_lane_allowed: true, reason: 'Lightweight route bypasses full mission orchestration by design.', blockers: [], skip_when_fast: SPEED_LANE_POLICY.skip_when_fast, keep: ['focused_implementation', 'listed_verification', 'honest_mode'] };
|
|
511
|
-
if (routeRequiresSubagents(route, task, taskProfile))
|
|
512
|
-
return { lane: 'official_subagent_lane', source: 'task_profile', fast_lane_allowed: false, reason: 'Explicit Naruto or parallel work uses the Codex subagent workflow.', blockers: [], skip_when_fast: [], keep: ['subagent_plan', 'official_subagent_execution', 'parent_integration', 'listed_verification', 'honest_summary'] };
|
|
513
|
-
if (taskProfile === 'high-risk')
|
|
514
|
-
return { lane: SPEED_LANE_POLICY.full_lane, source: 'task_profile', fast_lane_allowed: false, reason: 'High-risk work uses the full risk gate profile.', blockers: [], skip_when_fast: [], keep: SPEED_LANE_POLICY.always_keep };
|
|
515
|
-
return { lane: SPEED_LANE_POLICY.balanced_lane, source: 'route_policy', fast_lane_allowed: false, reason: 'Balanced parent-owned route until Proof Field proves a narrower lane.', blockers: ['proof_field_not_attached'], skip_when_fast: [], keep: SPEED_LANE_POLICY.always_keep };
|
|
516
|
-
}
|
|
517
|
-
function buildPipelineStages(route, task, taskProfile, gateProfile, ambiguity, lane, context7Required, officialSubagentPolicy = normalizeOfficialSubagentPolicy(route, task, {})) {
|
|
518
|
-
if (gateProfile === 'none')
|
|
519
|
-
return [];
|
|
520
|
-
const ids = [...GATE_PROFILE_STAGES[gateProfile]];
|
|
521
|
-
const specializedRoute = Boolean(route?.id && !LIGHTWEIGHT_ROUTES.has(route.id) && route.id !== 'SKS');
|
|
522
|
-
if (gateProfile === 'scoped' || gateProfile === 'full' || specializedRoute)
|
|
523
|
-
ids.push('pipeline_plan', 'focused_implementation');
|
|
524
|
-
if ((gateProfile === 'full' || specializedRoute) && !ids.includes('ssot_guard'))
|
|
525
|
-
ids.push('ssot_guard');
|
|
526
|
-
if (context7Required)
|
|
527
|
-
ids.push('context7_evidence');
|
|
528
|
-
if ((gateProfile === 'scoped' || gateProfile === 'full') && !LIGHTWEIGHT_ROUTES.has(route?.id)) {
|
|
529
|
-
ids.push('triwiki_use_first', 'triwiki_validate_before_final', 'mistake_recall', 'work_order_coverage');
|
|
530
|
-
}
|
|
531
|
-
if (routeNeedsEngineeringSanityReview(route, task))
|
|
532
|
-
ids.push('engineering_sanity_check');
|
|
533
|
-
if (routeRequiresSubagents(route, task, taskProfile))
|
|
534
|
-
ids.push('subagent_plan', 'official_subagent_execution', 'parent_integration');
|
|
535
|
-
if (specializedRoute)
|
|
536
|
-
ids.push('route_materialization');
|
|
537
|
-
if (specializedRoute)
|
|
538
|
-
ids.push('completion_proof');
|
|
539
|
-
if (reflectionRequiredForRoute(route))
|
|
540
|
-
ids.push('reflection');
|
|
541
|
-
return [...new Set(ids)].map((id) => {
|
|
542
|
-
const configuredGate = STAGE_BLOCKING_GATE[id] || null;
|
|
543
|
-
const blockingGate = configuredGate === 'safety' && gateProfile !== 'full'
|
|
544
|
-
? 'ownership'
|
|
545
|
-
: configuredGate;
|
|
546
|
-
const blocking = Boolean(blockingGate);
|
|
547
|
-
const metadata = { blocking, blocking_gate: blocking ? blockingGate : null };
|
|
548
|
-
if (id === OFFICIAL_SUBAGENT_EXECUTION_STAGE_ID)
|
|
549
|
-
return { ...officialSubagentPipelineStage(officialSubagentPolicy), status: 'required', reason: officialSubagentPolicy.reason, ...metadata };
|
|
550
|
-
if (id === 'engineering_sanity_check') {
|
|
551
|
-
return {
|
|
552
|
-
id,
|
|
553
|
-
status: 'keep',
|
|
554
|
-
reason: 'required_for_code_and_database_quality',
|
|
555
|
-
checks: [
|
|
556
|
-
'trace actual callers and preserve basic SOLID responsibility and dependency boundaries',
|
|
557
|
-
'inspect loops, collections, resolvers, and serializers for N+1 or repeated I/O',
|
|
558
|
-
'prove render, recursion, event, retry, and polling loops are bounded and cancellable',
|
|
559
|
-
'reject disabled checks, swallowed errors, placeholder success, and verification bypasses',
|
|
560
|
-
'for DB work, verify the existing canonical adapter and connection/pool lifecycle before changes',
|
|
561
|
-
'for sensitive or multi-step DB work, verify transaction rollback, error propagation, idempotency, and post-commit invariants'
|
|
562
|
-
],
|
|
563
|
-
...metadata
|
|
564
|
-
};
|
|
565
|
-
}
|
|
566
|
-
if (id === 'ambiguity_gate' && ambiguity?.required === false)
|
|
567
|
-
return { id, status: 'not_applicable', reason: 'ambiguity_gate_not_required_for_entrypoint', ...metadata };
|
|
568
|
-
if (id === 'ambiguity_gate' && ambiguity?.passed)
|
|
569
|
-
return { id, status: 'passed', reason: 'ambiguity_contract_already_sealed', ...metadata };
|
|
570
|
-
return { id, status: 'keep', reason: lane.fast_lane_allowed ? 'task_profile_minimal_lane' : 'required_by_task_and_route_profile', ...metadata };
|
|
571
|
-
});
|
|
572
|
-
}
|
|
573
|
-
function planVerification(route, task, proof, budget) {
|
|
574
|
-
if (budget === 'none')
|
|
575
|
-
return [];
|
|
576
|
-
const out = new Set(proof.verification || []);
|
|
577
|
-
if (budget === 'single-check')
|
|
578
|
-
out.add('run one focused check for the changed surface');
|
|
579
|
-
if (budget === 'affected')
|
|
580
|
-
out.add('run affected tests or checks for changed files');
|
|
581
|
-
if (budget === 'confidence') {
|
|
582
|
-
out.add('run the focused build or typecheck for the affected package');
|
|
583
|
-
out.add('run affected regression tests and the risk-specific safety check');
|
|
584
|
-
}
|
|
585
|
-
if (budget === 'release') {
|
|
586
|
-
out.add('npm run packcheck');
|
|
587
|
-
out.add('sks selftest --mock --json');
|
|
588
|
-
}
|
|
589
|
-
if (route?.id === 'Naruto')
|
|
590
|
-
out.add('validate official subagent evidence and the parent integration summary');
|
|
591
|
-
if (routeNeedsEngineeringSanityReview(route, task)) {
|
|
592
|
-
out.add('complete the engineering sanity review for SOLID boundaries, N+1/repeated I/O, bounded loops, and verification bypasses');
|
|
593
|
-
}
|
|
594
|
-
if (route?.id === 'DB' || route?.id === 'MadSKS') {
|
|
595
|
-
out.add('verify existing canonical DB access, pool lifecycle, and transaction integrity for sensitive or multi-step mutations');
|
|
596
|
-
}
|
|
597
|
-
if (reflectionRequiredForRoute(route))
|
|
598
|
-
out.add('sks wiki validate .sneakoscope/wiki/context-pack.json');
|
|
599
|
-
return [...out];
|
|
600
|
-
}
|
|
601
|
-
function pipelineInvariants(input) {
|
|
602
|
-
const out = ['no_unrequested_fallback_code'];
|
|
603
|
-
if (input.stages.some((stage) => stage.id === 'engineering_sanity_check'))
|
|
604
|
-
out.push('engineering_sanity_check');
|
|
605
|
-
if (input.verificationBudget !== 'none')
|
|
606
|
-
out.push('listed_verification');
|
|
607
|
-
if (input.stages.some((stage) => stage.id === 'ssot_guard' && stage.status !== 'not_applicable'))
|
|
608
|
-
out.push('ssot_guard');
|
|
609
|
-
if (input.stages.some((stage) => stage.id === 'triwiki_validate_before_final'))
|
|
610
|
-
out.push('triwiki_validate_before_final');
|
|
611
|
-
if (input.gateProfile !== 'none')
|
|
612
|
-
out.push('honest_summary');
|
|
613
|
-
return out;
|
|
614
|
-
}
|
|
615
469
|
function planNextActions(route, task, taskProfile, ambiguity, lane) {
|
|
616
470
|
if (taskProfile === 'passthrough' || taskProfile === 'answer')
|
|
617
471
|
return [];
|
|
@@ -1593,15 +1447,7 @@ function strictNarutoIntegerOption(text, option) {
|
|
|
1593
1447
|
function routeState(id, route, phase, context7Required, extra = {}) {
|
|
1594
1448
|
const reasoning = routeReasoning(route, extra.prompt || '');
|
|
1595
1449
|
const subagentsRequired = routeRequiresSubagents(route, extra.prompt || '');
|
|
1596
|
-
return { mission_id: id, route: route.id, route_command: route.command, mode: route.mode, phase, context7_required: context7Required, context7_verified: false, subagents_required: subagentsRequired, subagents_verified: !subagentsRequired, native_sessions_required: false, native_sessions_verified: false, reflection_required: reflectionRequiredForRoute(route), engineering_sanity_required: false, engineering_sanity_scope_base: null, visible_progress_required: true, context_tracking: 'triwiki', required_skills: route.requiredSkills, stop_gate: route.stopGate, reasoning_effort: reasoning.effort, reasoning_profile: reasoning.profile, reasoning_temporary: true, goal_continuation: ambientGoalContinuation(), ...extra };
|
|
1597
|
-
}
|
|
1598
|
-
function routeNeedsEngineeringSanityReview(route, task) {
|
|
1599
|
-
const id = String(route?.id || '');
|
|
1600
|
-
if (['DB', 'MadSKS'].includes(id))
|
|
1601
|
-
return true;
|
|
1602
|
-
if (['Answer', 'Help', 'Wiki', 'Goal', 'Research', 'AutoResearch', 'PPT', 'ImageUXReview', 'ComputerUse', 'GX'].includes(id))
|
|
1603
|
-
return false;
|
|
1604
|
-
return looksLikeCodeChangingWork(String(task || ''));
|
|
1450
|
+
return { mission_id: id, route: route.id, route_command: route.command, mode: route.mode, phase, context7_required: context7Required, context7_verified: false, subagents_required: subagentsRequired, subagents_verified: !subagentsRequired, native_sessions_required: false, native_sessions_verified: false, reflection_required: reflectionRequiredForRoute(route), engineering_sanity_required: false, engineering_sanity_scope_base: null, architecture_map_required: false, visible_progress_required: true, context_tracking: 'triwiki', required_skills: route.requiredSkills, stop_gate: route.stopGate, reasoning_effort: reasoning.effort, reasoning_profile: reasoning.profile, reasoning_temporary: true, goal_continuation: ambientGoalContinuation(), ...extra };
|
|
1605
1451
|
}
|
|
1606
1452
|
function routeContext(route, id, task, required, next) {
|
|
1607
1453
|
const visibleTask = stripVisibleDecisionAnswerBlocks(task);
|
|
@@ -17,6 +17,8 @@ import { checkStopGate } from '../stop-gate/stop-gate-check.js';
|
|
|
17
17
|
import { readWorkOrderLedger, evaluateWorkOrderCoverage } from '../work-order-ledger.js';
|
|
18
18
|
import { DB_ACCESS_REVIEW_ARTIFACT, DB_REVIEW_ARTIFACT, validateDbAccessReviewArtifact, validateDbReview, validateMadSksSqlPlaneCompletion } from '../db-review.js';
|
|
19
19
|
import { ENGINEERING_SANITY_REVIEW_ARTIFACT, validateEngineeringSanityReviewArtifact } from '../engineering-sanity-review.js';
|
|
20
|
+
import { ARCHITECTURE_MAP_REVIEW_ARTIFACT } from '../architecture-map-review.js';
|
|
21
|
+
import { architectureMapGateStatus } from '../architecture-map-pipeline.js';
|
|
20
22
|
import { clarificationStopReason, context7Evidence, hasContext7DocsEvidence, hasSubagentEvidence, subagentEvidence, PIPELINE_PLAN_ARTIFACT, } from './runtime-core.js';
|
|
21
23
|
import { projectTriwikiToAgentsMd } from '../triwiki/agents-md-projector.js';
|
|
22
24
|
import { effectiveSubagentTarget, normalizeLegacySubagentCountFields, subagentCountContractBlockers } from '../subagents/wave-lifecycle.js';
|
|
@@ -256,6 +258,7 @@ export async function evaluateStop(root, state, payload, opts = {}) {
|
|
|
256
258
|
&& !completionProofRequired
|
|
257
259
|
&& !reflectionRequired
|
|
258
260
|
&& state?.engineering_sanity_required !== true
|
|
261
|
+
&& state?.architecture_map_required !== true
|
|
259
262
|
&& state?.db_access_review_required !== true) {
|
|
260
263
|
return null;
|
|
261
264
|
}
|
|
@@ -267,12 +270,14 @@ export async function evaluateStop(root, state, payload, opts = {}) {
|
|
|
267
270
|
&& activeGatePreview?.covered_gate === 'official-subagent-evidence';
|
|
268
271
|
const dbAccessPromise = dbAccessReviewGateStatus(root, state, jsonCache);
|
|
269
272
|
const engineeringSanityPromise = engineeringSanityGateStatus(root, state, jsonCache);
|
|
273
|
+
const architectureMapPromise = architectureMapGateStatus(root, state);
|
|
270
274
|
const context7Promise = state?.context7_required ? hasContext7DocsEvidence(root, state) : Promise.resolve(true);
|
|
271
275
|
const subagentPromise = state?.subagents_required && !activeGateNotApplicable ? hasSubagentEvidence(root, state) : Promise.resolve(true);
|
|
272
276
|
const mistakeRecallPromise = mistakeRecallGateStatus(root, state);
|
|
273
|
-
const [dbAccess, engineeringSanity, context7Ok, subagentOk, mistakeRecall] = await Promise.all([
|
|
277
|
+
const [dbAccess, engineeringSanity, architectureMap, context7Ok, subagentOk, mistakeRecall] = await Promise.all([
|
|
274
278
|
dbAccessPromise,
|
|
275
279
|
engineeringSanityPromise,
|
|
280
|
+
architectureMapPromise,
|
|
276
281
|
context7Promise,
|
|
277
282
|
subagentPromise,
|
|
278
283
|
mistakeRecallPromise
|
|
@@ -280,6 +285,7 @@ export async function evaluateStop(root, state, payload, opts = {}) {
|
|
|
280
285
|
await resolveRecoveredComplianceBlocker(root, state, {
|
|
281
286
|
[DB_ACCESS_REVIEW_ARTIFACT]: dbAccess.ok,
|
|
282
287
|
[ENGINEERING_SANITY_REVIEW_ARTIFACT]: engineeringSanity.ok,
|
|
288
|
+
[ARCHITECTURE_MAP_REVIEW_ARTIFACT]: architectureMap.ok,
|
|
283
289
|
'context7-evidence': context7Ok,
|
|
284
290
|
'official-subagent-evidence': subagentOk
|
|
285
291
|
});
|
|
@@ -289,6 +295,9 @@ export async function evaluateStop(root, state, payload, opts = {}) {
|
|
|
289
295
|
if (!engineeringSanity.ok) {
|
|
290
296
|
return complianceBlock(root, state, `SKS ${state.route_command || state.mode || 'route'} must complete the engineering sanity review before completion. Static candidates are advisory only; inspect added hunks and real callers for SOLID boundaries, N+1/repeated I/O, bounded render/recursion/event/retry/polling behavior, and verification bypasses. Missing: ${(engineeringSanity.blockers || []).join(', ')}.`, { gate: ENGINEERING_SANITY_REVIEW_ARTIFACT, missing: engineeringSanity.blockers });
|
|
291
297
|
}
|
|
298
|
+
if (!architectureMap.ok) {
|
|
299
|
+
return complianceBlock(root, state, `SKS ${state.route_command || state.mode || 'route'} must seal Architecture Map baseline before mutation and produce a passing architecture-map-review before completion. Missing: ${(architectureMap.blockers || []).join(', ')}.`, { gate: ARCHITECTURE_MAP_REVIEW_ARTIFACT, missing: architectureMap.blockers });
|
|
300
|
+
}
|
|
292
301
|
if (state?.context7_required && !context7Ok) {
|
|
293
302
|
return complianceBlock(root, state, `SKS ${state.route_command || state.mode || 'route'} requires Context7 evidence before completion. Use Context7 resolve-library-id, then query-docs, so SKS can record context7-evidence.jsonl.`, { gate: 'context7-evidence' });
|
|
294
303
|
}
|
|
@@ -518,6 +527,8 @@ async function markReflectionInvalidatedForGateFailure(root, state = {}, detail
|
|
|
518
527
|
return;
|
|
519
528
|
if (/^work-order-ledger$/i.test(gate) || /^completion-proof$/i.test(gate))
|
|
520
529
|
return;
|
|
530
|
+
if (/^atlas:/.test(gate) || /^architecture-map:/.test(gate) || /(^|[\\/])?(architecture-atlas-review|architecture-map-review|atlas-gate)(\.json)?$/i.test(gate))
|
|
531
|
+
return;
|
|
521
532
|
await setCurrent(root, {
|
|
522
533
|
mission_id: state.mission_id,
|
|
523
534
|
reflection_invalidation_required: true,
|
|
@@ -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) =>
|
|
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
|
|
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
|
|
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
|
|
459
|
-
|
|
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
|
|
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.
|
|
297
|
-
|
|
298
|
-
]
|
|
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]));
|