sentinelayer-cli 0.8.0 → 0.8.1

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 (153) hide show
  1. package/README.md +13 -0
  2. package/package.json +4 -4
  3. package/src/agents/ai-governance/index.js +12 -0
  4. package/src/agents/ai-governance/tools/base.js +171 -0
  5. package/src/agents/ai-governance/tools/eval-regression.js +47 -0
  6. package/src/agents/ai-governance/tools/hitl-audit.js +81 -0
  7. package/src/agents/ai-governance/tools/index.js +52 -0
  8. package/src/agents/ai-governance/tools/prompt-drift.js +42 -0
  9. package/src/agents/ai-governance/tools/provenance-check.js +69 -0
  10. package/src/agents/backend/index.js +12 -0
  11. package/src/agents/backend/tools/base.js +189 -0
  12. package/src/agents/backend/tools/circuit-breaker-check.js +123 -0
  13. package/src/agents/backend/tools/idempotency-audit.js +105 -0
  14. package/src/agents/backend/tools/index.js +87 -0
  15. package/src/agents/backend/tools/retry-audit.js +132 -0
  16. package/src/agents/backend/tools/timeout-audit.js +144 -0
  17. package/src/agents/code-quality/index.js +12 -0
  18. package/src/agents/code-quality/tools/base.js +159 -0
  19. package/src/agents/code-quality/tools/complexity-measure.js +197 -0
  20. package/src/agents/code-quality/tools/coupling-analysis.js +81 -0
  21. package/src/agents/code-quality/tools/cycle-detect.js +49 -0
  22. package/src/agents/code-quality/tools/dep-graph.js +196 -0
  23. package/src/agents/code-quality/tools/index.js +89 -0
  24. package/src/agents/data-layer/index.js +12 -0
  25. package/src/agents/data-layer/tools/base.js +181 -0
  26. package/src/agents/data-layer/tools/index-audit.js +165 -0
  27. package/src/agents/data-layer/tools/index.js +83 -0
  28. package/src/agents/data-layer/tools/migration-scan.js +135 -0
  29. package/src/agents/data-layer/tools/query-explain.js +120 -0
  30. package/src/agents/data-layer/tools/tenancy-scan.js +166 -0
  31. package/src/agents/documentation/index.js +12 -0
  32. package/src/agents/documentation/tools/api-diff.js +91 -0
  33. package/src/agents/documentation/tools/base.js +151 -0
  34. package/src/agents/documentation/tools/dead-link-check.js +58 -0
  35. package/src/agents/documentation/tools/docstring-coverage.js +78 -0
  36. package/src/agents/documentation/tools/index.js +52 -0
  37. package/src/agents/documentation/tools/readme-freshness.js +61 -0
  38. package/src/agents/envelope/fix-cycle.js +45 -0
  39. package/src/agents/envelope/index.js +31 -0
  40. package/src/agents/envelope/loop.js +150 -0
  41. package/src/agents/envelope/pulse.js +18 -0
  42. package/src/agents/envelope/stream.js +40 -0
  43. package/src/agents/infrastructure/index.js +12 -0
  44. package/src/agents/infrastructure/tools/base.js +171 -0
  45. package/src/agents/infrastructure/tools/checkov-run.js +32 -0
  46. package/src/agents/infrastructure/tools/drift-detect.js +59 -0
  47. package/src/agents/infrastructure/tools/iam-least-priv-check.js +78 -0
  48. package/src/agents/infrastructure/tools/index.js +52 -0
  49. package/src/agents/infrastructure/tools/tflint-run.js +31 -0
  50. package/src/agents/jules/loop.js +7 -4
  51. package/src/agents/jules/swarm/sub-agent.js +5 -1
  52. package/src/agents/jules/tools/auth-audit.js +10 -1
  53. package/src/agents/mode.js +113 -0
  54. package/src/agents/observability/index.js +12 -0
  55. package/src/agents/observability/tools/alert-audit.js +39 -0
  56. package/src/agents/observability/tools/base.js +181 -0
  57. package/src/agents/observability/tools/dashboard-gap.js +42 -0
  58. package/src/agents/observability/tools/index.js +54 -0
  59. package/src/agents/observability/tools/log-schema-check.js +74 -0
  60. package/src/agents/observability/tools/span-coverage.js +74 -0
  61. package/src/agents/persona-visuals.js +38 -0
  62. package/src/agents/release/index.js +12 -0
  63. package/src/agents/release/tools/base.js +181 -0
  64. package/src/agents/release/tools/changelog-diff.js +86 -0
  65. package/src/agents/release/tools/feature-flag-audit.js +126 -0
  66. package/src/agents/release/tools/index.js +61 -0
  67. package/src/agents/release/tools/rollback-verify.js +129 -0
  68. package/src/agents/release/tools/semver-check.js +109 -0
  69. package/src/agents/reliability/index.js +12 -0
  70. package/src/agents/reliability/tools/backpressure-check.js +129 -0
  71. package/src/agents/reliability/tools/base.js +181 -0
  72. package/src/agents/reliability/tools/chaos-probe.js +109 -0
  73. package/src/agents/reliability/tools/graceful-degradation-check.js +114 -0
  74. package/src/agents/reliability/tools/health-check-audit.js +111 -0
  75. package/src/agents/reliability/tools/index.js +87 -0
  76. package/src/agents/run-persona.js +109 -0
  77. package/src/agents/security/index.js +12 -0
  78. package/src/agents/security/tools/authz-audit.js +134 -0
  79. package/src/agents/security/tools/base.js +190 -0
  80. package/src/agents/security/tools/crypto-review.js +175 -0
  81. package/src/agents/security/tools/index.js +97 -0
  82. package/src/agents/security/tools/sast-scan.js +175 -0
  83. package/src/agents/security/tools/secrets-scan.js +216 -0
  84. package/src/agents/supply-chain/index.js +12 -0
  85. package/src/agents/supply-chain/tools/attestation-check.js +42 -0
  86. package/src/agents/supply-chain/tools/base.js +151 -0
  87. package/src/agents/supply-chain/tools/index.js +52 -0
  88. package/src/agents/supply-chain/tools/lockfile-integrity.js +73 -0
  89. package/src/agents/supply-chain/tools/package-verify.js +56 -0
  90. package/src/agents/supply-chain/tools/sbom-diff.js +34 -0
  91. package/src/agents/testing/index.js +12 -0
  92. package/src/agents/testing/tools/base.js +202 -0
  93. package/src/agents/testing/tools/coverage-gap.js +144 -0
  94. package/src/agents/testing/tools/flake-detect.js +125 -0
  95. package/src/agents/testing/tools/index.js +85 -0
  96. package/src/agents/testing/tools/mutation-test.js +143 -0
  97. package/src/agents/testing/tools/snapshot-diff.js +103 -0
  98. package/src/auth/gate.js +65 -37
  99. package/src/cli.js +1 -1
  100. package/src/commands/chat.js +3 -10
  101. package/src/commands/legacy-args.js +10 -0
  102. package/src/commands/omargate.js +36 -2
  103. package/src/commands/persona.js +46 -1
  104. package/src/commands/scan.js +3 -10
  105. package/src/commands/session.js +654 -6
  106. package/src/commands/spec.js +3 -10
  107. package/src/coord/events-log.js +141 -0
  108. package/src/coord/handshake.js +719 -0
  109. package/src/coord/index.js +35 -0
  110. package/src/coord/paths.js +84 -0
  111. package/src/coord/priority.js +62 -0
  112. package/src/coord/tarjan.js +157 -0
  113. package/src/cost/tokenizer.js +160 -0
  114. package/src/cost/tracker.js +61 -0
  115. package/src/daemon/artifact-lineage.js +362 -0
  116. package/src/daemon/assignment-ledger.js +117 -0
  117. package/src/daemon/ast-drift.js +496 -0
  118. package/src/daemon/ingest-refresh.js +69 -2
  119. package/src/ingest/engine.js +15 -0
  120. package/src/ingest/ownership.js +380 -0
  121. package/src/legacy-cli.js +68 -1
  122. package/src/orchestrator/kai-chen.js +126 -0
  123. package/src/review/ai-review.js +3 -10
  124. package/src/review/compliance-pack.js +389 -0
  125. package/src/review/investor-dd-config.js +54 -0
  126. package/src/review/investor-dd-file-loop.js +303 -0
  127. package/src/review/investor-dd-file-router.js +406 -0
  128. package/src/review/investor-dd-html-report.js +233 -0
  129. package/src/review/investor-dd-notification.js +120 -0
  130. package/src/review/investor-dd-orchestrator.js +405 -0
  131. package/src/review/investor-dd-persona-runner.js +275 -0
  132. package/src/review/live-validator.js +253 -0
  133. package/src/review/omargate-orchestrator.js +90 -2
  134. package/src/review/persona-prompts.js +244 -56
  135. package/src/review/reconciliation-rules.js +329 -0
  136. package/src/review/reproducibility-chain.js +136 -0
  137. package/src/review/scan-modes.js +102 -3
  138. package/src/session/agent-registry.js +7 -0
  139. package/src/session/analytics.js +479 -0
  140. package/src/session/daemon.js +609 -14
  141. package/src/session/file-locks.js +666 -0
  142. package/src/session/paths.js +4 -0
  143. package/src/session/recap.js +567 -0
  144. package/src/session/redact.js +82 -0
  145. package/src/session/runtime-bridge.js +24 -1
  146. package/src/session/scoring.js +406 -0
  147. package/src/session/setup-guides.js +304 -0
  148. package/src/session/store.js +318 -2
  149. package/src/session/stream.js +9 -1
  150. package/src/session/sync.js +753 -0
  151. package/src/session/tasks.js +1054 -0
  152. package/src/session/templates.js +188 -0
  153. package/src/swarm/runtime.js +1 -8
@@ -3,7 +3,9 @@ import fsp from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import process from "node:process";
5
5
 
6
+ import { buildArtifactLineageIndex, verifyArtifactChain } from "../daemon/artifact-lineage.js";
6
7
  import { collectCodebaseIngest } from "../ingest/engine.js";
8
+ import { computeSessionAnalytics } from "./analytics.js";
7
9
  import { resolveSessionPaths, resolveSessionsRoot } from "./paths.js";
8
10
  import { appendToStream } from "./stream.js";
9
11
 
@@ -30,6 +32,17 @@ function normalizePositiveInteger(value, fallbackValue) {
30
32
  return Math.floor(normalized);
31
33
  }
32
34
 
35
+ function normalizeNonNegativeInteger(value, fallbackValue = 0) {
36
+ if (value === undefined || value === null || normalizeString(value) === "") {
37
+ return fallbackValue;
38
+ }
39
+ const normalized = Number(value);
40
+ if (!Number.isFinite(normalized) || normalized < 0) {
41
+ return fallbackValue;
42
+ }
43
+ return Math.floor(normalized);
44
+ }
45
+
33
46
  function normalizeIsoTimestamp(value, fallbackIso = new Date().toISOString()) {
34
47
  const normalized = normalizeString(value);
35
48
  if (!normalized) {
@@ -109,6 +122,90 @@ function normalizeCodebaseContext(ingest = {}) {
109
122
  };
110
123
  }
111
124
 
125
+ function normalizeStringList(values = []) {
126
+ if (!Array.isArray(values)) {
127
+ return [];
128
+ }
129
+ return Array.from(
130
+ new Set(
131
+ values
132
+ .map((value) => normalizeString(value))
133
+ .filter(Boolean)
134
+ )
135
+ );
136
+ }
137
+
138
+ function toPosixPath(value = "") {
139
+ return String(value || "").replace(/\\/g, "/");
140
+ }
141
+
142
+ function toRelativePosix(baseDir, absolutePath) {
143
+ if (!absolutePath) {
144
+ return "";
145
+ }
146
+ return toPosixPath(path.relative(baseDir, absolutePath));
147
+ }
148
+
149
+ function normalizeDateKeyFromCloseoutPath(closeoutPath = "", fallbackIso = new Date().toISOString()) {
150
+ const normalized = toPosixPath(closeoutPath);
151
+ const match = /\/observability\/(\d{4}-\d{2}-\d{2})\//.exec(`/${normalized}`);
152
+ if (match) {
153
+ return match[1];
154
+ }
155
+ return normalizeIsoTimestamp(fallbackIso).slice(0, 10);
156
+ }
157
+
158
+ function normalizeSharedResources(raw = {}, { nowIso = new Date().toISOString() } = {}) {
159
+ const source = raw && typeof raw === "object" ? raw : {};
160
+ return {
161
+ provisionedIdentityIds: normalizeStringList(source.provisionedIdentityIds),
162
+ provisioningTags: normalizeStringList(source.provisioningTags),
163
+ provisionCount: normalizeNonNegativeInteger(source.provisionCount, 0),
164
+ lastProvisionedAt: source.lastProvisionedAt
165
+ ? normalizeIsoTimestamp(source.lastProvisionedAt, nowIso)
166
+ : null,
167
+ updatedAt: normalizeIsoTimestamp(source.updatedAt, nowIso),
168
+ };
169
+ }
170
+
171
+ function normalizeTemplateAgent(raw = {}) {
172
+ const source = raw && typeof raw === "object" ? raw : {};
173
+ return {
174
+ role: normalizeString(source.role) || "agent",
175
+ instructions: normalizeString(source.instructions) || "Follow session guidance.",
176
+ };
177
+ }
178
+
179
+ function normalizeSessionTemplate(raw = null) {
180
+ if (!raw || typeof raw !== "object") {
181
+ return null;
182
+ }
183
+ const source = raw;
184
+ const id = normalizeString(source.id || source.name);
185
+ if (!id) {
186
+ return null;
187
+ }
188
+ const suggestedAgents = Array.isArray(source.suggestedAgents)
189
+ ? source.suggestedAgents.map((agent) => normalizeTemplateAgent(agent))
190
+ : [];
191
+ const ttlHours = normalizePositiveInteger(source.ttlHours, 1);
192
+ const normalizedAutoProvision =
193
+ source.autoProvisionEmails === undefined || source.autoProvisionEmails === null
194
+ ? null
195
+ : normalizePositiveInteger(source.autoProvisionEmails, 1);
196
+
197
+ return {
198
+ id,
199
+ version: normalizeString(source.version) || "1.0.0",
200
+ registryVersion: normalizeString(source.registryVersion) || "1.0.0",
201
+ description: normalizeString(source.description),
202
+ daemonModel: normalizeString(source.daemonModel),
203
+ ttlHours,
204
+ autoProvisionEmails: normalizedAutoProvision,
205
+ suggestedAgents,
206
+ };
207
+ }
208
+
112
209
  async function collectSessionCodebaseContext(targetPath) {
113
210
  const cachedIngestPath = path.join(targetPath, ".sentinelayer", "CODEBASE_INGEST.json");
114
211
  const cachedIngest = await readJsonFile(cachedIngestPath, { allowMissing: true });
@@ -119,6 +216,102 @@ async function collectSessionCodebaseContext(targetPath) {
119
216
  return normalizeCodebaseContext(ingest);
120
217
  }
121
218
 
219
+ async function buildArchiveSidecars(
220
+ sessionId,
221
+ {
222
+ targetPath,
223
+ outputDir = "",
224
+ env,
225
+ homeDir,
226
+ nowIso = new Date().toISOString(),
227
+ } = {}
228
+ ) {
229
+ const normalizedSessionId = normalizeString(sessionId);
230
+ const normalizedTargetPath = path.resolve(String(targetPath || "."));
231
+ const normalizedNow = normalizeIsoTimestamp(nowIso, new Date().toISOString());
232
+
233
+ const analytics = await computeSessionAnalytics(normalizedSessionId, {
234
+ targetPath: normalizedTargetPath,
235
+ outputDir,
236
+ env,
237
+ homeDir,
238
+ nowIso: normalizedNow,
239
+ });
240
+ const lineage = await buildArtifactLineageIndex({
241
+ targetPath: normalizedTargetPath,
242
+ outputDir,
243
+ env,
244
+ homeDir,
245
+ nowIso: normalizedNow,
246
+ });
247
+
248
+ const sessionWorkItems = Array.isArray(lineage.workItems)
249
+ ? lineage.workItems.filter(
250
+ (item) => normalizeString(item?.links?.sessionId) === normalizedSessionId
251
+ )
252
+ : [];
253
+ const verification = [];
254
+
255
+ for (const workItem of sessionWorkItems) {
256
+ const workItemId = normalizeString(workItem.workItemId);
257
+ if (!workItemId) {
258
+ continue;
259
+ }
260
+ const closeoutPath = normalizeString(workItem?.artifacts?.closeoutPath);
261
+ const dateKey = normalizeDateKeyFromCloseoutPath(closeoutPath, normalizedNow);
262
+ const chain = await verifyArtifactChain({
263
+ workItemId,
264
+ date: dateKey,
265
+ targetPath: normalizedTargetPath,
266
+ outputDir,
267
+ env,
268
+ homeDir,
269
+ });
270
+ verification.push({
271
+ workItemId,
272
+ date: chain.date,
273
+ closeoutPath: closeoutPath || null,
274
+ closeoutAnchorSha256: normalizeString(workItem?.artifacts?.closeoutAnchorSha256) || null,
275
+ valid: chain.valid,
276
+ mismatchCount: Array.isArray(chain.mismatches) ? chain.mismatches.length : 0,
277
+ mismatches: Array.isArray(chain.mismatches) ? chain.mismatches : [],
278
+ });
279
+ if (!chain.valid) {
280
+ throw new Error(
281
+ `Artifact chain verification failed for work item '${workItemId}' (${dateKey}).`
282
+ );
283
+ }
284
+ }
285
+
286
+ const analyticsSidecar = {
287
+ schemaVersion: "1.0.0",
288
+ generatedAt: normalizedNow,
289
+ sessionId: normalizedSessionId,
290
+ metrics: analytics,
291
+ };
292
+ const artifactChainSidecar = {
293
+ schemaVersion: "1.0.0",
294
+ generatedAt: normalizedNow,
295
+ sessionId: normalizedSessionId,
296
+ lineageRunId: normalizeString(lineage.lineageRunId) || null,
297
+ lineageIndexPath: toRelativePosix(
298
+ normalizedTargetPath,
299
+ normalizeString(lineage.indexPath)
300
+ ) || null,
301
+ summary: {
302
+ totalWorkItemsIndexed: Number(lineage?.summary?.totalWorkItemsIndexed || 0),
303
+ sessionWorkItems: sessionWorkItems.length,
304
+ verifiedWorkItems: verification.filter((item) => item.valid).length,
305
+ },
306
+ workItems: verification,
307
+ };
308
+
309
+ return {
310
+ analyticsSidecar,
311
+ artifactChainSidecar,
312
+ };
313
+ }
314
+
122
315
  function normalizeSessionStatus(value) {
123
316
  const normalized = normalizeString(value).toLowerCase();
124
317
  if (normalized === SESSION_STATUS_EXPIRED) return SESSION_STATUS_EXPIRED;
@@ -147,6 +340,8 @@ function normalizeMetadata(raw = {}, { sessionId, targetPath, nowIso } = {}) {
147
340
  s3Path: normalizeString(raw.s3Path) || null,
148
341
  archiveStatus: normalizeString(raw.archiveStatus) || "pending",
149
342
  codebaseContext: normalizeCodebaseContext(raw.codebaseContext || {}),
343
+ sharedResources: normalizeSharedResources(raw.sharedResources || {}, { nowIso }),
344
+ template: normalizeSessionTemplate(raw.template || null),
150
345
  };
151
346
  }
152
347
 
@@ -176,6 +371,8 @@ function buildSessionPayload(metadata, paths, nowIso = new Date().toISOString())
176
371
  archivedAt: metadata.archivedAt,
177
372
  s3Path: metadata.s3Path,
178
373
  codebaseContext: metadata.codebaseContext,
374
+ sharedResources: metadata.sharedResources,
375
+ template: metadata.template,
179
376
  };
180
377
  }
181
378
 
@@ -208,6 +405,7 @@ async function saveMetadata(metadata, paths) {
208
405
  export async function createSession({
209
406
  targetPath = process.cwd(),
210
407
  ttlSeconds = DEFAULT_TTL_SECONDS,
408
+ template = null,
211
409
  } = {}) {
212
410
  const resolvedTargetPath = path.resolve(String(targetPath || "."));
213
411
  const normalizedTtlSeconds = normalizePositiveInteger(ttlSeconds, DEFAULT_TTL_SECONDS);
@@ -234,6 +432,8 @@ export async function createSession({
234
432
  s3Path: null,
235
433
  archiveStatus: "pending",
236
434
  codebaseContext,
435
+ sharedResources: normalizeSharedResources({}, { nowIso }),
436
+ template: normalizeSessionTemplate(template),
237
437
  },
238
438
  {
239
439
  sessionId,
@@ -349,7 +549,14 @@ export async function expireSession(sessionId, { targetPath = process.cwd() } =
349
549
 
350
550
  export async function archiveSession(
351
551
  sessionId,
352
- { s3Bucket, s3Prefix = "", targetPath = process.cwd() } = {}
552
+ {
553
+ s3Bucket,
554
+ s3Prefix = "",
555
+ targetPath = process.cwd(),
556
+ outputDir = "",
557
+ env,
558
+ homeDir,
559
+ } = {}
353
560
  ) {
354
561
  const loaded = await loadMetadata(sessionId, { targetPath });
355
562
  if (!loaded) {
@@ -363,6 +570,13 @@ export async function archiveSession(
363
570
  const prefixSegment = normalizedPrefix ? `${normalizedPrefix}/` : "";
364
571
  const s3Path = `s3://${normalizedBucket}/${prefixSegment}sessions/${loaded.paths.sessionId}/`;
365
572
  const nowIso = new Date().toISOString();
573
+ const sidecars = await buildArchiveSidecars(loaded.paths.sessionId, {
574
+ targetPath: loaded.targetPath,
575
+ outputDir,
576
+ env,
577
+ homeDir,
578
+ nowIso,
579
+ });
366
580
 
367
581
  loaded.metadata.status = SESSION_STATUS_ARCHIVED;
368
582
  loaded.metadata.archivedAt = nowIso;
@@ -371,13 +585,115 @@ export async function archiveSession(
371
585
  loaded.metadata.s3Path = s3Path;
372
586
  const saved = await saveMetadata(loaded.metadata, loaded.paths);
373
587
 
588
+ await Promise.all([
589
+ writeJsonFile(path.join(loaded.paths.sessionDir, "analytics.json"), sidecars.analyticsSidecar),
590
+ writeJsonFile(
591
+ path.join(loaded.paths.sessionDir, "artifact-chain.json"),
592
+ sidecars.artifactChainSidecar
593
+ ),
594
+ ]);
374
595
  await writeJsonFile(path.join(loaded.paths.sessionDir, "archive-manifest.json"), {
375
596
  sessionId: loaded.paths.sessionId,
376
597
  archivedAt: nowIso,
377
598
  s3Path,
378
- files: ["metadata.json", "stream.ndjson", "stream.1.ndjson", "agents/"],
599
+ files: [
600
+ "metadata.json",
601
+ "stream.ndjson",
602
+ "stream.1.ndjson",
603
+ "agents/",
604
+ "analytics.json",
605
+ "artifact-chain.json",
606
+ ],
607
+ });
608
+
609
+ return buildSessionPayload(saved, loaded.paths, nowIso);
610
+ }
611
+
612
+ // Emit analytics.json + artifact-chain.json for a live session without archiving.
613
+ // Callers should invoke this on a timer (spec §PR 10 line 1451: "S3 archive
614
+ // carries analytics.json sidecar" + line 1452-1453: closeout.json observability
615
+ // invariant — mid-flight observability requires the sidecar on disk too).
616
+ // Safe to call frequently; the payload is idempotent per (sessionId, nowIso).
617
+ export async function persistSessionSidecarsSnapshot(
618
+ sessionId,
619
+ {
620
+ targetPath = process.cwd(),
621
+ outputDir = "",
622
+ env = process.env,
623
+ homeDir,
624
+ nowIso = new Date().toISOString(),
625
+ } = {}
626
+ ) {
627
+ const loaded = await loadMetadata(sessionId, { targetPath });
628
+ if (!loaded) {
629
+ throw new Error(`Session '${sessionId}' was not found.`);
630
+ }
631
+ const sidecars = await buildArchiveSidecars(loaded.paths.sessionId, {
632
+ targetPath: loaded.targetPath,
633
+ outputDir,
634
+ env,
635
+ homeDir,
636
+ nowIso,
637
+ });
638
+ await Promise.all([
639
+ writeJsonFile(path.join(loaded.paths.sessionDir, "analytics.json"), sidecars.analyticsSidecar),
640
+ writeJsonFile(
641
+ path.join(loaded.paths.sessionDir, "artifact-chain.json"),
642
+ sidecars.artifactChainSidecar
643
+ ),
644
+ ]);
645
+ return {
646
+ sessionId: loaded.paths.sessionId,
647
+ analyticsSidecar: sidecars.analyticsSidecar,
648
+ artifactChainSidecar: sidecars.artifactChainSidecar,
649
+ };
650
+ }
651
+
652
+ export async function recordSessionProvisionedIdentities(
653
+ sessionId,
654
+ { targetPath = process.cwd(), identityIds = [], tags = [] } = {}
655
+ ) {
656
+ const loaded = await loadMetadata(sessionId, { targetPath });
657
+ if (!loaded) {
658
+ throw new Error(`Session '${sessionId}' was not found.`);
659
+ }
660
+
661
+ const nowIso = new Date().toISOString();
662
+ const normalizedIdentityIds = normalizeStringList(identityIds);
663
+ if (normalizedIdentityIds.length === 0) {
664
+ return buildSessionPayload(loaded.metadata, loaded.paths, nowIso);
665
+ }
666
+
667
+ const existingSharedResources = normalizeSharedResources(loaded.metadata.sharedResources || {}, {
668
+ nowIso,
379
669
  });
670
+ const mergedIdentityIds = normalizeStringList([
671
+ ...existingSharedResources.provisionedIdentityIds,
672
+ ...normalizedIdentityIds,
673
+ ]);
674
+ const mergedTags = normalizeStringList([
675
+ ...existingSharedResources.provisioningTags,
676
+ ...normalizeStringList(tags),
677
+ ]);
678
+
679
+ loaded.metadata.sharedResources = normalizeSharedResources(
680
+ {
681
+ ...existingSharedResources,
682
+ provisionedIdentityIds: mergedIdentityIds,
683
+ provisioningTags: mergedTags,
684
+ provisionCount:
685
+ normalizeNonNegativeInteger(existingSharedResources.provisionCount, 0) +
686
+ normalizedIdentityIds.length,
687
+ lastProvisionedAt: nowIso,
688
+ updatedAt: nowIso,
689
+ },
690
+ { nowIso }
691
+ );
692
+ loaded.metadata.updatedAt = nowIso;
693
+ loaded.metadata.lastInteractionAt = nowIso;
694
+ loaded.metadata.status = SESSION_STATUS_ACTIVE;
380
695
 
696
+ const saved = await saveMetadata(loaded.metadata, loaded.paths);
381
697
  return buildSessionPayload(saved, loaded.paths, nowIso);
382
698
  }
383
699
 
@@ -4,6 +4,8 @@ import { setTimeout as sleep } from "node:timers/promises";
4
4
 
5
5
  import { createAgentEvent, normalizeAgentEvent } from "../events/schema.js";
6
6
  import { resolveSessionPaths } from "./paths.js";
7
+ import { redactEventPayload } from "./redact.js";
8
+ import { syncSessionEventToApi } from "./sync.js";
7
9
 
8
10
  const DEFAULT_POLL_MS = 500;
9
11
  const DEFAULT_LOCK_TIMEOUT_MS = 10_000;
@@ -232,7 +234,8 @@ export async function appendToStream(
232
234
  throw new Error(`Session '${paths.sessionId}' is expired and does not accept new events.`);
233
235
  }
234
236
 
235
- const canonicalEvent = materializeCanonicalEvent(paths.sessionId, event);
237
+ const rawEvent = materializeCanonicalEvent(paths.sessionId, event);
238
+ const canonicalEvent = redactEventPayload(rawEvent);
236
239
  const nowIso = new Date().toISOString();
237
240
  const normalizedMaxEvents = normalizePositiveInteger(maxEvents, DEFAULT_MAX_STREAM_EVENTS);
238
241
 
@@ -249,6 +252,11 @@ export async function appendToStream(
249
252
  await releaseLock(paths.lockPath);
250
253
  }
251
254
 
255
+ // Best-effort dashboard sync. Never block local stream durability on API state.
256
+ void syncSessionEventToApi(paths.sessionId, canonicalEvent, {
257
+ targetPath,
258
+ }).catch(() => {});
259
+
252
260
  return canonicalEvent;
253
261
  }
254
262