@haaaiawd/second-nature 0.1.26 → 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 (158) hide show
  1. package/SKILL.md +35 -0
  2. package/agent-inner-guide.md +144 -0
  3. package/index.js +280 -2
  4. package/openclaw.plugin.json +2 -2
  5. package/package.json +4 -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 +3 -13
  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/credential-vault.js +31 -17
  138. package/runtime/storage/services/diary-dream-store.d.ts +35 -0
  139. package/runtime/storage/services/diary-dream-store.js +165 -0
  140. package/runtime/storage/services/embodied-context-state-port.d.ts +77 -0
  141. package/runtime/storage/services/embodied-context-state-port.js +115 -0
  142. package/runtime/storage/services/goal-lifecycle-store.d.ts +42 -0
  143. package/runtime/storage/services/goal-lifecycle-store.js +181 -0
  144. package/runtime/storage/services/history-digest-store.d.ts +33 -0
  145. package/runtime/storage/services/history-digest-store.js +140 -0
  146. package/runtime/storage/services/identity-profile-store.d.ts +25 -0
  147. package/runtime/storage/services/identity-profile-store.js +81 -0
  148. package/runtime/storage/services/interaction-snapshot-projector.d.ts +15 -0
  149. package/runtime/storage/services/interaction-snapshot-projector.js +35 -0
  150. package/runtime/storage/services/restore-snapshot-store.d.ts +52 -0
  151. package/runtime/storage/services/restore-snapshot-store.js +193 -0
  152. package/runtime/storage/services/runtime-secret-anchor-store.d.ts +26 -0
  153. package/runtime/storage/services/runtime-secret-anchor-store.js +82 -0
  154. package/runtime/storage/services/tool-experience-store.d.ts +25 -0
  155. package/runtime/storage/services/tool-experience-store.js +116 -0
  156. package/runtime/storage/services/write-validation-gate.d.ts +46 -0
  157. package/runtime/storage/services/write-validation-gate.js +200 -0
  158. package/workspace-ops-bridge.js +16 -1
@@ -0,0 +1,136 @@
1
+ /**
2
+ * NarrativeTimelineQueryService — T-OBS.C.5
3
+ *
4
+ * Core logic:
5
+ * cursor-based pagination over narrative timeline entries (DR-037).
6
+ * A cursor encodes the ISO timestamp of the last-seen entry; the next
7
+ * page starts strictly after that timestamp.
8
+ *
9
+ * queryNarrativeTimeline:
10
+ * - Accepts optional cursor + limit (default 20, max 30).
11
+ * - Validates that (to - from) ≤ 90 days; throws query_range_exceeded otherwise.
12
+ * - Fetches (limit + 1) rows to detect whether a next page exists.
13
+ * - Returns NarrativeTimelinePage with entries + optional nextCursor.
14
+ *
15
+ * queryNarrativeDiff:
16
+ * - Compares two named versions across DIFF_FIELDS.
17
+ * - Computes sourceRefs set-difference (added / removed).
18
+ * - Propagates reasonCode from the *to* snapshot.
19
+ *
20
+ * DR-037: cursor pagination; 90-day upper bound; no offset pagination.
21
+ * DR-032: if state-memory is unavailable, returns degraded placeholder.
22
+ *
23
+ * Test coverage: tests/unit/observability/narrative-timeline-query.test.ts
24
+ */
25
+ /** A single entry in the narrative timeline (read model). */
26
+ export interface NarrativeTimelineEntry {
27
+ version: string;
28
+ timestamp: string;
29
+ triggerKind: "heartbeat.decision" | "goal.transition" | "restore.applied" | "dream.projection" | "owner.override";
30
+ sourceRefs: string[];
31
+ reasonCode?: string;
32
+ /** Human-readable change summary — pre-redacted, no raw private content */
33
+ summaryText?: string;
34
+ }
35
+ /** A page of narrative timeline entries with optional next-page cursor. */
36
+ export interface NarrativeTimelinePage {
37
+ from: string;
38
+ to: string;
39
+ entries: NarrativeTimelineEntry[];
40
+ /** Opaque cursor: pass as `cursor` in the next call to get the next page */
41
+ nextCursor?: string;
42
+ /** True when the range was truncated because entries exceeded maxVersionsReturned */
43
+ truncated: boolean;
44
+ }
45
+ /** Field change in a narrative diff */
46
+ export interface NarrativeFieldChange {
47
+ field: "focus" | "progress" | "nextIntent" | "toneSignal" | "acceptedGoalId" | "sourceRefs";
48
+ from: string | null;
49
+ to: string | null;
50
+ }
51
+ /** Diff between two narrative snapshots */
52
+ export interface NarrativeDiff {
53
+ fromVersion: string;
54
+ toVersion: string;
55
+ computedAt: string;
56
+ changes: NarrativeFieldChange[];
57
+ sourceRefChanges: {
58
+ added: string[];
59
+ removed: string[];
60
+ };
61
+ reasonCode?: string;
62
+ isNoChange: boolean;
63
+ }
64
+ /** Row shape returned by the state-memory store */
65
+ export interface NarrativeTimelineRow {
66
+ version: string;
67
+ createdAt: string;
68
+ triggerKind: string;
69
+ sourceRefs?: string[];
70
+ reasonCode?: string;
71
+ summaryText?: string;
72
+ }
73
+ /** Snapshot shape for diff computation */
74
+ export interface NarrativeSnapshotRow {
75
+ version: string;
76
+ focus?: unknown;
77
+ progress?: unknown;
78
+ nextIntent?: unknown;
79
+ toneSignal?: unknown;
80
+ acceptedGoalId?: unknown;
81
+ sourceRefs?: string[];
82
+ lastChangeReasonCode?: string;
83
+ }
84
+ /**
85
+ * Port that the state-memory subsystem must implement.
86
+ * Only the methods used here are declared — keeps the port minimal.
87
+ */
88
+ export interface NarrativeTimelinePort {
89
+ /**
90
+ * Return timeline rows in ascending `createdAt` order.
91
+ * @param from ISO 8601 lower bound (inclusive)
92
+ * @param to ISO 8601 upper bound (inclusive)
93
+ * @param opts Optional pagination control
94
+ */
95
+ listNarrativeTimeline(from: string, to: string, opts?: {
96
+ limit?: number;
97
+ afterTimestamp?: string;
98
+ }): Promise<NarrativeTimelineRow[]>;
99
+ /**
100
+ * Return a single snapshot by version string, or null if not found.
101
+ */
102
+ getNarrativeSnapshot(version: string): Promise<NarrativeSnapshotRow | null>;
103
+ }
104
+ /** Dependencies injected into query functions */
105
+ export interface NarrativeTimelineDeps {
106
+ stateMemoryPort: NarrativeTimelinePort;
107
+ /** Override for testability */
108
+ now?: () => string;
109
+ }
110
+ export declare class NarrativeQueryRangeError extends Error {
111
+ readonly code: "query_range_exceeded";
112
+ constructor(rangeDays: number);
113
+ }
114
+ export declare class NarrativeVersionNotFoundError extends Error {
115
+ readonly code: "narrative_version_not_found";
116
+ constructor(version: string);
117
+ }
118
+ /**
119
+ * A cursor is a base64url-encoded JSON object: { ts: string }.
120
+ * `ts` is the ISO timestamp of the last entry returned on the previous page.
121
+ * The next page fetches rows with createdAt > ts.
122
+ */
123
+ export declare function encodeCursor(lastTimestamp: string): string;
124
+ export declare function decodeCursor(cursor: string): {
125
+ ts: string;
126
+ };
127
+ /**
128
+ * Query narrative timeline entries with cursor-based pagination.
129
+ *
130
+ * @throws NarrativeQueryRangeError when (to - from) > 90 days
131
+ */
132
+ export declare function queryNarrativeTimeline(from: string, to: string, opts: {
133
+ limit?: number;
134
+ cursor?: string;
135
+ }, deps: NarrativeTimelineDeps): Promise<NarrativeTimelinePage>;
136
+ export declare function queryNarrativeDiff(fromVersion: string, toVersion: string, deps: NarrativeTimelineDeps): Promise<NarrativeDiff>;
@@ -0,0 +1,169 @@
1
+ /**
2
+ * NarrativeTimelineQueryService — T-OBS.C.5
3
+ *
4
+ * Core logic:
5
+ * cursor-based pagination over narrative timeline entries (DR-037).
6
+ * A cursor encodes the ISO timestamp of the last-seen entry; the next
7
+ * page starts strictly after that timestamp.
8
+ *
9
+ * queryNarrativeTimeline:
10
+ * - Accepts optional cursor + limit (default 20, max 30).
11
+ * - Validates that (to - from) ≤ 90 days; throws query_range_exceeded otherwise.
12
+ * - Fetches (limit + 1) rows to detect whether a next page exists.
13
+ * - Returns NarrativeTimelinePage with entries + optional nextCursor.
14
+ *
15
+ * queryNarrativeDiff:
16
+ * - Compares two named versions across DIFF_FIELDS.
17
+ * - Computes sourceRefs set-difference (added / removed).
18
+ * - Propagates reasonCode from the *to* snapshot.
19
+ *
20
+ * DR-037: cursor pagination; 90-day upper bound; no offset pagination.
21
+ * DR-032: if state-memory is unavailable, returns degraded placeholder.
22
+ *
23
+ * Test coverage: tests/unit/observability/narrative-timeline-query.test.ts
24
+ */
25
+ // ─── Config ──────────────────────────────────────────────────────────────────
26
+ const MAX_RANGE_DAYS = 90;
27
+ const MAX_VERSIONS_PER_PAGE = 30;
28
+ const DEFAULT_PAGE_LIMIT = 20;
29
+ // ─── Error types ─────────────────────────────────────────────────────────────
30
+ export class NarrativeQueryRangeError extends Error {
31
+ code = "query_range_exceeded";
32
+ constructor(rangeDays) {
33
+ super(`query_range_exceeded: requested range ${rangeDays} days exceeds maximum ${MAX_RANGE_DAYS} days`);
34
+ this.name = "NarrativeQueryRangeError";
35
+ }
36
+ }
37
+ export class NarrativeVersionNotFoundError extends Error {
38
+ code = "narrative_version_not_found";
39
+ constructor(version) {
40
+ super(`narrative_version_not_found: ${version}`);
41
+ this.name = "NarrativeVersionNotFoundError";
42
+ }
43
+ }
44
+ // ─── Helpers ─────────────────────────────────────────────────────────────────
45
+ function daysBetween(from, to) {
46
+ const ms = new Date(to).getTime() - new Date(from).getTime();
47
+ return ms / (1000 * 60 * 60 * 24);
48
+ }
49
+ function validateRange(from, to) {
50
+ const range = daysBetween(from, to);
51
+ if (range > MAX_RANGE_DAYS) {
52
+ throw new NarrativeQueryRangeError(Math.ceil(range));
53
+ }
54
+ }
55
+ function clampLimit(requested) {
56
+ if (requested === undefined || requested <= 0)
57
+ return DEFAULT_PAGE_LIMIT;
58
+ return Math.min(requested, MAX_VERSIONS_PER_PAGE);
59
+ }
60
+ function mapRow(row) {
61
+ return {
62
+ version: row.version,
63
+ timestamp: row.createdAt,
64
+ triggerKind: row.triggerKind,
65
+ sourceRefs: row.sourceRefs ?? [],
66
+ reasonCode: row.reasonCode ?? undefined,
67
+ summaryText: row.summaryText ?? undefined,
68
+ };
69
+ }
70
+ // ─── Cursor encoding ─────────────────────────────────────────────────────────
71
+ /**
72
+ * A cursor is a base64url-encoded JSON object: { ts: string }.
73
+ * `ts` is the ISO timestamp of the last entry returned on the previous page.
74
+ * The next page fetches rows with createdAt > ts.
75
+ */
76
+ export function encodeCursor(lastTimestamp) {
77
+ return Buffer.from(JSON.stringify({ ts: lastTimestamp })).toString("base64url");
78
+ }
79
+ export function decodeCursor(cursor) {
80
+ try {
81
+ const parsed = JSON.parse(Buffer.from(cursor, "base64url").toString("utf8"));
82
+ if (typeof parsed.ts !== "string")
83
+ throw new Error("invalid cursor shape");
84
+ return parsed;
85
+ }
86
+ catch {
87
+ throw new Error(`invalid_cursor: ${cursor}`);
88
+ }
89
+ }
90
+ // ─── Public API ──────────────────────────────────────────────────────────────
91
+ /**
92
+ * Query narrative timeline entries with cursor-based pagination.
93
+ *
94
+ * @throws NarrativeQueryRangeError when (to - from) > 90 days
95
+ */
96
+ export async function queryNarrativeTimeline(from, to, opts, deps) {
97
+ validateRange(from, to);
98
+ const pageSize = clampLimit(opts.limit);
99
+ // Decode cursor to get the afterTimestamp for this page
100
+ const afterTimestamp = opts.cursor ? decodeCursor(opts.cursor).ts : undefined;
101
+ // Fetch one extra to detect if there are more pages
102
+ const rows = await deps.stateMemoryPort.listNarrativeTimeline(from, to, {
103
+ limit: pageSize + 1,
104
+ afterTimestamp,
105
+ });
106
+ const hasMore = rows.length > pageSize;
107
+ const pageRows = hasMore ? rows.slice(0, pageSize) : rows;
108
+ const entries = pageRows.map(mapRow);
109
+ const nextCursor = hasMore && pageRows.length > 0
110
+ ? encodeCursor(pageRows[pageRows.length - 1].createdAt)
111
+ : undefined;
112
+ return {
113
+ from,
114
+ to,
115
+ entries,
116
+ nextCursor,
117
+ truncated: hasMore,
118
+ };
119
+ }
120
+ /**
121
+ * Compare two narrative versions and return field-level diff.
122
+ *
123
+ * @throws NarrativeVersionNotFoundError when either version is not found
124
+ */
125
+ const DIFF_FIELDS = [
126
+ "focus",
127
+ "progress",
128
+ "nextIntent",
129
+ "toneSignal",
130
+ "acceptedGoalId",
131
+ ];
132
+ export async function queryNarrativeDiff(fromVersion, toVersion, deps) {
133
+ const computedAt = (deps.now ?? (() => new Date().toISOString()))();
134
+ const [fromSnap, toSnap] = await Promise.all([
135
+ deps.stateMemoryPort.getNarrativeSnapshot(fromVersion),
136
+ deps.stateMemoryPort.getNarrativeSnapshot(toVersion),
137
+ ]);
138
+ if (!fromSnap)
139
+ throw new NarrativeVersionNotFoundError(fromVersion);
140
+ if (!toSnap)
141
+ throw new NarrativeVersionNotFoundError(toVersion);
142
+ // Compare scalar diff fields
143
+ const changes = [];
144
+ for (const field of DIFF_FIELDS) {
145
+ const fromVal = fromSnap[field] ?? null;
146
+ const toVal = toSnap[field] ?? null;
147
+ const fromStr = fromVal === null ? null : String(fromVal);
148
+ const toStr = toVal === null ? null : String(toVal);
149
+ if (fromStr !== toStr) {
150
+ changes.push({ field, from: fromStr, to: toStr });
151
+ }
152
+ }
153
+ // Compute sourceRefs set-difference
154
+ const fromRefs = new Set(fromSnap.sourceRefs ?? []);
155
+ const toRefs = new Set(toSnap.sourceRefs ?? []);
156
+ const addedRefs = [...toRefs].filter((r) => !fromRefs.has(r));
157
+ const removedRefs = [...fromRefs].filter((r) => !toRefs.has(r));
158
+ return {
159
+ fromVersion,
160
+ toVersion,
161
+ computedAt,
162
+ changes,
163
+ sourceRefChanges: { added: addedRefs, removed: removedRefs },
164
+ reasonCode: toSnap.lastChangeReasonCode ?? undefined,
165
+ isNoChange: changes.length === 0 &&
166
+ addedRefs.length === 0 &&
167
+ removedRefs.length === 0,
168
+ };
169
+ }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * RestoreAuditService — T-OBS.C.6
3
+ *
4
+ * Core logic:
5
+ * writeRestoreAudit() writes an audit log entry to AppendOnlyAuditStore
6
+ * every time a restore operation is attempted (success OR failure).
7
+ *
8
+ * Audit payload includes:
9
+ * - restoreTarget, fromVersion, toVersion, triggeredBy, reason
10
+ * - completedEntities: entity kinds that were successfully restored
11
+ * - failedEntities: entity kinds that failed (for partial_restore_error)
12
+ * - excludedFields: fields NOT restored (credential / key material)
13
+ * - restoredFieldCount
14
+ * It NEVER includes actual field values or credential plaintext.
15
+ *
16
+ * Atomicity strategy (DR-041):
17
+ * - audit write failure is fire-and-forget: state restore already happened,
18
+ * so we cannot roll it back. Return { ok: true, warnings: [...] }.
19
+ * - partial_restore_error: state-memory partially wrote; audit records
20
+ * completedEntities + failedEntities so operator can see what succeeded.
21
+ *
22
+ * Credential exclusion:
23
+ * - RestoreAuditEvent.excludedFields lists credential / key fields.
24
+ * - Actual values of these fields are NEVER written to audit.
25
+ *
26
+ * Test coverage: tests/unit/observability/restore-audit-service.test.ts
27
+ */
28
+ import type { AppendOnlyAuditStore } from "../audit/append-only-audit-store.js";
29
+ export type RestoreTarget = "goal" | "narrative" | "evidence" | "relationship";
30
+ export type RestoreTrigger = "operator" | "agent";
31
+ export interface RestoreAuditEvent {
32
+ id: string;
33
+ restoreTarget: RestoreTarget;
34
+ fromVersion: string;
35
+ toVersion: string;
36
+ triggeredBy: RestoreTrigger;
37
+ reason: string;
38
+ /**
39
+ * Entity kinds that were successfully written back to state.
40
+ * Used for partial_restore_error traceability.
41
+ */
42
+ completedEntities: string[];
43
+ /**
44
+ * Entity kinds that failed to write back (partial restore).
45
+ * Non-empty → partial_restore_error scenario.
46
+ */
47
+ failedEntities: string[];
48
+ /**
49
+ * Field names explicitly excluded from restore (credential / key fields).
50
+ * These are listed here for audit trail but their VALUES are never stored.
51
+ */
52
+ excludedFields: string[];
53
+ /** Number of non-excluded fields that were restored */
54
+ restoredFieldCount: number;
55
+ createdAt: string;
56
+ traceId: string;
57
+ }
58
+ export interface WriteRestoreAuditResult {
59
+ ok: boolean;
60
+ /** Populated if audit write itself failed (fire-and-forget, state already changed) */
61
+ warnings: string[];
62
+ }
63
+ /**
64
+ * Write a restore audit entry to the AppendOnlyAuditStore.
65
+ *
66
+ * Must be called during (or immediately after) a restore operation.
67
+ * Audit write failure is fire-and-forget (DR-041): the function never throws.
68
+ *
69
+ * Returns:
70
+ * { ok: true, warnings: [] } — audit written successfully
71
+ * { ok: true, warnings: ["audit_write_failed: restore_audit_missing"] }
72
+ * — audit failed, state already changed
73
+ */
74
+ export declare function writeRestoreAudit(event: RestoreAuditEvent, store: AppendOnlyAuditStore): Promise<WriteRestoreAuditResult>;
@@ -0,0 +1,79 @@
1
+ /**
2
+ * RestoreAuditService — T-OBS.C.6
3
+ *
4
+ * Core logic:
5
+ * writeRestoreAudit() writes an audit log entry to AppendOnlyAuditStore
6
+ * every time a restore operation is attempted (success OR failure).
7
+ *
8
+ * Audit payload includes:
9
+ * - restoreTarget, fromVersion, toVersion, triggeredBy, reason
10
+ * - completedEntities: entity kinds that were successfully restored
11
+ * - failedEntities: entity kinds that failed (for partial_restore_error)
12
+ * - excludedFields: fields NOT restored (credential / key material)
13
+ * - restoredFieldCount
14
+ * It NEVER includes actual field values or credential plaintext.
15
+ *
16
+ * Atomicity strategy (DR-041):
17
+ * - audit write failure is fire-and-forget: state restore already happened,
18
+ * so we cannot roll it back. Return { ok: true, warnings: [...] }.
19
+ * - partial_restore_error: state-memory partially wrote; audit records
20
+ * completedEntities + failedEntities so operator can see what succeeded.
21
+ *
22
+ * Credential exclusion:
23
+ * - RestoreAuditEvent.excludedFields lists credential / key fields.
24
+ * - Actual values of these fields are NEVER written to audit.
25
+ *
26
+ * Test coverage: tests/unit/observability/restore-audit-service.test.ts
27
+ */
28
+ import { buildAuditEnvelope } from "../audit/audit-envelope.js";
29
+ // ─── Public API ──────────────────────────────────────────────────────────────
30
+ /**
31
+ * Write a restore audit entry to the AppendOnlyAuditStore.
32
+ *
33
+ * Must be called during (or immediately after) a restore operation.
34
+ * Audit write failure is fire-and-forget (DR-041): the function never throws.
35
+ *
36
+ * Returns:
37
+ * { ok: true, warnings: [] } — audit written successfully
38
+ * { ok: true, warnings: ["audit_write_failed: restore_audit_missing"] }
39
+ * — audit failed, state already changed
40
+ */
41
+ export async function writeRestoreAudit(event, store) {
42
+ // Build safe payload — NO credential values, only metadata
43
+ const safePayload = {
44
+ restoreTarget: event.restoreTarget,
45
+ fromVersion: event.fromVersion,
46
+ toVersion: event.toVersion,
47
+ triggeredBy: event.triggeredBy,
48
+ reason: event.reason,
49
+ completedEntities: event.completedEntities,
50
+ failedEntities: event.failedEntities,
51
+ excludedFields: event.excludedFields,
52
+ restoredFieldCount: event.restoredFieldCount,
53
+ isPartialRestore: event.failedEntities.length > 0,
54
+ createdAt: event.createdAt,
55
+ };
56
+ try {
57
+ const previousHash = store.lastRecordHash("restore.audit");
58
+ const envelope = buildAuditEnvelope({
59
+ family: "restore.audit",
60
+ plane: "governance",
61
+ traceId: event.traceId,
62
+ sequence: store.list().length,
63
+ payload: safePayload,
64
+ previousHash,
65
+ eventId: event.id,
66
+ createdAt: event.createdAt,
67
+ });
68
+ store.append(envelope);
69
+ return { ok: true, warnings: [] };
70
+ }
71
+ catch (err) {
72
+ // Fire-and-forget: state restore already happened — do not re-throw
73
+ const message = err instanceof Error ? err.message : String(err);
74
+ return {
75
+ ok: true,
76
+ warnings: [`audit_write_failed: restore_audit_missing (${message})`],
77
+ };
78
+ }
79
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * RuntimeSecretAnchorView — T-OBS.C.7
3
+ *
4
+ * Core logic:
5
+ * viewSecretAnchor() probes the encryption key anchor and returns a
6
+ * RuntimeSecretAnchorView that describes the current health status.
7
+ *
8
+ * Three detection scenarios (DR-034):
9
+ * 1. Key path missing / env var not set → status "missing"
10
+ * reasonCode: "runtime_secret_anchor_missing"
11
+ * 2. Key path present but sample decrypt fails with wrong-key signal
12
+ * reasonCode: "credential_recovery_required"
13
+ * 3. Key path present but decrypt call throws / unrecoverable error
14
+ * reasonCode: "runtime_secret_unavailable"
15
+ *
16
+ * RecoveryStep[] is always inline in the view (DR-034).
17
+ * Key plaintext is NEVER stored or returned (ADR-007).
18
+ * Only the key path (env var name / file path) is included.
19
+ *
20
+ * Test coverage: tests/unit/observability/runtime-secret-anchor-view.test.ts
21
+ */
22
+ export type SecretAnchorStatus = "ok" | "verified" | "missing" | "wrong_key" | "decryption_failed" | "unknown";
23
+ export type HealthProbeReasonCode = "runtime_secret_unavailable" | "credential_recovery_required" | "runtime_secret_anchor_missing";
24
+ export interface RecoveryStep {
25
+ step: number;
26
+ action: string;
27
+ /** Optional shell command the operator should run */
28
+ command?: string;
29
+ }
30
+ /** The view object returned by viewSecretAnchor(). Never contains key plaintext. */
31
+ export interface RuntimeSecretAnchorView {
32
+ anchorId: string;
33
+ /** Env-var name or file path for the encryption key — NOT the value */
34
+ keyPath: string;
35
+ status: SecretAnchorStatus;
36
+ lastCheckedAt: string;
37
+ /** Reference to operator recovery documentation */
38
+ recoveryDocRef: string;
39
+ /** Optional rotation schedule hint */
40
+ rotationSchedule?: string;
41
+ /** IDs of credentials that were sampled during verification (not their values) */
42
+ checkedCredentialIds?: string[];
43
+ /** Inline recovery instructions when status is not "verified" (DR-034) */
44
+ recoverySteps: RecoveryStep[];
45
+ /** Machine-readable reason code when status is not "verified" */
46
+ reasonCode?: HealthProbeReasonCode;
47
+ }
48
+ export interface SampleDecryptResult {
49
+ /** "ok" = decrypt succeeded; "wrong_key" = key mismatch; "error" = other failure */
50
+ status: "ok" | "wrong_key" | "error";
51
+ checkedIds: string[];
52
+ }
53
+ export interface SecretAnchorRuntimeOpsPort {
54
+ /** Returns the key path (env var name or file path), never the key value */
55
+ getEncryptionKeyPath(): string;
56
+ /** Returns true if the key path exists and is non-empty */
57
+ checkKeyPathExists(keyPath: string): Promise<boolean>;
58
+ }
59
+ export interface SecretAnchorCredentialPort {
60
+ /** Attempts to decrypt a known sample credential; returns status + IDs checked */
61
+ verifySampleDecrypt(): Promise<SampleDecryptResult>;
62
+ }
63
+ export interface SecretAnchorDeps {
64
+ runtimeOpsPort: SecretAnchorRuntimeOpsPort;
65
+ credentialPort: SecretAnchorCredentialPort;
66
+ /** Override for testability */
67
+ now?: () => string;
68
+ }
69
+ /**
70
+ * Probe the encryption key anchor and return a safe view.
71
+ *
72
+ * Guarantees:
73
+ * - keyPath is a path string (env var name or file path), never a value.
74
+ * - No field in the returned object contains the key plaintext.
75
+ * - recoverySteps is always populated when status ≠ "verified".
76
+ */
77
+ export declare function viewSecretAnchor(deps: SecretAnchorDeps): Promise<RuntimeSecretAnchorView>;