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.
Files changed (124) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/MATURITY.md +2 -2
  3. package/README.md +86 -39
  4. package/ROADMAP.md +20 -17
  5. package/dist/brand/night-city.theme.json +122 -24
  6. package/dist/brand/rust-forge.theme.json +1 -1
  7. package/dist/commands/chat/native-root-loop.d.ts +27 -3
  8. package/dist/commands/chat/native-root-loop.js +375 -49
  9. package/dist/commands/dag.js +45 -0
  10. package/dist/commands/init/content.d.ts +1 -1
  11. package/dist/commands/init/content.js +3 -3
  12. package/dist/commands/init.js +3 -3
  13. package/dist/commands/verify.js +59 -1
  14. package/dist/contracts/dag.d.ts +9 -1
  15. package/dist/evidence/graph-ontology-relation-gate.d.ts +10 -0
  16. package/dist/evidence/graph-ontology-relation-gate.js +46 -0
  17. package/dist/evidence/headroom-replay-verifier.d.ts +19 -0
  18. package/dist/evidence/headroom-replay-verifier.js +86 -0
  19. package/dist/memory/local-graph-memory-store.d.ts +75 -0
  20. package/dist/memory/local-graph-memory-store.js +570 -0
  21. package/dist/orchestration/dag.d.ts +22 -1
  22. package/dist/orchestration/evidence-gate.js +12 -0
  23. package/dist/orchestration/executor.js +108 -15
  24. package/dist/orchestration/loop-controller-headroom.d.ts +6 -0
  25. package/dist/orchestration/loop-controller-headroom.js +33 -0
  26. package/dist/orchestration/scheduler.d.ts +2 -0
  27. package/dist/orchestration/scheduler.js +3 -0
  28. package/dist/orchestration/task-graph.d.ts +17 -0
  29. package/dist/orchestration/task-graph.js +38 -0
  30. package/dist/providers/codex-cli-runner.js +17 -2
  31. package/dist/runtime/agent-runtime.d.ts +32 -3
  32. package/dist/runtime/agent-runtime.js +20 -7
  33. package/dist/runtime/authority-matrix.d.ts +23 -0
  34. package/dist/runtime/authority-matrix.js +159 -0
  35. package/dist/runtime/chat-advisory-runtime.js +3 -0
  36. package/dist/runtime/codex-cli-runtime.js +2 -0
  37. package/dist/runtime/codex-runtime.d.ts +4 -1
  38. package/dist/runtime/codex-runtime.js +34 -12
  39. package/dist/runtime/context-broker-converter.js +32 -2
  40. package/dist/runtime/context-broker.js +18 -0
  41. package/dist/runtime/contracts/evidence.d.ts +52 -0
  42. package/dist/runtime/contracts/evidence.js +128 -1
  43. package/dist/runtime/contracts/shared.d.ts +27 -0
  44. package/dist/runtime/data-retention-gate.d.ts +42 -0
  45. package/dist/runtime/data-retention-gate.js +123 -0
  46. package/dist/runtime/deepseek-runtime.d.ts +5 -1
  47. package/dist/runtime/deepseek-runtime.js +13 -7
  48. package/dist/runtime/external-cli-adapter.js +20 -7
  49. package/dist/runtime/freedomd-evidence-envelope.d.ts +57 -0
  50. package/dist/runtime/freedomd-evidence-envelope.js +110 -0
  51. package/dist/runtime/freedomd-exception.d.ts +32 -0
  52. package/dist/runtime/freedomd-exception.js +95 -0
  53. package/dist/runtime/freedomd-incidents.d.ts +23 -0
  54. package/dist/runtime/freedomd-incidents.js +103 -0
  55. package/dist/runtime/freedomd-policy.d.ts +35 -0
  56. package/dist/runtime/freedomd-policy.js +114 -0
  57. package/dist/runtime/freedomd-router.d.ts +66 -0
  58. package/dist/runtime/freedomd-router.js +433 -0
  59. package/dist/runtime/headroom-aware-loop-decision.d.ts +21 -0
  60. package/dist/runtime/headroom-aware-loop-decision.js +67 -0
  61. package/dist/runtime/headroom-loop-risk.d.ts +19 -0
  62. package/dist/runtime/headroom-loop-risk.js +43 -0
  63. package/dist/runtime/headroom-policy.d.ts +14 -1
  64. package/dist/runtime/headroom-policy.js +74 -12
  65. package/dist/runtime/kimi-api-runtime.d.ts +4 -1
  66. package/dist/runtime/kimi-api-runtime.js +35 -11
  67. package/dist/runtime/kimi-print-runtime.js +21 -0
  68. package/dist/runtime/kimi-wire-protocol-runtime.js +17 -1
  69. package/dist/runtime/local-llm-runtime.d.ts +5 -1
  70. package/dist/runtime/local-llm-runtime.js +14 -23
  71. package/dist/runtime/private-stderr.d.ts +13 -0
  72. package/dist/runtime/private-stderr.js +69 -0
  73. package/dist/runtime/prompt-envelope.d.ts +1 -1
  74. package/dist/runtime/provider-sovereignty.d.ts +81 -0
  75. package/dist/runtime/provider-sovereignty.js +285 -0
  76. package/dist/runtime/runtime-backed-task-runner.d.ts +1 -0
  77. package/dist/runtime/runtime-backed-task-runner.js +282 -10
  78. package/dist/runtime/runtime-bootstrap.d.ts +12 -0
  79. package/dist/runtime/runtime-bootstrap.js +56 -3
  80. package/dist/runtime/runtime-failure-classifier.d.ts +15 -0
  81. package/dist/runtime/runtime-failure-classifier.js +107 -0
  82. package/dist/runtime/runtime-health-probes.d.ts +30 -0
  83. package/dist/runtime/runtime-health-probes.js +102 -0
  84. package/dist/runtime/runtime-router.d.ts +6 -2
  85. package/dist/runtime/runtime-router.js +575 -53
  86. package/dist/runtime/structured-compaction.d.ts +107 -0
  87. package/dist/runtime/structured-compaction.js +414 -0
  88. package/dist/runtime/tool-dispatch-contracts.d.ts +7 -5
  89. package/dist/runtime/tool-dispatch-contracts.js +15 -4
  90. package/dist/runtime/tool-plane.d.ts +1 -1
  91. package/dist/runtime/tool-plane.js +49 -1
  92. package/dist/runtime/worker-manifest.js +1 -1
  93. package/dist/schema/evidence-bundle.schema.d.ts +8 -8
  94. package/dist/schema/proof-bundle.schema.d.ts +2 -2
  95. package/dist/util/hash.d.ts +2 -0
  96. package/dist/util/hash.js +13 -0
  97. package/dist/util/session.js +6 -5
  98. package/docs/2026-06-16/critical-issues.md +19 -0
  99. package/docs/2026-06-16/improvements.md +15 -0
  100. package/docs/2026-06-16/init-checklist.md +25 -0
  101. package/docs/2026-06-16/plan.md +20 -0
  102. package/docs/ARCHITECTURE_ANALYSIS_CODEGRAPH.md +2 -1
  103. package/docs/GSTACK_MIGRATION.md +1 -1
  104. package/docs/algorithm-hardening-playbook.md +219 -0
  105. package/docs/claims.md +1 -1
  106. package/docs/getting-started.md +1 -1
  107. package/docs/native-root-runtime-hardening.md +42 -32
  108. package/docs/post-0793-hardening-roadmap.md +123 -0
  109. package/docs/provider-maturity.md +18 -16
  110. package/docs/versioning.md +3 -3
  111. package/docs/what-is-omk.md +1 -1
  112. package/package.json +8 -4
  113. package/readmeasset/.npmignore +2 -0
  114. package/readmeasset/ASSET_INDEX.md +2 -0
  115. package/readmeasset/ASSET_PROVENANCE.md +40 -20
  116. package/readmeasset/omk-adaptorch-ouroboros-supermemory.svg +1 -1
  117. package/readmeasset/omk-control-surfaces.svg +1 -1
  118. package/readmeasset/omk-core-loop.svg +1 -1
  119. package/readmeasset/omk-freedomd-control-plane.svg +59 -0
  120. package/readmeasset/omk-freedomd-control-plane.webp +0 -0
  121. package/readmeasset/omk-init-control-loop.svg +1 -1
  122. package/readmeasset/omk-logo-mark.svg +1 -1
  123. package/readmeasset/omk-parallel-subagents.svg +1 -1
  124. package/readmeasset/omk-release-assertions.svg +1 -1
@@ -6,8 +6,12 @@
6
6
  */
7
7
  import { capsuleToTask } from "./context-broker-converter.js";
8
8
  import { buildProviderToolPayload } from "./provider-tool-contracts.js";
9
+ import { probeOpenAiCompatibleModels } from "./runtime-health-probes.js";
9
10
  export class DeepSeekRuntime {
10
11
  id = "deepseek-api";
12
+ providerId = "deepseek";
13
+ advisory = true;
14
+ runtimeMode = "api";
11
15
  kind = "api";
12
16
  priority = 40;
13
17
  capabilities = {
@@ -42,14 +46,16 @@ export class DeepSeekRuntime {
42
46
  return false;
43
47
  return true;
44
48
  }
45
- async health() {
46
- const available = Boolean(this.apiKey);
47
- return {
49
+ async health(input = { probeKind: "static", highRisk: false }) {
50
+ return probeOpenAiCompatibleModels({
48
51
  runtimeId: this.id,
49
- available,
50
- reason: available ? undefined : "DEEPSEEK_API_KEY is not set",
51
- checkedAt: new Date().toISOString(),
52
- };
52
+ baseUrl: this.baseUrl,
53
+ apiKey: this.apiKey,
54
+ apiKeyName: "DEEPSEEK_API_KEY",
55
+ model: this.model,
56
+ providerName: "DeepSeek",
57
+ probeKind: input.probeKind,
58
+ });
53
59
  }
54
60
  async runNode(capsule, signal) {
55
61
  try {
@@ -5,14 +5,17 @@
5
5
  import { mkdtemp, rm, writeFile } from "node:fs/promises";
6
6
  import { tmpdir } from "node:os";
7
7
  import { join } from "node:path";
8
- import { checkCommand, } from "../util/shell.js";
8
+ import { checkCommand, runShell, } from "../util/shell.js";
9
9
  import { runtimeMetadataEnv } from "./child-env.js";
10
10
  import { runProcessSession } from "./process-session.js";
11
11
  import { createRuntimeSandboxProfile } from "./sandbox-profile.js";
12
+ import { staticRuntimeHealth } from "./runtime-health-probes.js";
12
13
  export function createExternalCliAdapter(options) {
13
14
  let pendingOnOutput;
14
15
  return {
15
16
  id: options.id,
17
+ providerId: options.id.split("-")[0],
18
+ runtimeMode: "cli",
16
19
  displayName: options.displayName,
17
20
  kind: "cli",
18
21
  priority: options.priority,
@@ -20,14 +23,24 @@ export function createExternalCliAdapter(options) {
20
23
  supports(capsule) {
21
24
  return runtimeCapabilitiesSupportCapsule(options.capabilities, capsule);
22
25
  },
23
- async health() {
24
- const available = await checkCommand(options.bin);
25
- return {
26
+ async health(input = { probeKind: "static", highRisk: false }) {
27
+ const started = Date.now();
28
+ const available = input.probeKind === "static"
29
+ ? await checkCommand(options.bin)
30
+ : (await runShell(options.bin, ["--version"], { cwd: options.cwd ?? process.cwd(), timeout: 5000 }).catch(() => ({ exitCode: 1 }))).exitCode === 0;
31
+ return staticRuntimeHealth({
26
32
  runtimeId: options.id,
27
33
  available,
28
- reason: available ? undefined : `Command not found: ${options.bin}`,
29
- checkedAt: new Date().toISOString(),
30
- };
34
+ reason: available ? undefined : `Command not found or failed --version probe: ${options.bin}`,
35
+ runtimeOk: available,
36
+ authOk: available,
37
+ modelOk: true,
38
+ quotaOk: true,
39
+ rateLimitOk: true,
40
+ latencyMs: input.probeKind === "static" ? undefined : Date.now() - started,
41
+ probeKind: input.probeKind,
42
+ ttlMs: input.probeKind === "static" ? 60_000 : 30_000,
43
+ });
31
44
  },
32
45
  async execute(task) {
33
46
  pendingOnOutput = task.context.onOutput;
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Local-first evidence envelope for Freedomd.
3
+ *
4
+ * Provider responses and evidence observations are persisted outside the
5
+ * provider runtime so that runs remain replayable and auditable even when a
6
+ * provider becomes unavailable or changes policy.
7
+ */
8
+ import type { AgentRuntime, AgentRunResult, AgentTask, AgentTaskSovereignty } from "./agent-runtime.js";
9
+ import type { EvidenceObservation, EvidenceRequirement } from "./contracts/evidence.js";
10
+ import type { LocalGraphMemoryStore } from "../memory/local-graph-memory-store.js";
11
+ export interface EvidenceEnvelopeArtifact {
12
+ readonly path: string;
13
+ readonly sha256: string;
14
+ readonly sizeBytes: number;
15
+ readonly redacted: boolean;
16
+ readonly replayable: boolean;
17
+ }
18
+ export interface EvidenceEnvelopeObservation {
19
+ readonly kind: string;
20
+ readonly verdict: "pass" | "fail" | "partial" | "pending";
21
+ readonly artifactRef?: string;
22
+ readonly sha256?: string;
23
+ readonly replayable: boolean;
24
+ readonly redacted: boolean;
25
+ readonly confidence: number;
26
+ readonly timestamp: string;
27
+ }
28
+ export interface FreedomdEvidenceEnvelope {
29
+ schemaVersion: "omk.freedomd.evidence-envelope.v1";
30
+ runId: string;
31
+ nodeId: string;
32
+ taskHash: string;
33
+ selectedRuntime: string;
34
+ providerId: string;
35
+ runtimeMode: string;
36
+ sovereignty: AgentTaskSovereignty;
37
+ evidenceRequirements: readonly EvidenceRequirement[];
38
+ evidenceObservations: readonly EvidenceEnvelopeObservation[];
39
+ providerResponseRef?: string;
40
+ localArtifacts: readonly EvidenceEnvelopeArtifact[];
41
+ createdAt: string;
42
+ }
43
+ export interface BuildEvidenceEnvelopeOptions {
44
+ readonly task: AgentTask;
45
+ readonly selectedRuntime: AgentRuntime;
46
+ readonly runContext: {
47
+ readonly runId: string;
48
+ readonly nodeId: string;
49
+ readonly projectRoot?: string;
50
+ };
51
+ readonly evidenceObservations?: readonly EvidenceObservation[];
52
+ readonly providerResponse?: AgentRunResult;
53
+ readonly sovereignty: AgentTaskSovereignty;
54
+ }
55
+ export declare function hashTaskPrompt(prompt: string): string;
56
+ export declare function buildLocalFirstEvidenceEnvelope(options: BuildEvidenceEnvelopeOptions): Promise<FreedomdEvidenceEnvelope>;
57
+ export declare function materializeEvidenceEnvelopeInGraph(store: LocalGraphMemoryStore, envelope: FreedomdEvidenceEnvelope): Promise<void>;
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Local-first evidence envelope for Freedomd.
3
+ *
4
+ * Provider responses and evidence observations are persisted outside the
5
+ * provider runtime so that runs remain replayable and auditable even when a
6
+ * provider becomes unavailable or changes policy.
7
+ */
8
+ import { mkdir, writeFile } from "fs/promises";
9
+ import { dirname, join, resolve } from "path";
10
+ import { sha256Hex } from "../util/hash.js";
11
+ function envelopeDir(root, runId) {
12
+ return join(resolve(root), ".omk", "runs", runId, "freedomd");
13
+ }
14
+ function artifactPath(root, runId, nodeId, kind) {
15
+ const safeNodeId = nodeId.replace(/[^a-zA-Z0-9_-]/g, "_");
16
+ return join(envelopeDir(root, runId), `${safeNodeId}-${kind}.json`);
17
+ }
18
+ export function hashTaskPrompt(prompt) {
19
+ return sha256Hex(prompt);
20
+ }
21
+ async function persistArtifact(root, runId, nodeId, kind, payload) {
22
+ const path = artifactPath(root, runId, nodeId, kind);
23
+ await mkdir(dirname(path), { recursive: true });
24
+ const text = JSON.stringify(payload, null, 2);
25
+ await writeFile(path, text, "utf-8");
26
+ return {
27
+ path,
28
+ sha256: sha256Hex(text),
29
+ sizeBytes: Buffer.byteLength(text, "utf-8"),
30
+ redacted: false,
31
+ replayable: true,
32
+ };
33
+ }
34
+ export async function buildLocalFirstEvidenceEnvelope(options) {
35
+ const { task, selectedRuntime, runContext, evidenceObservations, providerResponse, sovereignty } = options;
36
+ const root = runContext.projectRoot ?? process.cwd();
37
+ const now = new Date().toISOString();
38
+ const localArtifacts = [];
39
+ const evidenceObservationList = [];
40
+ for (const observation of evidenceObservations ?? []) {
41
+ const artifact = await persistArtifact(root, runContext.runId, runContext.nodeId, `observation-${observation.kind}`, observation);
42
+ localArtifacts.push(artifact);
43
+ evidenceObservationList.push({
44
+ kind: observation.kind,
45
+ verdict: observation.confidence >= 0.8 ? "pass" : observation.confidence >= 0.4 ? "partial" : "fail",
46
+ artifactRef: artifact.path,
47
+ sha256: artifact.sha256,
48
+ replayable: observation.replayable,
49
+ redacted: observation.redacted,
50
+ confidence: observation.confidence,
51
+ timestamp: observation.timestamp,
52
+ });
53
+ }
54
+ if (providerResponse) {
55
+ const safeResponse = {
56
+ success: providerResponse.success,
57
+ exitCode: providerResponse.exitCode,
58
+ stdout: providerResponse.stdout,
59
+ stderr: providerResponse.stderr,
60
+ tokenUsage: providerResponse.tokenUsage,
61
+ metadata: providerResponse.metadata,
62
+ };
63
+ const artifact = await persistArtifact(root, runContext.runId, runContext.nodeId, "provider-response", safeResponse);
64
+ localArtifacts.push(artifact);
65
+ const envelope = {
66
+ schemaVersion: "omk.freedomd.evidence-envelope.v1",
67
+ runId: runContext.runId,
68
+ nodeId: runContext.nodeId,
69
+ taskHash: hashTaskPrompt(task.prompt),
70
+ selectedRuntime: selectedRuntime.id,
71
+ providerId: selectedRuntime.providerId ?? selectedRuntime.id.split("-")[0] ?? selectedRuntime.id,
72
+ runtimeMode: selectedRuntime.runtimeMode,
73
+ sovereignty,
74
+ evidenceRequirements: [],
75
+ evidenceObservations: evidenceObservationList,
76
+ providerResponseRef: artifact.path,
77
+ localArtifacts,
78
+ createdAt: now,
79
+ };
80
+ const envelopeArtifact = await persistArtifact(root, runContext.runId, runContext.nodeId, "evidence-envelope", envelope);
81
+ return {
82
+ ...envelope,
83
+ localArtifacts: [...localArtifacts, envelopeArtifact],
84
+ };
85
+ }
86
+ const envelope = {
87
+ schemaVersion: "omk.freedomd.evidence-envelope.v1",
88
+ runId: runContext.runId,
89
+ nodeId: runContext.nodeId,
90
+ taskHash: hashTaskPrompt(task.prompt),
91
+ selectedRuntime: selectedRuntime.id,
92
+ providerId: selectedRuntime.providerId ?? selectedRuntime.id.split("-")[0] ?? selectedRuntime.id,
93
+ runtimeMode: selectedRuntime.runtimeMode,
94
+ sovereignty,
95
+ evidenceRequirements: [],
96
+ evidenceObservations: evidenceObservationList,
97
+ localArtifacts,
98
+ createdAt: now,
99
+ };
100
+ const envelopeArtifact = await persistArtifact(root, runContext.runId, runContext.nodeId, "evidence-envelope", envelope);
101
+ return {
102
+ ...envelope,
103
+ localArtifacts: [...localArtifacts, envelopeArtifact],
104
+ };
105
+ }
106
+ export async function materializeEvidenceEnvelopeInGraph(store, envelope) {
107
+ const runId = envelope.runId;
108
+ const nodeId = envelope.nodeId;
109
+ await store.write(join(".omk", "runs", runId, "freedomd", `${nodeId}-evidence-envelope.json`), JSON.stringify(envelope, null, 2));
110
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Freedomd provider exception approval.
3
+ *
4
+ * Interactive approval flow for blocked providers. Records user decisions as
5
+ * local artifacts and graph nodes so every override is auditable.
6
+ */
7
+ import type { LocalGraphMemoryStore } from "../memory/local-graph-memory-store.js";
8
+ export type ProviderExceptionScope = "once" | "run" | "deny";
9
+ export interface ProviderExceptionRequest {
10
+ readonly providerId: string;
11
+ readonly runtimeMode?: string;
12
+ readonly blockedReason: string;
13
+ readonly retentionDays: number;
14
+ readonly jurisdictionRisk: number;
15
+ readonly localFallbackAvailable: boolean;
16
+ readonly taskSensitivity: string;
17
+ }
18
+ export interface ProviderExceptionResult {
19
+ readonly decision: ProviderExceptionScope;
20
+ readonly reason: string;
21
+ readonly artifactPath?: string;
22
+ readonly policyOverridePath?: string;
23
+ }
24
+ export interface ProviderExceptionApproverOptions {
25
+ readonly runId: string;
26
+ readonly nodeId: string;
27
+ readonly projectRoot?: string;
28
+ readonly graphStore?: LocalGraphMemoryStore;
29
+ readonly isTty?: boolean;
30
+ readonly promptUser?: (message: string) => Promise<string>;
31
+ }
32
+ export declare function requestProviderException(request: ProviderExceptionRequest, options: ProviderExceptionApproverOptions): Promise<ProviderExceptionResult>;
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Freedomd provider exception approval.
3
+ *
4
+ * Interactive approval flow for blocked providers. Records user decisions as
5
+ * local artifacts and graph nodes so every override is auditable.
6
+ */
7
+ import { mkdir, writeFile } from "node:fs/promises";
8
+ import { dirname, join, resolve } from "node:path";
9
+ export async function requestProviderException(request, options) {
10
+ const root = resolve(options.projectRoot ?? process.cwd());
11
+ if (options.isTty === false) {
12
+ return { decision: "deny", reason: "non-interactive provider exception denied" };
13
+ }
14
+ const summary = [
15
+ `Provider: ${request.providerId}${request.runtimeMode ? ` (${request.runtimeMode})` : ""}`,
16
+ `Blocked reason: ${request.blockedReason}`,
17
+ `Retention: ${request.retentionDays} days`,
18
+ `Jurisdiction risk: ${request.jurisdictionRisk.toFixed(2)}`,
19
+ `Local fallback available: ${request.localFallbackAvailable}`,
20
+ `Task sensitivity: ${request.taskSensitivity}`,
21
+ ].join("\n");
22
+ const choice = await (options.promptUser?.(summary) ?? Promise.resolve("deny"));
23
+ const normalized = choice.trim().toLowerCase();
24
+ if (normalized.startsWith("allow once")) {
25
+ const artifactPath = await writeExceptionArtifact(root, options.runId, options.nodeId, request, "once");
26
+ await materializeExceptionInGraph(options, request, "once", artifactPath);
27
+ return { decision: "once", reason: "user approved once", artifactPath };
28
+ }
29
+ if (normalized.startsWith("allow for run") || normalized.startsWith("allow run")) {
30
+ const artifactPath = await writeExceptionArtifact(root, options.runId, options.nodeId, request, "run");
31
+ const policyOverridePath = await writeRunPolicyOverride(root, options.runId, request);
32
+ await materializeExceptionInGraph(options, request, "run", artifactPath);
33
+ return { decision: "run", reason: "user approved for this run", artifactPath, policyOverridePath };
34
+ }
35
+ return { decision: "deny", reason: "user denied provider exception" };
36
+ }
37
+ async function writeExceptionArtifact(root, runId, nodeId, request, scope) {
38
+ const dir = join(root, ".omk", "runs", runId, "freedomd");
39
+ const path = join(dir, `${nodeId}-provider-exception.json`);
40
+ await mkdir(dirname(path), { recursive: true });
41
+ const payload = {
42
+ schemaVersion: "omk.freedomd.provider-exception.v1",
43
+ runId,
44
+ nodeId,
45
+ providerId: request.providerId,
46
+ runtimeMode: request.runtimeMode,
47
+ scope,
48
+ requestedAt: new Date().toISOString(),
49
+ request,
50
+ };
51
+ await writeFile(path, JSON.stringify(payload, null, 2), "utf-8");
52
+ return path;
53
+ }
54
+ async function writeRunPolicyOverride(root, runId, request) {
55
+ const dir = join(root, ".omk", "runs", runId, "freedomd");
56
+ const path = join(dir, "policy-overrides.json");
57
+ await mkdir(dirname(path), { recursive: true });
58
+ const payload = {
59
+ schemaVersion: "omk.freedomd.policy-overrides.v1",
60
+ runId,
61
+ providerOverrides: {
62
+ [request.providerId]: {
63
+ enabled: true,
64
+ reason: request.blockedReason,
65
+ },
66
+ },
67
+ createdAt: new Date().toISOString(),
68
+ };
69
+ await writeFile(path, JSON.stringify(payload, null, 2), "utf-8");
70
+ return path;
71
+ }
72
+ async function materializeExceptionInGraph(options, request, scope, artifactPath) {
73
+ if (!options.graphStore)
74
+ return;
75
+ try {
76
+ await options.graphStore.materializeFreedomdSovereignty({
77
+ runId: options.runId,
78
+ nodeId: options.nodeId,
79
+ providerId: request.providerId,
80
+ runtimeMode: request.runtimeMode ?? "api",
81
+ sovereignty: {
82
+ mode: "freedomd",
83
+ dataBoundary: "internal",
84
+ retentionDecision: "allow",
85
+ jurisdictionDecision: "allow",
86
+ providerCutoffRisk: 0,
87
+ localFallbackAvailable: request.localFallbackAvailable,
88
+ reason: `provider exception approved: ${scope}`,
89
+ },
90
+ });
91
+ }
92
+ catch {
93
+ // Best-effort graph materialization.
94
+ }
95
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Freedomd provider incident monitor.
3
+ *
4
+ * Reads local incident overrides from `.omk/provider-incidents.json` and
5
+ * optional per-project policy files. This avoids scraping public web pages by
6
+ * default while still letting operators react to events like Fable 5 quickly.
7
+ */
8
+ import type { ProviderIncidentState } from "./provider-sovereignty.js";
9
+ export interface ProviderIncidentFile {
10
+ readonly schemaVersion?: string;
11
+ readonly updatedAt?: string;
12
+ readonly incidents: readonly ProviderIncidentState[];
13
+ }
14
+ export interface LoadIncidentsOptions {
15
+ readonly projectRoot?: string;
16
+ readonly env?: NodeJS.ProcessEnv;
17
+ readonly feedUrls?: readonly string[];
18
+ readonly fetch?: (url: string) => Promise<{
19
+ ok: boolean;
20
+ text: () => Promise<string>;
21
+ }>;
22
+ }
23
+ export declare function loadProviderIncidents(options?: LoadIncidentsOptions): Promise<ProviderIncidentState[]>;
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Freedomd provider incident monitor.
3
+ *
4
+ * Reads local incident overrides from `.omk/provider-incidents.json` and
5
+ * optional per-project policy files. This avoids scraping public web pages by
6
+ * default while still letting operators react to events like Fable 5 quickly.
7
+ */
8
+ import { readFile } from "node:fs/promises";
9
+ import { join, resolve } from "node:path";
10
+ function parseIncidentFile(raw) {
11
+ try {
12
+ const parsed = JSON.parse(raw);
13
+ if (!Array.isArray(parsed.incidents))
14
+ return [];
15
+ return parsed.incidents.filter(isValidIncident);
16
+ }
17
+ catch {
18
+ return [];
19
+ }
20
+ }
21
+ function isValidIncident(value) {
22
+ if (!value || typeof value !== "object")
23
+ return false;
24
+ const incident = value;
25
+ return (typeof incident.providerId === "string" &&
26
+ typeof incident.kind === "string" &&
27
+ typeof incident.severity === "string" &&
28
+ ["availability", "policy", "export-control", "retention", "jurisdiction"].includes(incident.kind) &&
29
+ ["info", "warn", "block"].includes(incident.severity));
30
+ }
31
+ function incidentsFromEnv(env) {
32
+ const raw = env.OMK_PROVIDER_INCIDENTS;
33
+ if (!raw)
34
+ return [];
35
+ try {
36
+ const parsed = JSON.parse(raw);
37
+ if (Array.isArray(parsed))
38
+ return parsed.filter(isValidIncident);
39
+ if (parsed && typeof parsed === "object" && "incidents" in parsed) {
40
+ return (parsed.incidents ?? []).filter(isValidIncident);
41
+ }
42
+ return [];
43
+ }
44
+ catch {
45
+ return [];
46
+ }
47
+ }
48
+ export async function loadProviderIncidents(options = {}) {
49
+ const root = resolve(options.projectRoot ?? process.cwd());
50
+ const env = options.env ?? process.env;
51
+ const filePath = join(root, ".omk", "provider-incidents.json");
52
+ let fileIncidents = [];
53
+ try {
54
+ const raw = await readFile(filePath, "utf-8");
55
+ fileIncidents = parseIncidentFile(raw);
56
+ }
57
+ catch {
58
+ // Missing or unreadable file is fine; fall back to env.
59
+ }
60
+ const envIncidents = incidentsFromEnv(env);
61
+ const feedIncidents = await fetchFeedIncidents(options.feedUrls ?? [], options.fetch);
62
+ // Merge by providerId+kind, preferring the most recent and most severe.
63
+ const map = new Map();
64
+ for (const incident of [...fileIncidents, ...envIncidents, ...feedIncidents]) {
65
+ const key = `${incident.providerId}:${incident.kind}:${incident.runtimeMode ?? ""}`;
66
+ const existing = map.get(key);
67
+ if (!existing || severityRank(incident.severity) > severityRank(existing.severity)) {
68
+ map.set(key, incident);
69
+ }
70
+ }
71
+ return [...map.values()];
72
+ }
73
+ async function fetchFeedIncidents(urls, fetchImpl) {
74
+ if (urls.length === 0)
75
+ return [];
76
+ const fetcher = fetchImpl ?? defaultFetch;
77
+ const results = [];
78
+ for (const url of urls) {
79
+ try {
80
+ const response = await fetcher(url);
81
+ if (!response.ok)
82
+ continue;
83
+ const text = await response.text();
84
+ const parsed = parseIncidentFile(text);
85
+ results.push(...parsed);
86
+ }
87
+ catch {
88
+ // Best-effort: individual feed failures must not block routing.
89
+ }
90
+ }
91
+ return results;
92
+ }
93
+ async function defaultFetch(url) {
94
+ const response = await fetch(url, { signal: AbortSignal.timeout(5_000) });
95
+ return { ok: response.ok, text: () => response.text() };
96
+ }
97
+ function severityRank(severity) {
98
+ if (severity === "block")
99
+ return 2;
100
+ if (severity === "warn")
101
+ return 1;
102
+ return 0;
103
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Freedomd policy compiler.
3
+ *
4
+ * Translates environment, org defaults, and optional user config into a
5
+ * normalized policy that drives provider-sovereignty routing, retention gates,
6
+ * and degraded-mode fallback decisions.
7
+ */
8
+ export type FreedomdMode = "off" | "balanced" | "strict";
9
+ export interface FreedomdProviderOverride {
10
+ readonly enabled: boolean;
11
+ readonly reason?: string;
12
+ readonly priority?: number;
13
+ }
14
+ export interface FreedomdPolicy {
15
+ readonly mode: FreedomdMode;
16
+ readonly preferLocal: boolean;
17
+ readonly maxRetentionDays: number;
18
+ readonly allowExportRestrictedProvider: boolean;
19
+ readonly allowProviderExceptions: boolean;
20
+ readonly requireLocalEvidenceEnvelope: boolean;
21
+ readonly providerOverrides: Readonly<Record<string, FreedomdProviderOverride>>;
22
+ readonly maxJurisdictionRisk: number;
23
+ readonly maxRetentionRisk: number;
24
+ readonly maxCutoffRisk: number;
25
+ /** Optional public/provider incident feed URLs. Best-effort only. */
26
+ readonly incidentFeedUrls: readonly string[];
27
+ }
28
+ export interface FreedomdPolicyInputs {
29
+ readonly env?: NodeJS.ProcessEnv;
30
+ readonly orgDefaults?: Partial<FreedomdPolicy>;
31
+ readonly taskFlags?: Partial<FreedomdPolicy>;
32
+ }
33
+ export declare function compileFreedomdPolicy(inputs?: FreedomdPolicyInputs): FreedomdPolicy;
34
+ export declare function isFreedomdEnabled(policy: Pick<FreedomdPolicy, "mode">): boolean;
35
+ export declare function explainFreedomdPolicy(policy: FreedomdPolicy): string;
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Freedomd policy compiler.
3
+ *
4
+ * Translates environment, org defaults, and optional user config into a
5
+ * normalized policy that drives provider-sovereignty routing, retention gates,
6
+ * and degraded-mode fallback decisions.
7
+ */
8
+ const DEFAULT_POLICY = {
9
+ mode: "off",
10
+ preferLocal: false,
11
+ maxRetentionDays: Number.POSITIVE_INFINITY,
12
+ allowExportRestrictedProvider: true,
13
+ allowProviderExceptions: true,
14
+ requireLocalEvidenceEnvelope: false,
15
+ providerOverrides: {},
16
+ maxJurisdictionRisk: 1.0,
17
+ maxRetentionRisk: 1.0,
18
+ maxCutoffRisk: 1.0,
19
+ incidentFeedUrls: [],
20
+ };
21
+ function parseMode(raw) {
22
+ if (!raw)
23
+ return undefined;
24
+ const normalized = raw.trim().toLowerCase();
25
+ if (normalized === "off" || normalized === "0" || normalized === "false")
26
+ return "off";
27
+ if (normalized === "balanced" || normalized === "1" || normalized === "true")
28
+ return "balanced";
29
+ if (normalized === "strict" || normalized === "2")
30
+ return "strict";
31
+ return undefined;
32
+ }
33
+ function parseBoolean(raw) {
34
+ if (raw === undefined)
35
+ return undefined;
36
+ const normalized = raw.trim().toLowerCase();
37
+ if (normalized === "1" || normalized === "true" || normalized === "on")
38
+ return true;
39
+ if (normalized === "0" || normalized === "false" || normalized === "off")
40
+ return false;
41
+ return undefined;
42
+ }
43
+ function parseNumber(raw) {
44
+ if (raw === undefined)
45
+ return undefined;
46
+ const value = Number(raw);
47
+ if (!Number.isFinite(value))
48
+ return undefined;
49
+ return value;
50
+ }
51
+ function mergePolicy(base, patch) {
52
+ return {
53
+ ...base,
54
+ ...patch,
55
+ providerOverrides: {
56
+ ...base.providerOverrides,
57
+ ...patch.providerOverrides,
58
+ },
59
+ };
60
+ }
61
+ export function compileFreedomdPolicy(inputs = {}) {
62
+ const env = inputs.env ?? process.env;
63
+ let policy = mergePolicy(DEFAULT_POLICY, inputs.orgDefaults ?? {});
64
+ const envMode = parseMode(env.OMK_FREEDOMD_MODE);
65
+ if (envMode !== undefined) {
66
+ policy = mergePolicy(policy, { mode: envMode });
67
+ }
68
+ const preferLocal = parseBoolean(env.OMK_PREFER_LOCAL);
69
+ if (preferLocal !== undefined) {
70
+ policy = mergePolicy(policy, { preferLocal });
71
+ }
72
+ const maxRetentionDays = parseNumber(env.OMK_MAX_RETENTION_DAYS);
73
+ if (maxRetentionDays !== undefined) {
74
+ policy = mergePolicy(policy, { maxRetentionDays });
75
+ }
76
+ const allowExportRestricted = parseBoolean(env.OMK_BLOCK_EXPORT_RESTRICTED);
77
+ if (allowExportRestricted !== undefined) {
78
+ policy = mergePolicy(policy, { allowExportRestrictedProvider: !allowExportRestricted });
79
+ }
80
+ const allowProviderExceptions = parseBoolean(env.OMK_ALLOW_PROVIDER_EXCEPTIONS);
81
+ if (allowProviderExceptions !== undefined) {
82
+ policy = mergePolicy(policy, { allowProviderExceptions });
83
+ }
84
+ const incidentFeedUrlsRaw = env.OMK_FREEDOMD_FEED_URLS;
85
+ if (incidentFeedUrlsRaw) {
86
+ const urls = incidentFeedUrlsRaw.split(",").map((u) => u.trim()).filter(Boolean);
87
+ policy = mergePolicy(policy, { incidentFeedUrls: urls });
88
+ }
89
+ policy = mergePolicy(policy, inputs.taskFlags ?? {});
90
+ if (policy.mode === "strict") {
91
+ policy = mergePolicy(policy, {
92
+ preferLocal: true,
93
+ allowExportRestrictedProvider: false,
94
+ requireLocalEvidenceEnvelope: true,
95
+ });
96
+ if (!Number.isFinite(policy.maxRetentionDays) || policy.maxRetentionDays > 0) {
97
+ policy = mergePolicy(policy, { maxRetentionDays: 0 });
98
+ }
99
+ }
100
+ return policy;
101
+ }
102
+ export function isFreedomdEnabled(policy) {
103
+ return policy.mode !== "off";
104
+ }
105
+ export function explainFreedomdPolicy(policy) {
106
+ return [
107
+ `mode=${policy.mode}`,
108
+ `preferLocal=${policy.preferLocal}`,
109
+ `maxRetentionDays=${Number.isFinite(policy.maxRetentionDays) ? policy.maxRetentionDays : "unlimited"}`,
110
+ `allowExportRestricted=${policy.allowExportRestrictedProvider}`,
111
+ `allowExceptions=${policy.allowProviderExceptions}`,
112
+ `localEvidenceEnvelope=${policy.requireLocalEvidenceEnvelope}`,
113
+ ].join("; ");
114
+ }