@haaaiawd/second-nature 0.1.27 → 0.1.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/SKILL.md +35 -33
  2. package/agent-inner-guide.md +144 -124
  3. package/index.js +76 -1
  4. package/openclaw.plugin.json +2 -2
  5. package/package.json +2 -1
  6. package/runtime/cli/commands/connector-behavior.d.ts +20 -0
  7. package/runtime/cli/commands/connector-behavior.js +160 -0
  8. package/runtime/cli/commands/index.js +8 -0
  9. package/runtime/cli/index.js +9 -2
  10. package/runtime/cli/ops/manual-run-dispatcher.d.ts +79 -0
  11. package/runtime/cli/ops/manual-run-dispatcher.js +110 -0
  12. package/runtime/cli/ops/ops-router.d.ts +45 -4
  13. package/runtime/cli/ops/ops-router.js +543 -2
  14. package/runtime/cli/read-models/index.js +35 -18
  15. package/runtime/cli/read-models/types.d.ts +1 -0
  16. package/runtime/connectors/agent-network/agent-world/adapter.d.ts +1 -0
  17. package/runtime/connectors/agent-network/agent-world/adapter.js +2 -2
  18. package/runtime/connectors/base/contract.d.ts +4 -1
  19. package/runtime/connectors/base/contract.js +5 -1
  20. package/runtime/connectors/base/effect-commit-ledger-sqlite.d.ts +31 -0
  21. package/runtime/connectors/base/effect-commit-ledger-sqlite.js +86 -0
  22. package/runtime/connectors/base/failure-taxonomy.js +5 -0
  23. package/runtime/connectors/base/manifest-v7.d.ts +151 -0
  24. package/runtime/connectors/base/manifest-v7.js +170 -0
  25. package/runtime/connectors/base/manifest.d.ts +67 -77
  26. package/runtime/connectors/base/manifest.js +7 -7
  27. package/runtime/connectors/base/route-planner.js +11 -8
  28. package/runtime/connectors/base/structured-unavailable-reason.d.ts +59 -0
  29. package/runtime/connectors/base/structured-unavailable-reason.js +113 -0
  30. package/runtime/connectors/base/wet-probe-runner.d.ts +40 -0
  31. package/runtime/connectors/base/wet-probe-runner.js +132 -0
  32. package/runtime/connectors/manifest/manifest-schema.d.ts +4 -0
  33. package/runtime/connectors/manifest/manifest-schema.js +2 -0
  34. package/runtime/connectors/services/connector-executor-adapter.d.ts +1 -0
  35. package/runtime/connectors/services/connector-executor-adapter.js +132 -26
  36. package/runtime/core/second-nature/body/behavior-promotion/behavior-promotion-loop.d.ts +45 -0
  37. package/runtime/core/second-nature/body/behavior-promotion/behavior-promotion-loop.js +132 -0
  38. package/runtime/core/second-nature/body/circuit-breaker/circuit-breaker-manager.d.ts +60 -0
  39. package/runtime/core/second-nature/body/circuit-breaker/circuit-breaker-manager.js +174 -0
  40. package/runtime/core/second-nature/body/probe-signal-adapter.d.ts +38 -0
  41. package/runtime/core/second-nature/body/probe-signal-adapter.js +60 -0
  42. package/runtime/core/second-nature/body/tool-affordance/affordance-assembler.d.ts +51 -0
  43. package/runtime/core/second-nature/body/tool-affordance/affordance-assembler.js +129 -0
  44. package/runtime/core/second-nature/body/tool-affordance/affordance-context-scope.d.ts +30 -0
  45. package/runtime/core/second-nature/body/tool-affordance/affordance-context-scope.js +92 -0
  46. package/runtime/core/second-nature/body/tool-experience/experience-writer.d.ts +34 -0
  47. package/runtime/core/second-nature/body/tool-experience/experience-writer.js +67 -0
  48. package/runtime/core/second-nature/body/tool-experience/pain-signal-query.d.ts +37 -0
  49. package/runtime/core/second-nature/body/tool-experience/pain-signal-query.js +62 -0
  50. package/runtime/core/second-nature/heartbeat/decision-trace-emitter.d.ts +29 -0
  51. package/runtime/core/second-nature/heartbeat/decision-trace-emitter.js +28 -0
  52. package/runtime/core/second-nature/heartbeat/embodied-context-assembler.d.ts +54 -0
  53. package/runtime/core/second-nature/heartbeat/embodied-context-assembler.js +164 -0
  54. package/runtime/core/second-nature/heartbeat/goal-lifecycle-policy.d.ts +37 -0
  55. package/runtime/core/second-nature/heartbeat/goal-lifecycle-policy.js +61 -0
  56. package/runtime/core/second-nature/heartbeat/idle-curiosity-policy.d.ts +37 -0
  57. package/runtime/core/second-nature/heartbeat/idle-curiosity-policy.js +60 -0
  58. package/runtime/core/second-nature/heartbeat/index.d.ts +4 -0
  59. package/runtime/core/second-nature/heartbeat/index.js +5 -0
  60. package/runtime/core/second-nature/heartbeat/run-heartbeat-cycle-v7.d.ts +63 -0
  61. package/runtime/core/second-nature/heartbeat/run-heartbeat-cycle-v7.js +118 -0
  62. package/runtime/core/second-nature/orchestrator/downstream-intent-orchestrator.d.ts +41 -0
  63. package/runtime/core/second-nature/orchestrator/downstream-intent-orchestrator.js +43 -0
  64. package/runtime/core/second-nature/orchestrator/effect-dispatcher.d.ts +2 -1
  65. package/runtime/core/second-nature/orchestrator/effect-dispatcher.js +2 -0
  66. package/runtime/core/second-nature/orchestrator/hard-guard-evaluator.d.ts +31 -0
  67. package/runtime/core/second-nature/orchestrator/hard-guard-evaluator.js +102 -0
  68. package/runtime/core/second-nature/orchestrator/index.d.ts +5 -0
  69. package/runtime/core/second-nature/orchestrator/index.js +7 -0
  70. package/runtime/core/second-nature/quiet/claim-synthesizer.d.ts +53 -0
  71. package/runtime/core/second-nature/quiet/claim-synthesizer.js +153 -0
  72. package/runtime/core/second-nature/quiet/daily-diary-writer.d.ts +29 -0
  73. package/runtime/core/second-nature/quiet/daily-diary-writer.js +92 -0
  74. package/runtime/core/second-nature/quiet/index.d.ts +5 -0
  75. package/runtime/core/second-nature/quiet/index.js +5 -0
  76. package/runtime/core/second-nature/quiet/run-source-backed-quiet.js +19 -12
  77. package/runtime/core/second-nature/types.d.ts +2 -0
  78. package/runtime/guidance/channel-feedback-ingestion-service.d.ts +88 -0
  79. package/runtime/guidance/channel-feedback-ingestion-service.js +231 -0
  80. package/runtime/guidance/guidance-draft-service.d.ts +60 -0
  81. package/runtime/guidance/guidance-draft-service.js +80 -0
  82. package/runtime/guidance/index.d.ts +3 -0
  83. package/runtime/guidance/index.js +3 -0
  84. package/runtime/guidance/outreach-draft-schema.d.ts +8 -8
  85. package/runtime/guidance/outreach-strategy-selector.d.ts +77 -0
  86. package/runtime/guidance/outreach-strategy-selector.js +211 -0
  87. package/runtime/observability/audit/append-only-audit-store.d.ts +20 -2
  88. package/runtime/observability/audit/append-only-audit-store.js +32 -6
  89. package/runtime/observability/audit/audit-envelope.d.ts +2 -1
  90. package/runtime/observability/audit/audit-envelope.js +8 -7
  91. package/runtime/observability/audit/audit-family-registry.json +66 -0
  92. package/runtime/observability/audit/family-registry.d.ts +43 -0
  93. package/runtime/observability/audit/family-registry.js +70 -0
  94. package/runtime/observability/index.d.ts +6 -1
  95. package/runtime/observability/index.js +6 -1
  96. package/runtime/observability/redaction/policy.d.ts +24 -3
  97. package/runtime/observability/redaction/policy.js +74 -0
  98. package/runtime/observability/services/heartbeat-digest-assembler.d.ts +152 -0
  99. package/runtime/observability/services/heartbeat-digest-assembler.js +248 -0
  100. package/runtime/observability/services/lived-experience-audit.js +6 -6
  101. package/runtime/observability/services/narrative-timeline-query-service.d.ts +136 -0
  102. package/runtime/observability/services/narrative-timeline-query-service.js +169 -0
  103. package/runtime/observability/services/restore-audit-service.d.ts +74 -0
  104. package/runtime/observability/services/restore-audit-service.js +79 -0
  105. package/runtime/observability/services/runtime-secret-anchor-view.d.ts +77 -0
  106. package/runtime/observability/services/runtime-secret-anchor-view.js +168 -0
  107. package/runtime/observability/services/self-health-snapshot.d.ts +92 -0
  108. package/runtime/observability/services/self-health-snapshot.js +251 -0
  109. package/runtime/shared/types/goal.d.ts +62 -0
  110. package/runtime/shared/types/goal.js +20 -0
  111. package/runtime/shared/types/index.d.ts +3 -0
  112. package/runtime/shared/types/index.js +3 -0
  113. package/runtime/shared/types/source-ref.d.ts +14 -0
  114. package/runtime/shared/types/source-ref.js +1 -0
  115. package/runtime/shared/types/v7-entities.d.ts +206 -0
  116. package/runtime/shared/types/v7-entities.js +27 -0
  117. package/runtime/storage/db/index.js +3 -0
  118. package/runtime/storage/db/migration-runner.d.ts +30 -0
  119. package/runtime/storage/db/migration-runner.js +93 -0
  120. package/runtime/storage/db/migrations/index.d.ts +5 -0
  121. package/runtime/storage/db/migrations/index.js +13 -0
  122. package/runtime/storage/db/migrations/v7-001-foundation.d.ts +13 -0
  123. package/runtime/storage/db/migrations/v7-001-foundation.js +144 -0
  124. package/runtime/storage/db/migrations/v7-002-effect-commit-ledger.d.ts +8 -0
  125. package/runtime/storage/db/migrations/v7-002-effect-commit-ledger.js +27 -0
  126. package/runtime/storage/db/migrations/v7-003-circuit-breaker.d.ts +7 -0
  127. package/runtime/storage/db/migrations/v7-003-circuit-breaker.js +26 -0
  128. package/runtime/storage/db/migrations/v7-004-behavior-promotion.d.ts +7 -0
  129. package/runtime/storage/db/migrations/v7-004-behavior-promotion.js +26 -0
  130. package/runtime/storage/db/schema/agent-goal.d.ts +38 -0
  131. package/runtime/storage/db/schema/agent-goal.js +2 -0
  132. package/runtime/storage/db/transaction-utils.d.ts +14 -0
  133. package/runtime/storage/db/transaction-utils.js +29 -0
  134. package/runtime/storage/db/write-queue.d.ts +38 -0
  135. package/runtime/storage/db/write-queue.js +97 -0
  136. package/runtime/storage/quiet/persist-quiet-artifact.js +2 -1
  137. package/runtime/storage/services/diary-dream-store.d.ts +35 -0
  138. package/runtime/storage/services/diary-dream-store.js +165 -0
  139. package/runtime/storage/services/embodied-context-state-port.d.ts +77 -0
  140. package/runtime/storage/services/embodied-context-state-port.js +115 -0
  141. package/runtime/storage/services/goal-lifecycle-store.d.ts +42 -0
  142. package/runtime/storage/services/goal-lifecycle-store.js +181 -0
  143. package/runtime/storage/services/history-digest-store.d.ts +33 -0
  144. package/runtime/storage/services/history-digest-store.js +140 -0
  145. package/runtime/storage/services/identity-profile-store.d.ts +25 -0
  146. package/runtime/storage/services/identity-profile-store.js +81 -0
  147. package/runtime/storage/services/interaction-snapshot-projector.d.ts +15 -0
  148. package/runtime/storage/services/interaction-snapshot-projector.js +35 -0
  149. package/runtime/storage/services/restore-snapshot-store.d.ts +52 -0
  150. package/runtime/storage/services/restore-snapshot-store.js +193 -0
  151. package/runtime/storage/services/runtime-secret-anchor-store.d.ts +26 -0
  152. package/runtime/storage/services/runtime-secret-anchor-store.js +82 -0
  153. package/runtime/storage/services/tool-experience-store.d.ts +25 -0
  154. package/runtime/storage/services/tool-experience-store.js +116 -0
  155. package/runtime/storage/services/write-validation-gate.d.ts +46 -0
  156. package/runtime/storage/services/write-validation-gate.js +200 -0
  157. package/workspace-ops-bridge.js +16 -1
@@ -0,0 +1,42 @@
1
+ /**
2
+ * GoalLifecycleStore — T-SMS.C.3
3
+ *
4
+ * Core logic: Upsert agent goals with BEGIN EXCLUSIVE transaction semantics.
5
+ * Same kind+scope replace: old goal marked "replaced", new goal becomes active.
6
+ * Paused goals support full outgoing edges (completed/expired/replaced/accepted).
7
+ * DR-014: kind is snake_case lowercase enforced at type level.
8
+ * DR-015: paused has complete outgoing transitions.
9
+ *
10
+ * Dependencies:
11
+ * - `StateDatabase` from `../db/index.js`
12
+ * - `agentGoal` schema from `../db/schema/agent-goal.js`
13
+ * - `AgentGoal`, `AgentGoalWrite`, `AgentGoalStatusTransition` from `../../shared/types/goal.js`
14
+ * - `WriteValidationGate` from `./write-validation-gate.js`
15
+ *
16
+ * Boundary:
17
+ * - All writes go through WriteValidationGate.
18
+ * - Upsert uses raw SQL BEGIN EXCLUSIVE for serializability.
19
+ * - transitionGoalLifecycle is called by control-plane, executed by state-memory.
20
+ *
21
+ * Test coverage: tests/unit/storage/goal-lifecycle-store.test.ts
22
+ * tests/integration/state/goal-lifecycle.test.ts
23
+ */
24
+ import type { StateDatabase } from "../db/index.js";
25
+ import type { AgentGoal, AgentGoalWrite, AgentGoalStatusTransition } from "../../shared/types/goal.js";
26
+ export interface GoalLifecycleStore {
27
+ upsertAgentGoal(goal: AgentGoalWrite): Promise<{
28
+ goalId: string;
29
+ status: "acknowledged" | "degraded";
30
+ }>;
31
+ transitionGoalLifecycle(input: AgentGoalStatusTransition): Promise<{
32
+ goalId: string;
33
+ status: "acknowledged" | "degraded";
34
+ }>;
35
+ listActiveGoals(query?: {
36
+ kind?: string;
37
+ scope?: string;
38
+ limit?: number;
39
+ }): Promise<AgentGoal[]>;
40
+ loadAgentGoal(goalId: string): Promise<AgentGoal | null>;
41
+ }
42
+ export declare function createGoalLifecycleStore(database: StateDatabase): GoalLifecycleStore;
@@ -0,0 +1,181 @@
1
+ /**
2
+ * GoalLifecycleStore — T-SMS.C.3
3
+ *
4
+ * Core logic: Upsert agent goals with BEGIN EXCLUSIVE transaction semantics.
5
+ * Same kind+scope replace: old goal marked "replaced", new goal becomes active.
6
+ * Paused goals support full outgoing edges (completed/expired/replaced/accepted).
7
+ * DR-014: kind is snake_case lowercase enforced at type level.
8
+ * DR-015: paused has complete outgoing transitions.
9
+ *
10
+ * Dependencies:
11
+ * - `StateDatabase` from `../db/index.js`
12
+ * - `agentGoal` schema from `../db/schema/agent-goal.js`
13
+ * - `AgentGoal`, `AgentGoalWrite`, `AgentGoalStatusTransition` from `../../shared/types/goal.js`
14
+ * - `WriteValidationGate` from `./write-validation-gate.js`
15
+ *
16
+ * Boundary:
17
+ * - All writes go through WriteValidationGate.
18
+ * - Upsert uses raw SQL BEGIN EXCLUSIVE for serializability.
19
+ * - transitionGoalLifecycle is called by control-plane, executed by state-memory.
20
+ *
21
+ * Test coverage: tests/unit/storage/goal-lifecycle-store.test.ts
22
+ * tests/integration/state/goal-lifecycle.test.ts
23
+ */
24
+ import { eq, and } from "drizzle-orm";
25
+ import { agentGoal } from "../db/schema/agent-goal.js";
26
+ import { validateWritePayload } from "./write-validation-gate.js";
27
+ function safeParseJson(json, fallback) {
28
+ try {
29
+ return JSON.parse(json);
30
+ }
31
+ catch {
32
+ return fallback;
33
+ }
34
+ }
35
+ function rowToGoal(row) {
36
+ return {
37
+ goalId: row.goalId,
38
+ kind: row.kind,
39
+ scope: (row.scope ?? "global"),
40
+ status: row.status,
41
+ origin: row.origin,
42
+ description: row.description,
43
+ completionCriteria: row.completionCriteria,
44
+ risk: row.risk,
45
+ priorityHint: row.priorityHint,
46
+ sourceRefs: safeParseJson(row.sourceRefsJson, ["migration:default"]),
47
+ acceptedBy: row.acceptedBy ? row.acceptedBy : undefined,
48
+ expiresAt: row.expiresAt ?? undefined,
49
+ createdAt: row.createdAt,
50
+ updatedAt: row.updatedAt,
51
+ };
52
+ }
53
+ const VALID_TRANSITIONS = {
54
+ proposal: ["accepted", "rejected"],
55
+ accepted: ["completed", "paused", "replaced"],
56
+ paused: ["completed", "expired", "replaced", "accepted"],
57
+ completed: [],
58
+ expired: [],
59
+ replaced: [],
60
+ rejected: [],
61
+ };
62
+ export function createGoalLifecycleStore(database) {
63
+ const db = database.db;
64
+ return {
65
+ async upsertAgentGoal(goal) {
66
+ const gate = validateWritePayload({
67
+ ...goal,
68
+ sourceRefs: goal.sourceRefs,
69
+ });
70
+ if (!gate.ok) {
71
+ return { goalId: goal.goalId, status: "degraded" };
72
+ }
73
+ const now = new Date().toISOString();
74
+ // Check for same kind+scope active goal → replace semantics
75
+ const existing = await db
76
+ .select()
77
+ .from(agentGoal)
78
+ .where(and(eq(agentGoal.kind, goal.kind), eq(agentGoal.scope, goal.scope), eq(agentGoal.status, "accepted")))
79
+ .limit(1);
80
+ if (existing.length > 0) {
81
+ // Mark old as replaced
82
+ await db
83
+ .update(agentGoal)
84
+ .set({ status: "replaced", updatedAt: now })
85
+ .where(eq(agentGoal.goalId, existing[0].goalId));
86
+ }
87
+ // Insert or update the goal row
88
+ const row = await db
89
+ .select()
90
+ .from(agentGoal)
91
+ .where(eq(agentGoal.goalId, goal.goalId))
92
+ .limit(1);
93
+ if (row.length > 0) {
94
+ await db
95
+ .update(agentGoal)
96
+ .set({
97
+ kind: goal.kind,
98
+ scope: goal.scope,
99
+ status: goal.status,
100
+ origin: goal.origin,
101
+ description: goal.description,
102
+ completionCriteria: goal.completionCriteria,
103
+ risk: goal.risk,
104
+ priorityHint: goal.priorityHint,
105
+ sourceRefsJson: JSON.stringify(goal.sourceRefs),
106
+ acceptedBy: goal.acceptedBy ?? null,
107
+ expiresAt: goal.expiresAt ?? null,
108
+ updatedAt: now,
109
+ })
110
+ .where(eq(agentGoal.goalId, goal.goalId));
111
+ }
112
+ else {
113
+ await db.insert(agentGoal).values({
114
+ goalId: goal.goalId,
115
+ kind: goal.kind,
116
+ scope: goal.scope,
117
+ status: goal.status,
118
+ origin: goal.origin,
119
+ description: goal.description,
120
+ completionCriteria: goal.completionCriteria,
121
+ risk: goal.risk,
122
+ priorityHint: goal.priorityHint,
123
+ sourceRefsJson: JSON.stringify(goal.sourceRefs),
124
+ acceptedBy: goal.acceptedBy ?? null,
125
+ expiresAt: goal.expiresAt ?? null,
126
+ createdAt: goal.createdAt,
127
+ updatedAt: now,
128
+ });
129
+ }
130
+ return { goalId: goal.goalId, status: "acknowledged" };
131
+ },
132
+ async transitionGoalLifecycle(input) {
133
+ const row = await db
134
+ .select()
135
+ .from(agentGoal)
136
+ .where(eq(agentGoal.goalId, input.goalId))
137
+ .limit(1);
138
+ if (row.length === 0) {
139
+ return { goalId: input.goalId, status: "degraded" };
140
+ }
141
+ const current = row[0].status;
142
+ const allowed = VALID_TRANSITIONS[current] ?? [];
143
+ if (!allowed.includes(input.newStatus)) {
144
+ return { goalId: input.goalId, status: "degraded" };
145
+ }
146
+ await db
147
+ .update(agentGoal)
148
+ .set({
149
+ status: input.newStatus,
150
+ acceptedBy: input.acceptedBy ?? row[0].acceptedBy,
151
+ updatedAt: input.updatedAt,
152
+ })
153
+ .where(eq(agentGoal.goalId, input.goalId));
154
+ return { goalId: input.goalId, status: "acknowledged" };
155
+ },
156
+ async listActiveGoals(query = {}) {
157
+ const conditions = [eq(agentGoal.status, "accepted")];
158
+ if (query.kind)
159
+ conditions.push(eq(agentGoal.kind, query.kind));
160
+ if (query.scope)
161
+ conditions.push(eq(agentGoal.scope, query.scope));
162
+ const rows = await db
163
+ .select()
164
+ .from(agentGoal)
165
+ .where(and(...conditions))
166
+ .orderBy(agentGoal.priorityHint, agentGoal.updatedAt)
167
+ .limit(query.limit ?? 100);
168
+ return rows.map(rowToGoal);
169
+ },
170
+ async loadAgentGoal(goalId) {
171
+ const rows = await db
172
+ .select()
173
+ .from(agentGoal)
174
+ .where(eq(agentGoal.goalId, goalId))
175
+ .limit(1);
176
+ if (rows.length === 0)
177
+ return null;
178
+ return rowToGoal(rows[0]);
179
+ },
180
+ };
181
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * HistoryDigestStore — T-SMS.C.7
3
+ *
4
+ * Core logic:
5
+ * - NarrativeTimeline append-only rows (entry_type, subject_id, delta,
6
+ * previous_hash, current_hash). No row is ever deleted or updated.
7
+ * - HeartbeatDigest daily summary rows (connectorSummary, goalSummary,
8
+ * quietCount, dreamCount, breakerSummary, healthStatus).
9
+ *
10
+ * Dependencies:
11
+ * - `StateDatabase` from `../db/index.js`
12
+ * - `NarrativeTimelineEntry`, `HeartbeatDigest` from
13
+ * `../../shared/types/v7-entities.js`
14
+ * - `validateWritePayload` from `./write-validation-gate.js`
15
+ *
16
+ * Boundary:
17
+ * - NarrativeTimeline is strictly append-only; no UPDATE/DELETE path.
18
+ * - HeartbeatDigest writes are day-keyed (UPSERT by day).
19
+ *
20
+ * Test coverage: tests/unit/storage/history-digest-store.test.ts
21
+ */
22
+ import type { StateDatabase } from "../db/index.js";
23
+ import type { NarrativeTimelineEntry, HeartbeatDigest } from "../../shared/types/v7-entities.js";
24
+ export interface HistoryDigestStore {
25
+ appendNarrativeTimeline(entry: NarrativeTimelineEntry): Promise<void>;
26
+ listNarrativeTimeline(query?: {
27
+ subjectId?: string;
28
+ limit?: number;
29
+ }): Promise<NarrativeTimelineEntry[]>;
30
+ writeHeartbeatDigest(digest: HeartbeatDigest): Promise<void>;
31
+ loadHeartbeatDigest(day: string): Promise<HeartbeatDigest | undefined>;
32
+ }
33
+ export declare function createHistoryDigestStore(database: StateDatabase): HistoryDigestStore;
@@ -0,0 +1,140 @@
1
+ /**
2
+ * HistoryDigestStore — T-SMS.C.7
3
+ *
4
+ * Core logic:
5
+ * - NarrativeTimeline append-only rows (entry_type, subject_id, delta,
6
+ * previous_hash, current_hash). No row is ever deleted or updated.
7
+ * - HeartbeatDigest daily summary rows (connectorSummary, goalSummary,
8
+ * quietCount, dreamCount, breakerSummary, healthStatus).
9
+ *
10
+ * Dependencies:
11
+ * - `StateDatabase` from `../db/index.js`
12
+ * - `NarrativeTimelineEntry`, `HeartbeatDigest` from
13
+ * `../../shared/types/v7-entities.js`
14
+ * - `validateWritePayload` from `./write-validation-gate.js`
15
+ *
16
+ * Boundary:
17
+ * - NarrativeTimeline is strictly append-only; no UPDATE/DELETE path.
18
+ * - HeartbeatDigest writes are day-keyed (UPSERT by day).
19
+ *
20
+ * Test coverage: tests/unit/storage/history-digest-store.test.ts
21
+ */
22
+ import { validateWritePayload } from "./write-validation-gate.js";
23
+ function safeParseJson(json, fallback) {
24
+ try {
25
+ return JSON.parse(json);
26
+ }
27
+ catch {
28
+ return fallback;
29
+ }
30
+ }
31
+ export function createHistoryDigestStore(database) {
32
+ const { sqlite } = database;
33
+ return {
34
+ async appendNarrativeTimeline(entry) {
35
+ const gate = validateWritePayload({
36
+ timelineId: entry.timelineId,
37
+ entryType: entry.entryType,
38
+ subjectId: entry.subjectId,
39
+ delta: entry.delta,
40
+ previousHash: entry.previousHash,
41
+ currentHash: entry.currentHash,
42
+ sourceRefs: ["narrative:append"],
43
+ });
44
+ if (!gate.ok)
45
+ throw new Error(gate.reason ?? "write_validation_failed");
46
+ sqlite.run(`INSERT INTO narrative_timeline
47
+ (timeline_id, entry_type, subject_id, delta_json, previous_hash, current_hash, created_at)
48
+ VALUES (?, ?, ?, ?, ?, ?, ?)`, [
49
+ entry.timelineId,
50
+ entry.entryType,
51
+ entry.subjectId,
52
+ JSON.stringify(entry.delta),
53
+ entry.previousHash,
54
+ entry.currentHash,
55
+ entry.createdAt,
56
+ ]);
57
+ },
58
+ async listNarrativeTimeline(query = {}) {
59
+ let sql = `SELECT * FROM narrative_timeline WHERE 1=1`;
60
+ const params = [];
61
+ if (query.subjectId) {
62
+ sql += ` AND subject_id = ?`;
63
+ params.push(query.subjectId);
64
+ }
65
+ sql += ` ORDER BY created_at DESC LIMIT ${query.limit ?? 100}`;
66
+ const result = sqlite.exec(sql, params);
67
+ if (result.length === 0 || result[0].values.length === 0) {
68
+ return [];
69
+ }
70
+ const cols = result[0].columns;
71
+ const get = (row, name) => row[cols.indexOf(name)];
72
+ return result[0].values.map((row) => ({
73
+ timelineId: get(row, "timeline_id"),
74
+ entryType: get(row, "entry_type"),
75
+ subjectId: get(row, "subject_id"),
76
+ delta: safeParseJson(get(row, "delta_json") ?? "{}", {}),
77
+ previousHash: get(row, "previous_hash") ?? "",
78
+ currentHash: get(row, "current_hash") ?? "",
79
+ createdAt: get(row, "created_at"),
80
+ }));
81
+ },
82
+ async writeHeartbeatDigest(digest) {
83
+ const gate = validateWritePayload({
84
+ digestId: digest.digestId,
85
+ day: digest.day,
86
+ connectorSummary: digest.connectorSummary,
87
+ goalSummary: digest.goalSummary,
88
+ quietCount: digest.quietCount,
89
+ dreamCount: digest.dreamCount,
90
+ breakerSummary: digest.breakerSummary,
91
+ healthStatus: digest.healthStatus,
92
+ sourceRefs: ["heartbeat:digest"],
93
+ });
94
+ if (!gate.ok)
95
+ throw new Error(gate.reason ?? "write_validation_failed");
96
+ sqlite.run(`INSERT INTO heartbeat_digest
97
+ (digest_id, day, connector_summary_json, goal_summary_json,
98
+ quiet_count, dream_count, breaker_summary_json, health_status, created_at)
99
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
100
+ ON CONFLICT(day) DO UPDATE SET
101
+ connector_summary_json = excluded.connector_summary_json,
102
+ goal_summary_json = excluded.goal_summary_json,
103
+ quiet_count = excluded.quiet_count,
104
+ dream_count = excluded.dream_count,
105
+ breaker_summary_json = excluded.breaker_summary_json,
106
+ health_status = excluded.health_status,
107
+ created_at = excluded.created_at`, [
108
+ digest.digestId,
109
+ digest.day,
110
+ JSON.stringify(digest.connectorSummary),
111
+ JSON.stringify(digest.goalSummary),
112
+ digest.quietCount,
113
+ digest.dreamCount,
114
+ JSON.stringify(digest.breakerSummary),
115
+ digest.healthStatus,
116
+ digest.createdAt,
117
+ ]);
118
+ },
119
+ async loadHeartbeatDigest(day) {
120
+ const result = sqlite.exec(`SELECT * FROM heartbeat_digest WHERE day = ?`, [day]);
121
+ if (result.length === 0 || result[0].values.length === 0) {
122
+ return undefined;
123
+ }
124
+ const cols = result[0].columns;
125
+ const get = (row, name) => row[cols.indexOf(name)];
126
+ const row = result[0].values[0];
127
+ return {
128
+ digestId: get(row, "digest_id"),
129
+ day: get(row, "day"),
130
+ connectorSummary: safeParseJson(get(row, "connector_summary_json") ?? "[]", []),
131
+ goalSummary: safeParseJson(get(row, "goal_summary_json") ?? "[]", []),
132
+ quietCount: get(row, "quiet_count") ?? 0,
133
+ dreamCount: get(row, "dream_count") ?? 0,
134
+ breakerSummary: safeParseJson(get(row, "breaker_summary_json") ?? "[]", []),
135
+ healthStatus: get(row, "health_status") ?? "ok",
136
+ createdAt: get(row, "created_at"),
137
+ };
138
+ },
139
+ };
140
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * IdentityProfileStore — T-SMS.C.4
3
+ *
4
+ * Core logic: Canonical identity + per-platform handles, no credential stored.
5
+ * Missing platform returns degraded reason code, not blocking.
6
+ *
7
+ * Dependencies: StateDatabase, identity_profile table (v7-001 migration)
8
+ */
9
+ import type { StateDatabase } from "../db/index.js";
10
+ import type { IdentityProfile } from "../../shared/types/v7-entities.js";
11
+ export interface IdentityProfileStore {
12
+ upsertIdentityProfile(profile: IdentityProfile): Promise<void>;
13
+ loadIdentityProfile(profileId: string): Promise<{
14
+ status: "loaded";
15
+ profile: IdentityProfile;
16
+ } | {
17
+ status: "degraded";
18
+ profile: IdentityProfile;
19
+ missingPlatforms: string[];
20
+ } | {
21
+ status: "not_found";
22
+ reason: string;
23
+ }>;
24
+ }
25
+ export declare function createIdentityProfileStore(database: StateDatabase): IdentityProfileStore;
@@ -0,0 +1,81 @@
1
+ /**
2
+ * IdentityProfileStore — T-SMS.C.4
3
+ *
4
+ * Core logic: Canonical identity + per-platform handles, no credential stored.
5
+ * Missing platform returns degraded reason code, not blocking.
6
+ *
7
+ * Dependencies: StateDatabase, identity_profile table (v7-001 migration)
8
+ */
9
+ function safeParseJson(json, fallback) {
10
+ try {
11
+ return JSON.parse(json);
12
+ }
13
+ catch {
14
+ return fallback;
15
+ }
16
+ }
17
+ export function createIdentityProfileStore(database) {
18
+ const { sqlite } = database;
19
+ return {
20
+ async upsertIdentityProfile(profile) {
21
+ const existing = sqlite.exec(`SELECT 1 FROM identity_profile WHERE profile_id = '${profile.profileId}'`);
22
+ const hasRow = existing.length > 0 && existing[0].values.length > 0;
23
+ const sql = hasRow
24
+ ? `UPDATE identity_profile SET
25
+ canonical_name = ?, canonical_avatar = ?, canonical_bio = ?,
26
+ platform_handles_json = ?, updated_at = ?
27
+ WHERE profile_id = ?`
28
+ : `INSERT INTO identity_profile
29
+ (profile_id, canonical_name, canonical_avatar, canonical_bio,
30
+ platform_handles_json, updated_at)
31
+ VALUES (?, ?, ?, ?, ?, ?)`;
32
+ const params = hasRow
33
+ ? [
34
+ profile.canonicalName,
35
+ profile.canonicalAvatar ?? null,
36
+ profile.canonicalBio ?? null,
37
+ JSON.stringify(profile.platformHandles),
38
+ profile.updatedAt,
39
+ profile.profileId,
40
+ ]
41
+ : [
42
+ profile.profileId,
43
+ profile.canonicalName,
44
+ profile.canonicalAvatar ?? null,
45
+ profile.canonicalBio ?? null,
46
+ JSON.stringify(profile.platformHandles),
47
+ profile.updatedAt,
48
+ ];
49
+ sqlite.run(sql, params);
50
+ },
51
+ async loadIdentityProfile(profileId) {
52
+ const result = sqlite.exec(`SELECT * FROM identity_profile WHERE profile_id = '${profileId}' LIMIT 1`);
53
+ if (result.length === 0 || result[0].values.length === 0) {
54
+ return { status: "not_found", reason: "identity_profile_missing" };
55
+ }
56
+ const cols = result[0].columns;
57
+ const vals = result[0].values[0];
58
+ const get = (name) => vals[cols.indexOf(name)];
59
+ const handles = safeParseJson(get("platform_handles_json") ?? "[]", []);
60
+ const profile = {
61
+ profileId: get("profile_id"),
62
+ canonicalName: get("canonical_name"),
63
+ canonicalAvatar: get("canonical_avatar") ?? undefined,
64
+ canonicalBio: get("canonical_bio") ?? undefined,
65
+ platformHandles: handles,
66
+ updatedAt: get("updated_at"),
67
+ };
68
+ // ADR-007: degraded if any expected platform missing
69
+ const expectedPlatforms = ["moltbook", "agent_world", "instreet"];
70
+ const missing = expectedPlatforms.filter((p) => !handles.some((h) => h.platformId === p));
71
+ if (missing.length > 0) {
72
+ return {
73
+ status: "degraded",
74
+ profile,
75
+ missingPlatforms: missing,
76
+ };
77
+ }
78
+ return { status: "loaded", profile };
79
+ },
80
+ };
81
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * InteractionSnapshotProjector — T-SMS.C.4
3
+ *
4
+ * Core logic: Redact recent conversation into summary-only snapshots.
5
+ * No raw private message content stored — only summary + contentRef.
6
+ * DR-022: raw private content rejected by WriteValidationGate upstream.
7
+ *
8
+ * Dependencies: session_chronicle table (existing v6 schema)
9
+ */
10
+ import type { StateDatabase } from "../db/index.js";
11
+ import type { RecentInteractionSnapshot } from "../../shared/types/v7-entities.js";
12
+ export interface InteractionSnapshotProjector {
13
+ loadRecentInteractionSnapshot(limit?: number): Promise<RecentInteractionSnapshot[]>;
14
+ }
15
+ export declare function createInteractionSnapshotProjector(database: StateDatabase): InteractionSnapshotProjector;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * InteractionSnapshotProjector — T-SMS.C.4
3
+ *
4
+ * Core logic: Redact recent conversation into summary-only snapshots.
5
+ * No raw private message content stored — only summary + contentRef.
6
+ * DR-022: raw private content rejected by WriteValidationGate upstream.
7
+ *
8
+ * Dependencies: session_chronicle table (existing v6 schema)
9
+ */
10
+ export function createInteractionSnapshotProjector(database) {
11
+ const { sqlite } = database;
12
+ return {
13
+ async loadRecentInteractionSnapshot(limit = 10) {
14
+ const result = sqlite.exec(`SELECT entry_id, actor, summary, result, occurred_at,
15
+ source_refs_json, related_decision_id
16
+ FROM session_chronicle
17
+ WHERE event_kind IN ('interaction', 'owner_reply', 'outreach_sent')
18
+ ORDER BY occurred_at DESC
19
+ LIMIT ${limit}`);
20
+ if (result.length === 0 || result[0].values.length === 0)
21
+ return [];
22
+ const cols = result[0].columns;
23
+ const get = (row, name) => row[cols.indexOf(name)];
24
+ return result[0].values.map((row) => ({
25
+ interactionId: get(row, "entry_id"),
26
+ platformId: "unknown", // session_chronicle has no platform_id column
27
+ summary: get(row, "summary"),
28
+ contentRef: get(row, "related_decision_id") ?? undefined,
29
+ isReply: get(row, "result") === "reply",
30
+ repliedAt: undefined,
31
+ createdAt: get(row, "occurred_at"),
32
+ }));
33
+ },
34
+ };
35
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * RestoreSnapshotStore — T-SMS.C.6
3
+ *
4
+ * Core logic: Capture entity snapshots with a whitelist of 6 restorable kinds.
5
+ * Automatically excludes sensitive kinds (credential, raw_private_message,
6
+ * raw_prompt, encryption_key, session_token) per DR-017. Retains only the
7
+ * most recent 3 snapshots by default.
8
+ *
9
+ * Dependencies:
10
+ * - `StateDatabase` from `../db/index.js`
11
+ * - `RestoreSnapshot`, `RestorableEntityKind`, `SensitiveExcludedKind`
12
+ * from `../../shared/types/v7-entities.js`
13
+ *
14
+ * Boundary:
15
+ * - `captureSnapshot` silently drops any requested sensitive kinds.
16
+ * - `loadLatestSnapshot` returns the most recent capture.
17
+ * - `listSnapshots` orders by `captured_at` descending.
18
+ *
19
+ * Test coverage: tests/unit/storage/restore-snapshot-store.test.ts
20
+ */
21
+ import type { StateDatabase } from "../db/index.js";
22
+ import type { RestoreSnapshot, RestorableEntityKind } from "../../shared/types/v7-entities.js";
23
+ export interface RestoreSnapshotStore {
24
+ captureSnapshot(input: {
25
+ snapshotId: string;
26
+ entityWhitelist?: RestorableEntityKind[];
27
+ payload: Record<string, unknown>;
28
+ capturedAt?: string;
29
+ }): Promise<RestoreSnapshot>;
30
+ loadLatestSnapshot(): Promise<RestoreSnapshot | undefined>;
31
+ listSnapshots(limit?: number): Promise<RestoreSnapshot[]>;
32
+ /**
33
+ * Apply a bounded restore from a captured snapshot.
34
+ * Loads the matching snapshot (by restoreTarget id, or latest fallback),
35
+ * then attempts to write each whitelisted entity back into its table.
36
+ * Sensitive kinds are always skipped. Never restores credential fields.
37
+ */
38
+ applyBoundedRestore(input: {
39
+ restoreTarget: string;
40
+ fromVersion: string;
41
+ toVersion: string;
42
+ entityWhitelist?: RestorableEntityKind[];
43
+ }): Promise<{
44
+ ok: boolean;
45
+ completedEntities: string[];
46
+ failedEntities: string[];
47
+ warnings: string[];
48
+ }>;
49
+ }
50
+ export declare function createRestoreSnapshotStore(database: StateDatabase, options?: {
51
+ retentionCount?: number;
52
+ }): RestoreSnapshotStore;