@haaaiawd/second-nature 0.2.0 → 0.2.2

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "second-nature",
3
3
  "name": "Second Nature",
4
- "version": "0.1.51",
4
+ "version": "0.2.2",
5
5
  "description": "OpenClaw native plugin with synchronous surface registration and bundled runtime spine. Set SECOND_NATURE_WORKSPACE_ROOT or tool workspaceRoot to the same path as the agent workspace. Agent inner guide is packaged as agent-inner-guide.md. v7 ops surface: self_health, tool_affordance, heartbeat_digest, snapshot:capture, narrative:diff, timeline, restore, runtime_secret_bootstrap, connector:run, guidance_payload.",
6
6
  "activation": {
7
7
  "onStartup": true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@haaaiawd/second-nature",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "OpenClaw native plugin with synchronous registration, a packaged runtime artifact, and operator-facing status/explain flows.",
5
5
  "keywords": [
6
6
  "openclaw",
@@ -5,6 +5,7 @@ import { createActionBridge } from "./action-bridge.js";
5
5
  import { createCliCommands, } from "./commands/index.js";
6
6
  import { createOpsRouter } from "./ops/ops-router.js";
7
7
  import { createCliReadModels, } from "./read-models/index.js";
8
+ import { AppendOnlyAuditStore } from "../observability/audit/append-only-audit-store.js";
8
9
  import { resolvePackagedRuntime } from "./runtime/runtime-artifact-boundary.js";
9
10
  import { createRestoreSnapshotStore, } from "../storage/services/restore-snapshot-store.js";
10
11
  import { createRuntimeDecisionRecorder, } from "../observability/services/runtime-decision-recorder.js";
@@ -221,12 +222,13 @@ export function createCliRuntimeDeps(overrides = {}) {
221
222
  const stateDb = overrides.stateDb ?? createStateDatabase();
222
223
  const observabilityDb = overrides.observabilityDb ?? createObservabilityDatabase();
223
224
  const stateApi = overrides.stateApi ?? createStateAPI(stateDb);
225
+ const auditStore = overrides.auditStore ?? overrides.livedExperienceAuditStore ?? new AppendOnlyAuditStore();
224
226
  const readModels = overrides.readModels ??
225
227
  createCliReadModels({
226
228
  stateDb,
227
229
  observabilityDb,
228
230
  workspaceRoot,
229
- livedExperienceAuditStore: overrides.livedExperienceAuditStore,
231
+ livedExperienceAuditStore: auditStore,
230
232
  });
231
233
  const actionBridge = overrides.actionBridge ?? createActionBridge(stateApi);
232
234
  const runtimeRecorder = overrides.runtimeRecorder ?? createRuntimeDecisionRecorder(observabilityDb);
@@ -261,6 +263,7 @@ export function createCliRuntimeDeps(overrides = {}) {
261
263
  narrativeTimelineDeps,
262
264
  secretAnchorDeps,
263
265
  restoreSnapshotStore,
266
+ auditStore,
264
267
  };
265
268
  }
266
269
  export function createCommandRouter(options = {}) {
@@ -281,6 +284,7 @@ export function createCommandRouter(options = {}) {
281
284
  narrativeTimelineDeps: runtime.narrativeTimelineDeps,
282
285
  secretAnchorDeps: runtime.secretAnchorDeps,
283
286
  restoreSnapshotStore: runtime.restoreSnapshotStore,
287
+ auditStore: runtime.auditStore,
284
288
  });
285
289
  const commands = createCliCommands({
286
290
  readModels: runtime.readModels,
@@ -18,6 +18,7 @@ import type { HeartbeatDigestAssemblerDeps } from "../../observability/services/
18
18
  import type { GoalLifecyclePolicy } from "../../core/second-nature/heartbeat/goal-lifecycle-policy.js";
19
19
  import type { IdleCuriosityPolicy } from "../../core/second-nature/heartbeat/idle-curiosity-policy.js";
20
20
  import type { CircuitBreakerManager } from "../../core/second-nature/body/circuit-breaker/circuit-breaker-manager.js";
21
+ import type { AppendOnlyAuditStore } from "../../observability/audit/append-only-audit-store.js";
21
22
  export type HeartbeatSurfaceStatus = "heartbeat_ok" | "intent_selected" | "denied" | "deferred" | "runtime_carrier_only" | "delivery_unavailable";
22
23
  export interface HeartbeatSurfaceResult {
23
24
  ok: boolean;
@@ -80,5 +81,7 @@ export interface HeartbeatCheckInput {
80
81
  idleCuriosityPolicy?: IdleCuriosityPolicy;
81
82
  /** v7 T-BTS.C.5: circuit breaker manager for connector execution health. */
82
83
  circuitBreakerManager?: CircuitBreakerManager;
84
+ /** T-OBS.R.1: shared audit sink for connector/Quiet events consumed by heartbeat_digest. */
85
+ auditStore?: AppendOnlyAuditStore;
83
86
  }
84
87
  export declare function heartbeatCheck(input: HeartbeatCheckInput): Promise<HeartbeatSurfaceResult>;
@@ -82,6 +82,7 @@ export async function heartbeatCheck(input) {
82
82
  goalLifecyclePolicy: input.goalLifecyclePolicy,
83
83
  idleCuriosityPolicy: input.idleCuriosityPolicy,
84
84
  circuitBreakerManager: input.circuitBreakerManager,
85
+ auditStore: input.auditStore,
85
86
  });
86
87
  try {
87
88
  const cycle = await run(signal);
@@ -26,6 +26,7 @@ import type { ConnectorExecutor, ConnectorResult } from "../../connectors/base/c
26
26
  import type { ExperienceWriter } from "../../core/second-nature/body/tool-experience/experience-writer.js";
27
27
  import type { WetProbeRunner } from "../../connectors/base/wet-probe-runner.js";
28
28
  import type { CapabilityContractRegistryV7 } from "../../connectors/base/manifest-v7.js";
29
+ import type { AppendOnlyAuditStore } from "../../observability/audit/append-only-audit-store.js";
29
30
  import { type HeartbeatCheckInput, type HeartbeatSurfaceResult } from "./heartbeat-surface.js";
30
31
  import type { RuntimeOpsEnvelope } from "./ops-router.js";
31
32
  export interface ManualTriggerContext {
@@ -75,5 +76,6 @@ export interface ManualRunDispatcherDeps {
75
76
  experienceWriter: ExperienceWriter;
76
77
  wetProbeRunner: WetProbeRunner;
77
78
  registryV7: CapabilityContractRegistryV7;
79
+ auditStore?: AppendOnlyAuditStore;
78
80
  }
79
81
  export declare function createManualRunDispatcher(deps: ManualRunDispatcherDeps): ManualRunDispatcher;
@@ -23,6 +23,7 @@
23
23
  * Test coverage: tests/unit/ops/manual-run-dispatcher.test.ts
24
24
  */
25
25
  import * as crypto from "node:crypto";
26
+ import { recordConnectorAttemptAudit } from "../../observability/services/audit-closure-recorders.js";
26
27
  import { heartbeatCheck, } from "./heartbeat-surface.js";
27
28
  function buildManualContext(input) {
28
29
  return {
@@ -54,6 +55,15 @@ export function createManualRunDispatcher(deps) {
54
55
  result: connectorResult,
55
56
  triggerSource: ctx.triggerSource,
56
57
  });
58
+ recordConnectorAttemptAudit({
59
+ auditStore: deps.auditStore,
60
+ platformId: input.platformId,
61
+ capability: input.capabilityId,
62
+ result: connectorResult,
63
+ triggerSource: ctx.triggerSource,
64
+ decisionId,
65
+ intentId,
66
+ });
57
67
  const runResult = {
58
68
  connectorResult,
59
69
  experienceId,
@@ -441,6 +441,7 @@ export function createOpsRouter(deps) {
441
441
  experienceWriter,
442
442
  digestOpts,
443
443
  dreamSchedulePort,
444
+ auditStore: deps.auditStore,
444
445
  goalLifecyclePolicy,
445
446
  idleCuriosityPolicy,
446
447
  circuitBreakerManager,
@@ -780,6 +781,7 @@ export function createOpsRouter(deps) {
780
781
  experienceWriter,
781
782
  wetProbeRunner,
782
783
  registryV7,
784
+ auditStore: deps.auditStore,
783
785
  });
784
786
  return dispatcher.runConnector({
785
787
  platformId,
@@ -26,6 +26,7 @@ import { type HeartbeatDigestAssemblerDeps } from "../../observability/services/
26
26
  import type { GoalLifecyclePolicy } from "../../core/second-nature/heartbeat/goal-lifecycle-policy.js";
27
27
  import type { IdleCuriosityPolicy } from "../../core/second-nature/heartbeat/idle-curiosity-policy.js";
28
28
  import type { CircuitBreakerManager } from "../../core/second-nature/body/circuit-breaker/circuit-breaker-manager.js";
29
+ import type { AppendOnlyAuditStore } from "../../observability/audit/append-only-audit-store.js";
29
30
  export interface WorkspaceHeartbeatRunnerOptions {
30
31
  /** When supplied, the runner persists the cycle so `loadStatus` can read it (T1.2.3). */
31
32
  runtimeRecorder?: RuntimeDecisionRecorder;
@@ -76,6 +77,8 @@ export interface WorkspaceHeartbeatRunnerOptions {
76
77
  */
77
78
  digestWindowHour?: number;
78
79
  };
80
+ /** T-OBS.R.1: shared audit sink for heartbeat connector and Quiet outcomes. */
81
+ auditStore?: AppendOnlyAuditStore;
79
82
  }
80
83
  export declare function loadSnapshotInputsForWorkspaceHeartbeat(readModels: CliReadModels, options?: {
81
84
  state?: StateDatabase;
@@ -145,6 +145,7 @@ export function createWorkspaceHeartbeatRunner(readModels, options = {}) {
145
145
  workspaceRoot: options.workspaceRoot,
146
146
  // v7 T-V7C.C.3: pass Dream schedule port so Quiet completion triggers Dream.
147
147
  dreamSchedulePort: options.dreamSchedulePort,
148
+ auditStore: options.auditStore,
148
149
  }
149
150
  : undefined,
150
151
  connectorExecutor: options.connectorExecutor,
@@ -162,6 +163,7 @@ export function createWorkspaceHeartbeatRunner(readModels, options = {}) {
162
163
  idleCuriosityPolicy: options.idleCuriosityPolicy,
163
164
  // v7 T-BTS.C.5: pass circuit breaker manager for execution health tracking.
164
165
  circuitBreakerManager: options.circuitBreakerManager,
166
+ auditStore: options.auditStore,
165
167
  },
166
168
  });
167
169
  if (options.runtimeRecorder) {
@@ -25,6 +25,7 @@ import type { NarrativeStateStore } from "../../../storage/narrative/narrative-s
25
25
  import type { NarrativeTracePayload } from "../../../observability/services/lived-experience-audit.js";
26
26
  import type { ExperienceWriter } from "../body/tool-experience/experience-writer.js";
27
27
  import type { QuietDreamSchedulePort } from "../quiet/run-source-backed-quiet.js";
28
+ import type { AppendOnlyAuditStore } from "../../../observability/audit/append-only-audit-store.js";
28
29
  import type { GoalLifecyclePolicy } from "./goal-lifecycle-policy.js";
29
30
  import type { IdleCuriosityPolicy } from "./idle-curiosity-policy.js";
30
31
  import type { CircuitBreakerManager } from "../body/circuit-breaker/circuit-breaker-manager.js";
@@ -50,12 +51,14 @@ export interface HeartbeatQuietWorkflowDeps {
50
51
  workspaceRoot: string;
51
52
  /** v7 T-V7C.C.3: when present, a successful Quiet write auto-triggers Dream scheduling. */
52
53
  dreamSchedulePort?: QuietDreamSchedulePort;
54
+ /** T-OBS.R.1: audit sink for Quiet outcomes consumed by heartbeat_digest. */
55
+ auditStore?: AppendOnlyAuditStore;
53
56
  }
54
57
  /**
55
58
  * Resolves the heartbeat outcome for a guard-allowed intent (outreach dispatch, quiet orchestration, or default).
56
59
  * Exported for unit tests (CR-M1 wiring).
57
60
  */
58
- export declare function resolveAllowedIntentResult(intent: CandidateIntent, runtime: HeartbeatRuntimeSnapshot, inputs: SnapshotInputs, signal: HeartbeatSignal, deps: Pick<HeartbeatDeps, "outreachDispatch" | "quietWorkflow" | "connectorExecutor" | "state" | "workspaceRoot" | "experienceWriter" | "circuitBreakerManager">): Promise<HeartbeatCycleResult>;
61
+ export declare function resolveAllowedIntentResult(intent: CandidateIntent, runtime: HeartbeatRuntimeSnapshot, inputs: SnapshotInputs, signal: HeartbeatSignal, deps: Pick<HeartbeatDeps, "outreachDispatch" | "quietWorkflow" | "connectorExecutor" | "state" | "workspaceRoot" | "experienceWriter" | "circuitBreakerManager" | "auditStore">): Promise<HeartbeatCycleResult>;
59
62
  export interface HeartbeatDeps {
60
63
  /** Load snapshot inputs from state-system */
61
64
  loadSnapshotInputs: () => Promise<SnapshotInputs>;
@@ -86,6 +89,8 @@ export interface HeartbeatDeps {
86
89
  idleCuriosityPolicy?: IdleCuriosityPolicy;
87
90
  /** v7 T-BTS.C.5: when present, updates breaker state after connector execution. */
88
91
  circuitBreakerManager?: CircuitBreakerManager;
92
+ /** T-OBS.R.1: shared audit sink for connector attempts consumed by heartbeat_digest. */
93
+ auditStore?: AppendOnlyAuditStore;
89
94
  }
90
95
  /**
91
96
  * Ingest a heartbeat rhythm signal and drive one full decision round.
@@ -10,6 +10,7 @@ import { toCapabilityIntent } from "../orchestrator/effect-dispatcher.js";
10
10
  import { updateNarrativeAfterEffect } from "../orchestrator/narrative-update.js";
11
11
  import { mapLifeEvidence } from "../../../connectors/base/map-life-evidence.js";
12
12
  import { appendLifeEvidence } from "../../../storage/life-evidence/append-life-evidence.js";
13
+ import { recordConnectorAttemptAudit } from "../../../observability/services/audit-closure-recorders.js";
13
14
  /**
14
15
  * Resolves the heartbeat outcome for a guard-allowed intent (outreach dispatch, quiet orchestration, or default).
15
16
  * Exported for unit tests (CR-M1 wiring).
@@ -40,6 +41,7 @@ export async function resolveAllowedIntentResult(intent, runtime, inputs, signal
40
41
  workspaceRoot: deps.quietWorkflow.workspaceRoot,
41
42
  // v7 T-V7C.C.3: pass Dream schedule port so Quiet completion triggers Dream.
42
43
  dreamSchedulePort: deps.quietWorkflow.dreamSchedulePort,
44
+ auditStore: deps.quietWorkflow.auditStore,
43
45
  });
44
46
  return quietRun.result;
45
47
  }
@@ -82,6 +84,15 @@ export async function resolveAllowedIntentResult(intent, runtime, inputs, signal
82
84
  }
83
85
  : undefined,
84
86
  });
87
+ recordConnectorAttemptAudit({
88
+ auditStore: deps.auditStore,
89
+ platformId: intent.platformId,
90
+ capability: toCapabilityIntent(intent),
91
+ result,
92
+ triggerSource: "heartbeat",
93
+ decisionId,
94
+ intentId: intent.id,
95
+ });
85
96
  // T3.3.1: on success, map connector result to life evidence and append.
86
97
  // On failure or empty result, no evidence is fabricated — attempt audit
87
98
  // is already recorded by the connector policy layer telemetry.
@@ -10,6 +10,7 @@ import type { HeartbeatRuntimeSnapshot } from "../heartbeat/runtime-snapshot.js"
10
10
  import type { HeartbeatCycleResult } from "../heartbeat/signal.js";
11
11
  import { type QuietArtifactAck } from "../../../storage/quiet/quiet-artifact-writer.js";
12
12
  import type { UserInterestSnapshot } from "../../../storage/user-interest/types.js";
13
+ import type { AppendOnlyAuditStore } from "../../../observability/audit/append-only-audit-store.js";
13
14
  /**
14
15
  * Minimal port for triggering Dream after Quiet completion (T-V7C.C.3).
15
16
  * Kept narrow so run-source-backed-quiet does not take a hard dependency on dream-scheduler.
@@ -32,6 +33,8 @@ export interface RunSourceBackedQuietParams {
32
33
  workspaceRoot?: string;
33
34
  /** v7 T-V7C.C.3: when present, a successful Quiet artifact write auto-triggers Dream scheduling. */
34
35
  dreamSchedulePort?: QuietDreamSchedulePort;
36
+ /** T-OBS.R.1: when present, Quiet outcomes write audit truth consumed by heartbeat_digest. */
37
+ auditStore?: AppendOnlyAuditStore;
35
38
  }
36
39
  export interface RunSourceBackedQuietResult {
37
40
  result: HeartbeatCycleResult;
@@ -2,6 +2,7 @@ import { isLifeEvidenceSliceEmpty } from "../heartbeat/runtime-snapshot.js";
2
2
  import { writeQuietArtifact } from "../../../storage/quiet/quiet-artifact-writer.js";
3
3
  import { persistQuietArtifactToWorkspace } from "../../../storage/quiet/persist-quiet-artifact.js";
4
4
  import { buildEvidencePack, buildQuietNarrativeGuidance, selectInterestBasis } from "../../../guidance/evidence-guidance.js";
5
+ import { recordQuietArtifactAudit } from "../../../observability/services/audit-closure-recorders.js";
5
6
  function toGuidanceRef(r) {
6
7
  return {
7
8
  id: r.id,
@@ -37,7 +38,7 @@ async function maybeScheduleDreamAfterQuiet(dreamSchedulePort, day) {
37
38
  }
38
39
  }
39
40
  export async function runSourceBackedQuiet(params) {
40
- const { candidate, runtime, day, userInterestSnapshot, workspaceRoot, dreamSchedulePort } = params;
41
+ const { candidate, runtime, day, userInterestSnapshot, workspaceRoot, dreamSchedulePort, auditStore } = params;
41
42
  const empty = isLifeEvidenceSliceEmpty(runtime.lifeEvidence);
42
43
  if (empty) {
43
44
  const input = {
@@ -54,6 +55,15 @@ export async function runSourceBackedQuiet(params) {
54
55
  const p = await persistQuietArtifactToWorkspace(workspaceRoot, ack, input);
55
56
  persistedRelativePath = p.relativePath;
56
57
  }
58
+ recordQuietArtifactAudit({
59
+ auditStore,
60
+ day,
61
+ kind: "empty_state",
62
+ status: "empty",
63
+ reasons: ["quiet_empty_state", "no_fictional_narrative"],
64
+ artifactAck: ack,
65
+ persistedRelativePath,
66
+ });
57
67
  return {
58
68
  result: {
59
69
  scope: "rhythm",
@@ -68,6 +78,13 @@ export async function runSourceBackedQuiet(params) {
68
78
  const guidanceRefs = runtime.lifeEvidence.evidenceRefs.map(toGuidanceRef);
69
79
  const ep = buildEvidencePack(guidanceRefs);
70
80
  if (!ep.ok) {
81
+ recordQuietArtifactAudit({
82
+ auditStore,
83
+ day,
84
+ kind: "daily_report",
85
+ status: "blocked",
86
+ reasons: ep.reasons,
87
+ });
71
88
  return {
72
89
  result: {
73
90
  scope: "rhythm",
@@ -78,6 +95,13 @@ export async function runSourceBackedQuiet(params) {
78
95
  };
79
96
  }
80
97
  if (ep.pack.sensitiveBlocked) {
98
+ recordQuietArtifactAudit({
99
+ auditStore,
100
+ day,
101
+ kind: "daily_report",
102
+ status: "blocked",
103
+ reasons: ["quiet_guidance_sensitive_source_blocked"],
104
+ });
81
105
  return {
82
106
  result: {
83
107
  scope: "rhythm",
@@ -128,6 +152,14 @@ export async function runSourceBackedQuiet(params) {
128
152
  outline: claims.map((c) => c.text),
129
153
  });
130
154
  if (gq.status === "unavailable") {
155
+ recordQuietArtifactAudit({
156
+ auditStore,
157
+ day,
158
+ kind: "daily_report",
159
+ status: "blocked",
160
+ reasons: gq.reasons,
161
+ artifactAck: ack,
162
+ });
131
163
  return {
132
164
  result: {
133
165
  scope: "rhythm",
@@ -147,6 +179,15 @@ export async function runSourceBackedQuiet(params) {
147
179
  const reasons = ["quiet_artifact_written", ...gq.hints.slice(0, 2)];
148
180
  if (dreamReason)
149
181
  reasons.push(dreamReason);
182
+ recordQuietArtifactAudit({
183
+ auditStore,
184
+ day,
185
+ kind: "daily_report",
186
+ status: "completed",
187
+ reasons,
188
+ artifactAck: ack,
189
+ persistedRelativePath,
190
+ });
150
191
  return {
151
192
  result: {
152
193
  scope: "rhythm",
@@ -0,0 +1,31 @@
1
+ import type { ConnectorResult } from "../../connectors/base/contract.js";
2
+ import type { QuietArtifactAck } from "../../storage/quiet/quiet-artifact-writer.js";
3
+ import type { QuietArtifactWrite } from "../../storage/quiet/quiet-artifact-types.js";
4
+ import type { AppendOnlyAuditStore } from "../audit/append-only-audit-store.js";
5
+ export type ConnectorAttemptAuditOutcome = "success" | "failure" | "circuit_open" | "blocked";
6
+ export interface RecordConnectorAttemptAuditInput {
7
+ auditStore?: AppendOnlyAuditStore;
8
+ platformId: string;
9
+ capability: string;
10
+ result: ConnectorResult<unknown>;
11
+ triggerSource: "manual_run" | "heartbeat";
12
+ decisionId?: string;
13
+ intentId?: string;
14
+ createdAt?: string;
15
+ }
16
+ export interface RecordQuietArtifactAuditInput {
17
+ auditStore?: AppendOnlyAuditStore;
18
+ day: string;
19
+ kind: QuietArtifactWrite["kind"];
20
+ status: "completed" | "empty" | "blocked" | "failed";
21
+ reasons: string[];
22
+ artifactAck?: QuietArtifactAck;
23
+ persistedRelativePath?: string;
24
+ createdAt?: string;
25
+ }
26
+ export declare function recordConnectorAttemptAudit(input: RecordConnectorAttemptAuditInput): {
27
+ eventId?: string;
28
+ };
29
+ export declare function recordQuietArtifactAudit(input: RecordQuietArtifactAuditInput): {
30
+ eventId?: string;
31
+ };
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Audit-backed closure recorders for connector attempts and Quiet artifacts.
3
+ *
4
+ * Core logic: write small redacted audit envelopes for runtime actions that
5
+ * heartbeat_digest depends on. Payloads contain outcome and source linkage only;
6
+ * raw connector payloads and credentials are deliberately excluded.
7
+ *
8
+ * Dependencies: AppendOnlyAuditStore and buildAuditEnvelope hash-chain helpers.
9
+ * Boundary: does not execute connectors, write Quiet artifacts, or mutate state.
10
+ *
11
+ * Test coverage: tests/unit/ops/manual-run-dispatcher.test.ts,
12
+ * tests/unit/observability/heartbeat-digest-assembler.test.ts.
13
+ */
14
+ import { randomUUID } from "node:crypto";
15
+ import { buildAuditEnvelope } from "../audit/audit-envelope.js";
16
+ function mapConnectorOutcome(result) {
17
+ if (result.status === "success")
18
+ return "success";
19
+ if (result.failureClass === "cooldown_blocked")
20
+ return "blocked";
21
+ if (result.failureClass === "platform_unavailable")
22
+ return "circuit_open";
23
+ return "failure";
24
+ }
25
+ export function recordConnectorAttemptAudit(input) {
26
+ if (!input.auditStore)
27
+ return {};
28
+ const createdAt = input.createdAt ?? new Date().toISOString();
29
+ const family = "connector.attempt";
30
+ const envelope = buildAuditEnvelope({
31
+ family,
32
+ plane: "telemetry",
33
+ traceId: `connector_attempt:${input.triggerSource}:${randomUUID()}`,
34
+ sequence: input.auditStore.list().length + 1,
35
+ previousHash: input.auditStore.lastRecordHash(family),
36
+ createdAt,
37
+ payload: {
38
+ platformId: input.platformId,
39
+ capability: input.capability,
40
+ outcome: mapConnectorOutcome(input.result),
41
+ triggerSource: input.triggerSource,
42
+ status: input.result.status,
43
+ failureClass: input.result.failureClass,
44
+ channel: input.result.metadata.channel,
45
+ latencyMs: input.result.metadata.latencyMs,
46
+ degraded: Boolean(input.result.metadata.degraded),
47
+ decisionId: input.decisionId,
48
+ intentId: input.intentId,
49
+ createdAt,
50
+ },
51
+ });
52
+ input.auditStore.append(envelope);
53
+ return { eventId: envelope.eventId };
54
+ }
55
+ export function recordQuietArtifactAudit(input) {
56
+ if (!input.auditStore)
57
+ return {};
58
+ const createdAt = input.createdAt ?? new Date().toISOString();
59
+ const family = "source_coverage";
60
+ const sourceRefs = input.artifactAck ? [input.artifactAck.artifactRef] : [];
61
+ const envelope = buildAuditEnvelope({
62
+ family,
63
+ plane: "source_coverage",
64
+ traceId: `quiet_artifact:${input.day}:${randomUUID()}`,
65
+ sequence: input.auditStore.list().length + 1,
66
+ previousHash: input.auditStore.lastRecordHash(family),
67
+ createdAt,
68
+ payload: {
69
+ auditId: `quiet-audit:${input.day}:${randomUUID()}`,
70
+ traceId: `quiet_trace:${input.day}`,
71
+ subjectType: "quiet_artifact",
72
+ subjectRef: input.artifactAck?.artifactRef.uri ?? `quiet:${input.day}:${input.kind}`,
73
+ day: input.day,
74
+ kind: input.kind,
75
+ status: input.status,
76
+ usedSourceRefs: sourceRefs,
77
+ unresolvedRefs: [],
78
+ coverageRatio: input.artifactAck?.sourceCoverage.coverageRatio ?? 0,
79
+ unsupportedClaims: input.artifactAck?.sourceCoverage.unsupportedClaims ?? [],
80
+ persistedRelativePath: input.persistedRelativePath,
81
+ reasonCodes: input.reasons,
82
+ createdAt,
83
+ },
84
+ });
85
+ input.auditStore.append(envelope);
86
+ return { eventId: envelope.eventId };
87
+ }
@@ -97,10 +97,21 @@ function aggregateHealthSummary(events, dateStr) {
97
97
  }
98
98
  function aggregateQuietDreamFromAudit(events, dateStr) {
99
99
  const dreamEvents = filterByDate(events, "dream.trace", dateStr);
100
+ const quietEvents = filterByDate(events, "source_coverage", dateStr)
101
+ .filter((ev) => ev.payload.subjectType === "quiet_artifact");
102
+ let quietRuns = 0;
103
+ let quietSucceeded = 0;
100
104
  let dreamRuns = 0;
101
105
  let dreamAccepted = 0;
102
106
  let dreamSkipped = 0;
103
107
  const dreamSkipReasons = [];
108
+ for (const ev of quietEvents) {
109
+ const payload = ev.payload;
110
+ quietRuns++;
111
+ if (payload.status === "completed" || payload.status === "empty") {
112
+ quietSucceeded++;
113
+ }
114
+ }
104
115
  for (const ev of dreamEvents) {
105
116
  const payload = ev.payload;
106
117
  if (payload.event === "dream_started")
@@ -113,10 +124,9 @@ function aggregateQuietDreamFromAudit(events, dateStr) {
113
124
  dreamSkipReasons.push(payload.skipReason);
114
125
  }
115
126
  }
116
- // Quiet stats not yet in audit (narrative.trace not fully wired) — default to 0
117
127
  return {
118
- quietRuns: 0,
119
- quietSucceeded: 0,
128
+ quietRuns,
129
+ quietSucceeded,
120
130
  dreamRuns,
121
131
  dreamAccepted,
122
132
  dreamSkipped,
@@ -176,6 +176,125 @@ const STATE_SCHEMA_SQL = `
176
176
  CREATE INDEX IF NOT EXISTS memory_store_created_at_idx ON memory_store(created_at);
177
177
  CREATE INDEX IF NOT EXISTS memory_store_input_idx ON memory_store(input_memory_store_id);
178
178
  CREATE INDEX IF NOT EXISTS memory_store_dream_run_idx ON memory_store(dream_run_id);
179
+ -- v8 Living Perception Loop entities
180
+ CREATE TABLE IF NOT EXISTS evidence_item (
181
+ id TEXT PRIMARY KEY,
182
+ created_at TEXT NOT NULL,
183
+ platform_id TEXT NOT NULL,
184
+ content_hash TEXT NOT NULL,
185
+ observed_at TEXT NOT NULL,
186
+ sensitivity_hint TEXT,
187
+ source_refs_json TEXT NOT NULL,
188
+ redaction_class TEXT NOT NULL DEFAULT 'none',
189
+ payload_json TEXT,
190
+ lifecycle_status TEXT NOT NULL DEFAULT 'pending'
191
+ );
192
+ CREATE TABLE IF NOT EXISTS perception_card (
193
+ id TEXT PRIMARY KEY,
194
+ created_at TEXT NOT NULL,
195
+ cycle_id TEXT NOT NULL,
196
+ topic TEXT,
197
+ entities_json TEXT,
198
+ novelty TEXT,
199
+ relevance REAL,
200
+ summary TEXT,
201
+ risk_flags_json TEXT,
202
+ confidence REAL,
203
+ review_priority TEXT,
204
+ source_refs_json TEXT NOT NULL,
205
+ redaction_class TEXT NOT NULL DEFAULT 'none',
206
+ payload_json TEXT,
207
+ lifecycle_status TEXT NOT NULL DEFAULT 'pending'
208
+ );
209
+ CREATE TABLE IF NOT EXISTS judgment_verdict (
210
+ id TEXT PRIMARY KEY,
211
+ created_at TEXT NOT NULL,
212
+ cycle_id TEXT NOT NULL,
213
+ perception_card_id TEXT NOT NULL,
214
+ action_kind TEXT NOT NULL,
215
+ confidence REAL,
216
+ reason TEXT,
217
+ risk_posture TEXT,
218
+ source_refs_json TEXT NOT NULL,
219
+ redaction_class TEXT NOT NULL DEFAULT 'none',
220
+ payload_json TEXT,
221
+ lifecycle_status TEXT NOT NULL DEFAULT 'pending'
222
+ );
223
+ CREATE TABLE IF NOT EXISTS action_closure_record (
224
+ id TEXT PRIMARY KEY,
225
+ created_at TEXT NOT NULL,
226
+ cycle_id TEXT NOT NULL,
227
+ proposal_id TEXT,
228
+ decision_id TEXT,
229
+ status TEXT NOT NULL,
230
+ reason TEXT,
231
+ next_state TEXT,
232
+ source_refs_json TEXT NOT NULL,
233
+ redaction_class TEXT NOT NULL DEFAULT 'none',
234
+ payload_json TEXT,
235
+ lifecycle_status TEXT NOT NULL DEFAULT 'closed'
236
+ );
237
+ CREATE TABLE IF NOT EXISTS quiet_daily_review (
238
+ id TEXT PRIMARY KEY,
239
+ created_at TEXT NOT NULL,
240
+ day TEXT NOT NULL,
241
+ closure_count INTEGER NOT NULL DEFAULT 0,
242
+ memory_candidate_count INTEGER NOT NULL DEFAULT 0,
243
+ source_refs_json TEXT NOT NULL,
244
+ redaction_class TEXT NOT NULL DEFAULT 'none',
245
+ payload_json TEXT,
246
+ lifecycle_status TEXT NOT NULL DEFAULT 'pending'
247
+ );
248
+ CREATE TABLE IF NOT EXISTS dream_consolidation_run (
249
+ id TEXT PRIMARY KEY,
250
+ created_at TEXT NOT NULL,
251
+ quiet_review_id TEXT NOT NULL,
252
+ status TEXT NOT NULL,
253
+ reason TEXT,
254
+ source_refs_json TEXT NOT NULL,
255
+ redaction_class TEXT NOT NULL DEFAULT 'none',
256
+ payload_json TEXT,
257
+ lifecycle_status TEXT NOT NULL DEFAULT 'pending'
258
+ );
259
+ CREATE TABLE IF NOT EXISTS long_term_memory_projection (
260
+ id TEXT PRIMARY KEY,
261
+ created_at TEXT NOT NULL,
262
+ candidate_id TEXT NOT NULL,
263
+ topic_key TEXT NOT NULL,
264
+ status TEXT NOT NULL DEFAULT 'candidate',
265
+ source_refs_json TEXT NOT NULL,
266
+ redaction_class TEXT NOT NULL DEFAULT 'none',
267
+ payload_json TEXT,
268
+ lifecycle_status TEXT NOT NULL DEFAULT 'candidate'
269
+ );
270
+ CREATE TABLE IF NOT EXISTS heartbeat_cycle_trace (
271
+ id TEXT PRIMARY KEY,
272
+ cycle_sequence INTEGER NOT NULL,
273
+ heartbeat_started_at TEXT NOT NULL,
274
+ heartbeat_completed_at TEXT,
275
+ input_count INTEGER NOT NULL DEFAULT 0,
276
+ output_count INTEGER NOT NULL DEFAULT 0,
277
+ expected_downstream_by_cycle INTEGER,
278
+ status TEXT NOT NULL,
279
+ source_refs_json TEXT,
280
+ redaction_class TEXT NOT NULL DEFAULT 'none',
281
+ payload_json TEXT,
282
+ lifecycle_status TEXT NOT NULL DEFAULT 'started'
283
+ );
284
+ CREATE TABLE IF NOT EXISTS loop_stage_event (
285
+ id TEXT PRIMARY KEY,
286
+ cycle_id TEXT NOT NULL,
287
+ cycle_sequence INTEGER NOT NULL,
288
+ stage TEXT NOT NULL,
289
+ status TEXT NOT NULL,
290
+ reason TEXT,
291
+ source_refs_json TEXT NOT NULL,
292
+ redaction_class TEXT NOT NULL DEFAULT 'none',
293
+ occurred_at TEXT NOT NULL,
294
+ expected_downstream_by_cycle INTEGER,
295
+ payload_json TEXT,
296
+ lifecycle_status TEXT NOT NULL DEFAULT 'started'
297
+ );
179
298
  `;
180
299
  function resolveDbPath(filename) {
181
300
  if (path.isAbsolute(filename) || filename === ":memory:") {
@@ -5,9 +5,11 @@ import { V7_001_FOUNDATION } from "./v7-001-foundation.js";
5
5
  import { V7_002_EFFECT_COMMIT_LEDGER } from "./v7-002-effect-commit-ledger.js";
6
6
  import { V7_003_CIRCUIT_BREAKER } from "./v7-003-circuit-breaker.js";
7
7
  import { V7_004_BEHAVIOR_PROMOTION } from "./v7-004-behavior-promotion.js";
8
+ import { V8_001_LIVING_PERCEPTION_LOOP } from "./v8-001-living-perception-loop.js";
8
9
  export const ALL_MIGRATIONS = [
9
10
  V7_001_FOUNDATION,
10
11
  V7_002_EFFECT_COMMIT_LEDGER,
11
12
  V7_003_CIRCUIT_BREAKER,
12
13
  V7_004_BEHAVIOR_PROMOTION,
14
+ V8_001_LIVING_PERCEPTION_LOOP,
13
15
  ];
@@ -0,0 +1,10 @@
1
+ /**
2
+ * v8-001 Living Perception Loop migration — adds v8 entity tables.
3
+ *
4
+ * Dependencies: v7-004 (behavior_promotion already exists).
5
+ */
6
+ export declare const V8_001_LIVING_PERCEPTION_LOOP: {
7
+ version: number;
8
+ label: string;
9
+ sql: string;
10
+ };
@@ -0,0 +1,129 @@
1
+ /**
2
+ * v8-001 Living Perception Loop migration — adds v8 entity tables.
3
+ *
4
+ * Dependencies: v7-004 (behavior_promotion already exists).
5
+ */
6
+ export const V8_001_LIVING_PERCEPTION_LOOP = {
7
+ version: 5,
8
+ label: "v8-living-perception-loop",
9
+ sql: `
10
+ CREATE TABLE IF NOT EXISTS evidence_item (
11
+ id TEXT PRIMARY KEY,
12
+ created_at TEXT NOT NULL,
13
+ platform_id TEXT NOT NULL,
14
+ content_hash TEXT NOT NULL,
15
+ observed_at TEXT NOT NULL,
16
+ sensitivity_hint TEXT,
17
+ source_refs_json TEXT NOT NULL,
18
+ redaction_class TEXT NOT NULL DEFAULT 'none',
19
+ payload_json TEXT,
20
+ lifecycle_status TEXT NOT NULL DEFAULT 'pending'
21
+ );
22
+ CREATE TABLE IF NOT EXISTS perception_card (
23
+ id TEXT PRIMARY KEY,
24
+ created_at TEXT NOT NULL,
25
+ cycle_id TEXT NOT NULL,
26
+ topic TEXT,
27
+ entities_json TEXT,
28
+ novelty TEXT,
29
+ relevance REAL,
30
+ summary TEXT,
31
+ risk_flags_json TEXT,
32
+ confidence REAL,
33
+ review_priority TEXT,
34
+ source_refs_json TEXT NOT NULL,
35
+ redaction_class TEXT NOT NULL DEFAULT 'none',
36
+ payload_json TEXT,
37
+ lifecycle_status TEXT NOT NULL DEFAULT 'pending'
38
+ );
39
+ CREATE TABLE IF NOT EXISTS judgment_verdict (
40
+ id TEXT PRIMARY KEY,
41
+ created_at TEXT NOT NULL,
42
+ cycle_id TEXT NOT NULL,
43
+ perception_card_id TEXT NOT NULL,
44
+ action_kind TEXT NOT NULL,
45
+ confidence REAL,
46
+ reason TEXT,
47
+ risk_posture TEXT,
48
+ source_refs_json TEXT NOT NULL,
49
+ redaction_class TEXT NOT NULL DEFAULT 'none',
50
+ payload_json TEXT,
51
+ lifecycle_status TEXT NOT NULL DEFAULT 'pending'
52
+ );
53
+ CREATE TABLE IF NOT EXISTS action_closure_record (
54
+ id TEXT PRIMARY KEY,
55
+ created_at TEXT NOT NULL,
56
+ cycle_id TEXT NOT NULL,
57
+ proposal_id TEXT,
58
+ decision_id TEXT,
59
+ status TEXT NOT NULL,
60
+ reason TEXT,
61
+ next_state TEXT,
62
+ source_refs_json TEXT NOT NULL,
63
+ redaction_class TEXT NOT NULL DEFAULT 'none',
64
+ payload_json TEXT,
65
+ lifecycle_status TEXT NOT NULL DEFAULT 'closed'
66
+ );
67
+ CREATE TABLE IF NOT EXISTS quiet_daily_review (
68
+ id TEXT PRIMARY KEY,
69
+ created_at TEXT NOT NULL,
70
+ day TEXT NOT NULL,
71
+ closure_count INTEGER NOT NULL DEFAULT 0,
72
+ memory_candidate_count INTEGER NOT NULL DEFAULT 0,
73
+ source_refs_json TEXT NOT NULL,
74
+ redaction_class TEXT NOT NULL DEFAULT 'none',
75
+ payload_json TEXT,
76
+ lifecycle_status TEXT NOT NULL DEFAULT 'pending'
77
+ );
78
+ CREATE TABLE IF NOT EXISTS dream_consolidation_run (
79
+ id TEXT PRIMARY KEY,
80
+ created_at TEXT NOT NULL,
81
+ quiet_review_id TEXT NOT NULL,
82
+ status TEXT NOT NULL,
83
+ reason TEXT,
84
+ source_refs_json TEXT NOT NULL,
85
+ redaction_class TEXT NOT NULL DEFAULT 'none',
86
+ payload_json TEXT,
87
+ lifecycle_status TEXT NOT NULL DEFAULT 'pending'
88
+ );
89
+ CREATE TABLE IF NOT EXISTS long_term_memory_projection (
90
+ id TEXT PRIMARY KEY,
91
+ created_at TEXT NOT NULL,
92
+ candidate_id TEXT NOT NULL,
93
+ topic_key TEXT NOT NULL,
94
+ status TEXT NOT NULL DEFAULT 'candidate',
95
+ source_refs_json TEXT NOT NULL,
96
+ redaction_class TEXT NOT NULL DEFAULT 'none',
97
+ payload_json TEXT,
98
+ lifecycle_status TEXT NOT NULL DEFAULT 'candidate'
99
+ );
100
+ CREATE TABLE IF NOT EXISTS heartbeat_cycle_trace (
101
+ id TEXT PRIMARY KEY,
102
+ cycle_sequence INTEGER NOT NULL,
103
+ heartbeat_started_at TEXT NOT NULL,
104
+ heartbeat_completed_at TEXT,
105
+ input_count INTEGER NOT NULL DEFAULT 0,
106
+ output_count INTEGER NOT NULL DEFAULT 0,
107
+ expected_downstream_by_cycle INTEGER,
108
+ status TEXT NOT NULL,
109
+ source_refs_json TEXT,
110
+ redaction_class TEXT NOT NULL DEFAULT 'none',
111
+ payload_json TEXT,
112
+ lifecycle_status TEXT NOT NULL DEFAULT 'started'
113
+ );
114
+ CREATE TABLE IF NOT EXISTS loop_stage_event (
115
+ id TEXT PRIMARY KEY,
116
+ cycle_id TEXT NOT NULL,
117
+ cycle_sequence INTEGER NOT NULL,
118
+ stage TEXT NOT NULL,
119
+ status TEXT NOT NULL,
120
+ reason TEXT,
121
+ source_refs_json TEXT NOT NULL,
122
+ redaction_class TEXT NOT NULL DEFAULT 'none',
123
+ occurred_at TEXT NOT NULL,
124
+ expected_downstream_by_cycle INTEGER,
125
+ payload_json TEXT,
126
+ lifecycle_status TEXT NOT NULL DEFAULT 'started'
127
+ );
128
+ `,
129
+ };