@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
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* - Optional registry: when absent, resolution is best-effort from goals/evidence.
|
|
13
13
|
*/
|
|
14
14
|
import type { IntentKind } from "../types.js";
|
|
15
|
-
import type {
|
|
15
|
+
import type { SourceRef } from "../../../shared/types/v8-contracts.js";
|
|
16
16
|
import type { CapabilityContractRegistry } from "../../../connectors/base/manifest.js";
|
|
17
17
|
/** Minimal goal shape accepted by the router to avoid coupling to AgentGoal. M-03 decoupling. */
|
|
18
18
|
interface GoalRouterContext {
|
|
@@ -24,7 +24,7 @@ export interface PlatformResolutionContext {
|
|
|
24
24
|
/** Accepted goals that may name a platform or capability. */
|
|
25
25
|
acceptedGoals?: GoalRouterContext[];
|
|
26
26
|
/** Evidence refs that may embed platform identity. */
|
|
27
|
-
evidenceRefs?:
|
|
27
|
+
evidenceRefs?: SourceRef[];
|
|
28
28
|
}
|
|
29
29
|
/**
|
|
30
30
|
* Resolve an explicit platformId for a candidate intent kind.
|
|
@@ -48,7 +48,7 @@ function extractPlatformIdsFromGoals(goals, kind, platformIds) {
|
|
|
48
48
|
function extractPlatformIdsFromEvidence(refs, platformIds) {
|
|
49
49
|
const results = new Set();
|
|
50
50
|
for (const ref of refs) {
|
|
51
|
-
if (ref.
|
|
51
|
+
if (ref.family === "connector_result" && ref.id) {
|
|
52
52
|
for (const pid of platformIds) {
|
|
53
53
|
if (ref.id.includes(pid)) {
|
|
54
54
|
results.add(pid);
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Aligns with control-plane-system.detail §3.9 buildOutreachDraftRequest.
|
|
4
4
|
*/
|
|
5
5
|
import * as crypto from "node:crypto";
|
|
6
|
+
import { legacyKindFromSourceRef } from "../../../shared/source-ref-compat.js";
|
|
6
7
|
function inferRhythmWindowKind(windowId) {
|
|
7
8
|
const id = windowId.toLowerCase();
|
|
8
9
|
if (id.includes("work"))
|
|
@@ -20,10 +21,8 @@ function inferRhythmWindowKind(windowId) {
|
|
|
20
21
|
function toGuidanceRefs(refs) {
|
|
21
22
|
return refs.map((r) => ({
|
|
22
23
|
id: r.id,
|
|
23
|
-
kind: r
|
|
24
|
+
kind: legacyKindFromSourceRef(r),
|
|
24
25
|
uri: r.uri,
|
|
25
|
-
excerptHash: r.excerptHash,
|
|
26
|
-
observedAt: r.observedAt,
|
|
27
26
|
}));
|
|
28
27
|
}
|
|
29
28
|
function mapDeliveryVerdict(verdict) {
|
|
@@ -7,7 +7,7 @@ import type { CandidateIntent } from "../types.js";
|
|
|
7
7
|
import type { HeartbeatRuntimeSnapshot } from "../heartbeat/runtime-snapshot.js";
|
|
8
8
|
import type { HeartbeatCycleResult } from "../heartbeat/signal.js";
|
|
9
9
|
import type { StateDatabase } from "../../../storage/db/index.js";
|
|
10
|
-
import type {
|
|
10
|
+
import type { LifeEvidenceSourceRef } from "../../../storage/life-evidence/types.js";
|
|
11
11
|
import { type JudgeOutreachInput } from "./judge-outreach.js";
|
|
12
12
|
import { type DeliveryTargetResolution } from "./delivery-target.js";
|
|
13
13
|
export interface OpenClawDeliverySendResult {
|
|
@@ -16,7 +16,7 @@ export interface OpenClawDeliverySendResult {
|
|
|
16
16
|
errorClass?: string;
|
|
17
17
|
messageId?: string;
|
|
18
18
|
/** Host-reported delivery proof when messageId is absent (T4.3.1). */
|
|
19
|
-
hostProofRef?:
|
|
19
|
+
hostProofRef?: LifeEvidenceSourceRef;
|
|
20
20
|
}
|
|
21
21
|
export interface OpenClawDeliveryPort {
|
|
22
22
|
sendDeliveryRequest(input: {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { legacyKindFromSourceRef } from "../../../shared/source-ref-compat.js";
|
|
1
2
|
import { writeDeliveryAttempt } from "../../../storage/delivery/write-delivery-attempt.js";
|
|
2
3
|
import { writeOperatorFallback } from "../../../storage/fallback/write-operator-fallback.js";
|
|
3
4
|
import { judgeOutreach } from "./judge-outreach.js";
|
|
@@ -6,7 +7,11 @@ import { buildOutreachDraftRequest } from "./build-outreach-draft-request.js";
|
|
|
6
7
|
import { createNarrativeStateStore } from "../../../storage/narrative/narrative-state-store.js";
|
|
7
8
|
import { createRelationshipMemoryStore } from "../../../storage/relationship/relationship-memory-store.js";
|
|
8
9
|
function toSourceRefs(refs) {
|
|
9
|
-
return refs.map((r) => ({
|
|
10
|
+
return refs.map((r) => ({
|
|
11
|
+
id: r.id,
|
|
12
|
+
kind: legacyKindFromSourceRef(r),
|
|
13
|
+
uri: r.uri,
|
|
14
|
+
}));
|
|
10
15
|
}
|
|
11
16
|
function hasDeliveryProof(attempt) {
|
|
12
17
|
return Boolean(attempt.messageId?.trim()) || Boolean(attempt.hostProofRef);
|
|
@@ -1,12 +1,7 @@
|
|
|
1
|
+
import { toCanonicalSourceRef } from "../../../shared/source-ref-compat.js";
|
|
1
2
|
import { isLifeEvidenceSliceEmpty } from "../heartbeat/runtime-snapshot.js";
|
|
2
3
|
function toControlPlaneRefs(refs) {
|
|
3
|
-
return refs.map((r) => (
|
|
4
|
-
id: r.id,
|
|
5
|
-
kind: r.kind,
|
|
6
|
-
uri: r.uri,
|
|
7
|
-
excerptHash: r.excerptHash,
|
|
8
|
-
observedAt: r.observedAt,
|
|
9
|
-
}));
|
|
4
|
+
return refs.map((r) => toCanonicalSourceRef(r));
|
|
10
5
|
}
|
|
11
6
|
export function userInterestSnapshotToJudge(snapshot) {
|
|
12
7
|
if (!snapshot) {
|
|
@@ -18,13 +13,7 @@ export function userInterestSnapshotToJudge(snapshot) {
|
|
|
18
13
|
signals: snapshot.signals.map((s) => ({
|
|
19
14
|
topic: s.topic,
|
|
20
15
|
confidence: s.confidence,
|
|
21
|
-
sourceRefs: s.sourceRefs.map((r) => (
|
|
22
|
-
id: r.id,
|
|
23
|
-
kind: r.kind,
|
|
24
|
-
uri: r.uri,
|
|
25
|
-
excerptHash: r.excerptHash,
|
|
26
|
-
observedAt: r.observedAt,
|
|
27
|
-
})),
|
|
16
|
+
sourceRefs: s.sourceRefs.map((r) => toCanonicalSourceRef(r)),
|
|
28
17
|
})),
|
|
29
18
|
sourceRefs: toControlPlaneRefs(snapshot.sourceRefs),
|
|
30
19
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { CandidateIntent
|
|
1
|
+
import type { CandidateIntent } from "../types.js";
|
|
2
|
+
import type { SourceRef } from "../../../shared/types/v8-contracts.js";
|
|
2
3
|
import { type DeliveryCapabilitySnapshot, type DeliveryTargetResolution } from "./delivery-target.js";
|
|
3
4
|
export type OutreachJudgmentVerdict = "allow" | "deny" | "defer";
|
|
4
5
|
export type CooldownState = "clear" | "cooling_down" | "duplicate";
|
|
@@ -8,9 +9,9 @@ export interface JudgeOutreachUserInterest {
|
|
|
8
9
|
signals: Array<{
|
|
9
10
|
topic: string;
|
|
10
11
|
confidence: number;
|
|
11
|
-
sourceRefs:
|
|
12
|
+
sourceRefs: SourceRef[];
|
|
12
13
|
}>;
|
|
13
|
-
sourceRefs:
|
|
14
|
+
sourceRefs: SourceRef[];
|
|
14
15
|
}
|
|
15
16
|
export interface JudgeOutreachLifeEvidence {
|
|
16
17
|
empty: boolean;
|
|
@@ -31,8 +32,8 @@ export interface OutreachJudgment {
|
|
|
31
32
|
valueScore: number;
|
|
32
33
|
userRelevance: number;
|
|
33
34
|
actionability: number;
|
|
34
|
-
interestRefs:
|
|
35
|
-
sourceRefs:
|
|
35
|
+
interestRefs: SourceRef[];
|
|
36
|
+
sourceRefs: SourceRef[];
|
|
36
37
|
cooldownState: CooldownState;
|
|
37
38
|
deliveryVerdict: DeliveryTargetResolution["verdict"];
|
|
38
39
|
reasons: string[];
|
|
@@ -22,7 +22,9 @@
|
|
|
22
22
|
* Test coverage: tests/unit/judgment/judgment-engine.test.ts
|
|
23
23
|
*/
|
|
24
24
|
import { readPerceptionCardById, writeJudgmentVerdict, } from "../../../storage/v8-state-stores.js";
|
|
25
|
+
import { parseSourceRefs } from "../../../shared/serialization.js";
|
|
25
26
|
import { ACTION_KIND_REGISTRY } from "../../../shared/types/v8-contracts.js";
|
|
27
|
+
import { classifyDegradedStatus } from "../../../shared/degraded-status-classifier.js";
|
|
26
28
|
// ───────────────────────────────────────────────────────────────
|
|
27
29
|
// Config
|
|
28
30
|
// ───────────────────────────────────────────────────────────────
|
|
@@ -42,17 +44,6 @@ function inferRiskPosture(sensitivityClass, riskFlags) {
|
|
|
42
44
|
}
|
|
43
45
|
return "low";
|
|
44
46
|
}
|
|
45
|
-
function parseCardSourceRefs(json) {
|
|
46
|
-
if (!json)
|
|
47
|
-
return [];
|
|
48
|
-
try {
|
|
49
|
-
const parsed = JSON.parse(json);
|
|
50
|
-
return Array.isArray(parsed) ? parsed : [];
|
|
51
|
-
}
|
|
52
|
-
catch {
|
|
53
|
-
return [];
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
47
|
function selectVerdict(relevance, confidence, riskPosture, hasSourceRefs, possibleIntents) {
|
|
57
48
|
// Missing source refs → ignore/watch only
|
|
58
49
|
if (!hasSourceRefs) {
|
|
@@ -117,7 +108,7 @@ export async function runAgentJudgment(db, perceptionCardId, options) {
|
|
|
117
108
|
const card = readResult.row;
|
|
118
109
|
if (!card) {
|
|
119
110
|
return {
|
|
120
|
-
status: "
|
|
111
|
+
status: classifyDegradedStatus("state_unreadable"),
|
|
121
112
|
reason: "state_unreadable",
|
|
122
113
|
ownerStage: "judgment",
|
|
123
114
|
sourceRefs: [],
|
|
@@ -125,7 +116,7 @@ export async function runAgentJudgment(db, perceptionCardId, options) {
|
|
|
125
116
|
retryable: false,
|
|
126
117
|
};
|
|
127
118
|
}
|
|
128
|
-
const sourceRefs =
|
|
119
|
+
const sourceRefs = parseSourceRefs(card.sourceRefsJson);
|
|
129
120
|
const hasSourceRefs = sourceRefs.length > 0;
|
|
130
121
|
// Parse sensitivity class from payload (stored there by perception-builder)
|
|
131
122
|
let sensitivityClass = "public_general";
|
|
@@ -204,9 +195,12 @@ export async function runAgentJudgment(db, perceptionCardId, options) {
|
|
|
204
195
|
});
|
|
205
196
|
if ("reason" in writeResult) {
|
|
206
197
|
return {
|
|
207
|
-
status:
|
|
208
|
-
verdicts: [],
|
|
198
|
+
status: classifyDegradedStatus(writeResult.reason),
|
|
209
199
|
reason: writeResult.reason,
|
|
200
|
+
ownerStage: "judgment",
|
|
201
|
+
sourceRefs: [],
|
|
202
|
+
operatorNextAction: `Failed to persist JudgmentVerdict: ${writeResult.reason}`,
|
|
203
|
+
retryable: true,
|
|
210
204
|
};
|
|
211
205
|
}
|
|
212
206
|
return {
|
|
@@ -228,7 +222,7 @@ export async function runAgentJudgments(db, perceptionCardIds, options) {
|
|
|
228
222
|
failed.push({
|
|
229
223
|
perceptionCardId,
|
|
230
224
|
degraded: {
|
|
231
|
-
status: "
|
|
225
|
+
status: classifyDegradedStatus(result.reason ?? "judgment_low_confidence"),
|
|
232
226
|
reason: result.reason ?? "judgment_low_confidence",
|
|
233
227
|
ownerStage: "judgment",
|
|
234
228
|
sourceRefs: [],
|
|
@@ -22,6 +22,8 @@
|
|
|
22
22
|
* Test coverage: tests/unit/perception/perception-builder.test.ts
|
|
23
23
|
*/
|
|
24
24
|
import { readEvidenceItemsByStatus, writePerceptionCard, updateEvidenceItemLifecycleStatus, } from "../../../storage/v8-state-stores.js";
|
|
25
|
+
import { parseSourceRefs } from "../../../shared/serialization.js";
|
|
26
|
+
import { classifyDegradedStatus } from "../../../shared/degraded-status-classifier.js";
|
|
25
27
|
// ───────────────────────────────────────────────────────────────
|
|
26
28
|
// Config
|
|
27
29
|
// ───────────────────────────────────────────────────────────────
|
|
@@ -29,15 +31,6 @@ const PERCEPTION_MAX_EVIDENCE_PER_CYCLE = 50;
|
|
|
29
31
|
// ───────────────────────────────────────────────────────────────
|
|
30
32
|
// Helpers
|
|
31
33
|
// ───────────────────────────────────────────────────────────────
|
|
32
|
-
function parseSourceRefs(json) {
|
|
33
|
-
try {
|
|
34
|
-
const parsed = JSON.parse(json);
|
|
35
|
-
return Array.isArray(parsed) ? parsed : [];
|
|
36
|
-
}
|
|
37
|
-
catch {
|
|
38
|
-
return [];
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
34
|
function parsePayload(json) {
|
|
42
35
|
if (!json)
|
|
43
36
|
return undefined;
|
|
@@ -118,6 +111,14 @@ function inferRelevanceClass(score) {
|
|
|
118
111
|
}
|
|
119
112
|
function inferSummary(evidence) {
|
|
120
113
|
const payload = parsePayload(evidence.payloadJson);
|
|
114
|
+
if (payload?.contentStatus === "content_missing") {
|
|
115
|
+
return {
|
|
116
|
+
summary: payload.contentMissingReason
|
|
117
|
+
? `Content missing from ${evidence.platformId}: ${payload.contentMissingReason}`
|
|
118
|
+
: `Ref-only observation from ${evidence.platformId}: no readable content`,
|
|
119
|
+
contentMissing: true,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
121
122
|
if (payload?.summary && String(payload.summary).trim().length > 0) {
|
|
122
123
|
return { summary: String(payload.summary), contentMissing: false };
|
|
123
124
|
}
|
|
@@ -254,9 +255,12 @@ export async function buildPerceptionCards(db, options) {
|
|
|
254
255
|
});
|
|
255
256
|
if ("reason" in writeResult) {
|
|
256
257
|
return {
|
|
257
|
-
status:
|
|
258
|
-
cards,
|
|
258
|
+
status: classifyDegradedStatus(writeResult.reason),
|
|
259
259
|
reason: writeResult.reason,
|
|
260
|
+
ownerStage: "perception",
|
|
261
|
+
sourceRefs: card.evidenceRefs,
|
|
262
|
+
operatorNextAction: `Failed to persist PerceptionCard: ${writeResult.reason}`,
|
|
263
|
+
retryable: true,
|
|
260
264
|
};
|
|
261
265
|
}
|
|
262
266
|
await updateEvidenceItemLifecycleStatus(db, evidence.id, "perceived");
|
|
@@ -3,13 +3,21 @@ import { writeQuietArtifact } from "../../../storage/quiet/quiet-artifact-writer
|
|
|
3
3
|
import { persistQuietArtifactToWorkspace } from "../../../storage/quiet/persist-quiet-artifact.js";
|
|
4
4
|
import { buildEvidencePack, buildQuietNarrativeGuidance, selectInterestBasis } from "../../../guidance/evidence-guidance.js";
|
|
5
5
|
import { recordQuietArtifactAudit } from "../../../observability/services/audit-closure-recorders.js";
|
|
6
|
+
import { legacyKindFromSourceRef } from "../../../shared/source-ref-compat.js";
|
|
6
7
|
function toGuidanceRef(r) {
|
|
7
8
|
return {
|
|
8
9
|
id: r.id,
|
|
9
|
-
kind: r
|
|
10
|
+
kind: legacyKindFromSourceRef(r),
|
|
10
11
|
uri: r.uri,
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
function toLifeEvidenceRef(ref) {
|
|
15
|
+
return {
|
|
16
|
+
id: ref.id,
|
|
17
|
+
kind: ref.kind,
|
|
18
|
+
uri: ref.uri,
|
|
19
|
+
excerptHash: ref.excerptHash,
|
|
20
|
+
observedAt: ref.observedAt,
|
|
13
21
|
};
|
|
14
22
|
}
|
|
15
23
|
/**
|
|
@@ -116,24 +124,14 @@ export async function runSourceBackedQuiet(params) {
|
|
|
116
124
|
confidence: userInterestSnapshot?.confidence ?? 0,
|
|
117
125
|
signalCount: userInterestSnapshot?.signals.length ?? 0,
|
|
118
126
|
});
|
|
119
|
-
const groundedSourceRefs = ep.pack.groundedRefs.map(
|
|
120
|
-
id: g.id,
|
|
121
|
-
kind: g.kind,
|
|
122
|
-
uri: g.uri,
|
|
123
|
-
excerptHash: g.excerptHash,
|
|
124
|
-
observedAt: g.observedAt,
|
|
125
|
-
}));
|
|
127
|
+
const groundedSourceRefs = ep.pack.groundedRefs.map(toLifeEvidenceRef);
|
|
126
128
|
const claims = ep.pack.groundedRefs.map((g, i) => ({
|
|
127
129
|
id: `fact:${g.id}`,
|
|
128
130
|
text: `Evidence-backed note ${i + 1}`,
|
|
129
131
|
claimType: "fact",
|
|
130
132
|
sourceRefs: [
|
|
131
133
|
{
|
|
132
|
-
|
|
133
|
-
kind: g.kind,
|
|
134
|
-
uri: g.uri,
|
|
135
|
-
excerptHash: g.excerptHash,
|
|
136
|
-
observedAt: g.observedAt,
|
|
134
|
+
...toLifeEvidenceRef(g),
|
|
137
135
|
},
|
|
138
136
|
],
|
|
139
137
|
}));
|
|
@@ -22,10 +22,11 @@
|
|
|
22
22
|
* so `dreamStatus` reaches completed/blocked.
|
|
23
23
|
* - Does not bypass Dream runner; only records due/completed/blocked.
|
|
24
24
|
*/
|
|
25
|
-
import { writeDailyRhythmState, readDailyRhythmStateByDay, readActionClosuresByDay, readDreamConsolidationRunById, readDreamConsolidationRunsByQuietId, updateDreamConsolidationRunStatus, } from "../../../storage/v8-state-stores.js";
|
|
25
|
+
import { writeDailyRhythmState, readDailyRhythmStateByDay, readActionClosuresByDay, readDreamConsolidationRunById, readDreamConsolidationRunsByQuietId, readLatestDreamConsolidationRunByStatus, updateDreamConsolidationRunStatus, } from "../../../storage/v8-state-stores.js";
|
|
26
26
|
import { buildQuietDailyReview } from "./quiet-daily-review-builder.js";
|
|
27
27
|
import { scheduleDreamAfterQuiet } from "./dream-scheduler.js";
|
|
28
28
|
import { runDreamConsolidation } from "./dream-consolidation-runner.js";
|
|
29
|
+
import { acceptMemoryProjection } from "./memory-projection-lifecycle.js";
|
|
29
30
|
// ───────────────────────────────────────────────────────────────
|
|
30
31
|
// Config
|
|
31
32
|
// ───────────────────────────────────────────────────────────────
|
|
@@ -106,13 +107,12 @@ async function executeStaleScheduledDreams(db, state, now) {
|
|
|
106
107
|
continue;
|
|
107
108
|
if ((run.status === "scheduled" || run.status === "started") && isStaleScheduled(run, now)) {
|
|
108
109
|
const consolidateResult = await runDreamConsolidation(db, runId, { now });
|
|
109
|
-
if ("status" in consolidateResult &&
|
|
110
|
+
if ("status" in consolidateResult && !("ownerStage" in consolidateResult)) {
|
|
110
111
|
const dreamResult = consolidateResult;
|
|
111
112
|
const finalStatus = dreamResult.status;
|
|
112
113
|
const finalReason = dreamResult.reason ?? undefined;
|
|
113
114
|
const updateResult = await updateDreamConsolidationRunStatus(db, runId, finalStatus, {
|
|
114
115
|
reason: finalReason ?? null,
|
|
115
|
-
lifecycleStatus: finalStatus === "completed" ? "completed" : "archived",
|
|
116
116
|
payloadJson: JSON.stringify({
|
|
117
117
|
...parsePayloadJson(run.payloadJson),
|
|
118
118
|
consolidatedAt: now,
|
|
@@ -123,6 +123,20 @@ async function executeStaleScheduledDreams(db, state, now) {
|
|
|
123
123
|
if ("reason" in updateResult) {
|
|
124
124
|
return updateResult;
|
|
125
125
|
}
|
|
126
|
+
// T-DQ.R.10: Accept valid candidates as long-term memory projections.
|
|
127
|
+
// This step was moved out of the runner to separate candidate generation
|
|
128
|
+
// from acceptance, per design §4.2.
|
|
129
|
+
if (dreamResult.status === "completed") {
|
|
130
|
+
for (const candidate of dreamResult.candidates.filter((c) => c.validationStatus === "valid")) {
|
|
131
|
+
const acceptResult = await acceptMemoryProjection(db, candidate.id, `topic_${state.day}`, candidate.candidateText, candidate.sourceRefs, { now });
|
|
132
|
+
if ("projectionId" in acceptResult) {
|
|
133
|
+
candidate.acceptedProjectionId = acceptResult.projectionId;
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
return acceptResult;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
126
140
|
lastResult = { completed: true, reason: finalReason ?? "dream_scheduled_stalled" };
|
|
127
141
|
}
|
|
128
142
|
else {
|
|
@@ -208,7 +222,7 @@ export async function checkDailyRhythm(db, options) {
|
|
|
208
222
|
else if (state.dreamStatus === "scheduled") {
|
|
209
223
|
// Stale scheduled run: try to execute consolidation now
|
|
210
224
|
const staleResult = await executeStaleScheduledDreams(db, state, now);
|
|
211
|
-
if ("status" in staleResult
|
|
225
|
+
if ("status" in staleResult) {
|
|
212
226
|
return staleResult;
|
|
213
227
|
}
|
|
214
228
|
const { completed, reason } = staleResult;
|
|
@@ -226,18 +240,17 @@ export async function checkDailyRhythm(db, options) {
|
|
|
226
240
|
// Already handled; do not re-schedule
|
|
227
241
|
}
|
|
228
242
|
else {
|
|
243
|
+
// Global 7-day interval check: look across all quiet reviews, not just today's.
|
|
229
244
|
const quietId = `quiet_${day}`;
|
|
230
|
-
const
|
|
231
|
-
if (
|
|
232
|
-
return
|
|
245
|
+
const globalLatest = await readLatestDreamConsolidationRunByStatus(db, ["completed", "blocked"]);
|
|
246
|
+
if (globalLatest.degraded) {
|
|
247
|
+
return globalLatest.degraded;
|
|
233
248
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
state.dreamReason = latestRun.row.reason ?? "dream_completed";
|
|
240
|
-
if (latestRun.row.status === "completed") {
|
|
249
|
+
if (globalLatest.row &&
|
|
250
|
+
isWithinDays(globalLatest.row.createdAt, now, DREAM_DEFAULT_INTERVAL_DAYS)) {
|
|
251
|
+
state.dreamStatus = globalLatest.row.status;
|
|
252
|
+
state.dreamReason = "dream_interval_active";
|
|
253
|
+
if (globalLatest.row.status === "completed") {
|
|
241
254
|
state.dreamCompletedAt = now;
|
|
242
255
|
}
|
|
243
256
|
}
|
|
@@ -264,11 +277,10 @@ export async function checkDailyRhythm(db, options) {
|
|
|
264
277
|
// Immediately execute the freshly scheduled dream so it does not sit
|
|
265
278
|
// pending forever (T-DQ.R.7).
|
|
266
279
|
const consolidateResult = await runDreamConsolidation(db, dreamResult.id, { now });
|
|
267
|
-
if ("status" in consolidateResult &&
|
|
280
|
+
if ("status" in consolidateResult && !("ownerStage" in consolidateResult)) {
|
|
268
281
|
const dreamOutcome = consolidateResult;
|
|
269
282
|
const updateResult = await updateDreamConsolidationRunStatus(db, dreamResult.id, dreamOutcome.status, {
|
|
270
283
|
reason: dreamOutcome.reason ?? null,
|
|
271
|
-
lifecycleStatus: dreamOutcome.status === "completed" ? "completed" : "archived",
|
|
272
284
|
payloadJson: JSON.stringify({
|
|
273
285
|
consolidatedAt: now,
|
|
274
286
|
candidateCount: dreamOutcome.candidates.length,
|
|
@@ -281,6 +293,18 @@ export async function checkDailyRhythm(db, options) {
|
|
|
281
293
|
state.dreamReason = dreamOutcome.reason ?? (dreamOutcome.status === "completed" ? "dream_completed" : "dream_failed");
|
|
282
294
|
if (dreamOutcome.status === "completed") {
|
|
283
295
|
state.dreamCompletedAt = now;
|
|
296
|
+
// T-DQ.R.10: Accept valid candidates as long-term memory projections.
|
|
297
|
+
// This step was moved out of the runner to separate candidate generation
|
|
298
|
+
// from acceptance, per design §4.2.
|
|
299
|
+
for (const candidate of dreamOutcome.candidates.filter((c) => c.validationStatus === "valid")) {
|
|
300
|
+
const acceptResult = await acceptMemoryProjection(db, candidate.id, `topic_${day}`, candidate.candidateText, candidate.sourceRefs, { now });
|
|
301
|
+
if ("projectionId" in acceptResult) {
|
|
302
|
+
candidate.acceptedProjectionId = acceptResult.projectionId;
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
305
|
+
return acceptResult;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
284
308
|
}
|
|
285
309
|
}
|
|
286
310
|
else {
|
|
@@ -10,13 +10,17 @@
|
|
|
10
10
|
* - `.anws/v8/04_SYSTEM_DESIGN/dream-quiet-memory-system.md §4.2`
|
|
11
11
|
*
|
|
12
12
|
* Dependencies:
|
|
13
|
-
* - `src/storage/v8-state-stores.js` (readDreamConsolidationRunById, readQuietDailyReviewById
|
|
13
|
+
* - `src/storage/v8-state-stores.js` (readDreamConsolidationRunById, readQuietDailyReviewById)
|
|
14
14
|
* - `src/shared/types/v8-contracts.js` (SourceRef, DegradedOperationResult, V8ReasonCode)
|
|
15
15
|
*
|
|
16
16
|
* Boundary:
|
|
17
17
|
* - Rules-only candidate generation; no model assist in this version.
|
|
18
18
|
* - Does not accept/reject projections; only creates candidates.
|
|
19
19
|
* - Redaction gate blocks sensitive private content, preserves public technical.
|
|
20
|
+
* - T-DQ.R.10: Does NOT call acceptMemoryProjection. Candidate acceptance is a
|
|
21
|
+
* separate step owned by the caller (dream-scheduler or explicit accept API).
|
|
22
|
+
* The runner only generates and validates candidates; it returns them for
|
|
23
|
+
* the caller to accept via `acceptMemoryProjection(candidateId)`.
|
|
20
24
|
*
|
|
21
25
|
* Test coverage: tests/unit/dream/dream-consolidation-runner.test.ts
|
|
22
26
|
*/
|
|
@@ -10,18 +10,22 @@
|
|
|
10
10
|
* - `.anws/v8/04_SYSTEM_DESIGN/dream-quiet-memory-system.md §4.2`
|
|
11
11
|
*
|
|
12
12
|
* Dependencies:
|
|
13
|
-
* - `src/storage/v8-state-stores.js` (readDreamConsolidationRunById, readQuietDailyReviewById
|
|
13
|
+
* - `src/storage/v8-state-stores.js` (readDreamConsolidationRunById, readQuietDailyReviewById)
|
|
14
14
|
* - `src/shared/types/v8-contracts.js` (SourceRef, DegradedOperationResult, V8ReasonCode)
|
|
15
15
|
*
|
|
16
16
|
* Boundary:
|
|
17
17
|
* - Rules-only candidate generation; no model assist in this version.
|
|
18
18
|
* - Does not accept/reject projections; only creates candidates.
|
|
19
19
|
* - Redaction gate blocks sensitive private content, preserves public technical.
|
|
20
|
+
* - T-DQ.R.10: Does NOT call acceptMemoryProjection. Candidate acceptance is a
|
|
21
|
+
* separate step owned by the caller (dream-scheduler or explicit accept API).
|
|
22
|
+
* The runner only generates and validates candidates; it returns them for
|
|
23
|
+
* the caller to accept via `acceptMemoryProjection(candidateId)`.
|
|
20
24
|
*
|
|
21
25
|
* Test coverage: tests/unit/dream/dream-consolidation-runner.test.ts
|
|
22
26
|
*/
|
|
23
27
|
import { readDreamConsolidationRunById, readQuietDailyReviewById, } from "../../../storage/v8-state-stores.js";
|
|
24
|
-
import {
|
|
28
|
+
import { classifyDegradedStatus } from "../../../shared/degraded-status-classifier.js";
|
|
25
29
|
// ───────────────────────────────────────────────────────────────
|
|
26
30
|
// Helpers
|
|
27
31
|
// ───────────────────────────────────────────────────────────────
|
|
@@ -47,9 +51,13 @@ function buildSourceRefsFromReview(review) {
|
|
|
47
51
|
];
|
|
48
52
|
}
|
|
49
53
|
function redactSensitive(input) {
|
|
50
|
-
//
|
|
54
|
+
// Credential-shaped patterns first — highest sensitivity.
|
|
51
55
|
if (/\b(?:Bearer|token|secret|password|key)\s*[:=]\s*[a-zA-Z0-9+/=]{8,}\b/i.test(input)) {
|
|
52
|
-
return { text: "[redacted: credential shape detected]", blocked: true };
|
|
56
|
+
return { text: "[redacted: credential shape detected]", blocked: true, reason: "dream_blocked_credential" };
|
|
57
|
+
}
|
|
58
|
+
// Private context markers (names, addresses, phone numbers) — lower threshold than credential.
|
|
59
|
+
if (/\b(?:ssn|social.security|phone|address|email)\s*[:=]\s*[^\s]+/i.test(input)) {
|
|
60
|
+
return { text: "[redacted: private context]", blocked: true, reason: "dream_blocked_private_redacted" };
|
|
53
61
|
}
|
|
54
62
|
return { text: input, blocked: false };
|
|
55
63
|
}
|
|
@@ -57,7 +65,7 @@ function generateCandidatesFromReview(runId, reviewId, reviewPayload) {
|
|
|
57
65
|
const candidates = [];
|
|
58
66
|
const summary = String(reviewPayload.reviewSummary ?? "");
|
|
59
67
|
if (summary.length > 0) {
|
|
60
|
-
const { text, blocked } = redactSensitive(summary);
|
|
68
|
+
const { text, blocked, reason } = redactSensitive(summary);
|
|
61
69
|
if (blocked) {
|
|
62
70
|
candidates.push({
|
|
63
71
|
id: `cand_${runId}_summary`,
|
|
@@ -67,7 +75,7 @@ function generateCandidatesFromReview(runId, reviewId, reviewPayload) {
|
|
|
67
75
|
sourceRefs: buildSourceRefsFromReview({ id: reviewId, day: "" }),
|
|
68
76
|
confidence: 0.3,
|
|
69
77
|
validationStatus: "blocked",
|
|
70
|
-
validationReason:
|
|
78
|
+
validationReason: reason,
|
|
71
79
|
});
|
|
72
80
|
}
|
|
73
81
|
else {
|
|
@@ -84,7 +92,7 @@ function generateCandidatesFromReview(runId, reviewId, reviewPayload) {
|
|
|
84
92
|
}
|
|
85
93
|
const importanceSignals = reviewPayload.importanceSignals;
|
|
86
94
|
if (importanceSignals && importanceSignals.length > 0) {
|
|
87
|
-
const { text, blocked } = redactSensitive(importanceSignals.join("; "));
|
|
95
|
+
const { text, blocked, reason } = redactSensitive(importanceSignals.join("; "));
|
|
88
96
|
if (!blocked) {
|
|
89
97
|
candidates.push({
|
|
90
98
|
id: `cand_${runId}_signals`,
|
|
@@ -96,9 +104,33 @@ function generateCandidatesFromReview(runId, reviewId, reviewPayload) {
|
|
|
96
104
|
validationStatus: "valid",
|
|
97
105
|
});
|
|
98
106
|
}
|
|
107
|
+
else {
|
|
108
|
+
candidates.push({
|
|
109
|
+
id: `cand_${runId}_signals`,
|
|
110
|
+
runId,
|
|
111
|
+
reviewId,
|
|
112
|
+
candidateText: text,
|
|
113
|
+
sourceRefs: buildSourceRefsFromReview({ id: reviewId, day: "" }),
|
|
114
|
+
confidence: 0.3,
|
|
115
|
+
validationStatus: "blocked",
|
|
116
|
+
validationReason: reason,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
99
119
|
}
|
|
100
120
|
return candidates;
|
|
101
121
|
}
|
|
122
|
+
function validateCandidate(candidate) {
|
|
123
|
+
if (!candidate.candidateText || candidate.candidateText.trim().length === 0) {
|
|
124
|
+
return "dream_blocked_validation_failed";
|
|
125
|
+
}
|
|
126
|
+
if (!candidate.sourceRefs || candidate.sourceRefs.length === 0) {
|
|
127
|
+
return "dream_blocked_validation_failed";
|
|
128
|
+
}
|
|
129
|
+
if (candidate.confidence < 0.1) {
|
|
130
|
+
return "dream_blocked_validation_failed";
|
|
131
|
+
}
|
|
132
|
+
return undefined;
|
|
133
|
+
}
|
|
102
134
|
// ───────────────────────────────────────────────────────────────
|
|
103
135
|
// Public API
|
|
104
136
|
// ───────────────────────────────────────────────────────────────
|
|
@@ -111,7 +143,7 @@ export async function runDreamConsolidation(db, runId, options) {
|
|
|
111
143
|
const run = runRead.row;
|
|
112
144
|
if (!run) {
|
|
113
145
|
return {
|
|
114
|
-
status: "
|
|
146
|
+
status: classifyDegradedStatus("state_unreadable"),
|
|
115
147
|
reason: "state_unreadable",
|
|
116
148
|
ownerStage: "dream",
|
|
117
149
|
sourceRefs: [],
|
|
@@ -126,7 +158,7 @@ export async function runDreamConsolidation(db, runId, options) {
|
|
|
126
158
|
const review = reviewRead.row;
|
|
127
159
|
if (!review) {
|
|
128
160
|
return {
|
|
129
|
-
status: "
|
|
161
|
+
status: classifyDegradedStatus("state_unreadable"),
|
|
130
162
|
reason: "state_unreadable",
|
|
131
163
|
ownerStage: "dream",
|
|
132
164
|
sourceRefs: [],
|
|
@@ -135,34 +167,41 @@ export async function runDreamConsolidation(db, runId, options) {
|
|
|
135
167
|
};
|
|
136
168
|
}
|
|
137
169
|
const reviewPayload = parsePayloadJson(review.payloadJson);
|
|
170
|
+
const contentStatus = String(reviewPayload.contentStatus ?? "");
|
|
171
|
+
// Block placeholder or empty Quiet reviews before candidate generation.
|
|
172
|
+
if (contentStatus === "placeholder_rejected" || contentStatus === "content_missing" || contentStatus === "empty") {
|
|
173
|
+
return {
|
|
174
|
+
runId,
|
|
175
|
+
status: "blocked",
|
|
176
|
+
candidates: [],
|
|
177
|
+
reason: "dream_blocked_no_content",
|
|
178
|
+
};
|
|
179
|
+
}
|
|
138
180
|
const candidates = generateCandidatesFromReview(runId, run.quietReviewId, reviewPayload);
|
|
139
|
-
//
|
|
181
|
+
// Run candidate validation; invalid candidates block the run with a precise reason.
|
|
182
|
+
for (const candidate of candidates) {
|
|
183
|
+
if (candidate.validationStatus === "valid") {
|
|
184
|
+
const validationReason = validateCandidate(candidate);
|
|
185
|
+
if (validationReason) {
|
|
186
|
+
candidate.validationStatus = "blocked";
|
|
187
|
+
candidate.validationReason = validationReason;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
// If all candidates blocked → run blocked with the first precise reason.
|
|
140
192
|
if (candidates.length > 0 && candidates.every((c) => c.validationStatus === "blocked")) {
|
|
193
|
+
const firstReason = candidates[0]?.validationReason;
|
|
141
194
|
return {
|
|
142
195
|
runId,
|
|
143
196
|
status: "blocked",
|
|
144
197
|
candidates,
|
|
145
|
-
reason: "
|
|
198
|
+
reason: firstReason ?? "dream_blocked_private_redacted",
|
|
146
199
|
};
|
|
147
200
|
}
|
|
148
|
-
//
|
|
149
|
-
//
|
|
150
|
-
//
|
|
151
|
-
|
|
152
|
-
for (const candidate of validCandidates) {
|
|
153
|
-
const acceptResult = await acceptMemoryProjection(db, candidate.id, `topic_${review.day}`, candidate.candidateText, candidate.sourceRefs, { now });
|
|
154
|
-
if ("projectionId" in acceptResult) {
|
|
155
|
-
candidate.acceptedProjectionId = acceptResult.projectionId;
|
|
156
|
-
}
|
|
157
|
-
else {
|
|
158
|
-
return {
|
|
159
|
-
runId,
|
|
160
|
-
status: "failed",
|
|
161
|
-
candidates,
|
|
162
|
-
reason: acceptResult.reason,
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
}
|
|
201
|
+
// T-DQ.R.10: Runner only generates and validates candidates.
|
|
202
|
+
// Acceptance is a separate step owned by the caller via acceptMemoryProjection.
|
|
203
|
+
// Valid candidates are returned with validationStatus="valid" for the caller
|
|
204
|
+
// to accept; the runner does NOT call acceptMemoryProjection here.
|
|
166
205
|
return {
|
|
167
206
|
runId,
|
|
168
207
|
status: "completed",
|