@haaaiawd/second-nature 0.1.26 → 0.1.29

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 (158) hide show
  1. package/SKILL.md +35 -0
  2. package/agent-inner-guide.md +144 -0
  3. package/index.js +280 -2
  4. package/openclaw.plugin.json +2 -2
  5. package/package.json +4 -1
  6. package/runtime/cli/commands/connector-behavior.d.ts +20 -0
  7. package/runtime/cli/commands/connector-behavior.js +160 -0
  8. package/runtime/cli/commands/index.js +8 -0
  9. package/runtime/cli/index.js +9 -2
  10. package/runtime/cli/ops/manual-run-dispatcher.d.ts +79 -0
  11. package/runtime/cli/ops/manual-run-dispatcher.js +110 -0
  12. package/runtime/cli/ops/ops-router.d.ts +45 -4
  13. package/runtime/cli/ops/ops-router.js +543 -2
  14. package/runtime/cli/read-models/index.js +35 -18
  15. package/runtime/cli/read-models/types.d.ts +1 -0
  16. package/runtime/connectors/agent-network/agent-world/adapter.d.ts +1 -0
  17. package/runtime/connectors/agent-network/agent-world/adapter.js +2 -2
  18. package/runtime/connectors/base/contract.d.ts +4 -1
  19. package/runtime/connectors/base/contract.js +5 -1
  20. package/runtime/connectors/base/effect-commit-ledger-sqlite.d.ts +31 -0
  21. package/runtime/connectors/base/effect-commit-ledger-sqlite.js +86 -0
  22. package/runtime/connectors/base/failure-taxonomy.js +5 -0
  23. package/runtime/connectors/base/manifest-v7.d.ts +151 -0
  24. package/runtime/connectors/base/manifest-v7.js +170 -0
  25. package/runtime/connectors/base/manifest.d.ts +3 -13
  26. package/runtime/connectors/base/manifest.js +7 -7
  27. package/runtime/connectors/base/route-planner.js +11 -8
  28. package/runtime/connectors/base/structured-unavailable-reason.d.ts +59 -0
  29. package/runtime/connectors/base/structured-unavailable-reason.js +113 -0
  30. package/runtime/connectors/base/wet-probe-runner.d.ts +40 -0
  31. package/runtime/connectors/base/wet-probe-runner.js +132 -0
  32. package/runtime/connectors/manifest/manifest-schema.d.ts +4 -0
  33. package/runtime/connectors/manifest/manifest-schema.js +2 -0
  34. package/runtime/connectors/services/connector-executor-adapter.d.ts +1 -0
  35. package/runtime/connectors/services/connector-executor-adapter.js +132 -26
  36. package/runtime/core/second-nature/body/behavior-promotion/behavior-promotion-loop.d.ts +45 -0
  37. package/runtime/core/second-nature/body/behavior-promotion/behavior-promotion-loop.js +132 -0
  38. package/runtime/core/second-nature/body/circuit-breaker/circuit-breaker-manager.d.ts +60 -0
  39. package/runtime/core/second-nature/body/circuit-breaker/circuit-breaker-manager.js +174 -0
  40. package/runtime/core/second-nature/body/probe-signal-adapter.d.ts +38 -0
  41. package/runtime/core/second-nature/body/probe-signal-adapter.js +60 -0
  42. package/runtime/core/second-nature/body/tool-affordance/affordance-assembler.d.ts +51 -0
  43. package/runtime/core/second-nature/body/tool-affordance/affordance-assembler.js +129 -0
  44. package/runtime/core/second-nature/body/tool-affordance/affordance-context-scope.d.ts +30 -0
  45. package/runtime/core/second-nature/body/tool-affordance/affordance-context-scope.js +92 -0
  46. package/runtime/core/second-nature/body/tool-experience/experience-writer.d.ts +34 -0
  47. package/runtime/core/second-nature/body/tool-experience/experience-writer.js +67 -0
  48. package/runtime/core/second-nature/body/tool-experience/pain-signal-query.d.ts +37 -0
  49. package/runtime/core/second-nature/body/tool-experience/pain-signal-query.js +62 -0
  50. package/runtime/core/second-nature/heartbeat/decision-trace-emitter.d.ts +29 -0
  51. package/runtime/core/second-nature/heartbeat/decision-trace-emitter.js +28 -0
  52. package/runtime/core/second-nature/heartbeat/embodied-context-assembler.d.ts +54 -0
  53. package/runtime/core/second-nature/heartbeat/embodied-context-assembler.js +164 -0
  54. package/runtime/core/second-nature/heartbeat/goal-lifecycle-policy.d.ts +37 -0
  55. package/runtime/core/second-nature/heartbeat/goal-lifecycle-policy.js +61 -0
  56. package/runtime/core/second-nature/heartbeat/idle-curiosity-policy.d.ts +37 -0
  57. package/runtime/core/second-nature/heartbeat/idle-curiosity-policy.js +60 -0
  58. package/runtime/core/second-nature/heartbeat/index.d.ts +4 -0
  59. package/runtime/core/second-nature/heartbeat/index.js +5 -0
  60. package/runtime/core/second-nature/heartbeat/run-heartbeat-cycle-v7.d.ts +63 -0
  61. package/runtime/core/second-nature/heartbeat/run-heartbeat-cycle-v7.js +118 -0
  62. package/runtime/core/second-nature/orchestrator/downstream-intent-orchestrator.d.ts +41 -0
  63. package/runtime/core/second-nature/orchestrator/downstream-intent-orchestrator.js +43 -0
  64. package/runtime/core/second-nature/orchestrator/effect-dispatcher.d.ts +2 -1
  65. package/runtime/core/second-nature/orchestrator/effect-dispatcher.js +2 -0
  66. package/runtime/core/second-nature/orchestrator/hard-guard-evaluator.d.ts +31 -0
  67. package/runtime/core/second-nature/orchestrator/hard-guard-evaluator.js +102 -0
  68. package/runtime/core/second-nature/orchestrator/index.d.ts +5 -0
  69. package/runtime/core/second-nature/orchestrator/index.js +7 -0
  70. package/runtime/core/second-nature/quiet/claim-synthesizer.d.ts +53 -0
  71. package/runtime/core/second-nature/quiet/claim-synthesizer.js +153 -0
  72. package/runtime/core/second-nature/quiet/daily-diary-writer.d.ts +29 -0
  73. package/runtime/core/second-nature/quiet/daily-diary-writer.js +92 -0
  74. package/runtime/core/second-nature/quiet/index.d.ts +5 -0
  75. package/runtime/core/second-nature/quiet/index.js +5 -0
  76. package/runtime/core/second-nature/quiet/run-source-backed-quiet.js +19 -12
  77. package/runtime/core/second-nature/types.d.ts +2 -0
  78. package/runtime/guidance/channel-feedback-ingestion-service.d.ts +88 -0
  79. package/runtime/guidance/channel-feedback-ingestion-service.js +231 -0
  80. package/runtime/guidance/guidance-draft-service.d.ts +60 -0
  81. package/runtime/guidance/guidance-draft-service.js +80 -0
  82. package/runtime/guidance/index.d.ts +3 -0
  83. package/runtime/guidance/index.js +3 -0
  84. package/runtime/guidance/outreach-draft-schema.d.ts +8 -8
  85. package/runtime/guidance/outreach-strategy-selector.d.ts +77 -0
  86. package/runtime/guidance/outreach-strategy-selector.js +211 -0
  87. package/runtime/observability/audit/append-only-audit-store.d.ts +20 -2
  88. package/runtime/observability/audit/append-only-audit-store.js +32 -6
  89. package/runtime/observability/audit/audit-envelope.d.ts +2 -1
  90. package/runtime/observability/audit/audit-envelope.js +8 -7
  91. package/runtime/observability/audit/audit-family-registry.json +66 -0
  92. package/runtime/observability/audit/family-registry.d.ts +43 -0
  93. package/runtime/observability/audit/family-registry.js +70 -0
  94. package/runtime/observability/index.d.ts +6 -1
  95. package/runtime/observability/index.js +6 -1
  96. package/runtime/observability/redaction/policy.d.ts +24 -3
  97. package/runtime/observability/redaction/policy.js +74 -0
  98. package/runtime/observability/services/heartbeat-digest-assembler.d.ts +152 -0
  99. package/runtime/observability/services/heartbeat-digest-assembler.js +248 -0
  100. package/runtime/observability/services/lived-experience-audit.js +6 -6
  101. package/runtime/observability/services/narrative-timeline-query-service.d.ts +136 -0
  102. package/runtime/observability/services/narrative-timeline-query-service.js +169 -0
  103. package/runtime/observability/services/restore-audit-service.d.ts +74 -0
  104. package/runtime/observability/services/restore-audit-service.js +79 -0
  105. package/runtime/observability/services/runtime-secret-anchor-view.d.ts +77 -0
  106. package/runtime/observability/services/runtime-secret-anchor-view.js +168 -0
  107. package/runtime/observability/services/self-health-snapshot.d.ts +92 -0
  108. package/runtime/observability/services/self-health-snapshot.js +251 -0
  109. package/runtime/shared/types/goal.d.ts +62 -0
  110. package/runtime/shared/types/goal.js +20 -0
  111. package/runtime/shared/types/index.d.ts +3 -0
  112. package/runtime/shared/types/index.js +3 -0
  113. package/runtime/shared/types/source-ref.d.ts +14 -0
  114. package/runtime/shared/types/source-ref.js +1 -0
  115. package/runtime/shared/types/v7-entities.d.ts +206 -0
  116. package/runtime/shared/types/v7-entities.js +27 -0
  117. package/runtime/storage/db/index.js +3 -0
  118. package/runtime/storage/db/migration-runner.d.ts +30 -0
  119. package/runtime/storage/db/migration-runner.js +93 -0
  120. package/runtime/storage/db/migrations/index.d.ts +5 -0
  121. package/runtime/storage/db/migrations/index.js +13 -0
  122. package/runtime/storage/db/migrations/v7-001-foundation.d.ts +13 -0
  123. package/runtime/storage/db/migrations/v7-001-foundation.js +144 -0
  124. package/runtime/storage/db/migrations/v7-002-effect-commit-ledger.d.ts +8 -0
  125. package/runtime/storage/db/migrations/v7-002-effect-commit-ledger.js +27 -0
  126. package/runtime/storage/db/migrations/v7-003-circuit-breaker.d.ts +7 -0
  127. package/runtime/storage/db/migrations/v7-003-circuit-breaker.js +26 -0
  128. package/runtime/storage/db/migrations/v7-004-behavior-promotion.d.ts +7 -0
  129. package/runtime/storage/db/migrations/v7-004-behavior-promotion.js +26 -0
  130. package/runtime/storage/db/schema/agent-goal.d.ts +38 -0
  131. package/runtime/storage/db/schema/agent-goal.js +2 -0
  132. package/runtime/storage/db/transaction-utils.d.ts +14 -0
  133. package/runtime/storage/db/transaction-utils.js +29 -0
  134. package/runtime/storage/db/write-queue.d.ts +38 -0
  135. package/runtime/storage/db/write-queue.js +97 -0
  136. package/runtime/storage/quiet/persist-quiet-artifact.js +2 -1
  137. package/runtime/storage/services/credential-vault.js +31 -17
  138. package/runtime/storage/services/diary-dream-store.d.ts +35 -0
  139. package/runtime/storage/services/diary-dream-store.js +165 -0
  140. package/runtime/storage/services/embodied-context-state-port.d.ts +77 -0
  141. package/runtime/storage/services/embodied-context-state-port.js +115 -0
  142. package/runtime/storage/services/goal-lifecycle-store.d.ts +42 -0
  143. package/runtime/storage/services/goal-lifecycle-store.js +181 -0
  144. package/runtime/storage/services/history-digest-store.d.ts +33 -0
  145. package/runtime/storage/services/history-digest-store.js +140 -0
  146. package/runtime/storage/services/identity-profile-store.d.ts +25 -0
  147. package/runtime/storage/services/identity-profile-store.js +81 -0
  148. package/runtime/storage/services/interaction-snapshot-projector.d.ts +15 -0
  149. package/runtime/storage/services/interaction-snapshot-projector.js +35 -0
  150. package/runtime/storage/services/restore-snapshot-store.d.ts +52 -0
  151. package/runtime/storage/services/restore-snapshot-store.js +193 -0
  152. package/runtime/storage/services/runtime-secret-anchor-store.d.ts +26 -0
  153. package/runtime/storage/services/runtime-secret-anchor-store.js +82 -0
  154. package/runtime/storage/services/tool-experience-store.d.ts +25 -0
  155. package/runtime/storage/services/tool-experience-store.js +116 -0
  156. package/runtime/storage/services/write-validation-gate.d.ts +46 -0
  157. package/runtime/storage/services/write-validation-gate.js +200 -0
  158. package/workspace-ops-bridge.js +16 -1
@@ -1,14 +1,32 @@
1
1
  /**
2
2
  * Shared ops command dispatch for CLI + tool surfaces (T1.1.3, T1.2.2).
3
+ *
4
+ * v7 additions (T-ROS.C.1): self_health, tool_affordance, connector_test --wet,
5
+ * heartbeat_digest, narrative:diff, timeline, restore, runtime_secret_bootstrap.
6
+ * All commands return RuntimeOpsEnvelope.
3
7
  */
8
+ import fs from "node:fs";
4
9
  import { heartbeatCheck, } from "./heartbeat-surface.js";
5
10
  import { showOperatorFallback, OperatorFallbackNotFoundError, } from "./show-operator-fallback.js";
6
11
  import { probeHostCapability } from "../host-capability/probe-host-capability.js";
7
12
  import { recordHostCapability } from "../host-capability/record-host-capability.js";
8
13
  import { runNearRealConnectorSmoke } from "../../connectors/near-real/near-real-connector-smoke.js";
9
14
  import { connectorInit } from "../commands/connector-init.js";
15
+ import { connectorBehaviorAdd } from "../commands/connector-behavior.js";
10
16
  import { connectorStatus, connectorTest } from "../commands/connector-status.js";
11
17
  import { goalCommand } from "../commands/goal.js";
18
+ // v7 observability services (T-ROS.C.1)
19
+ import { getSelfHealthSnapshot, ensureMinimumProbes, } from "../../observability/services/self-health-snapshot.js";
20
+ import { generateHeartbeatDigest, } from "../../observability/services/heartbeat-digest-assembler.js";
21
+ import { queryNarrativeTimeline, queryNarrativeDiff, } from "../../observability/services/narrative-timeline-query-service.js";
22
+ import { viewSecretAnchor, } from "../../observability/services/runtime-secret-anchor-view.js";
23
+ import { writeRestoreAudit, } from "../../observability/services/restore-audit-service.js";
24
+ // T-ROS.C.3: ManualRunDispatcher and its deps
25
+ import { createManualRunDispatcher, } from "./manual-run-dispatcher.js";
26
+ import { createExperienceWriter } from "../../core/second-nature/body/tool-experience/experience-writer.js";
27
+ import { createToolExperienceStore } from "../../storage/services/tool-experience-store.js";
28
+ import { createWetProbeRunner } from "../../connectors/base/wet-probe-runner.js";
29
+ import { CapabilityContractRegistryV7 } from "../../connectors/base/manifest-v7.js";
12
30
  function coerceProbeOnlyFlag(input) {
13
31
  const v = input?.probeOnly;
14
32
  return v === true || v === "true" || v === 1 || v === "1";
@@ -200,6 +218,23 @@ export function createOpsRouter(deps) {
200
218
  return result;
201
219
  })();
202
220
  }
221
+ if (command === "connector_behavior_add") {
222
+ return connectorBehaviorAdd({
223
+ platformId: typeof input?.platformId === "string" ? input.platformId : "",
224
+ behaviorId: typeof input?.behaviorId === "string"
225
+ ? input.behaviorId
226
+ : typeof input?.capabilityId === "string"
227
+ ? input.capabilityId
228
+ : "",
229
+ description: typeof input?.description === "string" ? input.description : undefined,
230
+ channel: typeof input?.channel === "string" ? input.channel : undefined,
231
+ sourceRefs: input?.sourceRefs,
232
+ observedCount: typeof input?.observedCount === "number" ? input.observedCount : undefined,
233
+ workspaceRoot: typeof input?.workspaceRoot === "string"
234
+ ? input.workspaceRoot
235
+ : deps.workspaceRoot,
236
+ });
237
+ }
203
238
  if (command === "connector_status") {
204
239
  return connectorStatus(deps.registry, undefined, {
205
240
  includeHealth: Boolean(input?.includeHealth),
@@ -209,9 +244,79 @@ export function createOpsRouter(deps) {
209
244
  });
210
245
  }
211
246
  if (command === "connector_test") {
212
- return connectorTest(deps.registry, {
247
+ // v7 T-ROS.C.1: --wet flag (wet=true) sets dryRun=false + marks triggerSource:"manual_run"
248
+ const isWet = input?.wet === true || input?.wet === "true";
249
+ const result = await connectorTest(deps.registry, {
213
250
  platformId: typeof input?.platformId === "string" ? input.platformId : "",
214
- dryRun: input?.dryRun === false ? false : true, // default dry-run
251
+ dryRun: isWet ? false : (input?.dryRun === false ? false : true),
252
+ });
253
+ if (isWet && result.ok) {
254
+ // Annotate result with manual trigger context (DR-038 / T-ROS.C.3)
255
+ result.triggerSource = "manual_run";
256
+ result.affectsHeartbeatCadence = false;
257
+ }
258
+ return result;
259
+ }
260
+ if (command === "connector:run") {
261
+ // T-ROS.C.3: manual connector execution — isolated from heartbeat cadence
262
+ const platformId = typeof input?.platformId === "string" ? input.platformId : "";
263
+ const capabilityId = typeof input?.capabilityId === "string" ? input.capabilityId : "";
264
+ if (!platformId || !capabilityId) {
265
+ return {
266
+ ok: false,
267
+ command: "connector:run",
268
+ error: {
269
+ code: "MISSING_PLATFORM_OR_CAPABILITY_ID",
270
+ message: "connector:run requires platformId and capabilityId",
271
+ requiredUserInput: ["platformId", "capabilityId"],
272
+ nextStep: "reinvoke_with_platform_and_capability_id",
273
+ },
274
+ };
275
+ }
276
+ if (!deps.connectorExecutor || !deps.state) {
277
+ return {
278
+ ok: false,
279
+ command: "connector:run",
280
+ error: {
281
+ code: "MANUAL_RUN_DEPS_UNAVAILABLE",
282
+ message: "connector:run requires connectorExecutor and state database",
283
+ nextStep: "wire_connector_executor_and_state_into_ops_router",
284
+ },
285
+ };
286
+ }
287
+ const toolExperienceStore = createToolExperienceStore(deps.state);
288
+ const experienceWriter = createExperienceWriter(toolExperienceStore);
289
+ const wetProbeRunner = createWetProbeRunner();
290
+ const registryV7 = new CapabilityContractRegistryV7();
291
+ // Populate V7 registry from dynamic registry if available (best-effort)
292
+ if (deps.registry) {
293
+ for (const entry of deps.registry.listConnectors()) {
294
+ if (entry.manifestPath) {
295
+ try {
296
+ const manifestText = fs.readFileSync(entry.manifestPath, "utf-8");
297
+ const manifest = JSON.parse(manifestText);
298
+ registryV7.register(manifest);
299
+ }
300
+ catch {
301
+ // Skip manifests that can't be read or don't validate as V7
302
+ }
303
+ }
304
+ }
305
+ }
306
+ const dispatcher = createManualRunDispatcher({
307
+ connectorExecutor: deps.connectorExecutor,
308
+ experienceWriter,
309
+ wetProbeRunner,
310
+ registryV7,
311
+ });
312
+ return dispatcher.runConnector({
313
+ platformId,
314
+ capabilityId,
315
+ payload: typeof input?.payload === "object" && input?.payload !== null
316
+ ? input.payload
317
+ : undefined,
318
+ caller: typeof input?.caller === "string" ? input.caller : undefined,
319
+ reason: typeof input?.reason === "string" ? input.reason : undefined,
215
320
  });
216
321
  }
217
322
  if (command === "goal") {
@@ -275,6 +380,442 @@ export function createOpsRouter(deps) {
275
380
  const data = await deps.readModels.loadCycleRecent(limit);
276
381
  return { ok: true, data };
277
382
  }
383
+ // ─── v7 commands (T-ROS.C.1) ─────────────────────────────────────────
384
+ /** [G2] self_health — transparent pass-through from SelfHealthSnapshot (DR-042). */
385
+ if (command === "self_health") {
386
+ const generatedAt = new Date().toISOString();
387
+ try {
388
+ ensureMinimumProbes();
389
+ const snap = await getSelfHealthSnapshot();
390
+ const degraded_dimensions = Object.entries(snap.dimensions)
391
+ .filter(([, d]) => d.status === "degraded")
392
+ .map(([k]) => k);
393
+ const envelope = {
394
+ ok: true,
395
+ command: "self_health",
396
+ runtimeMode: "workspace_full_runtime",
397
+ surfaceMode: "cli",
398
+ generatedAt,
399
+ data: {
400
+ overall: snap.overall,
401
+ generatedAt: snap.generatedAt,
402
+ degraded_dimensions,
403
+ dimensions: snap.dimensions,
404
+ },
405
+ warnings: [],
406
+ sourceRefs: ["observability/services/self-health-snapshot.ts"],
407
+ };
408
+ return envelope;
409
+ }
410
+ catch (err) {
411
+ const msg = err instanceof Error ? err.message : String(err);
412
+ const envelope = {
413
+ ok: false,
414
+ command: "self_health",
415
+ runtimeMode: "unavailable",
416
+ surfaceMode: "cli",
417
+ generatedAt,
418
+ error: { code: "SELF_HEALTH_PROBE_FAILED", message: msg },
419
+ warnings: [],
420
+ sourceRefs: [],
421
+ };
422
+ return envelope;
423
+ }
424
+ }
425
+ /**
426
+ * [G3] tool_affordance — body-tool AffordanceMap pass-through.
427
+ * Port not yet wired in this wave; returns degraded view with clear next-step.
428
+ */
429
+ if (command === "tool_affordance") {
430
+ const generatedAt = new Date().toISOString();
431
+ const envelope = {
432
+ ok: false,
433
+ command: "tool_affordance",
434
+ runtimeMode: "unavailable",
435
+ surfaceMode: "cli",
436
+ generatedAt,
437
+ error: {
438
+ code: "TOOL_AFFORDANCE_PORT_UNWIRED",
439
+ message: "tool_affordance requires body-tool AffordanceMap port (T-BTS.C.1) to be wired into OpsRouterDeps",
440
+ nextStep: "wire_body_tool_port_into_ops_router_deps",
441
+ },
442
+ warnings: [],
443
+ sourceRefs: [],
444
+ };
445
+ return envelope;
446
+ }
447
+ /**
448
+ * [G6] heartbeat_digest — wraps generateHeartbeatDigest.
449
+ * Requires auditStore in deps; degrades if unavailable.
450
+ */
451
+ if (command === "heartbeat_digest") {
452
+ const generatedAt = new Date().toISOString();
453
+ if (!deps.auditStore) {
454
+ const envelope = {
455
+ ok: false,
456
+ command: "heartbeat_digest",
457
+ runtimeMode: "unavailable",
458
+ surfaceMode: "cli",
459
+ generatedAt,
460
+ error: {
461
+ code: "AUDIT_STORE_UNAVAILABLE",
462
+ message: "heartbeat_digest requires auditStore in OpsRouterDeps",
463
+ nextStep: "wire_audit_store_into_ops_router",
464
+ },
465
+ warnings: [],
466
+ sourceRefs: [],
467
+ };
468
+ return envelope;
469
+ }
470
+ const date = typeof input?.date === "string" && input.date
471
+ ? input.date
472
+ : new Date().toISOString().slice(0, 10);
473
+ try {
474
+ const digestDeps = {
475
+ auditStore: deps.auditStore,
476
+ ...deps.heartbeatDigestDeps,
477
+ };
478
+ const digest = await generateHeartbeatDigest(date, digestDeps);
479
+ const envelope = {
480
+ ok: true,
481
+ command: "heartbeat_digest",
482
+ runtimeMode: "workspace_full_runtime",
483
+ surfaceMode: "cli",
484
+ generatedAt,
485
+ data: digest,
486
+ warnings: [],
487
+ sourceRefs: ["observability/services/heartbeat-digest-assembler.ts"],
488
+ };
489
+ return envelope;
490
+ }
491
+ catch (err) {
492
+ const msg = err instanceof Error ? err.message : String(err);
493
+ const envelope = {
494
+ ok: false,
495
+ command: "heartbeat_digest",
496
+ runtimeMode: "unavailable",
497
+ surfaceMode: "cli",
498
+ generatedAt,
499
+ error: { code: "DIGEST_GENERATION_FAILED", message: msg },
500
+ warnings: [],
501
+ sourceRefs: [],
502
+ };
503
+ return envelope;
504
+ }
505
+ }
506
+ /**
507
+ * [G6] narrative:diff — queryNarrativeDiff between two versions.
508
+ * Requires narrativeTimelineDeps in OpsRouterDeps.
509
+ */
510
+ if (command === "narrative:diff") {
511
+ const generatedAt = new Date().toISOString();
512
+ if (!deps.narrativeTimelineDeps) {
513
+ const envelope = {
514
+ ok: false,
515
+ command: "narrative:diff",
516
+ runtimeMode: "unavailable",
517
+ surfaceMode: "cli",
518
+ generatedAt,
519
+ error: {
520
+ code: "NARRATIVE_TIMELINE_PORT_UNAVAILABLE",
521
+ message: "narrative:diff requires narrativeTimelineDeps in OpsRouterDeps",
522
+ nextStep: "wire_narrative_timeline_deps_into_ops_router",
523
+ },
524
+ warnings: [],
525
+ sourceRefs: [],
526
+ };
527
+ return envelope;
528
+ }
529
+ const fromVersion = typeof input?.from === "string" ? input.from : "";
530
+ const toVersion = typeof input?.to === "string" ? input.to : "";
531
+ if (!fromVersion || !toVersion) {
532
+ const envelope = {
533
+ ok: false,
534
+ command: "narrative:diff",
535
+ runtimeMode: "workspace_full_runtime",
536
+ surfaceMode: "cli",
537
+ generatedAt,
538
+ error: {
539
+ code: "MISSING_VERSIONS",
540
+ message: "narrative:diff requires 'from' and 'to' version arguments",
541
+ nextStep: "reinvoke_with_from_and_to",
542
+ },
543
+ warnings: [],
544
+ sourceRefs: [],
545
+ };
546
+ return envelope;
547
+ }
548
+ try {
549
+ const diff = await queryNarrativeDiff(fromVersion, toVersion, deps.narrativeTimelineDeps);
550
+ const envelope = {
551
+ ok: true,
552
+ command: "narrative:diff",
553
+ runtimeMode: "workspace_full_runtime",
554
+ surfaceMode: "cli",
555
+ generatedAt,
556
+ data: diff,
557
+ warnings: [],
558
+ sourceRefs: ["observability/services/narrative-timeline-query-service.ts"],
559
+ };
560
+ return envelope;
561
+ }
562
+ catch (err) {
563
+ const msg = err instanceof Error ? err.message : String(err);
564
+ const envelope = {
565
+ ok: false,
566
+ command: "narrative:diff",
567
+ runtimeMode: "unavailable",
568
+ surfaceMode: "cli",
569
+ generatedAt,
570
+ error: { code: "NARRATIVE_DIFF_FAILED", message: msg },
571
+ warnings: [],
572
+ sourceRefs: [],
573
+ };
574
+ return envelope;
575
+ }
576
+ }
577
+ /**
578
+ * [G6] timeline — queryNarrativeTimeline with cursor pagination.
579
+ * Requires narrativeTimelineDeps in OpsRouterDeps.
580
+ */
581
+ if (command === "timeline") {
582
+ const generatedAt = new Date().toISOString();
583
+ if (!deps.narrativeTimelineDeps) {
584
+ const envelope = {
585
+ ok: false,
586
+ command: "timeline",
587
+ runtimeMode: "unavailable",
588
+ surfaceMode: "cli",
589
+ generatedAt,
590
+ error: {
591
+ code: "NARRATIVE_TIMELINE_PORT_UNAVAILABLE",
592
+ message: "timeline requires narrativeTimelineDeps in OpsRouterDeps",
593
+ nextStep: "wire_narrative_timeline_deps_into_ops_router",
594
+ },
595
+ warnings: [],
596
+ sourceRefs: [],
597
+ };
598
+ return envelope;
599
+ }
600
+ const now = new Date();
601
+ const to = typeof input?.to === "string" ? input.to : now.toISOString();
602
+ const from = typeof input?.from === "string"
603
+ ? input.from
604
+ : new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000).toISOString();
605
+ const limit = typeof input?.limit === "number" ? input.limit : 20;
606
+ const cursor = typeof input?.cursor === "string" ? input.cursor : undefined;
607
+ try {
608
+ const page = await queryNarrativeTimeline(from, to, { limit, cursor }, deps.narrativeTimelineDeps);
609
+ const envelope = {
610
+ ok: true,
611
+ command: "timeline",
612
+ runtimeMode: "workspace_full_runtime",
613
+ surfaceMode: "cli",
614
+ generatedAt,
615
+ data: page,
616
+ warnings: [],
617
+ sourceRefs: ["observability/services/narrative-timeline-query-service.ts"],
618
+ };
619
+ return envelope;
620
+ }
621
+ catch (err) {
622
+ const msg = err instanceof Error ? err.message : String(err);
623
+ const code = err.name === "NarrativeQueryRangeError"
624
+ ? "NARRATIVE_RANGE_EXCEEDED"
625
+ : "TIMELINE_QUERY_FAILED";
626
+ const envelope = {
627
+ ok: false,
628
+ command: "timeline",
629
+ runtimeMode: "unavailable",
630
+ surfaceMode: "cli",
631
+ generatedAt,
632
+ error: { code, message: msg },
633
+ warnings: [],
634
+ sourceRefs: [],
635
+ };
636
+ return envelope;
637
+ }
638
+ }
639
+ /**
640
+ * [G6] restore — bounded state restoration via RestoreSnapshotStore + audit (T-ROS.C.1, T-OBS.C.6).
641
+ * When restoreSnapshotStore is wired, attempts to apply the snapshot payload back to state.
642
+ * Always writes RestoreAudit. Never restores credential fields.
643
+ */
644
+ if (command === "restore") {
645
+ const generatedAt = new Date().toISOString();
646
+ if (!deps.auditStore) {
647
+ const envelope = {
648
+ ok: false,
649
+ command: "restore",
650
+ runtimeMode: "unavailable",
651
+ surfaceMode: "cli",
652
+ generatedAt,
653
+ error: {
654
+ code: "AUDIT_STORE_UNAVAILABLE",
655
+ message: "restore requires auditStore in OpsRouterDeps",
656
+ nextStep: "wire_audit_store_into_ops_router",
657
+ },
658
+ warnings: [],
659
+ sourceRefs: [],
660
+ };
661
+ return envelope;
662
+ }
663
+ const missingFields = [];
664
+ if (typeof input?.restoreTarget !== "string")
665
+ missingFields.push("restoreTarget");
666
+ if (typeof input?.fromVersion !== "string")
667
+ missingFields.push("fromVersion");
668
+ if (typeof input?.toVersion !== "string")
669
+ missingFields.push("toVersion");
670
+ if (missingFields.length > 0) {
671
+ const envelope = {
672
+ ok: false,
673
+ command: "restore",
674
+ runtimeMode: "workspace_full_runtime",
675
+ surfaceMode: "cli",
676
+ generatedAt,
677
+ error: {
678
+ code: "MISSING_RESTORE_FIELDS",
679
+ message: `restore requires: ${missingFields.join(", ")}`,
680
+ nextStep: "reinvoke_with_required_fields",
681
+ },
682
+ warnings: [],
683
+ sourceRefs: [],
684
+ };
685
+ return envelope;
686
+ }
687
+ // [NEW] Invoke bounded restore via RestoreSnapshotStore when wired
688
+ let restoreResult = {
689
+ ok: false,
690
+ completedEntities: [],
691
+ failedEntities: [],
692
+ warnings: ["restore_snapshot_store_unavailable"],
693
+ };
694
+ if (deps.restoreSnapshotStore) {
695
+ restoreResult = await deps.restoreSnapshotStore.applyBoundedRestore({
696
+ restoreTarget: input.restoreTarget,
697
+ fromVersion: input.fromVersion,
698
+ toVersion: input.toVersion,
699
+ });
700
+ }
701
+ const event = {
702
+ id: `restore-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
703
+ restoreTarget: input.restoreTarget,
704
+ fromVersion: input.fromVersion,
705
+ toVersion: input.toVersion,
706
+ triggeredBy: input?.triggeredBy ?? "operator",
707
+ reason: typeof input?.reason === "string" ? input.reason : "manual_restore",
708
+ completedEntities: restoreResult.completedEntities,
709
+ failedEntities: restoreResult.failedEntities,
710
+ // credentials are always excluded from restore audit
711
+ excludedFields: Array.isArray(input?.excludedFields)
712
+ ? input.excludedFields.filter((f) => typeof f === "string")
713
+ : ["credential", "encryptionKey"],
714
+ restoredFieldCount: restoreResult.completedEntities.length,
715
+ createdAt: generatedAt,
716
+ traceId: typeof input?.traceId === "string" ? input.traceId : `trace-restore-${Date.now()}`,
717
+ };
718
+ const auditResult = await writeRestoreAudit(event, deps.auditStore);
719
+ const envelope = {
720
+ ok: restoreResult.ok && auditResult.ok,
721
+ command: "restore",
722
+ runtimeMode: "workspace_full_runtime",
723
+ surfaceMode: "cli",
724
+ generatedAt,
725
+ data: {
726
+ auditWritten: auditResult.warnings.length === 0,
727
+ fromVersion: event.fromVersion,
728
+ toVersion: event.toVersion,
729
+ restoreTarget: event.restoreTarget,
730
+ isPartialRestore: event.failedEntities.length > 0,
731
+ failedEntities: event.failedEntities,
732
+ completedEntities: event.completedEntities,
733
+ restoreSnapshotStoreAvailable: !!deps.restoreSnapshotStore,
734
+ },
735
+ warnings: [...restoreResult.warnings, ...auditResult.warnings],
736
+ sourceRefs: [
737
+ "observability/services/restore-audit-service.ts",
738
+ "storage/services/restore-snapshot-store.ts",
739
+ ],
740
+ };
741
+ return envelope;
742
+ }
743
+ /**
744
+ * [G7] runtime_secret_bootstrap — RuntimeSecretAnchorView pass-through.
745
+ * Requires secretAnchorDeps in OpsRouterDeps; never returns key plaintext.
746
+ */
747
+ if (command === "runtime_secret_bootstrap") {
748
+ const generatedAt = new Date().toISOString();
749
+ if (!deps.secretAnchorDeps) {
750
+ const envelope = {
751
+ ok: false,
752
+ command: "runtime_secret_bootstrap",
753
+ runtimeMode: "unavailable",
754
+ surfaceMode: "cli",
755
+ generatedAt,
756
+ error: {
757
+ code: "SECRET_ANCHOR_DEPS_UNAVAILABLE",
758
+ message: "runtime_secret_bootstrap requires secretAnchorDeps in OpsRouterDeps",
759
+ nextStep: "wire_secret_anchor_deps_into_ops_router",
760
+ },
761
+ warnings: [],
762
+ sourceRefs: [],
763
+ };
764
+ return envelope;
765
+ }
766
+ try {
767
+ const view = await viewSecretAnchor(deps.secretAnchorDeps);
768
+ // Map to RuntimeSecretBootstrapView (design model §6.1)
769
+ const data = {
770
+ status: view.status === "verified" || view.status === "ok"
771
+ ? "ok"
772
+ : view.status === "missing"
773
+ ? "runtime_secret_anchor_missing"
774
+ : view.status === "wrong_key"
775
+ ? "credential_recovery_required"
776
+ : view.status === "decryption_failed"
777
+ ? "runtime_secret_unavailable"
778
+ : "unknown",
779
+ keyHealth: view.status === "verified" || view.status === "ok"
780
+ ? "ok"
781
+ : view.status === "missing"
782
+ ? "missing_key"
783
+ : view.status === "wrong_key"
784
+ ? "wrong_key"
785
+ : "unknown",
786
+ anchorLocation: view.keyPath,
787
+ recoveryPrincipleRef: view.recoveryDocRef,
788
+ plaintextKeyExposed: false,
789
+ reasonCode: view.reasonCode,
790
+ recoverySteps: view.recoverySteps,
791
+ };
792
+ const envelope = {
793
+ ok: true,
794
+ command: "runtime_secret_bootstrap",
795
+ runtimeMode: "workspace_full_runtime",
796
+ surfaceMode: "cli",
797
+ generatedAt,
798
+ data,
799
+ warnings: [],
800
+ sourceRefs: ["observability/services/runtime-secret-anchor-view.ts"],
801
+ };
802
+ return envelope;
803
+ }
804
+ catch (err) {
805
+ const msg = err instanceof Error ? err.message : String(err);
806
+ const envelope = {
807
+ ok: false,
808
+ command: "runtime_secret_bootstrap",
809
+ runtimeMode: "unavailable",
810
+ surfaceMode: "cli",
811
+ generatedAt,
812
+ error: { code: "SECRET_ANCHOR_PROBE_FAILED", message: msg },
813
+ warnings: [],
814
+ sourceRefs: [],
815
+ };
816
+ return envelope;
817
+ }
818
+ }
278
819
  return {
279
820
  ok: false,
280
821
  error: {