@haaaiawd/second-nature 0.2.2 → 0.2.5
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/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/runtime/cli/ops/heartbeat-surface.d.ts +20 -0
- package/runtime/cli/ops/heartbeat-surface.js +72 -1
- package/runtime/cli/ops/ops-router.js +119 -31
- package/runtime/connectors/base/contract.d.ts +11 -0
- package/runtime/connectors/base/failure-taxonomy.js +45 -26
- package/runtime/connectors/base/policy-bound-write-dispatch.d.ts +29 -0
- package/runtime/connectors/base/policy-bound-write-dispatch.js +127 -0
- package/runtime/connectors/services/connector-cooldown-port.d.ts +22 -0
- package/runtime/connectors/services/connector-cooldown-port.js +123 -0
- package/runtime/connectors/services/connector-executor-adapter.js +10 -4
- package/runtime/connectors/services/credential-route-context.d.ts +3 -2
- package/runtime/connectors/services/credential-route-context.js +19 -3
- package/runtime/core/second-nature/action/action-closure-recorder.d.ts +4 -0
- package/runtime/core/second-nature/action/action-closure-recorder.js +5 -0
- package/runtime/core/second-nature/action/action-proposal-builder.js +1 -0
- package/runtime/core/second-nature/control-plane/heartbeat-orchestrator.d.ts +2 -0
- package/runtime/core/second-nature/control-plane/heartbeat-orchestrator.js +412 -25
- package/runtime/core/second-nature/control-plane/real-runtime-spine.d.ts +35 -0
- package/runtime/core/second-nature/control-plane/real-runtime-spine.js +42 -0
- package/runtime/core/second-nature/guidance/impulse-context-reader.d.ts +44 -0
- package/runtime/core/second-nature/guidance/impulse-context-reader.js +84 -0
- package/runtime/core/second-nature/guidance/impulse-context-writer.d.ts +39 -0
- package/runtime/core/second-nature/guidance/impulse-context-writer.js +70 -0
- package/runtime/core/second-nature/perception/judgment-engine.d.ts +2 -0
- package/runtime/core/second-nature/perception/judgment-engine.js +11 -1
- package/runtime/core/second-nature/perception/perception-builder.d.ts +6 -2
- package/runtime/core/second-nature/perception/perception-builder.js +18 -7
- package/runtime/core/second-nature/quiet-dream/daily-rhythm-scheduler.d.ts +43 -0
- package/runtime/core/second-nature/quiet-dream/daily-rhythm-scheduler.js +162 -0
- package/runtime/core/second-nature/quiet-dream/memory-projection-lifecycle.d.ts +2 -2
- package/runtime/core/second-nature/quiet-dream/memory-projection-lifecycle.js +27 -44
- package/runtime/core/second-nature/quiet-dream/quiet-daily-review-builder.d.ts +3 -0
- package/runtime/core/second-nature/quiet-dream/quiet-daily-review-builder.js +4 -0
- package/runtime/observability/living-loop-health-gate.d.ts +49 -0
- package/runtime/observability/living-loop-health-gate.js +141 -0
- package/runtime/observability/loop-status.d.ts +30 -0
- package/runtime/observability/loop-status.js +167 -7
- package/runtime/observability/services/heartbeat-digest-assembler.d.ts +21 -0
- package/runtime/observability/services/heartbeat-digest-assembler.js +44 -0
- package/runtime/shared/types/v8-contracts.d.ts +2 -2
- package/runtime/storage/db/index.js +60 -6
- package/runtime/storage/db/migrations/index.js +4 -0
- package/runtime/storage/db/migrations/v8-001-living-perception-loop.js +119 -119
- package/runtime/storage/db/migrations/v8-002-perception-contract-alignment.d.ts +12 -0
- package/runtime/storage/db/migrations/v8-002-perception-contract-alignment.js +14 -0
- package/runtime/storage/db/migrations/v8-003-quiet-closure-refs.d.ts +10 -0
- package/runtime/storage/db/migrations/v8-003-quiet-closure-refs.js +12 -0
- package/runtime/storage/db/schema/v8-entities.d.ts +874 -0
- package/runtime/storage/db/schema/v8-entities.js +62 -1
- package/runtime/storage/v8-state-stores.d.ts +41 -2
- package/runtime/storage/v8-state-stores.js +206 -2
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* Dependencies: drizzle-orm/sqlite-core
|
|
13
13
|
* Boundary: Schema definitions only; no runtime logic.
|
|
14
14
|
*/
|
|
15
|
-
import { sqliteTable, text, integer, real } from "drizzle-orm/sqlite-core";
|
|
15
|
+
import { sqliteTable, text, integer, real, index } from "drizzle-orm/sqlite-core";
|
|
16
16
|
// ───────────────────────────────────────────────────────────────
|
|
17
17
|
// 1. EvidenceItem
|
|
18
18
|
// ───────────────────────────────────────────────────────────────
|
|
@@ -39,6 +39,7 @@ export const perceptionCard = sqliteTable("perception_card", {
|
|
|
39
39
|
entitiesJson: text("entities_json"),
|
|
40
40
|
novelty: text("novelty"),
|
|
41
41
|
relevance: real("relevance"),
|
|
42
|
+
relevanceClass: text("relevance_class"),
|
|
42
43
|
summary: text("summary"),
|
|
43
44
|
riskFlagsJson: text("risk_flags_json"),
|
|
44
45
|
confidence: real("confidence"),
|
|
@@ -72,6 +73,8 @@ export const actionClosureRecord = sqliteTable("action_closure_record", {
|
|
|
72
73
|
id: text("id").primaryKey(),
|
|
73
74
|
createdAt: text("created_at").notNull(),
|
|
74
75
|
cycleId: text("cycle_id").notNull(),
|
|
76
|
+
platformId: text("platform_id"),
|
|
77
|
+
capabilityId: text("capability_id"),
|
|
75
78
|
proposalId: text("proposal_id"),
|
|
76
79
|
decisionId: text("decision_id"),
|
|
77
80
|
status: text("status").notNull(),
|
|
@@ -92,6 +95,7 @@ export const quietDailyReview = sqliteTable("quiet_daily_review", {
|
|
|
92
95
|
closureCount: integer("closure_count").notNull().default(0),
|
|
93
96
|
memoryCandidateCount: integer("memory_candidate_count").notNull().default(0),
|
|
94
97
|
sourceRefsJson: text("source_refs_json").notNull(),
|
|
98
|
+
closureRefsJson: text("closure_refs_json"),
|
|
95
99
|
redactionClass: text("redaction_class").notNull().default("none"),
|
|
96
100
|
payloadJson: text("payload_json"),
|
|
97
101
|
lifecycleStatus: text("lifecycle_status").notNull().default("pending"),
|
|
@@ -158,3 +162,60 @@ export const loopStageEvent = sqliteTable("loop_stage_event", {
|
|
|
158
162
|
payloadJson: text("payload_json"),
|
|
159
163
|
lifecycleStatus: text("lifecycle_status").notNull().default("started"),
|
|
160
164
|
});
|
|
165
|
+
// ───────────────────────────────────────────────────────────────
|
|
166
|
+
// 10. ImpulseContextArtifact
|
|
167
|
+
// ───────────────────────────────────────────────────────────────
|
|
168
|
+
export const impulseContextArtifact = sqliteTable("impulse_context_artifact", {
|
|
169
|
+
id: text("id").primaryKey(),
|
|
170
|
+
createdAt: text("created_at").notNull(),
|
|
171
|
+
updatedAt: text("updated_at").notNull(),
|
|
172
|
+
sceneType: text("scene_type").notNull(),
|
|
173
|
+
capabilityIntent: text("capability_intent"),
|
|
174
|
+
platformId: text("platform_id"),
|
|
175
|
+
capabilityClass: text("capability_class"),
|
|
176
|
+
impulseSource: text("impulse_source").notNull(),
|
|
177
|
+
impulseText: text("impulse_text"),
|
|
178
|
+
atmosphereText: text("atmosphere_text"),
|
|
179
|
+
expressionBoundaryConstraintsJson: text("expression_boundary_constraints_json"),
|
|
180
|
+
expressionBoundaryStyle: text("expression_boundary_style"),
|
|
181
|
+
freshnessVersion: integer("freshness_version").notNull().default(1),
|
|
182
|
+
sourceRefsJson: text("source_refs_json").notNull(),
|
|
183
|
+
redactionClass: text("redaction_class").notNull().default("none"),
|
|
184
|
+
payloadJson: text("payload_json"),
|
|
185
|
+
lifecycleStatus: text("lifecycle_status").notNull().default("active"),
|
|
186
|
+
});
|
|
187
|
+
// 11. DailyRhythmState
|
|
188
|
+
// ───────────────────────────────────────────────────────────────
|
|
189
|
+
export const dailyRhythmState = sqliteTable("daily_rhythm_state", {
|
|
190
|
+
id: text("id").primaryKey(),
|
|
191
|
+
day: text("day").notNull(),
|
|
192
|
+
quietStatus: text("quiet_status").notNull().default("not_due"),
|
|
193
|
+
dreamStatus: text("dream_status").notNull().default("not_due"),
|
|
194
|
+
quietReason: text("quiet_reason"),
|
|
195
|
+
dreamReason: text("dream_reason"),
|
|
196
|
+
quietCompletedAt: text("quiet_completed_at"),
|
|
197
|
+
dreamCompletedAt: text("dream_completed_at"),
|
|
198
|
+
sourceRefsJson: text("source_refs_json").notNull(),
|
|
199
|
+
payloadJson: text("payload_json"),
|
|
200
|
+
updatedAt: text("updated_at").notNull(),
|
|
201
|
+
});
|
|
202
|
+
// ───────────────────────────────────────────────────────────────
|
|
203
|
+
// 12. ConnectorCooldownState
|
|
204
|
+
// ───────────────────────────────────────────────────────────────
|
|
205
|
+
export const connectorCooldownState = sqliteTable("connector_cooldown_state", {
|
|
206
|
+
id: text("id").primaryKey(),
|
|
207
|
+
platformId: text("platform_id").notNull(),
|
|
208
|
+
capabilityId: text("capability_id").notNull(),
|
|
209
|
+
failureClass: text("failure_class").notNull(),
|
|
210
|
+
retryAfterMs: integer("retry_after_ms"),
|
|
211
|
+
blockedUntil: text("blocked_until").notNull(),
|
|
212
|
+
failureCount: integer("failure_count").notNull().default(1),
|
|
213
|
+
terminalCount: integer("terminal_count").notNull().default(0),
|
|
214
|
+
sourceRefsJson: text("source_refs_json").notNull(),
|
|
215
|
+
redactionClass: text("redaction_class").notNull().default("none"),
|
|
216
|
+
payloadJson: text("payload_json"),
|
|
217
|
+
createdAt: text("created_at").notNull(),
|
|
218
|
+
updatedAt: text("updated_at").notNull(),
|
|
219
|
+
}, (table) => ({
|
|
220
|
+
platformCapabilityIdx: index("connector_cooldown_state_platform_capability_idx").on(table.platformId, table.capabilityId),
|
|
221
|
+
}));
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* Test coverage: tests/unit/storage/v8-state-stores.test.ts
|
|
23
23
|
*/
|
|
24
24
|
import type { StateDatabase } from "./db/index.js";
|
|
25
|
-
import { type EvidenceItemRecord, type NewEvidenceItemRecord, type PerceptionCardRecord, type NewPerceptionCardRecord, type JudgmentVerdictRecord, type NewJudgmentVerdictRecord, type ActionClosureRecordSelect, type ActionClosureRecordInsert, type QuietDailyReviewRecord, type NewQuietDailyReviewRecord, type DreamConsolidationRunRecord, type NewDreamConsolidationRunRecord, type LongTermMemoryProjectionRecord, type NewLongTermMemoryProjectionRecord, type HeartbeatCycleTraceRecord, type NewHeartbeatCycleTraceRecord, type LoopStageEventRecord, type NewLoopStageEventRecord } from "./db/schema/v8-entities.js";
|
|
25
|
+
import { type EvidenceItemRecord, type NewEvidenceItemRecord, type PerceptionCardRecord, type NewPerceptionCardRecord, type JudgmentVerdictRecord, type NewJudgmentVerdictRecord, type ActionClosureRecordSelect, type ActionClosureRecordInsert, type QuietDailyReviewRecord, type NewQuietDailyReviewRecord, type DreamConsolidationRunRecord, type NewDreamConsolidationRunRecord, type LongTermMemoryProjectionRecord, type NewLongTermMemoryProjectionRecord, type HeartbeatCycleTraceRecord, type NewHeartbeatCycleTraceRecord, type LoopStageEventRecord, type NewLoopStageEventRecord, type ImpulseContextArtifactRecord, type NewImpulseContextArtifactRecord, type DailyRhythmStateRecord, type NewDailyRhythmStateRecord, type ConnectorCooldownStateRecord, type NewConnectorCooldownStateRecord } from "./db/schema/v8-entities.js";
|
|
26
26
|
import type { SourceRef, DegradedOperationResult } from "../shared/types/v8-contracts.js";
|
|
27
27
|
export interface WriteValidationError {
|
|
28
28
|
ok: false;
|
|
@@ -81,8 +81,9 @@ export declare function readActionClosuresByDay(db: StateDatabase, day: string):
|
|
|
81
81
|
rows: ActionClosureRecordSelect[];
|
|
82
82
|
degraded?: DegradedOperationResult;
|
|
83
83
|
}>;
|
|
84
|
-
export declare function writeQuietDailyReview(db: StateDatabase, row: Omit<NewQuietDailyReviewRecord, "sourceRefsJson"> & {
|
|
84
|
+
export declare function writeQuietDailyReview(db: StateDatabase, row: Omit<NewQuietDailyReviewRecord, "sourceRefsJson" | "closureRefsJson"> & {
|
|
85
85
|
sourceRefs: SourceRef[];
|
|
86
|
+
closureRefs?: SourceRef[];
|
|
86
87
|
}): Promise<{
|
|
87
88
|
id: string;
|
|
88
89
|
} | DegradedOperationResult>;
|
|
@@ -112,6 +113,13 @@ export declare function writeLongTermMemoryProjection(db: StateDatabase, row: Om
|
|
|
112
113
|
}): Promise<{
|
|
113
114
|
id: string;
|
|
114
115
|
} | DegradedOperationResult>;
|
|
116
|
+
/**
|
|
117
|
+
* Update an existing projection's status — required for supersession lifecycle.
|
|
118
|
+
* Uses UPDATE instead of INSERT to avoid primary-key conflict.
|
|
119
|
+
*/
|
|
120
|
+
export declare function updateLongTermMemoryProjectionStatus(db: StateDatabase, id: string, status: LongTermMemoryProjectionRecord["status"], payloadJson?: string): Promise<{
|
|
121
|
+
id: string;
|
|
122
|
+
} | DegradedOperationResult>;
|
|
115
123
|
export declare function readMemoryProjectionsByStatus(db: StateDatabase, status: LongTermMemoryProjectionRecord["status"]): Promise<{
|
|
116
124
|
rows: LongTermMemoryProjectionRecord[];
|
|
117
125
|
degraded?: DegradedOperationResult;
|
|
@@ -120,6 +128,10 @@ export declare function readMemoryProjectionsByTopic(db: StateDatabase, topicKey
|
|
|
120
128
|
rows: LongTermMemoryProjectionRecord[];
|
|
121
129
|
degraded?: DegradedOperationResult;
|
|
122
130
|
}>;
|
|
131
|
+
export declare function readLongTermMemoryProjectionById(db: StateDatabase, id: string): Promise<{
|
|
132
|
+
row?: LongTermMemoryProjectionRecord;
|
|
133
|
+
degraded?: DegradedOperationResult;
|
|
134
|
+
}>;
|
|
123
135
|
export declare function writeHeartbeatCycleTrace(db: StateDatabase, row: Omit<NewHeartbeatCycleTraceRecord, "sourceRefsJson"> & {
|
|
124
136
|
sourceRefs?: SourceRef[];
|
|
125
137
|
}): Promise<{
|
|
@@ -142,6 +154,33 @@ export declare function readLoopStageEventsByStage(db: StateDatabase, stage: Loo
|
|
|
142
154
|
rows: LoopStageEventRecord[];
|
|
143
155
|
degraded?: DegradedOperationResult;
|
|
144
156
|
}>;
|
|
157
|
+
export declare function writeImpulseContextArtifact(db: StateDatabase, row: Omit<NewImpulseContextArtifactRecord, "sourceRefsJson"> & {
|
|
158
|
+
sourceRefs: SourceRef[];
|
|
159
|
+
}): Promise<{
|
|
160
|
+
id: string;
|
|
161
|
+
} | DegradedOperationResult>;
|
|
162
|
+
export declare function readImpulseContextArtifact(db: StateDatabase, sceneType: string, capabilityIntent?: string, platformId?: string): Promise<{
|
|
163
|
+
row?: ImpulseContextArtifactRecord;
|
|
164
|
+
degraded?: DegradedOperationResult;
|
|
165
|
+
}>;
|
|
166
|
+
export declare function writeDailyRhythmState(db: StateDatabase, row: Omit<NewDailyRhythmStateRecord, "sourceRefsJson"> & {
|
|
167
|
+
sourceRefs: SourceRef[];
|
|
168
|
+
}): Promise<{
|
|
169
|
+
id: string;
|
|
170
|
+
} | DegradedOperationResult>;
|
|
171
|
+
export declare function readDailyRhythmStateByDay(db: StateDatabase, day: string): Promise<{
|
|
172
|
+
row?: DailyRhythmStateRecord;
|
|
173
|
+
degraded?: DegradedOperationResult;
|
|
174
|
+
}>;
|
|
175
|
+
export declare function readConnectorCooldownState(db: StateDatabase, platformId: string, capabilityId: string): Promise<{
|
|
176
|
+
row?: ConnectorCooldownStateRecord;
|
|
177
|
+
degraded?: DegradedOperationResult;
|
|
178
|
+
}>;
|
|
179
|
+
export declare function writeConnectorCooldownState(db: StateDatabase, row: Omit<NewConnectorCooldownStateRecord, "sourceRefsJson"> & {
|
|
180
|
+
sourceRefs: SourceRef[];
|
|
181
|
+
}): Promise<{
|
|
182
|
+
id: string;
|
|
183
|
+
} | DegradedOperationResult>;
|
|
145
184
|
export declare function extractSourceRefs(row: {
|
|
146
185
|
sourceRefsJson: string | null;
|
|
147
186
|
}): SourceRef[];
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
*
|
|
22
22
|
* Test coverage: tests/unit/storage/v8-state-stores.test.ts
|
|
23
23
|
*/
|
|
24
|
-
import { eq, desc, like } from "drizzle-orm";
|
|
25
|
-
import { evidenceItem, perceptionCard, judgmentVerdict, actionClosureRecord, quietDailyReview, dreamConsolidationRun, longTermMemoryProjection, heartbeatCycleTrace, loopStageEvent, } from "./db/schema/v8-entities.js";
|
|
24
|
+
import { eq, and, desc, like, isNull } from "drizzle-orm";
|
|
25
|
+
import { evidenceItem, perceptionCard, judgmentVerdict, actionClosureRecord, quietDailyReview, dreamConsolidationRun, longTermMemoryProjection, heartbeatCycleTrace, loopStageEvent, impulseContextArtifact, dailyRhythmState, connectorCooldownState, } from "./db/schema/v8-entities.js";
|
|
26
26
|
// ───────────────────────────────────────────────────────────────
|
|
27
27
|
// Shared helpers
|
|
28
28
|
// ───────────────────────────────────────────────────────────────
|
|
@@ -99,10 +99,62 @@ export async function readEvidenceItemsByStatus(db, lifecycleStatus) {
|
|
|
99
99
|
// ───────────────────────────────────────────────────────────────
|
|
100
100
|
// PerceptionCard store
|
|
101
101
|
// ───────────────────────────────────────────────────────────────
|
|
102
|
+
const CANONICAL_NOVELTY_CLASSES = ["new", "changed", "duplicate", "stale"];
|
|
103
|
+
const CANONICAL_RELEVANCE_CLASSES = ["low", "medium", "high"];
|
|
104
|
+
function validatePerceptionCardCanonical(row) {
|
|
105
|
+
// Validate noveltyClass
|
|
106
|
+
if (row.novelty && !CANONICAL_NOVELTY_CLASSES.includes(row.novelty)) {
|
|
107
|
+
return {
|
|
108
|
+
ok: false,
|
|
109
|
+
degraded: {
|
|
110
|
+
status: "degraded",
|
|
111
|
+
reason: "perception_contract_drift",
|
|
112
|
+
ownerStage: "perception",
|
|
113
|
+
sourceRefs: row.sourceRefs,
|
|
114
|
+
operatorNextAction: `novelty "${row.novelty}" is not canonical. Expected one of: ${CANONICAL_NOVELTY_CLASSES.join(", ")}`,
|
|
115
|
+
retryable: false,
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
// Validate relevanceScore range
|
|
120
|
+
if (row.relevance !== undefined && row.relevance !== null) {
|
|
121
|
+
if (row.relevance < 0 || row.relevance > 1) {
|
|
122
|
+
return {
|
|
123
|
+
ok: false,
|
|
124
|
+
degraded: {
|
|
125
|
+
status: "degraded",
|
|
126
|
+
reason: "perception_contract_drift",
|
|
127
|
+
ownerStage: "perception",
|
|
128
|
+
sourceRefs: row.sourceRefs,
|
|
129
|
+
operatorNextAction: `relevanceScore ${row.relevance} out of range [0, 1]`,
|
|
130
|
+
retryable: false,
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
// Validate relevanceClass
|
|
136
|
+
if (row.relevanceClass && !CANONICAL_RELEVANCE_CLASSES.includes(row.relevanceClass)) {
|
|
137
|
+
return {
|
|
138
|
+
ok: false,
|
|
139
|
+
degraded: {
|
|
140
|
+
status: "degraded",
|
|
141
|
+
reason: "perception_contract_drift",
|
|
142
|
+
ownerStage: "perception",
|
|
143
|
+
sourceRefs: row.sourceRefs,
|
|
144
|
+
operatorNextAction: `relevanceClass "${row.relevanceClass}" is not canonical. Expected one of: ${CANONICAL_RELEVANCE_CLASSES.join(", ")}`,
|
|
145
|
+
retryable: false,
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
return { ok: true };
|
|
150
|
+
}
|
|
102
151
|
export async function writePerceptionCard(db, row) {
|
|
103
152
|
const validated = validateSourceRefs(row.sourceRefs, "perception");
|
|
104
153
|
if (!validated.ok)
|
|
105
154
|
return validated.degraded;
|
|
155
|
+
const canonicalCheck = validatePerceptionCardCanonical(row);
|
|
156
|
+
if (!canonicalCheck.ok)
|
|
157
|
+
return canonicalCheck.degraded;
|
|
106
158
|
try {
|
|
107
159
|
const record = {
|
|
108
160
|
...row,
|
|
@@ -258,6 +310,7 @@ export async function writeQuietDailyReview(db, row) {
|
|
|
258
310
|
const record = {
|
|
259
311
|
...row,
|
|
260
312
|
sourceRefsJson: serializeSourceRefs(validated.record),
|
|
313
|
+
closureRefsJson: row.closureRefs ? serializeSourceRefs(row.closureRefs) : null,
|
|
261
314
|
};
|
|
262
315
|
await db.db.insert(quietDailyReview).values(record);
|
|
263
316
|
return { id: row.id };
|
|
@@ -366,6 +419,23 @@ export async function writeLongTermMemoryProjection(db, row) {
|
|
|
366
419
|
return makeDegraded("state_unreadable", "projection", "Retry projection write after DB recovery", validated.record);
|
|
367
420
|
}
|
|
368
421
|
}
|
|
422
|
+
/**
|
|
423
|
+
* Update an existing projection's status — required for supersession lifecycle.
|
|
424
|
+
* Uses UPDATE instead of INSERT to avoid primary-key conflict.
|
|
425
|
+
*/
|
|
426
|
+
export async function updateLongTermMemoryProjectionStatus(db, id, status, payloadJson) {
|
|
427
|
+
try {
|
|
428
|
+
const updateData = { status, lifecycleStatus: status };
|
|
429
|
+
if (payloadJson !== undefined) {
|
|
430
|
+
updateData.payloadJson = payloadJson;
|
|
431
|
+
}
|
|
432
|
+
await db.db.update(longTermMemoryProjection).set(updateData).where(eq(longTermMemoryProjection.id, id));
|
|
433
|
+
return { id };
|
|
434
|
+
}
|
|
435
|
+
catch {
|
|
436
|
+
return makeDegraded("state_unreadable", "projection", `Retry projection status update for ${id} after DB recovery`);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
369
439
|
export async function readMemoryProjectionsByStatus(db, status) {
|
|
370
440
|
try {
|
|
371
441
|
const rows = await db.db
|
|
@@ -398,6 +468,21 @@ export async function readMemoryProjectionsByTopic(db, topicKey) {
|
|
|
398
468
|
};
|
|
399
469
|
}
|
|
400
470
|
}
|
|
471
|
+
export async function readLongTermMemoryProjectionById(db, id) {
|
|
472
|
+
try {
|
|
473
|
+
const rows = await db.db
|
|
474
|
+
.select()
|
|
475
|
+
.from(longTermMemoryProjection)
|
|
476
|
+
.where(eq(longTermMemoryProjection.id, id))
|
|
477
|
+
.limit(1);
|
|
478
|
+
return { row: rows[0] };
|
|
479
|
+
}
|
|
480
|
+
catch {
|
|
481
|
+
return {
|
|
482
|
+
degraded: makeDegraded("state_unreadable", "projection", `Check state database connectivity for projection ${id}`),
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
}
|
|
401
486
|
// ───────────────────────────────────────────────────────────────
|
|
402
487
|
// HeartbeatCycleTrace store
|
|
403
488
|
// ───────────────────────────────────────────────────────────────
|
|
@@ -484,6 +569,125 @@ export async function readLoopStageEventsByStage(db, stage, limit = 100) {
|
|
|
484
569
|
}
|
|
485
570
|
}
|
|
486
571
|
// ───────────────────────────────────────────────────────────────
|
|
572
|
+
// ImpulseContextArtifact store
|
|
573
|
+
// ───────────────────────────────────────────────────────────────
|
|
574
|
+
export async function writeImpulseContextArtifact(db, row) {
|
|
575
|
+
const validated = validateSourceRefs(row.sourceRefs, "projection");
|
|
576
|
+
if (!validated.ok)
|
|
577
|
+
return validated.degraded;
|
|
578
|
+
try {
|
|
579
|
+
const record = {
|
|
580
|
+
...row,
|
|
581
|
+
sourceRefsJson: serializeSourceRefs(validated.record),
|
|
582
|
+
};
|
|
583
|
+
// Upsert: delete existing then insert (SQLite primary-key conflict)
|
|
584
|
+
await db.db.delete(impulseContextArtifact).where(eq(impulseContextArtifact.id, row.id));
|
|
585
|
+
await db.db.insert(impulseContextArtifact).values(record);
|
|
586
|
+
return { id: row.id };
|
|
587
|
+
}
|
|
588
|
+
catch {
|
|
589
|
+
return makeDegraded("state_unreadable", "projection", "Retry impulse context write after DB recovery", validated.record);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
export async function readImpulseContextArtifact(db, sceneType, capabilityIntent, platformId) {
|
|
593
|
+
try {
|
|
594
|
+
const conditions = [eq(impulseContextArtifact.sceneType, sceneType)];
|
|
595
|
+
if (capabilityIntent) {
|
|
596
|
+
conditions.push(eq(impulseContextArtifact.capabilityIntent, capabilityIntent));
|
|
597
|
+
}
|
|
598
|
+
else {
|
|
599
|
+
conditions.push(isNull(impulseContextArtifact.capabilityIntent));
|
|
600
|
+
}
|
|
601
|
+
if (platformId) {
|
|
602
|
+
conditions.push(eq(impulseContextArtifact.platformId, platformId));
|
|
603
|
+
}
|
|
604
|
+
else {
|
|
605
|
+
conditions.push(isNull(impulseContextArtifact.platformId));
|
|
606
|
+
}
|
|
607
|
+
const rows = await db.db
|
|
608
|
+
.select()
|
|
609
|
+
.from(impulseContextArtifact)
|
|
610
|
+
.where(and(...conditions))
|
|
611
|
+
.orderBy(desc(impulseContextArtifact.updatedAt))
|
|
612
|
+
.limit(1);
|
|
613
|
+
return { row: rows[0] };
|
|
614
|
+
}
|
|
615
|
+
catch {
|
|
616
|
+
return {
|
|
617
|
+
degraded: makeDegraded("state_unreadable", "projection", "Check state database connectivity"),
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
// ───────────────────────────────────────────────────────────────
|
|
622
|
+
// DailyRhythmState store
|
|
623
|
+
// ───────────────────────────────────────────────────────────────
|
|
624
|
+
export async function writeDailyRhythmState(db, row) {
|
|
625
|
+
const validated = validateSourceRefs(row.sourceRefs, "dream");
|
|
626
|
+
if (!validated.ok)
|
|
627
|
+
return validated.degraded;
|
|
628
|
+
try {
|
|
629
|
+
const record = {
|
|
630
|
+
...row,
|
|
631
|
+
sourceRefsJson: serializeSourceRefs(validated.record),
|
|
632
|
+
};
|
|
633
|
+
await db.db.delete(dailyRhythmState).where(eq(dailyRhythmState.id, row.id));
|
|
634
|
+
await db.db.insert(dailyRhythmState).values(record);
|
|
635
|
+
return { id: row.id };
|
|
636
|
+
}
|
|
637
|
+
catch {
|
|
638
|
+
return makeDegraded("state_unreadable", "dream", "Retry daily rhythm state write after DB recovery", validated.record);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
export async function readDailyRhythmStateByDay(db, day) {
|
|
642
|
+
try {
|
|
643
|
+
const rows = await db.db
|
|
644
|
+
.select()
|
|
645
|
+
.from(dailyRhythmState)
|
|
646
|
+
.where(eq(dailyRhythmState.day, day))
|
|
647
|
+
.orderBy(desc(dailyRhythmState.updatedAt))
|
|
648
|
+
.limit(1);
|
|
649
|
+
return { row: rows[0] };
|
|
650
|
+
}
|
|
651
|
+
catch {
|
|
652
|
+
return {
|
|
653
|
+
degraded: makeDegraded("state_unreadable", "dream", `Check state database connectivity for day=${day}`),
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
export async function readConnectorCooldownState(db, platformId, capabilityId) {
|
|
658
|
+
try {
|
|
659
|
+
const rows = await db.db
|
|
660
|
+
.select()
|
|
661
|
+
.from(connectorCooldownState)
|
|
662
|
+
.where(and(eq(connectorCooldownState.platformId, platformId), eq(connectorCooldownState.capabilityId, capabilityId)))
|
|
663
|
+
.orderBy(desc(connectorCooldownState.updatedAt))
|
|
664
|
+
.limit(1);
|
|
665
|
+
return { row: rows[0] };
|
|
666
|
+
}
|
|
667
|
+
catch {
|
|
668
|
+
return {
|
|
669
|
+
degraded: makeDegraded("state_unreadable", "ingestion", `Check state database connectivity for cooldown ${platformId}:${capabilityId}`),
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
export async function writeConnectorCooldownState(db, row) {
|
|
674
|
+
const validated = validateSourceRefs(row.sourceRefs, "ingestion");
|
|
675
|
+
if (!validated.ok)
|
|
676
|
+
return validated.degraded;
|
|
677
|
+
try {
|
|
678
|
+
const record = {
|
|
679
|
+
...row,
|
|
680
|
+
sourceRefsJson: serializeSourceRefs(validated.record),
|
|
681
|
+
};
|
|
682
|
+
await db.db.delete(connectorCooldownState).where(eq(connectorCooldownState.id, row.id));
|
|
683
|
+
await db.db.insert(connectorCooldownState).values(record);
|
|
684
|
+
return { id: row.id };
|
|
685
|
+
}
|
|
686
|
+
catch {
|
|
687
|
+
return makeDegraded("state_unreadable", "ingestion", "Retry connector cooldown state write after DB recovery", validated.record);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
// ───────────────────────────────────────────────────────────────
|
|
487
691
|
// SourceRef round-trip helper (for tests and consumers)
|
|
488
692
|
// ───────────────────────────────────────────────────────────────
|
|
489
693
|
export function extractSourceRefs(row) {
|