@haaaiawd/second-nature 0.1.51 → 0.2.0

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 (71) hide show
  1. package/openclaw.plugin.json +29 -29
  2. package/package.json +55 -55
  3. package/runtime/cli/commands/index.js +326 -325
  4. package/runtime/cli/ops/heartbeat-surface.d.ts +84 -84
  5. package/runtime/cli/ops/heartbeat-surface.js +100 -100
  6. package/runtime/cli/ops/ops-router.js +1555 -1482
  7. package/runtime/cli/ops/workspace-heartbeat-runner.d.ts +85 -85
  8. package/runtime/cli/ops/workspace-heartbeat-runner.js +242 -242
  9. package/runtime/connectors/base/contract.d.ts +111 -111
  10. package/runtime/connectors/base/failure-taxonomy.d.ts +13 -13
  11. package/runtime/connectors/base/failure-taxonomy.js +186 -186
  12. package/runtime/connectors/base/map-life-evidence.js +137 -137
  13. package/runtime/connectors/base/policy-layer.js +202 -202
  14. package/runtime/connectors/evidence-normalizer.d.ts +45 -0
  15. package/runtime/connectors/evidence-normalizer.js +115 -0
  16. package/runtime/connectors/manifest/manifest-schema.d.ts +152 -152
  17. package/runtime/connectors/manifest/manifest-schema.js +54 -54
  18. package/runtime/connectors/services/connector-executor-adapter.d.ts +20 -20
  19. package/runtime/connectors/services/connector-executor-adapter.js +645 -645
  20. package/runtime/core/second-nature/action/action-closure-recorder.d.ts +70 -0
  21. package/runtime/core/second-nature/action/action-closure-recorder.js +184 -0
  22. package/runtime/core/second-nature/action/action-proposal-builder.d.ts +70 -0
  23. package/runtime/core/second-nature/action/action-proposal-builder.js +217 -0
  24. package/runtime/core/second-nature/action/autonomy-policy-evaluator.d.ts +43 -0
  25. package/runtime/core/second-nature/action/autonomy-policy-evaluator.js +213 -0
  26. package/runtime/core/second-nature/action/policy-bound-dispatch.d.ts +69 -0
  27. package/runtime/core/second-nature/action/policy-bound-dispatch.js +112 -0
  28. package/runtime/core/second-nature/body/tool-affordance/affordance-side-effect.d.ts +49 -0
  29. package/runtime/core/second-nature/body/tool-affordance/affordance-side-effect.js +100 -0
  30. package/runtime/core/second-nature/control-plane/accepted-projection-loader.d.ts +45 -0
  31. package/runtime/core/second-nature/control-plane/accepted-projection-loader.js +85 -0
  32. package/runtime/core/second-nature/control-plane/heartbeat-orchestrator.d.ts +38 -0
  33. package/runtime/core/second-nature/control-plane/heartbeat-orchestrator.js +165 -0
  34. package/runtime/core/second-nature/guidance/guidance-proposal-consumer.d.ts +51 -0
  35. package/runtime/core/second-nature/guidance/guidance-proposal-consumer.js +113 -0
  36. package/runtime/core/second-nature/heartbeat/goal-lifecycle-policy.d.ts +24 -24
  37. package/runtime/core/second-nature/heartbeat/goal-lifecycle-policy.js +61 -61
  38. package/runtime/core/second-nature/heartbeat/heartbeat-loop.d.ts +97 -97
  39. package/runtime/core/second-nature/heartbeat/heartbeat-loop.js +397 -397
  40. package/runtime/core/second-nature/orchestrator/platform-capability-router.js +149 -149
  41. package/runtime/core/second-nature/perception/judgment-engine.d.ts +53 -0
  42. package/runtime/core/second-nature/perception/judgment-engine.js +239 -0
  43. package/runtime/core/second-nature/perception/perception-builder.d.ts +62 -0
  44. package/runtime/core/second-nature/perception/perception-builder.js +208 -0
  45. package/runtime/core/second-nature/perception/sensitivity-classifier.d.ts +37 -0
  46. package/runtime/core/second-nature/perception/sensitivity-classifier.js +87 -0
  47. package/runtime/core/second-nature/quiet-dream/dream-consolidation-runner.d.ts +44 -0
  48. package/runtime/core/second-nature/quiet-dream/dream-consolidation-runner.js +180 -0
  49. package/runtime/core/second-nature/quiet-dream/dream-scheduler.d.ts +36 -0
  50. package/runtime/core/second-nature/quiet-dream/dream-scheduler.js +105 -0
  51. package/runtime/core/second-nature/quiet-dream/memory-projection-lifecycle.d.ts +36 -0
  52. package/runtime/core/second-nature/quiet-dream/memory-projection-lifecycle.js +151 -0
  53. package/runtime/core/second-nature/quiet-dream/quiet-daily-review-builder.d.ts +46 -0
  54. package/runtime/core/second-nature/quiet-dream/quiet-daily-review-builder.js +123 -0
  55. package/runtime/observability/causal-loop-health.d.ts +44 -0
  56. package/runtime/observability/causal-loop-health.js +118 -0
  57. package/runtime/observability/diagnostic-redaction.d.ts +43 -0
  58. package/runtime/observability/diagnostic-redaction.js +114 -0
  59. package/runtime/observability/loop-stage-event-sink.d.ts +43 -0
  60. package/runtime/observability/loop-stage-event-sink.js +148 -0
  61. package/runtime/observability/loop-status.d.ts +46 -0
  62. package/runtime/observability/loop-status.js +85 -0
  63. package/runtime/shared/types/index.js +3 -0
  64. package/runtime/shared/types/v8-contracts.d.ts +86 -0
  65. package/runtime/shared/types/v8-contracts.js +84 -0
  66. package/runtime/storage/db/schema/index.d.ts +1 -0
  67. package/runtime/storage/db/schema/index.js +1 -0
  68. package/runtime/storage/db/schema/v8-entities.d.ts +1973 -0
  69. package/runtime/storage/db/schema/v8-entities.js +160 -0
  70. package/runtime/storage/v8-state-stores.d.ts +147 -0
  71. package/runtime/storage/v8-state-stores.js +491 -0
@@ -0,0 +1,208 @@
1
+ /**
2
+ * PerceptionBuilder — Generate PerceptionCard records from EvidenceItem batches.
3
+ *
4
+ * Core logic: Read pending evidence, deduplicate by content hash, build
5
+ * PerceptionCard with topic, entities, novelty, relevance, summary, risk
6
+ * flags, confidence, and reviewPriority. Rules-only fallback when model
7
+ * assist is unavailable.
8
+ *
9
+ * Design authority:
10
+ * - `.anws/v8/04_SYSTEM_DESIGN/perception-judgment-system.detail.md §3.1`
11
+ * - `.anws/v8/04_SYSTEM_DESIGN/perception-judgment-system.md §5`
12
+ *
13
+ * Dependencies:
14
+ * - `src/storage/v8-state-stores.js` (readEvidenceItemsByStatus, writePerceptionCard)
15
+ * - `src/shared/types/v8-contracts.js` (PerceptionCard fields)
16
+ *
17
+ * Boundary:
18
+ * - Does not judge actionability; that is judgment's job.
19
+ * - Does not fabricate perception on empty input.
20
+ * - Rules-only path is deterministic and source-backed.
21
+ *
22
+ * Test coverage: tests/unit/perception/perception-builder.test.ts
23
+ */
24
+ import { readEvidenceItemsByStatus, writePerceptionCard, } from "../../../storage/v8-state-stores.js";
25
+ // ───────────────────────────────────────────────────────────────
26
+ // Config
27
+ // ───────────────────────────────────────────────────────────────
28
+ const PERCEPTION_MAX_EVIDENCE_PER_CYCLE = 50;
29
+ // ───────────────────────────────────────────────────────────────
30
+ // Helpers
31
+ // ───────────────────────────────────────────────────────────────
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
+ function extractTopic(evidence) {
42
+ if (evidence.payloadJson) {
43
+ try {
44
+ const payload = JSON.parse(evidence.payloadJson);
45
+ if (payload.topic)
46
+ return String(payload.topic);
47
+ if (payload.title)
48
+ return String(payload.title);
49
+ if (payload.subject)
50
+ return String(payload.subject);
51
+ }
52
+ catch {
53
+ /* ignore */
54
+ }
55
+ }
56
+ return `${evidence.platformId}_observation`;
57
+ }
58
+ function extractEntities(evidence) {
59
+ const entities = [evidence.platformId];
60
+ if (evidence.payloadJson) {
61
+ try {
62
+ const payload = JSON.parse(evidence.payloadJson);
63
+ if (payload.entities && Array.isArray(payload.entities)) {
64
+ entities.push(...payload.entities.map(String));
65
+ }
66
+ if (payload.tags && Array.isArray(payload.tags)) {
67
+ entities.push(...payload.tags.map(String));
68
+ }
69
+ if (payload.mentions && Array.isArray(payload.mentions)) {
70
+ entities.push(...payload.mentions.map(String));
71
+ }
72
+ }
73
+ catch {
74
+ /* ignore */
75
+ }
76
+ }
77
+ return [...new Set(entities)];
78
+ }
79
+ function inferNovelty(evidence) {
80
+ if (evidence.sensitivityHint === "public_technical")
81
+ return "recurring";
82
+ return "new";
83
+ }
84
+ function inferRelevance(evidence) {
85
+ if (evidence.sensitivityHint === "sensitive")
86
+ return 0.9;
87
+ if (evidence.sensitivityHint === "public_technical")
88
+ return 0.7;
89
+ if (evidence.sensitivityHint === "private_context")
90
+ return 0.5;
91
+ return 0.3;
92
+ }
93
+ function inferSummary(evidence) {
94
+ const platform = evidence.platformId;
95
+ const topic = extractTopic(evidence);
96
+ return `Observation from ${platform}: ${topic}`;
97
+ }
98
+ function inferPossibleIntents(evidence) {
99
+ const intents = ["watch"];
100
+ if (evidence.sensitivityHint !== "sensitive") {
101
+ intents.push("remember");
102
+ }
103
+ if (evidence.sensitivityHint === "public_technical") {
104
+ intents.push("notify_owner");
105
+ }
106
+ return intents;
107
+ }
108
+ function inferReviewPriority(evidence) {
109
+ if (evidence.sensitivityHint === "sensitive")
110
+ return "high";
111
+ if (evidence.sensitivityHint === "public_technical")
112
+ return "medium";
113
+ return "low";
114
+ }
115
+ function inferRiskFlags(evidence) {
116
+ const flags = [];
117
+ if (evidence.sensitivityHint === "sensitive") {
118
+ flags.push("credential_shape_detected");
119
+ }
120
+ if (evidence.sensitivityHint === "private_context") {
121
+ flags.push("private_context");
122
+ }
123
+ return flags;
124
+ }
125
+ function buildCardFromEvidence(evidence, cycleId, now) {
126
+ const sourceRefs = parseSourceRefs(evidence.sourceRefsJson);
127
+ return {
128
+ id: `per_${evidence.id}`,
129
+ cycleId,
130
+ topic: extractTopic(evidence),
131
+ entities: extractEntities(evidence),
132
+ novelty: inferNovelty(evidence),
133
+ relevance: inferRelevance(evidence),
134
+ summary: inferSummary(evidence),
135
+ possibleIntents: inferPossibleIntents(evidence),
136
+ reviewPriority: inferReviewPriority(evidence),
137
+ sensitivityClass: evidence.sensitivityHint || "public_general",
138
+ riskFlags: inferRiskFlags(evidence),
139
+ confidence: 0.6,
140
+ evidenceRefs: sourceRefs,
141
+ createdAt: now,
142
+ };
143
+ }
144
+ export async function buildPerceptionCards(db, options) {
145
+ const now = options.now ?? new Date().toISOString();
146
+ const maxEvidence = options.maxEvidence ?? PERCEPTION_MAX_EVIDENCE_PER_CYCLE;
147
+ const readResult = await readEvidenceItemsByStatus(db, "pending");
148
+ if (readResult.degraded) {
149
+ return readResult.degraded;
150
+ }
151
+ const evidenceItems = readResult.rows;
152
+ if (evidenceItems.length === 0) {
153
+ return {
154
+ status: "empty",
155
+ cards: [],
156
+ reason: "evidence_batch_empty",
157
+ };
158
+ }
159
+ const truncated = evidenceItems.length > maxEvidence;
160
+ const selectedEvidence = evidenceItems.slice(0, maxEvidence);
161
+ const cards = [];
162
+ for (const evidence of selectedEvidence) {
163
+ const card = buildCardFromEvidence({
164
+ id: evidence.id,
165
+ platformId: evidence.platformId,
166
+ contentHash: evidence.contentHash,
167
+ observedAt: evidence.observedAt,
168
+ sensitivityHint: evidence.sensitivityHint ?? undefined,
169
+ sourceRefsJson: evidence.sourceRefsJson,
170
+ payloadJson: evidence.payloadJson,
171
+ }, options.cycleId, now);
172
+ cards.push(card);
173
+ // Write card to state
174
+ const writeResult = await writePerceptionCard(db, {
175
+ id: card.id,
176
+ createdAt: now,
177
+ cycleId: card.cycleId,
178
+ topic: card.topic,
179
+ entitiesJson: JSON.stringify(card.entities),
180
+ novelty: card.novelty,
181
+ relevance: card.relevance,
182
+ summary: card.summary,
183
+ riskFlagsJson: JSON.stringify(card.riskFlags),
184
+ confidence: card.confidence,
185
+ reviewPriority: card.reviewPriority,
186
+ sourceRefs: card.evidenceRefs,
187
+ redactionClass: card.sensitivityClass === "sensitive" ? "blocked" : "none",
188
+ lifecycleStatus: "pending",
189
+ payloadJson: JSON.stringify({
190
+ possibleIntents: card.possibleIntents,
191
+ sensitivityClass: card.sensitivityClass,
192
+ }),
193
+ });
194
+ if ("reason" in writeResult) {
195
+ return {
196
+ status: "degraded",
197
+ cards,
198
+ reason: writeResult.reason,
199
+ };
200
+ }
201
+ }
202
+ return {
203
+ status: "completed",
204
+ cards,
205
+ truncated,
206
+ reason: truncated ? "evidence_batch_truncated" : undefined,
207
+ };
208
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * SensitivityClassifier — v8 context-aware sensitivity classifier.
3
+ *
4
+ * Core logic: Classify evidence text as public_technical, public_general,
5
+ * private_context, or sensitive using field context, source context,
6
+ * value shape, and entropy signals.
7
+ *
8
+ * Design authority:
9
+ * - `.anws/v8/04_SYSTEM_DESIGN/perception-judgment-system.detail.md §3.2`
10
+ * - `.anws/v8/04_SYSTEM_DESIGN/perception-judgment-system.detail.md §4.1`
11
+ *
12
+ * Dependencies:
13
+ * - `src/shared/types/v8-contracts.js` (SensitivityClass)
14
+ *
15
+ * Boundary:
16
+ * - Pure function; no DB access, no side effects.
17
+ * - Does not block on classification; returns degraded only on malformed input.
18
+ * - Technical vocabulary alone does not trigger sensitive.
19
+ *
20
+ * Test coverage: tests/unit/perception/sensitivity-classifier.test.ts
21
+ */
22
+ import type { SensitivityClass } from "../../../shared/types/v8-contracts.js";
23
+ export interface SensitivityClassification {
24
+ sensitivityClass: SensitivityClass;
25
+ confidence: number;
26
+ reason: string;
27
+ flags: string[];
28
+ }
29
+ export declare function classifyEvidenceSensitivity(text: string, sourceContext?: string): SensitivityClassification;
30
+ export interface BatchClassificationResult {
31
+ classifications: SensitivityClassification[];
32
+ sensitiveCount: number;
33
+ privateCount: number;
34
+ publicTechnicalCount: number;
35
+ publicGeneralCount: number;
36
+ }
37
+ export declare function classifyEvidenceBatch(texts: string[], sourceContexts?: string[]): BatchClassificationResult;
@@ -0,0 +1,87 @@
1
+ /**
2
+ * SensitivityClassifier — v8 context-aware sensitivity classifier.
3
+ *
4
+ * Core logic: Classify evidence text as public_technical, public_general,
5
+ * private_context, or sensitive using field context, source context,
6
+ * value shape, and entropy signals.
7
+ *
8
+ * Design authority:
9
+ * - `.anws/v8/04_SYSTEM_DESIGN/perception-judgment-system.detail.md §3.2`
10
+ * - `.anws/v8/04_SYSTEM_DESIGN/perception-judgment-system.detail.md §4.1`
11
+ *
12
+ * Dependencies:
13
+ * - `src/shared/types/v8-contracts.js` (SensitivityClass)
14
+ *
15
+ * Boundary:
16
+ * - Pure function; no DB access, no side effects.
17
+ * - Does not block on classification; returns degraded only on malformed input.
18
+ * - Technical vocabulary alone does not trigger sensitive.
19
+ *
20
+ * Test coverage: tests/unit/perception/sensitivity-classifier.test.ts
21
+ */
22
+ // ───────────────────────────────────────────────────────────────
23
+ // Detection patterns
24
+ // ───────────────────────────────────────────────────────────────
25
+ const TECHNICAL_VOCABULARY = /\b(token|secret|credential|api.key|auth|oauth|jwt|bearer|ssh|rsa|ecdsa|pem|key.pair)s?\b/i;
26
+ const VALUE_LIKE_SECRET = /\b[a-zA-Z0-9_]+\s*[:=]\s*['"][a-zA-Z0-9+/=\-_]{20,}['"]/i;
27
+ const BEARER_TOKEN = /\bBearer\s+[a-zA-Z0-9_\-\.]{20,}/i;
28
+ const PRIVATE_KEY_HEADER = /-----BEGIN\s+(RSA\s+)?PRIVATE\s+KEY-----/i;
29
+ const PRIVATE_CONTEXT_MARKERS = /\b(DM|私信|private message|confidential|internal only|not for distribution|restricted)\b/i;
30
+ // ───────────────────────────────────────────────────────────────
31
+ // Public API
32
+ // ───────────────────────────────────────────────────────────────
33
+ export function classifyEvidenceSensitivity(text, sourceContext) {
34
+ if (!text || text.trim().length === 0) {
35
+ return {
36
+ sensitivityClass: "public_general",
37
+ confidence: 0.1,
38
+ reason: "empty_input",
39
+ flags: ["classification_low_confidence"],
40
+ };
41
+ }
42
+ const flags = [];
43
+ // 1. Credential value shape — highest priority
44
+ if (VALUE_LIKE_SECRET.test(text) || BEARER_TOKEN.test(text) || PRIVATE_KEY_HEADER.test(text)) {
45
+ return {
46
+ sensitivityClass: "sensitive",
47
+ confidence: 0.95,
48
+ reason: "credential_shape_detected",
49
+ flags: ["value_like_secret"],
50
+ };
51
+ }
52
+ // 2. Private source context
53
+ if (PRIVATE_CONTEXT_MARKERS.test(text) || (sourceContext && PRIVATE_CONTEXT_MARKERS.test(sourceContext))) {
54
+ return {
55
+ sensitivityClass: "private_context",
56
+ confidence: 0.8,
57
+ reason: "private_context_detected",
58
+ flags: ["private_source_markers"],
59
+ };
60
+ }
61
+ // 3. Technical vocabulary only
62
+ if (TECHNICAL_VOCABULARY.test(text)) {
63
+ return {
64
+ sensitivityClass: "public_technical",
65
+ confidence: 0.75,
66
+ reason: "technical_vocabulary_only",
67
+ flags: ["no_value_shape"],
68
+ };
69
+ }
70
+ // 4. Default — public general
71
+ return {
72
+ sensitivityClass: "public_general",
73
+ confidence: 0.5,
74
+ reason: "no_distinctive_signals",
75
+ flags: ["classification_low_confidence"],
76
+ };
77
+ }
78
+ export function classifyEvidenceBatch(texts, sourceContexts) {
79
+ const classifications = texts.map((text, i) => classifyEvidenceSensitivity(text, sourceContexts?.[i]));
80
+ return {
81
+ classifications,
82
+ sensitiveCount: classifications.filter((c) => c.sensitivityClass === "sensitive").length,
83
+ privateCount: classifications.filter((c) => c.sensitivityClass === "private_context").length,
84
+ publicTechnicalCount: classifications.filter((c) => c.sensitivityClass === "public_technical").length,
85
+ publicGeneralCount: classifications.filter((c) => c.sensitivityClass === "public_general").length,
86
+ };
87
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * DreamConsolidationRunner — Generate memory candidates from Quiet review.
3
+ *
4
+ * Core logic: Read DreamConsolidationRun and associated QuietDailyReview,
5
+ * apply rules-only candidate generation, redaction gate, validation,
6
+ * and update run status to completed/failed/blocked.
7
+ *
8
+ * Design authority:
9
+ * - `.anws/v8/04_SYSTEM_DESIGN/dream-quiet-memory-system.detail.md §3.3`
10
+ * - `.anws/v8/04_SYSTEM_DESIGN/dream-quiet-memory-system.md §4.2`
11
+ *
12
+ * Dependencies:
13
+ * - `src/storage/v8-state-stores.js` (readDreamConsolidationRunById, readQuietDailyReviewById, writeLongTermMemoryProjection)
14
+ * - `src/shared/types/v8-contracts.js` (SourceRef, DegradedOperationResult, V8ReasonCode)
15
+ *
16
+ * Boundary:
17
+ * - Rules-only candidate generation; no model assist in this version.
18
+ * - Does not accept/reject projections; only creates candidates.
19
+ * - Redaction gate blocks sensitive private content, preserves public technical.
20
+ *
21
+ * Test coverage: tests/unit/dream/dream-consolidation-runner.test.ts
22
+ */
23
+ import type { StateDatabase } from "../../../storage/db/index.js";
24
+ import type { SourceRef, DegradedOperationResult, V8ReasonCode } from "../../../shared/types/v8-contracts.js";
25
+ export interface DreamMemoryCandidate {
26
+ id: string;
27
+ runId: string;
28
+ reviewId: string;
29
+ candidateText: string;
30
+ sourceRefs: SourceRef[];
31
+ confidence: number;
32
+ validationStatus: "valid" | "rejected" | "blocked";
33
+ validationReason?: string;
34
+ }
35
+ export interface RunDreamConsolidationResult {
36
+ runId: string;
37
+ status: "completed" | "failed" | "blocked";
38
+ candidates: DreamMemoryCandidate[];
39
+ reason?: V8ReasonCode;
40
+ }
41
+ export interface RunDreamConsolidationOptions {
42
+ now?: string;
43
+ }
44
+ export declare function runDreamConsolidation(db: StateDatabase, runId: string, options?: RunDreamConsolidationOptions): Promise<RunDreamConsolidationResult | DegradedOperationResult>;
@@ -0,0 +1,180 @@
1
+ /**
2
+ * DreamConsolidationRunner — Generate memory candidates from Quiet review.
3
+ *
4
+ * Core logic: Read DreamConsolidationRun and associated QuietDailyReview,
5
+ * apply rules-only candidate generation, redaction gate, validation,
6
+ * and update run status to completed/failed/blocked.
7
+ *
8
+ * Design authority:
9
+ * - `.anws/v8/04_SYSTEM_DESIGN/dream-quiet-memory-system.detail.md §3.3`
10
+ * - `.anws/v8/04_SYSTEM_DESIGN/dream-quiet-memory-system.md §4.2`
11
+ *
12
+ * Dependencies:
13
+ * - `src/storage/v8-state-stores.js` (readDreamConsolidationRunById, readQuietDailyReviewById, writeLongTermMemoryProjection)
14
+ * - `src/shared/types/v8-contracts.js` (SourceRef, DegradedOperationResult, V8ReasonCode)
15
+ *
16
+ * Boundary:
17
+ * - Rules-only candidate generation; no model assist in this version.
18
+ * - Does not accept/reject projections; only creates candidates.
19
+ * - Redaction gate blocks sensitive private content, preserves public technical.
20
+ *
21
+ * Test coverage: tests/unit/dream/dream-consolidation-runner.test.ts
22
+ */
23
+ import { readDreamConsolidationRunById, readQuietDailyReviewById, writeLongTermMemoryProjection, } from "../../../storage/v8-state-stores.js";
24
+ // ───────────────────────────────────────────────────────────────
25
+ // Helpers
26
+ // ───────────────────────────────────────────────────────────────
27
+ function parsePayloadJson(json) {
28
+ if (!json)
29
+ return {};
30
+ try {
31
+ return JSON.parse(json);
32
+ }
33
+ catch {
34
+ return {};
35
+ }
36
+ }
37
+ function buildSourceRefsFromReview(review) {
38
+ return [
39
+ {
40
+ uri: `sn://quiet/${review.id}`,
41
+ family: "quiet_review",
42
+ id: review.id,
43
+ redactionClass: "none",
44
+ resolveStatus: "resolvable",
45
+ },
46
+ ];
47
+ }
48
+ function redactSensitive(input) {
49
+ // Simple redaction: block credential-shaped patterns
50
+ if (/\b(?:Bearer|token|secret|password|key)\s*[:=]\s*[a-zA-Z0-9+/=]{8,}\b/i.test(input)) {
51
+ return { text: "[redacted: credential shape detected]", blocked: true };
52
+ }
53
+ return { text: input, blocked: false };
54
+ }
55
+ function generateCandidatesFromReview(runId, reviewId, reviewPayload) {
56
+ const candidates = [];
57
+ const summary = String(reviewPayload.reviewSummary ?? "");
58
+ if (summary.length > 0) {
59
+ const { text, blocked } = redactSensitive(summary);
60
+ if (blocked) {
61
+ candidates.push({
62
+ id: `cand_${runId}_summary`,
63
+ runId,
64
+ reviewId,
65
+ candidateText: text,
66
+ sourceRefs: buildSourceRefsFromReview({ id: reviewId, day: "" }),
67
+ confidence: 0.3,
68
+ validationStatus: "blocked",
69
+ validationReason: "redaction_blocked",
70
+ });
71
+ }
72
+ else {
73
+ candidates.push({
74
+ id: `cand_${runId}_summary`,
75
+ runId,
76
+ reviewId,
77
+ candidateText: `Daily review: ${text}`,
78
+ sourceRefs: buildSourceRefsFromReview({ id: reviewId, day: "" }),
79
+ confidence: 0.6,
80
+ validationStatus: "valid",
81
+ });
82
+ }
83
+ }
84
+ const importanceSignals = reviewPayload.importanceSignals;
85
+ if (importanceSignals && importanceSignals.length > 0) {
86
+ const { text, blocked } = redactSensitive(importanceSignals.join("; "));
87
+ if (!blocked) {
88
+ candidates.push({
89
+ id: `cand_${runId}_signals`,
90
+ runId,
91
+ reviewId,
92
+ candidateText: `Important signals: ${text}`,
93
+ sourceRefs: buildSourceRefsFromReview({ id: reviewId, day: "" }),
94
+ confidence: 0.5,
95
+ validationStatus: "valid",
96
+ });
97
+ }
98
+ }
99
+ return candidates;
100
+ }
101
+ // ───────────────────────────────────────────────────────────────
102
+ // Public API
103
+ // ───────────────────────────────────────────────────────────────
104
+ export async function runDreamConsolidation(db, runId, options) {
105
+ const now = options?.now ?? new Date().toISOString();
106
+ const runRead = await readDreamConsolidationRunById(db, runId);
107
+ if (runRead.degraded) {
108
+ return runRead.degraded;
109
+ }
110
+ const run = runRead.row;
111
+ if (!run) {
112
+ return {
113
+ status: "degraded",
114
+ reason: "state_unreadable",
115
+ ownerStage: "dream",
116
+ sourceRefs: [],
117
+ operatorNextAction: `DreamConsolidationRun ${runId} not found`,
118
+ retryable: false,
119
+ };
120
+ }
121
+ const reviewRead = await readQuietDailyReviewById(db, run.quietReviewId);
122
+ if (reviewRead.degraded) {
123
+ return reviewRead.degraded;
124
+ }
125
+ const review = reviewRead.row;
126
+ if (!review) {
127
+ return {
128
+ status: "degraded",
129
+ reason: "state_unreadable",
130
+ ownerStage: "dream",
131
+ sourceRefs: [],
132
+ operatorNextAction: `QuietDailyReview ${run.quietReviewId} not found`,
133
+ retryable: false,
134
+ };
135
+ }
136
+ const reviewPayload = parsePayloadJson(review.payloadJson);
137
+ const candidates = generateCandidatesFromReview(runId, run.quietReviewId, reviewPayload);
138
+ // If all candidates blocked → run blocked
139
+ if (candidates.length > 0 && candidates.every((c) => c.validationStatus === "blocked")) {
140
+ return {
141
+ runId,
142
+ status: "blocked",
143
+ candidates,
144
+ reason: "dream_blocked_redaction",
145
+ };
146
+ }
147
+ // Write valid candidates as projections (candidate status)
148
+ const validCandidates = candidates.filter((c) => c.validationStatus === "valid");
149
+ for (const candidate of validCandidates) {
150
+ const projectionResult = await writeLongTermMemoryProjection(db, {
151
+ id: `proj_${candidate.id}`,
152
+ createdAt: now,
153
+ candidateId: candidate.id,
154
+ topicKey: `topic_${review.day}`,
155
+ status: "candidate",
156
+ sourceRefs: candidate.sourceRefs,
157
+ redactionClass: "none",
158
+ lifecycleStatus: "candidate",
159
+ payloadJson: JSON.stringify({
160
+ candidateText: candidate.candidateText,
161
+ confidence: candidate.confidence,
162
+ runId,
163
+ }),
164
+ });
165
+ if ("reason" in projectionResult) {
166
+ return {
167
+ runId,
168
+ status: "failed",
169
+ candidates,
170
+ reason: projectionResult.reason,
171
+ };
172
+ }
173
+ }
174
+ return {
175
+ runId,
176
+ status: "completed",
177
+ candidates,
178
+ reason: "dream_completed",
179
+ };
180
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * DreamScheduler — Schedule Dream consolidation after Quiet completion.
3
+ *
4
+ * Core logic: Read a QuietDailyReview, create a DreamConsolidationRun
5
+ * with lifecycle trace, and write it to state. Handles unavailable
6
+ * scheduler by recording degraded state.
7
+ *
8
+ * Design authority:
9
+ * - `.anws/v8/04_SYSTEM_DESIGN/dream-quiet-memory-system.detail.md §3.2`
10
+ * - `.anws/v8/04_SYSTEM_DESIGN/dream-quiet-memory-system.md §4.2`
11
+ *
12
+ * Dependencies:
13
+ * - `src/storage/v8-state-stores.js` (readQuietDailyReviewById, writeDreamConsolidationRun)
14
+ * - `src/shared/types/v8-contracts.js` (SourceRef, DegradedOperationResult, V8ReasonCode)
15
+ *
16
+ * Boundary:
17
+ * - Does not run consolidation; only schedules and records lifecycle.
18
+ * - Does not form long-term memory; Dream runner does that.
19
+ * - Degrades gracefully on missing review or unreadable state.
20
+ *
21
+ * Test coverage: tests/unit/dream/dream-scheduler-lifecycle.test.ts
22
+ */
23
+ import type { StateDatabase } from "../../../storage/db/index.js";
24
+ import type { DegradedOperationResult, V8ReasonCode } from "../../../shared/types/v8-contracts.js";
25
+ export interface DreamScheduleResult {
26
+ id: string;
27
+ quietReviewId: string;
28
+ status: "scheduled" | "started" | "completed" | "failed" | "blocked";
29
+ reason?: V8ReasonCode;
30
+ createdAt: string;
31
+ }
32
+ export interface ScheduleDreamAfterQuietOptions {
33
+ now?: string;
34
+ schedulerAvailable?: boolean;
35
+ }
36
+ export declare function scheduleDreamAfterQuiet(db: StateDatabase, quietReviewId: string, options?: ScheduleDreamAfterQuietOptions): Promise<DreamScheduleResult | DegradedOperationResult>;