@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,105 @@
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 { readQuietDailyReviewById, writeDreamConsolidationRun, } from "../../../storage/v8-state-stores.js";
24
+ // ───────────────────────────────────────────────────────────────
25
+ // Public API
26
+ // ───────────────────────────────────────────────────────────────
27
+ export async function scheduleDreamAfterQuiet(db, quietReviewId, options) {
28
+ const now = options?.now ?? new Date().toISOString();
29
+ const readResult = await readQuietDailyReviewById(db, quietReviewId);
30
+ if (readResult.degraded) {
31
+ return readResult.degraded;
32
+ }
33
+ const review = readResult.row;
34
+ if (!review) {
35
+ return {
36
+ status: "degraded",
37
+ reason: "state_unreadable",
38
+ ownerStage: "dream",
39
+ sourceRefs: [],
40
+ operatorNextAction: `QuietDailyReview ${quietReviewId} not found`,
41
+ retryable: false,
42
+ };
43
+ }
44
+ const runId = `dream_${quietReviewId}_${now.replace(/[:.]/g, "")}`;
45
+ // Scheduler unavailable → record blocked state
46
+ if (options?.schedulerAvailable === false) {
47
+ const writeResult = await writeDreamConsolidationRun(db, {
48
+ id: runId,
49
+ createdAt: now,
50
+ quietReviewId,
51
+ status: "blocked",
52
+ reason: "dream_scheduler_unavailable",
53
+ sourceRefs: [
54
+ {
55
+ uri: `sn://dream/scheduler_unavailable/${quietReviewId}`,
56
+ family: "dream_run",
57
+ id: quietReviewId,
58
+ redactionClass: "none",
59
+ resolveStatus: "resolvable",
60
+ },
61
+ ],
62
+ redactionClass: "none",
63
+ lifecycleStatus: "pending",
64
+ payloadJson: JSON.stringify({ scheduledAt: now, blocked: true }),
65
+ });
66
+ if ("reason" in writeResult) {
67
+ return writeResult;
68
+ }
69
+ return {
70
+ id: runId,
71
+ quietReviewId,
72
+ status: "blocked",
73
+ reason: "dream_scheduler_unavailable",
74
+ createdAt: now,
75
+ };
76
+ }
77
+ // Normal schedule
78
+ const writeResult = await writeDreamConsolidationRun(db, {
79
+ id: runId,
80
+ createdAt: now,
81
+ quietReviewId,
82
+ status: "scheduled",
83
+ sourceRefs: [
84
+ {
85
+ uri: `sn://dream/scheduled/${quietReviewId}`,
86
+ family: "dream_run",
87
+ id: quietReviewId,
88
+ redactionClass: "none",
89
+ resolveStatus: "resolvable",
90
+ },
91
+ ],
92
+ redactionClass: "none",
93
+ lifecycleStatus: "pending",
94
+ payloadJson: JSON.stringify({ scheduledAt: now }),
95
+ });
96
+ if ("reason" in writeResult) {
97
+ return writeResult;
98
+ }
99
+ return {
100
+ id: runId,
101
+ quietReviewId,
102
+ status: "scheduled",
103
+ createdAt: now,
104
+ };
105
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * MemoryProjectionLifecycle — Manage accepted long-term memory projections.
3
+ *
4
+ * Core logic: Accept, activate, supersede, and reject projections.
5
+ * When accepting a projection on a topic with existing active projection,
6
+ * supersede the old one.
7
+ *
8
+ * Design authority:
9
+ * - `.anws/v8/04_SYSTEM_DESIGN/dream-quiet-memory-system.detail.md §3.4`
10
+ * - `.anws/v8/04_SYSTEM_DESIGN/dream-quiet-memory-system.md §4.2`
11
+ *
12
+ * Dependencies:
13
+ * - `src/storage/v8-state-stores.js` (readMemoryProjectionsByTopic, writeLongTermMemoryProjection)
14
+ * - `src/shared/types/v8-contracts.js` (SourceRef, DegradedOperationResult, V8ReasonCode)
15
+ *
16
+ * Boundary:
17
+ * - Only accepts projections with source refs.
18
+ * - Supersedes old active projections on same topic automatically.
19
+ * - Does not delete projections; only transitions status.
20
+ *
21
+ * Test coverage: tests/unit/dream/memory-projection-lifecycle.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 ProjectionLifecycleResult {
26
+ projectionId: string;
27
+ status: "accepted" | "rejected" | "superseded" | "retired";
28
+ reason: V8ReasonCode;
29
+ supersedesProjectionId?: string;
30
+ }
31
+ export interface AcceptMemoryProjectionOptions {
32
+ now?: string;
33
+ }
34
+ export declare function acceptMemoryProjection(db: StateDatabase, candidateId: string, topicKey: string, memoryText: string, sourceRefs: SourceRef[], options?: AcceptMemoryProjectionOptions): Promise<ProjectionLifecycleResult | DegradedOperationResult>;
35
+ export declare function rejectMemoryProjection(db: StateDatabase, projectionId: string, candidateId: string, topicKey: string, sourceRefs: SourceRef[], reason?: V8ReasonCode, options?: AcceptMemoryProjectionOptions): Promise<ProjectionLifecycleResult | DegradedOperationResult>;
36
+ export declare function retireMemoryProjection(db: StateDatabase, projectionId: string, candidateId: string, topicKey: string, sourceRefs: SourceRef[], options?: AcceptMemoryProjectionOptions): Promise<ProjectionLifecycleResult | DegradedOperationResult>;
@@ -0,0 +1,151 @@
1
+ /**
2
+ * MemoryProjectionLifecycle — Manage accepted long-term memory projections.
3
+ *
4
+ * Core logic: Accept, activate, supersede, and reject projections.
5
+ * When accepting a projection on a topic with existing active projection,
6
+ * supersede the old one.
7
+ *
8
+ * Design authority:
9
+ * - `.anws/v8/04_SYSTEM_DESIGN/dream-quiet-memory-system.detail.md §3.4`
10
+ * - `.anws/v8/04_SYSTEM_DESIGN/dream-quiet-memory-system.md §4.2`
11
+ *
12
+ * Dependencies:
13
+ * - `src/storage/v8-state-stores.js` (readMemoryProjectionsByTopic, writeLongTermMemoryProjection)
14
+ * - `src/shared/types/v8-contracts.js` (SourceRef, DegradedOperationResult, V8ReasonCode)
15
+ *
16
+ * Boundary:
17
+ * - Only accepts projections with source refs.
18
+ * - Supersedes old active projections on same topic automatically.
19
+ * - Does not delete projections; only transitions status.
20
+ *
21
+ * Test coverage: tests/unit/dream/memory-projection-lifecycle.test.ts
22
+ */
23
+ import { readMemoryProjectionsByTopic, writeLongTermMemoryProjection, } from "../../../storage/v8-state-stores.js";
24
+ // ───────────────────────────────────────────────────────────────
25
+ // Public API
26
+ // ───────────────────────────────────────────────────────────────
27
+ export async function acceptMemoryProjection(db, candidateId, topicKey, memoryText, sourceRefs, options) {
28
+ const now = options?.now ?? new Date().toISOString();
29
+ if (sourceRefs.length === 0) {
30
+ return {
31
+ status: "degraded",
32
+ reason: "source_refs_unresolved",
33
+ ownerStage: "projection",
34
+ sourceRefs: [],
35
+ operatorNextAction: "Acceptance requires source refs",
36
+ retryable: false,
37
+ };
38
+ }
39
+ const existing = await readMemoryProjectionsByTopic(db, topicKey);
40
+ if (existing.degraded) {
41
+ return existing.degraded;
42
+ }
43
+ // Find active projection to supersede
44
+ const activeProjection = existing.rows.find((r) => r.status === "active" || r.status === "accepted");
45
+ let supersedesId;
46
+ if (activeProjection) {
47
+ // Supersede existing active projection
48
+ const supersedeResult = await writeLongTermMemoryProjection(db, {
49
+ id: activeProjection.id,
50
+ createdAt: activeProjection.createdAt,
51
+ candidateId: activeProjection.candidateId,
52
+ topicKey: activeProjection.topicKey,
53
+ status: "superseded",
54
+ sourceRefs: parseSourceRefs(activeProjection.sourceRefsJson),
55
+ redactionClass: activeProjection.redactionClass,
56
+ lifecycleStatus: "superseded",
57
+ payloadJson: JSON.stringify({
58
+ supersededAt: now,
59
+ supersededBy: candidateId,
60
+ }),
61
+ });
62
+ if ("reason" in supersedeResult) {
63
+ return supersedeResult;
64
+ }
65
+ supersedesId = activeProjection.id;
66
+ }
67
+ // Create new accepted/active projection
68
+ const projectionId = `proj_${candidateId}_${now.replace(/[:.]/g, "")}`;
69
+ const writeResult = await writeLongTermMemoryProjection(db, {
70
+ id: projectionId,
71
+ createdAt: now,
72
+ candidateId,
73
+ topicKey,
74
+ status: "active",
75
+ sourceRefs,
76
+ redactionClass: "none",
77
+ lifecycleStatus: "active",
78
+ payloadJson: JSON.stringify({
79
+ memoryText,
80
+ acceptedAt: now,
81
+ supersedesProjectionId: supersedesId,
82
+ }),
83
+ });
84
+ if ("reason" in writeResult) {
85
+ return writeResult;
86
+ }
87
+ return {
88
+ projectionId,
89
+ status: "accepted",
90
+ reason: "projection_accepted",
91
+ supersedesProjectionId: supersedesId,
92
+ };
93
+ }
94
+ export async function rejectMemoryProjection(db, projectionId, candidateId, topicKey, sourceRefs, reason = "projection_rejected", options) {
95
+ const now = options?.now ?? new Date().toISOString();
96
+ const writeResult = await writeLongTermMemoryProjection(db, {
97
+ id: projectionId,
98
+ createdAt: now,
99
+ candidateId,
100
+ topicKey,
101
+ status: "rejected",
102
+ sourceRefs,
103
+ redactionClass: "none",
104
+ lifecycleStatus: "rejected",
105
+ payloadJson: JSON.stringify({ rejectedAt: now, reason }),
106
+ });
107
+ if ("reason" in writeResult) {
108
+ return writeResult;
109
+ }
110
+ return {
111
+ projectionId,
112
+ status: "rejected",
113
+ reason,
114
+ };
115
+ }
116
+ export async function retireMemoryProjection(db, projectionId, candidateId, topicKey, sourceRefs, options) {
117
+ const now = options?.now ?? new Date().toISOString();
118
+ const writeResult = await writeLongTermMemoryProjection(db, {
119
+ id: projectionId,
120
+ createdAt: now,
121
+ candidateId,
122
+ topicKey,
123
+ status: "retired",
124
+ sourceRefs,
125
+ redactionClass: "none",
126
+ lifecycleStatus: "retired",
127
+ payloadJson: JSON.stringify({ retiredAt: now }),
128
+ });
129
+ if ("reason" in writeResult) {
130
+ return writeResult;
131
+ }
132
+ return {
133
+ projectionId,
134
+ status: "retired",
135
+ reason: "projection_rejected",
136
+ };
137
+ }
138
+ // ───────────────────────────────────────────────────────────────
139
+ // Helpers
140
+ // ───────────────────────────────────────────────────────────────
141
+ function parseSourceRefs(json) {
142
+ if (!json)
143
+ return [];
144
+ try {
145
+ const parsed = JSON.parse(json);
146
+ return Array.isArray(parsed) ? parsed : [];
147
+ }
148
+ catch {
149
+ return [];
150
+ }
151
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * QuietDailyReviewBuilder — Aggregate daily closures, perceptions, and
3
+ * memory-review candidates into a source-backed QuietDailyReview.
4
+ *
5
+ * Core logic: Read ActionClosureRecords by day, collect memory-review
6
+ * candidates, build summary, and write QuietDailyReview row.
7
+ *
8
+ * Design authority:
9
+ * - `.anws/v8/04_SYSTEM_DESIGN/dream-quiet-memory-system.detail.md §3.1`
10
+ * - `.anws/v8/04_SYSTEM_DESIGN/dream-quiet-memory-system.md §4.2`
11
+ *
12
+ * Dependencies:
13
+ * - `src/storage/v8-state-stores.js` (readActionClosuresByDay, writeQuietDailyReview)
14
+ * - `src/shared/types/v8-contracts.js` (SourceRef, DegradedOperationResult, V8ReasonCode)
15
+ *
16
+ * Boundary:
17
+ * - Does not form long-term memory; only emits review input for Dream.
18
+ * - Does not judge importance; reads closure status and risk flags.
19
+ * - Degrades gracefully on unreadable state.
20
+ *
21
+ * Test coverage: tests/unit/quiet/quiet-daily-review-builder.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 QuietDailyReviewResult {
26
+ id: string;
27
+ day: string;
28
+ closureCount: number;
29
+ memoryCandidateCount: number;
30
+ sourceRefs: SourceRef[];
31
+ reviewSummary: string;
32
+ importanceSignals: string[];
33
+ createdAt: string;
34
+ }
35
+ export interface BuildQuietDailyReviewOptions {
36
+ day?: string;
37
+ now?: string;
38
+ }
39
+ export type BuildQuietDailyReviewOutput = {
40
+ status: "completed";
41
+ review: QuietDailyReviewResult;
42
+ } | {
43
+ status: "empty";
44
+ reason: V8ReasonCode;
45
+ } | DegradedOperationResult;
46
+ export declare function buildQuietDailyReview(db: StateDatabase, options?: BuildQuietDailyReviewOptions): Promise<BuildQuietDailyReviewOutput>;
@@ -0,0 +1,123 @@
1
+ /**
2
+ * QuietDailyReviewBuilder — Aggregate daily closures, perceptions, and
3
+ * memory-review candidates into a source-backed QuietDailyReview.
4
+ *
5
+ * Core logic: Read ActionClosureRecords by day, collect memory-review
6
+ * candidates, build summary, and write QuietDailyReview row.
7
+ *
8
+ * Design authority:
9
+ * - `.anws/v8/04_SYSTEM_DESIGN/dream-quiet-memory-system.detail.md §3.1`
10
+ * - `.anws/v8/04_SYSTEM_DESIGN/dream-quiet-memory-system.md §4.2`
11
+ *
12
+ * Dependencies:
13
+ * - `src/storage/v8-state-stores.js` (readActionClosuresByDay, writeQuietDailyReview)
14
+ * - `src/shared/types/v8-contracts.js` (SourceRef, DegradedOperationResult, V8ReasonCode)
15
+ *
16
+ * Boundary:
17
+ * - Does not form long-term memory; only emits review input for Dream.
18
+ * - Does not judge importance; reads closure status and risk flags.
19
+ * - Degrades gracefully on unreadable state.
20
+ *
21
+ * Test coverage: tests/unit/quiet/quiet-daily-review-builder.test.ts
22
+ */
23
+ import { readActionClosuresByDay, writeQuietDailyReview, } from "../../../storage/v8-state-stores.js";
24
+ // ───────────────────────────────────────────────────────────────
25
+ // Config
26
+ // ───────────────────────────────────────────────────────────────
27
+ const QUIET_MAX_CLOSURES_PER_DAY = 200;
28
+ // ───────────────────────────────────────────────────────────────
29
+ // Helpers
30
+ // ───────────────────────────────────────────────────────────────
31
+ function todayString(now) {
32
+ return now.slice(0, 10);
33
+ }
34
+ function parsePayloadJson(json) {
35
+ if (!json)
36
+ return {};
37
+ try {
38
+ return JSON.parse(json);
39
+ }
40
+ catch {
41
+ return {};
42
+ }
43
+ }
44
+ function buildSourceRefFromClosure(closure) {
45
+ return {
46
+ uri: `sn://closure/${closure.id}`,
47
+ family: "action_closure",
48
+ id: closure.id,
49
+ redactionClass: "none",
50
+ resolveStatus: "resolvable",
51
+ };
52
+ }
53
+ // ───────────────────────────────────────────────────────────────
54
+ // Public API
55
+ // ───────────────────────────────────────────────────────────────
56
+ export async function buildQuietDailyReview(db, options) {
57
+ const now = options?.now ?? new Date().toISOString();
58
+ const day = options?.day ?? todayString(now);
59
+ const readResult = await readActionClosuresByDay(db, day);
60
+ if (readResult.degraded) {
61
+ return readResult.degraded;
62
+ }
63
+ const closures = readResult.rows.slice(0, QUIET_MAX_CLOSURES_PER_DAY);
64
+ if (closures.length === 0) {
65
+ return {
66
+ status: "empty",
67
+ reason: "quiet_empty_input",
68
+ };
69
+ }
70
+ const sourceRefs = closures.map(buildSourceRefFromClosure);
71
+ // Collect memory-review candidates from closure payloads
72
+ const memoryCandidates = [];
73
+ for (const closure of closures) {
74
+ const payload = parsePayloadJson(closure.payloadJson);
75
+ if (payload.memoryReviewCandidate) {
76
+ memoryCandidates.push(payload.memoryReviewCandidate);
77
+ }
78
+ }
79
+ // Build summary
80
+ const completedCount = closures.filter((c) => c.status === "completed").length;
81
+ const deniedCount = closures.filter((c) => c.status === "denied").length;
82
+ const failedCount = closures.filter((c) => c.status === "failed").length;
83
+ const reviewSummary = `Day ${day}: ${closures.length} closures (${completedCount} completed, ${deniedCount} denied, ${failedCount} failed)`;
84
+ const importanceSignals = [];
85
+ if (memoryCandidates.length > 0) {
86
+ importanceSignals.push(`${memoryCandidates.length} memory-review candidates`);
87
+ }
88
+ if (failedCount > 0) {
89
+ importanceSignals.push(`${failedCount} failed actions`);
90
+ }
91
+ const reviewId = `quiet_${day}`;
92
+ const writeResult = await writeQuietDailyReview(db, {
93
+ id: reviewId,
94
+ createdAt: now,
95
+ day,
96
+ closureCount: closures.length,
97
+ memoryCandidateCount: memoryCandidates.length,
98
+ sourceRefs,
99
+ redactionClass: "none",
100
+ lifecycleStatus: "pending",
101
+ payloadJson: JSON.stringify({
102
+ reviewSummary,
103
+ importanceSignals,
104
+ memoryCandidates,
105
+ }),
106
+ });
107
+ if ("reason" in writeResult) {
108
+ return writeResult;
109
+ }
110
+ return {
111
+ status: "completed",
112
+ review: {
113
+ id: reviewId,
114
+ day,
115
+ closureCount: closures.length,
116
+ memoryCandidateCount: memoryCandidates.length,
117
+ sourceRefs,
118
+ reviewSummary,
119
+ importanceSignals,
120
+ createdAt: now,
121
+ },
122
+ };
123
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * CausalLoopHealth — Assemble loop health snapshot from cycle traces and stage events.
3
+ *
4
+ * Core logic: Read recent HeartbeatCycleTrace and LoopStageEvent rows,
5
+ * compute stage freshness, identify stalled stages, and return
6
+ * CausalLoopHealthSnapshot.
7
+ *
8
+ * Design authority:
9
+ * - `.anws/v8/04_SYSTEM_DESIGN/observability-health-system.detail.md §3.2`
10
+ * - `.anws/v8/04_SYSTEM_DESIGN/observability-health-system.md §5`
11
+ *
12
+ * Dependencies:
13
+ * - `src/storage/v8-state-stores.js` (readHeartbeatCycleTraces, readLoopStageEventsByStage)
14
+ * - `src/shared/types/v8-contracts.js` (LoopStage, DegradedOperationResult)
15
+ *
16
+ * Boundary:
17
+ * - Does not judge action correctness; only measures loop progression.
18
+ * - Does not block heartbeat; returns degraded diagnostics.
19
+ * - Stall detection uses cycle-sequence gaps, not wall-clock only.
20
+ *
21
+ * Test coverage: tests/unit/observability/causal-loop-health.test.ts
22
+ */
23
+ import type { StateDatabase } from "../storage/db/index.js";
24
+ import type { LoopStage, DegradedOperationResult } from "../shared/types/v8-contracts.js";
25
+ export interface StageHealth {
26
+ stage: LoopStage;
27
+ lastEventAt?: string;
28
+ lastCycleSequence?: number;
29
+ eventCount: number;
30
+ stalled: boolean;
31
+ }
32
+ export interface CausalLoopHealthSnapshot {
33
+ overallStatus: "healthy" | "degraded" | "stalled" | "no_data";
34
+ stalledAt?: LoopStage;
35
+ lastCycleSequence: number;
36
+ lastHeartbeatAt?: string;
37
+ stages: StageHealth[];
38
+ reason?: string;
39
+ }
40
+ export interface AssembleLoopStatusOptions {
41
+ stallThresholdCycles?: number;
42
+ limit?: number;
43
+ }
44
+ export declare function assembleLoopStatus(db: StateDatabase, options?: AssembleLoopStatusOptions): Promise<CausalLoopHealthSnapshot | DegradedOperationResult>;
@@ -0,0 +1,118 @@
1
+ /**
2
+ * CausalLoopHealth — Assemble loop health snapshot from cycle traces and stage events.
3
+ *
4
+ * Core logic: Read recent HeartbeatCycleTrace and LoopStageEvent rows,
5
+ * compute stage freshness, identify stalled stages, and return
6
+ * CausalLoopHealthSnapshot.
7
+ *
8
+ * Design authority:
9
+ * - `.anws/v8/04_SYSTEM_DESIGN/observability-health-system.detail.md §3.2`
10
+ * - `.anws/v8/04_SYSTEM_DESIGN/observability-health-system.md §5`
11
+ *
12
+ * Dependencies:
13
+ * - `src/storage/v8-state-stores.js` (readHeartbeatCycleTraces, readLoopStageEventsByStage)
14
+ * - `src/shared/types/v8-contracts.js` (LoopStage, DegradedOperationResult)
15
+ *
16
+ * Boundary:
17
+ * - Does not judge action correctness; only measures loop progression.
18
+ * - Does not block heartbeat; returns degraded diagnostics.
19
+ * - Stall detection uses cycle-sequence gaps, not wall-clock only.
20
+ *
21
+ * Test coverage: tests/unit/observability/causal-loop-health.test.ts
22
+ */
23
+ import { readHeartbeatCycleTraces, readLoopStageEventsByStage, } from "../storage/v8-state-stores.js";
24
+ // ───────────────────────────────────────────────────────────────
25
+ // Config
26
+ // ───────────────────────────────────────────────────────────────
27
+ const DEFAULT_STALL_THRESHOLD_CYCLES = 2;
28
+ const LOOP_STAGES = [
29
+ "ingestion",
30
+ "perception",
31
+ "judgment",
32
+ "policy",
33
+ "execution",
34
+ "closure",
35
+ "quiet",
36
+ "dream",
37
+ "projection",
38
+ ];
39
+ // ───────────────────────────────────────────────────────────────
40
+ // Helpers
41
+ // ───────────────────────────────────────────────────────────────
42
+ function maxCycleSequence(stages) {
43
+ return Math.max(0, ...stages.map((s) => s.lastCycleSequence ?? 0));
44
+ }
45
+ function findStalledStage(stages, threshold) {
46
+ const maxSeq = maxCycleSequence(stages);
47
+ if (maxSeq === 0)
48
+ return undefined;
49
+ for (const stage of stages) {
50
+ if (stage.lastCycleSequence === undefined) {
51
+ // No events for this stage → stalled if other stages have progressed
52
+ if (maxSeq > 0)
53
+ return stage.stage;
54
+ continue;
55
+ }
56
+ const gap = maxSeq - stage.lastCycleSequence;
57
+ if (gap >= threshold) {
58
+ return stage.stage;
59
+ }
60
+ }
61
+ return undefined;
62
+ }
63
+ // ───────────────────────────────────────────────────────────────
64
+ // Public API
65
+ // ───────────────────────────────────────────────────────────────
66
+ export async function assembleLoopStatus(db, options) {
67
+ const threshold = options?.stallThresholdCycles ?? DEFAULT_STALL_THRESHOLD_CYCLES;
68
+ const limit = options?.limit ?? 100;
69
+ const cycleResult = await readHeartbeatCycleTraces(db, limit);
70
+ if (cycleResult.degraded) {
71
+ return cycleResult.degraded;
72
+ }
73
+ const cycles = cycleResult.rows;
74
+ if (cycles.length === 0) {
75
+ return {
76
+ overallStatus: "no_data",
77
+ lastCycleSequence: 0,
78
+ stages: [],
79
+ reason: "no heartbeat cycles recorded",
80
+ };
81
+ }
82
+ const lastCycle = cycles[0];
83
+ // Gather stage health
84
+ const stages = [];
85
+ for (const stage of LOOP_STAGES) {
86
+ const eventResult = await readLoopStageEventsByStage(db, stage, limit);
87
+ if (eventResult.degraded) {
88
+ return eventResult.degraded;
89
+ }
90
+ const events = eventResult.rows;
91
+ const lastEvent = events[0];
92
+ stages.push({
93
+ stage,
94
+ lastEventAt: lastEvent?.occurredAt ?? undefined,
95
+ lastCycleSequence: lastEvent?.cycleSequence ?? undefined,
96
+ eventCount: events.length,
97
+ stalled: false, // computed below
98
+ });
99
+ }
100
+ const stalledAt = findStalledStage(stages, threshold);
101
+ // Mark stalled stages
102
+ for (const stage of stages) {
103
+ if (stalledAt === stage.stage) {
104
+ stage.stalled = true;
105
+ }
106
+ }
107
+ const overallStatus = stalledAt
108
+ ? "stalled"
109
+ : "healthy";
110
+ return {
111
+ overallStatus,
112
+ stalledAt,
113
+ lastCycleSequence: lastCycle.cycleSequence,
114
+ lastHeartbeatAt: lastCycle.heartbeatStartedAt,
115
+ stages,
116
+ reason: stalledAt ? `stage ${stalledAt} stalled for >=${threshold} cycles` : undefined,
117
+ };
118
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * DiagnosticRedaction — Attribute sensitivity blocks and redact diagnostics.
3
+ *
4
+ * Core logic: Given a diagnostic payload, classify its sensitivity,
5
+ * redact credential-shaped values, preserve public technical summaries,
6
+ * and attribute the block to the responsible system.
7
+ *
8
+ * Design authority:
9
+ * - `.anws/v8/04_SYSTEM_DESIGN/observability-health-system.detail.md §3.4`
10
+ * - `.anws/v8/04_SYSTEM_DESIGN/observability-health-system.md §5`
11
+ *
12
+ * Dependencies:
13
+ * - `src/shared/types/v8-contracts.js` (SourceRef, RedactionClass, SensitivityClass)
14
+ *
15
+ * Boundary:
16
+ * - Pure function; no DB access.
17
+ * - Does not modify source data; returns redacted copy.
18
+ * - Public technical vocabulary is preserved.
19
+ *
20
+ * Test coverage: tests/unit/observability/diagnostic-redaction.test.ts
21
+ */
22
+ import type { RedactionClass, SensitivityClass, V8ReasonCode } from "../shared/types/v8-contracts.js";
23
+ export interface DiagnosticPayload {
24
+ summary: string;
25
+ detail?: string;
26
+ sourceSystem: "perception" | "judgment" | "dream" | "policy" | "storage" | "unknown";
27
+ sensitivityHint?: SensitivityClass;
28
+ reasonCode?: V8ReasonCode;
29
+ }
30
+ export interface RedactedDiagnostic {
31
+ summary: string;
32
+ detail?: string;
33
+ redactionClass: RedactionClass;
34
+ attribution: string;
35
+ }
36
+ export type DiagnosticAttribution = "storage_validation_block" | "dream_redaction_block" | "perception_risk_block" | "policy_denial" | "credential_shape_detected" | "private_context" | "public_technical_preserved" | "no_redaction_needed";
37
+ /**
38
+ * Classify sensitivity-related diagnostic attribution based on source system
39
+ * and reason code. Maps to deterministic attribution categories per
40
+ * observability-health-system.detail.md §4.2.
41
+ */
42
+ export declare function classifyDiagnosticAttribution(sourceSystem: DiagnosticPayload["sourceSystem"], reasonCode?: V8ReasonCode): DiagnosticAttribution;
43
+ export declare function projectDiagnosticRedaction(payload: DiagnosticPayload): RedactedDiagnostic;