@haaaiawd/second-nature 0.1.27 → 0.1.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/SKILL.md +35 -33
  2. package/agent-inner-guide.md +144 -124
  3. package/index.js +76 -1
  4. package/openclaw.plugin.json +2 -2
  5. package/package.json +2 -1
  6. package/runtime/cli/commands/connector-behavior.d.ts +20 -0
  7. package/runtime/cli/commands/connector-behavior.js +160 -0
  8. package/runtime/cli/commands/index.js +8 -0
  9. package/runtime/cli/index.js +9 -2
  10. package/runtime/cli/ops/manual-run-dispatcher.d.ts +79 -0
  11. package/runtime/cli/ops/manual-run-dispatcher.js +110 -0
  12. package/runtime/cli/ops/ops-router.d.ts +45 -4
  13. package/runtime/cli/ops/ops-router.js +543 -2
  14. package/runtime/cli/read-models/index.js +35 -18
  15. package/runtime/cli/read-models/types.d.ts +1 -0
  16. package/runtime/connectors/agent-network/agent-world/adapter.d.ts +1 -0
  17. package/runtime/connectors/agent-network/agent-world/adapter.js +2 -2
  18. package/runtime/connectors/base/contract.d.ts +4 -1
  19. package/runtime/connectors/base/contract.js +5 -1
  20. package/runtime/connectors/base/effect-commit-ledger-sqlite.d.ts +31 -0
  21. package/runtime/connectors/base/effect-commit-ledger-sqlite.js +86 -0
  22. package/runtime/connectors/base/failure-taxonomy.js +5 -0
  23. package/runtime/connectors/base/manifest-v7.d.ts +151 -0
  24. package/runtime/connectors/base/manifest-v7.js +170 -0
  25. package/runtime/connectors/base/manifest.d.ts +67 -77
  26. package/runtime/connectors/base/manifest.js +7 -7
  27. package/runtime/connectors/base/route-planner.js +11 -8
  28. package/runtime/connectors/base/structured-unavailable-reason.d.ts +59 -0
  29. package/runtime/connectors/base/structured-unavailable-reason.js +113 -0
  30. package/runtime/connectors/base/wet-probe-runner.d.ts +40 -0
  31. package/runtime/connectors/base/wet-probe-runner.js +132 -0
  32. package/runtime/connectors/manifest/manifest-schema.d.ts +4 -0
  33. package/runtime/connectors/manifest/manifest-schema.js +2 -0
  34. package/runtime/connectors/services/connector-executor-adapter.d.ts +1 -0
  35. package/runtime/connectors/services/connector-executor-adapter.js +132 -26
  36. package/runtime/core/second-nature/body/behavior-promotion/behavior-promotion-loop.d.ts +45 -0
  37. package/runtime/core/second-nature/body/behavior-promotion/behavior-promotion-loop.js +132 -0
  38. package/runtime/core/second-nature/body/circuit-breaker/circuit-breaker-manager.d.ts +60 -0
  39. package/runtime/core/second-nature/body/circuit-breaker/circuit-breaker-manager.js +174 -0
  40. package/runtime/core/second-nature/body/probe-signal-adapter.d.ts +38 -0
  41. package/runtime/core/second-nature/body/probe-signal-adapter.js +60 -0
  42. package/runtime/core/second-nature/body/tool-affordance/affordance-assembler.d.ts +51 -0
  43. package/runtime/core/second-nature/body/tool-affordance/affordance-assembler.js +129 -0
  44. package/runtime/core/second-nature/body/tool-affordance/affordance-context-scope.d.ts +30 -0
  45. package/runtime/core/second-nature/body/tool-affordance/affordance-context-scope.js +92 -0
  46. package/runtime/core/second-nature/body/tool-experience/experience-writer.d.ts +34 -0
  47. package/runtime/core/second-nature/body/tool-experience/experience-writer.js +67 -0
  48. package/runtime/core/second-nature/body/tool-experience/pain-signal-query.d.ts +37 -0
  49. package/runtime/core/second-nature/body/tool-experience/pain-signal-query.js +62 -0
  50. package/runtime/core/second-nature/heartbeat/decision-trace-emitter.d.ts +29 -0
  51. package/runtime/core/second-nature/heartbeat/decision-trace-emitter.js +28 -0
  52. package/runtime/core/second-nature/heartbeat/embodied-context-assembler.d.ts +54 -0
  53. package/runtime/core/second-nature/heartbeat/embodied-context-assembler.js +164 -0
  54. package/runtime/core/second-nature/heartbeat/goal-lifecycle-policy.d.ts +37 -0
  55. package/runtime/core/second-nature/heartbeat/goal-lifecycle-policy.js +61 -0
  56. package/runtime/core/second-nature/heartbeat/idle-curiosity-policy.d.ts +37 -0
  57. package/runtime/core/second-nature/heartbeat/idle-curiosity-policy.js +60 -0
  58. package/runtime/core/second-nature/heartbeat/index.d.ts +4 -0
  59. package/runtime/core/second-nature/heartbeat/index.js +5 -0
  60. package/runtime/core/second-nature/heartbeat/run-heartbeat-cycle-v7.d.ts +63 -0
  61. package/runtime/core/second-nature/heartbeat/run-heartbeat-cycle-v7.js +118 -0
  62. package/runtime/core/second-nature/orchestrator/downstream-intent-orchestrator.d.ts +41 -0
  63. package/runtime/core/second-nature/orchestrator/downstream-intent-orchestrator.js +43 -0
  64. package/runtime/core/second-nature/orchestrator/effect-dispatcher.d.ts +2 -1
  65. package/runtime/core/second-nature/orchestrator/effect-dispatcher.js +2 -0
  66. package/runtime/core/second-nature/orchestrator/hard-guard-evaluator.d.ts +31 -0
  67. package/runtime/core/second-nature/orchestrator/hard-guard-evaluator.js +102 -0
  68. package/runtime/core/second-nature/orchestrator/index.d.ts +5 -0
  69. package/runtime/core/second-nature/orchestrator/index.js +7 -0
  70. package/runtime/core/second-nature/quiet/claim-synthesizer.d.ts +53 -0
  71. package/runtime/core/second-nature/quiet/claim-synthesizer.js +153 -0
  72. package/runtime/core/second-nature/quiet/daily-diary-writer.d.ts +29 -0
  73. package/runtime/core/second-nature/quiet/daily-diary-writer.js +92 -0
  74. package/runtime/core/second-nature/quiet/index.d.ts +5 -0
  75. package/runtime/core/second-nature/quiet/index.js +5 -0
  76. package/runtime/core/second-nature/quiet/run-source-backed-quiet.js +19 -12
  77. package/runtime/core/second-nature/types.d.ts +2 -0
  78. package/runtime/guidance/channel-feedback-ingestion-service.d.ts +88 -0
  79. package/runtime/guidance/channel-feedback-ingestion-service.js +231 -0
  80. package/runtime/guidance/guidance-draft-service.d.ts +60 -0
  81. package/runtime/guidance/guidance-draft-service.js +80 -0
  82. package/runtime/guidance/index.d.ts +3 -0
  83. package/runtime/guidance/index.js +3 -0
  84. package/runtime/guidance/outreach-draft-schema.d.ts +8 -8
  85. package/runtime/guidance/outreach-strategy-selector.d.ts +77 -0
  86. package/runtime/guidance/outreach-strategy-selector.js +211 -0
  87. package/runtime/observability/audit/append-only-audit-store.d.ts +20 -2
  88. package/runtime/observability/audit/append-only-audit-store.js +32 -6
  89. package/runtime/observability/audit/audit-envelope.d.ts +2 -1
  90. package/runtime/observability/audit/audit-envelope.js +8 -7
  91. package/runtime/observability/audit/audit-family-registry.json +66 -0
  92. package/runtime/observability/audit/family-registry.d.ts +43 -0
  93. package/runtime/observability/audit/family-registry.js +70 -0
  94. package/runtime/observability/index.d.ts +6 -1
  95. package/runtime/observability/index.js +6 -1
  96. package/runtime/observability/redaction/policy.d.ts +24 -3
  97. package/runtime/observability/redaction/policy.js +74 -0
  98. package/runtime/observability/services/heartbeat-digest-assembler.d.ts +152 -0
  99. package/runtime/observability/services/heartbeat-digest-assembler.js +248 -0
  100. package/runtime/observability/services/lived-experience-audit.js +6 -6
  101. package/runtime/observability/services/narrative-timeline-query-service.d.ts +136 -0
  102. package/runtime/observability/services/narrative-timeline-query-service.js +169 -0
  103. package/runtime/observability/services/restore-audit-service.d.ts +74 -0
  104. package/runtime/observability/services/restore-audit-service.js +79 -0
  105. package/runtime/observability/services/runtime-secret-anchor-view.d.ts +77 -0
  106. package/runtime/observability/services/runtime-secret-anchor-view.js +168 -0
  107. package/runtime/observability/services/self-health-snapshot.d.ts +92 -0
  108. package/runtime/observability/services/self-health-snapshot.js +251 -0
  109. package/runtime/shared/types/goal.d.ts +62 -0
  110. package/runtime/shared/types/goal.js +20 -0
  111. package/runtime/shared/types/index.d.ts +3 -0
  112. package/runtime/shared/types/index.js +3 -0
  113. package/runtime/shared/types/source-ref.d.ts +14 -0
  114. package/runtime/shared/types/source-ref.js +1 -0
  115. package/runtime/shared/types/v7-entities.d.ts +206 -0
  116. package/runtime/shared/types/v7-entities.js +27 -0
  117. package/runtime/storage/db/index.js +3 -0
  118. package/runtime/storage/db/migration-runner.d.ts +30 -0
  119. package/runtime/storage/db/migration-runner.js +93 -0
  120. package/runtime/storage/db/migrations/index.d.ts +5 -0
  121. package/runtime/storage/db/migrations/index.js +13 -0
  122. package/runtime/storage/db/migrations/v7-001-foundation.d.ts +13 -0
  123. package/runtime/storage/db/migrations/v7-001-foundation.js +144 -0
  124. package/runtime/storage/db/migrations/v7-002-effect-commit-ledger.d.ts +8 -0
  125. package/runtime/storage/db/migrations/v7-002-effect-commit-ledger.js +27 -0
  126. package/runtime/storage/db/migrations/v7-003-circuit-breaker.d.ts +7 -0
  127. package/runtime/storage/db/migrations/v7-003-circuit-breaker.js +26 -0
  128. package/runtime/storage/db/migrations/v7-004-behavior-promotion.d.ts +7 -0
  129. package/runtime/storage/db/migrations/v7-004-behavior-promotion.js +26 -0
  130. package/runtime/storage/db/schema/agent-goal.d.ts +38 -0
  131. package/runtime/storage/db/schema/agent-goal.js +2 -0
  132. package/runtime/storage/db/transaction-utils.d.ts +14 -0
  133. package/runtime/storage/db/transaction-utils.js +29 -0
  134. package/runtime/storage/db/write-queue.d.ts +38 -0
  135. package/runtime/storage/db/write-queue.js +97 -0
  136. package/runtime/storage/quiet/persist-quiet-artifact.js +2 -1
  137. package/runtime/storage/services/diary-dream-store.d.ts +35 -0
  138. package/runtime/storage/services/diary-dream-store.js +165 -0
  139. package/runtime/storage/services/embodied-context-state-port.d.ts +77 -0
  140. package/runtime/storage/services/embodied-context-state-port.js +115 -0
  141. package/runtime/storage/services/goal-lifecycle-store.d.ts +42 -0
  142. package/runtime/storage/services/goal-lifecycle-store.js +181 -0
  143. package/runtime/storage/services/history-digest-store.d.ts +33 -0
  144. package/runtime/storage/services/history-digest-store.js +140 -0
  145. package/runtime/storage/services/identity-profile-store.d.ts +25 -0
  146. package/runtime/storage/services/identity-profile-store.js +81 -0
  147. package/runtime/storage/services/interaction-snapshot-projector.d.ts +15 -0
  148. package/runtime/storage/services/interaction-snapshot-projector.js +35 -0
  149. package/runtime/storage/services/restore-snapshot-store.d.ts +52 -0
  150. package/runtime/storage/services/restore-snapshot-store.js +193 -0
  151. package/runtime/storage/services/runtime-secret-anchor-store.d.ts +26 -0
  152. package/runtime/storage/services/runtime-secret-anchor-store.js +82 -0
  153. package/runtime/storage/services/tool-experience-store.d.ts +25 -0
  154. package/runtime/storage/services/tool-experience-store.js +116 -0
  155. package/runtime/storage/services/write-validation-gate.d.ts +46 -0
  156. package/runtime/storage/services/write-validation-gate.js +200 -0
  157. package/workspace-ops-bridge.js +16 -1
@@ -0,0 +1,211 @@
1
+ /**
2
+ * OutreachStrategySelector — T-GVS.C.3
3
+ *
4
+ * Core logic: select expression frequency, phrasing style and fallback copy
5
+ * based on RelationshipMemory; apply DR-031 language quality lint (3 rules).
6
+ * style_lint_failed is a degraded marker — it never blocks delivery.
7
+ * fallback copy always has information value (not empty string).
8
+ *
9
+ * DR-031 lint rules:
10
+ * 1. no_dry_filler — no hollow phrases ("reach out", "touch base", "just checking in", etc.)
11
+ * 2. anchored — must contain a concrete source anchor (specific fact/observation reference)
12
+ * 3. no_over_explain — no excessive qualifications (≥3 hedge phrases in one draft)
13
+ *
14
+ * Boundary:
15
+ * - Reads RelationshipMemory (channelPreferences, responsePatterns, trustDelta).
16
+ * - Does NOT write state; returns OutreachStrategy recommendation only.
17
+ * - style_lint checks run on draft text passed in; strategy selection itself is rule-based.
18
+ *
19
+ * Test coverage:
20
+ * tests/unit/guidance/outreach-strategy-selector.test.ts
21
+ * tests/unit/guidance/outreach-style-fixtures.test.ts
22
+ */
23
+ // ─── Constants ───────────────────────────────────────────────────────────────
24
+ /** DR-031: hollow filler phrases that indicate "dry/plain" copy */
25
+ const DRY_FILLER_PATTERNS = [
26
+ /\bjust checking in\b/i,
27
+ /\breach(?:ing)? out\b/i,
28
+ /\btouch(?:ing)? base\b/i,
29
+ /\bcircle(?:ing)? back\b/i,
30
+ /\bfollowing up\b/i,
31
+ /\bhoping (?:this|to)\b/i,
32
+ /\b没什么特别的\b/,
33
+ /\b只是想聊聊\b/,
34
+ /\b随便问问\b/,
35
+ ];
36
+ /** DR-031: patterns that signal a concrete anchor is present */
37
+ const ANCHOR_PATTERNS = [
38
+ /\b(?:because|since|given that|based on|after|when|you (?:said|mentioned|shared|noted))\b/i,
39
+ /\b(?:上次|你说|你提到|你分享|你提过|基于|根据|因为|之后|在.*之后)\b/,
40
+ /\[.*?\]/, // inline reference bracket
41
+ /\[ref:/i, // explicit source ref notation
42
+ ];
43
+ /** DR-031: hedge phrases that over-explain */
44
+ const HEDGE_PHRASES = [
45
+ /\bnot sure if\b/i,
46
+ /\bperhaps\b/i,
47
+ /\bmaybe\b/i,
48
+ /\bpossibly\b/i,
49
+ /\bif that makes sense\b/i,
50
+ /\bi (?:don't|do not) know if\b/i,
51
+ /\bi could be wrong\b/i,
52
+ /\b也许\b/,
53
+ /\b可能\b/,
54
+ /\b说不定\b/,
55
+ /\b不确定\b/,
56
+ /\b不知道是否\b/,
57
+ ];
58
+ const HEDGE_THRESHOLD = 3; // ≥3 in one draft → over_explain
59
+ /** Trust thresholds for frequency selection */
60
+ const TRUST_MINIMAL = -0.4;
61
+ const TRUST_REDUCED = -0.1;
62
+ /** No-reply signal: if ≥ this ratio of recent patterns are "ignore", reduce frequency */
63
+ const NO_REPLY_RATIO_THRESHOLD = 0.5;
64
+ const RECENT_PATTERNS_WINDOW = 5;
65
+ // ─── Language Quality Lint (DR-031) ─────────────────────────────────────────
66
+ /**
67
+ * Run DR-031 language quality checklist on a draft text.
68
+ * Returns StyleLintResult. style_lint_failed is a degraded marker — never blocks delivery.
69
+ */
70
+ export function runStyleLint(draftText) {
71
+ const violations = [];
72
+ const hitRules = [];
73
+ // Rule 1: no_dry_filler
74
+ const hasDryFiller = DRY_FILLER_PATTERNS.some((p) => p.test(draftText));
75
+ if (hasDryFiller) {
76
+ violations.push({
77
+ rule: "no_dry_filler",
78
+ description: "Draft contains hollow filler phrases (e.g. 'just checking in', 'reach out'). Replace with source-backed opener.",
79
+ });
80
+ hitRules.push("no_dry_filler");
81
+ }
82
+ // Rule 2: anchored — must have at least one concrete anchor
83
+ const hasAnchor = ANCHOR_PATTERNS.some((p) => p.test(draftText));
84
+ if (!hasAnchor) {
85
+ violations.push({
86
+ rule: "anchored",
87
+ description: "Draft lacks a concrete source anchor. Every claim should trace to observed evidence.",
88
+ });
89
+ hitRules.push("anchored");
90
+ }
91
+ // Rule 3: no_over_explain — count hedge phrase matches
92
+ const hedgeCount = HEDGE_PHRASES.reduce((acc, p) => {
93
+ const matches = draftText.match(new RegExp(p.source, p.flags + "g"));
94
+ return acc + (matches ? matches.length : 0);
95
+ }, 0);
96
+ if (hedgeCount >= HEDGE_THRESHOLD) {
97
+ violations.push({
98
+ rule: "no_over_explain",
99
+ description: `Draft contains ${hedgeCount} hedge phrases (threshold: ${HEDGE_THRESHOLD}). Remove qualifications unsupported by evidence.`,
100
+ });
101
+ hitRules.push("no_over_explain");
102
+ }
103
+ const passed = violations.length === 0;
104
+ return {
105
+ passed,
106
+ violations,
107
+ lintStatus: passed ? "passed" : "style_lint_failed",
108
+ hitRules,
109
+ };
110
+ }
111
+ // ─── Fallback Copy Builder ───────────────────────────────────────────────────
112
+ /**
113
+ * Build channel-safe fallback copy that always has information value (DR-031 G4).
114
+ * Never returns empty string. Includes sourceRefs anchor and human-readable channel reason.
115
+ */
116
+ export function buildFallbackCopy(ctx) {
117
+ const anchor = ctx.sourceRefs.length > 0 ? ctx.sourceRefs[0] : undefined;
118
+ // Construct informative text — not just a status message
119
+ let text;
120
+ if (anchor) {
121
+ text = `[channel-safe] Based on ${anchor}: ${ctx.reason}`;
122
+ }
123
+ else {
124
+ text = `[channel-safe] ${ctx.reason}`;
125
+ }
126
+ // Ensure the text contains an unsupported-claim-free, factual statement
127
+ const channelSafeReason = `Delivery currently unavailable via ${ctx.channelId ?? "this channel"}. ${ctx.reason}`;
128
+ return {
129
+ text,
130
+ hasInformationValue: true,
131
+ sourceAnchor: anchor,
132
+ channelSafeReason,
133
+ };
134
+ }
135
+ // ─── Strategy Selection ──────────────────────────────────────────────────────
136
+ /**
137
+ * Compute outreach frequency from RelationshipMemory.
138
+ * - noReply signals: if ≥50% of last 5 patterns are "ignore" → reduce frequency
139
+ * - trustDelta: negative trust pushes toward minimal/paused
140
+ */
141
+ function computeFrequency(memory) {
142
+ const recent = memory.responsePatterns.slice(-RECENT_PATTERNS_WINDOW);
143
+ const noReplyCount = recent.filter((p) => p.reaction === "ignore" || p.reaction === "block").length;
144
+ const noReplyRatio = recent.length > 0 ? noReplyCount / recent.length : 0;
145
+ // Block reaction → always paused regardless of trust
146
+ const hasBlock = memory.responsePatterns.some((p) => p.reaction === "block");
147
+ if (hasBlock)
148
+ return "paused";
149
+ // Trust-based floor
150
+ if (memory.trustDelta <= TRUST_MINIMAL)
151
+ return "minimal";
152
+ if (memory.trustDelta <= TRUST_REDUCED)
153
+ return "reduced";
154
+ // No-reply signal override
155
+ if (noReplyRatio >= NO_REPLY_RATIO_THRESHOLD && recent.length >= 2)
156
+ return "reduced";
157
+ return "standard";
158
+ }
159
+ /**
160
+ * Compute phrasing style from RelationshipMemory.
161
+ * - positive tone patterns → warm_anchored
162
+ * - neutral or mixed → concise_factual
163
+ * - degraded trust / mostly negative → light_check
164
+ */
165
+ function computeStyle(memory, frequency) {
166
+ if (frequency === "paused" || frequency === "minimal")
167
+ return "light_check";
168
+ const recent = memory.responsePatterns.slice(-RECENT_PATTERNS_WINDOW);
169
+ const positiveCount = recent.filter((p) => p.tone === "positive").length;
170
+ const negativeCount = recent.filter((p) => p.tone === "negative").length;
171
+ if (positiveCount > negativeCount && positiveCount >= 2)
172
+ return "warm_anchored";
173
+ if (negativeCount > positiveCount)
174
+ return "light_check";
175
+ return "concise_factual";
176
+ }
177
+ /**
178
+ * Compute rationale string summarising why this strategy was chosen.
179
+ * Used for transparency / explain bundle — not for delivery copy.
180
+ */
181
+ function buildRationale(memory, frequency, style) {
182
+ const parts = [];
183
+ parts.push(`trust_delta=${memory.trustDelta.toFixed(2)}`);
184
+ parts.push(`patterns=${memory.responsePatterns.length}`);
185
+ const hasBlock = memory.responsePatterns.some((p) => p.reaction === "block");
186
+ if (hasBlock)
187
+ parts.push("block_detected");
188
+ const recent = memory.responsePatterns.slice(-RECENT_PATTERNS_WINDOW);
189
+ const noReplyCount = recent.filter((p) => p.reaction === "ignore").length;
190
+ if (noReplyCount > 0)
191
+ parts.push(`no_reply_signals=${noReplyCount}`);
192
+ parts.push(`→ frequency=${frequency} style=${style}`);
193
+ return parts.join("; ");
194
+ }
195
+ /**
196
+ * Select outreach strategy based on RelationshipMemory.
197
+ * Returns OutreachStrategy (frequency + style + fallbackCopy + rationale).
198
+ * Does NOT write state.
199
+ */
200
+ export function selectOutreachStrategy(memory, options) {
201
+ const frequency = computeFrequency(memory);
202
+ const style = computeStyle(memory, frequency);
203
+ const rationale = buildRationale(memory, frequency, style);
204
+ const fallbackCtx = options?.fallbackContext ?? {
205
+ sourceRefs: [],
206
+ reason: "Outreach conditions not met at this time.",
207
+ channelId: undefined,
208
+ };
209
+ const fallbackCopy = buildFallbackCopy(fallbackCtx);
210
+ return { frequency, style, fallbackCopy, rationale };
211
+ }
@@ -1,14 +1,32 @@
1
1
  /**
2
2
  * In-memory append-only audit store with hash-chain verification hooks.
3
+ * v7 (DR-033): per-family lastHashCache for O(1) previousHash lookup.
3
4
  *
4
- * Core logic: reject broken previousHash links; expose ordered envelopes for tests / local tooling.
5
+ * Core logic:
6
+ * - reject broken previousHash links per audit family
7
+ * - maintain in-memory lastHashCache so append() is O(1) regardless of store size
8
+ * - seedFamilyHash() for post-restart backfill from DB latest record
5
9
  *
6
10
  * Test coverage: tests/unit/observability/audit-envelope.test.ts
7
11
  */
8
12
  import type { AuditEnvelope } from "./audit-envelope.js";
9
13
  export declare class AppendOnlyAuditStore {
10
14
  private readonly events;
15
+ private readonly lastHashCache;
11
16
  append<T>(envelope: AuditEnvelope<T>): void;
12
17
  list(): readonly AuditEnvelope<unknown>[];
13
- lastRecordHash(): string | undefined;
18
+ /** O(1) per-family previousHash; falls back to global last when family omitted (backward compat). */
19
+ lastRecordHash(family?: string): string | undefined;
20
+ /**
21
+ * Seed cache after process restart from DB latest record (DR-033 backfill).
22
+ *
23
+ * TODO(T-OBS.C.1): wire a startup bootstrap routine that queries the DB audit_log
24
+ * table for the latest recordHash per family and calls seedFamilyHash().
25
+ * Blocked: the observability DB schema does not yet have an audit_log table
26
+ * with previousHash/recordHash columns; once added, backfill should be
27
+ * invoked in the store constructor or at app bootstrap time.
28
+ */
29
+ seedFamilyHash(family: string, hash: string): void;
30
+ /** Expose cached families for diagnostics / testing. */
31
+ cachedFamilies(): readonly string[];
14
32
  }
@@ -1,21 +1,47 @@
1
1
  export class AppendOnlyAuditStore {
2
2
  events = [];
3
+ lastHashCache = new Map();
3
4
  append(envelope) {
4
- const last = this.events[this.events.length - 1];
5
- if (last) {
6
- if (envelope.integrity.previousHash !== last.integrity.recordHash) {
5
+ const family = envelope.family;
6
+ const cachedHash = this.lastHashCache.get(family);
7
+ if (cachedHash !== undefined) {
8
+ if (envelope.integrity.previousHash !== cachedHash) {
7
9
  throw new Error("audit_previous_hash_mismatch");
8
10
  }
9
11
  }
10
- else if (envelope.integrity.previousHash !== undefined) {
11
- throw new Error("audit_genesis_previous_hash");
12
+ else {
13
+ // No family cache — first event for this family
14
+ if (envelope.integrity.previousHash !== undefined) {
15
+ throw new Error("audit_genesis_previous_hash");
16
+ }
12
17
  }
13
18
  this.events.push(envelope);
19
+ this.lastHashCache.set(family, envelope.integrity.recordHash);
14
20
  }
15
21
  list() {
16
22
  return this.events;
17
23
  }
18
- lastRecordHash() {
24
+ /** O(1) per-family previousHash; falls back to global last when family omitted (backward compat). */
25
+ lastRecordHash(family) {
26
+ if (family) {
27
+ return this.lastHashCache.get(family);
28
+ }
19
29
  return this.events[this.events.length - 1]?.integrity.recordHash;
20
30
  }
31
+ /**
32
+ * Seed cache after process restart from DB latest record (DR-033 backfill).
33
+ *
34
+ * TODO(T-OBS.C.1): wire a startup bootstrap routine that queries the DB audit_log
35
+ * table for the latest recordHash per family and calls seedFamilyHash().
36
+ * Blocked: the observability DB schema does not yet have an audit_log table
37
+ * with previousHash/recordHash columns; once added, backfill should be
38
+ * invoked in the store constructor or at app bootstrap time.
39
+ */
40
+ seedFamilyHash(family, hash) {
41
+ this.lastHashCache.set(family, hash);
42
+ }
43
+ /** Expose cached families for diagnostics / testing. */
44
+ cachedFamilies() {
45
+ return Array.from(this.lastHashCache.keys());
46
+ }
21
47
  }
@@ -1,6 +1,6 @@
1
1
  import { type RedactionManifest as FieldRedactionManifest } from "../redaction/manifest.js";
2
2
  export type AuditPlane = "decision" | "delivery" | "source_coverage" | "governance" | "telemetry";
3
- export type AuditEventFamily = "heartbeat.decision" | "delivery" | "source_coverage" | "guidance.grounding" | "host_capability" | "connector.attempt" | "state.governance" | "narrative.trace" | "dream.trace";
3
+ export type AuditEventFamily = "heartbeat.decision" | "delivery" | "source_coverage" | "guidance.grounding" | "host_capability" | "connector.attempt" | "state.governance" | "narrative.trace" | "dream.trace" | "restore.audit" | "health.probe" | "narrative.snapshot" | "secret.anchor";
4
4
  export type AuditEnvelopeSensitivity = "public" | "internal" | "private" | "credential" | "sensitive";
5
5
  export interface AuditRedactionManifest {
6
6
  manifestId: string;
@@ -32,6 +32,7 @@ export interface RedactAuditEventResult<TPayload> {
32
32
  }
33
33
  /**
34
34
  * Apply field redaction rules and lift manifests to audit path vocabulary (observability-system.detail §2).
35
+ * T-OBS.C.1: now delegates to the unified redactPayload gate (DR-033).
35
36
  */
36
37
  export declare function redactAuditEvent<TPayload extends object>(payload: TPayload): RedactAuditEventResult<TPayload>;
37
38
  export interface BuildAuditEnvelopeInput<TPayload extends object> {
@@ -11,7 +11,7 @@
11
11
  * Test coverage: tests/unit/observability/audit-envelope.test.ts
12
12
  */
13
13
  import * as crypto from "node:crypto";
14
- import { redactEvent } from "../redaction/manifest.js";
14
+ import { redactPayload } from "../redaction/policy.js";
15
15
  function fieldPathToAuditPath(field) {
16
16
  if (field.startsWith("/")) {
17
17
  return field;
@@ -34,16 +34,17 @@ function mapSensitivity(level) {
34
34
  }
35
35
  /**
36
36
  * Apply field redaction rules and lift manifests to audit path vocabulary (observability-system.detail §2).
37
+ * T-OBS.C.1: now delegates to the unified redactPayload gate (DR-033).
37
38
  */
38
39
  export function redactAuditEvent(payload) {
39
- const { redacted, manifest } = redactEvent(payload);
40
+ const { payload: redacted, manifest } = redactPayload(payload);
40
41
  const redaction = {
41
- manifestId: manifest.id,
42
- maskedPaths: manifest.maskedFields.map(fieldPathToAuditPath),
43
- erasedPaths: manifest.erasedFields.map(fieldPathToAuditPath),
44
- hashedPaths: manifest.hashedFields.map(fieldPathToAuditPath),
42
+ manifestId: `rm:${Date.now()}:${crypto.randomBytes(4).toString("hex")}`,
43
+ maskedPaths: manifest.maskedPaths.map(fieldPathToAuditPath),
44
+ erasedPaths: manifest.erasedPaths.map(fieldPathToAuditPath),
45
+ hashedPaths: manifest.hashedPaths.map(fieldPathToAuditPath),
45
46
  contentRefPaths: [],
46
- sensitivity: mapSensitivity(manifest.sensitivityLevel),
47
+ sensitivity: mapSensitivity(manifest.sensitivity),
47
48
  };
48
49
  return { payload: redacted, redaction };
49
50
  }
@@ -0,0 +1,66 @@
1
+ {
2
+ "$schema": "audit-family-registry/v1",
3
+ "description": "Registered audit families for all 8 v7 systems. Unknown families are rejected at write time.",
4
+ "families": [
5
+ {
6
+ "family": "heartbeat.decision",
7
+ "plane": "decision",
8
+ "systemId": "control-plane-system",
9
+ "description": "Heartbeat decision cycle trace"
10
+ },
11
+ {
12
+ "family": "delivery",
13
+ "plane": "delivery",
14
+ "systemId": "guidance-voice-system",
15
+ "description": "Guidance delivery attempt and outcome"
16
+ },
17
+ {
18
+ "family": "source_coverage",
19
+ "plane": "source_coverage",
20
+ "systemId": "observability-health-system",
21
+ "description": "Source grounding and coverage assertions"
22
+ },
23
+ {
24
+ "family": "guidance.grounding",
25
+ "plane": "governance",
26
+ "systemId": "guidance-voice-system",
27
+ "description": "Guidance draft grounding and voice audit"
28
+ },
29
+ {
30
+ "family": "host_capability",
31
+ "plane": "telemetry",
32
+ "systemId": "runtime-ops-system",
33
+ "description": "Host capability probe and environment telemetry"
34
+ },
35
+ {
36
+ "family": "connector.attempt",
37
+ "plane": "telemetry",
38
+ "systemId": "connector-system",
39
+ "description": "Connector execution attempt and circuit breaker events"
40
+ },
41
+ {
42
+ "family": "state.governance",
43
+ "plane": "governance",
44
+ "systemId": "state-memory-system",
45
+ "description": "State mutation governance and write validation"
46
+ },
47
+ {
48
+ "family": "narrative.trace",
49
+ "plane": "telemetry",
50
+ "systemId": "observability-health-system",
51
+ "description": "Narrative timeline change trace"
52
+ },
53
+ {
54
+ "family": "dream.trace",
55
+ "plane": "telemetry",
56
+ "systemId": "dream-quiet-system",
57
+ "description": "Dream and quiet pipeline execution trace"
58
+ },
59
+ {
60
+ "family": "body.tool_experience",
61
+ "plane": "telemetry",
62
+ "systemId": "body-tool-system",
63
+ "description": "Tool experience logging and affordance map events"
64
+ }
65
+ ]
66
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Audit family registry runtime (DR-040).
3
+ *
4
+ * Core logic:
5
+ * - Loads registered families from `audit-family-registry.json`.
6
+ * - Provides lookup and validation for audit write operations.
7
+ * - Rejects writes from unknown (unregistered) families with
8
+ * `unknown_audit_family` error.
9
+ * - Covers all 8 v7 system boundaries.
10
+ *
11
+ * Dependencies: audit-family-registry.json (co-located).
12
+ * Boundary: Called before any audit write to validate family membership.
13
+ * Test coverage: tests/unit/observability/family-registry.test.ts
14
+ */
15
+ export interface AuditFamilyEntry {
16
+ family: string;
17
+ plane: string;
18
+ systemId: string;
19
+ description: string;
20
+ }
21
+ export interface AuditFamilyRegistryData {
22
+ $schema: string;
23
+ description: string;
24
+ families: AuditFamilyEntry[];
25
+ }
26
+ export declare class AuditFamilyRegistry {
27
+ private readonly entries;
28
+ constructor(families: readonly AuditFamilyEntry[]);
29
+ isRegistered(family: string): boolean;
30
+ getEntry(family: string): AuditFamilyEntry | undefined;
31
+ validateFamily(family: string): {
32
+ ok: true;
33
+ } | {
34
+ ok: false;
35
+ error: string;
36
+ };
37
+ listFamilies(): readonly AuditFamilyEntry[];
38
+ listSystemIds(): string[];
39
+ familiesForSystem(systemId: string): AuditFamilyEntry[];
40
+ get size(): number;
41
+ }
42
+ export declare function loadAuditFamilyRegistry(): AuditFamilyRegistry;
43
+ export declare function createAuditFamilyRegistry(families: readonly AuditFamilyEntry[]): AuditFamilyRegistry;
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Audit family registry runtime (DR-040).
3
+ *
4
+ * Core logic:
5
+ * - Loads registered families from `audit-family-registry.json`.
6
+ * - Provides lookup and validation for audit write operations.
7
+ * - Rejects writes from unknown (unregistered) families with
8
+ * `unknown_audit_family` error.
9
+ * - Covers all 8 v7 system boundaries.
10
+ *
11
+ * Dependencies: audit-family-registry.json (co-located).
12
+ * Boundary: Called before any audit write to validate family membership.
13
+ * Test coverage: tests/unit/observability/family-registry.test.ts
14
+ */
15
+ import { readFileSync } from "node:fs";
16
+ import { fileURLToPath } from "node:url";
17
+ import path from "node:path";
18
+ export class AuditFamilyRegistry {
19
+ entries;
20
+ constructor(families) {
21
+ const map = new Map();
22
+ for (const entry of families) {
23
+ map.set(entry.family, entry);
24
+ }
25
+ this.entries = map;
26
+ }
27
+ isRegistered(family) {
28
+ return this.entries.has(family);
29
+ }
30
+ getEntry(family) {
31
+ return this.entries.get(family);
32
+ }
33
+ validateFamily(family) {
34
+ if (this.entries.has(family)) {
35
+ return { ok: true };
36
+ }
37
+ return { ok: false, error: `unknown_audit_family: ${family}` };
38
+ }
39
+ listFamilies() {
40
+ return [...this.entries.values()];
41
+ }
42
+ listSystemIds() {
43
+ const ids = new Set();
44
+ for (const entry of this.entries.values()) {
45
+ ids.add(entry.systemId);
46
+ }
47
+ return [...ids].sort();
48
+ }
49
+ familiesForSystem(systemId) {
50
+ return [...this.entries.values()].filter((e) => e.systemId === systemId);
51
+ }
52
+ get size() {
53
+ return this.entries.size;
54
+ }
55
+ }
56
+ let defaultRegistry;
57
+ export function loadAuditFamilyRegistry() {
58
+ if (defaultRegistry) {
59
+ return defaultRegistry;
60
+ }
61
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
62
+ const jsonPath = path.join(__dirname, "audit-family-registry.json");
63
+ const raw = readFileSync(jsonPath, "utf-8");
64
+ const data = JSON.parse(raw);
65
+ defaultRegistry = new AuditFamilyRegistry(data.families);
66
+ return defaultRegistry;
67
+ }
68
+ export function createAuditFamilyRegistry(families) {
69
+ return new AuditFamilyRegistry(families);
70
+ }
@@ -3,7 +3,7 @@ export * as obsSchema from "./db/schema/index.js";
3
3
  export { buildAuditEnvelope, computeAuditRecordHash, redactAuditEvent, auditManifestFromFieldManifest, type AuditEnvelope, type AuditEnvelopeSensitivity, type AuditEventFamily, type AuditPlane, type AuditRedactionManifest, type AuditIntegrity, type BuildAuditEnvelopeInput, type RedactAuditEventResult, } from "./audit/audit-envelope.js";
4
4
  export { AppendOnlyAuditStore } from "./audit/append-only-audit-store.js";
5
5
  export { verifyAuditHashChain, createAppendOnlyAuditStoreRangeLoader, type AuditExportRange, type AuditHashChainVerificationReport, type AuditHashChainVerificationStatus, type VerifyAuditHashChainDeps, } from "./audit/verify-audit-hash-chain.js";
6
- export { REDACTION_CONFIG, DEFAULT_REDACTION_POLICY, getFieldRedactionRule, type RedactionPolicy, type RedactionRule, type SensitivityLevel, } from "./redaction/policy.js";
6
+ export { REDACTION_CONFIG, DEFAULT_REDACTION_POLICY, getFieldRedactionRule, redactPayload, type RedactionPolicy, type RedactionRule, type SensitivityLevel, type RedactPayloadManifest, type RedactPayloadResult, } from "./redaction/policy.js";
7
7
  export { redactEvent, createEmptyManifest, mergeManifests, type RedactionManifest, type RedactionResult, } from "./redaction/manifest.js";
8
8
  export { DecisionLedger, type QuietLifecycleEvent, type OutreachDecision, type HeartbeatDecisionEvent } from "./services/decision-ledger.js";
9
9
  export { GovernanceAudit, type CredentialLifecycleAudit } from "./services/governance-audit.js";
@@ -18,3 +18,8 @@ export type { EvidenceQuery, EvidenceBundle, EvidenceResolutionPlan, GovernanceE
18
18
  export { projectReflectionAudit, type ReflectionAudit, type ReflectionAuditProjection, } from "./projections/reflection-audit.js";
19
19
  export { projectOutreachQualityAudit, type OutreachQualityAudit, type OutreachQualityProjection, } from "./projections/outreach-quality-audit.js";
20
20
  export { projectGuidanceParticipationAudit, type GuidanceParticipationAudit, type GuidanceParticipationProjection, } from "./projections/guidance-audit.js";
21
+ export { getSelfHealthSnapshot, registerHealthProbe, unregisterHealthProbe, clearHealthProbeRegistry, ensureMinimumProbes, getRegisteredProbes, MINIMUM_REQUIRED_DIMENSIONS, type HealthStatus, type DimensionHealth, type SelfHealthSnapshot, type HealthProbeFunction, type RegisteredProbe, type HealthProbeScope, } from "./services/self-health-snapshot.js";
22
+ export { generateHeartbeatDigest, type HeartbeatDigest, type HeartbeatDigestAssemblerDeps, type StateMemoryDigestPort, type ConnectorDaySummary, type GoalDaySummary, type QuietDreamDaySummary, type HealthDaySummary, type DeliveryProofRef, type DigestDeliveryAdapter, type DigestDeliveryResult, } from "./services/heartbeat-digest-assembler.js";
23
+ export { queryNarrativeTimeline, queryNarrativeDiff, encodeCursor, decodeCursor, NarrativeQueryRangeError, NarrativeVersionNotFoundError, type NarrativeTimelineEntry, type NarrativeTimelinePage, type NarrativeFieldChange, type NarrativeDiff, type NarrativeTimelineRow, type NarrativeSnapshotRow, type NarrativeTimelinePort, type NarrativeTimelineDeps, } from "./services/narrative-timeline-query-service.js";
24
+ export { viewSecretAnchor, type RuntimeSecretAnchorView, type SecretAnchorStatus, type HealthProbeReasonCode, type RecoveryStep, type SampleDecryptResult, type SecretAnchorRuntimeOpsPort, type SecretAnchorCredentialPort, type SecretAnchorDeps, } from "./services/runtime-secret-anchor-view.js";
25
+ export { writeRestoreAudit, type RestoreAuditEvent, type RestoreTarget, type RestoreTrigger, type WriteRestoreAuditResult, } from "./services/restore-audit-service.js";
@@ -3,7 +3,7 @@ export * as obsSchema from "./db/schema/index.js";
3
3
  export { buildAuditEnvelope, computeAuditRecordHash, redactAuditEvent, auditManifestFromFieldManifest, } from "./audit/audit-envelope.js";
4
4
  export { AppendOnlyAuditStore } from "./audit/append-only-audit-store.js";
5
5
  export { verifyAuditHashChain, createAppendOnlyAuditStoreRangeLoader, } from "./audit/verify-audit-hash-chain.js";
6
- export { REDACTION_CONFIG, DEFAULT_REDACTION_POLICY, getFieldRedactionRule, } from "./redaction/policy.js";
6
+ export { REDACTION_CONFIG, DEFAULT_REDACTION_POLICY, getFieldRedactionRule, redactPayload, } from "./redaction/policy.js";
7
7
  export { redactEvent, createEmptyManifest, mergeManifests, } from "./redaction/manifest.js";
8
8
  export { DecisionLedger } from "./services/decision-ledger.js";
9
9
  export { GovernanceAudit } from "./services/governance-audit.js";
@@ -17,3 +17,8 @@ export { EvidenceQueryEngine, } from "./query/evidence-query-engine.js";
17
17
  export { projectReflectionAudit, } from "./projections/reflection-audit.js";
18
18
  export { projectOutreachQualityAudit, } from "./projections/outreach-quality-audit.js";
19
19
  export { projectGuidanceParticipationAudit, } from "./projections/guidance-audit.js";
20
+ export { getSelfHealthSnapshot, registerHealthProbe, unregisterHealthProbe, clearHealthProbeRegistry, ensureMinimumProbes, getRegisteredProbes, MINIMUM_REQUIRED_DIMENSIONS, } from "./services/self-health-snapshot.js";
21
+ export { generateHeartbeatDigest, } from "./services/heartbeat-digest-assembler.js";
22
+ export { queryNarrativeTimeline, queryNarrativeDiff, encodeCursor, decodeCursor, NarrativeQueryRangeError, NarrativeVersionNotFoundError, } from "./services/narrative-timeline-query-service.js";
23
+ export { viewSecretAnchor, } from "./services/runtime-secret-anchor-view.js";
24
+ export { writeRestoreAudit, } from "./services/restore-audit-service.js";
@@ -1,7 +1,7 @@
1
1
  export declare const REDACTION_CONFIG: {
2
- readonly maskedFieldNames: readonly ["token", "access_token", "refresh_token", "api_key", "apiSecret", "secret", "password", "bearer_token", "authorization", "node_secret"];
3
- readonly eraseFieldNames: readonly ["full_message", "full_post", "private_message", "prompt", "system_prompt", "completion", "response_content"];
4
- readonly hashFieldNames: readonly ["user_id", "session_id", "trace_id", "content_hash"];
2
+ readonly maskedFieldNames: readonly ["token", "access_token", "refresh_token", "api_key", "apiSecret", "secret", "password", "bearer_token", "authorization", "node_secret", "encryption_key", "key_material"];
3
+ readonly eraseFieldNames: readonly ["full_message", "full_post", "private_message", "prompt", "system_prompt", "completion", "response_content", "raw_payload", "credential_value", "raw_prompt"];
4
+ readonly hashFieldNames: readonly ["user_id", "session_id", "trace_id", "content_hash", "message_hash"];
5
5
  readonly sensitivityLevels: readonly ["public", "internal", "confidential", "restricted"];
6
6
  };
7
7
  export type SensitivityLevel = (typeof REDACTION_CONFIG)["sensitivityLevels"][number];
@@ -17,3 +17,24 @@ export interface RedactionPolicy {
17
17
  }
18
18
  export declare const DEFAULT_REDACTION_POLICY: RedactionPolicy;
19
19
  export declare function getFieldRedactionRule(fieldName: string, policy?: RedactionPolicy): RedactionRule;
20
+ export interface RedactPayloadManifest {
21
+ maskedPaths: string[];
22
+ erasedPaths: string[];
23
+ hashedPaths: string[];
24
+ sensitivity: SensitivityLevel;
25
+ }
26
+ export interface RedactPayloadResult<T> {
27
+ payload: T;
28
+ manifest: RedactPayloadManifest;
29
+ }
30
+ /**
31
+ * Unified redaction gate — all audit-bound payloads must pass through this
32
+ * before persistence. Recursively applies mask/erase/hash rules from the
33
+ * active RedactionPolicy, preserving object shape (erase → null, not delete).
34
+ *
35
+ * Boundary:
36
+ * - Arrays are not recursed (avoid unbounded complexity).
37
+ * - erase fields become null so downstream JSON schema stays stable.
38
+ * - hash uses SHA-256 of the stringified original value.
39
+ */
40
+ export declare function redactPayload<T extends object>(payload: T, policy?: RedactionPolicy): RedactPayloadResult<T>;