open-multi-agent-kit 0.79.3 → 0.80.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/CHANGELOG.md +22 -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 +375 -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 +108 -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 +159 -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 +128 -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 +56 -3
- 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 +6 -5
- 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/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,84 @@ 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 isAgentFreedomMode() {
|
|
629
|
+
const raw = process.env.OMK_AGENT_FREEDOM ?? process.env.OMK_SWEBENCH_MODE ?? "";
|
|
630
|
+
const normalized = raw.trim().toLowerCase();
|
|
631
|
+
return normalized === "1" || normalized === "true" || normalized === "on" || normalized === "";
|
|
632
|
+
}
|
|
633
|
+
function isStrictGuardrailMode() {
|
|
634
|
+
const raw = process.env.OMK_STRICT_GUARDRAIL ?? "";
|
|
635
|
+
const normalized = raw.trim().toLowerCase();
|
|
636
|
+
return normalized === "1" || normalized === "true" || normalized === "on";
|
|
637
|
+
}
|
|
638
|
+
function nativeTurnRequiresEvidence(risk) {
|
|
639
|
+
if (isStrictGuardrailMode())
|
|
640
|
+
return risk === "write" || risk === "shell" || risk === "merge";
|
|
641
|
+
return false;
|
|
642
|
+
}
|
|
643
|
+
function nativeEvidenceOutputForRisk(risk) {
|
|
644
|
+
if (!nativeTurnRequiresEvidence(risk))
|
|
645
|
+
return undefined;
|
|
646
|
+
if (risk === "shell") {
|
|
647
|
+
return [{ name: "command evidence", gate: "command-pass", required: true }];
|
|
648
|
+
}
|
|
649
|
+
if (risk === "merge") {
|
|
650
|
+
return [{ name: "release evidence", ref: "## Evidence", gate: "summary", required: true }];
|
|
651
|
+
}
|
|
652
|
+
return [{ name: "change evidence", ref: "## Evidence", gate: "summary", required: true }];
|
|
653
|
+
}
|
|
519
654
|
export function buildNativeRootLoopTurnNode(input) {
|
|
520
655
|
const id = input.nodeId ?? `turn-${Date.now()}`;
|
|
521
|
-
const
|
|
656
|
+
const riskTrace = inferNativeTurnRisk(input.prompt);
|
|
657
|
+
let turnRisk = riskTrace.risk;
|
|
658
|
+
// Low-confidence prompts fall back to ask/read-only to preserve safety.
|
|
659
|
+
if (riskTrace.confidence < 0.6 && turnRisk !== "read") {
|
|
660
|
+
turnRisk = "ask";
|
|
661
|
+
}
|
|
662
|
+
const evidenceRequired = nativeTurnRequiresEvidence(turnRisk);
|
|
522
663
|
const routingPolicy = nativeTurnRoutingPolicy(input.bootstrap.provider, turnRisk);
|
|
523
664
|
const capabilityInjection = buildCapabilityInjection({
|
|
524
665
|
mcpAllowlist: input.mcpAllowlist,
|
|
@@ -545,7 +686,7 @@ export function buildNativeRootLoopTurnNode(input) {
|
|
|
545
686
|
sandbox: routingPolicy.sandboxMode,
|
|
546
687
|
executionSelection: input.executionPrompt,
|
|
547
688
|
role: "coordinator",
|
|
548
|
-
evidenceRequired
|
|
689
|
+
evidenceRequired,
|
|
549
690
|
capabilityEnvelope: {
|
|
550
691
|
mcpEnabled: capabilityInjection.mcpServers,
|
|
551
692
|
skillsEnabled: capabilityInjection.skills,
|
|
@@ -566,14 +707,19 @@ export function buildNativeRootLoopTurnNode(input) {
|
|
|
566
707
|
&& compiled.runtimeSidecar.requiredMcp.length > 0,
|
|
567
708
|
requiresToolCalling: capabilityInjection.requiresToolCalling,
|
|
568
709
|
});
|
|
710
|
+
const promptHash = hashPrompt(compiled.modelPrompt);
|
|
711
|
+
const promptPayloadRef = input.runId
|
|
712
|
+
? writePrivatePromptArtifact({ runId: input.runId, nodeId: id, promptHash, userPrompt: input.prompt, compiledPrompt: compiled.modelPrompt })
|
|
713
|
+
: undefined;
|
|
569
714
|
return {
|
|
570
715
|
id,
|
|
571
|
-
name:
|
|
716
|
+
name: redactedPromptLabel(input.prompt),
|
|
572
717
|
role: "coordinator",
|
|
573
718
|
dependsOn: [],
|
|
574
719
|
status: "running",
|
|
575
720
|
retries: 0,
|
|
576
721
|
maxRetries: 1,
|
|
722
|
+
outputs: nativeEvidenceOutputForRisk(turnRisk),
|
|
577
723
|
routing: applyCapabilityInjectionToRouting({
|
|
578
724
|
provider: input.bootstrap.provider,
|
|
579
725
|
providerModel: input.bootstrap.selectedModel,
|
|
@@ -582,7 +728,11 @@ export function buildNativeRootLoopTurnNode(input) {
|
|
|
582
728
|
contextBudget: "normal",
|
|
583
729
|
readOnly: routingPolicy.readOnly,
|
|
584
730
|
risk: turnRisk,
|
|
585
|
-
|
|
731
|
+
riskTrace,
|
|
732
|
+
evidenceRequired,
|
|
733
|
+
promptMode: promptPayloadRef ? "structured-private" : "dnc-nlp",
|
|
734
|
+
promptHash,
|
|
735
|
+
...(promptPayloadRef && { promptPayloadRef, promptPayloadPrivate: true }),
|
|
586
736
|
runtimeSidecar: {
|
|
587
737
|
...compiled.runtimeSidecar,
|
|
588
738
|
requiredMcp: [...compiled.runtimeSidecar.requiredMcp],
|
|
@@ -597,6 +747,137 @@ export function buildNativeRootLoopTurnNode(input) {
|
|
|
597
747
|
}, selectedCapabilityInjection),
|
|
598
748
|
};
|
|
599
749
|
}
|
|
750
|
+
async function writeTurnRoutingArtifact(runId, node, runContext) {
|
|
751
|
+
const artifact = getRunArtifactPath(runId, `turns/${sanitizeRunIdForPath(node.id)}-routing.json`);
|
|
752
|
+
const payload = {
|
|
753
|
+
schemaVersion: "omk.native-turn.routing.v1",
|
|
754
|
+
nodeId: node.id,
|
|
755
|
+
runId,
|
|
756
|
+
workerRunId: runContext?.runId,
|
|
757
|
+
workerOwner: runContext?.worker?.owner,
|
|
758
|
+
routing: node.routing,
|
|
759
|
+
outputs: node.outputs,
|
|
760
|
+
timestamp: new Date().toISOString(),
|
|
761
|
+
};
|
|
762
|
+
try {
|
|
763
|
+
await mkdir(dirname(artifact), { recursive: true });
|
|
764
|
+
await writeFile(artifact, JSON.stringify(payload, null, 2), "utf-8");
|
|
765
|
+
return undefined;
|
|
766
|
+
}
|
|
767
|
+
catch (err) {
|
|
768
|
+
// Non-fatal: artifact write failures must not block turn execution, but must be observable.
|
|
769
|
+
return { error: err instanceof Error ? err.message : String(err) };
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
async function appendTurnResultArtifact(runId, node, result) {
|
|
773
|
+
const artifact = getRunArtifactPath(runId, `turns/${sanitizeRunIdForPath(node.id)}-result.jsonl`);
|
|
774
|
+
const payload = {
|
|
775
|
+
schemaVersion: "omk.native-turn.result.v1",
|
|
776
|
+
nodeId: node.id,
|
|
777
|
+
runId,
|
|
778
|
+
exitCode: result.exitCode,
|
|
779
|
+
success: result.success,
|
|
780
|
+
evidenceRequired: result.metadata?.evidenceRequired,
|
|
781
|
+
timestamp: new Date().toISOString(),
|
|
782
|
+
};
|
|
783
|
+
try {
|
|
784
|
+
await mkdir(dirname(artifact), { recursive: true });
|
|
785
|
+
await appendFile(artifact, `${JSON.stringify(payload)}\n`, "utf-8");
|
|
786
|
+
return { artifact };
|
|
787
|
+
}
|
|
788
|
+
catch (err) {
|
|
789
|
+
// Non-fatal: artifact write failures must not block turn execution, but must be observable.
|
|
790
|
+
return { error: err instanceof Error ? err.message : String(err) };
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
function sanitizeRunIdForPath(value) {
|
|
794
|
+
return value.replace(/[^A-Za-z0-9._-]/g, "-").slice(0, 64);
|
|
795
|
+
}
|
|
796
|
+
async function sha256File(path) {
|
|
797
|
+
try {
|
|
798
|
+
return createHash("sha256").update(await readFile(path)).digest("hex");
|
|
799
|
+
}
|
|
800
|
+
catch {
|
|
801
|
+
return undefined;
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
function parseReplayIndex(raw, runId) {
|
|
805
|
+
try {
|
|
806
|
+
const parsed = JSON.parse(raw);
|
|
807
|
+
if (parsed.schemaVersion !== "omk.replay-index.v1" || parsed.runId !== runId || !Array.isArray(parsed.artifacts)) {
|
|
808
|
+
throw new Error("invalid replay index");
|
|
809
|
+
}
|
|
810
|
+
return {
|
|
811
|
+
schemaVersion: "omk.replay-index.v1",
|
|
812
|
+
runId,
|
|
813
|
+
updatedAt: typeof parsed.updatedAt === "string" ? parsed.updatedAt : new Date().toISOString(),
|
|
814
|
+
artifacts: parsed.artifacts.filter((artifact) => Boolean(artifact && typeof artifact.path === "string" && typeof artifact.kind === "string")),
|
|
815
|
+
};
|
|
816
|
+
}
|
|
817
|
+
catch {
|
|
818
|
+
return { schemaVersion: "omk.replay-index.v1", runId, updatedAt: new Date().toISOString(), artifacts: [] };
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
async function upsertReplayIndexArtifact(input) {
|
|
822
|
+
const now = new Date().toISOString();
|
|
823
|
+
let existing = { schemaVersion: "omk.replay-index.v1", runId: input.runId, updatedAt: now, artifacts: [] };
|
|
824
|
+
try {
|
|
825
|
+
existing = parseReplayIndex(await readFile(input.replayIndexPath, "utf-8"), input.runId);
|
|
826
|
+
}
|
|
827
|
+
catch {
|
|
828
|
+
// Missing replay index is expected for the first turn.
|
|
829
|
+
}
|
|
830
|
+
const artifacts = existing.artifacts.filter((artifact) => artifact.path !== input.artifactPath);
|
|
831
|
+
artifacts.push({ path: input.artifactPath, sha256: input.artifactHash, kind: input.artifactKind, updatedAt: now });
|
|
832
|
+
await writeFile(input.replayIndexPath, JSON.stringify({
|
|
833
|
+
schemaVersion: "omk.replay-index.v1",
|
|
834
|
+
runId: input.runId,
|
|
835
|
+
updatedAt: now,
|
|
836
|
+
artifacts,
|
|
837
|
+
}, null, 2), "utf-8");
|
|
838
|
+
}
|
|
839
|
+
async function recordTurnAuditGraph(input) {
|
|
840
|
+
try {
|
|
841
|
+
const artifactHash = input.resultArtifact ? await sha256File(input.resultArtifact) : undefined;
|
|
842
|
+
const replayIndex = getRunArtifactPath(input.runId, "replay-index.json", input.root);
|
|
843
|
+
await mkdir(dirname(replayIndex), { recursive: true });
|
|
844
|
+
if (input.resultArtifact) {
|
|
845
|
+
await upsertReplayIndexArtifact({
|
|
846
|
+
replayIndexPath: replayIndex,
|
|
847
|
+
runId: input.runId,
|
|
848
|
+
artifactPath: input.resultArtifact,
|
|
849
|
+
artifactHash,
|
|
850
|
+
artifactKind: "turn-result",
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
const store = await LocalGraphMemoryStore.create({ projectRoot: input.root, sessionId: input.runId, source: "native-root-loop-audit" });
|
|
854
|
+
await store?.materializeTurnAudit({
|
|
855
|
+
runId: input.runId,
|
|
856
|
+
nodeId: input.node.id,
|
|
857
|
+
provider: String(input.node.routing?.provider ?? "auto"),
|
|
858
|
+
selectedRuntime: typeof input.result.metadata?.selectedRuntime === "string"
|
|
859
|
+
? input.result.metadata.selectedRuntime
|
|
860
|
+
: typeof input.result.metadata?.runtime === "string"
|
|
861
|
+
? input.result.metadata.runtime
|
|
862
|
+
: undefined,
|
|
863
|
+
fallbackChain: Array.isArray(input.result.metadata?.fallbackChain)
|
|
864
|
+
? input.result.metadata.fallbackChain.filter((entry) => typeof entry === "string")
|
|
865
|
+
: undefined,
|
|
866
|
+
evidenceKind: input.result.success ? "turn-result-pass" : "turn-result-fail",
|
|
867
|
+
evidenceArtifactPath: input.resultArtifact,
|
|
868
|
+
evidenceHash: artifactHash,
|
|
869
|
+
evidenceRequirements: evidenceRequirementsFromOutputs(input.node.outputs),
|
|
870
|
+
evidenceObservations: evidenceObservationsFromResult({
|
|
871
|
+
metadata: input.result.metadata,
|
|
872
|
+
stdout: input.result.stdout,
|
|
873
|
+
artifactPaths: input.resultArtifact ? [input.resultArtifact] : [],
|
|
874
|
+
}),
|
|
875
|
+
});
|
|
876
|
+
}
|
|
877
|
+
catch {
|
|
878
|
+
// Best-effort audit graph materialization must not block turn completion.
|
|
879
|
+
}
|
|
880
|
+
}
|
|
600
881
|
function runtimeSidecarIntent(node) {
|
|
601
882
|
const sidecar = node.routing?.runtimeSidecar;
|
|
602
883
|
if (!sidecar || typeof sidecar !== "object")
|
|
@@ -617,12 +898,34 @@ function describeNativeTurnActivity(node) {
|
|
|
617
898
|
: "local ctx";
|
|
618
899
|
return `${intentPart} · ${guardPart} · ${scopePart} · ${provider}`;
|
|
619
900
|
}
|
|
620
|
-
async function executeNativeRootTurn(input) {
|
|
901
|
+
export async function executeNativeRootTurn(input) {
|
|
621
902
|
const startedAt = Date.now();
|
|
622
903
|
const routing = input.node.routing;
|
|
623
904
|
const activity = describeNativeTurnActivity(input.node);
|
|
624
905
|
emitNativeTurnRoute(input);
|
|
625
|
-
recordNativeTurnToolAuthority({ node: input.node, env: input.env, renderer: input.renderer });
|
|
906
|
+
const authorityRecord = recordNativeTurnToolAuthority({ node: input.node, env: input.env, renderer: input.renderer });
|
|
907
|
+
if (authorityRecord.enforced) {
|
|
908
|
+
const message = `${authorityRecord.reason}; native turn dispatch blocked`;
|
|
909
|
+
if (input.renderer) {
|
|
910
|
+
input.renderer.emit({ type: "turn:error", message });
|
|
911
|
+
}
|
|
912
|
+
else {
|
|
913
|
+
process.stderr.write(style.metricsRed(message) + "\n");
|
|
914
|
+
}
|
|
915
|
+
return {
|
|
916
|
+
success: false,
|
|
917
|
+
exitCode: 78,
|
|
918
|
+
stdout: "",
|
|
919
|
+
stderr: message,
|
|
920
|
+
metadata: {
|
|
921
|
+
code: "TOOL_AUTHORITY_BLOCKED",
|
|
922
|
+
nodeId: input.node.id,
|
|
923
|
+
op: authorityRecord.op,
|
|
924
|
+
decision: authorityRecord.decision,
|
|
925
|
+
authorityMode: authorityRecord.mode,
|
|
926
|
+
},
|
|
927
|
+
};
|
|
928
|
+
}
|
|
626
929
|
let heartbeatPrinted = false;
|
|
627
930
|
let heartbeatLineClosed = false;
|
|
628
931
|
const heartbeat = input.heartbeatEnabled
|
|
@@ -644,8 +947,29 @@ async function executeNativeRootTurn(input) {
|
|
|
644
947
|
}, 3000)
|
|
645
948
|
: undefined;
|
|
646
949
|
heartbeat?.unref?.();
|
|
950
|
+
const diagnostics = {};
|
|
647
951
|
try {
|
|
952
|
+
const turnRunId = input.env.OMK_RUN_ID ?? input.runContext?.goal.runId ?? input.node.id;
|
|
953
|
+
const routingWrite = await writeTurnRoutingArtifact(turnRunId, input.node, input.runContext);
|
|
954
|
+
if (routingWrite?.error)
|
|
955
|
+
diagnostics.routingWrite = routingWrite.error;
|
|
648
956
|
const result = await input.taskRunner.run(input.node, input.env, input.signal, input.runContext);
|
|
957
|
+
const resultWrite = await appendTurnResultArtifact(turnRunId, input.node, result);
|
|
958
|
+
if (resultWrite?.error)
|
|
959
|
+
diagnostics.resultWrite = resultWrite.error;
|
|
960
|
+
await recordTurnAuditGraph({
|
|
961
|
+
root: input.env.OMK_PROJECT_ROOT ?? process.cwd(),
|
|
962
|
+
runId: turnRunId,
|
|
963
|
+
node: input.node,
|
|
964
|
+
result,
|
|
965
|
+
resultArtifact: resultWrite?.artifact,
|
|
966
|
+
});
|
|
967
|
+
if (Object.keys(diagnostics).length > 0) {
|
|
968
|
+
result.metadata = {
|
|
969
|
+
...(result.metadata ?? {}),
|
|
970
|
+
artifactWriteDiagnostics: diagnostics,
|
|
971
|
+
};
|
|
972
|
+
}
|
|
649
973
|
if (input.heartbeatEnabled) {
|
|
650
974
|
const sec = ((Date.now() - startedAt) / 1000).toFixed(1);
|
|
651
975
|
if (input.renderer) {
|
|
@@ -690,6 +1014,7 @@ async function buildNativeRootLoopTurnDag(input) {
|
|
|
690
1014
|
skillNames: input.skillNames,
|
|
691
1015
|
hookNames: input.hookNames,
|
|
692
1016
|
executionPrompt: input.executionPrompt,
|
|
1017
|
+
runId: input.runId,
|
|
693
1018
|
});
|
|
694
1019
|
return createDag({ nodes: [node] });
|
|
695
1020
|
}
|
|
@@ -724,6 +1049,7 @@ async function executeNativeRootHarnessTurn(input) {
|
|
|
724
1049
|
hookNames: input.hookNames,
|
|
725
1050
|
executionPrompt: input.executionPrompt,
|
|
726
1051
|
workers: input.workers,
|
|
1052
|
+
runId: input.runId,
|
|
727
1053
|
});
|
|
728
1054
|
const completed = [];
|
|
729
1055
|
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;
|