sentinelayer-cli 0.46.4 → 0.48.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 (137) hide show
  1. package/README.md +63 -3
  2. package/package.json +12 -7
  3. package/skills/omar-gate-setup/SKILL.md +103 -0
  4. package/skills/senti-agent-identity/SKILL.md +80 -0
  5. package/skills/senti-judge/SKILL.md +139 -0
  6. package/skills/senti-room-notifications/SKILL.md +73 -0
  7. package/skills/senti-room-notifications/listen-wrapper.cjs +150 -0
  8. package/skills/senti-room-notifications/package.json +1 -0
  9. package/skills/senti-room-notifications/test-classifier.js +109 -0
  10. package/skills/senti-room-notifications/test-receipt.js +54 -0
  11. package/skills/sentinelayer-cli/SKILL.md +222 -0
  12. package/src/agents/devtestbot/runner.js +22 -11
  13. package/src/agents/devtestbot/tool.js +24 -11
  14. package/src/agents/jules/fix-cycle.js +7 -1
  15. package/src/agents/jules/tools/runtime-audit.js +17 -12
  16. package/src/agents/shared-tools/dispatch-core.js +10 -5
  17. package/src/ai/aidenid.js +4 -2
  18. package/src/ai/domain-target-store.js +11 -5
  19. package/src/ai/identity-store.js +11 -5
  20. package/src/ai/site-store.js +8 -4
  21. package/src/audit/orchestrator.js +21 -14
  22. package/src/audit/package.js +6 -4
  23. package/src/audit/persona-loop.js +7 -0
  24. package/src/audit/replay.js +4 -2
  25. package/src/auth/gate.js +46 -8
  26. package/src/auth/service.js +34 -15
  27. package/src/auth/session-store.js +105 -47
  28. package/src/board/evidence-resolvers.js +3 -4
  29. package/src/cli.js +23 -3
  30. package/src/commands/ai/identity-lifecycle.js +30 -26
  31. package/src/commands/ai/provision-governance.js +32 -24
  32. package/src/commands/ai/shared.js +6 -4
  33. package/src/commands/audit.js +14 -12
  34. package/src/commands/auth.js +24 -4
  35. package/src/commands/board.js +2 -2
  36. package/src/commands/chat.js +8 -6
  37. package/src/commands/judge.js +125 -0
  38. package/src/commands/legacy-args.js +1 -0
  39. package/src/commands/mcp.js +45 -52
  40. package/src/commands/omargate.js +2 -1
  41. package/src/commands/plugin.js +11 -3
  42. package/src/commands/review.js +9 -4
  43. package/src/commands/scan.js +7 -7
  44. package/src/commands/session.js +607 -57
  45. package/src/commands/skills.js +45 -0
  46. package/src/commands/watch.js +9 -7
  47. package/src/config/output-filesystem.js +156 -0
  48. package/src/config/schema.js +4 -2
  49. package/src/config/service.js +40 -4
  50. package/src/cost/history.js +15 -9
  51. package/src/daemon/artifact-lineage.js +15 -12
  52. package/src/daemon/assignment-ledger.js +31 -25
  53. package/src/daemon/ast-drift.js +7 -3
  54. package/src/daemon/budget-governor.js +26 -20
  55. package/src/daemon/error-worker.js +28 -22
  56. package/src/daemon/fix-cycle.js +5 -4
  57. package/src/daemon/hybrid-mapper.js +19 -16
  58. package/src/daemon/ingest-refresh.js +4 -0
  59. package/src/daemon/jira-lifecycle.js +21 -17
  60. package/src/daemon/operator-control.js +18 -15
  61. package/src/daemon/reliability-lane.js +18 -15
  62. package/src/daemon/scope-engine.js +12 -8
  63. package/src/daemon/watchdog.js +15 -12
  64. package/src/engram/build-state.js +24 -3
  65. package/src/engram/capability-index.js +367 -0
  66. package/src/engram/compile-graph.js +1063 -0
  67. package/src/engram/contracts/action-receipt.js +82 -0
  68. package/src/engram/contracts/capability-index.js +259 -0
  69. package/src/engram/contracts/capability.js +377 -0
  70. package/src/engram/contracts/content-hash.js +8 -0
  71. package/src/engram/contracts/evidence.js +258 -0
  72. package/src/engram/contracts/graph.js +594 -0
  73. package/src/engram/contracts/identity.js +8 -0
  74. package/src/engram/contracts/index.js +149 -0
  75. package/src/engram/contracts/media-asset.js +85 -0
  76. package/src/engram/contracts/money.js +249 -0
  77. package/src/engram/contracts/source-snapshot.js +57 -0
  78. package/src/engram/contracts/task-context-pack.js +114 -0
  79. package/src/engram/contracts/temporal-segment.js +72 -0
  80. package/src/engram/contracts/versions.js +96 -0
  81. package/src/engram/extract-html.js +692 -0
  82. package/src/engram/graph-store.js +877 -0
  83. package/src/engram/index.js +98 -14
  84. package/src/engram/ingest-site.js +132 -0
  85. package/src/engram/live-state.js +1180 -0
  86. package/src/engram/namespace.js +34 -1
  87. package/src/engram/retrieve-site.js +753 -0
  88. package/src/engram/safe-reader.js +597 -0
  89. package/src/engram/source-intake.js +251 -0
  90. package/src/engram/store.js +227 -9
  91. package/src/ingest/engine.js +21 -28
  92. package/src/judge/verdict.js +305 -0
  93. package/src/legacy-cli.js +96 -19
  94. package/src/mcp/bridge-inputs.js +28 -4
  95. package/src/mcp/cli-command-tools.js +70 -0
  96. package/src/mcp/registry.js +17 -13
  97. package/src/mcp/session-stdio-server.js +297 -19
  98. package/src/memory/blackboard.js +5 -4
  99. package/src/net/gh-api-path.js +35 -9
  100. package/src/net/test-egress-guard.js +16 -4
  101. package/src/plugin/manifest.js +12 -8
  102. package/src/review/ai-review.js +110 -10
  103. package/src/review/investor-dd-devtestbot.js +17 -6
  104. package/src/review/investor-dd-orchestrator.js +21 -13
  105. package/src/review/local-review.js +26 -16
  106. package/src/review/omargate-cache.js +5 -5
  107. package/src/review/omargate-orchestrator.js +89 -17
  108. package/src/review/replay.js +9 -4
  109. package/src/review/report.js +22 -13
  110. package/src/review/typesafe-severity.js +201 -0
  111. package/src/session/admission-auth.js +34 -7
  112. package/src/session/admission.js +322 -71
  113. package/src/session/agent-access.js +137 -0
  114. package/src/session/agent-registry.js +2 -1
  115. package/src/session/attachment-summary.js +77 -0
  116. package/src/session/edit-summary.js +19 -0
  117. package/src/session/invitations.js +65 -1
  118. package/src/session/mentions.js +17 -9
  119. package/src/session/message-edits.js +5 -4
  120. package/src/session/preview.js +10 -2
  121. package/src/session/recap.js +3 -2
  122. package/src/session/room-files.js +519 -0
  123. package/src/session/route-classes.js +22 -8
  124. package/src/session/setup-guides.js +4 -0
  125. package/src/session/standup.js +192 -0
  126. package/src/session/sync.js +229 -10
  127. package/src/session/tickets.js +41 -0
  128. package/src/session/transcript.js +2 -1
  129. package/src/session/wake/resolve-target.js +7 -3
  130. package/src/skills/listener-runtime.cjs +92 -0
  131. package/src/skills/service.js +207 -0
  132. package/src/swarm/factory.js +5 -5
  133. package/src/swarm/pentest.js +9 -9
  134. package/src/swarm/report.js +4 -4
  135. package/src/swarm/runtime.js +15 -12
  136. package/src/swarm/scenario-dsl.js +7 -5
  137. package/src/telemetry/ledger.js +16 -7
@@ -3,7 +3,8 @@ import fsp from "node:fs/promises";
3
3
  import path from "node:path";
4
4
 
5
5
  import { getIdentityById } from "../../ai/identity-store.js";
6
- import { resolveOutputRoot } from "../../config/service.js";
6
+ import { resolveOutputContext } from "../../config/service.js";
7
+ import { requireOutputFileSystem } from "../../config/output-filesystem.js";
7
8
  import { createAgentEvent } from "../../events/schema.js";
8
9
  import { DEVTESTBOT_DEFINITION, DEVTESTBOT_LANES } from "./config/definition.js";
9
10
  import { launch } from "./runner.js";
@@ -63,7 +64,7 @@ export async function executeDevTestBotRunSessionTool(input = {}, ctx = {}) {
63
64
 
64
65
  export async function runDevTestBotSession(input = {}, ctx = {}) {
65
66
  const targetPath = path.resolve(String(input.targetPath || ctx.targetPath || process.cwd()));
66
- const outputRoot = await resolveOutputRoot({
67
+ const { outputRoot, outputFs: resolvedOutputFs } = await resolveOutputContext({
67
68
  cwd: targetPath,
68
69
  outputDirOverride: input.outputRoot || ctx.outputRoot || "",
69
70
  env: ctx.env || process.env,
@@ -74,10 +75,13 @@ export async function runDevTestBotSession(input = {}, ctx = {}) {
74
75
  const recordVideo = input.recordVideo !== undefined ? Boolean(input.recordVideo) : true;
75
76
  const baseUrl = normalizeString(input.baseUrl || ctx.baseUrl);
76
77
  const identityId = normalizeString(input.identityId || ctx.identityId);
77
- const artifactRoot = path.resolve(
78
- input.outputDir || ctx.outputDir || path.join(outputRoot, "runs", runId, "devtestbot")
79
- );
80
- await fsp.mkdir(artifactRoot, { recursive: true });
78
+ const explicitOutputDir = input.outputDir || ctx.outputDir;
79
+ const artifactOutput = explicitOutputDir
80
+ ? await resolveOutputContext({ cwd: targetPath, outputDirOverride: explicitOutputDir, env: ctx.env || process.env })
81
+ : { outputRoot: path.join(outputRoot, "runs", runId, "devtestbot"), outputFs: resolvedOutputFs };
82
+ const artifactRoot = artifactOutput.outputRoot;
83
+ const outputFs = ctx.outputFs === undefined ? artifactOutput.outputFs : requireOutputFileSystem(ctx.outputFs);
84
+ await outputFs.mkdir(artifactRoot, { recursive: true });
81
85
 
82
86
  const { registryPath, identity } = await resolveIdentity({
83
87
  outputRoot,
@@ -142,6 +146,7 @@ export async function runDevTestBotSession(input = {}, ctx = {}) {
142
146
  scope,
143
147
  baseUrl,
144
148
  artifactRoot,
149
+ outputFs,
145
150
  sensitiveValues,
146
151
  });
147
152
  for (const finding of dryRun.findings) {
@@ -168,6 +173,7 @@ export async function runDevTestBotSession(input = {}, ctx = {}) {
168
173
  completed: true,
169
174
  dryRun: true,
170
175
  artifactRoot,
176
+ outputFs,
171
177
  artifactBundle: dryRun.artifactBundle,
172
178
  artifacts: dryRun.artifacts,
173
179
  findings: dryRun.findings,
@@ -183,6 +189,7 @@ export async function runDevTestBotSession(input = {}, ctx = {}) {
183
189
  baseUrl: normalizedBaseUrl,
184
190
  identityCreds,
185
191
  outputDir: artifactRoot,
192
+ outputFs,
186
193
  recordVideo,
187
194
  runLighthouse: input.runLighthouse !== undefined ? Boolean(input.runLighthouse) : true,
188
195
  lighthouseTimeoutMs: input.lighthouseTimeoutMs,
@@ -229,6 +236,7 @@ export async function runDevTestBotSession(input = {}, ctx = {}) {
229
236
  completed: true,
230
237
  dryRun: false,
231
238
  artifactRoot,
239
+ outputFs,
232
240
  artifacts,
233
241
  findings,
234
242
  laneSummaries,
@@ -249,6 +257,7 @@ export async function runDevTestBotSession(input = {}, ctx = {}) {
249
257
  completed: false,
250
258
  dryRun: !execute,
251
259
  artifactRoot,
260
+ outputFs,
252
261
  artifacts: {},
253
262
  findings: [],
254
263
  laneSummaries: {},
@@ -351,7 +360,8 @@ async function driveScope({ runner, scope }) {
351
360
  }
352
361
  }
353
362
 
354
- async function buildDryRunResult({ runId, scope, baseUrl, artifactRoot, sensitiveValues }) {
363
+ async function buildDryRunResult({ runId, scope, baseUrl, artifactRoot, sensitiveValues, outputFs }) {
364
+ const writeJson = (...args) => writeJsonWithFs(outputFs, ...args);
355
365
  const artifacts = {
356
366
  manifestPath: path.join(artifactRoot, "manifest.json"),
357
367
  };
@@ -561,6 +571,7 @@ function normalizeFinding(input, sensitiveValues) {
561
571
  }
562
572
 
563
573
  async function writeDevTestBotResult({
574
+ outputFs,
564
575
  runId,
565
576
  scope,
566
577
  completed,
@@ -574,6 +585,7 @@ async function writeDevTestBotResult({
574
585
  sensitiveValues,
575
586
  error = "",
576
587
  }) {
588
+ const writeJson = (...args) => writeJsonWithFs(outputFs, ...args);
577
589
  const findingsPath = path.join(artifactRoot, "findings.json");
578
590
  const eventsPath = path.join(artifactRoot, "events.ndjson");
579
591
  const resultPath = path.join(artifactRoot, "devtestbot-result.json");
@@ -586,7 +598,7 @@ async function writeDevTestBotResult({
586
598
  artifacts: sanitizeArtifacts(artifacts || artifactBundle?.artifacts || {}, sensitiveValues),
587
599
  };
588
600
  await writeJson(findingsPath, findings);
589
- await fsp.writeFile(eventsPath, `${events.map((event) => JSON.stringify(event)).join("\n")}\n`, "utf-8");
601
+ await outputFs.writeFile(eventsPath, `${events.map((event) => JSON.stringify(event)).join("\n")}\n`, "utf-8");
590
602
  const result = sanitizeJson({
591
603
  schemaVersion: 1,
592
604
  runId,
@@ -701,7 +713,8 @@ async function readJsonIfPresent(filePath) {
701
713
  }
702
714
  }
703
715
 
704
- async function writeJson(filePath, payload) {
705
- await fsp.mkdir(path.dirname(filePath), { recursive: true });
706
- await fsp.writeFile(filePath, `${JSON.stringify(payload, null, 2)}\n`, "utf-8");
716
+ async function writeJsonWithFs(outputFs, filePath, payload) {
717
+ requireOutputFileSystem(outputFs);
718
+ await outputFs.mkdir(path.dirname(filePath), { recursive: true });
719
+ await outputFs.writeFile(filePath, `${JSON.stringify(payload, null, 2)}\n`, "utf-8");
707
720
  }
@@ -2,10 +2,16 @@
2
2
  // Jules passes its own identity; any persona can use the same lifecycle.
3
3
  import { runFixCycle as _runFixCycle } from "../../daemon/fix-cycle.js";
4
4
  import { JULES_DEFINITION } from "./config/definition.js";
5
+ import { resolveOutputContext } from "../../config/service.js";
6
+ import { requireOutputFileSystem } from "../../config/output-filesystem.js";
5
7
 
6
- export function runFixCycle(opts) {
8
+ export async function runFixCycle(opts = {}) {
9
+ const outputFs = Object.hasOwn(opts, "outputFs")
10
+ ? requireOutputFileSystem(opts.outputFs)
11
+ : (await resolveOutputContext({ cwd: opts.rootPath || process.cwd(), outputDirOverride: ".sentinelayer" })).outputFs;
7
12
  return _runFixCycle({
8
13
  ...opts,
14
+ outputFs,
9
15
  agentIdentity: {
10
16
  id: JULES_DEFINITION.id,
11
17
  persona: JULES_DEFINITION.persona,
@@ -3,6 +3,7 @@ import path from "node:path";
3
3
  import fs from "node:fs";
4
4
  import os from "node:os";
5
5
  import { randomUUID } from "node:crypto";
6
+ import { fileURLToPath } from "node:url";
6
7
  import { credentialedRequest, isAuthenticated } from "../../../auth/credential-destinations.js";
7
8
  import { urlPathSegment } from "../../../net/url-path.js";
8
9
  import { assertPermittedAuditTarget } from "./url-policy.js";
@@ -52,16 +53,24 @@ const RUNTIME_DISPATCH = {
52
53
  /**
53
54
  * How Lighthouse runs. No shell: the target URL can come from the repository (package.json,
54
55
  * vercel.json, .env files), and a shell would expand variables and operators inside it. Credentials
55
- * are removed from npx's environment, so a repository .npmrc cannot place them in a request.
56
- * On Windows, where npx is a .cmd file, npm's npx script runs under this Node.js instead.
56
+ * are removed from the child environment. Resolve the installed Lighthouse dependency and run
57
+ * its CLI with Node directly, so this fallback cannot download another Lighthouse version.
57
58
  */
58
59
  function lighthouseInvocation(
59
60
  targetUrl,
60
61
  outputPath,
61
- { platform = process.platform, execPath = process.execPath, exists = fs.existsSync, env = process.env } = {},
62
+ { execPath = process.execPath, exists = fs.existsSync, resolve = import.meta.resolve, env = process.env } = {},
62
63
  ) {
64
+ let lighthouseCli;
65
+ try {
66
+ lighthouseCli = fileURLToPath(resolve("lighthouse/cli/index.js"));
67
+ } catch {
68
+ return null;
69
+ }
70
+ if (!exists(lighthouseCli)) return null;
71
+
63
72
  const args = [
64
- "--yes", "lighthouse@12", targetUrl,
73
+ lighthouseCli, targetUrl,
65
74
  "--output", "json", "--output-path", outputPath,
66
75
  "--chrome-flags=--headless --no-sandbox --disable-gpu", "--quiet",
67
76
  ];
@@ -71,17 +80,13 @@ function lighthouseInvocation(
71
80
  stdio: ["pipe", "pipe", "pipe"],
72
81
  env: buildScrubbedEnv(env),
73
82
  };
74
- if (platform !== "win32") {
75
- return { file: "npx", args, options };
76
- }
77
- const npxCli = path.join(path.dirname(execPath), "node_modules", "npm", "bin", "npx-cli.js");
78
- return exists(npxCli) ? { file: execPath, args: [npxCli, ...args], options } : null;
83
+ return { file: execPath, args, options };
79
84
  }
80
85
 
81
86
  export const __runtimeAuditForTests = Object.freeze({ lighthouseInvocation, detectDeployedUrl });
82
87
 
83
88
  /**
84
- * Run Lighthouse via npx (no install required).
89
+ * Run the installed Lighthouse CLI.
85
90
  * Returns performance, accessibility, best-practices, SEO scores + key metrics.
86
91
  */
87
92
  async function lighthouseScan(input) {
@@ -92,7 +97,7 @@ async function lighthouseScan(input) {
92
97
  const targetUrl = resolveRuntimeTargetUrl(url, input, "lighthouse_scan");
93
98
 
94
99
  // Prefer SentinelLayer API scanner (authenticated, server-side Lighthouse).
95
- // Falls back to local npx lighthouse if API unavailable.
100
+ // Falls back to the local Lighthouse dependency if API unavailable.
96
101
  try {
97
102
  const apiResult = await callScannerApi(targetUrl);
98
103
  if (apiResult.available) return apiResult;
@@ -110,7 +115,7 @@ async function lighthouseScan(input) {
110
115
 
111
116
  const invocation = lighthouseInvocation(targetUrl, outputPath);
112
117
  if (!invocation) {
113
- return { available: false, reason: "npx is not available next to this Node.js" };
118
+ return { available: false, reason: "Lighthouse is not installed locally" };
114
119
  }
115
120
  execFileSync(invocation.file, invocation.args, invocation.options);
116
121
 
@@ -1,4 +1,5 @@
1
1
  import { randomUUID } from "node:crypto";
2
+ import { bindOutputFileSystem, requireOutputFileSystem } from "../../config/output-filesystem.js";
2
3
  import { evaluateBudget } from "../../cost/budget.js";
3
4
  import {
4
5
  normalizeRunEvent,
@@ -28,6 +29,7 @@ export function createToolDispatcher(toolMap, readOnlyTools) {
28
29
  const READ_ONLY_TOOLS = new Set(readOnlyTools || []);
29
30
 
30
31
  async function dispatchTool(toolName, input, ctx) {
32
+ if (ctx.artifactDir) requireOutputFileSystem(ctx.outputFs);
31
33
  const handler = TOOL_MAP[toolName];
32
34
  if (!handler) {
33
35
  throw new ToolDispatchError(`Unknown tool: ${toolName}`);
@@ -186,9 +188,9 @@ export function createToolDispatcher(toolMap, readOnlyTools) {
186
188
  const serialized = JSON.stringify(result);
187
189
  if (serialized.length > RESULT_PERSIST_THRESHOLD && ctx.artifactDir) {
188
190
  const refPath = `${ctx.artifactDir}/tool-results/${eventId}.json`;
189
- const fsp = await import("node:fs/promises");
190
- await fsp.mkdir(`${ctx.artifactDir}/tool-results`, { recursive: true });
191
- await fsp.writeFile(refPath, serialized, "utf-8");
191
+ const outputFs = requireOutputFileSystem(ctx.outputFs);
192
+ await outputFs.mkdir(`${ctx.artifactDir}/tool-results`, { recursive: true });
193
+ await outputFs.writeFile(refPath, serialized, "utf-8");
192
194
  return {
193
195
  _persisted: true,
194
196
  _refPath: refPath,
@@ -224,9 +226,11 @@ export function createAgentContext({
224
226
  sessionId,
225
227
  runId,
226
228
  artifactDir,
229
+ outputFs,
227
230
  onEvent,
228
231
  }) {
229
- return {
232
+ if (artifactDir || outputFs !== undefined) requireOutputFileSystem(outputFs);
233
+ const context = {
230
234
  agentIdentity,
231
235
  budget: {
232
236
  maxCostUsd: budget?.maxCostUsd ?? 5.0,
@@ -250,6 +254,7 @@ export function createAgentContext({
250
254
  lastToolName: null,
251
255
  onEvent,
252
256
  };
257
+ return outputFs === undefined ? context : bindOutputFileSystem(context, outputFs);
253
258
  }
254
259
 
255
260
  function snapshotUsage(ctx) {
@@ -295,7 +300,7 @@ async function safeAppendEvent(ctx, eventData) {
295
300
  });
296
301
  if (ctx.artifactDir) {
297
302
  await appendRunEvent(
298
- { targetPath: ctx.artifactDir, outputDir: ctx.artifactDir },
303
+ { targetPath: ctx.artifactDir, outputDir: ctx.artifactDir, outputFs: ctx.outputFs },
299
304
  normalized,
300
305
  );
301
306
  }
package/src/ai/aidenid.js CHANGED
@@ -440,7 +440,9 @@ export async function revokeIdentity({
440
440
  });
441
441
 
442
442
  const response = await fetchImpl(
443
- `${normalizedApiUrl}/v1/identities/${urlPathSegment(normalizedIdentityId, { label: "identityId" })}/revoke`,
443
+ // AIdenID has no single-identity /revoke route: POST /{identity_id}/squash is the revoke (it records SQUASHED
444
+ // and schedules cleanup). /revoke-children (cascade) is a separate, unchanged route.
445
+ `${normalizedApiUrl}/v1/identities/${urlPathSegment(normalizedIdentityId, { label: "identityId" })}/squash`,
444
446
  {
445
447
  method: "POST",
446
448
  headers: requestHeaders,
@@ -450,7 +452,7 @@ export async function revokeIdentity({
450
452
  if (!response.ok) {
451
453
  const details = await parseErrorBody(response);
452
454
  throw new Error(
453
- `AIdenID revoke request failed with status ${response.status}${details ? `: ${details}` : ""}`
455
+ `AIdenID squash (revoke) request failed with status ${response.status}${details ? `: ${details}` : ""}`
454
456
  );
455
457
  }
456
458
 
@@ -1,5 +1,6 @@
1
1
  import fsp from "node:fs/promises";
2
2
  import path from "node:path";
3
+ import { requireOutputFileSystem } from "../config/output-filesystem.js";
3
4
 
4
5
  const REGISTRY_SCHEMA_VERSION = "1.0.0";
5
6
 
@@ -104,15 +105,16 @@ async function loadRegistryInternal(filePath) {
104
105
  }
105
106
  }
106
107
 
107
- async function writeRegistryInternal(filePath, registry = {}) {
108
+ async function writeRegistryInternal(filePath, registry = {}, outputFs) {
109
+ requireOutputFileSystem(outputFs);
108
110
  const normalized = {
109
111
  schemaVersion: REGISTRY_SCHEMA_VERSION,
110
112
  generatedAt: new Date().toISOString(),
111
113
  domains: Array.isArray(registry.domains) ? registry.domains.map(normalizeDomainRecord) : [],
112
114
  targets: Array.isArray(registry.targets) ? registry.targets.map(normalizeTargetRecord) : [],
113
115
  };
114
- await fsp.mkdir(path.dirname(filePath), { recursive: true });
115
- await fsp.writeFile(filePath, `${JSON.stringify(normalized, null, 2)}\n`, "utf-8");
116
+ await outputFs.mkdir(path.dirname(filePath), { recursive: true });
117
+ await outputFs.writeFile(filePath, `${JSON.stringify(normalized, null, 2)}\n`, "utf-8");
116
118
  return normalized;
117
119
  }
118
120
 
@@ -153,11 +155,13 @@ export async function getTargetById({ outputRoot, targetId } = {}) {
153
155
  }
154
156
 
155
157
  export async function recordDomainProofResponse({
158
+ outputFs,
156
159
  outputRoot,
157
160
  domain = {},
158
161
  proof = {},
159
162
  context = {},
160
163
  } = {}) {
164
+ requireOutputFileSystem(outputFs);
161
165
  const domainId = normalizeString(domain.id || context.domainId);
162
166
  if (!domainId) {
163
167
  throw new Error("Cannot record domain without domain.id.");
@@ -201,7 +205,7 @@ export async function recordDomainProofResponse({
201
205
  registry.domains.push(nextRecord);
202
206
  }
203
207
 
204
- const saved = await writeRegistryInternal(registryPath, registry);
208
+ const saved = await writeRegistryInternal(registryPath, registry, outputFs);
205
209
  const record = saved.domains.find((item) => item.domainId === domainId) || null;
206
210
  return {
207
211
  registryPath,
@@ -210,11 +214,13 @@ export async function recordDomainProofResponse({
210
214
  }
211
215
 
212
216
  export async function recordTargetProofResponse({
217
+ outputFs,
213
218
  outputRoot,
214
219
  target = {},
215
220
  proof = {},
216
221
  context = {},
217
222
  } = {}) {
223
+ requireOutputFileSystem(outputFs);
218
224
  const targetId = normalizeString(target.id || context.targetId);
219
225
  if (!targetId) {
220
226
  throw new Error("Cannot record target without target.id.");
@@ -259,7 +265,7 @@ export async function recordTargetProofResponse({
259
265
  registry.targets.push(nextRecord);
260
266
  }
261
267
 
262
- const saved = await writeRegistryInternal(registryPath, registry);
268
+ const saved = await writeRegistryInternal(registryPath, registry, outputFs);
263
269
  const record = saved.targets.find((item) => item.targetId === targetId) || null;
264
270
  return {
265
271
  registryPath,
@@ -1,5 +1,6 @@
1
1
  import fsp from "node:fs/promises";
2
2
  import path from "node:path";
3
+ import { requireOutputFileSystem } from "../config/output-filesystem.js";
3
4
 
4
5
  const REGISTRY_SCHEMA_VERSION = "1.0.0";
5
6
  const TERMINAL_IDENTITY_STATUSES = new Set(["SQUASHED"]);
@@ -96,14 +97,15 @@ async function loadRegistryInternal(filePath) {
96
97
  }
97
98
  }
98
99
 
99
- async function writeRegistryInternal(filePath, registry = {}) {
100
+ async function writeRegistryInternal(filePath, registry = {}, outputFs) {
101
+ requireOutputFileSystem(outputFs);
100
102
  const normalized = {
101
103
  schemaVersion: REGISTRY_SCHEMA_VERSION,
102
104
  generatedAt: new Date().toISOString(),
103
105
  identities: Array.isArray(registry.identities) ? registry.identities.map(normalizeIdentityRecord) : [],
104
106
  };
105
- await fsp.mkdir(path.dirname(filePath), { recursive: true });
106
- await fsp.writeFile(filePath, `${JSON.stringify(normalized, null, 2)}\n`, "utf-8");
107
+ await outputFs.mkdir(path.dirname(filePath), { recursive: true });
108
+ await outputFs.writeFile(filePath, `${JSON.stringify(normalized, null, 2)}\n`, "utf-8");
107
109
  return normalized;
108
110
  }
109
111
 
@@ -130,10 +132,12 @@ export async function getIdentityById({ outputRoot, identityId } = {}) {
130
132
  }
131
133
 
132
134
  export async function recordProvisionedIdentity({
135
+ outputFs,
133
136
  outputRoot,
134
137
  response = {},
135
138
  context = {},
136
139
  } = {}) {
140
+ requireOutputFileSystem(outputFs);
137
141
  const registryPath = resolveIdentityRegistryPath({ outputRoot });
138
142
  const registry = await loadRegistryInternal(registryPath);
139
143
  const identityId = normalizeString(response.id);
@@ -180,7 +184,7 @@ export async function recordProvisionedIdentity({
180
184
  registry.identities.push(nextRecord);
181
185
  }
182
186
 
183
- const saved = await writeRegistryInternal(registryPath, registry);
187
+ const saved = await writeRegistryInternal(registryPath, registry, outputFs);
184
188
  const identity = saved.identities.find((item) => item.identityId === identityId) || null;
185
189
  return {
186
190
  registryPath,
@@ -189,6 +193,7 @@ export async function recordProvisionedIdentity({
189
193
  }
190
194
 
191
195
  export async function updateIdentityStatus({
196
+ outputFs,
192
197
  outputRoot,
193
198
  identityId,
194
199
  status,
@@ -196,6 +201,7 @@ export async function updateIdentityStatus({
196
201
  squashedAt = "",
197
202
  metadataPatch = {},
198
203
  } = {}) {
204
+ requireOutputFileSystem(outputFs);
199
205
  const normalizedIdentityId = normalizeString(identityId);
200
206
  if (!normalizedIdentityId) {
201
207
  throw new Error("identityId is required.");
@@ -226,7 +232,7 @@ export async function updateIdentityStatus({
226
232
  },
227
233
  });
228
234
 
229
- const saved = await writeRegistryInternal(registryPath, registry);
235
+ const saved = await writeRegistryInternal(registryPath, registry, outputFs);
230
236
  return {
231
237
  registryPath,
232
238
  identity: saved.identities[index],
@@ -1,5 +1,6 @@
1
1
  import fsp from "node:fs/promises";
2
2
  import path from "node:path";
3
+ import { requireOutputFileSystem } from "../config/output-filesystem.js";
3
4
 
4
5
  const REGISTRY_SCHEMA_VERSION = "1.0.0";
5
6
 
@@ -60,14 +61,15 @@ async function loadRegistryInternal(filePath) {
60
61
  }
61
62
  }
62
63
 
63
- async function writeRegistryInternal(filePath, registry = {}) {
64
+ async function writeRegistryInternal(filePath, registry = {}, outputFs) {
65
+ requireOutputFileSystem(outputFs);
64
66
  const normalized = {
65
67
  schemaVersion: REGISTRY_SCHEMA_VERSION,
66
68
  generatedAt: new Date().toISOString(),
67
69
  sites: Array.isArray(registry.sites) ? registry.sites.map(normalizeSiteRecord) : [],
68
70
  };
69
- await fsp.mkdir(path.dirname(filePath), { recursive: true });
70
- await fsp.writeFile(filePath, `${JSON.stringify(normalized, null, 2)}\n`, "utf-8");
71
+ await outputFs.mkdir(path.dirname(filePath), { recursive: true });
72
+ await outputFs.writeFile(filePath, `${JSON.stringify(normalized, null, 2)}\n`, "utf-8");
71
73
  return normalized;
72
74
  }
73
75
 
@@ -85,10 +87,12 @@ export async function listSites({ outputRoot, identityId = "" } = {}) {
85
87
  }
86
88
 
87
89
  export async function recordTemporarySite({
90
+ outputFs,
88
91
  outputRoot,
89
92
  site = {},
90
93
  context = {},
91
94
  } = {}) {
95
+ requireOutputFileSystem(outputFs);
92
96
  const siteId = normalizeString(site.id || context.siteId);
93
97
  if (!siteId) {
94
98
  throw new Error("Cannot record temporary site without site.id.");
@@ -136,7 +140,7 @@ export async function recordTemporarySite({
136
140
  registry.sites.push(nextRecord);
137
141
  }
138
142
 
139
- const saved = await writeRegistryInternal(registryPath, registry);
143
+ const saved = await writeRegistryInternal(registryPath, registry, outputFs);
140
144
  const savedSite = saved.sites.find((item) => item.siteId === siteId) || null;
141
145
  return {
142
146
  registryPath,
@@ -4,7 +4,8 @@ import path from "node:path";
4
4
 
5
5
  import { resolveAgentIdPath } from "../agents/agent-id-path.js";
6
6
  import { userCredential } from "../auth/credential-destinations.js";
7
- import { resolveOutputRoot } from "../config/service.js";
7
+ import { resolveOutputContext } from "../config/service.js";
8
+ import { bindOutputFileSystem, requireOutputFileSystem } from "../config/output-filesystem.js";
8
9
  import { createAgentEvent } from "../events/schema.js";
9
10
  import { resolveCodebaseIngest } from "../ingest/engine.js";
10
11
  import { runDeterministicReviewPipeline } from "../review/local-review.js";
@@ -271,7 +272,9 @@ async function buildSpecialistSeed({
271
272
  deterministicBaseline,
272
273
  ingest,
273
274
  agentsDirectory,
275
+ outputFs,
274
276
  }) {
277
+ requireOutputFileSystem(outputFs);
275
278
  let findings = [];
276
279
  let summary = severitySummary(findings);
277
280
  let confidence = computeConfidenceFloor(agent.confidenceFloor, findings.length);
@@ -285,7 +288,7 @@ async function buildSpecialistSeed({
285
288
  summary = securitySpecialist.summary;
286
289
  confidence = securitySpecialist.confidence;
287
290
  specialistReportPath = path.join(agentsDirectory, "SECURITY_AGENT_REPORT.md");
288
- await fsp.writeFile(
291
+ await outputFs.writeFile(
289
292
  specialistReportPath,
290
293
  `${renderSecuritySpecialistMarkdown(securitySpecialist).trim()}\n`,
291
294
  "utf-8"
@@ -299,7 +302,7 @@ async function buildSpecialistSeed({
299
302
  summary = architectureSpecialist.summary;
300
303
  confidence = architectureSpecialist.confidence;
301
304
  specialistReportPath = path.join(agentsDirectory, "ARCHITECTURE_AGENT_REPORT.md");
302
- await fsp.writeFile(
305
+ await outputFs.writeFile(
303
306
  specialistReportPath,
304
307
  `${renderArchitectureSpecialistMarkdown(architectureSpecialist).trim()}\n`,
305
308
  "utf-8"
@@ -313,7 +316,7 @@ async function buildSpecialistSeed({
313
316
  summary = testingSpecialist.summary;
314
317
  confidence = testingSpecialist.confidence;
315
318
  specialistReportPath = path.join(agentsDirectory, "TESTING_AGENT_REPORT.md");
316
- await fsp.writeFile(
319
+ await outputFs.writeFile(
317
320
  specialistReportPath,
318
321
  `${renderTestingSpecialistMarkdown(testingSpecialist).trim()}\n`,
319
322
  "utf-8"
@@ -327,7 +330,7 @@ async function buildSpecialistSeed({
327
330
  summary = performanceSpecialist.summary;
328
331
  confidence = performanceSpecialist.confidence;
329
332
  specialistReportPath = path.join(agentsDirectory, "PERFORMANCE_AGENT_REPORT.md");
330
- await fsp.writeFile(
333
+ await outputFs.writeFile(
331
334
  specialistReportPath,
332
335
  `${renderPerformanceSpecialistMarkdown(performanceSpecialist).trim()}\n`,
333
336
  "utf-8"
@@ -341,7 +344,7 @@ async function buildSpecialistSeed({
341
344
  summary = complianceSpecialist.summary;
342
345
  confidence = complianceSpecialist.confidence;
343
346
  specialistReportPath = path.join(agentsDirectory, "COMPLIANCE_AGENT_REPORT.md");
344
- await fsp.writeFile(
347
+ await outputFs.writeFile(
345
348
  specialistReportPath,
346
349
  `${renderComplianceSpecialistMarkdown(complianceSpecialist).trim()}\n`,
347
350
  "utf-8"
@@ -355,7 +358,7 @@ async function buildSpecialistSeed({
355
358
  summary = documentationSpecialist.summary;
356
359
  confidence = documentationSpecialist.confidence;
357
360
  specialistReportPath = path.join(agentsDirectory, "DOCUMENTATION_AGENT_REPORT.md");
358
- await fsp.writeFile(
361
+ await outputFs.writeFile(
359
362
  specialistReportPath,
360
363
  `${renderDocumentationSpecialistMarkdown(documentationSpecialist).trim()}\n`,
361
364
  "utf-8"
@@ -388,7 +391,7 @@ export async function runAuditOrchestrator({
388
391
  const isolationMode = normalizeIsolationMode(isolation);
389
392
  const useDeterministicSeed = seedFromDeterministic !== false;
390
393
  const requestedOmarGateReuse = normalizeString(reuseOmarGate);
391
- const outputRoot = await resolveOutputRoot({
394
+ const { outputRoot, outputFs } = await resolveOutputContext({
392
395
  cwd: normalizedTargetPath,
393
396
  outputDirOverride: outputDir,
394
397
  env: process.env,
@@ -402,7 +405,7 @@ export async function runAuditOrchestrator({
402
405
  const agentPaths = new Map(
403
406
  agents.map((agent) => [agent, resolveAgentIdPath(agentsDirectory, agent.id, ".json")])
404
407
  );
405
- await fsp.mkdir(agentsDirectory, { recursive: true });
408
+ await outputFs.mkdir(agentsDirectory, { recursive: true });
406
409
  const blackboard = createBlackboard({
407
410
  runId,
408
411
  scope: "audit-orchestrator",
@@ -655,6 +658,7 @@ export async function runAuditOrchestrator({
655
658
  deterministicBaseline,
656
659
  ingest,
657
660
  agentsDirectory,
661
+ outputFs,
658
662
  })
659
663
  : {
660
664
  findings: [],
@@ -682,6 +686,7 @@ export async function runAuditOrchestrator({
682
686
  sharedContext,
683
687
  hybridContext,
684
688
  artifactDir: agentsDirectory,
689
+ outputFs,
685
690
  provider,
686
691
  onEvent,
687
692
  clientFactory,
@@ -761,7 +766,7 @@ export async function runAuditOrchestrator({
761
766
  confidence,
762
767
  });
763
768
  const agentPath = agentPaths.get(agent);
764
- await fsp.writeFile(agentPath, `${JSON.stringify(result, null, 2)}\n`, "utf-8");
769
+ await outputFs.writeFile(agentPath, `${JSON.stringify(result, null, 2)}\n`, "utf-8");
765
770
  emitAuditLifecycleEvent(
766
771
  onEvent,
767
772
  runId,
@@ -844,6 +849,7 @@ export async function runAuditOrchestrator({
844
849
  `Reconcile complete: ${uniqueFindings.length} unique finding(s).`
845
850
  );
846
851
  const sharedMemoryArtifact = await writeBlackboardArtifact(blackboard, {
852
+ outputFs,
847
853
  outputRoot,
848
854
  });
849
855
  const sharedMemorySummary = summarizeBlackboard(blackboard);
@@ -907,9 +913,10 @@ export async function runAuditOrchestrator({
907
913
 
908
914
  const reportJsonPath = path.join(runDirectory, "AUDIT_REPORT.json");
909
915
  const reportMarkdownPath = path.join(runDirectory, "AUDIT_REPORT.md");
910
- await fsp.writeFile(reportJsonPath, `${JSON.stringify(report, null, 2)}\n`, "utf-8");
911
- await fsp.writeFile(reportMarkdownPath, `${buildAuditMarkdown(report).trim()}\n`, "utf-8");
916
+ await outputFs.writeFile(reportJsonPath, `${JSON.stringify(report, null, 2)}\n`, "utf-8");
917
+ await outputFs.writeFile(reportMarkdownPath, `${buildAuditMarkdown(report).trim()}\n`, "utf-8");
912
918
  const ddPackage = await writeDdPackage({
919
+ outputFs,
913
920
  report,
914
921
  runDirectory,
915
922
  });
@@ -930,11 +937,11 @@ export async function runAuditOrchestrator({
930
937
  `Audit orchestrator complete: P0=${summary.P0} P1=${summary.P1} P2=${summary.P2} P3=${summary.P3}.`
931
938
  );
932
939
 
933
- return {
940
+ return bindOutputFileSystem({
934
941
  ...report,
935
942
  reportJsonPath,
936
943
  reportMarkdownPath,
937
944
  ddPackage,
938
- };
945
+ }, outputFs);
939
946
  }
940
947
 
@@ -1,3 +1,4 @@
1
+ import { requireOutputFileSystem } from "../config/output-filesystem.js";
1
2
  import fsp from "node:fs/promises";
2
3
  import path from "node:path";
3
4
 
@@ -116,7 +117,8 @@ export function buildDdPackageManifest(report = {}) {
116
117
  };
117
118
  }
118
119
 
119
- export async function writeDdPackage({ report = {}, runDirectory } = {}) {
120
+ export async function writeDdPackage({ report = {}, runDirectory, outputFs } = {}) {
121
+ requireOutputFileSystem(outputFs);
120
122
  const resolvedRunDirectory = normalizeRunDirectory(runDirectory || report.runDirectory);
121
123
  const manifest = buildDdPackageManifest({
122
124
  ...report,
@@ -138,9 +140,9 @@ export async function writeDdPackage({ report = {}, runDirectory } = {}) {
138
140
  findingsIndex,
139
141
  });
140
142
 
141
- await fsp.writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, "utf-8");
142
- await fsp.writeFile(findingsIndexPath, `${JSON.stringify(findingsIndex, null, 2)}\n`, "utf-8");
143
- await fsp.writeFile(executiveSummaryPath, `${executiveSummary.trim()}\n`, "utf-8");
143
+ await outputFs.writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, "utf-8");
144
+ await outputFs.writeFile(findingsIndexPath, `${JSON.stringify(findingsIndex, null, 2)}\n`, "utf-8");
145
+ await outputFs.writeFile(executiveSummaryPath, `${executiveSummary.trim()}\n`, "utf-8");
144
146
 
145
147
  return {
146
148
  manifest,