open-multi-agent-kit 0.79.3 → 0.80.2
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/CHANGELOG.md +38 -0
- package/MATURITY.md +2 -2
- package/README.md +86 -39
- package/ROADMAP.md +20 -17
- package/dist/brand/night-city.theme.json +122 -24
- package/dist/brand/rust-forge.theme.json +1 -1
- package/dist/commands/chat/native-root-loop.d.ts +27 -3
- package/dist/commands/chat/native-root-loop.js +370 -49
- package/dist/commands/dag.js +45 -0
- package/dist/commands/init/content.d.ts +1 -1
- package/dist/commands/init/content.js +3 -3
- package/dist/commands/init.js +3 -3
- package/dist/commands/verify.js +59 -1
- package/dist/contracts/dag.d.ts +9 -1
- package/dist/evidence/graph-ontology-relation-gate.d.ts +10 -0
- package/dist/evidence/graph-ontology-relation-gate.js +46 -0
- package/dist/evidence/headroom-replay-verifier.d.ts +19 -0
- package/dist/evidence/headroom-replay-verifier.js +86 -0
- package/dist/memory/local-graph-memory-store.d.ts +75 -0
- package/dist/memory/local-graph-memory-store.js +570 -0
- package/dist/orchestration/dag.d.ts +22 -1
- package/dist/orchestration/evidence-gate.js +12 -0
- package/dist/orchestration/executor.js +111 -15
- package/dist/orchestration/loop-controller-headroom.d.ts +6 -0
- package/dist/orchestration/loop-controller-headroom.js +33 -0
- package/dist/orchestration/scheduler.d.ts +2 -0
- package/dist/orchestration/scheduler.js +3 -0
- package/dist/orchestration/task-graph.d.ts +17 -0
- package/dist/orchestration/task-graph.js +38 -0
- package/dist/providers/codex-cli-runner.js +17 -2
- package/dist/runtime/agent-runtime.d.ts +32 -3
- package/dist/runtime/agent-runtime.js +20 -7
- package/dist/runtime/authority-matrix.d.ts +23 -0
- package/dist/runtime/authority-matrix.js +171 -0
- package/dist/runtime/chat-advisory-runtime.js +3 -0
- package/dist/runtime/codex-cli-runtime.js +2 -0
- package/dist/runtime/codex-runtime.d.ts +4 -1
- package/dist/runtime/codex-runtime.js +34 -12
- package/dist/runtime/context-broker-converter.js +32 -2
- package/dist/runtime/context-broker.js +18 -0
- package/dist/runtime/contracts/evidence.d.ts +52 -0
- package/dist/runtime/contracts/evidence.js +138 -1
- package/dist/runtime/contracts/shared.d.ts +27 -0
- package/dist/runtime/data-retention-gate.d.ts +42 -0
- package/dist/runtime/data-retention-gate.js +123 -0
- package/dist/runtime/deepseek-runtime.d.ts +5 -1
- package/dist/runtime/deepseek-runtime.js +13 -7
- package/dist/runtime/external-cli-adapter.js +20 -7
- package/dist/runtime/freedomd-evidence-envelope.d.ts +57 -0
- package/dist/runtime/freedomd-evidence-envelope.js +110 -0
- package/dist/runtime/freedomd-exception.d.ts +32 -0
- package/dist/runtime/freedomd-exception.js +95 -0
- package/dist/runtime/freedomd-incidents.d.ts +23 -0
- package/dist/runtime/freedomd-incidents.js +103 -0
- package/dist/runtime/freedomd-policy.d.ts +35 -0
- package/dist/runtime/freedomd-policy.js +114 -0
- package/dist/runtime/freedomd-router.d.ts +66 -0
- package/dist/runtime/freedomd-router.js +433 -0
- package/dist/runtime/headroom-aware-loop-decision.d.ts +21 -0
- package/dist/runtime/headroom-aware-loop-decision.js +67 -0
- package/dist/runtime/headroom-loop-risk.d.ts +19 -0
- package/dist/runtime/headroom-loop-risk.js +43 -0
- package/dist/runtime/headroom-policy.d.ts +14 -1
- package/dist/runtime/headroom-policy.js +74 -12
- package/dist/runtime/kimi-api-runtime.d.ts +4 -1
- package/dist/runtime/kimi-api-runtime.js +35 -11
- package/dist/runtime/kimi-print-runtime.js +21 -0
- package/dist/runtime/kimi-wire-protocol-runtime.js +17 -1
- package/dist/runtime/local-llm-runtime.d.ts +5 -1
- package/dist/runtime/local-llm-runtime.js +14 -23
- package/dist/runtime/private-stderr.d.ts +13 -0
- package/dist/runtime/private-stderr.js +69 -0
- package/dist/runtime/prompt-envelope.d.ts +1 -1
- package/dist/runtime/provider-sovereignty.d.ts +81 -0
- package/dist/runtime/provider-sovereignty.js +285 -0
- package/dist/runtime/runtime-backed-task-runner.d.ts +1 -0
- package/dist/runtime/runtime-backed-task-runner.js +282 -10
- package/dist/runtime/runtime-bootstrap.d.ts +12 -0
- package/dist/runtime/runtime-bootstrap.js +54 -11
- package/dist/runtime/runtime-failure-classifier.d.ts +15 -0
- package/dist/runtime/runtime-failure-classifier.js +107 -0
- package/dist/runtime/runtime-health-probes.d.ts +30 -0
- package/dist/runtime/runtime-health-probes.js +102 -0
- package/dist/runtime/runtime-router.d.ts +6 -2
- package/dist/runtime/runtime-router.js +575 -53
- package/dist/runtime/structured-compaction.d.ts +107 -0
- package/dist/runtime/structured-compaction.js +414 -0
- package/dist/runtime/tool-dispatch-contracts.d.ts +7 -5
- package/dist/runtime/tool-dispatch-contracts.js +15 -4
- package/dist/runtime/tool-plane.d.ts +1 -1
- package/dist/runtime/tool-plane.js +49 -1
- package/dist/runtime/worker-manifest.js +1 -1
- package/dist/schema/evidence-bundle.schema.d.ts +8 -8
- package/dist/schema/proof-bundle.schema.d.ts +2 -2
- package/dist/util/hash.d.ts +2 -0
- package/dist/util/hash.js +13 -0
- package/dist/util/session.js +5 -4
- package/docs/2026-06-16/critical-issues.md +19 -0
- package/docs/2026-06-16/improvements.md +15 -0
- package/docs/2026-06-16/init-checklist.md +25 -0
- package/docs/2026-06-16/plan.md +20 -0
- package/docs/2026-06-17/critical-issues.md +19 -0
- package/docs/2026-06-17/improvements.md +15 -0
- package/docs/2026-06-17/init-checklist.md +25 -0
- package/docs/2026-06-17/plan.md +20 -0
- package/docs/ARCHITECTURE_ANALYSIS_CODEGRAPH.md +2 -1
- package/docs/GSTACK_MIGRATION.md +1 -1
- package/docs/algorithm-hardening-playbook.md +219 -0
- package/docs/claims.md +1 -1
- package/docs/getting-started.md +1 -1
- package/docs/native-root-runtime-hardening.md +42 -32
- package/docs/post-0793-hardening-roadmap.md +123 -0
- package/docs/provider-maturity.md +18 -16
- package/docs/versioning.md +3 -3
- package/docs/what-is-omk.md +1 -1
- package/package.json +8 -4
- package/readmeasset/.npmignore +2 -0
- package/readmeasset/ASSET_INDEX.md +2 -0
- package/readmeasset/ASSET_PROVENANCE.md +40 -20
- package/readmeasset/omk-adaptorch-ouroboros-supermemory.svg +1 -1
- package/readmeasset/omk-control-surfaces.svg +1 -1
- package/readmeasset/omk-core-loop.svg +1 -1
- package/readmeasset/omk-freedomd-control-plane.svg +59 -0
- package/readmeasset/omk-freedomd-control-plane.webp +0 -0
- package/readmeasset/omk-init-control-loop.svg +1 -1
- package/readmeasset/omk-logo-mark.svg +1 -1
- package/readmeasset/omk-parallel-subagents.svg +1 -1
- package/readmeasset/omk-release-assertions.svg +1 -1
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import { style } from "../../util/theme.js";
|
|
2
|
+
import { getRunArtifactPath } from "../../util/run-store.js";
|
|
2
3
|
import { runShell } from "../../util/shell.js";
|
|
4
|
+
import { mkdir, writeFile, appendFile, readFile } from "node:fs/promises";
|
|
5
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
6
|
+
import { createHash } from "node:crypto";
|
|
7
|
+
import { dirname } from "node:path";
|
|
3
8
|
import { createDag } from "../../orchestration/dag.js";
|
|
4
9
|
import { applyCapabilityInjectionToRouting, buildCapabilityInjection, } from "../../runtime/capability-injection.js";
|
|
5
10
|
import { capabilityScopesFromRouting } from "../../orchestration/capability-routing.js";
|
|
6
11
|
import { decideToolAuthority, } from "../../safety/tool-authority-gate.js";
|
|
7
|
-
import {
|
|
12
|
+
import { resolveToolAuthorityMode } from "../../runtime/tool-dispatch-contracts.js";
|
|
8
13
|
import { compileBloatToNlp, } from "../../runtime/debloat-nlp.js";
|
|
9
14
|
import { buildPromptEnvelope, renderPromptEnvelope, } from "../../runtime/prompt-envelope.js";
|
|
10
15
|
import { buildTaskRunContext } from "../../runtime/worker-manifest.js";
|
|
@@ -12,6 +17,7 @@ import { buildInputEnvelope, normalizeMcpScope, } from "../../input/input-envelo
|
|
|
12
17
|
import { persistInputEnvelope } from "../../input/input-artifacts.js";
|
|
13
18
|
import { buildDagCompileResult } from "../../orchestration/dag-compiler.js";
|
|
14
19
|
import { persistDagCompileArtifacts } from "../../orchestration/dag-artifacts.js";
|
|
20
|
+
import { evidenceObservationsFromResult, evidenceRequirementsFromOutputs } from "../../runtime/contracts/evidence.js";
|
|
15
21
|
import { TerminalOwner } from "../../util/terminal-owner.js";
|
|
16
22
|
import { resumeInteractiveInput } from "../../util/terminal-input.js";
|
|
17
23
|
import { executeHarnessRun } from "../../harness/execute-harness-run.js";
|
|
@@ -25,6 +31,8 @@ import { buildNativeChatSlashCommands } from "./slash/commands/index.js";
|
|
|
25
31
|
import { parseSlashInput } from "./slash/parser.js";
|
|
26
32
|
import { createSlashCommandRegistry } from "./slash/registry.js";
|
|
27
33
|
import { emitSlashResult, okSlashResult, printSlashResult, } from "./slash/result.js";
|
|
34
|
+
import { LocalGraphMemoryStore } from "../../memory/local-graph-memory-store.js";
|
|
35
|
+
import { runtimeIsAdvisory } from "../../runtime/authority-matrix.js";
|
|
28
36
|
async function runSlashHandler(handler, parsed, ctx, options = {}) {
|
|
29
37
|
const result = await handler.handler(ctx, parsed.args);
|
|
30
38
|
const normalized = result ?? okSlashResult();
|
|
@@ -317,22 +325,15 @@ function nativeTurnRiskToToolOp(risk) {
|
|
|
317
325
|
}
|
|
318
326
|
}
|
|
319
327
|
/**
|
|
320
|
-
*
|
|
321
|
-
*
|
|
322
|
-
*
|
|
323
|
-
* record the turn-level verdict for the trace. Default output is byte-identical
|
|
324
|
-
* (emitted only under OMK_DEBUG / OMK_TOOL_AUTHORITY_TRACE); this path never
|
|
325
|
-
* blocks dispatch.
|
|
328
|
+
* Turn-level tool-authority checkpoint. Shadow mode records only when tracing is
|
|
329
|
+
* enabled, warn mode emits a diagnostic, and enforce mode fail-closes block/ask
|
|
330
|
+
* verdicts before dispatching the native turn.
|
|
326
331
|
*/
|
|
327
332
|
function recordNativeTurnToolAuthority(input) {
|
|
328
|
-
const traceEnabled = input.env.OMK_DEBUG === "1" ||
|
|
329
|
-
/^(1|true|yes|on)$/i.test(input.env.OMK_TOOL_AUTHORITY_TRACE ?? "");
|
|
330
|
-
if (!traceEnabled)
|
|
331
|
-
return;
|
|
332
333
|
const routing = input.node.routing;
|
|
333
334
|
const scopes = capabilityScopesFromRouting(routing);
|
|
334
335
|
const op = nativeTurnRiskToToolOp(routing?.risk);
|
|
335
|
-
const
|
|
336
|
+
const mode = resolveToolAuthorityMode(input.env);
|
|
336
337
|
const decision = decideToolAuthority({
|
|
337
338
|
op,
|
|
338
339
|
writeAuthority: scopes.writeAuthority,
|
|
@@ -341,15 +342,23 @@ function recordNativeTurnToolAuthority(input) {
|
|
|
341
342
|
sandboxMode: routing?.sandboxMode === "read-only" ? "read-only" : "workspace-write",
|
|
342
343
|
tty: Boolean(process.stdout.isTTY),
|
|
343
344
|
});
|
|
344
|
-
const
|
|
345
|
-
|
|
346
|
-
`
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
345
|
+
const enforced = mode === "enforce" && decision !== "allow";
|
|
346
|
+
const reason = `tool-authority ${decision} for ${op} op ` +
|
|
347
|
+
`(write=${scopes.writeAuthority}, shell=${scopes.shellAuthority}, ` +
|
|
348
|
+
`sandbox=${routing?.sandboxMode ?? "auto"}, mode=${mode})`;
|
|
349
|
+
const traceEnabled = mode !== "shadow" ||
|
|
350
|
+
input.env.OMK_DEBUG === "1" ||
|
|
351
|
+
/^(1|true|yes|on)$/i.test(input.env.OMK_TOOL_AUTHORITY_TRACE ?? "");
|
|
352
|
+
if (traceEnabled) {
|
|
353
|
+
const line = ` ${reason}${enforced ? " [enforced]" : ""}\n`;
|
|
354
|
+
if (input.renderer) {
|
|
355
|
+
input.renderer.emit({ type: "control:output", text: style.phosphorDim(line) });
|
|
356
|
+
}
|
|
357
|
+
else {
|
|
358
|
+
process.stderr.write(style.phosphorDim(line));
|
|
359
|
+
}
|
|
352
360
|
}
|
|
361
|
+
return { mode, op, decision, enforced, reason };
|
|
353
362
|
}
|
|
354
363
|
function emitNativeTurnRoute(input) {
|
|
355
364
|
if (!input.heartbeatEnabled)
|
|
@@ -449,30 +458,96 @@ async function persistNativeDagCompileArtifacts(input) {
|
|
|
449
458
|
process.stderr.write(style.metricsRed(text) + "\n");
|
|
450
459
|
}
|
|
451
460
|
}
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
"openrouter",
|
|
458
|
-
"qwen",
|
|
459
|
-
]);
|
|
460
|
-
function hasExplicitReadOnlyIntent(text) {
|
|
461
|
+
function hasExplicitReadOnlyConstraint(text) {
|
|
462
|
+
return (/\b(without changing|without editing|do not change|don't change|do not edit|don't edit|no edits?|no file changes?|read[- ]only)\b/.test(text) ||
|
|
463
|
+
/읽기\s*전용|수정하지\s*말|변경하지\s*말|파일\s*(수정|변경)\s*(없이|하지\s*말)|검토만|분석만/.test(text));
|
|
464
|
+
}
|
|
465
|
+
function hasReadOnlyIntent(text) {
|
|
461
466
|
return (/\b(read|inspect|look|show|list|summarize|explain|describe|review|audit|status|diagnose)\b/.test(text) ||
|
|
462
|
-
|
|
463
|
-
|
|
467
|
+
/요약|설명|상태|검토|분석|읽어|살펴/.test(text));
|
|
468
|
+
}
|
|
469
|
+
function detectExcludedOps(text) {
|
|
470
|
+
const excluded = new Set();
|
|
471
|
+
const scopes = [];
|
|
472
|
+
const add = (op, scope) => {
|
|
473
|
+
excluded.add(op);
|
|
474
|
+
scopes.push(scope);
|
|
475
|
+
};
|
|
476
|
+
const mergeTerms = "push|publish|release|merge|tag|deploy|version|npm publish|릴리즈|머지|배포|태그|푸시|버전|퍼블리시";
|
|
477
|
+
const shellTerms = "run|test|build|exec|execute|shell|terminal|command|npm|pnpm|yarn|bun|pytest|cargo|tsc|lint|verify|check|테스트|빌드|실행|검증|쉘|터미널|체크|명령";
|
|
478
|
+
const writeTerms = "fix|edit|write|implement|modify|patch|refactor|add|create|delete|update|change|수정|구현|패치|리팩터|추가|삭제|변경";
|
|
479
|
+
const englishNegationPrefix = "no|not|without|excluding|exclude|except|skip|skipping|omit|avoid|but not|do not|don't";
|
|
480
|
+
const englishNegationSuffix = "excluded|excepted|skipped|omitted|disabled|only on explicit request|only when requested";
|
|
481
|
+
if (new RegExp(`\\b(?:${englishNegationPrefix})\\b.{0,40}\\b(?:${mergeTerms})\\b`, "i").test(text) ||
|
|
482
|
+
new RegExp(`\\b(?:${mergeTerms})\\b.{0,40}\\b(?:${englishNegationSuffix})\\b`, "i").test(text) ||
|
|
483
|
+
/(?:릴리즈|머지|배포|태그|푸시|버전|퍼블리시).{0,16}(?:빼고|제외|없이|말고|하지\s*말|금지|명령시(?:에)?만|요청시(?:에)?만)/.test(text) ||
|
|
484
|
+
/(?:빼고|제외|없이|말고).{0,16}(?:릴리즈|머지|배포|태그|푸시|버전|퍼블리시)/.test(text)) {
|
|
485
|
+
add("merge", "excluded-merge-scope");
|
|
486
|
+
}
|
|
487
|
+
if (new RegExp(`\\b(?:${englishNegationPrefix})\\b.{0,40}\\b(?:${shellTerms})\\b`, "i").test(text) ||
|
|
488
|
+
new RegExp(`\\b(?:${shellTerms})\\b.{0,40}\\b(?:${englishNegationSuffix})\\b`, "i").test(text) ||
|
|
489
|
+
/(?:npm|테스트|빌드|실행|검증|체크|명령|쉘|터미널).{0,16}(?:빼고|제외|없이|말고|하지\s*말|금지|명령시(?:에)?만|요청시(?:에)?만)/.test(text) ||
|
|
490
|
+
/(?:빼고|제외|없이|말고).{0,16}(?:npm|테스트|빌드|실행|검증|체크|명령|쉘|터미널)/.test(text)) {
|
|
491
|
+
add("shell", "excluded-shell-scope");
|
|
492
|
+
}
|
|
493
|
+
if (new RegExp(`\\b(?:${englishNegationPrefix})\\b.{0,40}\\b(?:${writeTerms})\\b`, "i").test(text) ||
|
|
494
|
+
/(?:수정|구현|패치|리팩터|추가|삭제|변경).{0,16}(?:빼고|제외|없이|말고|하지\s*말|금지)/.test(text)) {
|
|
495
|
+
add("write", "excluded-write-scope");
|
|
496
|
+
}
|
|
497
|
+
return { ops: [...excluded], scopes };
|
|
498
|
+
}
|
|
499
|
+
function nativeRiskTrace(input) {
|
|
500
|
+
return {
|
|
501
|
+
risk: input.risk,
|
|
502
|
+
confidence: input.confidence,
|
|
503
|
+
matchedSignal: input.matchedSignal,
|
|
504
|
+
readOnlyOverride: input.readOnlyOverride ?? false,
|
|
505
|
+
positiveSignals: [...(input.positiveSignals ?? [])],
|
|
506
|
+
negativeScopes: [...(input.negativeScopes ?? [])],
|
|
507
|
+
requestedOps: [...(input.requestedOps ?? [])],
|
|
508
|
+
excludedOps: [...(input.excludedOps ?? [])],
|
|
509
|
+
};
|
|
464
510
|
}
|
|
465
511
|
export function inferNativeTurnRisk(prompt) {
|
|
466
512
|
const text = prompt.toLowerCase();
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
513
|
+
const excluded = detectExcludedOps(text);
|
|
514
|
+
if (hasExplicitReadOnlyConstraint(text)) {
|
|
515
|
+
return nativeRiskTrace({ risk: "read", confidence: 0.95, matchedSignal: "explicit-read-only-constraint", readOnlyOverride: true, negativeScopes: excluded.scopes, excludedOps: excluded.ops });
|
|
516
|
+
}
|
|
517
|
+
const candidates = [
|
|
518
|
+
{ risk: "merge", confidence: 0.9, signal: "merge/release-keyword", pattern: /\b(push|publish|release|merge|tag|deploy)\b|푸시|퍼블리시|릴리즈|머지|배포/ },
|
|
519
|
+
{ risk: "shell", confidence: 0.85, signal: "shell/execution-keyword", pattern: /\b(run|test|build|exec|execute|shell|terminal|command|npm|pnpm|yarn|bun|pytest|cargo|go test|tsc|lint|verify|check)\b|테스트|빌드|실행|검증|쉘|터미널/ },
|
|
520
|
+
{ risk: "write", confidence: 0.85, signal: "write/modify-keyword", pattern: /\b(fix|edit|write|implement|modify|patch|refactor|add|create|delete|update|change)\b|수정|구현|패치|리팩터|추가|삭제|변경/ },
|
|
521
|
+
];
|
|
522
|
+
const positive = candidates.filter((candidate) => candidate.pattern.test(text));
|
|
523
|
+
const requestedOps = positive.map((candidate) => candidate.risk);
|
|
524
|
+
const selected = positive.find((candidate) => !excluded.ops.includes(candidate.risk));
|
|
525
|
+
if (selected) {
|
|
526
|
+
return nativeRiskTrace({
|
|
527
|
+
risk: selected.risk,
|
|
528
|
+
confidence: selected.confidence,
|
|
529
|
+
matchedSignal: selected.signal,
|
|
530
|
+
positiveSignals: positive.map((candidate) => candidate.signal),
|
|
531
|
+
negativeScopes: excluded.scopes,
|
|
532
|
+
requestedOps,
|
|
533
|
+
excludedOps: excluded.ops,
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
if (positive.length > 0) {
|
|
537
|
+
const risk = hasReadOnlyIntent(text) ? "read" : "ask";
|
|
538
|
+
return nativeRiskTrace({
|
|
539
|
+
risk,
|
|
540
|
+
confidence: risk === "read" ? 0.75 : 0.55,
|
|
541
|
+
matchedSignal: `all-positive-risk-signals-excluded:${positive.map((candidate) => candidate.signal).join(",")}`,
|
|
542
|
+
positiveSignals: positive.map((candidate) => candidate.signal),
|
|
543
|
+
negativeScopes: excluded.scopes,
|
|
544
|
+
requestedOps,
|
|
545
|
+
excludedOps: excluded.ops,
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
if (hasReadOnlyIntent(text))
|
|
549
|
+
return nativeRiskTrace({ risk: "read", confidence: 0.75, matchedSignal: "read-only-intent", negativeScopes: excluded.scopes, excludedOps: excluded.ops });
|
|
550
|
+
return nativeRiskTrace({ risk: "ask", confidence: 0.5, matchedSignal: "no-clear-signal", negativeScopes: excluded.scopes, excludedOps: excluded.ops });
|
|
476
551
|
}
|
|
477
552
|
function nativeTurnRoutingPolicy(provider, risk) {
|
|
478
553
|
if (isApiAdvisoryProvider(provider) && risk !== "read") {
|
|
@@ -483,7 +558,7 @@ function nativeTurnRoutingPolicy(provider, risk) {
|
|
|
483
558
|
providerReasonSuffix: `; ${provider} API is advisory/read-only for ${risk} intent; OMK owns write, shell, MCP, and merge authority`,
|
|
484
559
|
};
|
|
485
560
|
}
|
|
486
|
-
if (risk === "read") {
|
|
561
|
+
if (risk === "read" || risk === "ask") {
|
|
487
562
|
return { capabilities: ["read"], readOnly: true, sandboxMode: "read-only" };
|
|
488
563
|
}
|
|
489
564
|
if (risk === "write") {
|
|
@@ -507,18 +582,79 @@ function nativeTurnRoutingPolicy(provider, risk) {
|
|
|
507
582
|
};
|
|
508
583
|
}
|
|
509
584
|
function isApiAdvisoryProvider(provider) {
|
|
510
|
-
return
|
|
585
|
+
return runtimeIsAdvisory({
|
|
586
|
+
id: `${provider}-api`,
|
|
587
|
+
providerId: provider,
|
|
588
|
+
runtimeMode: "api",
|
|
589
|
+
kind: "api",
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
function hashPrompt(value) {
|
|
593
|
+
return createHash("sha256").update(value).digest("hex");
|
|
594
|
+
}
|
|
595
|
+
function redactedPromptLabel(prompt) {
|
|
596
|
+
const compact = prompt.replace(/\s+/g, " ").trim().replace(/(sk-[A-Za-z0-9_-]+|ghp_[A-Za-z0-9_]+|[A-Za-z0-9+/]{32,}=*)/g, "[redacted]");
|
|
597
|
+
const summary = compact.length > 80 ? `${compact.slice(0, 77)}...` : compact;
|
|
598
|
+
return summary ? `native turn: ${summary}` : "native turn";
|
|
599
|
+
}
|
|
600
|
+
function writePrivatePromptArtifact(input) {
|
|
601
|
+
try {
|
|
602
|
+
const rel = `private/prompts/${sanitizeRunIdForPath(input.nodeId)}.json`;
|
|
603
|
+
const artifact = getRunArtifactPath(input.runId, rel);
|
|
604
|
+
mkdirSync(dirname(artifact), { recursive: true });
|
|
605
|
+
writeFileSync(artifact, JSON.stringify({
|
|
606
|
+
schemaVersion: "omk.prompt-envelope.v1",
|
|
607
|
+
nodeId: input.nodeId,
|
|
608
|
+
runId: input.runId,
|
|
609
|
+
promptHash: input.promptHash,
|
|
610
|
+
userPrompt: input.userPrompt,
|
|
611
|
+
compiledPrompt: input.compiledPrompt,
|
|
612
|
+
redaction: "private-run-artifact",
|
|
613
|
+
timestamp: new Date().toISOString(),
|
|
614
|
+
}, null, 2), "utf-8");
|
|
615
|
+
return rel;
|
|
616
|
+
}
|
|
617
|
+
catch {
|
|
618
|
+
return undefined;
|
|
619
|
+
}
|
|
511
620
|
}
|
|
512
621
|
function debloatRiskFromNativeTurnRisk(risk) {
|
|
513
|
-
if (risk === "read")
|
|
622
|
+
if (risk === "read" || risk === "ask")
|
|
514
623
|
return "read";
|
|
515
624
|
if (risk === "write")
|
|
516
625
|
return "write";
|
|
517
626
|
return "dangerous";
|
|
518
627
|
}
|
|
628
|
+
function isStrictGuardrailMode() {
|
|
629
|
+
const raw = process.env.OMK_STRICT_GUARDRAIL ?? "";
|
|
630
|
+
const normalized = raw.trim().toLowerCase();
|
|
631
|
+
return normalized === "1" || normalized === "true" || normalized === "on";
|
|
632
|
+
}
|
|
633
|
+
function nativeTurnRequiresEvidence(risk) {
|
|
634
|
+
if (isStrictGuardrailMode())
|
|
635
|
+
return risk === "write" || risk === "shell" || risk === "merge";
|
|
636
|
+
return false;
|
|
637
|
+
}
|
|
638
|
+
function nativeEvidenceOutputForRisk(risk) {
|
|
639
|
+
if (!nativeTurnRequiresEvidence(risk))
|
|
640
|
+
return undefined;
|
|
641
|
+
if (risk === "shell") {
|
|
642
|
+
return [{ name: "command evidence", gate: "command-pass", required: true }];
|
|
643
|
+
}
|
|
644
|
+
if (risk === "merge") {
|
|
645
|
+
return [{ name: "release evidence", ref: "## Evidence", gate: "summary", required: true }];
|
|
646
|
+
}
|
|
647
|
+
return [{ name: "change evidence", ref: "## Evidence", gate: "summary", required: true }];
|
|
648
|
+
}
|
|
519
649
|
export function buildNativeRootLoopTurnNode(input) {
|
|
520
650
|
const id = input.nodeId ?? `turn-${Date.now()}`;
|
|
521
|
-
const
|
|
651
|
+
const riskTrace = inferNativeTurnRisk(input.prompt);
|
|
652
|
+
let turnRisk = riskTrace.risk;
|
|
653
|
+
// Low-confidence prompts fall back to ask/read-only to preserve safety.
|
|
654
|
+
if (riskTrace.confidence < 0.6 && turnRisk !== "read") {
|
|
655
|
+
turnRisk = "ask";
|
|
656
|
+
}
|
|
657
|
+
const evidenceRequired = nativeTurnRequiresEvidence(turnRisk);
|
|
522
658
|
const routingPolicy = nativeTurnRoutingPolicy(input.bootstrap.provider, turnRisk);
|
|
523
659
|
const capabilityInjection = buildCapabilityInjection({
|
|
524
660
|
mcpAllowlist: input.mcpAllowlist,
|
|
@@ -545,7 +681,7 @@ export function buildNativeRootLoopTurnNode(input) {
|
|
|
545
681
|
sandbox: routingPolicy.sandboxMode,
|
|
546
682
|
executionSelection: input.executionPrompt,
|
|
547
683
|
role: "coordinator",
|
|
548
|
-
evidenceRequired
|
|
684
|
+
evidenceRequired,
|
|
549
685
|
capabilityEnvelope: {
|
|
550
686
|
mcpEnabled: capabilityInjection.mcpServers,
|
|
551
687
|
skillsEnabled: capabilityInjection.skills,
|
|
@@ -566,14 +702,19 @@ export function buildNativeRootLoopTurnNode(input) {
|
|
|
566
702
|
&& compiled.runtimeSidecar.requiredMcp.length > 0,
|
|
567
703
|
requiresToolCalling: capabilityInjection.requiresToolCalling,
|
|
568
704
|
});
|
|
705
|
+
const promptHash = hashPrompt(compiled.modelPrompt);
|
|
706
|
+
const promptPayloadRef = input.runId
|
|
707
|
+
? writePrivatePromptArtifact({ runId: input.runId, nodeId: id, promptHash, userPrompt: input.prompt, compiledPrompt: compiled.modelPrompt })
|
|
708
|
+
: undefined;
|
|
569
709
|
return {
|
|
570
710
|
id,
|
|
571
|
-
name:
|
|
711
|
+
name: redactedPromptLabel(input.prompt),
|
|
572
712
|
role: "coordinator",
|
|
573
713
|
dependsOn: [],
|
|
574
714
|
status: "running",
|
|
575
715
|
retries: 0,
|
|
576
716
|
maxRetries: 1,
|
|
717
|
+
outputs: nativeEvidenceOutputForRisk(turnRisk),
|
|
577
718
|
routing: applyCapabilityInjectionToRouting({
|
|
578
719
|
provider: input.bootstrap.provider,
|
|
579
720
|
providerModel: input.bootstrap.selectedModel,
|
|
@@ -582,7 +723,11 @@ export function buildNativeRootLoopTurnNode(input) {
|
|
|
582
723
|
contextBudget: "normal",
|
|
583
724
|
readOnly: routingPolicy.readOnly,
|
|
584
725
|
risk: turnRisk,
|
|
585
|
-
|
|
726
|
+
riskTrace,
|
|
727
|
+
evidenceRequired,
|
|
728
|
+
promptMode: promptPayloadRef ? "structured-private" : "dnc-nlp",
|
|
729
|
+
promptHash,
|
|
730
|
+
...(promptPayloadRef && { promptPayloadRef, promptPayloadPrivate: true }),
|
|
586
731
|
runtimeSidecar: {
|
|
587
732
|
...compiled.runtimeSidecar,
|
|
588
733
|
requiredMcp: [...compiled.runtimeSidecar.requiredMcp],
|
|
@@ -597,6 +742,137 @@ export function buildNativeRootLoopTurnNode(input) {
|
|
|
597
742
|
}, selectedCapabilityInjection),
|
|
598
743
|
};
|
|
599
744
|
}
|
|
745
|
+
async function writeTurnRoutingArtifact(runId, node, runContext) {
|
|
746
|
+
const artifact = getRunArtifactPath(runId, `turns/${sanitizeRunIdForPath(node.id)}-routing.json`);
|
|
747
|
+
const payload = {
|
|
748
|
+
schemaVersion: "omk.native-turn.routing.v1",
|
|
749
|
+
nodeId: node.id,
|
|
750
|
+
runId,
|
|
751
|
+
workerRunId: runContext?.runId,
|
|
752
|
+
workerOwner: runContext?.worker?.owner,
|
|
753
|
+
routing: node.routing,
|
|
754
|
+
outputs: node.outputs,
|
|
755
|
+
timestamp: new Date().toISOString(),
|
|
756
|
+
};
|
|
757
|
+
try {
|
|
758
|
+
await mkdir(dirname(artifact), { recursive: true });
|
|
759
|
+
await writeFile(artifact, JSON.stringify(payload, null, 2), "utf-8");
|
|
760
|
+
return undefined;
|
|
761
|
+
}
|
|
762
|
+
catch (err) {
|
|
763
|
+
// Non-fatal: artifact write failures must not block turn execution, but must be observable.
|
|
764
|
+
return { error: err instanceof Error ? err.message : String(err) };
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
async function appendTurnResultArtifact(runId, node, result) {
|
|
768
|
+
const artifact = getRunArtifactPath(runId, `turns/${sanitizeRunIdForPath(node.id)}-result.jsonl`);
|
|
769
|
+
const payload = {
|
|
770
|
+
schemaVersion: "omk.native-turn.result.v1",
|
|
771
|
+
nodeId: node.id,
|
|
772
|
+
runId,
|
|
773
|
+
exitCode: result.exitCode,
|
|
774
|
+
success: result.success,
|
|
775
|
+
evidenceRequired: result.metadata?.evidenceRequired,
|
|
776
|
+
timestamp: new Date().toISOString(),
|
|
777
|
+
};
|
|
778
|
+
try {
|
|
779
|
+
await mkdir(dirname(artifact), { recursive: true });
|
|
780
|
+
await appendFile(artifact, `${JSON.stringify(payload)}\n`, "utf-8");
|
|
781
|
+
return { artifact };
|
|
782
|
+
}
|
|
783
|
+
catch (err) {
|
|
784
|
+
// Non-fatal: artifact write failures must not block turn execution, but must be observable.
|
|
785
|
+
return { error: err instanceof Error ? err.message : String(err) };
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
function sanitizeRunIdForPath(value) {
|
|
789
|
+
return value.replace(/[^A-Za-z0-9._-]/g, "-").slice(0, 64);
|
|
790
|
+
}
|
|
791
|
+
async function sha256File(path) {
|
|
792
|
+
try {
|
|
793
|
+
return createHash("sha256").update(await readFile(path)).digest("hex");
|
|
794
|
+
}
|
|
795
|
+
catch {
|
|
796
|
+
return undefined;
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
function parseReplayIndex(raw, runId) {
|
|
800
|
+
try {
|
|
801
|
+
const parsed = JSON.parse(raw);
|
|
802
|
+
if (parsed.schemaVersion !== "omk.replay-index.v1" || parsed.runId !== runId || !Array.isArray(parsed.artifacts)) {
|
|
803
|
+
throw new Error("invalid replay index");
|
|
804
|
+
}
|
|
805
|
+
return {
|
|
806
|
+
schemaVersion: "omk.replay-index.v1",
|
|
807
|
+
runId,
|
|
808
|
+
updatedAt: typeof parsed.updatedAt === "string" ? parsed.updatedAt : new Date().toISOString(),
|
|
809
|
+
artifacts: parsed.artifacts.filter((artifact) => Boolean(artifact && typeof artifact.path === "string" && typeof artifact.kind === "string")),
|
|
810
|
+
};
|
|
811
|
+
}
|
|
812
|
+
catch {
|
|
813
|
+
return { schemaVersion: "omk.replay-index.v1", runId, updatedAt: new Date().toISOString(), artifacts: [] };
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
async function upsertReplayIndexArtifact(input) {
|
|
817
|
+
const now = new Date().toISOString();
|
|
818
|
+
let existing = { schemaVersion: "omk.replay-index.v1", runId: input.runId, updatedAt: now, artifacts: [] };
|
|
819
|
+
try {
|
|
820
|
+
existing = parseReplayIndex(await readFile(input.replayIndexPath, "utf-8"), input.runId);
|
|
821
|
+
}
|
|
822
|
+
catch {
|
|
823
|
+
// Missing replay index is expected for the first turn.
|
|
824
|
+
}
|
|
825
|
+
const artifacts = existing.artifacts.filter((artifact) => artifact.path !== input.artifactPath);
|
|
826
|
+
artifacts.push({ path: input.artifactPath, sha256: input.artifactHash, kind: input.artifactKind, updatedAt: now });
|
|
827
|
+
await writeFile(input.replayIndexPath, JSON.stringify({
|
|
828
|
+
schemaVersion: "omk.replay-index.v1",
|
|
829
|
+
runId: input.runId,
|
|
830
|
+
updatedAt: now,
|
|
831
|
+
artifacts,
|
|
832
|
+
}, null, 2), "utf-8");
|
|
833
|
+
}
|
|
834
|
+
async function recordTurnAuditGraph(input) {
|
|
835
|
+
try {
|
|
836
|
+
const artifactHash = input.resultArtifact ? await sha256File(input.resultArtifact) : undefined;
|
|
837
|
+
const replayIndex = getRunArtifactPath(input.runId, "replay-index.json", input.root);
|
|
838
|
+
await mkdir(dirname(replayIndex), { recursive: true });
|
|
839
|
+
if (input.resultArtifact) {
|
|
840
|
+
await upsertReplayIndexArtifact({
|
|
841
|
+
replayIndexPath: replayIndex,
|
|
842
|
+
runId: input.runId,
|
|
843
|
+
artifactPath: input.resultArtifact,
|
|
844
|
+
artifactHash,
|
|
845
|
+
artifactKind: "turn-result",
|
|
846
|
+
});
|
|
847
|
+
}
|
|
848
|
+
const store = await LocalGraphMemoryStore.create({ projectRoot: input.root, sessionId: input.runId, source: "native-root-loop-audit" });
|
|
849
|
+
await store?.materializeTurnAudit({
|
|
850
|
+
runId: input.runId,
|
|
851
|
+
nodeId: input.node.id,
|
|
852
|
+
provider: String(input.node.routing?.provider ?? "auto"),
|
|
853
|
+
selectedRuntime: typeof input.result.metadata?.selectedRuntime === "string"
|
|
854
|
+
? input.result.metadata.selectedRuntime
|
|
855
|
+
: typeof input.result.metadata?.runtime === "string"
|
|
856
|
+
? input.result.metadata.runtime
|
|
857
|
+
: undefined,
|
|
858
|
+
fallbackChain: Array.isArray(input.result.metadata?.fallbackChain)
|
|
859
|
+
? input.result.metadata.fallbackChain.filter((entry) => typeof entry === "string")
|
|
860
|
+
: undefined,
|
|
861
|
+
evidenceKind: input.result.success ? "turn-result-pass" : "turn-result-fail",
|
|
862
|
+
evidenceArtifactPath: input.resultArtifact,
|
|
863
|
+
evidenceHash: artifactHash,
|
|
864
|
+
evidenceRequirements: evidenceRequirementsFromOutputs(input.node.outputs),
|
|
865
|
+
evidenceObservations: evidenceObservationsFromResult({
|
|
866
|
+
metadata: input.result.metadata,
|
|
867
|
+
stdout: input.result.stdout,
|
|
868
|
+
artifactPaths: input.resultArtifact ? [input.resultArtifact] : [],
|
|
869
|
+
}),
|
|
870
|
+
});
|
|
871
|
+
}
|
|
872
|
+
catch {
|
|
873
|
+
// Best-effort audit graph materialization must not block turn completion.
|
|
874
|
+
}
|
|
875
|
+
}
|
|
600
876
|
function runtimeSidecarIntent(node) {
|
|
601
877
|
const sidecar = node.routing?.runtimeSidecar;
|
|
602
878
|
if (!sidecar || typeof sidecar !== "object")
|
|
@@ -617,12 +893,34 @@ function describeNativeTurnActivity(node) {
|
|
|
617
893
|
: "local ctx";
|
|
618
894
|
return `${intentPart} · ${guardPart} · ${scopePart} · ${provider}`;
|
|
619
895
|
}
|
|
620
|
-
async function executeNativeRootTurn(input) {
|
|
896
|
+
export async function executeNativeRootTurn(input) {
|
|
621
897
|
const startedAt = Date.now();
|
|
622
898
|
const routing = input.node.routing;
|
|
623
899
|
const activity = describeNativeTurnActivity(input.node);
|
|
624
900
|
emitNativeTurnRoute(input);
|
|
625
|
-
recordNativeTurnToolAuthority({ node: input.node, env: input.env, renderer: input.renderer });
|
|
901
|
+
const authorityRecord = recordNativeTurnToolAuthority({ node: input.node, env: input.env, renderer: input.renderer });
|
|
902
|
+
if (authorityRecord.enforced) {
|
|
903
|
+
const message = `${authorityRecord.reason}; native turn dispatch blocked`;
|
|
904
|
+
if (input.renderer) {
|
|
905
|
+
input.renderer.emit({ type: "turn:error", message });
|
|
906
|
+
}
|
|
907
|
+
else {
|
|
908
|
+
process.stderr.write(style.metricsRed(message) + "\n");
|
|
909
|
+
}
|
|
910
|
+
return {
|
|
911
|
+
success: false,
|
|
912
|
+
exitCode: 78,
|
|
913
|
+
stdout: "",
|
|
914
|
+
stderr: message,
|
|
915
|
+
metadata: {
|
|
916
|
+
code: "TOOL_AUTHORITY_BLOCKED",
|
|
917
|
+
nodeId: input.node.id,
|
|
918
|
+
op: authorityRecord.op,
|
|
919
|
+
decision: authorityRecord.decision,
|
|
920
|
+
authorityMode: authorityRecord.mode,
|
|
921
|
+
},
|
|
922
|
+
};
|
|
923
|
+
}
|
|
626
924
|
let heartbeatPrinted = false;
|
|
627
925
|
let heartbeatLineClosed = false;
|
|
628
926
|
const heartbeat = input.heartbeatEnabled
|
|
@@ -644,8 +942,29 @@ async function executeNativeRootTurn(input) {
|
|
|
644
942
|
}, 3000)
|
|
645
943
|
: undefined;
|
|
646
944
|
heartbeat?.unref?.();
|
|
945
|
+
const diagnostics = {};
|
|
647
946
|
try {
|
|
947
|
+
const turnRunId = input.env.OMK_RUN_ID ?? input.runContext?.goal.runId ?? input.node.id;
|
|
948
|
+
const routingWrite = await writeTurnRoutingArtifact(turnRunId, input.node, input.runContext);
|
|
949
|
+
if (routingWrite?.error)
|
|
950
|
+
diagnostics.routingWrite = routingWrite.error;
|
|
648
951
|
const result = await input.taskRunner.run(input.node, input.env, input.signal, input.runContext);
|
|
952
|
+
const resultWrite = await appendTurnResultArtifact(turnRunId, input.node, result);
|
|
953
|
+
if (resultWrite?.error)
|
|
954
|
+
diagnostics.resultWrite = resultWrite.error;
|
|
955
|
+
await recordTurnAuditGraph({
|
|
956
|
+
root: input.env.OMK_PROJECT_ROOT ?? process.cwd(),
|
|
957
|
+
runId: turnRunId,
|
|
958
|
+
node: input.node,
|
|
959
|
+
result,
|
|
960
|
+
resultArtifact: resultWrite?.artifact,
|
|
961
|
+
});
|
|
962
|
+
if (Object.keys(diagnostics).length > 0) {
|
|
963
|
+
result.metadata = {
|
|
964
|
+
...(result.metadata ?? {}),
|
|
965
|
+
artifactWriteDiagnostics: diagnostics,
|
|
966
|
+
};
|
|
967
|
+
}
|
|
649
968
|
if (input.heartbeatEnabled) {
|
|
650
969
|
const sec = ((Date.now() - startedAt) / 1000).toFixed(1);
|
|
651
970
|
if (input.renderer) {
|
|
@@ -690,6 +1009,7 @@ async function buildNativeRootLoopTurnDag(input) {
|
|
|
690
1009
|
skillNames: input.skillNames,
|
|
691
1010
|
hookNames: input.hookNames,
|
|
692
1011
|
executionPrompt: input.executionPrompt,
|
|
1012
|
+
runId: input.runId,
|
|
693
1013
|
});
|
|
694
1014
|
return createDag({ nodes: [node] });
|
|
695
1015
|
}
|
|
@@ -724,6 +1044,7 @@ async function executeNativeRootHarnessTurn(input) {
|
|
|
724
1044
|
hookNames: input.hookNames,
|
|
725
1045
|
executionPrompt: input.executionPrompt,
|
|
726
1046
|
workers: input.workers,
|
|
1047
|
+
runId: input.runId,
|
|
727
1048
|
});
|
|
728
1049
|
const completed = [];
|
|
729
1050
|
const workerCount = Math.max(1, input.workers ?? 1);
|
package/dist/commands/dag.js
CHANGED
|
@@ -5,6 +5,7 @@ import { getProjectRoot, pathExists, readTextFile, getOmkPath, getRunPath, getRu
|
|
|
5
5
|
import { createDag } from "../orchestration/dag.js";
|
|
6
6
|
import { createStatePersister } from "../orchestration/state-persister.js";
|
|
7
7
|
import { checkEvidenceGates } from "../orchestration/evidence-gate.js";
|
|
8
|
+
import { checkEvidenceGate } from "../runtime/contracts/evidence.js";
|
|
8
9
|
import { createExecutor } from "../orchestration/executor.js";
|
|
9
10
|
import { createExecutableDagFromState, routeRunState } from "../orchestration/run-state.js";
|
|
10
11
|
import { createOmkSessionEnv } from "../util/session.js";
|
|
@@ -126,6 +127,16 @@ export async function dagReplayCommand(runId, target, subtarget, options) {
|
|
|
126
127
|
case "test-pass":
|
|
127
128
|
gates.push({ type: "command-pass", command: output.ref ?? "npm test" });
|
|
128
129
|
break;
|
|
130
|
+
case "command-pass":
|
|
131
|
+
gates.push({ type: "command-pass", command: output.ref ?? "" });
|
|
132
|
+
break;
|
|
133
|
+
case "artifact":
|
|
134
|
+
if (output.ref)
|
|
135
|
+
gates.push({ type: "file-exists", path: output.ref });
|
|
136
|
+
break;
|
|
137
|
+
case "diff":
|
|
138
|
+
gates.push({ type: "diff-nonempty" });
|
|
139
|
+
break;
|
|
129
140
|
case "review-pass":
|
|
130
141
|
case "summary":
|
|
131
142
|
gates.push({ type: "summary-present", summaryMarker: output.ref ?? "## Summary" });
|
|
@@ -134,6 +145,11 @@ export async function dagReplayCommand(runId, target, subtarget, options) {
|
|
|
134
145
|
break;
|
|
135
146
|
}
|
|
136
147
|
}
|
|
148
|
+
if (node.routing?.evidenceRequired === true && gates.length === 0) {
|
|
149
|
+
console.log(style.pink(` ✗ ${node.id}: evidence-required — no replayable evidence gates declared`));
|
|
150
|
+
failedCount++;
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
137
153
|
if (gates.length === 0)
|
|
138
154
|
continue;
|
|
139
155
|
const result = await checkEvidenceGates(gates, {
|
|
@@ -141,6 +157,35 @@ export async function dagReplayCommand(runId, target, subtarget, options) {
|
|
|
141
157
|
stdout: "",
|
|
142
158
|
nodeId: node.id,
|
|
143
159
|
});
|
|
160
|
+
const evidenceGates = [];
|
|
161
|
+
const artifactPaths = [];
|
|
162
|
+
const metadata = {};
|
|
163
|
+
for (const ev of result.evidence) {
|
|
164
|
+
if (!ev.passed)
|
|
165
|
+
continue;
|
|
166
|
+
if (ev.gate === "command-pass")
|
|
167
|
+
evidenceGates.push("command-pass");
|
|
168
|
+
else if (ev.gate === "summary-present")
|
|
169
|
+
evidenceGates.push("summary");
|
|
170
|
+
else if (ev.gate === "file-exists") {
|
|
171
|
+
evidenceGates.push("artifact");
|
|
172
|
+
if (ev.ref)
|
|
173
|
+
artifactPaths.push(ev.ref);
|
|
174
|
+
}
|
|
175
|
+
else if (ev.gate === "diff-nonempty") {
|
|
176
|
+
evidenceGates.push("diff");
|
|
177
|
+
metadata.diff = true;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (evidenceGates.length > 0)
|
|
181
|
+
metadata.evidenceGates = evidenceGates;
|
|
182
|
+
const v2 = checkEvidenceGate(true, node.outputs, metadata, "", artifactPaths);
|
|
183
|
+
if (!v2.satisfied) {
|
|
184
|
+
for (const gate of v2.missing) {
|
|
185
|
+
console.log(style.pink(` ✗ ${node.id}: ${gate} — ${v2.reason}`));
|
|
186
|
+
failedCount++;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
144
189
|
for (const ev of result.evidence) {
|
|
145
190
|
const icon = ev.passed ? "✓" : "✗";
|
|
146
191
|
const color = ev.passed ? style.mint : style.pink;
|