@haaaiawd/second-nature 0.1.7 → 0.1.9

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 (184) hide show
  1. package/index.js +429 -96
  2. package/openclaw.plugin.json +1 -1
  3. package/package.json +1 -1
  4. package/runtime/cli/commands/index.d.ts +2 -0
  5. package/runtime/cli/commands/index.js +61 -6
  6. package/runtime/cli/explain/explain-surface-subject.d.ts +8 -0
  7. package/runtime/cli/explain/explain-surface-subject.js +9 -0
  8. package/runtime/cli/explain/format-explanation.d.ts +2 -0
  9. package/runtime/cli/explain/format-explanation.js +2 -0
  10. package/runtime/cli/explain/resolve-subject.js +15 -0
  11. package/runtime/cli/host-capability/classify-delivery.d.ts +14 -0
  12. package/runtime/cli/host-capability/classify-delivery.js +20 -0
  13. package/runtime/cli/host-capability/probe-host-capability.d.ts +2 -0
  14. package/runtime/cli/host-capability/probe-host-capability.js +58 -0
  15. package/runtime/cli/host-capability/record-host-capability.d.ts +6 -0
  16. package/runtime/cli/host-capability/record-host-capability.js +14 -0
  17. package/runtime/cli/host-capability/types.d.ts +71 -0
  18. package/runtime/cli/host-capability/types.js +6 -0
  19. package/runtime/cli/host-smoke/run-host-smoke.d.ts +2 -0
  20. package/runtime/cli/host-smoke/run-host-smoke.js +40 -0
  21. package/runtime/cli/host-smoke/types.d.ts +35 -0
  22. package/runtime/cli/host-smoke/types.js +6 -0
  23. package/runtime/cli/index.js +18 -0
  24. package/runtime/cli/ops/heartbeat-surface.d.ts +35 -0
  25. package/runtime/cli/ops/heartbeat-surface.js +71 -0
  26. package/runtime/cli/ops/ops-router.d.ts +16 -0
  27. package/runtime/cli/ops/ops-router.js +83 -0
  28. package/runtime/cli/ops/show-operator-fallback.d.ts +13 -0
  29. package/runtime/cli/ops/show-operator-fallback.js +22 -0
  30. package/runtime/cli/ops/workspace-heartbeat-runner.d.ts +10 -0
  31. package/runtime/cli/ops/workspace-heartbeat-runner.js +26 -0
  32. package/runtime/cli/read-models/index.d.ts +11 -2
  33. package/runtime/cli/read-models/index.js +50 -0
  34. package/runtime/cli/read-models/operator-explain-map.d.ts +6 -0
  35. package/runtime/cli/read-models/operator-explain-map.js +10 -0
  36. package/runtime/cli/read-models/types.d.ts +5 -1
  37. package/runtime/cli/runtime/runtime-artifact-boundary.d.ts +28 -0
  38. package/runtime/cli/runtime/runtime-artifact-boundary.js +94 -0
  39. package/runtime/connectors/base/contract.d.ts +6 -0
  40. package/runtime/connectors/base/execution-policy.d.ts +47 -0
  41. package/runtime/connectors/base/execution-policy.js +82 -0
  42. package/runtime/connectors/base/index.d.ts +2 -0
  43. package/runtime/connectors/base/index.js +2 -0
  44. package/runtime/connectors/base/manifest.d.ts +55 -2
  45. package/runtime/connectors/base/manifest.js +50 -0
  46. package/runtime/connectors/base/map-life-evidence.d.ts +16 -0
  47. package/runtime/connectors/base/map-life-evidence.js +79 -0
  48. package/runtime/connectors/base/policy-layer.d.ts +2 -0
  49. package/runtime/connectors/base/policy-layer.js +20 -35
  50. package/runtime/connectors/base/route-planner.js +1 -0
  51. package/runtime/connectors/index.d.ts +1 -0
  52. package/runtime/connectors/index.js +1 -0
  53. package/runtime/connectors/near-real/near-real-connector-smoke.d.ts +19 -0
  54. package/runtime/connectors/near-real/near-real-connector-smoke.js +152 -0
  55. package/runtime/core/second-nature/heartbeat/heartbeat-executor.js +2 -0
  56. package/runtime/core/second-nature/heartbeat/heartbeat-loop.d.ts +37 -16
  57. package/runtime/core/second-nature/heartbeat/heartbeat-loop.js +95 -29
  58. package/runtime/core/second-nature/heartbeat/index.d.ts +4 -1
  59. package/runtime/core/second-nature/heartbeat/index.js +4 -1
  60. package/runtime/core/second-nature/heartbeat/run-heartbeat-cycle.d.ts +21 -0
  61. package/runtime/core/second-nature/heartbeat/run-heartbeat-cycle.js +35 -0
  62. package/runtime/core/second-nature/heartbeat/runtime-snapshot.d.ts +28 -0
  63. package/runtime/core/second-nature/heartbeat/runtime-snapshot.js +35 -0
  64. package/runtime/core/second-nature/heartbeat/signal.d.ts +9 -2
  65. package/runtime/core/second-nature/heartbeat/snapshot-builder.d.ts +19 -1
  66. package/runtime/core/second-nature/index.d.ts +8 -0
  67. package/runtime/core/second-nature/index.js +8 -0
  68. package/runtime/core/second-nature/orchestrator/effect-dispatcher.d.ts +1 -1
  69. package/runtime/core/second-nature/orchestrator/effect-dispatcher.js +9 -4
  70. package/runtime/core/second-nature/orchestrator/guard-layer.d.ts +6 -0
  71. package/runtime/core/second-nature/orchestrator/guard-layer.js +76 -20
  72. package/runtime/core/second-nature/orchestrator/intent-planner.d.ts +10 -0
  73. package/runtime/core/second-nature/orchestrator/intent-planner.js +135 -28
  74. package/runtime/core/second-nature/orchestrator/lease-manager.d.ts +1 -1
  75. package/runtime/core/second-nature/orchestrator/lease-manager.js +1 -1
  76. package/runtime/core/second-nature/outreach/build-outreach-draft-request.d.ts +6 -0
  77. package/runtime/core/second-nature/outreach/build-outreach-draft-request.js +63 -0
  78. package/runtime/core/second-nature/outreach/delivery-target.d.ts +26 -0
  79. package/runtime/core/second-nature/outreach/delivery-target.js +70 -0
  80. package/runtime/core/second-nature/outreach/dispatch-user-outreach.d.ts +38 -0
  81. package/runtime/core/second-nature/outreach/dispatch-user-outreach.js +119 -0
  82. package/runtime/core/second-nature/outreach/judge-input-from-snapshot.d.ts +7 -0
  83. package/runtime/core/second-nature/outreach/judge-input-from-snapshot.js +45 -0
  84. package/runtime/core/second-nature/outreach/judge-outreach.d.ts +40 -0
  85. package/runtime/core/second-nature/outreach/judge-outreach.js +121 -0
  86. package/runtime/core/second-nature/quiet/run-source-backed-quiet.d.ts +21 -0
  87. package/runtime/core/second-nature/quiet/run-source-backed-quiet.js +123 -0
  88. package/runtime/core/second-nature/rhythm/planner-rhythm-window.d.ts +15 -0
  89. package/runtime/core/second-nature/rhythm/planner-rhythm-window.js +52 -0
  90. package/runtime/core/second-nature/rhythm/policy-bridge.d.ts +19 -0
  91. package/runtime/core/second-nature/rhythm/policy-bridge.js +34 -0
  92. package/runtime/core/second-nature/types.d.ts +16 -2
  93. package/runtime/guidance/draft-outreach-message.d.ts +7 -0
  94. package/runtime/guidance/draft-outreach-message.js +42 -0
  95. package/runtime/guidance/evidence-guidance.d.ts +40 -0
  96. package/runtime/guidance/evidence-guidance.js +52 -0
  97. package/runtime/guidance/index.d.ts +3 -0
  98. package/runtime/guidance/index.js +3 -0
  99. package/runtime/guidance/outreach-draft-schema.d.ts +228 -0
  100. package/runtime/guidance/outreach-draft-schema.js +80 -0
  101. package/runtime/observability/audit/append-only-audit-store.d.ts +14 -0
  102. package/runtime/observability/audit/append-only-audit-store.js +21 -0
  103. package/runtime/observability/audit/audit-envelope.d.ts +51 -0
  104. package/runtime/observability/audit/audit-envelope.js +130 -0
  105. package/runtime/observability/audit/verify-audit-hash-chain.d.ts +23 -0
  106. package/runtime/observability/audit/verify-audit-hash-chain.js +83 -0
  107. package/runtime/observability/db/index.js +11 -0
  108. package/runtime/observability/db/schema/host-capability-reports.d.ts +180 -0
  109. package/runtime/observability/db/schema/host-capability-reports.js +12 -0
  110. package/runtime/observability/db/schema/index.d.ts +1 -0
  111. package/runtime/observability/db/schema/index.js +1 -0
  112. package/runtime/observability/index.d.ts +7 -0
  113. package/runtime/observability/index.js +7 -0
  114. package/runtime/observability/query/explain-query.d.ts +48 -0
  115. package/runtime/observability/query/explain-query.js +114 -0
  116. package/runtime/observability/query/export-audit-bundle.d.ts +22 -0
  117. package/runtime/observability/query/export-audit-bundle.js +27 -0
  118. package/runtime/observability/services/decision-ledger.d.ts +1 -1
  119. package/runtime/observability/services/decision-ledger.js +4 -0
  120. package/runtime/observability/services/execution-telemetry.d.ts +0 -1
  121. package/runtime/observability/services/governance-audit.d.ts +14 -0
  122. package/runtime/observability/services/governance-audit.js +25 -1
  123. package/runtime/observability/services/governance-plane-recorder.d.ts +47 -0
  124. package/runtime/observability/services/governance-plane-recorder.js +55 -0
  125. package/runtime/observability/services/lived-experience-audit.d.ts +97 -0
  126. package/runtime/observability/services/lived-experience-audit.js +161 -0
  127. package/runtime/storage/bootstrap/native-sqlite-probe.d.ts +7 -0
  128. package/runtime/storage/bootstrap/native-sqlite-probe.js +28 -0
  129. package/runtime/storage/bootstrap/repair-gate.d.ts +17 -0
  130. package/runtime/storage/bootstrap/repair-gate.js +71 -0
  131. package/runtime/storage/bootstrap/storage-mode-smoke.d.ts +38 -0
  132. package/runtime/storage/bootstrap/storage-mode-smoke.js +85 -0
  133. package/runtime/storage/db/index.js +49 -0
  134. package/runtime/storage/db/schema/delivery-attempts.d.ts +199 -0
  135. package/runtime/storage/db/schema/delivery-attempts.js +13 -0
  136. package/runtime/storage/db/schema/index.d.ts +3 -0
  137. package/runtime/storage/db/schema/index.js +3 -0
  138. package/runtime/storage/db/schema/life-evidence-index.d.ts +161 -0
  139. package/runtime/storage/db/schema/life-evidence-index.js +11 -0
  140. package/runtime/storage/db/schema/operator-fallback-artifacts.d.ts +161 -0
  141. package/runtime/storage/db/schema/operator-fallback-artifacts.js +11 -0
  142. package/runtime/storage/db/schema/policies.d.ts +17 -0
  143. package/runtime/storage/db/schema/policies.js +1 -0
  144. package/runtime/storage/delivery/query-delivery-attempts.d.ts +3 -0
  145. package/runtime/storage/delivery/query-delivery-attempts.js +32 -0
  146. package/runtime/storage/delivery/types.d.ts +27 -0
  147. package/runtime/storage/delivery/types.js +1 -0
  148. package/runtime/storage/delivery/write-delivery-attempt.d.ts +6 -0
  149. package/runtime/storage/delivery/write-delivery-attempt.js +36 -0
  150. package/runtime/storage/fallback/load-operator-fallback.d.ts +14 -0
  151. package/runtime/storage/fallback/load-operator-fallback.js +47 -0
  152. package/runtime/storage/fallback/operator-fallback-types.d.ts +9 -0
  153. package/runtime/storage/fallback/operator-fallback-types.js +1 -0
  154. package/runtime/storage/fallback/operator-fallback-view.d.ts +11 -0
  155. package/runtime/storage/fallback/operator-fallback-view.js +1 -0
  156. package/runtime/storage/fallback/write-operator-fallback.d.ts +6 -0
  157. package/runtime/storage/fallback/write-operator-fallback.js +21 -0
  158. package/runtime/storage/index.d.ts +21 -0
  159. package/runtime/storage/index.js +14 -0
  160. package/runtime/storage/life-evidence/append-life-evidence.d.ts +7 -0
  161. package/runtime/storage/life-evidence/append-life-evidence.js +64 -0
  162. package/runtime/storage/life-evidence/types.d.ts +45 -0
  163. package/runtime/storage/life-evidence/types.js +6 -0
  164. package/runtime/storage/quiet/persist-quiet-artifact.d.ts +7 -0
  165. package/runtime/storage/quiet/persist-quiet-artifact.js +22 -0
  166. package/runtime/storage/quiet/quiet-artifact-types.d.ts +18 -0
  167. package/runtime/storage/quiet/quiet-artifact-types.js +1 -0
  168. package/runtime/storage/quiet/quiet-artifact-writer.d.ts +15 -0
  169. package/runtime/storage/quiet/quiet-artifact-writer.js +56 -0
  170. package/runtime/storage/rhythm/rhythm-policy-snapshot.d.ts +10 -0
  171. package/runtime/storage/rhythm/rhythm-policy-snapshot.js +34 -0
  172. package/runtime/storage/services/credential-vault.d.ts +5 -0
  173. package/runtime/storage/services/credential-vault.js +46 -9
  174. package/runtime/storage/snapshots/continuity-snapshot.d.ts +9 -0
  175. package/runtime/storage/snapshots/continuity-snapshot.js +41 -0
  176. package/runtime/storage/snapshots/life-evidence-snapshot.d.ts +6 -0
  177. package/runtime/storage/snapshots/life-evidence-snapshot.js +114 -0
  178. package/runtime/storage/snapshots/types.d.ts +58 -0
  179. package/runtime/storage/snapshots/types.js +1 -0
  180. package/runtime/storage/state-api.js +11 -4
  181. package/runtime/storage/user-interest/load-user-interest-snapshot.d.ts +2 -0
  182. package/runtime/storage/user-interest/load-user-interest-snapshot.js +150 -0
  183. package/runtime/storage/user-interest/types.d.ts +25 -0
  184. package/runtime/storage/user-interest/types.js +1 -0
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Persists delivery attempts with state-system validation (T4.3.1).
3
+ */
4
+ import type { StateDatabase } from "../db/index.js";
5
+ import type { DeliveryAttemptAck, DeliveryAttemptWrite } from "./types.js";
6
+ export declare function writeDeliveryAttempt(state: StateDatabase, attempt: DeliveryAttemptWrite): Promise<DeliveryAttemptAck>;
@@ -0,0 +1,36 @@
1
+ import { deliveryAttempts } from "../db/schema/delivery-attempts.js";
2
+ function assertDeliveryAttemptValid(attempt) {
3
+ if (attempt.status === "sent") {
4
+ const hasProof = Boolean(attempt.messageId?.trim()) || Boolean(attempt.hostProofRef);
5
+ if (!hasProof) {
6
+ throw new Error("delivery_attempt_sent_requires_message_id_or_host_proof_ref");
7
+ }
8
+ }
9
+ if (attempt.status === "failed" || attempt.status === "dropped_by_host_policy") {
10
+ const hasDiag = Boolean(attempt.errorClass?.trim()) || Boolean(attempt.fallbackRef?.trim());
11
+ if (!hasDiag) {
12
+ throw new Error("delivery_attempt_failed_requires_error_class_or_fallback_ref");
13
+ }
14
+ }
15
+ }
16
+ export async function writeDeliveryAttempt(state, attempt) {
17
+ assertDeliveryAttemptValid(attempt);
18
+ const createdAt = new Date().toISOString();
19
+ await state.db.insert(deliveryAttempts).values({
20
+ attemptId: attempt.attemptId,
21
+ decisionId: attempt.decisionId,
22
+ target: attempt.target ?? null,
23
+ channel: attempt.channel ?? null,
24
+ status: attempt.status,
25
+ messageId: attempt.messageId ?? null,
26
+ hostProofRefJson: attempt.hostProofRef ? JSON.stringify(attempt.hostProofRef) : null,
27
+ errorClass: attempt.errorClass ?? null,
28
+ fallbackRef: attempt.fallbackRef ?? null,
29
+ createdAt,
30
+ });
31
+ return {
32
+ attemptId: attempt.attemptId,
33
+ status: attempt.status,
34
+ fallbackRef: attempt.fallbackRef,
35
+ };
36
+ }
@@ -0,0 +1,14 @@
1
+ import type { StateDatabase } from "../db/index.js";
2
+ import type { SourceRef } from "../life-evidence/types.js";
3
+ import type { OperatorFallbackView } from "./operator-fallback-view.js";
4
+ export declare function normalizeFallbackRef(ref: string): string;
5
+ /** Loads persisted operator fallback row; does not coerce status (use `toOperatorFallbackView`). */
6
+ export declare function loadOperatorFallbackRow(state: StateDatabase, fallbackRef: string): Promise<{
7
+ fallbackRef: string;
8
+ reason: string;
9
+ status: string;
10
+ sourceRefs: SourceRef[];
11
+ candidateMessage?: string;
12
+ nextStep: string;
13
+ } | null>;
14
+ export declare function toOperatorFallbackView(row: NonNullable<Awaited<ReturnType<typeof loadOperatorFallbackRow>>>): OperatorFallbackView;
@@ -0,0 +1,47 @@
1
+ import { eq } from "drizzle-orm";
2
+ import { operatorFallbackArtifacts } from "../db/schema/operator-fallback-artifacts.js";
3
+ const REASONS = new Set(["target_none", "channel_missing", "host_unsupported", "delivery_failed"]);
4
+ export function normalizeFallbackRef(ref) {
5
+ const t = ref.trim();
6
+ if (!t)
7
+ return t;
8
+ return t.startsWith("fallback:") ? t : `fallback:${t}`;
9
+ }
10
+ function parseSourceRefs(json) {
11
+ try {
12
+ const parsed = JSON.parse(json);
13
+ return Array.isArray(parsed) ? parsed : [];
14
+ }
15
+ catch {
16
+ return [];
17
+ }
18
+ }
19
+ /** Loads persisted operator fallback row; does not coerce status (use `toOperatorFallbackView`). */
20
+ export async function loadOperatorFallbackRow(state, fallbackRef) {
21
+ const key = normalizeFallbackRef(fallbackRef);
22
+ if (!key)
23
+ return null;
24
+ const rows = await state.db.select().from(operatorFallbackArtifacts).where(eq(operatorFallbackArtifacts.fallbackRef, key)).limit(1);
25
+ const row = rows[0];
26
+ if (!row)
27
+ return null;
28
+ return {
29
+ fallbackRef: row.fallbackRef,
30
+ reason: row.reason,
31
+ status: row.status,
32
+ sourceRefs: parseSourceRefs(row.sourceRefsJson),
33
+ candidateMessage: row.candidateMessage ?? undefined,
34
+ nextStep: row.nextStep,
35
+ };
36
+ }
37
+ export function toOperatorFallbackView(row) {
38
+ const reason = REASONS.has(row.reason) ? row.reason : row.reason;
39
+ return {
40
+ fallbackRef: row.fallbackRef,
41
+ reason,
42
+ status: "not_sent",
43
+ sourceRefs: row.sourceRefs,
44
+ candidateMessage: row.candidateMessage,
45
+ nextStep: row.nextStep,
46
+ };
47
+ }
@@ -0,0 +1,9 @@
1
+ import type { SourceRef } from "../life-evidence/types.js";
2
+ export type OperatorFallbackReason = "target_none" | "channel_missing" | "host_unsupported" | "delivery_failed";
3
+ export interface OperatorFallbackWrite {
4
+ reason: OperatorFallbackReason;
5
+ decisionId: string;
6
+ sourceRefs: SourceRef[];
7
+ candidateMessage?: string;
8
+ nextStep: string;
9
+ }
@@ -0,0 +1,11 @@
1
+ import type { SourceRef } from "../life-evidence/types.js";
2
+ import type { OperatorFallbackReason } from "./operator-fallback-types.js";
3
+ /** Operator-facing delivery fallback (T1.2.2 / cli-system v5). Status is always not_sent — never sent/delivered. */
4
+ export interface OperatorFallbackView {
5
+ fallbackRef: string;
6
+ reason: OperatorFallbackReason | string;
7
+ status: "not_sent";
8
+ sourceRefs: SourceRef[];
9
+ candidateMessage?: string;
10
+ nextStep: string;
11
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,6 @@
1
+ import type { StateDatabase } from "../db/index.js";
2
+ import type { OperatorFallbackWrite } from "./operator-fallback-types.js";
3
+ export interface OperatorFallbackAck {
4
+ fallbackRef: string;
5
+ }
6
+ export declare function writeOperatorFallback(state: StateDatabase, input: OperatorFallbackWrite): Promise<OperatorFallbackAck>;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Persists operator-visible delivery fallback artifacts (T2.3.2 / state-system v5).
3
+ * status is always not_sent per ADR-007.
4
+ */
5
+ import * as crypto from "node:crypto";
6
+ import { operatorFallbackArtifacts } from "../db/schema/operator-fallback-artifacts.js";
7
+ export async function writeOperatorFallback(state, input) {
8
+ const fallbackRef = `fallback:${crypto.randomUUID()}`;
9
+ const createdAt = new Date().toISOString();
10
+ await state.db.insert(operatorFallbackArtifacts).values({
11
+ fallbackRef,
12
+ decisionId: input.decisionId,
13
+ status: "not_sent",
14
+ reason: input.reason,
15
+ sourceRefsJson: JSON.stringify(input.sourceRefs),
16
+ candidateMessage: input.candidateMessage ?? null,
17
+ nextStep: input.nextStep,
18
+ createdAt,
19
+ });
20
+ return { fallbackRef };
21
+ }
@@ -13,4 +13,25 @@ export { createGovernanceLayer, type GovernanceLayer, type AnchorWriteProposal,
13
13
  export { createProvenanceService, type ProvenanceService, type ProvenanceTrace, type ProvenanceDetail } from "./services/provenance-service.js";
14
14
  export { createRepairAndBackupService, type RepairAndBackupService, type RepairAndBackupOptions, type RepairAndBackupResult, } from "./services/repair-and-backup.js";
15
15
  export { runStartupRepairAndBackup } from "./bootstrap/repair.js";
16
+ export { probeNativeSqliteLoad } from "./bootstrap/native-sqlite-probe.js";
17
+ export { runStorageModeSmoke, type StorageModeSmokeReport, type RunStorageModeSmokeOptions, } from "./bootstrap/storage-mode-smoke.js";
18
+ export { repairStateIndexes, type RepairSummary, type RepairStateIndexesOptions, type RepairGateStatus, } from "./bootstrap/repair-gate.js";
16
19
  export { createStateAPI, type StateAPI, type MemoryReadPort, type MemoryWritePort, type CredentialContextPort, type IntentCommitPort, type ProvenancePort } from "./state-api.js";
20
+ export type { LifeEvidence, LifeEvidenceCandidate, LifeEvidenceType, LifeEvidenceWriteAck, Sensitivity, SourceRef, } from "./life-evidence/types.js";
21
+ export { appendLifeEvidence, type AppendLifeEvidenceOptions } from "./life-evidence/append-life-evidence.js";
22
+ export { loadRhythmPolicySnapshot, type RhythmPolicySnapshot } from "./rhythm/rhythm-policy-snapshot.js";
23
+ export type { LifeEvidenceQuery, LifeEvidenceSnapshot, LifeEvidenceReadModel, ContinuitySnapshot, SourceCoverage, } from "./snapshots/types.js";
24
+ export { loadLifeEvidenceSnapshot, type LoadLifeEvidenceSnapshotOptions } from "./snapshots/life-evidence-snapshot.js";
25
+ export { loadContinuitySnapshot, type LoadContinuitySnapshotParams } from "./snapshots/continuity-snapshot.js";
26
+ export type { UserInterestSnapshot, UserInterestSignal, UserInterestStaleness } from "./user-interest/types.js";
27
+ export { loadUserInterestSnapshot } from "./user-interest/load-user-interest-snapshot.js";
28
+ export type { DeliveryAttemptWrite, DeliveryAttemptRecord, DeliveryAttemptAck } from "./delivery/types.js";
29
+ export { writeDeliveryAttempt } from "./delivery/write-delivery-attempt.js";
30
+ export { listDeliveryAttemptsByDecisionId } from "./delivery/query-delivery-attempts.js";
31
+ export type { OperatorFallbackWrite, OperatorFallbackReason } from "./fallback/operator-fallback-types.js";
32
+ export type { OperatorFallbackView } from "./fallback/operator-fallback-view.js";
33
+ export { writeOperatorFallback, type OperatorFallbackAck } from "./fallback/write-operator-fallback.js";
34
+ export { loadOperatorFallbackRow, normalizeFallbackRef, toOperatorFallbackView, } from "./fallback/load-operator-fallback.js";
35
+ export type { QuietArtifactWrite, QuietClaim, QuietArtifactKind } from "./quiet/quiet-artifact-types.js";
36
+ export { writeQuietArtifact, calculateQuietSourceCoverage, evidenceGroundingRatio, type QuietArtifactAck, } from "./quiet/quiet-artifact-writer.js";
37
+ export { persistQuietArtifactToWorkspace, type PersistQuietArtifactResult } from "./quiet/persist-quiet-artifact.js";
@@ -13,4 +13,18 @@ export { createGovernanceLayer } from "./services/governance-layer.js";
13
13
  export { createProvenanceService } from "./services/provenance-service.js";
14
14
  export { createRepairAndBackupService, } from "./services/repair-and-backup.js";
15
15
  export { runStartupRepairAndBackup } from "./bootstrap/repair.js";
16
+ export { probeNativeSqliteLoad } from "./bootstrap/native-sqlite-probe.js";
17
+ export { runStorageModeSmoke, } from "./bootstrap/storage-mode-smoke.js";
18
+ export { repairStateIndexes, } from "./bootstrap/repair-gate.js";
16
19
  export { createStateAPI } from "./state-api.js";
20
+ export { appendLifeEvidence } from "./life-evidence/append-life-evidence.js";
21
+ export { loadRhythmPolicySnapshot } from "./rhythm/rhythm-policy-snapshot.js";
22
+ export { loadLifeEvidenceSnapshot } from "./snapshots/life-evidence-snapshot.js";
23
+ export { loadContinuitySnapshot } from "./snapshots/continuity-snapshot.js";
24
+ export { loadUserInterestSnapshot } from "./user-interest/load-user-interest-snapshot.js";
25
+ export { writeDeliveryAttempt } from "./delivery/write-delivery-attempt.js";
26
+ export { listDeliveryAttemptsByDecisionId } from "./delivery/query-delivery-attempts.js";
27
+ export { writeOperatorFallback } from "./fallback/write-operator-fallback.js";
28
+ export { loadOperatorFallbackRow, normalizeFallbackRef, toOperatorFallbackView, } from "./fallback/load-operator-fallback.js";
29
+ export { writeQuietArtifact, calculateQuietSourceCoverage, evidenceGroundingRatio, } from "./quiet/quiet-artifact-writer.js";
30
+ export { persistQuietArtifactToWorkspace } from "./quiet/persist-quiet-artifact.js";
@@ -0,0 +1,7 @@
1
+ import type { StateDatabase } from "../db/index.js";
2
+ import type { ProvenanceRepository } from "../repositories/provenance-repository.js";
3
+ import type { LifeEvidenceCandidate, LifeEvidenceWriteAck } from "./types.js";
4
+ export interface AppendLifeEvidenceOptions {
5
+ provenance?: ProvenanceRepository;
6
+ }
7
+ export declare function appendLifeEvidence(state: StateDatabase, workspaceRoot: string, candidate: LifeEvidenceCandidate, options?: AppendLifeEvidenceOptions): Promise<LifeEvidenceWriteAck>;
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Append canonical LifeEvidence artifacts and SQLite index rows (state-system T4.1.1).
3
+ *
4
+ * Core logic: reject missing source refs and credential sensitivity; write JSON artifact;
5
+ * insert life_evidence_index; bump snapshot epoch file for downstream snapshot invalidation.
6
+ *
7
+ * Dependencies: StateDatabase drizzle + optional ProvenanceRepository for source edges.
8
+ *
9
+ * Boundaries: does not run connector normalization; caller supplies validated candidates.
10
+ *
11
+ * Test coverage: tests/unit/storage/life-evidence.test.ts
12
+ */
13
+ import * as crypto from "node:crypto";
14
+ import fs from "node:fs";
15
+ import path from "node:path";
16
+ import { lifeEvidenceIndex } from "../db/schema/life-evidence-index.js";
17
+ export async function appendLifeEvidence(state, workspaceRoot, candidate, options) {
18
+ if (!candidate.sourceRefs || candidate.sourceRefs.length === 0) {
19
+ throw new Error("life_evidence_missing_source_refs");
20
+ }
21
+ if (candidate.sensitivity === "credential") {
22
+ throw new Error("life_evidence_credential_rejected");
23
+ }
24
+ const evidenceId = candidate.id ?? `lev_${crypto.randomUUID()}`;
25
+ const artifactRel = path.join(".second-nature", "evidence", `${evidenceId}.json`);
26
+ const artifactAbs = path.join(workspaceRoot, artifactRel);
27
+ const artifactRef = {
28
+ id: `artifact:${evidenceId}`,
29
+ kind: "workspace_artifact",
30
+ uri: artifactRel.replace(/\\/g, "/"),
31
+ };
32
+ const life = {
33
+ id: evidenceId,
34
+ timestamp: candidate.timestamp,
35
+ evidenceType: candidate.evidenceType,
36
+ platformId: candidate.platformId,
37
+ summary: candidate.summary,
38
+ rawContentRef: candidate.rawContentRef,
39
+ sourceRefs: candidate.sourceRefs,
40
+ sensitivity: candidate.sensitivity,
41
+ confidence: candidate.confidence ?? 1,
42
+ tags: candidate.tags ?? [],
43
+ producer: candidate.producer,
44
+ artifactRef,
45
+ };
46
+ fs.mkdirSync(path.dirname(artifactAbs), { recursive: true });
47
+ fs.writeFileSync(artifactAbs, JSON.stringify(life, null, 2), "utf-8");
48
+ const epochPath = path.join(workspaceRoot, ".second-nature", "evidence", ".snapshot_epoch");
49
+ fs.writeFileSync(epochPath, `${Date.now()}\n`, "utf-8");
50
+ await state.db.insert(lifeEvidenceIndex).values({
51
+ id: evidenceId,
52
+ timestamp: candidate.timestamp,
53
+ evidenceType: candidate.evidenceType,
54
+ sensitivity: candidate.sensitivity,
55
+ producer: candidate.producer,
56
+ artifactPath: artifactRel.replace(/\\/g, "/"),
57
+ platformId: candidate.platformId ?? null,
58
+ sourceRefsJson: JSON.stringify(candidate.sourceRefs),
59
+ });
60
+ if (options?.provenance) {
61
+ await options.provenance.linkEntrySources(evidenceId, candidate.sourceRefs.map((ref) => ref.id));
62
+ }
63
+ return { evidenceId, artifactRef };
64
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Life evidence contract types (state-system v5 subset for ingest port).
3
+ *
4
+ * Test coverage: tests/unit/storage/life-evidence.test.ts
5
+ */
6
+ export type LifeEvidenceType = "platform_browse" | "platform_interaction" | "work_progress" | "task_discovery" | "user_interaction" | "quiet_reflection" | "delivery_fallback";
7
+ export type Sensitivity = "public" | "private" | "credential" | "sensitive";
8
+ export interface SourceRef {
9
+ id: string;
10
+ kind: "platform_item" | "workspace_artifact" | "decision_record" | "user_anchor" | "connector_result" | "host_report" | "fallback_artifact";
11
+ uri: string;
12
+ excerptHash?: string;
13
+ observedAt?: string;
14
+ }
15
+ export interface LifeEvidenceCandidate {
16
+ id?: string;
17
+ timestamp: string;
18
+ evidenceType: LifeEvidenceType;
19
+ platformId?: string;
20
+ summary: string;
21
+ rawContentRef?: string;
22
+ sourceRefs: SourceRef[];
23
+ sensitivity: Sensitivity;
24
+ confidence?: number;
25
+ tags?: string[];
26
+ producer: "connector-system" | "control-plane-system" | "observability-system" | "state-system";
27
+ }
28
+ export interface LifeEvidence {
29
+ id: string;
30
+ timestamp: string;
31
+ evidenceType: LifeEvidenceType;
32
+ platformId?: string;
33
+ summary: string;
34
+ rawContentRef?: string;
35
+ sourceRefs: SourceRef[];
36
+ sensitivity: Sensitivity;
37
+ confidence: number;
38
+ tags: string[];
39
+ producer: string;
40
+ artifactRef: SourceRef;
41
+ }
42
+ export interface LifeEvidenceWriteAck {
43
+ evidenceId: string;
44
+ artifactRef: SourceRef;
45
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Life evidence contract types (state-system v5 subset for ingest port).
3
+ *
4
+ * Test coverage: tests/unit/storage/life-evidence.test.ts
5
+ */
6
+ export {};
@@ -0,0 +1,7 @@
1
+ import type { QuietArtifactWrite } from "./quiet-artifact-types.js";
2
+ import type { QuietArtifactAck } from "./quiet-artifact-writer.js";
3
+ export interface PersistQuietArtifactResult {
4
+ relativePath: string;
5
+ absolutePath: string;
6
+ }
7
+ export declare function persistQuietArtifactToWorkspace(workspaceRoot: string, ack: QuietArtifactAck, input: QuietArtifactWrite): Promise<PersistQuietArtifactResult>;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Writes validated Quiet artifact JSON under workspace `.second-nature/quiet/{day}/` (CR-M3 / T2.3.3).
3
+ */
4
+ import * as fs from "node:fs/promises";
5
+ import * as path from "node:path";
6
+ export async function persistQuietArtifactToWorkspace(workspaceRoot, ack, input) {
7
+ const dir = path.join(workspaceRoot, ".second-nature", "quiet", input.day);
8
+ await fs.mkdir(dir, { recursive: true });
9
+ const file = path.join(dir, `${ack.artifactId}.json`);
10
+ const payload = {
11
+ artifactId: ack.artifactId,
12
+ artifactRef: ack.artifactRef,
13
+ sourceCoverage: ack.sourceCoverage,
14
+ write: input,
15
+ persistedAt: new Date().toISOString(),
16
+ };
17
+ await fs.writeFile(file, JSON.stringify(payload, null, 2), "utf8");
18
+ return {
19
+ relativePath: path.relative(workspaceRoot, file),
20
+ absolutePath: file,
21
+ };
22
+ }
@@ -0,0 +1,18 @@
1
+ import type { SourceRef } from "../life-evidence/types.js";
2
+ export type QuietArtifactKind = "daily_report" | "narrative_reflection" | "curated_memory_candidate" | "empty_state";
3
+ export type QuietClaimType = "fact" | "emotion" | "interpretation" | "next_step";
4
+ export interface QuietClaim {
5
+ id: string;
6
+ text: string;
7
+ sourceRefs: SourceRef[];
8
+ claimType: QuietClaimType;
9
+ }
10
+ export interface QuietArtifactWrite {
11
+ day: string;
12
+ kind: QuietArtifactKind;
13
+ title: string;
14
+ body: string;
15
+ claims: QuietClaim[];
16
+ sourceRefs: SourceRef[];
17
+ memoryCandidateRefs?: SourceRef[];
18
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,15 @@
1
+ import type { SourceRef } from "../life-evidence/types.js";
2
+ import type { SourceCoverage } from "../snapshots/types.js";
3
+ import type { QuietArtifactWrite } from "./quiet-artifact-types.js";
4
+ import type { QuietClaim } from "./quiet-artifact-types.js";
5
+ export declare function calculateQuietSourceCoverage(claims: QuietClaim[]): SourceCoverage;
6
+ /** Ratio of factual claims whose refs intersect the artifact's evidence bundle (T4.4.1). */
7
+ export declare function evidenceGroundingRatio(input: Pick<QuietArtifactWrite, "claims" | "sourceRefs">): number;
8
+ export interface QuietArtifactAck {
9
+ artifactId: string;
10
+ artifactRef: SourceRef;
11
+ sourceCoverage: SourceCoverage;
12
+ }
13
+ export declare function writeQuietArtifact(input: QuietArtifactWrite, opts?: {
14
+ minimumCoverageRatio?: number;
15
+ }): QuietArtifactAck;
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Quiet artifact validation + source coverage gate (T4.4.1 / ADR-003).
3
+ * Returns artifact identity for persistence layers; does not write FS/SQLite here.
4
+ */
5
+ import * as crypto from "node:crypto";
6
+ const DEFAULT_MIN_COVERAGE_RATIO = 0.51;
7
+ export function calculateQuietSourceCoverage(claims) {
8
+ const factual = claims.filter((c) => c.claimType === "fact");
9
+ const claimCoverage = claims.map((c) => {
10
+ const backed = c.claimType !== "fact" || c.sourceRefs.length > 0;
11
+ return { claimId: c.id, backed, sourceRefs: [...c.sourceRefs] };
12
+ });
13
+ const unsupportedClaims = claimCoverage.filter((x) => !x.backed).map((x) => x.claimId);
14
+ const coverageRatio = factual.length === 0 ? 1 : factual.filter((c) => c.sourceRefs.length > 0).length / factual.length;
15
+ return { coverageRatio, unsupportedClaims, claimCoverage };
16
+ }
17
+ /** Ratio of factual claims whose refs intersect the artifact's evidence bundle (T4.4.1). */
18
+ export function evidenceGroundingRatio(input) {
19
+ const allowedIds = new Set(input.sourceRefs.map((s) => s.id));
20
+ const facts = input.claims.filter((c) => c.claimType === "fact");
21
+ if (facts.length === 0)
22
+ return 1;
23
+ let grounded = 0;
24
+ for (const f of facts) {
25
+ if (f.sourceRefs.length === 0)
26
+ continue;
27
+ if (f.sourceRefs.some((r) => allowedIds.has(r.id)))
28
+ grounded += 1;
29
+ }
30
+ return grounded / facts.length;
31
+ }
32
+ export function writeQuietArtifact(input, opts) {
33
+ const minCov = opts?.minimumCoverageRatio ?? DEFAULT_MIN_COVERAGE_RATIO;
34
+ if (input.sourceRefs.length === 0 && input.kind !== "empty_state") {
35
+ throw new Error("quiet_artifact_requires_source_refs");
36
+ }
37
+ const localCoverage = calculateQuietSourceCoverage(input.claims);
38
+ if (input.kind !== "empty_state" && localCoverage.unsupportedClaims.length > 0) {
39
+ throw new Error("quiet_artifact_unsupported_factual_claim");
40
+ }
41
+ const grounding = evidenceGroundingRatio(input);
42
+ if (input.kind !== "empty_state" && grounding < minCov) {
43
+ throw new Error("quiet_artifact_source_coverage_too_low");
44
+ }
45
+ const artifactId = crypto.randomUUID();
46
+ const artifactRef = {
47
+ id: `quiet:${artifactId}`,
48
+ kind: "workspace_artifact",
49
+ uri: `urn:second-nature:quiet:${input.day}:${input.kind}:${artifactId}`,
50
+ };
51
+ const sourceCoverage = {
52
+ ...localCoverage,
53
+ coverageRatio: input.kind === "empty_state" ? 1 : grounding,
54
+ };
55
+ return { artifactId, artifactRef, sourceCoverage };
56
+ }
@@ -0,0 +1,10 @@
1
+ import type { StateDatabase } from "../db/index.js";
2
+ export interface RhythmPolicySnapshot {
3
+ snapshotId: string;
4
+ generatedAt: string;
5
+ quietEnabled: boolean;
6
+ socialDailyLimit: number;
7
+ outreachDailyBudget: number;
8
+ updatedAt: string;
9
+ }
10
+ export declare function loadRhythmPolicySnapshot(db: StateDatabase): Promise<RhythmPolicySnapshot>;
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Rhythm policy read model: state produces policy fields only (T4.1.2 owner boundary).
3
+ *
4
+ * Core logic: load workspace-scoped policy row or defaults; never emit control-plane decision fields.
5
+ *
6
+ * Test coverage: tests/unit/storage/rhythm-policy-snapshot.test.ts
7
+ */
8
+ import * as crypto from "node:crypto";
9
+ import { eq } from "drizzle-orm";
10
+ import { policyRecords } from "../db/schema/policies.js";
11
+ const WORKSPACE_SCOPE = "workspace";
12
+ export async function loadRhythmPolicySnapshot(db) {
13
+ const scoped = await db.db.select().from(policyRecords).where(eq(policyRecords.platformId, WORKSPACE_SCOPE)).limit(1);
14
+ const row = scoped[0] ?? (await db.db.select().from(policyRecords).limit(1))[0];
15
+ const generatedAt = new Date().toISOString();
16
+ if (!row) {
17
+ return {
18
+ snapshotId: crypto.randomUUID(),
19
+ generatedAt,
20
+ quietEnabled: true,
21
+ socialDailyLimit: 5,
22
+ outreachDailyBudget: 2,
23
+ updatedAt: generatedAt,
24
+ };
25
+ }
26
+ return {
27
+ snapshotId: crypto.randomUUID(),
28
+ generatedAt,
29
+ quietEnabled: row.quietEnabled,
30
+ socialDailyLimit: row.socialDailyLimit,
31
+ outreachDailyBudget: row.outreachDailyBudget ?? 2,
32
+ updatedAt: row.updatedAt,
33
+ };
34
+ }
@@ -1,5 +1,10 @@
1
1
  import type { StateDatabase } from "../db/index.js";
2
2
  import type { CredentialContextWrite, CredentialContext, CredentialState } from "../../shared/types/index.js";
3
+ /** Three colon-separated hex segments produced by `encryptCredentialAtRest`. */
4
+ export declare function isCredentialCiphertext(value: string): boolean;
5
+ /** Encrypts non-empty plaintext; empty string returns empty. */
6
+ export declare function encryptCredentialAtRest(plaintext: string): string;
7
+ export declare function decryptCredentialAtRest(ciphertext: string): string;
3
8
  export interface CredentialVault {
4
9
  saveCredentialContext(input: CredentialContextWrite): Promise<void>;
5
10
  loadCredentialContext(platformId: string): Promise<CredentialContext | null>;
@@ -1,32 +1,63 @@
1
+ /**
2
+ * Credential encryption at rest (AES-256-GCM).
3
+ *
4
+ * Key material: `SECOND_NATURE_ENCRYPTION_KEY` (UTF-8, first 32 bytes used). Lazy read so tests can set env before first encrypt.
5
+ * Test coverage: tests/integration/cli/cli-ops-surface.test.ts (credential save path via state-api).
6
+ */
1
7
  import * as crypto from "crypto";
2
8
  import { eq } from "drizzle-orm";
3
9
  import { credentialRecords } from "../db/schema/index.js";
4
- const ENCRYPTION_KEY = process.env.SECOND_NATURE_ENCRYPTION_KEY || crypto.randomBytes(32).toString("hex");
5
10
  const ALGORITHM = "aes-256-gcm";
6
- function encrypt(plaintext) {
11
+ function resolveKeyBuffer() {
12
+ const raw = process.env.SECOND_NATURE_ENCRYPTION_KEY?.trim();
13
+ if (!raw || raw.length < 32) {
14
+ throw new Error("SECOND_NATURE_ENCRYPTION_KEY is required for credential encryption at rest (min 32 UTF-8 characters)");
15
+ }
16
+ return Buffer.from(raw.slice(0, 32), "utf8");
17
+ }
18
+ function encryptInternal(plaintext) {
7
19
  const iv = crypto.randomBytes(16);
8
- const key = Buffer.from(ENCRYPTION_KEY.slice(0, 32), "utf8");
20
+ const key = resolveKeyBuffer();
9
21
  const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
10
22
  const encrypted = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
11
23
  const authTag = cipher.getAuthTag();
12
24
  return iv.toString("hex") + ":" + authTag.toString("hex") + ":" + encrypted.toString("hex");
13
25
  }
14
- function decrypt(ciphertext) {
26
+ function decryptInternal(ciphertext) {
15
27
  const parts = ciphertext.split(":");
16
- if (parts.length !== 3)
17
- return ciphertext;
28
+ if (parts.length !== 3) {
29
+ throw new Error("credential_ciphertext_invalid_format");
30
+ }
18
31
  const iv = Buffer.from(parts[0], "hex");
19
32
  const authTag = Buffer.from(parts[1], "hex");
20
33
  const encrypted = Buffer.from(parts[2], "hex");
21
- const key = Buffer.from(ENCRYPTION_KEY.slice(0, 32), "utf8");
34
+ const key = resolveKeyBuffer();
22
35
  const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
23
36
  decipher.setAuthTag(authTag);
24
37
  return decipher.update(encrypted) + decipher.final("utf8");
25
38
  }
39
+ /** Three colon-separated hex segments produced by `encryptCredentialAtRest`. */
40
+ export function isCredentialCiphertext(value) {
41
+ const parts = value.split(":");
42
+ if (parts.length !== 3)
43
+ return false;
44
+ return parts.every((p) => /^[0-9a-f]+$/i.test(p));
45
+ }
46
+ /** Encrypts non-empty plaintext; empty string returns empty. */
47
+ export function encryptCredentialAtRest(plaintext) {
48
+ if (!plaintext)
49
+ return "";
50
+ return encryptInternal(plaintext);
51
+ }
52
+ export function decryptCredentialAtRest(ciphertext) {
53
+ if (!ciphertext)
54
+ return "";
55
+ return decryptInternal(ciphertext);
56
+ }
26
57
  export function createCredentialVault(db) {
27
58
  return {
28
59
  async saveCredentialContext(input) {
29
- const encrypted = input.encryptedValue ? encrypt(input.encryptedValue) : "";
60
+ const encrypted = input.encryptedValue ? encryptCredentialAtRest(input.encryptedValue) : "";
30
61
  await db.insert(credentialRecords).values({
31
62
  platformId: input.platformId,
32
63
  credentialType: input.credentialType,
@@ -57,11 +88,17 @@ export function createCredentialVault(db) {
57
88
  });
58
89
  if (!record)
59
90
  return null;
91
+ let plain;
92
+ if (record.encryptedValue) {
93
+ plain = isCredentialCiphertext(record.encryptedValue)
94
+ ? decryptCredentialAtRest(record.encryptedValue)
95
+ : record.encryptedValue;
96
+ }
60
97
  return {
61
98
  platformId: record.platformId,
62
99
  credentialType: record.credentialType,
63
100
  status: record.status,
64
- encryptedValue: record.encryptedValue ? decrypt(record.encryptedValue) : undefined,
101
+ encryptedValue: plain,
65
102
  verificationCode: record.verificationCode ?? undefined,
66
103
  challengeText: record.challengeText ?? undefined,
67
104
  verificationDeadline: record.expiresAt ?? undefined,
@@ -0,0 +1,9 @@
1
+ import type { ObservabilityDatabase } from "../../observability/db/index.js";
2
+ import type { StateDatabase } from "../db/index.js";
3
+ import type { ContinuitySnapshot } from "./types.js";
4
+ export interface LoadContinuitySnapshotParams {
5
+ state: StateDatabase;
6
+ workspaceRoot: string;
7
+ observability?: ObservabilityDatabase;
8
+ }
9
+ export declare function loadContinuitySnapshot(params: LoadContinuitySnapshotParams): Promise<ContinuitySnapshot>;