sneakoscope 4.0.9 → 4.0.10

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/dist/bin/sks.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- const FAST_PACKAGE_VERSION = '4.0.9';
2
+ const FAST_PACKAGE_VERSION = '4.0.10';
3
3
  const args = process.argv.slice(2);
4
4
  try {
5
5
  if (args[0] === '--agent' && args[1] === 'worker') {
package/dist/core/fsx.js CHANGED
@@ -5,7 +5,7 @@ import os from 'node:os';
5
5
  import crypto from 'node:crypto';
6
6
  import { spawn } from 'node:child_process';
7
7
  import { fileURLToPath } from 'node:url';
8
- export const PACKAGE_VERSION = '4.0.9';
8
+ export const PACKAGE_VERSION = '4.0.10';
9
9
  export const DEFAULT_PROCESS_TAIL_BYTES = 256 * 1024;
10
10
  export const DEFAULT_PROCESS_TIMEOUT_MS = 30 * 60 * 1000;
11
11
  export function nowIso() {
@@ -236,7 +236,9 @@ export async function evaluateStop(root, state, payload, opts = {}) {
236
236
  if (state?.mission_id && state?.stop_gate && !['none', 'honest_mode', 'clarification-gate'].includes(state.stop_gate)) {
237
237
  // 4.0.9: Use canonical stop-gate resolver first for NARUTO/GLM_NARUTO routes.
238
238
  const modeUpper = String(state?.mode || '').toUpperCase();
239
- if (modeUpper === 'NARUTO' || state.stop_gate === 'stop-gate.json' || state.stop_gate === 'naruto-gate.json') {
239
+ const routeUpper = String(state?.route || state?.route_command || '').replace(/^\$/, '').toUpperCase();
240
+ const narutoFamily = modeUpper === 'NARUTO' || routeUpper === 'NARUTO' || routeUpper === 'GLM_NARUTO';
241
+ if (narutoFamily || state.stop_gate === 'stop-gate.json' || state.stop_gate === 'naruto-gate.json') {
240
242
  const stopCheck = await checkStopGate({
241
243
  root,
242
244
  route: state.route || state.mode,
@@ -244,7 +246,8 @@ export async function evaluateStop(root, state, payload, opts = {}) {
244
246
  explicitGatePath: typeof state.stop_gate_abs_path === 'string' && state.stop_gate_abs_path ? state.stop_gate_abs_path : undefined,
245
247
  });
246
248
  if (stopCheck.action === 'allow_stop') {
247
- // Gate passed via canonical resolver; fall through to remaining checks.
249
+ if (narutoFamily)
250
+ return { continue: true, systemMessage: `SKS: canonical stop-gate passed at ${stopCheck.gate_path}` };
248
251
  }
249
252
  else if (stopCheck.action === 'hard_blocked') {
250
253
  return { continue: true, systemMessage: `SKS: ${stopCheck.feedback}` };
@@ -3,8 +3,10 @@ import { SksLruCache } from '../../perf/lru-cache.js';
3
3
  export function createGlmEncodedRequestCache(maxEntries = 128) {
4
4
  return new SksLruCache(maxEntries);
5
5
  }
6
- export function encodeGlmRequestWithCache(request, cache = defaultEncodedRequestCache) {
7
- const key = digestRequestForCache(request);
6
+ export function encodeGlmRequestWithCache(input, cache = defaultEncodedRequestCache) {
7
+ const request = 'request' in input ? input.request : input;
8
+ const stringify = 'request' in input && input.stringify ? input.stringify : JSON.stringify;
9
+ const key = 'request' in input && input.cacheKeyParts ? digestRequestCacheKeyParts(input.cacheKeyParts) : digestRequestForCache(request);
8
10
  const hit = cache.get(key);
9
11
  // Fix 18.2: On cache hit, return stored body without JSON.stringify
10
12
  if (hit) {
@@ -12,11 +14,11 @@ export function encodeGlmRequestWithCache(request, cache = defaultEncodedRequest
12
14
  return { body: hit.body, entry: hit, cacheHit: true };
13
15
  }
14
16
  // Even for non-stored bodies, skip re-stringifying by computing from request
15
- const body = JSON.stringify(request);
17
+ const body = stringify(request);
16
18
  return { body, entry: hit, cacheHit: true };
17
19
  }
18
20
  // Cache miss: stringify once
19
- const body = JSON.stringify(request);
21
+ const body = stringify(request);
20
22
  if (containsSecretLikeContent(body)) {
21
23
  const entry = {
22
24
  key,
@@ -40,6 +42,9 @@ export function encodeGlmRequestWithCache(request, cache = defaultEncodedRequest
40
42
  cache.set(key, entry);
41
43
  return { body, entry, cacheHit: false };
42
44
  }
45
+ export function digestRequestCacheKeyParts(parts) {
46
+ return crypto.createHash('sha256').update(stableStringify(parts)).digest('hex');
47
+ }
43
48
  export function digestRequestForCache(request) {
44
49
  const safe = {
45
50
  model: request.model,
@@ -1,6 +1,10 @@
1
- import { nowIso, writeJsonAtomic } from '../../../fsx.js';
1
+ import { nowIso } from '../../../fsx.js';
2
2
  import path from 'node:path';
3
+ import fsp from 'node:fs/promises';
4
+ import os from 'node:os';
3
5
  import { GLM_52_OPENROUTER_MODEL } from '../glm-52-settings.js';
6
+ import { resolveOpenRouterApiKey } from '../../openrouter/openrouter-secret-store.js';
7
+ import { runGlmNarutoMission } from './glm-naruto-orchestrator.js';
4
8
  export async function runGlmNarutoBench(root, args = []) {
5
9
  const live = args.includes('--live');
6
10
  const execute = args.includes('--execute');
@@ -11,7 +15,7 @@ export async function runGlmNarutoBench(root, args = []) {
11
15
  if (!live) {
12
16
  return {
13
17
  schema: 'sks.glm-naruto-bench.v1',
14
- version: '4.0.9',
18
+ version: '4.0.10',
15
19
  generated_at: nowIso(),
16
20
  status: 'dry_run',
17
21
  model: GLM_52_OPENROUTER_MODEL,
@@ -27,29 +31,60 @@ export async function runGlmNarutoBench(root, args = []) {
27
31
  warnings: ['dry_run_no_live_api_calls']
28
32
  };
29
33
  }
30
- // Live bench would require OpenRouter key and real API calls
34
+ const key = await resolveOpenRouterApiKey({ env: process.env });
35
+ if (!key.key)
36
+ return blocked(root, ['live_bench_requires_openrouter_key']);
37
+ const fixture = await fsp.mkdtemp(path.join(os.tmpdir(), 'sks-glm-naruto-live-bench-'));
38
+ await fsp.mkdir(path.join(fixture, 'src'), { recursive: true });
39
+ await fsp.writeFile(path.join(fixture, 'src', 'bench-target.ts'), 'export const value = 1;\n', 'utf8');
40
+ const cases = [];
41
+ for (const workers of [1, 4, 8, 12]) {
42
+ const caseStarted = Date.now();
43
+ const result = await runGlmNarutoMission({
44
+ cwd: fixture,
45
+ task: 'Change src/bench-target.ts so value is 2. Return the smallest patch only.',
46
+ args: ['--bench', '--live', '--no-apply'],
47
+ missionId: `glm-naruto-live-bench-${workers}-${Date.now()}`,
48
+ maxWorkers: workers,
49
+ noApply: true
50
+ });
51
+ cases.push({
52
+ name: workers === 1 ? 'direct single GLM' : `GLM Naruto ${workers} workers`,
53
+ workers,
54
+ wall_clock_ms: Date.now() - caseStarted,
55
+ p50_ttft_ms: null,
56
+ p90_ttft_ms: null,
57
+ candidate_count: result.patch_candidates,
58
+ gate_pass_rate: result.patch_candidates ? result.gate_passed_candidates / result.patch_candidates : 0,
59
+ verifier_pass_rate: 0,
60
+ merge_success: result.mergeable_candidates > 0,
61
+ cached_tokens: 0,
62
+ cache_write_tokens: 0
63
+ });
64
+ }
31
65
  return {
32
66
  schema: 'sks.glm-naruto-bench.v1',
33
- version: '4.0.9',
67
+ version: '4.0.10',
34
68
  generated_at: nowIso(),
35
- status: 'blocked',
69
+ status: 'live',
36
70
  model: GLM_52_OPENROUTER_MODEL,
37
71
  gpt_fallback_allowed: false,
72
+ cases,
38
73
  summary: {
39
- simulated_workers: 0,
40
- simulated_waves: 0,
41
- simulated_patch_candidates: 0,
42
- simulated_gate_passed: 0,
43
- simulated_mergeable: 0,
74
+ simulated_workers: Math.max(...cases.map((row) => row.workers)),
75
+ simulated_waves: cases.length,
76
+ simulated_patch_candidates: cases.reduce((sum, row) => sum + row.candidate_count, 0),
77
+ simulated_gate_passed: cases.reduce((sum, row) => sum + Math.round(row.candidate_count * row.gate_pass_rate), 0),
78
+ simulated_mergeable: cases.filter((row) => row.merge_success).length,
44
79
  wall_clock_ms: Date.now() - started
45
80
  },
46
- warnings: ['live_bench_requires_openrouter_key_and_task']
81
+ warnings: ['live_bench_no_apply_temp_repo']
47
82
  };
48
83
  }
49
84
  function blocked(root, warnings) {
50
85
  return {
51
86
  schema: 'sks.glm-naruto-bench.v1',
52
- version: '4.0.9',
87
+ version: '4.0.10',
53
88
  generated_at: nowIso(),
54
89
  status: 'blocked',
55
90
  model: GLM_52_OPENROUTER_MODEL,
@@ -0,0 +1,49 @@
1
+ import { checkAndApplyGlmPatch } from '../glm-patch-apply.js';
2
+ export function combineGlmNarutoPatches(envelopes, selectedPatchIds) {
3
+ const selected = selectedPatchIds
4
+ .map((patchId) => envelopes.find((env) => env.worker_id === patchId || env.patch_sha256 === patchId))
5
+ .filter((env) => Boolean(env))
6
+ .sort((a, b) => a.worker_id.localeCompare(b.worker_id));
7
+ return mergeDiffSections(selected.flatMap((env) => splitDiffSections(env.patch)));
8
+ }
9
+ export async function checkAndApplyCombinedGlmNarutoPatch(input) {
10
+ const patch = combineGlmNarutoPatches(input.envelopes, input.selectedPatchIds);
11
+ if (!patch.trim())
12
+ return { ok: false, patch, applied: [], blocker: 'combined_patch_empty' };
13
+ const checked = await checkAndApplyGlmPatch({ cwd: input.cwd, patch, apply: input.apply });
14
+ if (!checked.ok)
15
+ return { ok: false, patch, applied: [], blocker: checked.error.code };
16
+ return { ok: true, patch, applied: input.selectedPatchIds };
17
+ }
18
+ function mergeDiffSections(sections) {
19
+ const byFile = new Map();
20
+ for (const section of sections)
21
+ byFile.set(section.file, [...(byFile.get(section.file) || []), section]);
22
+ const merged = [];
23
+ for (const [file] of byFile) {
24
+ const group = byFile.get(file);
25
+ const first = group[0];
26
+ merged.push([...first.header, ...group.flatMap((section) => section.hunks)].join('\n').trimEnd());
27
+ }
28
+ return merged.filter(Boolean).join('\n\n') + (merged.length ? '\n' : '');
29
+ }
30
+ function splitDiffSections(patch) {
31
+ const rawSections = patch
32
+ .split(/(?=^diff --git )/m)
33
+ .map((section) => section.trim())
34
+ .filter(Boolean);
35
+ return rawSections.map((section) => {
36
+ const lines = section.split(/\r?\n/);
37
+ const diff = lines[0]?.match(/^diff --git a\/(.+?) b\/(.+)$/);
38
+ const file = diff?.[2] || diff?.[1] || lines[0] || 'unknown';
39
+ const firstHunk = lines.findIndex((line) => line.startsWith('@@ '));
40
+ if (firstHunk < 0)
41
+ return { file, header: lines, hunks: [] };
42
+ return {
43
+ file,
44
+ header: lines.slice(0, firstHunk),
45
+ hunks: lines.slice(firstHunk)
46
+ };
47
+ });
48
+ }
49
+ //# sourceMappingURL=glm-naruto-combined-patch.js.map
@@ -1,3 +1,4 @@
1
+ import { envelopesHaveHunkConflict } from './glm-naruto-hunk-conflict.js';
1
2
  export function buildConflictGraph(envelopes, nodes) {
2
3
  const edges = [];
3
4
  for (let i = 0; i < nodes.length; i++) {
@@ -23,11 +24,16 @@ function detectConflict(left, right, envelopes) {
23
24
  if (leftEnv && rightEnv && leftEnv.base_digest !== rightEnv.base_digest) {
24
25
  return { left_patch_id: left.patch_id, right_patch_id: right.patch_id, reason: 'base_digest_mismatch' };
25
26
  }
27
+ if (left.shard_id === right.shard_id) {
28
+ return { left_patch_id: left.patch_id, right_patch_id: right.patch_id, reason: 'same_hunk' };
29
+ }
26
30
  const leftPaths = new Set(left.target_paths);
27
31
  const rightPaths = new Set(right.target_paths);
28
32
  const sharedFiles = [...leftPaths].filter((p) => rightPaths.has(p));
29
33
  if (sharedFiles.length > 0) {
30
- if (left.shard_id === right.shard_id) {
34
+ if (leftEnv && rightEnv) {
35
+ if (!envelopesHaveHunkConflict(leftEnv, rightEnv))
36
+ return null;
31
37
  return { left_patch_id: left.patch_id, right_patch_id: right.patch_id, reason: 'same_hunk' };
32
38
  }
33
39
  return { left_patch_id: left.patch_id, right_patch_id: right.patch_id, reason: 'same_file' };
@@ -0,0 +1,16 @@
1
+ import { hunksOverlap, parseUnifiedDiffHunks } from './glm-naruto-hunk-parser.js';
2
+ export function envelopesHaveHunkConflict(left, right) {
3
+ const leftHunks = parseUnifiedDiffHunks(left.patch);
4
+ const rightHunks = parseUnifiedDiffHunks(right.patch);
5
+ if (leftHunks.length === 0 || rightHunks.length === 0)
6
+ return sharesPath(left, right);
7
+ return leftHunks.some((leftHunk) => rightHunks.some((rightHunk) => hunksOverlap(leftHunk, rightHunk)));
8
+ }
9
+ export function envelopesShareFileButNotHunk(left, right) {
10
+ return sharesPath(left, right) && !envelopesHaveHunkConflict(left, right);
11
+ }
12
+ function sharesPath(left, right) {
13
+ const rightPaths = new Set(right.target_paths);
14
+ return left.target_paths.some((file) => rightPaths.has(file));
15
+ }
16
+ //# sourceMappingURL=glm-naruto-hunk-conflict.js.map
@@ -0,0 +1,36 @@
1
+ export function parseUnifiedDiffHunks(patch) {
2
+ const hunks = [];
3
+ let currentFile = null;
4
+ for (const line of patch.split(/\r?\n/)) {
5
+ const diff = line.match(/^diff --git a\/(.+?) b\/(.+)$/);
6
+ if (diff?.[2]) {
7
+ currentFile = diff[2];
8
+ continue;
9
+ }
10
+ const file = line.match(/^\+\+\+ b\/(.+)$/);
11
+ if (file?.[1] && file[1] !== '/dev/null')
12
+ currentFile = file[1];
13
+ const hunk = line.match(/^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*)$/);
14
+ if (hunk && currentFile) {
15
+ hunks.push({
16
+ file: currentFile,
17
+ old_start: Number(hunk[1]),
18
+ old_lines: Number(hunk[2] || 1),
19
+ new_start: Number(hunk[3]),
20
+ new_lines: Number(hunk[4] || 1),
21
+ header: line
22
+ });
23
+ }
24
+ }
25
+ return hunks;
26
+ }
27
+ export function hunksOverlap(left, right) {
28
+ if (left.file !== right.file)
29
+ return false;
30
+ return rangesOverlap(left.old_start, left.old_start + Math.max(1, left.old_lines) - 1, right.old_start, right.old_start + Math.max(1, right.old_lines) - 1)
31
+ || rangesOverlap(left.new_start, left.new_start + Math.max(1, left.new_lines) - 1, right.new_start, right.new_start + Math.max(1, right.new_lines) - 1);
32
+ }
33
+ function rangesOverlap(aStart, aEnd, bStart, bEnd) {
34
+ return aStart <= bEnd && bStart <= aEnd;
35
+ }
36
+ //# sourceMappingURL=glm-naruto-hunk-parser.js.map
@@ -2,7 +2,6 @@ import path from 'node:path';
2
2
  import { nowIso, writeJsonAtomic } from '../../../fsx.js';
3
3
  import { GLM_52_OPENROUTER_MODEL } from '../glm-52-settings.js';
4
4
  import { resolveOpenRouterApiKey } from '../../openrouter/openrouter-secret-store.js';
5
- import { checkAndApplyGlmPatch } from '../glm-patch-apply.js';
6
5
  import { decomposeTask, validateWorkGraph } from './glm-naruto-decomposer.js';
7
6
  import { planShardCandidates, computeInitialLaneMix } from './glm-naruto-shard-planner.js';
8
7
  import { runPatchWorkerPool } from './glm-naruto-worker-pool.js';
@@ -16,6 +15,8 @@ import { createProviderHealthTracker } from '../../openrouter/openrouter-provide
16
15
  import { createMissionTrace, recordWorkerTrace, writeMissionArtifacts, buildMissionSummary } from './glm-naruto-trace.js';
17
16
  import { runGlmJudge } from './glm-naruto-judge.js';
18
17
  import { writeFinalStopGate } from '../../../stop-gate/stop-gate-writer.js';
18
+ import { checkAndApplyCombinedGlmNarutoPatch } from './glm-naruto-combined-patch.js';
19
+ import { auditGlmNarutoArtifactsForSecrets } from './glm-naruto-secret-audit.js';
19
20
  import { GLM_NARUTO_LIMITS } from './glm-naruto-types.js';
20
21
  export async function runGlmNarutoMission(input) {
21
22
  const missionId = input.missionId || `glm-naruto-${nowIso().replace(/[:.]/g, '-')}`;
@@ -156,16 +157,14 @@ export async function runGlmNarutoMission(input) {
156
157
  let appliedPatches = 0;
157
158
  let applyResult = null;
158
159
  if (!input.noApply && mergePlan.selected_patches.length > 0) {
159
- for (const patchId of mergePlan.selected_patches) {
160
- const envelope = envelopes.find((e) => e.worker_id === patchId);
161
- if (!envelope)
162
- continue;
163
- const applied = await checkAndApplyGlmPatch({ cwd, patch: envelope.patch, apply: true });
164
- if (applied.ok) {
165
- appliedPatches++;
166
- }
167
- }
168
- applyResult = { ok: appliedPatches > 0, applied: mergePlan.selected_patches };
160
+ const combinedApply = await checkAndApplyCombinedGlmNarutoPatch({
161
+ cwd,
162
+ envelopes,
163
+ selectedPatchIds: mergePlan.selected_patches,
164
+ apply: true
165
+ });
166
+ appliedPatches = combinedApply.ok ? combinedApply.applied.length : 0;
167
+ applyResult = { ok: combinedApply.ok, applied: combinedApply.applied, ...(combinedApply.blocker ? { blocker: combinedApply.blocker } : {}) };
169
168
  }
170
169
  const terminalState = appliedPatches > 0 ? 'completed' : passedEnvelopes.length > 0 ? 'partial_candidates' : 'blocked';
171
170
  const terminationReason = appliedPatches > 0 ? 'completed_merge_applied' : passedEnvelopes.length > 0 ? 'partial_no_apply' : 'no_gate_passed_candidates';
@@ -216,13 +215,21 @@ export async function runGlmNarutoMission(input) {
216
215
  missionResult: result,
217
216
  envelopes
218
217
  });
218
+ const secretAudit = await auditGlmNarutoArtifactsForSecrets(path.join(cwd, '.sneakoscope', 'glm-naruto', missionId)).catch((err) => ({
219
+ schema: 'sks.glm-naruto-secret-audit.v1',
220
+ ok: false,
221
+ root: path.join(cwd, '.sneakoscope', 'glm-naruto', missionId),
222
+ scanned_files: 0,
223
+ findings: [`audit_failed:${err instanceof Error ? err.message : String(err)}`]
224
+ }));
225
+ await writeJsonAtomic(path.join(artifactDir, 'secret-audit.json'), secretAudit).catch(() => undefined);
219
226
  // 4.0.9: Write canonical stop-gate artifacts for hook resolution.
220
227
  await writeFinalStopGate({
221
228
  root: cwd,
222
229
  missionId,
223
230
  route: 'GLM_NARUTO',
224
231
  routeCommand: '$Naruto',
225
- status: result.ok ? 'passed' : (terminalState === 'blocked' ? 'blocked' : 'failed'),
232
+ status: result.ok && secretAudit.ok ? 'passed' : (terminalState === 'blocked' || !secretAudit.ok ? 'blocked' : 'failed'),
226
233
  terminal: terminalState === 'completed' || terminalState === 'blocked',
227
234
  terminalState,
228
235
  evidence: {
@@ -232,8 +239,12 @@ export async function runGlmNarutoMission(input) {
232
239
  per_worker_artifacts: true,
233
240
  verifier_wave_run: true,
234
241
  model_guard_enforced: true,
242
+ proof_required: false,
243
+ proof_passed: true,
244
+ reflection_required: false,
245
+ reflection_passed: 'not_required',
235
246
  },
236
- blockers: result.blockers || [],
247
+ blockers: secretAudit.ok ? (result.blockers || []) : ['glm_naruto_secret_leak_detected'],
237
248
  nativeGateFile: 'termination.json',
238
249
  }).catch(() => null);
239
250
  return { ...result, artifact_dir: artifactDir };
@@ -0,0 +1,42 @@
1
+ import { checkAndApplyGlmPatch } from '../glm-patch-apply.js';
2
+ import { parseGlmNarutoPatchCandidate } from './glm-naruto-patch-candidate-parser.js';
3
+ const PROTECTED_PATH = /(^|\/)(\.github|dist|node_modules)(\/|$)/;
4
+ const SECRET_PATTERN = /\b(?:Bearer\s+[A-Za-z0-9._~+/-]+|sk-(?:or-)?[A-Za-z0-9_-]{12,}|OPENROUTER_API_KEY|SKS_OPENROUTER_API_KEY)\b/;
5
+ export async function evaluateGlmNarutoPatchCandidateGate(input) {
6
+ const checks = [];
7
+ const parsed = parseGlmNarutoPatchCandidate(input.envelope.patch);
8
+ checks.push(check('candidate_parse', parsed.ok, parsed.blockers.join(',') || undefined));
9
+ checks.push(check('patch_section', Boolean(parsed.patch), parsed.patch ? undefined : 'missing_patch_section'));
10
+ checks.push(check('unified_diff', /^diff --git /m.test(parsed.patch), parsed.patch ? undefined : 'no_diff_git'));
11
+ const blockedPath = parsed.touched_paths.find((file) => PROTECTED_PATH.test(file));
12
+ checks.push(check('protected_path_guard', !blockedPath, blockedPath));
13
+ const secretLeak = SECRET_PATTERN.test(parsed.patch);
14
+ checks.push(check('secret_leakage_guard', !secretLeak, secretLeak ? 'secret_like_content' : undefined));
15
+ if (parsed.ok && !blockedPath && !secretLeak) {
16
+ const applyCheck = await checkAndApplyGlmPatch({
17
+ cwd: input.cwd,
18
+ patch: parsed.patch,
19
+ apply: input.apply === true
20
+ });
21
+ checks.push(check('git_apply_check', applyCheck.ok, applyCheck.ok ? undefined : applyCheck.error.code));
22
+ }
23
+ else {
24
+ checks.push({ id: 'git_apply_check', ok: false, reason: 'skipped_due_to_prior_blocker', ms: 0 });
25
+ }
26
+ const blockers = checks.filter((row) => !row.ok).map((row) => row.reason || row.id);
27
+ return {
28
+ schema: 'sks.glm-naruto-patch-candidate-gate.v1',
29
+ ok: blockers.length === 0,
30
+ worker_id: input.envelope.worker_id,
31
+ shard_id: input.envelope.shard_id,
32
+ patch_id: input.envelope.patch_sha256,
33
+ extracted_patch: parsed.patch,
34
+ touched_paths: parsed.touched_paths,
35
+ checks,
36
+ blockers
37
+ };
38
+ }
39
+ function check(id, ok, reason) {
40
+ return { id, ok, ...(reason ? { reason } : {}), ms: 0 };
41
+ }
42
+ //# sourceMappingURL=glm-naruto-patch-candidate-gate.js.map
@@ -0,0 +1,62 @@
1
+ import { parseUnifiedDiffPatch } from '../glm-patch-parser.js';
2
+ const CANDIDATE_OPEN = '<sks_patch_candidate>';
3
+ const CANDIDATE_CLOSE = '</sks_patch_candidate>';
4
+ const LEGACY_PATCH_OPEN = '<sks_patch>';
5
+ export function parseGlmNarutoPatchCandidate(text) {
6
+ const blockers = [];
7
+ if (text.includes(LEGACY_PATCH_OPEN) && !text.includes(CANDIDATE_OPEN)) {
8
+ return failed(text, ['legacy_sks_patch_envelope_rejected']);
9
+ }
10
+ const body = unwrapCandidateBody(text);
11
+ if (!body.ok)
12
+ return failed(text, body.blockers);
13
+ const extracted = extractPatchSection(body.body);
14
+ if (!extracted.ok)
15
+ return failed(body.body, extracted.blockers);
16
+ const parsed = parseUnifiedDiffPatch(extracted.patch);
17
+ if (parsed.empty)
18
+ blockers.push('patch_missing_unified_diff');
19
+ return {
20
+ ok: blockers.length === 0,
21
+ body: body.body,
22
+ patch: extracted.patch,
23
+ touched_paths: parsed.touchedPaths,
24
+ blockers
25
+ };
26
+ }
27
+ function unwrapCandidateBody(text) {
28
+ const start = text.indexOf(CANDIDATE_OPEN);
29
+ const end = text.indexOf(CANDIDATE_CLOSE);
30
+ if (start >= 0 || end >= 0) {
31
+ if (start < 0 || end <= start)
32
+ return { ok: false, blockers: ['malformed_patch_candidate_envelope'] };
33
+ return { ok: true, body: text.slice(start + CANDIDATE_OPEN.length, end).trim() };
34
+ }
35
+ if (/^\s*patch\s*:/im.test(text))
36
+ return { ok: true, body: text.trim() };
37
+ return { ok: false, blockers: ['missing_patch_candidate_envelope'] };
38
+ }
39
+ function extractPatchSection(body) {
40
+ const lines = body.split(/\r?\n/);
41
+ const patchLine = lines.findIndex((line) => /^\s*patch\s*:/i.test(line));
42
+ if (patchLine < 0)
43
+ return { ok: false, blockers: ['missing_patch_section'] };
44
+ const firstLine = lines[patchLine] || '';
45
+ const inline = firstLine.replace(/^\s*patch\s*:\s*/i, '');
46
+ const tail = inline.trim() ? [inline, ...lines.slice(patchLine + 1)] : lines.slice(patchLine + 1);
47
+ const raw = tail.join('\n').replace(/^\s*```(?:diff|patch)?\s*/i, '').replace(/\s*```\s*$/i, '').trim();
48
+ const diffIndex = raw.search(/^diff --git /m);
49
+ if (diffIndex < 0)
50
+ return { ok: false, blockers: ['patch_missing_diff_git'] };
51
+ return { ok: true, patch: raw.slice(diffIndex).trimEnd() + '\n' };
52
+ }
53
+ function failed(body, blockers) {
54
+ return {
55
+ ok: false,
56
+ body,
57
+ patch: '',
58
+ touched_paths: [],
59
+ blockers
60
+ };
61
+ }
62
+ //# sourceMappingURL=glm-naruto-patch-candidate-parser.js.map
@@ -0,0 +1,39 @@
1
+ import fsp from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ const SECRET_PATTERN = /\b(?:Bearer\s+[A-Za-z0-9._~+/-]+|sk-(?:or-)?[A-Za-z0-9_-]{12,}|OPENROUTER_API_KEY|SKS_OPENROUTER_API_KEY)\b/;
4
+ export async function auditGlmNarutoArtifactsForSecrets(root) {
5
+ const findings = [];
6
+ let scanned = 0;
7
+ await scan(root, async (file) => {
8
+ if (!/\.(json|jsonl|md|txt)$/i.test(file))
9
+ return;
10
+ scanned++;
11
+ const content = await fsp.readFile(file, 'utf8').catch(() => '');
12
+ if (SECRET_PATTERN.test(content))
13
+ findings.push(path.relative(root, file));
14
+ });
15
+ return {
16
+ schema: 'sks.glm-naruto-secret-audit.v1',
17
+ ok: findings.length === 0,
18
+ root,
19
+ scanned_files: scanned,
20
+ findings
21
+ };
22
+ }
23
+ async function scan(dir, visit) {
24
+ let entries;
25
+ try {
26
+ entries = await fsp.readdir(dir, { withFileTypes: true });
27
+ }
28
+ catch {
29
+ return;
30
+ }
31
+ for (const entry of entries) {
32
+ const p = path.join(dir, String(entry.name));
33
+ if (entry.isDirectory())
34
+ await scan(p, visit);
35
+ else if (entry.isFile())
36
+ await visit(p);
37
+ }
38
+ }
39
+ //# sourceMappingURL=glm-naruto-secret-audit.js.map
@@ -0,0 +1,26 @@
1
+ export function parseGlmNarutoVerifierOutput(content) {
2
+ try {
3
+ const parsed = JSON.parse(stripJsonFences(content));
4
+ const issues = [];
5
+ if (parsed.schema !== 'sks.glm-naruto-verifier-output.v1')
6
+ issues.push('schema');
7
+ if (typeof parsed.ok !== 'boolean')
8
+ issues.push('ok');
9
+ if (!Array.isArray(parsed.issues) || parsed.issues.some((issue) => typeof issue !== 'string'))
10
+ issues.push('issues');
11
+ if (typeof parsed.risk_score !== 'number' || parsed.risk_score < 0 || parsed.risk_score > 1)
12
+ issues.push('risk_score');
13
+ if (typeof parsed.confidence !== 'number' || parsed.confidence < 0 || parsed.confidence > 1)
14
+ issues.push('confidence');
15
+ if (issues.length)
16
+ return { ok: false, issues: issues.map((issue) => `invalid_${issue}`) };
17
+ return { ok: true, output: parsed, issues: [] };
18
+ }
19
+ catch {
20
+ return { ok: false, issues: ['malformed_json'] };
21
+ }
22
+ }
23
+ function stripJsonFences(content) {
24
+ return content.trim().replace(/^```(?:json)?\s*/i, '').replace(/\s*```\s*$/i, '').trim();
25
+ }
26
+ //# sourceMappingURL=glm-naruto-verifier-output.js.map
@@ -0,0 +1,42 @@
1
+ import path from 'node:path';
2
+ import { ensureDir, nowIso, writeJsonAtomic } from '../../../fsx.js';
3
+ export async function writeGlmNarutoWorkerArtifacts(input) {
4
+ const dir = path.join(input.root, '.sneakoscope', 'glm-naruto', input.missionId, 'workers', input.workerId);
5
+ await ensureDir(dir);
6
+ if (input.requestSummary) {
7
+ await writeJsonAtomic(path.join(dir, 'request-summary.json'), sanitizeArtifact({
8
+ schema: 'sks.glm-naruto-worker-request-summary.v1',
9
+ worker_id: input.workerId,
10
+ shard_id: input.shardId,
11
+ created_at: nowIso(),
12
+ ...input.requestSummary
13
+ }));
14
+ }
15
+ if (input.streamTrace)
16
+ await writeJsonAtomic(path.join(dir, 'stream-trace.json'), sanitizeArtifact(input.streamTrace));
17
+ if (input.patchEnvelope)
18
+ await writeJsonAtomic(path.join(dir, 'patch-envelope.json'), sanitizeArtifact(input.patchEnvelope));
19
+ if (input.gateResult)
20
+ await writeJsonAtomic(path.join(dir, 'gate-result.json'), sanitizeArtifact(input.gateResult));
21
+ if (input.termination) {
22
+ await writeJsonAtomic(path.join(dir, 'termination.json'), sanitizeArtifact({
23
+ schema: 'sks.glm-naruto-worker-termination.v1',
24
+ worker_id: input.workerId,
25
+ shard_id: input.shardId,
26
+ created_at: nowIso(),
27
+ ...input.termination
28
+ }));
29
+ }
30
+ return dir;
31
+ }
32
+ function sanitizeArtifact(value) {
33
+ return JSON.parse(JSON.stringify(value, (_key, raw) => {
34
+ if (typeof raw !== 'string')
35
+ return raw;
36
+ return raw
37
+ .replace(/Bearer\s+[A-Za-z0-9._~+/-]+/g, 'Bearer [REDACTED]')
38
+ .replace(/sk-or-[A-Za-z0-9_-]+/g, 'sk-or-[REDACTED]')
39
+ .replace(/sk-[A-Za-z0-9_-]{12,}/g, 'sk-[REDACTED]');
40
+ }));
41
+ }
42
+ //# sourceMappingURL=glm-naruto-worker-artifacts.js.map
@@ -1,8 +1,9 @@
1
1
  import { runPatchWorker } from './glm-naruto-worker-runtime.js';
2
- import { checkAndApplyGlmPatch } from '../glm-patch-apply.js';
3
- import { evaluateGlmSpeedGate } from '../glm-speed-gate.js';
4
2
  import { decideConcurrency } from './glm-naruto-concurrency-governor.js';
5
3
  import { planFileLeases } from './glm-naruto-file-lease.js';
4
+ import { evaluateGlmNarutoPatchCandidateGate } from './glm-naruto-patch-candidate-gate.js';
5
+ import { createPatchEnvelope } from './glm-naruto-patch-envelope.js';
6
+ import { writeGlmNarutoWorkerArtifacts } from './glm-naruto-worker-artifacts.js';
6
7
  export async function runPatchWorkerPool(input) {
7
8
  const envelopes = [];
8
9
  const traces = [];
@@ -34,6 +35,7 @@ export async function runPatchWorkerPool(input) {
34
35
  apiKey: input.apiKey,
35
36
  missionId: input.missionId,
36
37
  workerId,
38
+ root: input.cwd,
37
39
  shard: shardWithStrategy,
38
40
  contextSummary: input.contextSummary,
39
41
  timeoutMs: input.workerTimeoutMs
@@ -43,25 +45,42 @@ export async function runPatchWorkerPool(input) {
43
45
  const results = await Promise.allSettled(workerTasks);
44
46
  for (const result of results) {
45
47
  if (result.status === 'fulfilled' && result.value.ok && result.value.envelope) {
46
- const gate = evaluateGlmSpeedGate(result.value.envelope.patch);
48
+ const gate = await evaluateGlmNarutoPatchCandidateGate({
49
+ cwd: input.cwd,
50
+ envelope: result.value.envelope,
51
+ apply: false
52
+ });
47
53
  let envelope = result.value.envelope;
48
54
  if (gate.ok) {
49
- const applyCheck = await checkAndApplyGlmPatch({
50
- cwd: input.cwd,
51
- patch: envelope.patch,
52
- apply: false
55
+ envelope = createPatchEnvelope({
56
+ missionId: envelope.mission_id,
57
+ workerId: envelope.worker_id,
58
+ shardId: envelope.shard_id,
59
+ baseDigest: envelope.base_digest,
60
+ patch: gate.extracted_patch,
61
+ strategy: envelope.strategy,
62
+ reasoningEffort: envelope.reasoning_effort,
63
+ status: 'gate_passed',
64
+ warnings: envelope.warnings
53
65
  });
54
- envelope = applyCheck.ok
55
- ? { ...envelope, status: 'gate_passed' }
56
- : { ...envelope, status: 'gate_failed', blockers: [applyCheck.error.code] };
57
66
  }
58
67
  else {
59
68
  envelope = {
60
69
  ...envelope,
61
70
  status: 'gate_failed',
62
- blockers: gate.checks.filter((c) => !c.ok).map((c) => c.reason || c.id)
71
+ blockers: gate.blockers
63
72
  };
64
73
  }
74
+ await writeGlmNarutoWorkerArtifacts({
75
+ root: input.cwd,
76
+ missionId: input.missionId,
77
+ workerId: envelope.worker_id,
78
+ shardId: envelope.shard_id,
79
+ patchEnvelope: envelope,
80
+ gateResult: gate,
81
+ streamTrace: result.value.trace,
82
+ termination: { status: envelope.status, ok: gate.ok, blockers: envelope.blockers }
83
+ }).catch(() => undefined);
65
84
  envelopes.push(envelope);
66
85
  traces.push(result.value.trace);
67
86
  }
@@ -6,9 +6,12 @@ import { sendOpenRouterChatCompletionStream } from '../../openrouter/openrouter-
6
6
  import { assertGlm52ActualModel } from '../glm-52-response-guard.js';
7
7
  import { encodeGlmRequestWithCache } from '../glm-request-cache.js';
8
8
  import { parsePatchCandidateOutput, createPatchEnvelope, digestPatch } from './glm-naruto-patch-envelope.js';
9
+ import { parseGlmNarutoVerifierOutput } from './glm-naruto-verifier-output.js';
10
+ import { writeGlmNarutoWorkerArtifacts } from './glm-naruto-worker-artifacts.js';
9
11
  const STABLE_SYSTEM_PREFIX = `You are a SKS GLM Naruto patch worker. Model lock: ${GLM_52_OPENROUTER_MODEL}. No GPT/OpenAI fallback allowed. Output only <sks_patch_candidate>, <sks_need_context>, or <sks_blocked> envelopes. Use unified diff format for patches. Never write to main workspace directly. Follow proof-first mutation rules.`;
10
12
  export async function runPatchWorker(input) {
11
13
  const started = Date.now();
14
+ const artifactRoot = input.root ?? process.cwd();
12
15
  const sessionId = `sks-glm-naruto-${input.missionId}-${input.workerId}`;
13
16
  const reasoningEffort = input.shard.reasoning;
14
17
  const shardSuffix = JSON.stringify({
@@ -34,7 +37,38 @@ export async function runPatchWorker(input) {
34
37
  providerSort: 'throughput'
35
38
  });
36
39
  const requestWithSession = { ...request, session_id: sessionId };
37
- const encoded = encodeGlmRequestWithCache(requestWithSession);
40
+ const stablePrefixDigest = crypto.createHash('sha256').update(STABLE_SYSTEM_PREFIX).digest('hex');
41
+ const shardSuffixDigest = crypto.createHash('sha256').update(shardSuffix).digest('hex');
42
+ const encoded = encodeGlmRequestWithCache({
43
+ request: requestWithSession,
44
+ cacheKeyParts: {
45
+ model: requestWithSession.model,
46
+ profile: 'glm-naruto-worker-speed',
47
+ stable_prefix_digest: stablePrefixDigest,
48
+ shard_suffix_digest: shardSuffixDigest,
49
+ tools_digest: requestWithSession.tools ? crypto.createHash('sha256').update(JSON.stringify(requestWithSession.tools)).digest('hex') : null,
50
+ response_format_digest: requestWithSession.response_format ? crypto.createHash('sha256').update(JSON.stringify(requestWithSession.response_format)).digest('hex') : null,
51
+ provider_digest: crypto.createHash('sha256').update(JSON.stringify(requestWithSession.provider ?? null)).digest('hex'),
52
+ session_id: sessionId
53
+ }
54
+ });
55
+ await writeGlmNarutoWorkerArtifacts({
56
+ root: artifactRoot,
57
+ missionId: input.missionId,
58
+ workerId: input.workerId,
59
+ shardId: input.shard.id,
60
+ requestSummary: {
61
+ model: requestWithSession.model,
62
+ provider: 'openrouter',
63
+ session_id: sessionId,
64
+ request_body_sha256: encoded.entry.bodySha256,
65
+ request_body_size: encoded.entry.byteLength,
66
+ request_body_stored: encoded.entry.bodyStored,
67
+ cache_hit: encoded.cacheHit,
68
+ stable_prefix_digest: stablePrefixDigest,
69
+ shard_suffix_digest: shardSuffixDigest
70
+ }
71
+ }).catch(() => undefined);
38
72
  const traceBase = {
39
73
  worker_id: input.workerId,
40
74
  shard_id: input.shard.id,
@@ -54,35 +88,63 @@ export async function runPatchWorker(input) {
54
88
  apiKey: input.apiKey,
55
89
  request: requestWithSession,
56
90
  timeoutMs: input.timeoutMs,
57
- idleTimeoutMs: 60_000
91
+ idleTimeoutMs: 60_000,
92
+ ...(input.fetchImpl ? { fetchImpl: input.fetchImpl } : {})
58
93
  });
59
94
  if (!response.ok) {
95
+ const trace = { ...traceBase, total_ms: Date.now() - started, status: 'failed' };
96
+ await writeGlmNarutoWorkerArtifacts({
97
+ root: artifactRoot,
98
+ missionId: input.missionId,
99
+ workerId: input.workerId,
100
+ shardId: input.shard.id,
101
+ streamTrace: trace,
102
+ termination: { status: 'failed', ok: false, error: response.error.code }
103
+ }).catch(() => undefined);
60
104
  return {
61
105
  envelope: null,
62
- trace: { ...traceBase, total_ms: Date.now() - started, status: 'failed' },
106
+ trace,
63
107
  ok: false,
64
108
  error: response.error.code
65
109
  };
66
110
  }
67
111
  const modelGuard = assertGlm52ActualModel(response.value.model);
68
112
  if (!modelGuard.ok) {
113
+ const trace = { ...traceBase, total_ms: Date.now() - started, status: 'blocked' };
114
+ await writeGlmNarutoWorkerArtifacts({
115
+ root: artifactRoot,
116
+ missionId: input.missionId,
117
+ workerId: input.workerId,
118
+ shardId: input.shard.id,
119
+ streamTrace: trace,
120
+ termination: { status: 'blocked', ok: false, error: `model_guard:${modelGuard.code}` }
121
+ }).catch(() => undefined);
69
122
  return {
70
123
  envelope: null,
71
- trace: { ...traceBase, total_ms: Date.now() - started, status: 'blocked' },
124
+ trace,
72
125
  ok: false,
73
126
  error: `model_guard:${modelGuard.code}`
74
127
  };
75
128
  }
76
129
  const parsed = parsePatchCandidateOutput(response.value.content);
77
130
  if (parsed.kind !== 'patch') {
131
+ const trace = {
132
+ ...traceBase,
133
+ ttft_ms: response.value.ttft_ms,
134
+ total_ms: Date.now() - started,
135
+ status: parsed.kind === 'blocked' ? 'blocked' : 'no_patch'
136
+ };
137
+ await writeGlmNarutoWorkerArtifacts({
138
+ root: artifactRoot,
139
+ missionId: input.missionId,
140
+ workerId: input.workerId,
141
+ shardId: input.shard.id,
142
+ streamTrace: trace,
143
+ termination: { status: trace.status, ok: false, error: parsed.kind }
144
+ }).catch(() => undefined);
78
145
  return {
79
146
  envelope: null,
80
- trace: {
81
- ...traceBase,
82
- ttft_ms: response.value.ttft_ms,
83
- total_ms: Date.now() - started,
84
- status: parsed.kind === 'blocked' ? 'blocked' : 'no_patch'
85
- },
147
+ trace,
86
148
  ok: false,
87
149
  error: parsed.kind
88
150
  };
@@ -103,12 +165,38 @@ export async function runPatchWorker(input) {
103
165
  patch_digest: digestPatch(envelope.patch),
104
166
  status: 'completed'
105
167
  };
168
+ await writeGlmNarutoWorkerArtifacts({
169
+ root: artifactRoot,
170
+ missionId: input.missionId,
171
+ workerId: input.workerId,
172
+ shardId: input.shard.id,
173
+ streamTrace: trace,
174
+ patchEnvelope: envelope,
175
+ gateResult: {
176
+ schema: 'sks.glm-naruto-worker-gate-result.v1',
177
+ worker_id: input.workerId,
178
+ shard_id: input.shard.id,
179
+ status: 'pending_orchestrator_gate',
180
+ ok: false,
181
+ reason: 'deterministic_gate_runs_in_orchestrator_with_repo_cwd'
182
+ },
183
+ termination: { status: 'completed', ok: true }
184
+ }).catch(() => undefined);
106
185
  return { envelope, trace, ok: true };
107
186
  }
108
187
  catch (err) {
188
+ const trace = { ...traceBase, total_ms: Date.now() - started, status: 'failed' };
189
+ await writeGlmNarutoWorkerArtifacts({
190
+ root: artifactRoot,
191
+ missionId: input.missionId,
192
+ workerId: input.workerId,
193
+ shardId: input.shard.id,
194
+ streamTrace: trace,
195
+ termination: { status: 'failed', ok: false, error: err instanceof Error ? err.message : String(err) }
196
+ }).catch(() => undefined);
109
197
  return {
110
198
  envelope: null,
111
- trace: { ...traceBase, total_ms: Date.now() - started, status: 'failed' },
199
+ trace,
112
200
  ok: false,
113
201
  error: err instanceof Error ? err.message : String(err)
114
202
  };
@@ -118,7 +206,7 @@ export async function runVerifierWorker(input) {
118
206
  const started = Date.now();
119
207
  const sessionId = `sks-glm-naruto-verify-${input.missionId}-${input.workerId}`;
120
208
  const messages = [
121
- { role: 'system', content: `You are a SKS GLM Naruto verifier. Model: ${GLM_52_OPENROUTER_MODEL}. No GPT fallback. Check if the patch is correct and safe. Output JSON: {"ok":true/false,"issues":["..."]}` },
209
+ { role: 'system', content: `You are a SKS GLM Naruto verifier. Model: ${GLM_52_OPENROUTER_MODEL}. No GPT fallback. Return only JSON with schema "sks.glm-naruto-verifier-output.v1", ok boolean, issues string array, risk_score number 0..1, confidence number 0..1. Do not include markdown.` },
122
210
  { role: 'user', content: JSON.stringify({ patch_sha256: input.envelope.patch_sha256, target_paths: input.envelope.target_paths, patch: input.envelope.patch.slice(0, 4000) }) }
123
211
  ];
124
212
  const request = buildGlm52Request({
@@ -126,14 +214,16 @@ export async function runVerifierWorker(input) {
126
214
  messages,
127
215
  maxTokens: 2048,
128
216
  toolChoice: 'none',
129
- parallelToolCalls: false
217
+ parallelToolCalls: false,
218
+ providerSort: 'throughput'
130
219
  });
131
220
  try {
132
221
  const response = await sendOpenRouterChatCompletionStream({
133
222
  apiKey: input.apiKey,
134
223
  request: { ...request, session_id: sessionId },
135
224
  timeoutMs: input.timeoutMs,
136
- idleTimeoutMs: 60_000
225
+ idleTimeoutMs: 60_000,
226
+ ...(input.fetchImpl ? { fetchImpl: input.fetchImpl } : {})
137
227
  });
138
228
  if (!response.ok) {
139
229
  return {
@@ -155,6 +245,68 @@ export async function runVerifierWorker(input) {
155
245
  issues: [response.error.code]
156
246
  };
157
247
  }
248
+ const modelGuard = assertGlm52ActualModel(response.value.model);
249
+ if (!modelGuard.ok) {
250
+ return {
251
+ ok: false,
252
+ trace: {
253
+ worker_id: input.workerId,
254
+ shard_id: input.envelope.shard_id,
255
+ strategy: 'minimal_patch',
256
+ model: GLM_52_OPENROUTER_MODEL,
257
+ provider: 'openrouter',
258
+ session_id: sessionId,
259
+ ttft_ms: response.value.ttft_ms,
260
+ total_ms: Date.now() - started,
261
+ request_cache_hit: false,
262
+ output_digest: crypto.createHash('sha256').update(response.value.content).digest('hex'),
263
+ patch_digest: input.envelope.patch_sha256,
264
+ status: 'verification_failed'
265
+ },
266
+ issues: [`model_guard:${modelGuard.code}`]
267
+ };
268
+ }
269
+ const parsed = parseGlmNarutoVerifierOutput(response.value.content);
270
+ if (!parsed.ok || !parsed.output) {
271
+ return {
272
+ ok: false,
273
+ trace: {
274
+ worker_id: input.workerId,
275
+ shard_id: input.envelope.shard_id,
276
+ strategy: 'minimal_patch',
277
+ model: GLM_52_OPENROUTER_MODEL,
278
+ provider: 'openrouter',
279
+ session_id: sessionId,
280
+ ttft_ms: response.value.ttft_ms,
281
+ total_ms: Date.now() - started,
282
+ request_cache_hit: false,
283
+ output_digest: crypto.createHash('sha256').update(response.value.content).digest('hex'),
284
+ patch_digest: input.envelope.patch_sha256,
285
+ status: 'verification_failed'
286
+ },
287
+ issues: parsed.issues
288
+ };
289
+ }
290
+ if (!parsed.output.ok) {
291
+ return {
292
+ ok: false,
293
+ trace: {
294
+ worker_id: input.workerId,
295
+ shard_id: input.envelope.shard_id,
296
+ strategy: 'minimal_patch',
297
+ model: GLM_52_OPENROUTER_MODEL,
298
+ provider: 'openrouter',
299
+ session_id: sessionId,
300
+ ttft_ms: response.value.ttft_ms,
301
+ total_ms: Date.now() - started,
302
+ request_cache_hit: false,
303
+ output_digest: crypto.createHash('sha256').update(response.value.content).digest('hex'),
304
+ patch_digest: input.envelope.patch_sha256,
305
+ status: 'verification_failed'
306
+ },
307
+ issues: parsed.output.issues
308
+ };
309
+ }
158
310
  return {
159
311
  ok: true,
160
312
  trace: {
@@ -169,7 +321,7 @@ export async function runVerifierWorker(input) {
169
321
  request_cache_hit: false,
170
322
  output_digest: crypto.createHash('sha256').update(response.value.content).digest('hex'),
171
323
  patch_digest: input.envelope.patch_sha256,
172
- status: 'completed'
324
+ status: 'verification_passed'
173
325
  },
174
326
  issues: []
175
327
  };
@@ -2,6 +2,15 @@ import { redactOpenRouterString } from '../../security/redact-secrets.js';
2
2
  import { OPENROUTER_CHAT_COMPLETIONS_URL } from './openrouter-types.js';
3
3
  import { normalizeOpenRouterError } from './openrouter-error.js';
4
4
  import { encodeGlmRequestWithCache } from '../glm/glm-request-cache.js';
5
+ export class GlmStreamIdleTimeout extends Error {
6
+ code;
7
+ constructor(afterTtft) {
8
+ const code = afterTtft ? 'glm_stream_idle_timeout_after_ttft' : 'glm_stream_idle_timeout';
9
+ super(code);
10
+ this.name = 'GlmStreamIdleTimeout';
11
+ this.code = code;
12
+ }
13
+ }
5
14
  export async function sendOpenRouterChatCompletionStream(input) {
6
15
  const started = Date.now();
7
16
  const controller = input.timeoutMs ? new AbortController() : null;
@@ -37,12 +46,13 @@ export async function sendOpenRouterChatCompletionStream(input) {
37
46
  catch (err) {
38
47
  if (timeout)
39
48
  clearTimeout(timeout);
40
- if (err instanceof Error && (err.name === 'AbortError' || err.message === 'glm_stream_idle_timeout' || err.message === 'glm_stream_idle_timeout_after_ttft')) {
41
- const isIdle = err.message.startsWith('glm_stream_idle');
49
+ if (err instanceof Error && (err.name === 'AbortError' || err instanceof GlmStreamIdleTimeout || err.message === 'glm_stream_idle_timeout' || err.message === 'glm_stream_idle_timeout_after_ttft')) {
50
+ const code = err instanceof GlmStreamIdleTimeout ? err.code : err.message;
51
+ const isIdle = code.startsWith('glm_stream_idle');
42
52
  return {
43
53
  ok: false,
44
54
  error: {
45
- code: isIdle ? err.message : 'glm_request_timeout',
55
+ code: isIdle ? code : 'glm_request_timeout',
46
56
  message: isIdle ? `OpenRouter stream idle timeout after ${input.idleTimeoutMs || 0}ms.` : `OpenRouter stream aborted after ${input.timeoutMs || 'external'}ms.`,
47
57
  severity: 'failed'
48
58
  }
@@ -71,18 +81,23 @@ async function readRealStream(body, startedAtMs, idleTimeoutMs) {
71
81
  let lastChunkMs = startedAtMs;
72
82
  try {
73
83
  while (true) {
74
- // 4.0.9: Idle timeout between chunks — abort if stream stalls.
75
84
  const readPromise = reader.read();
76
85
  let idleTimer = null;
77
- if (idleTimeoutMs && idleTimeoutMs > 0) {
78
- idleTimer = setTimeout(() => {
79
- const code = ttft === null ? 'glm_stream_idle_timeout' : 'glm_stream_idle_timeout_after_ttft';
80
- reader.cancel(code).catch(() => undefined);
81
- }, idleTimeoutMs);
82
- }
83
86
  let result;
84
87
  try {
85
- result = await readPromise;
88
+ result = idleTimeoutMs && idleTimeoutMs > 0
89
+ ? await Promise.race([
90
+ readPromise,
91
+ new Promise((_, reject) => {
92
+ idleTimer = setTimeout(() => reject(new GlmStreamIdleTimeout(ttft !== null)), idleTimeoutMs);
93
+ })
94
+ ])
95
+ : await readPromise;
96
+ }
97
+ catch (err) {
98
+ if (err instanceof GlmStreamIdleTimeout)
99
+ await reader.cancel(err.code).catch(() => undefined);
100
+ throw err;
86
101
  }
87
102
  finally {
88
103
  if (idleTimer)
@@ -86,6 +86,7 @@ export async function checkStopGate(input) {
86
86
  missing_fields: [],
87
87
  blockers: [],
88
88
  };
89
+ await writeDiagnostics(root, missionId, diagnostics);
89
90
  return {
90
91
  schema: 'sks.stop-gate-check.v1',
91
92
  ok: true,
@@ -115,6 +116,7 @@ export async function checkStopGate(input) {
115
116
  missing_fields: [],
116
117
  blockers: [],
117
118
  };
119
+ await writeDiagnostics(root, missionId, diagnostics);
118
120
  return {
119
121
  schema: 'sks.stop-gate-check.v1',
120
122
  ok: false,
@@ -129,9 +131,19 @@ export async function checkStopGate(input) {
129
131
  const missingFields = [];
130
132
  if (normalizedGate.passed !== true)
131
133
  missingFields.push('passed');
132
- if (!normalizedGate.terminal)
134
+ if (normalizedGate.terminal !== true)
133
135
  missingFields.push('terminal');
134
- if (normalizedGate.passed === true && missingFields.length === 0) {
136
+ if (normalizedGate.status !== 'passed')
137
+ missingFields.push('status');
138
+ if (normalizedGate.blockers.length > 0)
139
+ missingFields.push('blockers');
140
+ if (normalizedGate.missing_fields.length > 0)
141
+ missingFields.push(...normalizedGate.missing_fields.map((field) => `missing_fields:${field}`));
142
+ if (normalizedGate.passed === true
143
+ && normalizedGate.terminal === true
144
+ && normalizedGate.status === 'passed'
145
+ && normalizedGate.blockers.length === 0
146
+ && normalizedGate.missing_fields.length === 0) {
135
147
  const action = 'allow_stop';
136
148
  const diagnostics = {
137
149
  schema: 'sks.stop-gate-diagnostics.v1',
@@ -5,7 +5,11 @@ import { setCurrent } from '../mission.js';
5
5
  export async function writeFinalStopGate(input) {
6
6
  const dir = missionDir(input.root, input.missionId);
7
7
  await ensureDir(dir);
8
- const passed = input.status === 'passed';
8
+ const evidenceBlockers = input.status === 'passed' ? evidenceMissingBlockers(input.evidence) : [];
9
+ const status = evidenceBlockers.length ? 'blocked' : input.status;
10
+ const blockers = [...(input.blockers ?? []), ...evidenceBlockers];
11
+ const missingFields = [...(input.missingFields ?? []), ...evidenceBlockers.map((blocker) => `evidence:${blocker}`)];
12
+ const passed = status === 'passed';
9
13
  const nativeGateFile = input.nativeGateFile ?? 'naruto-gate.json';
10
14
  const nativeGatePath = path.join(dir, nativeGateFile);
11
15
  const canonicalGatePath = path.join(dir, 'stop-gate.json');
@@ -18,17 +22,55 @@ export async function writeFinalStopGate(input) {
18
22
  mission_id: input.missionId,
19
23
  gate_file: nativeGateFile,
20
24
  gate_abs_path: canonicalGatePath,
21
- status: input.status,
25
+ status,
22
26
  passed,
23
- terminal: input.terminal,
24
- terminal_state: input.terminalState,
27
+ terminal: evidenceBlockers.length ? false : input.terminal,
28
+ terminal_state: evidenceBlockers.length ? 'blocked' : input.terminalState,
25
29
  evidence: input.evidence,
26
- blockers: input.blockers ?? [],
27
- missing_fields: input.missingFields ?? [],
30
+ blockers,
31
+ missing_fields: missingFields,
28
32
  created_at: nowIso(),
29
33
  };
30
- // 1. Write route-native gate file (backwards compat)
31
- await writeJsonAtomic(nativeGatePath, { ...gate, schema: 'sks.naruto-gate' });
34
+ // 1. Write route-native gate file (backwards compat), preserving detailed native fields by default.
35
+ const preserveNativeGate = input.preserveNativeGate !== false;
36
+ if (preserveNativeGate && await exists(nativeGatePath)) {
37
+ const existing = await readJson(nativeGatePath, {});
38
+ await writeJsonAtomic(nativeGatePath, {
39
+ ...existing,
40
+ ...(input.nativeGatePatch ?? {}),
41
+ route: input.route,
42
+ route_command: input.routeCommand,
43
+ mission_id: input.missionId,
44
+ status,
45
+ passed,
46
+ terminal: gate.terminal,
47
+ terminal_state: gate.terminal_state,
48
+ evidence: {
49
+ ...(existing.evidence || {}),
50
+ ...input.evidence
51
+ },
52
+ blockers,
53
+ missing_fields: missingFields,
54
+ updated_at: nowIso()
55
+ });
56
+ }
57
+ else {
58
+ await writeJsonAtomic(nativeGatePath, {
59
+ schema: nativeGateFile === 'termination.json' ? 'sks.glm-naruto-termination.v1' : 'sks.naruto-gate.v1',
60
+ route: input.route,
61
+ route_command: input.routeCommand,
62
+ mission_id: input.missionId,
63
+ status,
64
+ passed,
65
+ terminal: gate.terminal,
66
+ terminal_state: gate.terminal_state,
67
+ evidence: input.evidence,
68
+ blockers,
69
+ missing_fields: missingFields,
70
+ updated_at: nowIso(),
71
+ ...(input.nativeGatePatch ?? {})
72
+ });
73
+ }
32
74
  // 2. Write canonical stop-gate.json
33
75
  await writeJsonAtomic(canonicalGatePath, gate);
34
76
  // 3. Write stop-gate.latest.json
@@ -41,11 +83,11 @@ export async function writeFinalStopGate(input) {
41
83
  mode: input.route === 'GLM_NARUTO' ? 'NARUTO' : (input.route === 'Naruto' ? 'NARUTO' : input.route),
42
84
  stop_gate: 'stop-gate.json',
43
85
  stop_gate_abs_path: canonicalGatePath,
44
- stop_gate_status: input.status,
86
+ stop_gate_status: status,
45
87
  stop_gate_passed: passed,
46
88
  route_evidence_passed: input.evidence.route_evidence_passed ?? passed,
47
- terminal: input.terminal,
48
- terminal_state: input.terminalState,
89
+ terminal: gate.terminal,
90
+ terminal_state: gate.terminal_state,
49
91
  });
50
92
  // 5. Re-read and verify
51
93
  const verifyResult = {
@@ -73,4 +115,12 @@ export async function writeFinalStopGate(input) {
73
115
  await writeJsonAtomic(verifyPath, verifyResult);
74
116
  return gate;
75
117
  }
118
+ function evidenceMissingBlockers(evidence) {
119
+ const blockers = [];
120
+ if (evidence.proof_required === true && evidence.proof_passed !== true)
121
+ blockers.push('proof_not_passed');
122
+ if (evidence.reflection_required === true && evidence.reflection_passed !== true && evidence.reflection_passed !== 'not_required')
123
+ blockers.push('reflection_not_passed');
124
+ return blockers;
125
+ }
76
126
  //# sourceMappingURL=stop-gate-writer.js.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sneakoscope",
3
3
  "displayName": "ㅅㅋㅅ",
4
- "version": "4.0.9",
4
+ "version": "4.0.10",
5
5
  "description": "Sneakoscope Codex: fast proof-first Codex trust layer with image-based Voxel TriWiki.",
6
6
  "type": "module",
7
7
  "homepage": "https://github.com/mandarange/Sneakoscope-Codex#readme",