@haaaiawd/second-nature 0.2.12 → 0.2.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +96 -6
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/runtime/cli/commands/index.js +85 -11
- package/runtime/cli/host-capability/host-discovery-port.d.ts +85 -0
- package/runtime/cli/host-capability/host-discovery-port.js +137 -0
- package/runtime/cli/ops/heartbeat-surface.d.ts +3 -3
- package/runtime/cli/ops/heartbeat-surface.js +6 -5
- package/runtime/cli/ops/ops-router.d.ts +6 -2
- package/runtime/cli/ops/ops-router.js +1273 -1145
- package/runtime/connectors/base/normalized-evidence-content.d.ts +4 -0
- package/runtime/connectors/base/normalized-evidence-content.js +21 -2
- package/runtime/connectors/evidence-normalizer.js +32 -1
- package/runtime/core/second-nature/action/action-closure-recorder.d.ts +2 -0
- package/runtime/core/second-nature/action/action-closure-recorder.js +49 -34
- package/runtime/core/second-nature/action/action-proposal-builder.js +3 -2
- package/runtime/core/second-nature/action/policy-bound-dispatch.d.ts +2 -0
- package/runtime/core/second-nature/action/policy-bound-dispatch.js +7 -3
- package/runtime/core/second-nature/control-plane/cycle-finalizer.d.ts +82 -0
- package/runtime/core/second-nature/control-plane/cycle-finalizer.js +187 -0
- package/runtime/core/second-nature/control-plane/heartbeat-orchestrator.js +13 -9
- package/runtime/core/second-nature/control-plane/real-runtime-spine.js +1 -1
- package/runtime/core/second-nature/guidance/guidance-proposal-consumer.d.ts +2 -1
- package/runtime/core/second-nature/guidance/guidance-proposal-consumer.js +4 -2
- package/runtime/core/second-nature/perception/judgment-engine.js +8 -4
- package/runtime/core/second-nature/perception/perception-builder.js +14 -2
- package/runtime/core/second-nature/quiet-dream/daily-rhythm-scheduler.js +30 -3
- package/runtime/core/second-nature/quiet-dream/dream-consolidation-runner.d.ts +5 -1
- package/runtime/core/second-nature/quiet-dream/dream-consolidation-runner.js +68 -29
- package/runtime/core/second-nature/quiet-dream/dream-scheduler.js +2 -1
- package/runtime/core/second-nature/quiet-dream/memory-projection-lifecycle.js +2 -1
- package/runtime/core/second-nature/quiet-dream/quiet-daily-review-builder.d.ts +1 -0
- package/runtime/core/second-nature/quiet-dream/quiet-daily-review-builder.js +33 -0
- package/runtime/observability/causal-loop-health.d.ts +2 -1
- package/runtime/observability/causal-loop-health.js +7 -0
- package/runtime/observability/loop-stage-event-sink.js +6 -1
- package/runtime/observability/loop-status.d.ts +2 -0
- package/runtime/observability/loop-status.js +14 -1
- package/runtime/observability/services/heartbeat-digest-assembler.d.ts +3 -0
- package/runtime/observability/services/heartbeat-digest-assembler.js +9 -0
- package/runtime/shared/degraded-status-classifier.d.ts +16 -0
- package/runtime/shared/degraded-status-classifier.js +68 -0
- package/runtime/shared/evidence-level-classifier.d.ts +61 -0
- package/runtime/shared/evidence-level-classifier.js +116 -0
- package/runtime/shared/provenance-tier.d.ts +37 -0
- package/runtime/shared/provenance-tier.js +97 -0
- package/runtime/shared/setup-ack.d.ts +54 -0
- package/runtime/shared/setup-ack.js +108 -0
- package/runtime/shared/source-ref-compat.js +5 -2
- package/runtime/shared/types/v8-contracts.d.ts +13 -2
- package/runtime/storage/db/index.js +71 -28
- package/runtime/storage/db/migrations/v8-005-single-status-schema.js +2 -2
- package/runtime/storage/db/migrations/v8-006-loop-stage-event-proof-trace-columns.d.ts +9 -0
- package/runtime/storage/db/migrations/v8-006-loop-stage-event-proof-trace-columns.js +15 -0
- package/runtime/storage/db/schema/v8-entities.d.ts +76 -0
- package/runtime/storage/db/schema/v8-entities.js +4 -0
- package/runtime/storage/services/write-validation-gate.js +1 -1
- package/runtime/storage/v8-state-stores.d.ts +7 -2
- package/runtime/storage/v8-state-stores.js +37 -19
|
@@ -44,6 +44,100 @@ import { createGoalLifecyclePolicy } from "../../core/second-nature/heartbeat/go
|
|
|
44
44
|
import { createIdleCuriosityPolicy } from "../../core/second-nature/heartbeat/idle-curiosity-policy.js";
|
|
45
45
|
import { createCircuitBreakerManager } from "../../core/second-nature/body/circuit-breaker/circuit-breaker-manager.js";
|
|
46
46
|
import { createProbeSignalAdapter } from "../../core/second-nature/body/probe-signal-adapter.js";
|
|
47
|
+
function finalizeEnvelope(envelope, fallbackLevel = "carrier_ack") {
|
|
48
|
+
return {
|
|
49
|
+
...envelope,
|
|
50
|
+
evidenceLevel: envelope.evidenceLevel ?? fallbackLevel,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function isRuntimeOpsEnvelope(value) {
|
|
54
|
+
return (typeof value === "object" &&
|
|
55
|
+
value !== null &&
|
|
56
|
+
"ok" in value &&
|
|
57
|
+
"command" in value &&
|
|
58
|
+
"generatedAt" in value);
|
|
59
|
+
}
|
|
60
|
+
function defaultEvidenceLevelForCommand(command, runtimeMode) {
|
|
61
|
+
if (runtimeMode === "host_safe_carrier" || runtimeMode === "unavailable") {
|
|
62
|
+
return "carrier_ack";
|
|
63
|
+
}
|
|
64
|
+
// workspace_full_runtime default: contract_smoke unless proven otherwise
|
|
65
|
+
if (command === "setup_ack" || command === "setup_hint") {
|
|
66
|
+
return command === "setup_ack" ? "state_present" : "contract_smoke";
|
|
67
|
+
}
|
|
68
|
+
return "contract_smoke";
|
|
69
|
+
}
|
|
70
|
+
function normalizeEnvelopeResult(raw, command, runtimeMode) {
|
|
71
|
+
if (isRuntimeOpsEnvelope(raw)) {
|
|
72
|
+
const mode = raw.runtimeMode ?? runtimeMode ?? "host_safe_carrier";
|
|
73
|
+
const fallbackSurface = raw.surfaceMode ?? (mode === "workspace_full_runtime" ? "workspace_full_runtime" : "cli");
|
|
74
|
+
const fallback = defaultEvidenceLevelForCommand(command, mode);
|
|
75
|
+
return {
|
|
76
|
+
...raw,
|
|
77
|
+
runtimeMode: mode,
|
|
78
|
+
surfaceMode: fallbackSurface,
|
|
79
|
+
evidenceLevel: raw.evidenceLevel ?? fallback,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
// Partial result (e.g. goal, connector_status, connector_test) — wrap into a valid envelope
|
|
83
|
+
// while preserving the caller's data and error shape.
|
|
84
|
+
if (typeof raw === "object" &&
|
|
85
|
+
raw !== null &&
|
|
86
|
+
"ok" in raw &&
|
|
87
|
+
typeof raw.ok === "boolean" &&
|
|
88
|
+
"command" in raw &&
|
|
89
|
+
typeof raw.command === "string") {
|
|
90
|
+
const partial = raw;
|
|
91
|
+
const mode = runtimeMode ?? partial.runtimeMode ?? "host_safe_carrier";
|
|
92
|
+
const fallbackSurface = partial.surfaceMode ??
|
|
93
|
+
(mode === "workspace_full_runtime" ? "workspace_full_runtime" : "cli");
|
|
94
|
+
const fallback = defaultEvidenceLevelForCommand(command, mode);
|
|
95
|
+
return {
|
|
96
|
+
...partial,
|
|
97
|
+
runtimeMode: mode,
|
|
98
|
+
surfaceMode: fallbackSurface,
|
|
99
|
+
generatedAt: new Date().toISOString(),
|
|
100
|
+
warnings: Array.isArray(partial.warnings) ? partial.warnings : [],
|
|
101
|
+
sourceRefs: Array.isArray(partial.sourceRefs) ? partial.sourceRefs : [],
|
|
102
|
+
evidenceLevel: partial.evidenceLevel ?? fallback,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
// Non-envelope result (e.g. plain error from an internal helper) — wrap honestly,
|
|
106
|
+
// but preserve any structured error already present on the raw object.
|
|
107
|
+
const generatedAt = new Date().toISOString();
|
|
108
|
+
const existingError = (() => {
|
|
109
|
+
if (typeof raw === "object" &&
|
|
110
|
+
raw !== null &&
|
|
111
|
+
"error" in raw &&
|
|
112
|
+
raw.error !== null &&
|
|
113
|
+
typeof raw.error === "object" &&
|
|
114
|
+
"code" in raw.error) {
|
|
115
|
+
const err = raw.error;
|
|
116
|
+
return {
|
|
117
|
+
code: err.code,
|
|
118
|
+
message: err.message ?? "Internal ops error",
|
|
119
|
+
nextStep: err.nextStep,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
return undefined;
|
|
123
|
+
})();
|
|
124
|
+
return {
|
|
125
|
+
ok: false,
|
|
126
|
+
command,
|
|
127
|
+
runtimeMode: runtimeMode ?? "host_safe_carrier",
|
|
128
|
+
surfaceMode: runtimeMode === "workspace_full_runtime" ? "workspace_full_runtime" : "cli",
|
|
129
|
+
generatedAt,
|
|
130
|
+
error: existingError ?? {
|
|
131
|
+
code: "OPS_RESULT_NOT_AN_ENVELOPE",
|
|
132
|
+
message: typeof raw === "object" && raw !== null && "message" in raw
|
|
133
|
+
? String(raw.message)
|
|
134
|
+
: "Internal ops result did not match RuntimeOpsEnvelope shape",
|
|
135
|
+
},
|
|
136
|
+
warnings: [],
|
|
137
|
+
sourceRefs: [],
|
|
138
|
+
evidenceLevel: "carrier_ack",
|
|
139
|
+
};
|
|
140
|
+
}
|
|
47
141
|
function coerceProbeOnlyFlag(input) {
|
|
48
142
|
const v = input?.probeOnly;
|
|
49
143
|
return v === true || v === "true" || v === 1 || v === "1";
|
|
@@ -360,1294 +454,1328 @@ export function createOpsRouter(deps) {
|
|
|
360
454
|
dreamSchedulePort: input.dreamSchedulePort,
|
|
361
455
|
}),
|
|
362
456
|
async dispatch(command, input) {
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
if (deps.toolAffordancePort) {
|
|
370
|
-
try {
|
|
371
|
-
affordanceMap = await deps.toolAffordancePort.assembleAffordanceMap({});
|
|
372
|
-
}
|
|
373
|
-
catch {
|
|
374
|
-
// degrade gracefully; guard-layer will skip breaker check without affordanceMap
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
let experienceWriter;
|
|
378
|
-
if (deps.state) {
|
|
379
|
-
experienceWriter = createExperienceWriter(createToolExperienceStore(deps.state));
|
|
380
|
-
}
|
|
381
|
-
// v7 T-V7C.C.6: assemble digest opts when auditStore is wired.
|
|
382
|
-
let digestOpts;
|
|
383
|
-
if (deps.auditStore) {
|
|
384
|
-
digestOpts = {
|
|
385
|
-
assemblerDeps: {
|
|
386
|
-
auditStore: deps.auditStore,
|
|
387
|
-
...deps.heartbeatDigestDeps,
|
|
388
|
-
},
|
|
389
|
-
};
|
|
390
|
-
}
|
|
391
|
-
// v7 T-V7C.C.6: assemble dream schedule port when state DB is wired.
|
|
392
|
-
let dreamSchedulePort;
|
|
393
|
-
if (deps.state) {
|
|
394
|
-
dreamSchedulePort = createQuietDreamSchedulePort(deps.state);
|
|
395
|
-
}
|
|
396
|
-
// v7 T-CP.C.3: assemble goal lifecycle and idle curiosity policies.
|
|
397
|
-
const goalLifecyclePolicy = createGoalLifecyclePolicy();
|
|
398
|
-
const idleCuriosityPolicy = createIdleCuriosityPolicy();
|
|
399
|
-
// v7 T-BTS.C.5: assemble circuit breaker manager when state DB is wired.
|
|
400
|
-
let circuitBreakerManager;
|
|
401
|
-
if (deps.state) {
|
|
402
|
-
const probeResultStore = createCapabilityProbeResultStore(deps.state);
|
|
403
|
-
const toolExpStore = createToolExperienceStore(deps.state);
|
|
404
|
-
const probeAdapter = createProbeSignalAdapter({
|
|
405
|
-
wetProbeRunner: createWetProbeRunner(),
|
|
406
|
-
probeResultStore,
|
|
407
|
-
toolExperienceStore: toolExpStore,
|
|
408
|
-
});
|
|
409
|
-
const registryV7 = new CapabilityContractRegistryV7();
|
|
410
|
-
circuitBreakerManager = createCircuitBreakerManager({
|
|
411
|
-
database: deps.state,
|
|
412
|
-
probeAdapter,
|
|
413
|
-
registry: registryV7,
|
|
414
|
-
});
|
|
415
|
-
}
|
|
416
|
-
try {
|
|
417
|
-
const result = await heartbeatCheck({
|
|
418
|
-
probeOnly: coerceProbeOnlyFlag(input),
|
|
419
|
-
runtimeAvailable,
|
|
420
|
-
fakeControlPlanePassthrough: input?.fakeControlPlanePassthrough &&
|
|
421
|
-
typeof input.fakeControlPlanePassthrough === "object"
|
|
422
|
-
? input.fakeControlPlanePassthrough
|
|
423
|
-
: undefined,
|
|
424
|
-
readModels: input?.readModels ??
|
|
425
|
-
deps.readModels,
|
|
426
|
-
runtimeRecorder: input
|
|
427
|
-
?.runtimeRecorder ?? deps.runtimeRecorder,
|
|
428
|
-
state: input?.state ??
|
|
429
|
-
deps.state,
|
|
430
|
-
workspaceRoot: input
|
|
431
|
-
?.workspaceRoot ?? deps.workspaceRoot,
|
|
432
|
-
timestamp: typeof input?.timestamp === "string" ? input.timestamp : undefined,
|
|
433
|
-
sessionContext: typeof input?.sessionContext === "string"
|
|
434
|
-
? input.sessionContext
|
|
435
|
-
: undefined,
|
|
436
|
-
scopeHint: input?.scopeHint,
|
|
437
|
-
connectorExecutor: input
|
|
438
|
-
?.connectorExecutor ?? deps.connectorExecutor,
|
|
439
|
-
connectorRegistry: input
|
|
440
|
-
?.connectorRegistry ?? deps.connectorRegistry,
|
|
441
|
-
affordanceMap,
|
|
442
|
-
experienceWriter,
|
|
443
|
-
digestOpts,
|
|
444
|
-
dreamSchedulePort,
|
|
445
|
-
auditStore: deps.auditStore,
|
|
446
|
-
goalLifecyclePolicy,
|
|
447
|
-
idleCuriosityPolicy,
|
|
448
|
-
circuitBreakerManager,
|
|
449
|
-
v8SpineEnabled: input
|
|
450
|
-
?.v8SpineEnabled ?? (deps.state !== undefined),
|
|
451
|
-
});
|
|
452
|
-
if (result.ok &&
|
|
453
|
-
result.surfaceMode === "workspace_full_runtime" &&
|
|
454
|
-
!coerceProbeOnlyFlag(input) &&
|
|
455
|
-
deps.state &&
|
|
456
|
-
deps.restoreSnapshotStore) {
|
|
457
|
+
const rawResult = await (async () => {
|
|
458
|
+
if (command === "heartbeat") {
|
|
459
|
+
// T-CP.R.5: legacy v7 heartbeat command is no longer the operator-facing model.
|
|
460
|
+
// It still runs through heartbeat_check for backward compatibility, but the
|
|
461
|
+
// canonical operator-facing command is `heartbeat_check` (v8 living-loop spine).
|
|
462
|
+
return (async () => {
|
|
457
463
|
try {
|
|
458
|
-
const
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
});
|
|
470
|
-
if (capture.ok) {
|
|
471
|
-
result.reasons = [...result.reasons, "restore_snapshot_captured"];
|
|
472
|
-
}
|
|
464
|
+
const result = await this.dispatch("heartbeat_check", input);
|
|
465
|
+
return {
|
|
466
|
+
...result,
|
|
467
|
+
warnings: [
|
|
468
|
+
...(Array.isArray(result.warnings) ? result.warnings : []),
|
|
469
|
+
{
|
|
470
|
+
code: "LEGACY_HEARTBEAT_DEPRECATED",
|
|
471
|
+
message: "`heartbeat` is deprecated; use `heartbeat_check` (v8 living-loop spine)",
|
|
472
|
+
},
|
|
473
|
+
],
|
|
474
|
+
};
|
|
473
475
|
}
|
|
474
476
|
catch (err) {
|
|
475
477
|
const msg = err instanceof Error ? err.message : String(err);
|
|
476
|
-
result.reasons = [...result.reasons, `restore_snapshot_capture_failed:${msg}`];
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
return result;
|
|
480
|
-
}
|
|
481
|
-
catch (err) {
|
|
482
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
483
|
-
const envelope = {
|
|
484
|
-
ok: false,
|
|
485
|
-
command: "heartbeat_check",
|
|
486
|
-
runtimeMode: runtimeAvailable ? "workspace_full_runtime" : "unavailable",
|
|
487
|
-
surfaceMode: "cli",
|
|
488
|
-
generatedAt: new Date().toISOString(),
|
|
489
|
-
error: {
|
|
490
|
-
code: "HEARTBEAT_CYCLE_EXCEPTION",
|
|
491
|
-
message: `heartbeat_check cycle threw unexpectedly: ${msg.slice(0, 200)}`,
|
|
492
|
-
nextStep: "check_logs_and_report",
|
|
493
|
-
},
|
|
494
|
-
warnings: [],
|
|
495
|
-
sourceRefs: [],
|
|
496
|
-
};
|
|
497
|
-
return envelope;
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
if (command === "fallback") {
|
|
501
|
-
const ref = typeof input?.ref === "string" ? input.ref.trim() : "";
|
|
502
|
-
if (!ref) {
|
|
503
|
-
return {
|
|
504
|
-
ok: false,
|
|
505
|
-
error: {
|
|
506
|
-
code: "MISSING_FALLBACK_REF",
|
|
507
|
-
message: "fallback requires args.ref (e.g. fallback:…)",
|
|
508
|
-
requiredUserInput: ["ref"],
|
|
509
|
-
nextStep: "reinvoke_with_ref",
|
|
510
|
-
},
|
|
511
|
-
};
|
|
512
|
-
}
|
|
513
|
-
if (!deps.readModels?.loadFallbackView) {
|
|
514
|
-
return {
|
|
515
|
-
ok: false,
|
|
516
|
-
error: {
|
|
517
|
-
code: "FALLBACK_READ_MODEL_UNAVAILABLE",
|
|
518
|
-
message: "Operator fallback view requires workspace read models",
|
|
519
|
-
requiredUserInput: ["ref"],
|
|
520
|
-
nextStep: "wire_read_models_into_ops_router",
|
|
521
|
-
},
|
|
522
|
-
};
|
|
523
|
-
}
|
|
524
|
-
return (async () => {
|
|
525
|
-
try {
|
|
526
|
-
const data = await showOperatorFallback(ref, deps.readModels);
|
|
527
|
-
return { ok: true, command: "fallback", data };
|
|
528
|
-
}
|
|
529
|
-
catch (error) {
|
|
530
|
-
if (error instanceof OperatorFallbackNotFoundError) {
|
|
531
478
|
return {
|
|
532
479
|
ok: false,
|
|
533
|
-
command: "
|
|
480
|
+
command: "heartbeat",
|
|
534
481
|
error: {
|
|
535
|
-
code:
|
|
536
|
-
message:
|
|
537
|
-
|
|
538
|
-
nextStep: "verify_fallback_ref_from_delivery_audit",
|
|
482
|
+
code: "HEARTBEAT_CYCLE_EXCEPTION",
|
|
483
|
+
message: msg.slice(0, 200),
|
|
484
|
+
nextStep: "use_heartbeat_check_command",
|
|
539
485
|
},
|
|
540
486
|
};
|
|
541
487
|
}
|
|
542
|
-
|
|
488
|
+
})();
|
|
489
|
+
}
|
|
490
|
+
if (command === "heartbeat_check") {
|
|
491
|
+
const runtimeAvailable = typeof input?.runtimeAvailable === "boolean"
|
|
492
|
+
? input.runtimeAvailable
|
|
493
|
+
: deps.runtimeAvailable;
|
|
494
|
+
// v7 T-V7C.C.2: assemble affordance map and experience writer for breaker-aware heartbeat.
|
|
495
|
+
let affordanceMap;
|
|
496
|
+
if (deps.toolAffordancePort) {
|
|
497
|
+
try {
|
|
498
|
+
affordanceMap = await deps.toolAffordancePort.assembleAffordanceMap({});
|
|
499
|
+
}
|
|
500
|
+
catch {
|
|
501
|
+
// degrade gracefully; guard-layer will skip breaker check without affordanceMap
|
|
502
|
+
}
|
|
543
503
|
}
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
// T1.2.8 (SN-CODE-03): run host capability probe with static unknown adapter (CLI context).
|
|
548
|
-
// Persists report when observabilityDb is available; returns safe JSON subset.
|
|
549
|
-
return (async () => {
|
|
550
|
-
const adapter = createStaticUnknownAdapter(deps.workspaceRoot);
|
|
551
|
-
const docCheckedAt = new Date().toISOString();
|
|
552
|
-
const report = probeHostCapability({
|
|
553
|
-
adapter,
|
|
554
|
-
docLinks: [],
|
|
555
|
-
docCheckedAt,
|
|
556
|
-
});
|
|
557
|
-
if (deps.observabilityDb) {
|
|
558
|
-
await recordHostCapability(deps.observabilityDb, report);
|
|
504
|
+
let experienceWriter;
|
|
505
|
+
if (deps.state) {
|
|
506
|
+
experienceWriter = createExperienceWriter(createToolExperienceStore(deps.state));
|
|
559
507
|
}
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
pluginLoad: { verdict: report.pluginLoad.verdict },
|
|
568
|
-
heartbeatBridge: { verdict: report.heartbeatBridge.verdict },
|
|
569
|
-
heartbeatToolInvocation: {
|
|
570
|
-
verdict: report.heartbeatToolInvocation.verdict,
|
|
508
|
+
// v7 T-V7C.C.6: assemble digest opts when auditStore is wired.
|
|
509
|
+
let digestOpts;
|
|
510
|
+
if (deps.auditStore) {
|
|
511
|
+
digestOpts = {
|
|
512
|
+
assemblerDeps: {
|
|
513
|
+
auditStore: deps.auditStore,
|
|
514
|
+
...deps.heartbeatDigestDeps,
|
|
571
515
|
},
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
}
|
|
592
|
-
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
// v7 T-V7C.C.6: assemble dream schedule port when state DB is wired.
|
|
519
|
+
let dreamSchedulePort;
|
|
520
|
+
if (deps.state) {
|
|
521
|
+
dreamSchedulePort = createQuietDreamSchedulePort(deps.state);
|
|
522
|
+
}
|
|
523
|
+
// v7 T-CP.C.3: assemble goal lifecycle and idle curiosity policies.
|
|
524
|
+
const goalLifecyclePolicy = createGoalLifecyclePolicy();
|
|
525
|
+
const idleCuriosityPolicy = createIdleCuriosityPolicy();
|
|
526
|
+
// v7 T-BTS.C.5: assemble circuit breaker manager when state DB is wired.
|
|
527
|
+
let circuitBreakerManager;
|
|
528
|
+
if (deps.state) {
|
|
529
|
+
const probeResultStore = createCapabilityProbeResultStore(deps.state);
|
|
530
|
+
const toolExpStore = createToolExperienceStore(deps.state);
|
|
531
|
+
const probeAdapter = createProbeSignalAdapter({
|
|
532
|
+
wetProbeRunner: createWetProbeRunner(),
|
|
533
|
+
probeResultStore,
|
|
534
|
+
toolExperienceStore: toolExpStore,
|
|
535
|
+
});
|
|
536
|
+
const registryV7 = new CapabilityContractRegistryV7();
|
|
537
|
+
circuitBreakerManager = createCircuitBreakerManager({
|
|
538
|
+
database: deps.state,
|
|
539
|
+
probeAdapter,
|
|
540
|
+
registry: registryV7,
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
try {
|
|
544
|
+
const result = await heartbeatCheck({
|
|
545
|
+
probeOnly: coerceProbeOnlyFlag(input),
|
|
546
|
+
runtimeAvailable,
|
|
547
|
+
fakeControlPlanePassthrough: input?.fakeControlPlanePassthrough &&
|
|
548
|
+
typeof input.fakeControlPlanePassthrough === "object"
|
|
549
|
+
? input.fakeControlPlanePassthrough
|
|
550
|
+
: undefined,
|
|
551
|
+
readModels: input?.readModels ??
|
|
552
|
+
deps.readModels,
|
|
553
|
+
runtimeRecorder: input
|
|
554
|
+
?.runtimeRecorder ?? deps.runtimeRecorder,
|
|
555
|
+
state: input?.state ??
|
|
556
|
+
deps.state,
|
|
557
|
+
workspaceRoot: input
|
|
558
|
+
?.workspaceRoot ?? deps.workspaceRoot,
|
|
559
|
+
timestamp: typeof input?.timestamp === "string" ? input.timestamp : undefined,
|
|
560
|
+
sessionContext: typeof input?.sessionContext === "string"
|
|
561
|
+
? input.sessionContext
|
|
562
|
+
: undefined,
|
|
563
|
+
scopeHint: input?.scopeHint,
|
|
564
|
+
connectorExecutor: input
|
|
565
|
+
?.connectorExecutor ?? deps.connectorExecutor,
|
|
566
|
+
connectorRegistry: input
|
|
567
|
+
?.connectorRegistry ?? deps.connectorRegistry,
|
|
568
|
+
affordanceMap,
|
|
569
|
+
experienceWriter,
|
|
570
|
+
digestOpts,
|
|
571
|
+
dreamSchedulePort,
|
|
572
|
+
auditStore: deps.auditStore,
|
|
573
|
+
goalLifecyclePolicy,
|
|
574
|
+
idleCuriosityPolicy,
|
|
575
|
+
circuitBreakerManager,
|
|
576
|
+
v8SpineEnabled: input
|
|
577
|
+
?.v8SpineEnabled ?? (deps.state !== undefined),
|
|
578
|
+
});
|
|
579
|
+
if (result.ok &&
|
|
580
|
+
result.surfaceMode === "workspace_full_runtime" &&
|
|
581
|
+
!coerceProbeOnlyFlag(input) &&
|
|
582
|
+
deps.state &&
|
|
583
|
+
deps.restoreSnapshotStore) {
|
|
584
|
+
try {
|
|
585
|
+
const capture = await captureRuntimeSnapshot(deps, {
|
|
586
|
+
snapshotId: `heartbeat:${result.decisionId ?? "cycle"}:${Date.now()}`,
|
|
587
|
+
subjectId: result.decisionId ?? "heartbeat_check",
|
|
588
|
+
reasonCode: "heartbeat_check",
|
|
589
|
+
summaryText: `Heartbeat ${result.status} captured bounded restore snapshot`,
|
|
590
|
+
focus: result.status,
|
|
591
|
+
progress: result.reasons.join(",") || "heartbeat_completed",
|
|
592
|
+
nextIntent: "continue_runtime_loop",
|
|
593
|
+
sourceRefs: result.decisionId
|
|
594
|
+
? [`heartbeat:${result.decisionId}`]
|
|
595
|
+
: ["heartbeat:runtime"],
|
|
596
|
+
});
|
|
597
|
+
if (capture.ok) {
|
|
598
|
+
result.reasons = [...result.reasons, "restore_snapshot_captured"];
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
catch (err) {
|
|
602
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
603
|
+
result.reasons = [...result.reasons, `restore_snapshot_capture_failed:${msg}`];
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
const heartbeatEvidenceLevel = (() => {
|
|
607
|
+
if (!runtimeAvailable || result.surfaceMode === "host_safe_carrier" || result.status === "runtime_carrier_only") {
|
|
608
|
+
return "carrier_ack";
|
|
609
|
+
}
|
|
610
|
+
if (result.schemaParityOnly || coerceProbeOnlyFlag(input)) {
|
|
611
|
+
return "contract_smoke";
|
|
612
|
+
}
|
|
613
|
+
if (result.v8Spine?.cycleId && (result.v8Spine.closureRef || result.v8Spine.noActionReason)) {
|
|
614
|
+
return "real_runtime";
|
|
615
|
+
}
|
|
616
|
+
return "contract_smoke";
|
|
617
|
+
})();
|
|
618
|
+
const heartbeatEnvelope = {
|
|
619
|
+
...result,
|
|
620
|
+
command: "heartbeat_check",
|
|
621
|
+
runtimeMode: runtimeAvailable ? "workspace_full_runtime" : "host_safe_carrier",
|
|
622
|
+
surfaceMode: result.surfaceMode,
|
|
623
|
+
generatedAt: new Date().toISOString(),
|
|
624
|
+
data: result,
|
|
625
|
+
warnings: [],
|
|
626
|
+
sourceRefs: result.decisionId ? [`heartbeat:${result.decisionId}`] : ["heartbeat:runtime"],
|
|
627
|
+
evidenceLevel: heartbeatEvidenceLevel,
|
|
628
|
+
};
|
|
629
|
+
return heartbeatEnvelope;
|
|
630
|
+
}
|
|
631
|
+
catch (err) {
|
|
632
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
633
|
+
const envelope = {
|
|
634
|
+
ok: false,
|
|
635
|
+
command: "heartbeat_check",
|
|
636
|
+
runtimeMode: runtimeAvailable ? "workspace_full_runtime" : "unavailable",
|
|
637
|
+
surfaceMode: runtimeAvailable ? "workspace_full_runtime" : "cli",
|
|
638
|
+
generatedAt: new Date().toISOString(),
|
|
639
|
+
error: {
|
|
640
|
+
code: "HEARTBEAT_CYCLE_EXCEPTION",
|
|
641
|
+
message: `heartbeat_check cycle threw unexpectedly: ${msg.slice(0, 200)}`,
|
|
642
|
+
nextStep: "check_logs_and_report",
|
|
643
|
+
},
|
|
644
|
+
warnings: [],
|
|
645
|
+
sourceRefs: [],
|
|
646
|
+
};
|
|
647
|
+
return envelope;
|
|
648
|
+
}
|
|
593
649
|
}
|
|
594
|
-
|
|
595
|
-
const
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
});
|
|
641
|
-
}
|
|
642
|
-
if (command === "connector_status") {
|
|
643
|
-
return connectorStatus(deps.registry, undefined, {
|
|
644
|
-
includeHealth: Boolean(input?.includeHealth),
|
|
645
|
-
workspaceRoot: typeof input?.workspaceRoot === "string"
|
|
646
|
-
? input.workspaceRoot
|
|
647
|
-
: deps.workspaceRoot,
|
|
648
|
-
});
|
|
649
|
-
}
|
|
650
|
-
if (command === "connector_test") {
|
|
651
|
-
// v7 T-V7C.C.1: dryRun=false is the canonical wet probe switch.
|
|
652
|
-
const isWet = input?.wet === true ||
|
|
653
|
-
input?.wet === "true" ||
|
|
654
|
-
input?.dryRun === false ||
|
|
655
|
-
input?.dryRun === "false";
|
|
656
|
-
const result = await connectorTest(deps.registry, {
|
|
657
|
-
platformId: typeof input?.platformId === "string" ? input.platformId : "",
|
|
658
|
-
dryRun: isWet ? false : (input?.dryRun === false ? false : true),
|
|
659
|
-
workspaceRoot: typeof input?.workspaceRoot === "string"
|
|
660
|
-
? input.workspaceRoot
|
|
661
|
-
: deps.workspaceRoot,
|
|
662
|
-
});
|
|
663
|
-
if (!isWet || !result.ok) {
|
|
664
|
-
return result;
|
|
650
|
+
if (command === "fallback") {
|
|
651
|
+
const ref = typeof input?.ref === "string" ? input.ref.trim() : "";
|
|
652
|
+
if (!ref) {
|
|
653
|
+
return {
|
|
654
|
+
ok: false,
|
|
655
|
+
command: "fallback",
|
|
656
|
+
error: {
|
|
657
|
+
code: "MISSING_FALLBACK_REF",
|
|
658
|
+
message: "fallback requires args.ref (e.g. fallback:…)",
|
|
659
|
+
requiredUserInput: ["ref"],
|
|
660
|
+
nextStep: "reinvoke_with_ref",
|
|
661
|
+
},
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
if (!deps.readModels?.loadFallbackView) {
|
|
665
|
+
return {
|
|
666
|
+
ok: false,
|
|
667
|
+
error: {
|
|
668
|
+
code: "FALLBACK_READ_MODEL_UNAVAILABLE",
|
|
669
|
+
message: "Operator fallback view requires workspace read models",
|
|
670
|
+
requiredUserInput: ["ref"],
|
|
671
|
+
nextStep: "wire_read_models_into_ops_router",
|
|
672
|
+
},
|
|
673
|
+
};
|
|
674
|
+
}
|
|
675
|
+
return (async () => {
|
|
676
|
+
try {
|
|
677
|
+
const data = await showOperatorFallback(ref, deps.readModels);
|
|
678
|
+
return { ok: true, command: "fallback", data };
|
|
679
|
+
}
|
|
680
|
+
catch (error) {
|
|
681
|
+
if (error instanceof OperatorFallbackNotFoundError) {
|
|
682
|
+
return {
|
|
683
|
+
ok: false,
|
|
684
|
+
command: "fallback",
|
|
685
|
+
error: {
|
|
686
|
+
code: error.code,
|
|
687
|
+
message: error.message,
|
|
688
|
+
requiredUserInput: ["ref"],
|
|
689
|
+
nextStep: "verify_fallback_ref_from_delivery_audit",
|
|
690
|
+
},
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
throw error;
|
|
694
|
+
}
|
|
695
|
+
})();
|
|
665
696
|
}
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
697
|
+
if (command === "capability_probe") {
|
|
698
|
+
// T1.2.8 (SN-CODE-03): run host capability probe with static unknown adapter (CLI context).
|
|
699
|
+
// Persists report when observabilityDb is available; returns safe JSON subset.
|
|
700
|
+
return (async () => {
|
|
701
|
+
const adapter = createStaticUnknownAdapter(deps.workspaceRoot);
|
|
702
|
+
const docCheckedAt = new Date().toISOString();
|
|
703
|
+
const report = probeHostCapability({
|
|
704
|
+
adapter,
|
|
705
|
+
docLinks: [],
|
|
706
|
+
docCheckedAt,
|
|
707
|
+
});
|
|
708
|
+
if (deps.observabilityDb) {
|
|
709
|
+
await recordHostCapability(deps.observabilityDb, report);
|
|
710
|
+
}
|
|
711
|
+
return {
|
|
712
|
+
ok: true,
|
|
713
|
+
command: "capability_probe",
|
|
714
|
+
data: {
|
|
715
|
+
reportId: report.reportId,
|
|
716
|
+
generatedAt: report.generatedAt,
|
|
717
|
+
deliveryTarget: report.deliveryTarget,
|
|
718
|
+
pluginLoad: { verdict: report.pluginLoad.verdict },
|
|
719
|
+
heartbeatBridge: { verdict: report.heartbeatBridge.verdict },
|
|
720
|
+
heartbeatToolInvocation: {
|
|
721
|
+
verdict: report.heartbeatToolInvocation.verdict,
|
|
722
|
+
},
|
|
723
|
+
ackDropBehavior: { verdict: report.ackDropBehavior.verdict },
|
|
724
|
+
conflictCount: report.conflictRecords.length,
|
|
725
|
+
recommendedNextStep: report.recommendedNextStep,
|
|
726
|
+
note: "static_local_probe: all verdicts are unknown without live host context",
|
|
727
|
+
},
|
|
728
|
+
};
|
|
729
|
+
})();
|
|
730
|
+
}
|
|
731
|
+
if (command === "near_real_smoke") {
|
|
732
|
+
// T3.3.2 (SN-CODE-05): wrap runNearRealConnectorSmoke as an ops surface command.
|
|
733
|
+
// Requires state + observabilityDb + workspaceRoot to be wired into OpsRouterDeps.
|
|
734
|
+
if (!deps.state || !deps.observabilityDb || !deps.workspaceRoot) {
|
|
735
|
+
return {
|
|
736
|
+
ok: false,
|
|
737
|
+
command: "near_real_smoke",
|
|
738
|
+
error: {
|
|
739
|
+
code: "NEAR_REAL_SMOKE_DEPS_UNAVAILABLE",
|
|
740
|
+
message: "near_real_smoke requires state, observabilityDb, and workspaceRoot in OpsRouterDeps",
|
|
741
|
+
nextStep: "wire_deps_into_ops_router",
|
|
742
|
+
},
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
return (async () => {
|
|
746
|
+
const result = await runNearRealConnectorSmoke({
|
|
747
|
+
state: deps.state,
|
|
748
|
+
observabilityDb: deps.observabilityDb,
|
|
749
|
+
workspaceRoot: deps.workspaceRoot,
|
|
750
|
+
});
|
|
751
|
+
return {
|
|
752
|
+
ok: true,
|
|
753
|
+
command: "near_real_smoke",
|
|
754
|
+
data: result,
|
|
755
|
+
};
|
|
756
|
+
})();
|
|
684
757
|
}
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
758
|
+
if (command === "connector_init") {
|
|
759
|
+
// T1.3.1 (SN-CODE-06): generate connector manifest stub.
|
|
760
|
+
return (async () => {
|
|
761
|
+
const result = await connectorInit({
|
|
762
|
+
platformId: typeof input?.platformId === "string" ? input.platformId : "",
|
|
763
|
+
family: typeof input?.family === "string"
|
|
764
|
+
? input.family
|
|
765
|
+
: undefined,
|
|
766
|
+
displayName: typeof input?.displayName === "string" ? input.displayName : undefined,
|
|
767
|
+
runnerKind: typeof input?.runnerKind === "string"
|
|
768
|
+
? input.runnerKind
|
|
769
|
+
: undefined,
|
|
770
|
+
force: Boolean(input?.force),
|
|
771
|
+
workspaceRoot: deps.workspaceRoot,
|
|
772
|
+
});
|
|
773
|
+
return { command: "connector_init", ...result };
|
|
774
|
+
})();
|
|
689
775
|
}
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
? input.
|
|
700
|
-
:
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
let persistedProbeResult = false;
|
|
707
|
-
if (deps.state) {
|
|
708
|
-
await createCapabilityProbeResultStore(deps.state).appendProbeResult(wetResult.probeResult);
|
|
709
|
-
persistedProbeResult = true;
|
|
776
|
+
if (command === "connector_behavior_add") {
|
|
777
|
+
return connectorBehaviorAdd({
|
|
778
|
+
platformId: typeof input?.platformId === "string" ? input.platformId : "",
|
|
779
|
+
behaviorId: typeof input?.behaviorId === "string"
|
|
780
|
+
? input.behaviorId
|
|
781
|
+
: typeof input?.capabilityId === "string"
|
|
782
|
+
? input.capabilityId
|
|
783
|
+
: "",
|
|
784
|
+
description: typeof input?.description === "string" ? input.description : undefined,
|
|
785
|
+
channel: typeof input?.channel === "string" ? input.channel : undefined,
|
|
786
|
+
sourceRefs: input?.sourceRefs,
|
|
787
|
+
observedCount: typeof input?.observedCount === "number" ? input.observedCount : undefined,
|
|
788
|
+
workspaceRoot: typeof input?.workspaceRoot === "string"
|
|
789
|
+
? input.workspaceRoot
|
|
790
|
+
: deps.workspaceRoot,
|
|
791
|
+
});
|
|
710
792
|
}
|
|
711
|
-
|
|
712
|
-
|
|
793
|
+
if (command === "connector_status") {
|
|
794
|
+
return connectorStatus(deps.registry, undefined, {
|
|
795
|
+
includeHealth: Boolean(input?.includeHealth),
|
|
796
|
+
workspaceRoot: typeof input?.workspaceRoot === "string"
|
|
797
|
+
? input.workspaceRoot
|
|
798
|
+
: deps.workspaceRoot,
|
|
799
|
+
});
|
|
713
800
|
}
|
|
714
|
-
|
|
715
|
-
// T-V7C.C.
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
801
|
+
if (command === "connector_test") {
|
|
802
|
+
// v7 T-V7C.C.1: dryRun=false is the canonical wet probe switch.
|
|
803
|
+
const isWet = input?.wet === true ||
|
|
804
|
+
input?.wet === "true" ||
|
|
805
|
+
input?.dryRun === false ||
|
|
806
|
+
input?.dryRun === "false";
|
|
807
|
+
const result = await connectorTest(deps.registry, {
|
|
808
|
+
platformId: typeof input?.platformId === "string" ? input.platformId : "",
|
|
809
|
+
dryRun: isWet ? false : (input?.dryRun === false ? false : true),
|
|
810
|
+
workspaceRoot: typeof input?.workspaceRoot === "string"
|
|
811
|
+
? input.workspaceRoot
|
|
812
|
+
: deps.workspaceRoot,
|
|
813
|
+
});
|
|
814
|
+
if (!isWet || !result.ok) {
|
|
815
|
+
return result;
|
|
816
|
+
}
|
|
817
|
+
const data = result.data && typeof result.data === "object"
|
|
818
|
+
? result.data
|
|
819
|
+
: {};
|
|
820
|
+
const capabilities = Array.isArray(data.capabilities)
|
|
821
|
+
? data.capabilities.filter((item) => typeof item === "string")
|
|
822
|
+
: [];
|
|
823
|
+
const capabilityId = textInput(input, "capabilityId") ?? capabilities[0] ?? "";
|
|
824
|
+
if (!capabilityId) {
|
|
825
|
+
return {
|
|
826
|
+
ok: false,
|
|
827
|
+
command: "connector_test",
|
|
828
|
+
error: {
|
|
829
|
+
code: "MISSING_CAPABILITY_ID",
|
|
830
|
+
message: "wet connector_test requires capabilityId or at least one connector capability",
|
|
831
|
+
requiredUserInput: ["capabilityId"],
|
|
832
|
+
nextStep: "reinvoke_with_capability_id",
|
|
833
|
+
},
|
|
834
|
+
};
|
|
835
|
+
}
|
|
836
|
+
const platformId = String(data.platformId ?? input?.platformId ?? "");
|
|
837
|
+
const registryEntry = deps.registry?.describeConnector(platformId);
|
|
838
|
+
if (!registryEntry) {
|
|
839
|
+
return result;
|
|
840
|
+
}
|
|
841
|
+
const registryV7 = new CapabilityContractRegistryV7();
|
|
842
|
+
registerConnectorForWetProbe({
|
|
843
|
+
registryV7,
|
|
844
|
+
entry: {
|
|
845
|
+
platformId: registryEntry.platformId,
|
|
846
|
+
capabilities: registryEntry.capabilities,
|
|
847
|
+
manifestPath: registryEntry.manifestPath,
|
|
749
848
|
},
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
849
|
+
workspaceRoot: typeof input?.workspaceRoot === "string"
|
|
850
|
+
? input.workspaceRoot
|
|
851
|
+
: deps.workspaceRoot,
|
|
852
|
+
selectedCapabilityId: capabilityId,
|
|
853
|
+
safeEndpoint: textInput(input, "safeEndpoint"),
|
|
854
|
+
});
|
|
855
|
+
const wetResult = await createWetProbeRunner().runWetProbe(platformId, capabilityId, registryV7);
|
|
856
|
+
const warnings = [];
|
|
857
|
+
let persistedProbeResult = false;
|
|
858
|
+
if (deps.state) {
|
|
859
|
+
await createCapabilityProbeResultStore(deps.state).appendProbeResult(wetResult.probeResult);
|
|
860
|
+
persistedProbeResult = true;
|
|
861
|
+
}
|
|
862
|
+
else {
|
|
863
|
+
warnings.push("state_db_unavailable:capability_probe_result_not_persisted");
|
|
864
|
+
}
|
|
753
865
|
return {
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
866
|
+
// T-V7C.C.5: only "available" (HTTP 200-299) counts as success;
|
|
867
|
+
// "degraded" (429/503) and "unavailable" both result in ok=false.
|
|
868
|
+
ok: wetResult.probeResult.actualStatus === "available",
|
|
869
|
+
command: "connector_test",
|
|
870
|
+
data: {
|
|
871
|
+
...data,
|
|
872
|
+
dryRun: false,
|
|
873
|
+
capabilityId,
|
|
874
|
+
actualStatus: wetResult.probeResult.actualStatus,
|
|
875
|
+
httpStatus: wetResult.probeResult.httpStatus ?? wetResult.httpStatus,
|
|
876
|
+
probeResultId: wetResult.probeResult.probeResultId,
|
|
877
|
+
probeConfigRef: wetResult.probeResult.probeConfigRef,
|
|
878
|
+
sampleResponseRef: wetResult.probeResult.sampleResponseRef,
|
|
879
|
+
persistedProbeResult,
|
|
880
|
+
triggerSource: "manual_run",
|
|
881
|
+
affectsHeartbeatCadence: false,
|
|
882
|
+
note: "wet probe mode: executed safe probe endpoint and persisted capability_probe_result when state DB is available",
|
|
760
883
|
},
|
|
884
|
+
warnings,
|
|
761
885
|
};
|
|
762
886
|
}
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
887
|
+
if (command === "connector:run") {
|
|
888
|
+
// T-ROS.C.3: manual connector execution — isolated from heartbeat cadence
|
|
889
|
+
const platformId = typeof input?.platformId === "string" ? input.platformId : "";
|
|
890
|
+
const capabilityId = typeof input?.capabilityId === "string" ? input.capabilityId : "";
|
|
891
|
+
if (!platformId || !capabilityId) {
|
|
892
|
+
return {
|
|
893
|
+
ok: false,
|
|
894
|
+
command: "connector:run",
|
|
895
|
+
error: {
|
|
896
|
+
code: "MISSING_PLATFORM_OR_CAPABILITY_ID",
|
|
897
|
+
message: "connector:run requires platformId and capabilityId",
|
|
898
|
+
requiredUserInput: ["platformId", "capabilityId"],
|
|
899
|
+
nextStep: "reinvoke_with_platform_and_capability_id",
|
|
900
|
+
},
|
|
901
|
+
};
|
|
902
|
+
}
|
|
903
|
+
if (!deps.connectorExecutor || !deps.state) {
|
|
904
|
+
return {
|
|
905
|
+
ok: false,
|
|
906
|
+
command: "connector:run",
|
|
907
|
+
error: {
|
|
908
|
+
code: "MANUAL_RUN_DEPS_UNAVAILABLE",
|
|
909
|
+
message: "connector:run requires connectorExecutor and state database",
|
|
910
|
+
nextStep: "wire_connector_executor_and_state_into_ops_router",
|
|
911
|
+
},
|
|
912
|
+
};
|
|
913
|
+
}
|
|
914
|
+
const toolExperienceStore = createToolExperienceStore(deps.state);
|
|
915
|
+
const experienceWriter = createExperienceWriter(toolExperienceStore);
|
|
916
|
+
const wetProbeRunner = createWetProbeRunner();
|
|
917
|
+
const registryV7 = new CapabilityContractRegistryV7();
|
|
918
|
+
// Populate V7 registry from dynamic registry if available (best-effort)
|
|
919
|
+
if (deps.registry) {
|
|
920
|
+
for (const entry of deps.registry.listConnectors()) {
|
|
921
|
+
if (entry.manifestPath) {
|
|
922
|
+
try {
|
|
923
|
+
const manifestText = fs.readFileSync(entry.manifestPath, "utf-8");
|
|
924
|
+
const manifest = JSON.parse(manifestText);
|
|
925
|
+
registryV7.register(manifest);
|
|
926
|
+
}
|
|
927
|
+
catch {
|
|
928
|
+
// Skip manifests that can't be read or don't validate as V7
|
|
929
|
+
}
|
|
778
930
|
}
|
|
779
931
|
}
|
|
780
932
|
}
|
|
933
|
+
const dispatcher = createManualRunDispatcher({
|
|
934
|
+
connectorExecutor: deps.connectorExecutor,
|
|
935
|
+
experienceWriter,
|
|
936
|
+
wetProbeRunner,
|
|
937
|
+
registryV7,
|
|
938
|
+
auditStore: deps.auditStore,
|
|
939
|
+
state: deps.state,
|
|
940
|
+
workspaceRoot: typeof input?.workspaceRoot === "string" ? input.workspaceRoot : process.cwd(),
|
|
941
|
+
});
|
|
942
|
+
return dispatcher.runConnector({
|
|
943
|
+
platformId,
|
|
944
|
+
capabilityId,
|
|
945
|
+
payload: typeof input?.payload === "object" && input?.payload !== null
|
|
946
|
+
? input.payload
|
|
947
|
+
: undefined,
|
|
948
|
+
caller: typeof input?.caller === "string" ? input.caller : undefined,
|
|
949
|
+
reason: typeof input?.reason === "string" ? input.reason : undefined,
|
|
950
|
+
});
|
|
781
951
|
}
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
payload: typeof input?.payload === "object" && input?.payload !== null
|
|
795
|
-
? input.payload
|
|
796
|
-
: undefined,
|
|
797
|
-
caller: typeof input?.caller === "string" ? input.caller : undefined,
|
|
798
|
-
reason: typeof input?.reason === "string" ? input.reason : undefined,
|
|
799
|
-
});
|
|
800
|
-
}
|
|
801
|
-
if (command === "goal") {
|
|
802
|
-
const rawAction = typeof input?.action === "string" ? input.action : "list";
|
|
803
|
-
const action = ["set", "list", "accept", "reject"].includes(rawAction)
|
|
804
|
-
? rawAction
|
|
805
|
-
: "list";
|
|
806
|
-
const sanitizeText = (v, maxLen = 1000) => {
|
|
807
|
-
if (typeof v !== "string")
|
|
808
|
-
return undefined;
|
|
809
|
-
const trimmed = v.trim();
|
|
810
|
-
if (trimmed.length === 0)
|
|
811
|
-
return undefined;
|
|
812
|
-
return trimmed.slice(0, maxLen);
|
|
813
|
-
};
|
|
814
|
-
return goalCommand(deps.state, {
|
|
815
|
-
action,
|
|
816
|
-
goalId: typeof input?.goalId === "string" ? input.goalId.trim().slice(0, 128) : undefined,
|
|
817
|
-
description: sanitizeText(input?.description),
|
|
818
|
-
completionCriteria: sanitizeText(input?.completionCriteria),
|
|
819
|
-
// T1.4.2: criteria alias for completionCriteria
|
|
820
|
-
criteria: sanitizeText(input?.criteria),
|
|
821
|
-
risk: typeof input?.risk === "string"
|
|
822
|
-
? input.risk
|
|
823
|
-
: undefined,
|
|
824
|
-
kind: typeof input?.kind === "string"
|
|
825
|
-
? input.kind
|
|
826
|
-
: undefined,
|
|
827
|
-
statusFilter: typeof input?.statusFilter === "string" ? input.statusFilter : undefined,
|
|
828
|
-
originFilter: typeof input?.originFilter === "string" ? input.originFilter : undefined,
|
|
829
|
-
limit: typeof input?.limit === "number" ? input.limit : undefined,
|
|
830
|
-
});
|
|
831
|
-
}
|
|
832
|
-
if (command === "dream:recent") {
|
|
833
|
-
if (!deps.readModels) {
|
|
834
|
-
return {
|
|
835
|
-
ok: false,
|
|
836
|
-
error: {
|
|
837
|
-
code: "READ_MODELS_UNAVAILABLE",
|
|
838
|
-
message: "dream:recent requires workspace read models",
|
|
839
|
-
nextStep: "wire_read_models_into_ops_router",
|
|
840
|
-
},
|
|
841
|
-
};
|
|
842
|
-
}
|
|
843
|
-
const limit = typeof input?.limit === "number" ? input.limit : 5;
|
|
844
|
-
const data = await deps.readModels.loadDreamRecent(limit);
|
|
845
|
-
return { ok: true, data };
|
|
846
|
-
}
|
|
847
|
-
if (command === "cycle:recent") {
|
|
848
|
-
if (!deps.readModels) {
|
|
849
|
-
return {
|
|
850
|
-
ok: false,
|
|
851
|
-
error: {
|
|
852
|
-
code: "READ_MODELS_UNAVAILABLE",
|
|
853
|
-
message: "cycle:recent requires workspace read models",
|
|
854
|
-
nextStep: "wire_read_models_into_ops_router",
|
|
855
|
-
},
|
|
856
|
-
};
|
|
857
|
-
}
|
|
858
|
-
const limit = typeof input?.limit === "number" ? input.limit : 5;
|
|
859
|
-
const data = await deps.readModels.loadCycleRecent(limit);
|
|
860
|
-
return { ok: true, data };
|
|
861
|
-
}
|
|
862
|
-
// ─── v8 commands (T-ROS.C.1) ─────────────────────────────────────────
|
|
863
|
-
/**
|
|
864
|
-
* [G1] loop_status — v8 causal loop health read model.
|
|
865
|
-
* Returns machine-readable overallStatus, stalledAt, stageSummaries,
|
|
866
|
-
* and human-readable nextAction for operator diagnosis.
|
|
867
|
-
*/
|
|
868
|
-
if (command === "loop_status") {
|
|
869
|
-
const generatedAt = new Date().toISOString();
|
|
870
|
-
if (!deps.state) {
|
|
871
|
-
const envelope = {
|
|
872
|
-
ok: false,
|
|
873
|
-
command: "loop_status",
|
|
874
|
-
runtimeMode: "unavailable",
|
|
875
|
-
surfaceMode: "cli",
|
|
876
|
-
generatedAt,
|
|
877
|
-
error: {
|
|
878
|
-
code: "STATE_DB_UNAVAILABLE",
|
|
879
|
-
message: "loop_status requires state database in OpsRouterDeps",
|
|
880
|
-
nextStep: "wire_state_db_into_ops_router",
|
|
881
|
-
},
|
|
882
|
-
warnings: [],
|
|
883
|
-
sourceRefs: [],
|
|
952
|
+
if (command === "goal") {
|
|
953
|
+
const rawAction = typeof input?.action === "string" ? input.action : "list";
|
|
954
|
+
const action = ["set", "list", "accept", "reject"].includes(rawAction)
|
|
955
|
+
? rawAction
|
|
956
|
+
: "list";
|
|
957
|
+
const sanitizeText = (v, maxLen = 1000) => {
|
|
958
|
+
if (typeof v !== "string")
|
|
959
|
+
return undefined;
|
|
960
|
+
const trimmed = v.trim();
|
|
961
|
+
if (trimmed.length === 0)
|
|
962
|
+
return undefined;
|
|
963
|
+
return trimmed.slice(0, maxLen);
|
|
884
964
|
};
|
|
885
|
-
return
|
|
965
|
+
return goalCommand(deps.state, {
|
|
966
|
+
action,
|
|
967
|
+
goalId: typeof input?.goalId === "string" ? input.goalId.trim().slice(0, 128) : undefined,
|
|
968
|
+
description: sanitizeText(input?.description),
|
|
969
|
+
completionCriteria: sanitizeText(input?.completionCriteria),
|
|
970
|
+
// T1.4.2: criteria alias for completionCriteria
|
|
971
|
+
criteria: sanitizeText(input?.criteria),
|
|
972
|
+
risk: typeof input?.risk === "string"
|
|
973
|
+
? input.risk
|
|
974
|
+
: undefined,
|
|
975
|
+
kind: typeof input?.kind === "string"
|
|
976
|
+
? input.kind
|
|
977
|
+
: undefined,
|
|
978
|
+
statusFilter: typeof input?.statusFilter === "string" ? input.statusFilter : undefined,
|
|
979
|
+
originFilter: typeof input?.originFilter === "string" ? input.originFilter : undefined,
|
|
980
|
+
limit: typeof input?.limit === "number" ? input.limit : undefined,
|
|
981
|
+
});
|
|
886
982
|
}
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
983
|
+
// ─── v8 commands (T-ROS.C.1) ─────────────────────────────────────────
|
|
984
|
+
/**
|
|
985
|
+
* [G1] loop_status — v8 causal loop health read model.
|
|
986
|
+
* Returns machine-readable overallStatus, stalledAt, stageSummaries,
|
|
987
|
+
* and human-readable nextAction for operator diagnosis.
|
|
988
|
+
*/
|
|
989
|
+
if (command === "loop_status") {
|
|
990
|
+
const generatedAt = new Date().toISOString();
|
|
991
|
+
if (!deps.state) {
|
|
890
992
|
const envelope = {
|
|
891
993
|
ok: false,
|
|
892
994
|
command: "loop_status",
|
|
893
|
-
runtimeMode: "
|
|
995
|
+
runtimeMode: "unavailable",
|
|
894
996
|
surfaceMode: "cli",
|
|
895
997
|
generatedAt,
|
|
896
998
|
error: {
|
|
897
|
-
code: "
|
|
898
|
-
message:
|
|
899
|
-
nextStep: "
|
|
999
|
+
code: "STATE_DB_UNAVAILABLE",
|
|
1000
|
+
message: "loop_status requires state database in OpsRouterDeps",
|
|
1001
|
+
nextStep: "wire_state_db_into_ops_router",
|
|
900
1002
|
},
|
|
901
|
-
warnings: [
|
|
902
|
-
sourceRefs:
|
|
1003
|
+
warnings: [],
|
|
1004
|
+
sourceRefs: [],
|
|
903
1005
|
};
|
|
904
1006
|
return envelope;
|
|
905
1007
|
}
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
command: "loop_status",
|
|
923
|
-
runtimeMode: "unavailable",
|
|
924
|
-
surfaceMode: "cli",
|
|
925
|
-
generatedAt,
|
|
926
|
-
error: { code: "LOOP_STATUS_EXCEPTION", message: msg },
|
|
927
|
-
warnings: [],
|
|
928
|
-
sourceRefs: [],
|
|
929
|
-
};
|
|
930
|
-
return envelope;
|
|
931
|
-
}
|
|
932
|
-
}
|
|
933
|
-
// ─── v7 commands (T-ROS.C.1) ─────────────────────────────────────────
|
|
934
|
-
/** [G2] self_health — transparent pass-through from SelfHealthSnapshot (DR-042). */
|
|
935
|
-
if (command === "self_health") {
|
|
936
|
-
const generatedAt = new Date().toISOString();
|
|
937
|
-
try {
|
|
938
|
-
ensureMinimumProbes();
|
|
939
|
-
const snap = await getSelfHealthSnapshot();
|
|
940
|
-
const degraded_dimensions = Object.entries(snap.dimensions)
|
|
941
|
-
.filter(([, d]) => d.status === "degraded")
|
|
942
|
-
.map(([k]) => k);
|
|
943
|
-
const envelope = {
|
|
944
|
-
ok: true,
|
|
945
|
-
command: "self_health",
|
|
946
|
-
runtimeMode: "workspace_full_runtime",
|
|
947
|
-
surfaceMode: "cli",
|
|
948
|
-
generatedAt,
|
|
949
|
-
data: {
|
|
950
|
-
overall: snap.overall,
|
|
951
|
-
generatedAt: snap.generatedAt,
|
|
952
|
-
degraded_dimensions,
|
|
953
|
-
dimensions: snap.dimensions,
|
|
954
|
-
},
|
|
955
|
-
warnings: [],
|
|
956
|
-
sourceRefs: ["observability/services/self-health-snapshot.ts"],
|
|
957
|
-
};
|
|
958
|
-
return envelope;
|
|
959
|
-
}
|
|
960
|
-
catch (err) {
|
|
961
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
962
|
-
const envelope = {
|
|
963
|
-
ok: false,
|
|
964
|
-
command: "self_health",
|
|
965
|
-
runtimeMode: "unavailable",
|
|
966
|
-
surfaceMode: "cli",
|
|
967
|
-
generatedAt,
|
|
968
|
-
error: { code: "SELF_HEALTH_PROBE_FAILED", message: msg },
|
|
969
|
-
warnings: [],
|
|
970
|
-
sourceRefs: [],
|
|
971
|
-
};
|
|
972
|
-
return envelope;
|
|
973
|
-
}
|
|
974
|
-
}
|
|
975
|
-
/**
|
|
976
|
-
* [G3] tool_affordance — body-tool AffordanceMap pass-through.
|
|
977
|
-
* Port not yet wired in this wave; returns degraded view with clear next-step.
|
|
978
|
-
*/
|
|
979
|
-
if (command === "tool_affordance") {
|
|
980
|
-
const generatedAt = new Date().toISOString();
|
|
981
|
-
if (deps.toolAffordancePort) {
|
|
982
|
-
const allStatuses = [
|
|
983
|
-
"safe",
|
|
984
|
-
"exploratory",
|
|
985
|
-
"needs_auth",
|
|
986
|
-
"painful",
|
|
987
|
-
"unavailable",
|
|
988
|
-
];
|
|
989
|
-
const platformIds = Array.isArray(input?.platformIds)
|
|
990
|
-
? input.platformIds.filter((item) => typeof item === "string")
|
|
991
|
-
: typeof input?.platformId === "string"
|
|
992
|
-
? [input.platformId]
|
|
993
|
-
: undefined;
|
|
994
|
-
const data = await deps.toolAffordancePort.assembleAffordanceMap({
|
|
995
|
-
platformIds,
|
|
996
|
-
allowedStatuses: allStatuses,
|
|
997
|
-
goalKind: typeof input?.goalKind === "string" ? input.goalKind : undefined,
|
|
998
|
-
});
|
|
999
|
-
const envelope = {
|
|
1000
|
-
ok: true,
|
|
1001
|
-
command: "tool_affordance",
|
|
1002
|
-
runtimeMode: "workspace_full_runtime",
|
|
1003
|
-
surfaceMode: "cli",
|
|
1004
|
-
generatedAt,
|
|
1005
|
-
data,
|
|
1006
|
-
warnings: [],
|
|
1007
|
-
sourceRefs: [
|
|
1008
|
-
"core/second-nature/body/tool-affordance/affordance-assembler.ts",
|
|
1009
|
-
],
|
|
1010
|
-
};
|
|
1011
|
-
return envelope;
|
|
1012
|
-
}
|
|
1013
|
-
const envelope = {
|
|
1014
|
-
ok: false,
|
|
1015
|
-
command: "tool_affordance",
|
|
1016
|
-
runtimeMode: "unavailable",
|
|
1017
|
-
surfaceMode: "cli",
|
|
1018
|
-
generatedAt,
|
|
1019
|
-
error: {
|
|
1020
|
-
code: "TOOL_AFFORDANCE_PORT_UNWIRED",
|
|
1021
|
-
message: "tool_affordance requires body-tool AffordanceMap port (T-BTS.C.1) to be wired into OpsRouterDeps",
|
|
1022
|
-
nextStep: "wire_body_tool_port_into_ops_router_deps",
|
|
1023
|
-
},
|
|
1024
|
-
warnings: [],
|
|
1025
|
-
sourceRefs: [],
|
|
1026
|
-
};
|
|
1027
|
-
return envelope;
|
|
1028
|
-
}
|
|
1029
|
-
/**
|
|
1030
|
-
* [G6] heartbeat_digest — wraps generateHeartbeatDigest.
|
|
1031
|
-
* Requires auditStore in deps; degrades if unavailable.
|
|
1032
|
-
*/
|
|
1033
|
-
if (command === "heartbeat_digest") {
|
|
1034
|
-
const generatedAt = new Date().toISOString();
|
|
1035
|
-
if (!deps.auditStore) {
|
|
1036
|
-
const envelope = {
|
|
1037
|
-
ok: false,
|
|
1038
|
-
command: "heartbeat_digest",
|
|
1039
|
-
runtimeMode: "unavailable",
|
|
1040
|
-
surfaceMode: "cli",
|
|
1041
|
-
generatedAt,
|
|
1042
|
-
error: {
|
|
1043
|
-
code: "AUDIT_STORE_UNAVAILABLE",
|
|
1044
|
-
message: "heartbeat_digest requires auditStore in OpsRouterDeps",
|
|
1045
|
-
nextStep: "wire_audit_store_into_ops_router",
|
|
1046
|
-
},
|
|
1047
|
-
warnings: [],
|
|
1048
|
-
sourceRefs: [],
|
|
1049
|
-
};
|
|
1050
|
-
return envelope;
|
|
1051
|
-
}
|
|
1052
|
-
const date = typeof input?.date === "string" && input.date
|
|
1053
|
-
? input.date
|
|
1054
|
-
: new Date().toISOString().slice(0, 10);
|
|
1055
|
-
try {
|
|
1056
|
-
const digestDeps = {
|
|
1057
|
-
auditStore: deps.auditStore,
|
|
1058
|
-
...deps.heartbeatDigestDeps,
|
|
1059
|
-
};
|
|
1060
|
-
const digest = await generateHeartbeatDigest(date, digestDeps);
|
|
1061
|
-
// T-OBS.R.3: Embed real-run health into digest when state DB is available
|
|
1062
|
-
if (deps.state) {
|
|
1063
|
-
const realRunResult = await checkRealRunHealth(deps.state, date);
|
|
1064
|
-
if (realRunResult.ok) {
|
|
1065
|
-
digest.realRunHealth = {
|
|
1066
|
-
gatePassed: realRunResult.gate.gatePassed,
|
|
1067
|
-
contractSmokeOnly: realRunResult.gate.contractSmokeOnly,
|
|
1068
|
-
seededStateDetected: realRunResult.gate.seededStateDetected,
|
|
1069
|
-
hasRealClosure: realRunResult.gate.hasRealClosure,
|
|
1070
|
-
hasQuietArtifact: realRunResult.gate.hasQuietArtifact,
|
|
1071
|
-
hasDreamArtifact: realRunResult.gate.hasDreamArtifact,
|
|
1072
|
-
hasFreshImpulseContext: realRunResult.gate.hasFreshImpulseContext,
|
|
1073
|
-
hasProjectionFeedback: realRunResult.gate.hasProjectionFeedback,
|
|
1074
|
-
missingStage: realRunResult.gate.missingStage,
|
|
1075
|
-
missingReason: realRunResult.gate.missingReason,
|
|
1076
|
-
};
|
|
1077
|
-
}
|
|
1078
|
-
else {
|
|
1079
|
-
digest.realRunHealth = {
|
|
1080
|
-
gatePassed: false,
|
|
1081
|
-
contractSmokeOnly: false,
|
|
1082
|
-
seededStateDetected: false,
|
|
1083
|
-
hasRealClosure: false,
|
|
1084
|
-
hasQuietArtifact: false,
|
|
1085
|
-
hasDreamArtifact: false,
|
|
1086
|
-
hasFreshImpulseContext: false,
|
|
1087
|
-
hasProjectionFeedback: false,
|
|
1088
|
-
missingReason: "Real-run health check degraded: " + realRunResult.degraded.reason,
|
|
1008
|
+
try {
|
|
1009
|
+
const result = await readLoopStatus(deps.state);
|
|
1010
|
+
if (!result.ok) {
|
|
1011
|
+
const envelope = {
|
|
1012
|
+
ok: false,
|
|
1013
|
+
command: "loop_status",
|
|
1014
|
+
runtimeMode: "workspace_full_runtime",
|
|
1015
|
+
surfaceMode: "cli",
|
|
1016
|
+
generatedAt,
|
|
1017
|
+
error: {
|
|
1018
|
+
code: "LOOP_STATUS_DEGRADED",
|
|
1019
|
+
message: result.degraded.operatorNextAction,
|
|
1020
|
+
nextStep: "check_state_db_and_retry",
|
|
1021
|
+
},
|
|
1022
|
+
warnings: [result.degraded.reason],
|
|
1023
|
+
sourceRefs: result.degraded.sourceRefs.map((r) => r.uri),
|
|
1089
1024
|
};
|
|
1025
|
+
return envelope;
|
|
1090
1026
|
}
|
|
1027
|
+
const envelope = {
|
|
1028
|
+
ok: true,
|
|
1029
|
+
command: "loop_status",
|
|
1030
|
+
runtimeMode: "workspace_full_runtime",
|
|
1031
|
+
surfaceMode: "cli",
|
|
1032
|
+
generatedAt,
|
|
1033
|
+
data: result.status,
|
|
1034
|
+
warnings: [],
|
|
1035
|
+
sourceRefs: [],
|
|
1036
|
+
evidenceLevel: result.status.evidenceLevel,
|
|
1037
|
+
};
|
|
1038
|
+
return envelope;
|
|
1039
|
+
}
|
|
1040
|
+
catch (err) {
|
|
1041
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1042
|
+
const envelope = {
|
|
1043
|
+
ok: false,
|
|
1044
|
+
command: "loop_status",
|
|
1045
|
+
runtimeMode: "unavailable",
|
|
1046
|
+
surfaceMode: "cli",
|
|
1047
|
+
generatedAt,
|
|
1048
|
+
error: { code: "LOOP_STATUS_EXCEPTION", message: msg },
|
|
1049
|
+
warnings: [],
|
|
1050
|
+
sourceRefs: [],
|
|
1051
|
+
};
|
|
1052
|
+
return envelope;
|
|
1091
1053
|
}
|
|
1092
|
-
const envelope = {
|
|
1093
|
-
ok: true,
|
|
1094
|
-
command: "heartbeat_digest",
|
|
1095
|
-
runtimeMode: "workspace_full_runtime",
|
|
1096
|
-
surfaceMode: "cli",
|
|
1097
|
-
generatedAt,
|
|
1098
|
-
data: digest,
|
|
1099
|
-
warnings: [],
|
|
1100
|
-
sourceRefs: ["observability/services/heartbeat-digest-assembler.ts"],
|
|
1101
|
-
};
|
|
1102
|
-
return envelope;
|
|
1103
1054
|
}
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1055
|
+
// ─── v7 commands (T-ROS.C.1) ─────────────────────────────────────────
|
|
1056
|
+
/** [G2] self_health — transparent pass-through from SelfHealthSnapshot (DR-042). */
|
|
1057
|
+
if (command === "self_health") {
|
|
1058
|
+
const generatedAt = new Date().toISOString();
|
|
1059
|
+
try {
|
|
1060
|
+
ensureMinimumProbes();
|
|
1061
|
+
const snap = await getSelfHealthSnapshot();
|
|
1062
|
+
const degraded_dimensions = Object.entries(snap.dimensions)
|
|
1063
|
+
.filter(([, d]) => d.status === "degraded")
|
|
1064
|
+
.map(([k]) => k);
|
|
1065
|
+
const envelope = {
|
|
1066
|
+
ok: true,
|
|
1067
|
+
command: "self_health",
|
|
1068
|
+
runtimeMode: "workspace_full_runtime",
|
|
1069
|
+
surfaceMode: "cli",
|
|
1070
|
+
generatedAt,
|
|
1071
|
+
data: {
|
|
1072
|
+
overall: snap.overall,
|
|
1073
|
+
generatedAt: snap.generatedAt,
|
|
1074
|
+
degraded_dimensions,
|
|
1075
|
+
dimensions: snap.dimensions,
|
|
1076
|
+
},
|
|
1077
|
+
warnings: [],
|
|
1078
|
+
sourceRefs: ["observability/services/self-health-snapshot.ts"],
|
|
1079
|
+
};
|
|
1080
|
+
return envelope;
|
|
1081
|
+
}
|
|
1082
|
+
catch (err) {
|
|
1083
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1084
|
+
const envelope = {
|
|
1085
|
+
ok: false,
|
|
1086
|
+
command: "self_health",
|
|
1087
|
+
runtimeMode: "unavailable",
|
|
1088
|
+
surfaceMode: "cli",
|
|
1089
|
+
generatedAt,
|
|
1090
|
+
error: { code: "SELF_HEALTH_PROBE_FAILED", message: msg },
|
|
1091
|
+
warnings: [],
|
|
1092
|
+
sourceRefs: [],
|
|
1093
|
+
};
|
|
1094
|
+
return envelope;
|
|
1095
|
+
}
|
|
1117
1096
|
}
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1097
|
+
/**
|
|
1098
|
+
* [G3] tool_affordance — body-tool AffordanceMap pass-through.
|
|
1099
|
+
* Port not yet wired in this wave; returns degraded view with clear next-step.
|
|
1100
|
+
*/
|
|
1101
|
+
if (command === "tool_affordance") {
|
|
1102
|
+
const generatedAt = new Date().toISOString();
|
|
1103
|
+
if (deps.toolAffordancePort) {
|
|
1104
|
+
const allStatuses = [
|
|
1105
|
+
"safe",
|
|
1106
|
+
"exploratory",
|
|
1107
|
+
"needs_auth",
|
|
1108
|
+
"painful",
|
|
1109
|
+
"unavailable",
|
|
1110
|
+
];
|
|
1111
|
+
const platformIds = Array.isArray(input?.platformIds)
|
|
1112
|
+
? input.platformIds.filter((item) => typeof item === "string")
|
|
1113
|
+
: typeof input?.platformId === "string"
|
|
1114
|
+
? [input.platformId]
|
|
1115
|
+
: undefined;
|
|
1116
|
+
const data = await deps.toolAffordancePort.assembleAffordanceMap({
|
|
1117
|
+
platformIds,
|
|
1118
|
+
allowedStatuses: allStatuses,
|
|
1119
|
+
goalKind: typeof input?.goalKind === "string" ? input.goalKind : undefined,
|
|
1120
|
+
});
|
|
1121
|
+
const envelope = {
|
|
1122
|
+
ok: true,
|
|
1123
|
+
command: "tool_affordance",
|
|
1124
|
+
runtimeMode: "workspace_full_runtime",
|
|
1125
|
+
surfaceMode: "cli",
|
|
1126
|
+
generatedAt,
|
|
1127
|
+
data,
|
|
1128
|
+
warnings: [],
|
|
1129
|
+
sourceRefs: [
|
|
1130
|
+
"core/second-nature/body/tool-affordance/affordance-assembler.ts",
|
|
1131
|
+
],
|
|
1132
|
+
};
|
|
1133
|
+
return envelope;
|
|
1134
|
+
}
|
|
1133
1135
|
const envelope = {
|
|
1134
1136
|
ok: false,
|
|
1135
|
-
command: "
|
|
1137
|
+
command: "tool_affordance",
|
|
1136
1138
|
runtimeMode: "unavailable",
|
|
1137
1139
|
surfaceMode: "cli",
|
|
1138
1140
|
generatedAt,
|
|
1139
1141
|
error: {
|
|
1140
|
-
code: "
|
|
1141
|
-
message: "
|
|
1142
|
-
nextStep: "
|
|
1142
|
+
code: "TOOL_AFFORDANCE_PORT_UNWIRED",
|
|
1143
|
+
message: "tool_affordance requires body-tool AffordanceMap port (T-BTS.C.1) to be wired into OpsRouterDeps",
|
|
1144
|
+
nextStep: "wire_body_tool_port_into_ops_router_deps",
|
|
1143
1145
|
},
|
|
1144
1146
|
warnings: [],
|
|
1145
1147
|
sourceRefs: [],
|
|
1146
1148
|
};
|
|
1147
1149
|
return envelope;
|
|
1148
1150
|
}
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1151
|
+
/**
|
|
1152
|
+
* [G6] heartbeat_digest — wraps generateHeartbeatDigest.
|
|
1153
|
+
* Requires auditStore in deps; degrades if unavailable.
|
|
1154
|
+
*/
|
|
1155
|
+
if (command === "heartbeat_digest") {
|
|
1156
|
+
const generatedAt = new Date().toISOString();
|
|
1157
|
+
if (!deps.auditStore) {
|
|
1155
1158
|
const envelope = {
|
|
1156
1159
|
ok: false,
|
|
1157
|
-
command: "
|
|
1158
|
-
runtimeMode: "
|
|
1160
|
+
command: "heartbeat_digest",
|
|
1161
|
+
runtimeMode: "unavailable",
|
|
1159
1162
|
surfaceMode: "cli",
|
|
1160
1163
|
generatedAt,
|
|
1161
1164
|
error: {
|
|
1162
|
-
code: "
|
|
1163
|
-
message:
|
|
1164
|
-
nextStep: "
|
|
1165
|
+
code: "AUDIT_STORE_UNAVAILABLE",
|
|
1166
|
+
message: "heartbeat_digest requires auditStore in OpsRouterDeps",
|
|
1167
|
+
nextStep: "wire_audit_store_into_ops_router",
|
|
1165
1168
|
},
|
|
1166
1169
|
warnings: [],
|
|
1167
1170
|
sourceRefs: [],
|
|
1168
1171
|
};
|
|
1169
1172
|
return envelope;
|
|
1170
1173
|
}
|
|
1171
|
-
|
|
1172
|
-
|
|
1174
|
+
const date = typeof input?.date === "string" && input.date
|
|
1175
|
+
? input.date
|
|
1176
|
+
: new Date().toISOString().slice(0, 10);
|
|
1177
|
+
try {
|
|
1178
|
+
const digestDeps = {
|
|
1179
|
+
auditStore: deps.auditStore,
|
|
1180
|
+
...deps.heartbeatDigestDeps,
|
|
1181
|
+
};
|
|
1182
|
+
const digest = await generateHeartbeatDigest(date, digestDeps);
|
|
1183
|
+
// T-OBS.R.3: Embed real-run health into digest when state DB is available
|
|
1184
|
+
if (deps.state) {
|
|
1185
|
+
const realRunResult = await checkRealRunHealth(deps.state, date);
|
|
1186
|
+
if (realRunResult.ok) {
|
|
1187
|
+
digest.realRunHealth = {
|
|
1188
|
+
gatePassed: realRunResult.gate.gatePassed,
|
|
1189
|
+
contractSmokeOnly: realRunResult.gate.contractSmokeOnly,
|
|
1190
|
+
seededStateDetected: realRunResult.gate.seededStateDetected,
|
|
1191
|
+
hasRealClosure: realRunResult.gate.hasRealClosure,
|
|
1192
|
+
hasQuietArtifact: realRunResult.gate.hasQuietArtifact,
|
|
1193
|
+
hasDreamArtifact: realRunResult.gate.hasDreamArtifact,
|
|
1194
|
+
hasFreshImpulseContext: realRunResult.gate.hasFreshImpulseContext,
|
|
1195
|
+
hasProjectionFeedback: realRunResult.gate.hasProjectionFeedback,
|
|
1196
|
+
missingStage: realRunResult.gate.missingStage,
|
|
1197
|
+
missingReason: realRunResult.gate.missingReason,
|
|
1198
|
+
};
|
|
1199
|
+
}
|
|
1200
|
+
else {
|
|
1201
|
+
digest.realRunHealth = {
|
|
1202
|
+
gatePassed: false,
|
|
1203
|
+
contractSmokeOnly: false,
|
|
1204
|
+
seededStateDetected: false,
|
|
1205
|
+
hasRealClosure: false,
|
|
1206
|
+
hasQuietArtifact: false,
|
|
1207
|
+
hasDreamArtifact: false,
|
|
1208
|
+
hasFreshImpulseContext: false,
|
|
1209
|
+
hasProjectionFeedback: false,
|
|
1210
|
+
missingReason: "Real-run health check degraded: " + realRunResult.degraded.reason,
|
|
1211
|
+
};
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
const envelope = {
|
|
1215
|
+
ok: true,
|
|
1216
|
+
command: "heartbeat_digest",
|
|
1217
|
+
runtimeMode: "workspace_full_runtime",
|
|
1218
|
+
surfaceMode: "cli",
|
|
1219
|
+
generatedAt,
|
|
1220
|
+
data: digest,
|
|
1221
|
+
warnings: [],
|
|
1222
|
+
sourceRefs: ["observability/services/heartbeat-digest-assembler.ts"],
|
|
1223
|
+
};
|
|
1224
|
+
return envelope;
|
|
1225
|
+
}
|
|
1226
|
+
catch (err) {
|
|
1227
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1228
|
+
const envelope = {
|
|
1229
|
+
ok: false,
|
|
1230
|
+
command: "heartbeat_digest",
|
|
1231
|
+
runtimeMode: "unavailable",
|
|
1232
|
+
surfaceMode: "cli",
|
|
1233
|
+
generatedAt,
|
|
1234
|
+
error: { code: "DIGEST_GENERATION_FAILED", message: msg },
|
|
1235
|
+
warnings: [],
|
|
1236
|
+
sourceRefs: [],
|
|
1237
|
+
};
|
|
1238
|
+
return envelope;
|
|
1239
|
+
}
|
|
1173
1240
|
}
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
surfaceMode: "cli",
|
|
1181
|
-
generatedAt,
|
|
1182
|
-
data: diff,
|
|
1183
|
-
warnings: [],
|
|
1184
|
-
sourceRefs: ["observability/services/narrative-timeline-query-service.ts"],
|
|
1185
|
-
};
|
|
1186
|
-
return envelope;
|
|
1241
|
+
/**
|
|
1242
|
+
* [G6] snapshot:capture — production capture path for RestoreSnapshot +
|
|
1243
|
+
* NarrativeTimeline. This gives restore and narrative:diff real state to consume.
|
|
1244
|
+
*/
|
|
1245
|
+
if (command === "snapshot:capture") {
|
|
1246
|
+
return captureRuntimeSnapshot(deps, input);
|
|
1187
1247
|
}
|
|
1188
|
-
|
|
1189
|
-
|
|
1248
|
+
/**
|
|
1249
|
+
* [G6] narrative:diff — queryNarrativeDiff between two versions.
|
|
1250
|
+
* Requires narrativeTimelineDeps in OpsRouterDeps.
|
|
1251
|
+
*/
|
|
1252
|
+
if (command === "narrative:diff") {
|
|
1253
|
+
const generatedAt = new Date().toISOString();
|
|
1254
|
+
if (!deps.narrativeTimelineDeps) {
|
|
1190
1255
|
const envelope = {
|
|
1191
1256
|
ok: false,
|
|
1192
1257
|
command: "narrative:diff",
|
|
1193
|
-
runtimeMode: "
|
|
1258
|
+
runtimeMode: "unavailable",
|
|
1194
1259
|
surfaceMode: "cli",
|
|
1195
1260
|
generatedAt,
|
|
1196
1261
|
error: {
|
|
1197
|
-
code: "
|
|
1198
|
-
message:
|
|
1199
|
-
nextStep: "
|
|
1262
|
+
code: "NARRATIVE_TIMELINE_PORT_UNAVAILABLE",
|
|
1263
|
+
message: "narrative:diff requires narrativeTimelineDeps in OpsRouterDeps",
|
|
1264
|
+
nextStep: "wire_narrative_timeline_deps_into_ops_router",
|
|
1200
1265
|
},
|
|
1201
1266
|
warnings: [],
|
|
1202
1267
|
sourceRefs: [],
|
|
1203
1268
|
};
|
|
1204
1269
|
return envelope;
|
|
1205
1270
|
}
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
warnings: [],
|
|
1277
|
-
sourceRefs: [],
|
|
1278
|
-
};
|
|
1279
|
-
return envelope;
|
|
1280
|
-
}
|
|
1281
|
-
}
|
|
1282
|
-
/**
|
|
1283
|
-
* [G6] restore — bounded state restoration via RestoreSnapshotStore + audit (T-ROS.C.1, T-OBS.C.6).
|
|
1284
|
-
* When restoreSnapshotStore is wired, attempts to apply the snapshot payload back to state.
|
|
1285
|
-
* Always writes RestoreAudit. Never restores credential fields.
|
|
1286
|
-
*/
|
|
1287
|
-
if (command === "restore") {
|
|
1288
|
-
const generatedAt = new Date().toISOString();
|
|
1289
|
-
if (!deps.auditStore) {
|
|
1290
|
-
const envelope = {
|
|
1291
|
-
ok: false,
|
|
1292
|
-
command: "restore",
|
|
1293
|
-
runtimeMode: "unavailable",
|
|
1294
|
-
surfaceMode: "cli",
|
|
1295
|
-
generatedAt,
|
|
1296
|
-
error: {
|
|
1297
|
-
code: "AUDIT_STORE_UNAVAILABLE",
|
|
1298
|
-
message: "restore requires auditStore in OpsRouterDeps",
|
|
1299
|
-
nextStep: "wire_audit_store_into_ops_router",
|
|
1300
|
-
},
|
|
1301
|
-
warnings: [],
|
|
1302
|
-
sourceRefs: [],
|
|
1303
|
-
};
|
|
1304
|
-
return envelope;
|
|
1271
|
+
let fromVersion = typeof input?.from === "string" ? input.from : "";
|
|
1272
|
+
let toVersion = typeof input?.to === "string" ? input.to : "";
|
|
1273
|
+
if (!fromVersion || !toVersion) {
|
|
1274
|
+
// Auto-resolve the two most recent narrative timeline versions when not provided.
|
|
1275
|
+
const recent = await deps.narrativeTimelineDeps.stateMemoryPort.listNarrativeTimeline(new Date(0).toISOString(), new Date().toISOString(), { limit: 2 });
|
|
1276
|
+
if (recent.length < 2) {
|
|
1277
|
+
const envelope = {
|
|
1278
|
+
ok: false,
|
|
1279
|
+
command: "narrative:diff",
|
|
1280
|
+
runtimeMode: "workspace_full_runtime",
|
|
1281
|
+
surfaceMode: "cli",
|
|
1282
|
+
generatedAt,
|
|
1283
|
+
error: {
|
|
1284
|
+
code: "NARRATIVE_DIFF_REQUIRES_TWO_VERSIONS",
|
|
1285
|
+
message: `narrative:diff requires at least two timeline versions; found ${recent.length}. Pass explicit 'from' and 'to', or run snapshot:capture twice.`,
|
|
1286
|
+
nextStep: "run_snapshot_capture_twice_or_pass_from_and_to",
|
|
1287
|
+
},
|
|
1288
|
+
warnings: [],
|
|
1289
|
+
sourceRefs: [],
|
|
1290
|
+
};
|
|
1291
|
+
return envelope;
|
|
1292
|
+
}
|
|
1293
|
+
fromVersion = recent[1].version;
|
|
1294
|
+
toVersion = recent[0].version;
|
|
1295
|
+
}
|
|
1296
|
+
try {
|
|
1297
|
+
const diff = await queryNarrativeDiff(fromVersion, toVersion, deps.narrativeTimelineDeps);
|
|
1298
|
+
const envelope = {
|
|
1299
|
+
ok: true,
|
|
1300
|
+
command: "narrative:diff",
|
|
1301
|
+
runtimeMode: "workspace_full_runtime",
|
|
1302
|
+
surfaceMode: "cli",
|
|
1303
|
+
generatedAt,
|
|
1304
|
+
data: diff,
|
|
1305
|
+
warnings: [],
|
|
1306
|
+
sourceRefs: ["observability/services/narrative-timeline-query-service.ts"],
|
|
1307
|
+
};
|
|
1308
|
+
return envelope;
|
|
1309
|
+
}
|
|
1310
|
+
catch (err) {
|
|
1311
|
+
if (err instanceof NarrativeVersionNotFoundError) {
|
|
1312
|
+
const envelope = {
|
|
1313
|
+
ok: false,
|
|
1314
|
+
command: "narrative:diff",
|
|
1315
|
+
runtimeMode: "workspace_full_runtime",
|
|
1316
|
+
surfaceMode: "cli",
|
|
1317
|
+
generatedAt,
|
|
1318
|
+
error: {
|
|
1319
|
+
code: "NARRATIVE_VERSION_NOT_FOUND",
|
|
1320
|
+
message: err.message,
|
|
1321
|
+
nextStep: "verify_version_exists_in_timeline",
|
|
1322
|
+
},
|
|
1323
|
+
warnings: [],
|
|
1324
|
+
sourceRefs: [],
|
|
1325
|
+
};
|
|
1326
|
+
return envelope;
|
|
1327
|
+
}
|
|
1328
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1329
|
+
const envelope = {
|
|
1330
|
+
ok: false,
|
|
1331
|
+
command: "narrative:diff",
|
|
1332
|
+
runtimeMode: "unavailable",
|
|
1333
|
+
surfaceMode: "cli",
|
|
1334
|
+
generatedAt,
|
|
1335
|
+
error: { code: "NARRATIVE_DIFF_FAILED", message: msg },
|
|
1336
|
+
warnings: [],
|
|
1337
|
+
sourceRefs: [],
|
|
1338
|
+
};
|
|
1339
|
+
return envelope;
|
|
1340
|
+
}
|
|
1305
1341
|
}
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
if (snapshotId) {
|
|
1314
|
-
if (!deps.restoreSnapshotStore) {
|
|
1342
|
+
/**
|
|
1343
|
+
* [G6] timeline — queryNarrativeTimeline with cursor pagination.
|
|
1344
|
+
* Requires narrativeTimelineDeps in OpsRouterDeps.
|
|
1345
|
+
*/
|
|
1346
|
+
if (command === "timeline") {
|
|
1347
|
+
const generatedAt = new Date().toISOString();
|
|
1348
|
+
if (!deps.narrativeTimelineDeps) {
|
|
1315
1349
|
const envelope = {
|
|
1316
1350
|
ok: false,
|
|
1317
|
-
command: "
|
|
1351
|
+
command: "timeline",
|
|
1318
1352
|
runtimeMode: "unavailable",
|
|
1319
1353
|
surfaceMode: "cli",
|
|
1320
1354
|
generatedAt,
|
|
1321
1355
|
error: {
|
|
1322
|
-
code: "
|
|
1323
|
-
message: "
|
|
1324
|
-
nextStep: "
|
|
1356
|
+
code: "NARRATIVE_TIMELINE_PORT_UNAVAILABLE",
|
|
1357
|
+
message: "timeline requires narrativeTimelineDeps in OpsRouterDeps",
|
|
1358
|
+
nextStep: "wire_narrative_timeline_deps_into_ops_router",
|
|
1325
1359
|
},
|
|
1326
1360
|
warnings: [],
|
|
1327
1361
|
sourceRefs: [],
|
|
1328
1362
|
};
|
|
1329
1363
|
return envelope;
|
|
1330
1364
|
}
|
|
1331
|
-
const
|
|
1332
|
-
const
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1365
|
+
const now = new Date();
|
|
1366
|
+
const to = typeof input?.to === "string" ? input.to : now.toISOString();
|
|
1367
|
+
const from = typeof input?.from === "string"
|
|
1368
|
+
? input.from
|
|
1369
|
+
: new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000).toISOString();
|
|
1370
|
+
const limit = typeof input?.limit === "number" ? input.limit : 20;
|
|
1371
|
+
const cursor = typeof input?.cursor === "string" ? input.cursor : undefined;
|
|
1372
|
+
try {
|
|
1373
|
+
const page = await queryNarrativeTimeline(from, to, { limit, cursor }, deps.narrativeTimelineDeps);
|
|
1374
|
+
const envelope = {
|
|
1375
|
+
ok: true,
|
|
1376
|
+
command: "timeline",
|
|
1377
|
+
runtimeMode: "workspace_full_runtime",
|
|
1378
|
+
surfaceMode: "cli",
|
|
1379
|
+
generatedAt,
|
|
1380
|
+
data: page,
|
|
1381
|
+
warnings: [],
|
|
1382
|
+
sourceRefs: ["observability/services/narrative-timeline-query-service.ts"],
|
|
1383
|
+
};
|
|
1384
|
+
return envelope;
|
|
1337
1385
|
}
|
|
1338
|
-
|
|
1386
|
+
catch (err) {
|
|
1387
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1388
|
+
const code = err.name === "NarrativeQueryRangeError"
|
|
1389
|
+
? "NARRATIVE_RANGE_EXCEEDED"
|
|
1390
|
+
: "TIMELINE_QUERY_FAILED";
|
|
1339
1391
|
const envelope = {
|
|
1340
1392
|
ok: false,
|
|
1341
|
-
command: "
|
|
1342
|
-
runtimeMode: "
|
|
1393
|
+
command: "timeline",
|
|
1394
|
+
runtimeMode: "unavailable",
|
|
1343
1395
|
surfaceMode: "cli",
|
|
1344
1396
|
generatedAt,
|
|
1345
|
-
error: {
|
|
1346
|
-
code: "SNAPSHOT_NOT_FOUND",
|
|
1347
|
-
message: `snapshotId ${snapshotId} not found in restore_snapshot table`,
|
|
1348
|
-
nextStep: "list_available_snapshots_or_verify_snapshotId",
|
|
1349
|
-
},
|
|
1397
|
+
error: { code, message: msg },
|
|
1350
1398
|
warnings: [],
|
|
1351
1399
|
sourceRefs: [],
|
|
1352
1400
|
};
|
|
1353
1401
|
return envelope;
|
|
1354
1402
|
}
|
|
1355
1403
|
}
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
if (missingFields.length > 0) {
|
|
1404
|
+
/**
|
|
1405
|
+
* [G6] restore — bounded state restoration via RestoreSnapshotStore + audit (T-ROS.C.1, T-OBS.C.6).
|
|
1406
|
+
* When restoreSnapshotStore is wired, attempts to apply the snapshot payload back to state.
|
|
1407
|
+
* Always writes RestoreAudit. Never restores credential fields.
|
|
1408
|
+
*/
|
|
1409
|
+
if (command === "restore") {
|
|
1410
|
+
const generatedAt = new Date().toISOString();
|
|
1411
|
+
if (!deps.auditStore) {
|
|
1365
1412
|
const envelope = {
|
|
1366
1413
|
ok: false,
|
|
1367
1414
|
command: "restore",
|
|
1368
|
-
runtimeMode: "
|
|
1415
|
+
runtimeMode: "unavailable",
|
|
1369
1416
|
surfaceMode: "cli",
|
|
1370
1417
|
generatedAt,
|
|
1371
1418
|
error: {
|
|
1372
|
-
code: "
|
|
1373
|
-
message:
|
|
1374
|
-
nextStep: "
|
|
1419
|
+
code: "AUDIT_STORE_UNAVAILABLE",
|
|
1420
|
+
message: "restore requires auditStore in OpsRouterDeps",
|
|
1421
|
+
nextStep: "wire_audit_store_into_ops_router",
|
|
1375
1422
|
},
|
|
1376
1423
|
warnings: [],
|
|
1377
1424
|
sourceRefs: [],
|
|
1378
1425
|
};
|
|
1379
1426
|
return envelope;
|
|
1380
1427
|
}
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1428
|
+
let restoreTarget;
|
|
1429
|
+
let fromVersion;
|
|
1430
|
+
let toVersion;
|
|
1431
|
+
// T-V7C.C.5: snapshotId operator-friendly parameter takes precedence over legacy fields.
|
|
1432
|
+
// When snapshotId is provided, resolve restoreTarget/fromVersion/toVersion from the
|
|
1433
|
+
// matching snapshot row; otherwise fall back to explicit legacy parameters.
|
|
1434
|
+
const snapshotId = textInput(input, "snapshotId");
|
|
1435
|
+
if (snapshotId) {
|
|
1436
|
+
if (!deps.restoreSnapshotStore) {
|
|
1437
|
+
const envelope = {
|
|
1438
|
+
ok: false,
|
|
1439
|
+
command: "restore",
|
|
1440
|
+
runtimeMode: "unavailable",
|
|
1441
|
+
surfaceMode: "cli",
|
|
1442
|
+
generatedAt,
|
|
1443
|
+
error: {
|
|
1444
|
+
code: "RESTORE_SNAPSHOT_STORE_UNAVAILABLE",
|
|
1445
|
+
message: "snapshotId restore requires restoreSnapshotStore in OpsRouterDeps",
|
|
1446
|
+
nextStep: "wire_restore_snapshot_store_into_ops_router",
|
|
1447
|
+
},
|
|
1448
|
+
warnings: [],
|
|
1449
|
+
sourceRefs: [],
|
|
1450
|
+
};
|
|
1451
|
+
return envelope;
|
|
1452
|
+
}
|
|
1453
|
+
const snapshots = await deps.restoreSnapshotStore.listSnapshots();
|
|
1454
|
+
const match = snapshots.find((s) => s.snapshotId === snapshotId);
|
|
1455
|
+
if (match) {
|
|
1456
|
+
restoreTarget = snapshotId;
|
|
1457
|
+
fromVersion = match.capturedAt;
|
|
1458
|
+
toVersion = snapshotId;
|
|
1459
|
+
}
|
|
1460
|
+
else {
|
|
1461
|
+
const envelope = {
|
|
1462
|
+
ok: false,
|
|
1463
|
+
command: "restore",
|
|
1464
|
+
runtimeMode: "workspace_full_runtime",
|
|
1465
|
+
surfaceMode: "cli",
|
|
1466
|
+
generatedAt,
|
|
1467
|
+
error: {
|
|
1468
|
+
code: "SNAPSHOT_NOT_FOUND",
|
|
1469
|
+
message: `snapshotId ${snapshotId} not found in restore_snapshot table`,
|
|
1470
|
+
nextStep: "list_available_snapshots_or_verify_snapshotId",
|
|
1471
|
+
},
|
|
1472
|
+
warnings: [],
|
|
1473
|
+
sourceRefs: [],
|
|
1474
|
+
};
|
|
1475
|
+
return envelope;
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
else {
|
|
1479
|
+
const missingFields = [];
|
|
1480
|
+
if (typeof input?.restoreTarget !== "string")
|
|
1481
|
+
missingFields.push("restoreTarget");
|
|
1482
|
+
if (typeof input?.fromVersion !== "string")
|
|
1483
|
+
missingFields.push("fromVersion");
|
|
1484
|
+
if (typeof input?.toVersion !== "string")
|
|
1485
|
+
missingFields.push("toVersion");
|
|
1486
|
+
if (missingFields.length > 0) {
|
|
1487
|
+
const envelope = {
|
|
1488
|
+
ok: false,
|
|
1489
|
+
command: "restore",
|
|
1490
|
+
runtimeMode: "workspace_full_runtime",
|
|
1491
|
+
surfaceMode: "cli",
|
|
1492
|
+
generatedAt,
|
|
1493
|
+
error: {
|
|
1494
|
+
code: "MISSING_RESTORE_FIELDS",
|
|
1495
|
+
message: `restore requires: ${missingFields.join(", ")}`,
|
|
1496
|
+
nextStep: "reinvoke_with_required_fields",
|
|
1497
|
+
},
|
|
1498
|
+
warnings: [],
|
|
1499
|
+
sourceRefs: [],
|
|
1500
|
+
};
|
|
1501
|
+
return envelope;
|
|
1502
|
+
}
|
|
1503
|
+
restoreTarget = input.restoreTarget;
|
|
1504
|
+
fromVersion = input.fromVersion;
|
|
1505
|
+
toVersion = input.toVersion;
|
|
1506
|
+
}
|
|
1507
|
+
// [NEW] Invoke bounded restore via RestoreSnapshotStore when wired
|
|
1508
|
+
let restoreResult = {
|
|
1509
|
+
ok: false,
|
|
1510
|
+
completedEntities: [],
|
|
1511
|
+
failedEntities: [],
|
|
1512
|
+
warnings: ["restore_snapshot_store_unavailable"],
|
|
1513
|
+
};
|
|
1514
|
+
if (deps.restoreSnapshotStore) {
|
|
1515
|
+
restoreResult = await deps.restoreSnapshotStore.applyBoundedRestore({
|
|
1516
|
+
restoreTarget: restoreTarget,
|
|
1517
|
+
fromVersion: fromVersion,
|
|
1518
|
+
toVersion: toVersion,
|
|
1519
|
+
});
|
|
1520
|
+
}
|
|
1521
|
+
const event = {
|
|
1522
|
+
id: `restore-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
1394
1523
|
restoreTarget: restoreTarget,
|
|
1395
1524
|
fromVersion: fromVersion,
|
|
1396
1525
|
toVersion: toVersion,
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
// credentials are always excluded from restore audit
|
|
1409
|
-
excludedFields: Array.isArray(input?.excludedFields)
|
|
1410
|
-
? input.excludedFields.filter((f) => typeof f === "string")
|
|
1411
|
-
: ["credential", "encryptionKey"],
|
|
1412
|
-
restoredFieldCount: restoreResult.completedEntities.length,
|
|
1413
|
-
createdAt: generatedAt,
|
|
1414
|
-
traceId: typeof input?.traceId === "string" ? input.traceId : `trace-restore-${Date.now()}`,
|
|
1415
|
-
};
|
|
1416
|
-
const auditResult = await writeRestoreAudit(event, deps.auditStore);
|
|
1417
|
-
const envelope = {
|
|
1418
|
-
ok: restoreResult.ok && auditResult.ok,
|
|
1419
|
-
command: "restore",
|
|
1420
|
-
runtimeMode: "workspace_full_runtime",
|
|
1421
|
-
surfaceMode: "cli",
|
|
1422
|
-
generatedAt,
|
|
1423
|
-
data: {
|
|
1424
|
-
auditWritten: auditResult.warnings.length === 0,
|
|
1425
|
-
fromVersion: event.fromVersion,
|
|
1426
|
-
toVersion: event.toVersion,
|
|
1427
|
-
restoreTarget: event.restoreTarget,
|
|
1428
|
-
isPartialRestore: event.failedEntities.length > 0,
|
|
1429
|
-
failedEntities: event.failedEntities,
|
|
1430
|
-
completedEntities: event.completedEntities,
|
|
1431
|
-
restoreSnapshotStoreAvailable: !!deps.restoreSnapshotStore,
|
|
1432
|
-
},
|
|
1433
|
-
warnings: [...restoreResult.warnings, ...auditResult.warnings],
|
|
1434
|
-
sourceRefs: [
|
|
1435
|
-
"observability/services/restore-audit-service.ts",
|
|
1436
|
-
"storage/services/restore-snapshot-store.ts",
|
|
1437
|
-
],
|
|
1438
|
-
};
|
|
1439
|
-
return envelope;
|
|
1440
|
-
}
|
|
1441
|
-
/**
|
|
1442
|
-
* [G7] runtime_secret_bootstrap — RuntimeSecretAnchorView pass-through.
|
|
1443
|
-
* Requires secretAnchorDeps in OpsRouterDeps; never returns key plaintext.
|
|
1444
|
-
*/
|
|
1445
|
-
if (command === "runtime_secret_bootstrap") {
|
|
1446
|
-
const generatedAt = new Date().toISOString();
|
|
1447
|
-
if (!deps.secretAnchorDeps) {
|
|
1448
|
-
const envelope = {
|
|
1449
|
-
ok: false,
|
|
1450
|
-
command: "runtime_secret_bootstrap",
|
|
1451
|
-
runtimeMode: "unavailable",
|
|
1452
|
-
surfaceMode: "cli",
|
|
1453
|
-
generatedAt,
|
|
1454
|
-
error: {
|
|
1455
|
-
code: "SECRET_ANCHOR_DEPS_UNAVAILABLE",
|
|
1456
|
-
message: "runtime_secret_bootstrap requires secretAnchorDeps in OpsRouterDeps",
|
|
1457
|
-
nextStep: "wire_secret_anchor_deps_into_ops_router",
|
|
1458
|
-
},
|
|
1459
|
-
warnings: [],
|
|
1460
|
-
sourceRefs: [],
|
|
1461
|
-
};
|
|
1462
|
-
return envelope;
|
|
1463
|
-
}
|
|
1464
|
-
try {
|
|
1465
|
-
const view = await viewSecretAnchor(deps.secretAnchorDeps);
|
|
1466
|
-
// Map to RuntimeSecretBootstrapView (design model §6.1)
|
|
1467
|
-
const data = {
|
|
1468
|
-
status: view.status === "verified" || view.status === "ok"
|
|
1469
|
-
? "ok"
|
|
1470
|
-
: view.status === "missing"
|
|
1471
|
-
? "runtime_secret_anchor_missing"
|
|
1472
|
-
: view.status === "wrong_key"
|
|
1473
|
-
? "credential_recovery_required"
|
|
1474
|
-
: view.status === "decryption_failed"
|
|
1475
|
-
? "runtime_secret_unavailable"
|
|
1476
|
-
: "unknown",
|
|
1477
|
-
keyHealth: view.status === "verified" || view.status === "ok"
|
|
1478
|
-
? "ok"
|
|
1479
|
-
: view.status === "missing"
|
|
1480
|
-
? "missing_key"
|
|
1481
|
-
: view.status === "wrong_key"
|
|
1482
|
-
? "wrong_key"
|
|
1483
|
-
: "unknown",
|
|
1484
|
-
anchorLocation: view.keyPath,
|
|
1485
|
-
recoveryPrincipleRef: view.recoveryDocRef,
|
|
1486
|
-
plaintextKeyExposed: false,
|
|
1487
|
-
reasonCode: view.reasonCode,
|
|
1488
|
-
recoverySteps: view.recoverySteps,
|
|
1526
|
+
triggeredBy: input?.triggeredBy ?? "operator",
|
|
1527
|
+
reason: typeof input?.reason === "string" ? input.reason : "manual_restore",
|
|
1528
|
+
completedEntities: restoreResult.completedEntities,
|
|
1529
|
+
failedEntities: restoreResult.failedEntities,
|
|
1530
|
+
// credentials are always excluded from restore audit
|
|
1531
|
+
excludedFields: Array.isArray(input?.excludedFields)
|
|
1532
|
+
? input.excludedFields.filter((f) => typeof f === "string")
|
|
1533
|
+
: ["credential", "encryptionKey"],
|
|
1534
|
+
restoredFieldCount: restoreResult.completedEntities.length,
|
|
1535
|
+
createdAt: generatedAt,
|
|
1536
|
+
traceId: typeof input?.traceId === "string" ? input.traceId : `trace-restore-${Date.now()}`,
|
|
1489
1537
|
};
|
|
1538
|
+
const auditResult = await writeRestoreAudit(event, deps.auditStore);
|
|
1490
1539
|
const envelope = {
|
|
1491
|
-
ok:
|
|
1492
|
-
command: "
|
|
1540
|
+
ok: restoreResult.ok && auditResult.ok,
|
|
1541
|
+
command: "restore",
|
|
1493
1542
|
runtimeMode: "workspace_full_runtime",
|
|
1494
1543
|
surfaceMode: "cli",
|
|
1495
1544
|
generatedAt,
|
|
1496
|
-
data
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
ok: false,
|
|
1506
|
-
command: "runtime_secret_bootstrap",
|
|
1507
|
-
runtimeMode: "unavailable",
|
|
1508
|
-
surfaceMode: "cli",
|
|
1509
|
-
generatedAt,
|
|
1510
|
-
error: { code: "SECRET_ANCHOR_PROBE_FAILED", message: msg },
|
|
1511
|
-
warnings: [],
|
|
1512
|
-
sourceRefs: [],
|
|
1513
|
-
};
|
|
1514
|
-
return envelope;
|
|
1515
|
-
}
|
|
1516
|
-
}
|
|
1517
|
-
// ─── T-V7C.C.4R + T-GVS.R.1: guidance_payload ─────────────────────────
|
|
1518
|
-
// Returns the assembled impulse + atmosphere for a given scene context.
|
|
1519
|
-
// When state DB is wired, reads persisted artifact first; falls back to
|
|
1520
|
-
// real-time assembly and persists for subsequent reads.
|
|
1521
|
-
if (command === "guidance_payload") {
|
|
1522
|
-
const generatedAt = new Date().toISOString();
|
|
1523
|
-
const sceneType = input?.sceneType ?? "social";
|
|
1524
|
-
const capabilityIntent = typeof input?.capabilityIntent === "string"
|
|
1525
|
-
? input.capabilityIntent
|
|
1526
|
-
: undefined;
|
|
1527
|
-
const platformId = typeof input?.platformId === "string"
|
|
1528
|
-
? input.platformId
|
|
1529
|
-
: undefined;
|
|
1530
|
-
const validSceneTypes = ["social", "reply", "outreach", "quiet", "heartbeat", "explain", "user_reply"];
|
|
1531
|
-
if (!validSceneTypes.includes(sceneType)) {
|
|
1532
|
-
const envelope = {
|
|
1533
|
-
ok: false,
|
|
1534
|
-
command: "guidance_payload",
|
|
1535
|
-
runtimeMode: "unavailable",
|
|
1536
|
-
surfaceMode: "cli",
|
|
1537
|
-
generatedAt,
|
|
1538
|
-
error: {
|
|
1539
|
-
code: "INVALID_SCENE_TYPE",
|
|
1540
|
-
message: `sceneType must be one of: ${validSceneTypes.join(", ")}`,
|
|
1541
|
-
nextStep: "reinvoke_with_valid_scene_type",
|
|
1545
|
+
data: {
|
|
1546
|
+
auditWritten: auditResult.warnings.length === 0,
|
|
1547
|
+
fromVersion: event.fromVersion,
|
|
1548
|
+
toVersion: event.toVersion,
|
|
1549
|
+
restoreTarget: event.restoreTarget,
|
|
1550
|
+
isPartialRestore: event.failedEntities.length > 0,
|
|
1551
|
+
failedEntities: event.failedEntities,
|
|
1552
|
+
completedEntities: event.completedEntities,
|
|
1553
|
+
restoreSnapshotStoreAvailable: !!deps.restoreSnapshotStore,
|
|
1542
1554
|
},
|
|
1543
|
-
warnings: [],
|
|
1544
|
-
sourceRefs: [
|
|
1555
|
+
warnings: [...restoreResult.warnings, ...auditResult.warnings],
|
|
1556
|
+
sourceRefs: [
|
|
1557
|
+
"observability/services/restore-audit-service.ts",
|
|
1558
|
+
"storage/services/restore-snapshot-store.ts",
|
|
1559
|
+
],
|
|
1545
1560
|
};
|
|
1546
1561
|
return envelope;
|
|
1547
1562
|
}
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1563
|
+
/**
|
|
1564
|
+
* [G7] runtime_secret_bootstrap — RuntimeSecretAnchorView pass-through.
|
|
1565
|
+
* Requires secretAnchorDeps in OpsRouterDeps; never returns key plaintext.
|
|
1566
|
+
*/
|
|
1567
|
+
if (command === "runtime_secret_bootstrap") {
|
|
1568
|
+
const generatedAt = new Date().toISOString();
|
|
1569
|
+
if (!deps.secretAnchorDeps) {
|
|
1570
|
+
const envelope = {
|
|
1571
|
+
ok: false,
|
|
1572
|
+
command: "runtime_secret_bootstrap",
|
|
1573
|
+
runtimeMode: "unavailable",
|
|
1574
|
+
surfaceMode: "cli",
|
|
1575
|
+
generatedAt,
|
|
1576
|
+
error: {
|
|
1577
|
+
code: "SECRET_ANCHOR_DEPS_UNAVAILABLE",
|
|
1578
|
+
message: "runtime_secret_bootstrap requires secretAnchorDeps in OpsRouterDeps",
|
|
1579
|
+
nextStep: "wire_secret_anchor_deps_into_ops_router",
|
|
1580
|
+
},
|
|
1581
|
+
warnings: [],
|
|
1582
|
+
sourceRefs: [],
|
|
1583
|
+
};
|
|
1584
|
+
return envelope;
|
|
1585
|
+
}
|
|
1558
1586
|
try {
|
|
1559
|
-
const
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1587
|
+
const view = await viewSecretAnchor(deps.secretAnchorDeps);
|
|
1588
|
+
// Map to RuntimeSecretBootstrapView (design model §6.1)
|
|
1589
|
+
const data = {
|
|
1590
|
+
status: view.status === "verified" || view.status === "ok"
|
|
1591
|
+
? "ok"
|
|
1592
|
+
: view.status === "missing"
|
|
1593
|
+
? "runtime_secret_anchor_missing"
|
|
1594
|
+
: view.status === "wrong_key"
|
|
1595
|
+
? "credential_recovery_required"
|
|
1596
|
+
: view.status === "decryption_failed"
|
|
1597
|
+
? "runtime_secret_unavailable"
|
|
1598
|
+
: "unknown",
|
|
1599
|
+
keyHealth: view.status === "verified" || view.status === "ok"
|
|
1600
|
+
? "ok"
|
|
1601
|
+
: view.status === "missing"
|
|
1602
|
+
? "missing_key"
|
|
1603
|
+
: view.status === "wrong_key"
|
|
1604
|
+
? "wrong_key"
|
|
1605
|
+
: "unknown",
|
|
1606
|
+
anchorLocation: view.keyPath,
|
|
1607
|
+
recoveryPrincipleRef: view.recoveryDocRef,
|
|
1608
|
+
plaintextKeyExposed: false,
|
|
1609
|
+
reasonCode: view.reasonCode,
|
|
1610
|
+
recoverySteps: view.recoverySteps,
|
|
1611
|
+
};
|
|
1612
|
+
const envelope = {
|
|
1613
|
+
ok: true,
|
|
1614
|
+
command: "runtime_secret_bootstrap",
|
|
1615
|
+
runtimeMode: "workspace_full_runtime",
|
|
1616
|
+
surfaceMode: "cli",
|
|
1617
|
+
generatedAt,
|
|
1618
|
+
data,
|
|
1619
|
+
warnings: [],
|
|
1620
|
+
sourceRefs: ["observability/services/runtime-secret-anchor-view.ts"],
|
|
1621
|
+
};
|
|
1622
|
+
return envelope;
|
|
1577
1623
|
}
|
|
1578
|
-
catch {
|
|
1579
|
-
|
|
1624
|
+
catch (err) {
|
|
1625
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1626
|
+
const envelope = {
|
|
1627
|
+
ok: false,
|
|
1628
|
+
command: "runtime_secret_bootstrap",
|
|
1629
|
+
runtimeMode: "unavailable",
|
|
1630
|
+
surfaceMode: "cli",
|
|
1631
|
+
generatedAt,
|
|
1632
|
+
error: { code: "SECRET_ANCHOR_PROBE_FAILED", message: msg },
|
|
1633
|
+
warnings: [],
|
|
1634
|
+
sourceRefs: [],
|
|
1635
|
+
};
|
|
1636
|
+
return envelope;
|
|
1580
1637
|
}
|
|
1581
1638
|
}
|
|
1582
|
-
//
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
const
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
const
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1639
|
+
// ─── T-V7C.C.4R + T-GVS.R.1: guidance_payload ─────────────────────────
|
|
1640
|
+
// Returns the assembled impulse + atmosphere for a given scene context.
|
|
1641
|
+
// When state DB is wired, reads persisted artifact first; falls back to
|
|
1642
|
+
// real-time assembly and persists for subsequent reads.
|
|
1643
|
+
if (command === "guidance_payload") {
|
|
1644
|
+
const generatedAt = new Date().toISOString();
|
|
1645
|
+
const sceneType = input?.sceneType ?? "social";
|
|
1646
|
+
const capabilityIntent = typeof input?.capabilityIntent === "string"
|
|
1647
|
+
? input.capabilityIntent
|
|
1648
|
+
: undefined;
|
|
1649
|
+
const platformId = typeof input?.platformId === "string"
|
|
1650
|
+
? input.platformId
|
|
1651
|
+
: undefined;
|
|
1652
|
+
const validSceneTypes = ["social", "reply", "outreach", "quiet", "heartbeat", "explain", "user_reply"];
|
|
1653
|
+
if (!validSceneTypes.includes(sceneType)) {
|
|
1654
|
+
const envelope = {
|
|
1655
|
+
ok: false,
|
|
1656
|
+
command: "guidance_payload",
|
|
1657
|
+
runtimeMode: "unavailable",
|
|
1658
|
+
surfaceMode: "cli",
|
|
1659
|
+
generatedAt,
|
|
1660
|
+
error: {
|
|
1661
|
+
code: "INVALID_SCENE_TYPE",
|
|
1662
|
+
message: `sceneType must be one of: ${validSceneTypes.join(", ")}`,
|
|
1663
|
+
nextStep: "reinvoke_with_valid_scene_type",
|
|
1664
|
+
},
|
|
1665
|
+
warnings: [],
|
|
1666
|
+
sourceRefs: [],
|
|
1667
|
+
};
|
|
1668
|
+
return envelope;
|
|
1610
1669
|
}
|
|
1611
|
-
// T-GVS.R.1:
|
|
1670
|
+
// T-GVS.R.1: Try reading persisted artifact first
|
|
1671
|
+
let artifactData;
|
|
1672
|
+
let warnings = [];
|
|
1673
|
+
let sourceRefs = [
|
|
1674
|
+
"guidance/capability-class.ts",
|
|
1675
|
+
"guidance/impulse-assembler.ts",
|
|
1676
|
+
"guidance/template-registry.ts",
|
|
1677
|
+
"guidance/output-guard.ts",
|
|
1678
|
+
];
|
|
1612
1679
|
if (deps.state) {
|
|
1613
1680
|
try {
|
|
1614
|
-
const {
|
|
1615
|
-
await
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1681
|
+
const { readImpulseContext } = await import("../../core/second-nature/guidance/impulse-context-reader.js");
|
|
1682
|
+
const existing = await readImpulseContext(deps.state, sceneType, capabilityIntent, platformId);
|
|
1683
|
+
if (existing.available) {
|
|
1684
|
+
artifactData = {
|
|
1685
|
+
sceneType: existing.artifact.sceneType,
|
|
1686
|
+
capabilityIntent: existing.artifact.capabilityIntent,
|
|
1687
|
+
platformId: existing.artifact.platformId,
|
|
1688
|
+
capabilityClass: existing.artifact.capabilityClass,
|
|
1689
|
+
impulseSource: existing.artifact.impulseSource,
|
|
1690
|
+
impulseText: existing.artifact.impulseText,
|
|
1691
|
+
atmosphereText: existing.artifact.atmosphereText,
|
|
1692
|
+
expressionBoundaryConstraints: existing.artifact.expressionBoundaryConstraints,
|
|
1693
|
+
expressionBoundaryStyle: existing.artifact.expressionBoundaryStyle,
|
|
1694
|
+
freshnessMs: existing.freshnessMs,
|
|
1695
|
+
persisted: true,
|
|
1696
|
+
};
|
|
1697
|
+
sourceRefs.push("core/second-nature/guidance/impulse-context-reader.ts");
|
|
1698
|
+
}
|
|
1625
1699
|
}
|
|
1626
1700
|
catch {
|
|
1627
|
-
//
|
|
1628
|
-
|
|
1701
|
+
// Reader failure → fall through to assembly
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
// Real-time assembly if no persisted artifact
|
|
1705
|
+
if (!artifactData) {
|
|
1706
|
+
const { assembleImpulseSync } = await import("../../guidance/impulse-assembler.js");
|
|
1707
|
+
const { buildExpressionBoundary } = await import("../../guidance/output-guard.js");
|
|
1708
|
+
const { getShortAtmosphereTemplate } = await import("../../guidance/template-registry.js");
|
|
1709
|
+
const impulseResult = assembleImpulseSync({
|
|
1710
|
+
sceneType: sceneType,
|
|
1711
|
+
capabilityIntent,
|
|
1712
|
+
platformId,
|
|
1713
|
+
});
|
|
1714
|
+
const atmosphere = getShortAtmosphereTemplate("active", "low");
|
|
1715
|
+
const expressionBoundary = buildExpressionBoundary(sceneType);
|
|
1716
|
+
artifactData = {
|
|
1717
|
+
sceneType,
|
|
1718
|
+
capabilityIntent: capabilityIntent ?? null,
|
|
1719
|
+
platformId: platformId ?? null,
|
|
1720
|
+
capabilityClass: impulseResult.capabilityClass,
|
|
1721
|
+
impulseSource: impulseResult.source,
|
|
1722
|
+
impulseText: impulseResult.impulse?.text ?? null,
|
|
1723
|
+
impulseReviewStatus: impulseResult.impulse?.reviewStatus ?? null,
|
|
1724
|
+
atmosphereText: atmosphere.text,
|
|
1725
|
+
atmosphereReviewStatus: atmosphere.reviewStatus,
|
|
1726
|
+
expressionBoundaryConstraints: expressionBoundary.constraints,
|
|
1727
|
+
expressionBoundaryStyle: expressionBoundary.style,
|
|
1728
|
+
persisted: false,
|
|
1729
|
+
};
|
|
1730
|
+
if (impulseResult.source === "none") {
|
|
1731
|
+
warnings.push("no_impulse_available_for_this_scene_and_capability");
|
|
1732
|
+
}
|
|
1733
|
+
// T-GVS.R.1: Persist assembled artifact for future reads
|
|
1734
|
+
if (deps.state) {
|
|
1735
|
+
try {
|
|
1736
|
+
const { writeImpulseContext } = await import("../../core/second-nature/guidance/impulse-context-writer.js");
|
|
1737
|
+
await writeImpulseContext(deps.state, {
|
|
1738
|
+
sceneType,
|
|
1739
|
+
capabilityIntent,
|
|
1740
|
+
platformId,
|
|
1741
|
+
impulseResult,
|
|
1742
|
+
atmosphereText: atmosphere.text,
|
|
1743
|
+
expressionBoundaryConstraints: expressionBoundary.constraints,
|
|
1744
|
+
expressionBoundaryStyle: expressionBoundary.style,
|
|
1745
|
+
}, { now: generatedAt });
|
|
1746
|
+
sourceRefs.push("core/second-nature/guidance/impulse-context-writer.ts");
|
|
1747
|
+
}
|
|
1748
|
+
catch {
|
|
1749
|
+
// Persistence failure is non-fatal; surface still returns assembled payload
|
|
1750
|
+
warnings.push("impulse_context_persistence_failed");
|
|
1751
|
+
}
|
|
1629
1752
|
}
|
|
1630
1753
|
}
|
|
1754
|
+
const envelope = {
|
|
1755
|
+
ok: true,
|
|
1756
|
+
command: "guidance_payload",
|
|
1757
|
+
runtimeMode: deps.runtimeAvailable ? "workspace_full_runtime" : "host_safe_carrier",
|
|
1758
|
+
surfaceMode: "cli",
|
|
1759
|
+
generatedAt,
|
|
1760
|
+
data: artifactData,
|
|
1761
|
+
warnings,
|
|
1762
|
+
sourceRefs,
|
|
1763
|
+
};
|
|
1764
|
+
return envelope;
|
|
1631
1765
|
}
|
|
1632
|
-
|
|
1633
|
-
ok:
|
|
1634
|
-
command
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
warnings,
|
|
1640
|
-
sourceRefs,
|
|
1766
|
+
return {
|
|
1767
|
+
ok: false,
|
|
1768
|
+
command,
|
|
1769
|
+
error: {
|
|
1770
|
+
code: "unknown_ops_command",
|
|
1771
|
+
message: `Unknown ops command: ${command}`,
|
|
1772
|
+
},
|
|
1641
1773
|
};
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
code: "unknown_ops_command",
|
|
1648
|
-
message: `Unknown ops command: ${command}`,
|
|
1649
|
-
},
|
|
1650
|
-
};
|
|
1774
|
+
})();
|
|
1775
|
+
const envelopeRuntimeMode = typeof input?.runtimeAvailable === "boolean"
|
|
1776
|
+
? (input.runtimeAvailable ? "workspace_full_runtime" : "host_safe_carrier")
|
|
1777
|
+
: (deps.runtimeAvailable ? "workspace_full_runtime" : "host_safe_carrier");
|
|
1778
|
+
return normalizeEnvelopeResult(rawResult, command, envelopeRuntimeMode);
|
|
1651
1779
|
},
|
|
1652
1780
|
};
|
|
1653
1781
|
}
|