@haaaiawd/second-nature 0.2.9 → 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 +102 -6
- package/openclaw.plugin.json +2 -5
- 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/host-capability/probe-host-capability.js +1 -1
- package/runtime/cli/host-capability/types.d.ts +2 -7
- package/runtime/cli/host-capability/types.js +0 -5
- package/runtime/cli/ops/heartbeat-surface.d.ts +5 -3
- package/runtime/cli/ops/heartbeat-surface.js +38 -8
- package/runtime/cli/ops/ops-router.d.ts +6 -2
- package/runtime/cli/ops/ops-router.js +1275 -1147
- package/runtime/cli/ops/workspace-heartbeat-runner.js +2 -5
- 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/connectors/manifest/manifest-schema.d.ts +2 -2
- package/runtime/connectors/registry/dynamic-connector-registry.js +16 -1
- package/runtime/connectors/services/connector-executor-adapter.js +54 -35
- package/runtime/core/second-nature/action/action-closure-recorder.d.ts +4 -0
- package/runtime/core/second-nature/action/action-closure-recorder.js +51 -38
- package/runtime/core/second-nature/action/action-proposal-builder.js +8 -34
- package/runtime/core/second-nature/action/policy-bound-dispatch.d.ts +2 -0
- package/runtime/core/second-nature/action/policy-bound-dispatch.js +10 -5
- package/runtime/core/second-nature/control-plane/accepted-projection-loader.js +1 -11
- 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.d.ts +1 -0
- package/runtime/core/second-nature/control-plane/heartbeat-orchestrator.js +23 -15
- package/runtime/core/second-nature/control-plane/real-runtime-spine.d.ts +2 -0
- package/runtime/core/second-nature/control-plane/real-runtime-spine.js +2 -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/heartbeat/heartbeat-loop.js +4 -3
- package/runtime/core/second-nature/heartbeat/runtime-snapshot.d.ts +3 -2
- package/runtime/core/second-nature/heartbeat/snapshot-builder.d.ts +3 -2
- package/runtime/core/second-nature/orchestrator/intent-planner.js +4 -2
- package/runtime/core/second-nature/orchestrator/narrative-update.js +1 -2
- package/runtime/core/second-nature/orchestrator/platform-capability-router.d.ts +2 -2
- package/runtime/core/second-nature/orchestrator/platform-capability-router.js +1 -1
- package/runtime/core/second-nature/outreach/build-outreach-draft-request.js +2 -3
- package/runtime/core/second-nature/outreach/dispatch-user-outreach.d.ts +2 -2
- package/runtime/core/second-nature/outreach/dispatch-user-outreach.js +6 -1
- package/runtime/core/second-nature/outreach/judge-input-from-snapshot.js +3 -14
- package/runtime/core/second-nature/outreach/judge-outreach.d.ts +6 -5
- package/runtime/core/second-nature/perception/judgment-engine.js +10 -16
- package/runtime/core/second-nature/perception/perception-builder.js +15 -11
- package/runtime/core/second-nature/quiet/run-source-backed-quiet.js +13 -15
- package/runtime/core/second-nature/quiet-dream/daily-rhythm-scheduler.js +40 -16
- 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 -3
- package/runtime/core/second-nature/quiet-dream/memory-projection-lifecycle.js +2 -13
- 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 +34 -11
- package/runtime/core/second-nature/types.d.ts +2 -9
- package/runtime/dream/dream-engine.js +11 -4
- package/runtime/guidance/outreach-draft-schema.d.ts +12 -12
- package/runtime/guidance/persona-selection.js +5 -0
- package/runtime/guidance/template-registry.js +6 -1
- package/runtime/guidance/types.d.ts +2 -2
- package/runtime/observability/causal-loop-health.d.ts +2 -1
- package/runtime/observability/causal-loop-health.js +7 -0
- package/runtime/observability/living-loop-health-gate.js +2 -8
- package/runtime/observability/loop-stage-event-sink.js +6 -2
- 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/observability/services/lived-experience-audit.d.ts +7 -7
- 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/serialization.d.ts +17 -0
- package/runtime/shared/serialization.js +27 -0
- package/runtime/shared/setup-ack.d.ts +54 -0
- package/runtime/shared/setup-ack.js +108 -0
- package/runtime/shared/source-ref-compat.d.ts +26 -0
- package/runtime/shared/source-ref-compat.js +64 -0
- package/runtime/shared/types/goal.d.ts +4 -4
- package/runtime/shared/types/goal.js +1 -1
- package/runtime/shared/types/index.d.ts +1 -0
- package/runtime/shared/types/index.js +1 -3
- package/runtime/shared/types/source-ref.d.ts +2 -2
- package/runtime/shared/types/v7-entities.d.ts +5 -5
- package/runtime/shared/types/v7-entities.js +1 -1
- package/runtime/shared/types/v8-contracts.d.ts +13 -2
- package/runtime/storage/db/index.js +60 -12
- package/runtime/storage/db/migrations/index.js +4 -0
- package/runtime/storage/db/migrations/v8-004-schema-closure.d.ts +19 -0
- package/runtime/storage/db/migrations/v8-004-schema-closure.js +74 -0
- package/runtime/storage/db/migrations/v8-005-single-status-schema.d.ts +11 -0
- package/runtime/storage/db/migrations/v8-005-single-status-schema.js +16 -0
- 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 +65 -84
- package/runtime/storage/db/schema/v8-entities.js +8 -7
- package/runtime/storage/delivery/types.d.ts +2 -2
- package/runtime/storage/fallback/load-operator-fallback.d.ts +2 -2
- package/runtime/storage/fallback/operator-fallback-types.d.ts +2 -2
- package/runtime/storage/fallback/operator-fallback-view.d.ts +2 -2
- package/runtime/storage/index.d.ts +1 -1
- package/runtime/storage/life-evidence/types.d.ts +5 -5
- package/runtime/storage/quiet/quiet-artifact-types.d.ts +4 -4
- package/runtime/storage/quiet/quiet-artifact-writer.d.ts +2 -2
- package/runtime/storage/services/write-validation-gate.d.ts +1 -1
- package/runtime/storage/services/write-validation-gate.js +15 -3
- package/runtime/storage/snapshots/types.d.ts +8 -8
- package/runtime/storage/user-interest/types.d.ts +3 -3
- package/runtime/storage/v8-state-stores.d.ts +15 -3
- package/runtime/storage/v8-state-stores.js +60 -39
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
*
|
|
21
21
|
* Test coverage: tests/unit/action/policy-bound-dispatch.test.ts
|
|
22
22
|
*/
|
|
23
|
+
import { serializeSourceRefs } from "../../../shared/serialization.js";
|
|
24
|
+
import { classifyDegradedStatus } from "../../../shared/degraded-status-classifier.js";
|
|
23
25
|
// ───────────────────────────────────────────────────────────────
|
|
24
26
|
// Helpers
|
|
25
27
|
// ───────────────────────────────────────────────────────────────
|
|
@@ -60,7 +62,8 @@ export function dispatchAllowedAction(proposal, decision, options) {
|
|
|
60
62
|
actionKind: target,
|
|
61
63
|
draftType,
|
|
62
64
|
policyProof: { decisionId: decision.id, decision: decision.decision },
|
|
63
|
-
sourceRefs:
|
|
65
|
+
sourceRefs: serializeSourceRefs(proposal.sourceRefs),
|
|
66
|
+
proofRefs: serializeSourceRefs(decision.proofRefs),
|
|
64
67
|
},
|
|
65
68
|
};
|
|
66
69
|
}
|
|
@@ -75,7 +78,8 @@ export function dispatchAllowedAction(proposal, decision, options) {
|
|
|
75
78
|
capabilityId: proposal.targetCapabilityId ?? "run_connector",
|
|
76
79
|
idempotencyKey: proposal.idempotencyKey,
|
|
77
80
|
policyProof: { decisionId: decision.id, decision: decision.decision },
|
|
78
|
-
sourceRefs:
|
|
81
|
+
sourceRefs: serializeSourceRefs(proposal.sourceRefs),
|
|
82
|
+
proofRefs: serializeSourceRefs(decision.proofRefs),
|
|
79
83
|
},
|
|
80
84
|
};
|
|
81
85
|
}
|
|
@@ -91,7 +95,8 @@ export function dispatchAllowedAction(proposal, decision, options) {
|
|
|
91
95
|
actionKind: proposal.actionKind,
|
|
92
96
|
draftType,
|
|
93
97
|
policyProof: { decisionId: decision.id, decision: decision.decision },
|
|
94
|
-
sourceRefs:
|
|
98
|
+
sourceRefs: serializeSourceRefs(proposal.sourceRefs),
|
|
99
|
+
proofRefs: serializeSourceRefs(decision.proofRefs),
|
|
95
100
|
},
|
|
96
101
|
};
|
|
97
102
|
}
|
|
@@ -101,10 +106,10 @@ export function dispatchAllowedAction(proposal, decision, options) {
|
|
|
101
106
|
return {
|
|
102
107
|
type: "degraded",
|
|
103
108
|
degraded: {
|
|
104
|
-
status: "
|
|
109
|
+
status: classifyDegradedStatus("closure_failed"),
|
|
105
110
|
reason: "closure_failed",
|
|
106
111
|
ownerStage: "execution",
|
|
107
|
-
sourceRefs:
|
|
112
|
+
sourceRefs: proposal.sourceRefs,
|
|
108
113
|
operatorNextAction: "Unexpected policy decision shape",
|
|
109
114
|
retryable: false,
|
|
110
115
|
},
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
* Test coverage: tests/unit/control-plane/accepted-projection-loader.test.ts
|
|
21
21
|
*/
|
|
22
22
|
import { readMemoryProjectionsByStatus, } from "../../../storage/v8-state-stores.js";
|
|
23
|
+
import { parseSourceRefs } from "../../../shared/serialization.js";
|
|
23
24
|
// ───────────────────────────────────────────────────────────────
|
|
24
25
|
// Helpers
|
|
25
26
|
// ───────────────────────────────────────────────────────────────
|
|
@@ -33,17 +34,6 @@ function parsePayloadJson(json) {
|
|
|
33
34
|
return {};
|
|
34
35
|
}
|
|
35
36
|
}
|
|
36
|
-
function parseSourceRefs(json) {
|
|
37
|
-
if (!json)
|
|
38
|
-
return [];
|
|
39
|
-
try {
|
|
40
|
-
const parsed = JSON.parse(json);
|
|
41
|
-
return Array.isArray(parsed) ? parsed : [];
|
|
42
|
-
}
|
|
43
|
-
catch {
|
|
44
|
-
return [];
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
37
|
// ───────────────────────────────────────────────────────────────
|
|
48
38
|
// Public API
|
|
49
39
|
// ───────────────────────────────────────────────────────────────
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CycleFinalizer — v8 exactly-one closure invariant (T-AC.R.2, T-AC.R.3)
|
|
3
|
+
*
|
|
4
|
+
* Core logic: provide a single boundary that records exactly one
|
|
5
|
+
* ActionClosureRecord or no-action closure per heartbeat cycle.
|
|
6
|
+
* Enforces idempotency key = cycleId, write order (closure row first),
|
|
7
|
+
* and restart reconcile for orphaned closure/event rows.
|
|
8
|
+
*
|
|
9
|
+
* Design authority:
|
|
10
|
+
* - `.anws/v8/04_SYSTEM_DESIGN/action-closure-policy-system.md §6.1a`
|
|
11
|
+
* - `.anws/v8/04_SYSTEM_DESIGN/action-closure-policy-system.detail.md §3.4`
|
|
12
|
+
* - `.anws/v8/04_SYSTEM_DESIGN/control-plane-system.md §3.4`
|
|
13
|
+
*
|
|
14
|
+
* Dependencies:
|
|
15
|
+
* - `src/core/second-nature/action/action-closure-recorder.js`
|
|
16
|
+
* - `src/storage/v8-state-stores.js`
|
|
17
|
+
*
|
|
18
|
+
* Boundary:
|
|
19
|
+
* - One cycle → one closure row.
|
|
20
|
+
* - Duplicate terminal closure for same cycleId → `unsafe` idempotency conflict.
|
|
21
|
+
* - On partial failure, returns degraded diagnostic; caller records stage event.
|
|
22
|
+
* - Reconcile detects orphaned closure (event missing) or orphaned event (closure missing).
|
|
23
|
+
*
|
|
24
|
+
* Test coverage: tests/unit/control-plane/cycle-finalizer.test.ts
|
|
25
|
+
*/
|
|
26
|
+
import type { StateDatabase } from "../../../storage/db/index.js";
|
|
27
|
+
import type { DegradedOperationResult, V8ReasonCode, SourceRef } from "../../../shared/types/v8-contracts.js";
|
|
28
|
+
export interface CycleFinalizerResult {
|
|
29
|
+
closureRef?: SourceRef;
|
|
30
|
+
noActionReason?: V8ReasonCode;
|
|
31
|
+
degraded?: DegradedOperationResult;
|
|
32
|
+
}
|
|
33
|
+
export type ClosureKind = {
|
|
34
|
+
kind: "no_action";
|
|
35
|
+
reason: V8ReasonCode;
|
|
36
|
+
} | {
|
|
37
|
+
kind: "policy";
|
|
38
|
+
closureStatus: "completed" | "denied" | "deferred" | "downgraded";
|
|
39
|
+
reason: V8ReasonCode;
|
|
40
|
+
proposalId: string;
|
|
41
|
+
decisionId: string;
|
|
42
|
+
platformId?: string;
|
|
43
|
+
capabilityId?: string;
|
|
44
|
+
downgradedActionKind?: string;
|
|
45
|
+
} | {
|
|
46
|
+
kind: "execution";
|
|
47
|
+
closureStatus: "completed" | "failed";
|
|
48
|
+
reason: V8ReasonCode;
|
|
49
|
+
proposalId: string;
|
|
50
|
+
decisionId: string;
|
|
51
|
+
platformId?: string;
|
|
52
|
+
capabilityId?: string;
|
|
53
|
+
executionResultRef?: string;
|
|
54
|
+
outputSummary?: string;
|
|
55
|
+
};
|
|
56
|
+
export declare function finalizeCycle(db: StateDatabase, cycleId: string, closure: ClosureKind, options?: {
|
|
57
|
+
now?: string;
|
|
58
|
+
}): Promise<CycleFinalizerResult>;
|
|
59
|
+
export interface ReconcileResult {
|
|
60
|
+
/** Closure row exists but no closure stage event — event should be replayed. */
|
|
61
|
+
orphanedClosure?: {
|
|
62
|
+
closureId: string;
|
|
63
|
+
cycleId: string;
|
|
64
|
+
};
|
|
65
|
+
/** Stage event exists but no closure row — closure is missing, do not fabricate. */
|
|
66
|
+
orphanedEvent?: {
|
|
67
|
+
cycleId: string;
|
|
68
|
+
stage: string;
|
|
69
|
+
};
|
|
70
|
+
/** No orphan detected — cycle is consistent. */
|
|
71
|
+
consistent: boolean;
|
|
72
|
+
degraded?: DegradedOperationResult;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Reconcile a cycle's closure and stage event rows.
|
|
76
|
+
* Called at cycle start or by `loop_status` to detect partial-failure leftovers.
|
|
77
|
+
*
|
|
78
|
+
* Per design §6.1a:
|
|
79
|
+
* - closure row written, event missing → replay event with traceRefs
|
|
80
|
+
* - event written, closure row missing → report closure_unavailable / unsafe
|
|
81
|
+
*/
|
|
82
|
+
export declare function reconcileCycleClosure(db: StateDatabase, cycleId: string): Promise<ReconcileResult>;
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CycleFinalizer — v8 exactly-one closure invariant (T-AC.R.2, T-AC.R.3)
|
|
3
|
+
*
|
|
4
|
+
* Core logic: provide a single boundary that records exactly one
|
|
5
|
+
* ActionClosureRecord or no-action closure per heartbeat cycle.
|
|
6
|
+
* Enforces idempotency key = cycleId, write order (closure row first),
|
|
7
|
+
* and restart reconcile for orphaned closure/event rows.
|
|
8
|
+
*
|
|
9
|
+
* Design authority:
|
|
10
|
+
* - `.anws/v8/04_SYSTEM_DESIGN/action-closure-policy-system.md §6.1a`
|
|
11
|
+
* - `.anws/v8/04_SYSTEM_DESIGN/action-closure-policy-system.detail.md §3.4`
|
|
12
|
+
* - `.anws/v8/04_SYSTEM_DESIGN/control-plane-system.md §3.4`
|
|
13
|
+
*
|
|
14
|
+
* Dependencies:
|
|
15
|
+
* - `src/core/second-nature/action/action-closure-recorder.js`
|
|
16
|
+
* - `src/storage/v8-state-stores.js`
|
|
17
|
+
*
|
|
18
|
+
* Boundary:
|
|
19
|
+
* - One cycle → one closure row.
|
|
20
|
+
* - Duplicate terminal closure for same cycleId → `unsafe` idempotency conflict.
|
|
21
|
+
* - On partial failure, returns degraded diagnostic; caller records stage event.
|
|
22
|
+
* - Reconcile detects orphaned closure (event missing) or orphaned event (closure missing).
|
|
23
|
+
*
|
|
24
|
+
* Test coverage: tests/unit/control-plane/cycle-finalizer.test.ts
|
|
25
|
+
*/
|
|
26
|
+
import { recordNoActionClosure, recordPolicyOutcomeClosure, recordExecutionClosure, } from "../action/action-closure-recorder.js";
|
|
27
|
+
import { readActionClosuresByCycle, readLoopStageEventsByCycle, } from "../../../storage/v8-state-stores.js";
|
|
28
|
+
import { classifyDegradedStatus } from "../../../shared/degraded-status-classifier.js";
|
|
29
|
+
// Terminal closure statuses — a cycle with any of these is considered finalized.
|
|
30
|
+
const TERMINAL_CLOSURE_STATUSES = new Set([
|
|
31
|
+
"completed",
|
|
32
|
+
"no_action",
|
|
33
|
+
"denied",
|
|
34
|
+
"deferred",
|
|
35
|
+
"downgraded",
|
|
36
|
+
"failed",
|
|
37
|
+
]);
|
|
38
|
+
/**
|
|
39
|
+
* Check whether a cycle already has a terminal closure row.
|
|
40
|
+
* This is the idempotency gate for `finalizeCycle`.
|
|
41
|
+
*/
|
|
42
|
+
async function checkExistingTerminalClosure(db, cycleId) {
|
|
43
|
+
const existing = await readActionClosuresByCycle(db, cycleId);
|
|
44
|
+
if (existing.degraded)
|
|
45
|
+
return { hasTerminal: false };
|
|
46
|
+
const terminal = existing.rows.find((r) => TERMINAL_CLOSURE_STATUSES.has(r.status));
|
|
47
|
+
if (terminal)
|
|
48
|
+
return { hasTerminal: true, existingClosureId: terminal.id };
|
|
49
|
+
return { hasTerminal: false };
|
|
50
|
+
}
|
|
51
|
+
export async function finalizeCycle(db, cycleId, closure, options) {
|
|
52
|
+
const now = options?.now ?? new Date().toISOString();
|
|
53
|
+
// Idempotency gate: check if this cycle already has a terminal closure.
|
|
54
|
+
// recordNoActionClosure has its own idempotency check, but policy/execution do not.
|
|
55
|
+
// We check uniformly here to enforce the exactly-one invariant.
|
|
56
|
+
if (closure.kind !== "no_action") {
|
|
57
|
+
const existing = await checkExistingTerminalClosure(db, cycleId);
|
|
58
|
+
if (existing.hasTerminal) {
|
|
59
|
+
return {
|
|
60
|
+
degraded: {
|
|
61
|
+
status: "unsafe",
|
|
62
|
+
reason: "closure_idempotency_conflict",
|
|
63
|
+
ownerStage: "closure",
|
|
64
|
+
sourceRefs: [],
|
|
65
|
+
operatorNextAction: `Cycle ${cycleId} already has terminal closure ${existing.existingClosureId}; duplicate finalize blocked`,
|
|
66
|
+
retryable: false,
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
switch (closure.kind) {
|
|
72
|
+
case "no_action": {
|
|
73
|
+
const result = await recordNoActionClosure(db, cycleId, closure.reason, { now });
|
|
74
|
+
if ("closureId" in result) {
|
|
75
|
+
return {
|
|
76
|
+
closureRef: {
|
|
77
|
+
uri: `sn://closure/${result.closureId}`,
|
|
78
|
+
family: "action_closure",
|
|
79
|
+
id: result.closureId,
|
|
80
|
+
redactionClass: "none",
|
|
81
|
+
resolveStatus: "resolvable",
|
|
82
|
+
},
|
|
83
|
+
noActionReason: closure.reason,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
noActionReason: closure.reason,
|
|
88
|
+
degraded: result,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
case "policy": {
|
|
92
|
+
const result = await recordPolicyOutcomeClosure(db, cycleId, closure.closureStatus, closure.reason, {
|
|
93
|
+
proposalId: closure.proposalId,
|
|
94
|
+
decisionId: closure.decisionId,
|
|
95
|
+
platformId: closure.platformId,
|
|
96
|
+
capabilityId: closure.capabilityId,
|
|
97
|
+
downgradedActionKind: closure.downgradedActionKind,
|
|
98
|
+
}, { now });
|
|
99
|
+
if ("closureId" in result) {
|
|
100
|
+
return {
|
|
101
|
+
closureRef: {
|
|
102
|
+
uri: `sn://closure/${result.closureId}`,
|
|
103
|
+
family: "action_closure",
|
|
104
|
+
id: result.closureId,
|
|
105
|
+
redactionClass: "none",
|
|
106
|
+
resolveStatus: "resolvable",
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
return { degraded: result };
|
|
111
|
+
}
|
|
112
|
+
case "execution": {
|
|
113
|
+
const result = await recordExecutionClosure(db, cycleId, closure.closureStatus, closure.reason, {
|
|
114
|
+
proposalId: closure.proposalId,
|
|
115
|
+
decisionId: closure.decisionId,
|
|
116
|
+
platformId: closure.platformId,
|
|
117
|
+
capabilityId: closure.capabilityId,
|
|
118
|
+
executionResultRef: closure.executionResultRef,
|
|
119
|
+
outputSummary: closure.outputSummary,
|
|
120
|
+
}, { now });
|
|
121
|
+
if ("closureId" in result) {
|
|
122
|
+
return {
|
|
123
|
+
closureRef: {
|
|
124
|
+
uri: `sn://closure/${result.closureId}`,
|
|
125
|
+
family: "action_closure",
|
|
126
|
+
id: result.closureId,
|
|
127
|
+
redactionClass: "none",
|
|
128
|
+
resolveStatus: "resolvable",
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
return { degraded: result };
|
|
133
|
+
}
|
|
134
|
+
default: {
|
|
135
|
+
const exhaustive = closure;
|
|
136
|
+
return {
|
|
137
|
+
degraded: {
|
|
138
|
+
status: classifyDegradedStatus("closure_failed"),
|
|
139
|
+
reason: "closure_failed",
|
|
140
|
+
ownerStage: "closure",
|
|
141
|
+
sourceRefs: [],
|
|
142
|
+
operatorNextAction: `Unknown closure kind: ${JSON.stringify(exhaustive)}`,
|
|
143
|
+
retryable: false,
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Reconcile a cycle's closure and stage event rows.
|
|
151
|
+
* Called at cycle start or by `loop_status` to detect partial-failure leftovers.
|
|
152
|
+
*
|
|
153
|
+
* Per design §6.1a:
|
|
154
|
+
* - closure row written, event missing → replay event with traceRefs
|
|
155
|
+
* - event written, closure row missing → report closure_unavailable / unsafe
|
|
156
|
+
*/
|
|
157
|
+
export async function reconcileCycleClosure(db, cycleId) {
|
|
158
|
+
const closures = await readActionClosuresByCycle(db, cycleId);
|
|
159
|
+
if (closures.degraded) {
|
|
160
|
+
return {
|
|
161
|
+
consistent: false,
|
|
162
|
+
degraded: closures.degraded,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
const events = await readLoopStageEventsByCycle(db, cycleId);
|
|
166
|
+
if (events.degraded) {
|
|
167
|
+
return {
|
|
168
|
+
consistent: false,
|
|
169
|
+
degraded: events.degraded,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
const terminalClosure = closures.rows.find((r) => TERMINAL_CLOSURE_STATUSES.has(r.status));
|
|
173
|
+
const closureEvent = events.rows.find((r) => r.stage === "closure" || r.stage === "action_closure");
|
|
174
|
+
if (terminalClosure && !closureEvent) {
|
|
175
|
+
return {
|
|
176
|
+
orphanedClosure: { closureId: terminalClosure.id, cycleId },
|
|
177
|
+
consistent: false,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
if (!terminalClosure && closureEvent) {
|
|
181
|
+
return {
|
|
182
|
+
orphanedEvent: { cycleId, stage: closureEvent.stage },
|
|
183
|
+
consistent: false,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
return { consistent: true };
|
|
187
|
+
}
|
|
@@ -35,6 +35,7 @@ export interface HeartbeatOrchestrationResult {
|
|
|
35
35
|
closureRef?: SourceRef;
|
|
36
36
|
noActionReason?: V8ReasonCode;
|
|
37
37
|
degraded?: DegradedOperationResult;
|
|
38
|
+
rhythmDegraded?: DegradedOperationResult;
|
|
38
39
|
rhythmState?: DailyRhythmState;
|
|
39
40
|
}
|
|
40
41
|
export declare function runHeartbeatCycle(db: StateDatabase, request: HeartbeatOrchestrationRequest): Promise<HeartbeatOrchestrationResult | DegradedOperationResult>;
|
|
@@ -31,6 +31,7 @@ import { evaluateActionPolicy } from "../action/autonomy-policy-evaluator.js";
|
|
|
31
31
|
import { dispatchAllowedAction } from "../action/policy-bound-dispatch.js";
|
|
32
32
|
import { recordNoActionClosure, recordRememberClosure, recordPolicyOutcomeClosure, recordExecutionClosure, } from "../action/action-closure-recorder.js";
|
|
33
33
|
import { checkDailyRhythm } from "../quiet-dream/daily-rhythm-scheduler.js";
|
|
34
|
+
import { classifyDegradedStatus } from "../../../shared/degraded-status-classifier.js";
|
|
34
35
|
// ───────────────────────────────────────────────────────────────
|
|
35
36
|
// Helpers
|
|
36
37
|
// ───────────────────────────────────────────────────────────────
|
|
@@ -84,7 +85,7 @@ async function advanceAndRecordDailyRhythm(db, cycleId, cycleSequence, cycleRef,
|
|
|
84
85
|
catch (rhythmErr) {
|
|
85
86
|
const errMsg = rhythmErr instanceof Error ? rhythmErr.message : String(rhythmErr);
|
|
86
87
|
const degraded = {
|
|
87
|
-
status: "
|
|
88
|
+
status: classifyDegradedStatus("state_unreadable"),
|
|
88
89
|
reason: "state_unreadable",
|
|
89
90
|
ownerStage: "quiet",
|
|
90
91
|
sourceRefs: [cycleRef],
|
|
@@ -130,7 +131,7 @@ export async function runHeartbeatCycle(db, request) {
|
|
|
130
131
|
});
|
|
131
132
|
if ("reason" in traceResult) {
|
|
132
133
|
return {
|
|
133
|
-
status: "
|
|
134
|
+
status: classifyDegradedStatus("state_unreadable"),
|
|
134
135
|
reason: "state_unreadable",
|
|
135
136
|
ownerStage: "ingestion",
|
|
136
137
|
sourceRefs: [cycleRef],
|
|
@@ -150,7 +151,7 @@ export async function runHeartbeatCycle(db, request) {
|
|
|
150
151
|
});
|
|
151
152
|
// ── Perception stage ──
|
|
152
153
|
const perceptionResult = await buildPerceptionCards(db, { cycleId, now });
|
|
153
|
-
const perceptionDegraded = "
|
|
154
|
+
const perceptionDegraded = "ownerStage" in perceptionResult
|
|
154
155
|
? perceptionResult
|
|
155
156
|
: null;
|
|
156
157
|
await recordLoopStageEvent(db, {
|
|
@@ -191,7 +192,7 @@ export async function runHeartbeatCycle(db, request) {
|
|
|
191
192
|
reason: degradedReason,
|
|
192
193
|
sourceRefs: degradedClosureRef ? [degradedClosureRef, cycleRef] : [cycleRef],
|
|
193
194
|
});
|
|
194
|
-
const { rhythmState } = await advanceAndRecordDailyRhythm(db, cycleId, cycleSequence, cycleRef, now);
|
|
195
|
+
const { rhythmState, rhythmDegraded } = await advanceAndRecordDailyRhythm(db, cycleId, cycleSequence, cycleRef, now);
|
|
195
196
|
return {
|
|
196
197
|
cycleId,
|
|
197
198
|
cycleSequence,
|
|
@@ -199,14 +200,15 @@ export async function runHeartbeatCycle(db, request) {
|
|
|
199
200
|
noActionReason: degradedReason,
|
|
200
201
|
degraded: perceptionDegraded
|
|
201
202
|
? {
|
|
202
|
-
status:
|
|
203
|
-
reason:
|
|
203
|
+
status: classifyDegradedStatus(degradedReason),
|
|
204
|
+
reason: degradedReason,
|
|
204
205
|
ownerStage: "perception",
|
|
205
206
|
sourceRefs: [cycleRef],
|
|
206
207
|
operatorNextAction: "Retry heartbeat after perception recovery",
|
|
207
208
|
retryable: true,
|
|
208
209
|
}
|
|
209
|
-
:
|
|
210
|
+
: rhythmDegraded,
|
|
211
|
+
rhythmDegraded,
|
|
210
212
|
rhythmState,
|
|
211
213
|
};
|
|
212
214
|
}
|
|
@@ -245,12 +247,14 @@ export async function runHeartbeatCycle(db, request) {
|
|
|
245
247
|
reason: "evidence_batch_empty",
|
|
246
248
|
sourceRefs: emptyClosureRef ? [emptyClosureRef, cycleRef] : [cycleRef],
|
|
247
249
|
});
|
|
248
|
-
const { rhythmState } = await advanceAndRecordDailyRhythm(db, cycleId, cycleSequence, cycleRef, now);
|
|
250
|
+
const { rhythmState, rhythmDegraded } = await advanceAndRecordDailyRhythm(db, cycleId, cycleSequence, cycleRef, now);
|
|
249
251
|
return {
|
|
250
252
|
cycleId,
|
|
251
253
|
cycleSequence,
|
|
252
254
|
closureRef: emptyClosureRef,
|
|
253
255
|
noActionReason: "evidence_batch_empty",
|
|
256
|
+
degraded: rhythmDegraded,
|
|
257
|
+
rhythmDegraded,
|
|
254
258
|
rhythmState,
|
|
255
259
|
};
|
|
256
260
|
}
|
|
@@ -324,7 +328,7 @@ export async function runHeartbeatCycle(db, request) {
|
|
|
324
328
|
else {
|
|
325
329
|
// Build proposal for the actionable verdict
|
|
326
330
|
const proposalResult = await buildActionProposal(db, actionableVerdict.id, { now });
|
|
327
|
-
if ("
|
|
331
|
+
if ("operatorNextAction" in proposalResult) {
|
|
328
332
|
// Proposal build failed — still need a closure
|
|
329
333
|
closureDegraded = proposalResult;
|
|
330
334
|
const closureResult = await recordNoActionClosure(db, cycleId, closureDegraded.reason, { now });
|
|
@@ -365,7 +369,7 @@ export async function runHeartbeatCycle(db, request) {
|
|
|
365
369
|
}
|
|
366
370
|
else if (proposalResult.status === "remember_for_review") {
|
|
367
371
|
const remember = proposalResult;
|
|
368
|
-
const closureResult = await recordRememberClosure(db, cycleId, remember.memoryReviewCandidate, { now });
|
|
372
|
+
const closureResult = await recordRememberClosure(db, cycleId, remember.memoryReviewCandidate, { now, platformId: "heartbeat" });
|
|
369
373
|
if ("closureId" in closureResult) {
|
|
370
374
|
closureRef = {
|
|
371
375
|
uri: `sn://closure/${closureResult.closureId}`,
|
|
@@ -395,7 +399,8 @@ export async function runHeartbeatCycle(db, request) {
|
|
|
395
399
|
status: "completed",
|
|
396
400
|
occurredAt: new Date().toISOString(),
|
|
397
401
|
reason: decision.decisionReason,
|
|
398
|
-
sourceRefs:
|
|
402
|
+
sourceRefs: proposal.sourceRefs,
|
|
403
|
+
proofRefs: decision.proofRefs,
|
|
399
404
|
});
|
|
400
405
|
// Record execution stage started
|
|
401
406
|
await recordLoopStageEvent(db, {
|
|
@@ -405,7 +410,8 @@ export async function runHeartbeatCycle(db, request) {
|
|
|
405
410
|
stage: "execution",
|
|
406
411
|
status: "started",
|
|
407
412
|
occurredAt: new Date().toISOString(),
|
|
408
|
-
sourceRefs:
|
|
413
|
+
sourceRefs: proposal.sourceRefs,
|
|
414
|
+
proofRefs: decision.proofRefs,
|
|
409
415
|
});
|
|
410
416
|
// Dispatch — no real external write in T-CP.R.2
|
|
411
417
|
const dispatchResult = dispatchAllowedAction(proposal, decision, { guidanceAvailable: false });
|
|
@@ -509,7 +515,8 @@ export async function runHeartbeatCycle(db, request) {
|
|
|
509
515
|
status: closureDegraded ? "failed" : "completed",
|
|
510
516
|
occurredAt: new Date().toISOString(),
|
|
511
517
|
reason: closureDegraded?.reason,
|
|
512
|
-
sourceRefs:
|
|
518
|
+
sourceRefs: proposal.sourceRefs,
|
|
519
|
+
proofRefs: decision.proofRefs,
|
|
513
520
|
});
|
|
514
521
|
}
|
|
515
522
|
}
|
|
@@ -540,13 +547,14 @@ export async function runHeartbeatCycle(db, request) {
|
|
|
540
547
|
noActionReason = "proposal_no_action";
|
|
541
548
|
}
|
|
542
549
|
// T-CP.R.3: Advance daily rhythm after closure/no-action
|
|
543
|
-
const { rhythmState } = await advanceAndRecordDailyRhythm(db, cycleId, cycleSequence, cycleRef, now);
|
|
550
|
+
const { rhythmState, rhythmDegraded } = await advanceAndRecordDailyRhythm(db, cycleId, cycleSequence, cycleRef, now);
|
|
544
551
|
return {
|
|
545
552
|
cycleId,
|
|
546
553
|
cycleSequence,
|
|
547
554
|
closureRef,
|
|
548
555
|
noActionReason,
|
|
549
|
-
degraded: closureDegraded,
|
|
556
|
+
degraded: closureDegraded ?? rhythmDegraded,
|
|
557
|
+
rhythmDegraded,
|
|
550
558
|
rhythmState,
|
|
551
559
|
};
|
|
552
560
|
}
|
|
@@ -31,5 +31,7 @@ export interface RealRuntimeSpineResult {
|
|
|
31
31
|
noActionReason?: V8ReasonCode;
|
|
32
32
|
degraded?: DegradedOperationResult;
|
|
33
33
|
rhythmState?: DailyRhythmState;
|
|
34
|
+
rhythmDegraded?: DegradedOperationResult;
|
|
35
|
+
impulseContextArtifactId?: string;
|
|
34
36
|
}
|
|
35
37
|
export declare function runRealRuntimeHeartbeatCycle(options: RealRuntimeSpineOptions): Promise<RealRuntimeSpineResult | DegradedOperationResult>;
|
|
@@ -27,7 +27,7 @@ export async function runRealRuntimeHeartbeatCycle(options) {
|
|
|
27
27
|
};
|
|
28
28
|
const result = await runHeartbeatCycle(options.state, request);
|
|
29
29
|
// Pass through degraded results directly
|
|
30
|
-
if ("status" in result
|
|
30
|
+
if ("status" in result) {
|
|
31
31
|
return result;
|
|
32
32
|
}
|
|
33
33
|
const orchestrationResult = result;
|
|
@@ -38,5 +38,6 @@ export async function runRealRuntimeHeartbeatCycle(options) {
|
|
|
38
38
|
noActionReason: orchestrationResult.noActionReason,
|
|
39
39
|
degraded: orchestrationResult.degraded,
|
|
40
40
|
rhythmState: orchestrationResult.rhythmState,
|
|
41
|
+
rhythmDegraded: orchestrationResult.rhythmDegraded,
|
|
41
42
|
};
|
|
42
43
|
}
|
|
@@ -29,10 +29,11 @@ export interface GuidanceOutput {
|
|
|
29
29
|
mode: "draft" | "notify";
|
|
30
30
|
textRef: SourceRef;
|
|
31
31
|
sourceRefs: SourceRef[];
|
|
32
|
+
proofRefs: SourceRef[];
|
|
32
33
|
deliveryClaim: "not_delivered";
|
|
33
34
|
decisionId: string;
|
|
34
35
|
actionKind: PlatformNeutralActionKind;
|
|
35
|
-
ownerVisible:
|
|
36
|
+
ownerVisible: true;
|
|
36
37
|
}
|
|
37
38
|
export type GuidanceValidationResult = {
|
|
38
39
|
ok: true;
|
|
@@ -68,7 +68,8 @@ function buildGuidanceOutput(proposal, decision) {
|
|
|
68
68
|
id: `guidance_${decision.id}_${Date.now()}`,
|
|
69
69
|
mode,
|
|
70
70
|
textRef,
|
|
71
|
-
sourceRefs:
|
|
71
|
+
sourceRefs: proposal.sourceRefs,
|
|
72
|
+
proofRefs: decision.proofRefs,
|
|
72
73
|
deliveryClaim: "not_delivered",
|
|
73
74
|
decisionId: decision.id,
|
|
74
75
|
actionKind,
|
|
@@ -87,7 +88,8 @@ export function consumeGuidanceProposal(proposal, decision) {
|
|
|
87
88
|
status: "blocked",
|
|
88
89
|
reason: "policy_denied_high_risk",
|
|
89
90
|
ownerStage: "execution",
|
|
90
|
-
sourceRefs:
|
|
91
|
+
sourceRefs: proposal.sourceRefs,
|
|
92
|
+
proofRefs: decision.proofRefs,
|
|
91
93
|
operatorNextAction: "Review policy decision before requesting guidance",
|
|
92
94
|
retryable: false,
|
|
93
95
|
},
|
|
@@ -12,6 +12,7 @@ import { mapLifeEvidence } from "../../../connectors/base/map-life-evidence.js";
|
|
|
12
12
|
import { appendLifeEvidence } from "../../../storage/life-evidence/append-life-evidence.js";
|
|
13
13
|
import { normalizeConnectorEvidence } from "../../../connectors/evidence-normalizer.js";
|
|
14
14
|
import { recordConnectorAttemptAudit } from "../../../observability/services/audit-closure-recorders.js";
|
|
15
|
+
import { makeCanonicalSourceRef } from "../../../shared/source-ref-compat.js";
|
|
15
16
|
/**
|
|
16
17
|
* Resolves the heartbeat outcome for a guard-allowed intent (outreach dispatch, quiet orchestration, or default).
|
|
17
18
|
* Exported for unit tests (CR-M1 wiring).
|
|
@@ -302,11 +303,11 @@ export async function ingestRhythmSignal(signal, deps) {
|
|
|
302
303
|
effectClass: "connector_action",
|
|
303
304
|
capabilityIntent: idleResult.candidate.capabilityId,
|
|
304
305
|
sourceRefs: [
|
|
305
|
-
{
|
|
306
|
+
makeCanonicalSourceRef({
|
|
306
307
|
id: "idle_curiosity",
|
|
307
|
-
|
|
308
|
+
family: "audit",
|
|
308
309
|
uri: `idle://${idleResult.candidate.platformId}`,
|
|
309
|
-
},
|
|
310
|
+
}),
|
|
310
311
|
],
|
|
311
312
|
idempotencyKey: `idle:${idleResult.candidate.platformId}:${idleResult.candidate.capabilityId}`,
|
|
312
313
|
goalInfluenceRefs: [],
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* HeartbeatRuntimeSnapshot assembly for candidate planner + hard guards (T2.1.3, T2.2.1).
|
|
3
3
|
*/
|
|
4
|
-
import type { ContinuitySnapshot
|
|
4
|
+
import type { ContinuitySnapshot } from "../types.js";
|
|
5
|
+
import type { SourceRef } from "../../../shared/types/v8-contracts.js";
|
|
5
6
|
import type { RhythmPolicy } from "../rhythm/rhythm-policy.js";
|
|
6
7
|
import { type PlannerRhythmWindowSlice } from "../rhythm/planner-rhythm-window.js";
|
|
7
8
|
import type { SnapshotInputs } from "./snapshot-builder.js";
|
|
8
9
|
import type { AffordanceMap } from "../../../shared/types/v7-entities.js";
|
|
9
10
|
export interface PlannerLifeEvidenceSlice {
|
|
10
|
-
evidenceRefs:
|
|
11
|
+
evidenceRefs: SourceRef[];
|
|
11
12
|
platformEventCount: number;
|
|
12
13
|
workEventCount: number;
|
|
13
14
|
emptyReason?: "no_sources" | "state_unavailable" | "redacted_only";
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Per design doc §4.2: SnapshotBuilder prepares inputs for the Rhythm Engine.
|
|
8
8
|
*/
|
|
9
|
-
import type { ContinuitySnapshot,
|
|
9
|
+
import type { ContinuitySnapshot, TopLevelMode } from "../types.js";
|
|
10
|
+
import type { SourceRef } from "../../../shared/types/v8-contracts.js";
|
|
10
11
|
import type { RhythmPolicy } from "../rhythm/rhythm-policy.js";
|
|
11
12
|
import type { DeliveryCapabilitySnapshot } from "../outreach/delivery-target.js";
|
|
12
13
|
import type { UserInterestSnapshot } from "../../../storage/user-interest/types.js";
|
|
@@ -30,7 +31,7 @@ export interface SnapshotInputs {
|
|
|
30
31
|
awaitingUserInput?: boolean;
|
|
31
32
|
riskSuppressed?: boolean;
|
|
32
33
|
/** Evidence refs for source-backed planner/guards (T2.1.3 / T2.2.1). */
|
|
33
|
-
lifeEvidenceRefs?:
|
|
34
|
+
lifeEvidenceRefs?: SourceRef[];
|
|
34
35
|
platformEventCount?: number;
|
|
35
36
|
workEventCount?: number;
|
|
36
37
|
lifeEvidenceEmptyReason?: "no_sources" | "state_unavailable" | "redacted_only";
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { makeCanonicalSourceRef } from "../../../shared/source-ref-compat.js";
|
|
1
2
|
import { isLifeEvidenceSliceEmpty } from "../heartbeat/runtime-snapshot.js";
|
|
2
3
|
import { buildHeartbeatRuntimeSnapshot } from "../heartbeat/runtime-snapshot.js";
|
|
3
4
|
import { resolvePlatformForIntent, } from "./platform-capability-router.js";
|
|
4
5
|
import { isGoalRelatedToCandidate } from "./goal-priority.js";
|
|
5
6
|
const MAX_CANDIDATE_INTENTS = 6;
|
|
6
7
|
const OBLIGATION_SOURCE = [
|
|
7
|
-
{ id: "obligation-anchor",
|
|
8
|
+
makeCanonicalSourceRef({ id: "obligation-anchor", family: "audit", uri: "workspace://obligations/pending" }),
|
|
8
9
|
];
|
|
9
10
|
function evidenceRefsForConnector(runtime) {
|
|
10
11
|
if (!isLifeEvidenceSliceEmpty(runtime.lifeEvidence) && runtime.lifeEvidence.evidenceRefs.length > 0) {
|
|
@@ -253,8 +254,9 @@ export function planCandidateIntents(runtime, options) {
|
|
|
253
254
|
if (intent.sourceRefs.length === 0 && related.length > 0) {
|
|
254
255
|
intent.sourceRefs = related.slice(0, 4).map((g) => ({
|
|
255
256
|
id: g.goalId,
|
|
256
|
-
|
|
257
|
+
family: "audit",
|
|
257
258
|
uri: `goal://${g.goalId}`,
|
|
259
|
+
redactionClass: "none",
|
|
258
260
|
}));
|
|
259
261
|
}
|
|
260
262
|
}
|