@haaaiawd/second-nature 0.1.16 → 0.1.18
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.
- package/index.js +855 -851
- package/openclaw.plugin.json +29 -29
- package/package.json +52 -52
- package/runtime/cli/commands/index.d.ts +14 -14
- package/runtime/cli/commands/index.js +193 -193
- package/runtime/cli/explain/explain-surface-subject.d.ts +8 -8
- package/runtime/cli/explain/explain-surface-subject.js +9 -9
- package/runtime/cli/explain/format-explanation.d.ts +12 -12
- package/runtime/cli/explain/format-explanation.js +12 -12
- package/runtime/cli/explain/resolve-subject.js +41 -41
- package/runtime/cli/host-capability/classify-delivery.d.ts +14 -14
- package/runtime/cli/host-capability/classify-delivery.js +20 -20
- package/runtime/cli/host-capability/probe-host-capability.d.ts +2 -2
- package/runtime/cli/host-capability/probe-host-capability.js +58 -58
- package/runtime/cli/host-capability/record-host-capability.d.ts +6 -6
- package/runtime/cli/host-capability/record-host-capability.js +14 -14
- package/runtime/cli/host-capability/types.d.ts +71 -71
- package/runtime/cli/host-capability/types.js +6 -6
- package/runtime/cli/host-smoke/run-host-smoke.d.ts +2 -2
- package/runtime/cli/host-smoke/run-host-smoke.js +40 -40
- package/runtime/cli/host-smoke/types.d.ts +35 -35
- package/runtime/cli/host-smoke/types.js +6 -6
- package/runtime/cli/index.js +58 -54
- package/runtime/cli/ops/heartbeat-surface.d.ts +38 -35
- package/runtime/cli/ops/heartbeat-surface.js +73 -71
- package/runtime/cli/ops/ops-router.d.ts +19 -16
- package/runtime/cli/ops/ops-router.js +89 -87
- package/runtime/cli/ops/show-operator-fallback.d.ts +13 -13
- package/runtime/cli/ops/show-operator-fallback.js +22 -22
- package/runtime/cli/ops/workspace-heartbeat-runner.d.ts +19 -10
- package/runtime/cli/ops/workspace-heartbeat-runner.js +39 -26
- package/runtime/cli/read-models/index.d.ts +29 -29
- package/runtime/cli/read-models/index.js +256 -256
- package/runtime/cli/read-models/operator-explain-map.d.ts +6 -6
- package/runtime/cli/read-models/operator-explain-map.js +10 -10
- package/runtime/cli/read-models/types.d.ts +79 -79
- package/runtime/cli/runtime/runtime-artifact-boundary.d.ts +28 -28
- package/runtime/cli/runtime/runtime-artifact-boundary.js +94 -94
- package/runtime/connectors/base/contract.d.ts +87 -87
- package/runtime/connectors/base/execution-policy.d.ts +47 -47
- package/runtime/connectors/base/execution-policy.js +82 -82
- package/runtime/connectors/base/index.d.ts +8 -8
- package/runtime/connectors/base/index.js +8 -8
- package/runtime/connectors/base/manifest.d.ts +64 -64
- package/runtime/connectors/base/manifest.js +86 -86
- package/runtime/connectors/base/map-life-evidence.d.ts +16 -16
- package/runtime/connectors/base/map-life-evidence.js +79 -79
- package/runtime/connectors/base/policy-layer.d.ts +29 -29
- package/runtime/connectors/base/policy-layer.js +198 -198
- package/runtime/connectors/base/route-planner.js +99 -99
- package/runtime/connectors/index.d.ts +5 -5
- package/runtime/connectors/index.js +5 -5
- package/runtime/connectors/near-real/near-real-connector-smoke.d.ts +19 -19
- package/runtime/connectors/near-real/near-real-connector-smoke.js +152 -152
- package/runtime/core/second-nature/heartbeat/heartbeat-executor.js +114 -114
- package/runtime/core/second-nature/heartbeat/heartbeat-loop.d.ts +63 -63
- package/runtime/core/second-nature/heartbeat/heartbeat-loop.js +139 -139
- package/runtime/core/second-nature/heartbeat/index.d.ts +8 -8
- package/runtime/core/second-nature/heartbeat/index.js +7 -7
- package/runtime/core/second-nature/heartbeat/run-heartbeat-cycle.d.ts +21 -21
- package/runtime/core/second-nature/heartbeat/run-heartbeat-cycle.js +35 -35
- package/runtime/core/second-nature/heartbeat/runtime-snapshot.d.ts +28 -28
- package/runtime/core/second-nature/heartbeat/runtime-snapshot.js +35 -35
- package/runtime/core/second-nature/heartbeat/signal.d.ts +42 -42
- package/runtime/core/second-nature/heartbeat/snapshot-builder.d.ts +51 -51
- package/runtime/core/second-nature/index.d.ts +22 -22
- package/runtime/core/second-nature/index.js +22 -22
- package/runtime/core/second-nature/orchestrator/effect-dispatcher.d.ts +100 -100
- package/runtime/core/second-nature/orchestrator/effect-dispatcher.js +144 -144
- package/runtime/core/second-nature/orchestrator/guard-layer.d.ts +8 -8
- package/runtime/core/second-nature/orchestrator/guard-layer.js +110 -110
- package/runtime/core/second-nature/orchestrator/intent-planner.d.ts +13 -13
- package/runtime/core/second-nature/orchestrator/intent-planner.js +199 -199
- package/runtime/core/second-nature/orchestrator/lease-manager.d.ts +14 -14
- package/runtime/core/second-nature/orchestrator/lease-manager.js +58 -58
- package/runtime/core/second-nature/outreach/build-outreach-draft-request.d.ts +6 -6
- package/runtime/core/second-nature/outreach/build-outreach-draft-request.js +63 -63
- package/runtime/core/second-nature/outreach/delivery-target.d.ts +26 -26
- package/runtime/core/second-nature/outreach/delivery-target.js +70 -70
- package/runtime/core/second-nature/outreach/dispatch-user-outreach.d.ts +38 -38
- package/runtime/core/second-nature/outreach/dispatch-user-outreach.js +119 -119
- package/runtime/core/second-nature/outreach/judge-input-from-snapshot.d.ts +7 -7
- package/runtime/core/second-nature/outreach/judge-input-from-snapshot.js +45 -45
- package/runtime/core/second-nature/outreach/judge-outreach.d.ts +40 -40
- package/runtime/core/second-nature/outreach/judge-outreach.js +121 -121
- package/runtime/core/second-nature/quiet/run-source-backed-quiet.d.ts +21 -21
- package/runtime/core/second-nature/quiet/run-source-backed-quiet.js +123 -123
- package/runtime/core/second-nature/rhythm/planner-rhythm-window.d.ts +15 -15
- package/runtime/core/second-nature/rhythm/planner-rhythm-window.js +52 -52
- package/runtime/core/second-nature/rhythm/policy-bridge.d.ts +19 -19
- package/runtime/core/second-nature/rhythm/policy-bridge.js +34 -34
- package/runtime/core/second-nature/runtime/service-entry.js +45 -45
- package/runtime/core/second-nature/types.d.ts +51 -51
- package/runtime/guidance/draft-outreach-message.d.ts +7 -7
- package/runtime/guidance/draft-outreach-message.js +42 -42
- package/runtime/guidance/evidence-guidance.d.ts +40 -40
- package/runtime/guidance/evidence-guidance.js +52 -52
- package/runtime/guidance/index.d.ts +11 -11
- package/runtime/guidance/index.js +11 -11
- package/runtime/guidance/outreach-draft-schema.d.ts +228 -228
- package/runtime/guidance/outreach-draft-schema.js +80 -80
- package/runtime/observability/audit/append-only-audit-store.d.ts +14 -14
- package/runtime/observability/audit/append-only-audit-store.js +21 -21
- package/runtime/observability/audit/audit-envelope.d.ts +51 -51
- package/runtime/observability/audit/audit-envelope.js +130 -130
- package/runtime/observability/audit/verify-audit-hash-chain.d.ts +23 -23
- package/runtime/observability/audit/verify-audit-hash-chain.js +83 -83
- package/runtime/observability/db/index.js +124 -124
- package/runtime/observability/db/schema/host-capability-reports.d.ts +180 -180
- package/runtime/observability/db/schema/host-capability-reports.js +12 -12
- package/runtime/observability/db/schema/index.d.ts +947 -947
- package/runtime/observability/db/schema/index.js +71 -71
- package/runtime/observability/index.d.ts +20 -19
- package/runtime/observability/index.js +19 -18
- package/runtime/observability/query/explain-query.d.ts +48 -48
- package/runtime/observability/query/explain-query.js +114 -114
- package/runtime/observability/query/export-audit-bundle.d.ts +22 -22
- package/runtime/observability/query/export-audit-bundle.js +27 -27
- package/runtime/observability/services/decision-ledger.d.ts +46 -46
- package/runtime/observability/services/decision-ledger.js +161 -161
- package/runtime/observability/services/governance-audit.d.ts +41 -41
- package/runtime/observability/services/governance-audit.js +163 -163
- package/runtime/observability/services/governance-plane-recorder.d.ts +47 -47
- package/runtime/observability/services/governance-plane-recorder.js +55 -55
- package/runtime/observability/services/lived-experience-audit.d.ts +97 -97
- package/runtime/observability/services/lived-experience-audit.js +162 -162
- package/runtime/observability/services/runtime-decision-recorder.d.ts +29 -0
- package/runtime/observability/services/runtime-decision-recorder.js +94 -0
- package/runtime/storage/bootstrap/native-sqlite-probe.d.ts +7 -7
- package/runtime/storage/bootstrap/native-sqlite-probe.js +28 -28
- package/runtime/storage/bootstrap/repair-gate.d.ts +17 -17
- package/runtime/storage/bootstrap/repair-gate.js +71 -71
- package/runtime/storage/bootstrap/storage-mode-smoke.d.ts +38 -38
- package/runtime/storage/bootstrap/storage-mode-smoke.js +85 -85
- package/runtime/storage/db/index.js +154 -154
- package/runtime/storage/db/schema/delivery-attempts.d.ts +199 -199
- package/runtime/storage/db/schema/delivery-attempts.js +13 -13
- package/runtime/storage/db/schema/index.d.ts +9 -9
- package/runtime/storage/db/schema/index.js +9 -9
- package/runtime/storage/db/schema/life-evidence-index.d.ts +161 -161
- package/runtime/storage/db/schema/life-evidence-index.js +11 -11
- package/runtime/storage/db/schema/operator-fallback-artifacts.d.ts +161 -161
- package/runtime/storage/db/schema/operator-fallback-artifacts.js +11 -11
- package/runtime/storage/db/schema/policies.d.ts +98 -98
- package/runtime/storage/db/schema/policies.js +8 -8
- package/runtime/storage/delivery/query-delivery-attempts.d.ts +3 -3
- package/runtime/storage/delivery/query-delivery-attempts.js +32 -32
- package/runtime/storage/delivery/types.d.ts +27 -27
- package/runtime/storage/delivery/types.js +1 -1
- package/runtime/storage/delivery/write-delivery-attempt.d.ts +6 -6
- package/runtime/storage/delivery/write-delivery-attempt.js +36 -36
- package/runtime/storage/fallback/load-operator-fallback.d.ts +14 -14
- package/runtime/storage/fallback/load-operator-fallback.js +47 -47
- package/runtime/storage/fallback/operator-fallback-types.d.ts +9 -9
- package/runtime/storage/fallback/operator-fallback-types.js +1 -1
- package/runtime/storage/fallback/operator-fallback-view.d.ts +11 -11
- package/runtime/storage/fallback/operator-fallback-view.js +1 -1
- package/runtime/storage/fallback/write-operator-fallback.d.ts +6 -6
- package/runtime/storage/fallback/write-operator-fallback.js +21 -21
- package/runtime/storage/index.d.ts +37 -37
- package/runtime/storage/index.js +30 -30
- package/runtime/storage/life-evidence/append-life-evidence.d.ts +7 -7
- package/runtime/storage/life-evidence/append-life-evidence.js +64 -64
- package/runtime/storage/life-evidence/types.d.ts +45 -45
- package/runtime/storage/life-evidence/types.js +6 -6
- package/runtime/storage/quiet/persist-quiet-artifact.d.ts +7 -7
- package/runtime/storage/quiet/persist-quiet-artifact.js +22 -22
- package/runtime/storage/quiet/quiet-artifact-types.d.ts +18 -18
- package/runtime/storage/quiet/quiet-artifact-types.js +1 -1
- package/runtime/storage/quiet/quiet-artifact-writer.d.ts +15 -15
- package/runtime/storage/quiet/quiet-artifact-writer.js +56 -56
- package/runtime/storage/repositories/credential-repository.js +30 -30
- package/runtime/storage/rhythm/rhythm-policy-snapshot.d.ts +10 -10
- package/runtime/storage/rhythm/rhythm-policy-snapshot.js +34 -34
- package/runtime/storage/services/credential-vault.d.ts +13 -13
- package/runtime/storage/services/credential-vault.js +116 -116
- package/runtime/storage/snapshots/continuity-snapshot.d.ts +9 -9
- package/runtime/storage/snapshots/continuity-snapshot.js +41 -41
- package/runtime/storage/snapshots/life-evidence-snapshot.d.ts +6 -6
- package/runtime/storage/snapshots/life-evidence-snapshot.js +114 -114
- package/runtime/storage/snapshots/types.d.ts +58 -58
- package/runtime/storage/snapshots/types.js +1 -1
- package/runtime/storage/state-api.js +104 -104
- package/runtime/storage/user-interest/load-user-interest-snapshot.d.ts +2 -2
- package/runtime/storage/user-interest/load-user-interest-snapshot.js +150 -150
- package/runtime/storage/user-interest/types.d.ts +25 -25
- package/runtime/storage/user-interest/types.js +1 -1
- package/workspace-ops-bridge.js +81 -80
|
@@ -1,56 +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
|
-
}
|
|
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
|
+
}
|
|
@@ -1,30 +1,30 @@
|
|
|
1
|
-
import { eq } from "drizzle-orm";
|
|
2
|
-
import { credentialRecords } from "../db/schema/index.js";
|
|
3
|
-
export class CredentialRepository {
|
|
4
|
-
database;
|
|
5
|
-
constructor(database) {
|
|
6
|
-
this.database = database;
|
|
7
|
-
}
|
|
8
|
-
async upsert(record) {
|
|
9
|
-
await this.database.db.insert(credentialRecords).values(record).onConflictDoUpdate({
|
|
10
|
-
target: credentialRecords.platformId,
|
|
11
|
-
set: record,
|
|
12
|
-
});
|
|
13
|
-
}
|
|
14
|
-
async findByPlatformId(platformId) {
|
|
15
|
-
const row = await this.database.db.query.credentialRecords.findFirst({
|
|
16
|
-
where: eq(credentialRecords.platformId, platformId),
|
|
17
|
-
});
|
|
18
|
-
if (row == null) {
|
|
19
|
-
return undefined;
|
|
20
|
-
}
|
|
21
|
-
const r = row;
|
|
22
|
-
const pid = (r.platformId ?? r.platform_id);
|
|
23
|
-
const enc = (r.encryptedValue ?? r.encrypted_value);
|
|
24
|
-
// sql.js + Drizzle: no-match can still return a "shell" row (keys present, values undefined).
|
|
25
|
-
if (pid == null || pid === "" || enc == null || enc === "") {
|
|
26
|
-
return undefined;
|
|
27
|
-
}
|
|
28
|
-
return row;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
1
|
+
import { eq } from "drizzle-orm";
|
|
2
|
+
import { credentialRecords } from "../db/schema/index.js";
|
|
3
|
+
export class CredentialRepository {
|
|
4
|
+
database;
|
|
5
|
+
constructor(database) {
|
|
6
|
+
this.database = database;
|
|
7
|
+
}
|
|
8
|
+
async upsert(record) {
|
|
9
|
+
await this.database.db.insert(credentialRecords).values(record).onConflictDoUpdate({
|
|
10
|
+
target: credentialRecords.platformId,
|
|
11
|
+
set: record,
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
async findByPlatformId(platformId) {
|
|
15
|
+
const row = await this.database.db.query.credentialRecords.findFirst({
|
|
16
|
+
where: eq(credentialRecords.platformId, platformId),
|
|
17
|
+
});
|
|
18
|
+
if (row == null) {
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
const r = row;
|
|
22
|
+
const pid = (r.platformId ?? r.platform_id);
|
|
23
|
+
const enc = (r.encryptedValue ?? r.encrypted_value);
|
|
24
|
+
// sql.js + Drizzle: no-match can still return a "shell" row (keys present, values undefined).
|
|
25
|
+
if (pid == null || pid === "" || enc == null || enc === "") {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
return row;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -1,10 +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>;
|
|
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>;
|
|
@@ -1,34 +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
|
+
/**
|
|
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,13 +1,13 @@
|
|
|
1
|
-
import type { StateDatabase } from "../db/index.js";
|
|
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;
|
|
8
|
-
export interface CredentialVault {
|
|
9
|
-
saveCredentialContext(input: CredentialContextWrite): Promise<void>;
|
|
10
|
-
loadCredentialContext(platformId: string): Promise<CredentialContext | null>;
|
|
11
|
-
getCredentialState(platformId: string): Promise<CredentialState>;
|
|
12
|
-
}
|
|
13
|
-
export declare function createCredentialVault(db: StateDatabase["db"]): CredentialVault;
|
|
1
|
+
import type { StateDatabase } from "../db/index.js";
|
|
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;
|
|
8
|
+
export interface CredentialVault {
|
|
9
|
+
saveCredentialContext(input: CredentialContextWrite): Promise<void>;
|
|
10
|
+
loadCredentialContext(platformId: string): Promise<CredentialContext | null>;
|
|
11
|
+
getCredentialState(platformId: string): Promise<CredentialState>;
|
|
12
|
+
}
|
|
13
|
+
export declare function createCredentialVault(db: StateDatabase["db"]): CredentialVault;
|
|
@@ -1,116 +1,116 @@
|
|
|
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
|
-
*/
|
|
7
|
-
import * as crypto from "crypto";
|
|
8
|
-
import { eq } from "drizzle-orm";
|
|
9
|
-
import { credentialRecords } from "../db/schema/index.js";
|
|
10
|
-
const ALGORITHM = "aes-256-gcm";
|
|
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) {
|
|
19
|
-
const iv = crypto.randomBytes(16);
|
|
20
|
-
const key = resolveKeyBuffer();
|
|
21
|
-
const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
|
|
22
|
-
const encrypted = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
|
|
23
|
-
const authTag = cipher.getAuthTag();
|
|
24
|
-
return iv.toString("hex") + ":" + authTag.toString("hex") + ":" + encrypted.toString("hex");
|
|
25
|
-
}
|
|
26
|
-
function decryptInternal(ciphertext) {
|
|
27
|
-
const parts = ciphertext.split(":");
|
|
28
|
-
if (parts.length !== 3) {
|
|
29
|
-
throw new Error("credential_ciphertext_invalid_format");
|
|
30
|
-
}
|
|
31
|
-
const iv = Buffer.from(parts[0], "hex");
|
|
32
|
-
const authTag = Buffer.from(parts[1], "hex");
|
|
33
|
-
const encrypted = Buffer.from(parts[2], "hex");
|
|
34
|
-
const key = resolveKeyBuffer();
|
|
35
|
-
const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
|
|
36
|
-
decipher.setAuthTag(authTag);
|
|
37
|
-
return decipher.update(encrypted) + decipher.final("utf8");
|
|
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
|
-
}
|
|
57
|
-
export function createCredentialVault(db) {
|
|
58
|
-
return {
|
|
59
|
-
async saveCredentialContext(input) {
|
|
60
|
-
const encrypted = input.encryptedValue ? encryptCredentialAtRest(input.encryptedValue) : "";
|
|
61
|
-
await db.insert(credentialRecords).values({
|
|
62
|
-
platformId: input.platformId,
|
|
63
|
-
credentialType: input.credentialType,
|
|
64
|
-
encryptedValue: encrypted,
|
|
65
|
-
status: input.status,
|
|
66
|
-
verificationCode: input.verificationCode ?? null,
|
|
67
|
-
challengeText: input.challengeText ?? null,
|
|
68
|
-
expiresAt: input.expiresAt ?? null,
|
|
69
|
-
attemptsRemaining: input.attemptsRemaining ?? null,
|
|
70
|
-
updatedAt: new Date().toISOString(),
|
|
71
|
-
}).onConflictDoUpdate({
|
|
72
|
-
target: credentialRecords.platformId,
|
|
73
|
-
set: {
|
|
74
|
-
credentialType: input.credentialType,
|
|
75
|
-
encryptedValue: encrypted,
|
|
76
|
-
status: input.status,
|
|
77
|
-
verificationCode: input.verificationCode ?? null,
|
|
78
|
-
challengeText: input.challengeText ?? null,
|
|
79
|
-
expiresAt: input.expiresAt ?? null,
|
|
80
|
-
attemptsRemaining: input.attemptsRemaining ?? null,
|
|
81
|
-
updatedAt: new Date().toISOString(),
|
|
82
|
-
},
|
|
83
|
-
});
|
|
84
|
-
},
|
|
85
|
-
async loadCredentialContext(platformId) {
|
|
86
|
-
const record = await db.query.credentialRecords.findFirst({
|
|
87
|
-
where: (tbl) => eq(tbl.platformId, platformId),
|
|
88
|
-
});
|
|
89
|
-
if (!record)
|
|
90
|
-
return null;
|
|
91
|
-
let plain;
|
|
92
|
-
if (record.encryptedValue) {
|
|
93
|
-
if (!isCredentialCiphertext(record.encryptedValue)) {
|
|
94
|
-
throw new Error("credential_store_plaintext_or_invalid_legacy_record");
|
|
95
|
-
}
|
|
96
|
-
plain = decryptCredentialAtRest(record.encryptedValue);
|
|
97
|
-
}
|
|
98
|
-
return {
|
|
99
|
-
platformId: record.platformId,
|
|
100
|
-
credentialType: record.credentialType,
|
|
101
|
-
status: record.status,
|
|
102
|
-
encryptedValue: plain,
|
|
103
|
-
verificationCode: record.verificationCode ?? undefined,
|
|
104
|
-
challengeText: record.challengeText ?? undefined,
|
|
105
|
-
verificationDeadline: record.expiresAt ?? undefined,
|
|
106
|
-
attemptsRemaining: record.attemptsRemaining ?? undefined,
|
|
107
|
-
};
|
|
108
|
-
},
|
|
109
|
-
async getCredentialState(platformId) {
|
|
110
|
-
const record = await db.query.credentialRecords.findFirst({
|
|
111
|
-
where: (tbl) => eq(tbl.platformId, platformId),
|
|
112
|
-
});
|
|
113
|
-
return record?.status || "missing";
|
|
114
|
-
},
|
|
115
|
-
};
|
|
116
|
-
}
|
|
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
|
+
*/
|
|
7
|
+
import * as crypto from "crypto";
|
|
8
|
+
import { eq } from "drizzle-orm";
|
|
9
|
+
import { credentialRecords } from "../db/schema/index.js";
|
|
10
|
+
const ALGORITHM = "aes-256-gcm";
|
|
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) {
|
|
19
|
+
const iv = crypto.randomBytes(16);
|
|
20
|
+
const key = resolveKeyBuffer();
|
|
21
|
+
const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
|
|
22
|
+
const encrypted = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
|
|
23
|
+
const authTag = cipher.getAuthTag();
|
|
24
|
+
return iv.toString("hex") + ":" + authTag.toString("hex") + ":" + encrypted.toString("hex");
|
|
25
|
+
}
|
|
26
|
+
function decryptInternal(ciphertext) {
|
|
27
|
+
const parts = ciphertext.split(":");
|
|
28
|
+
if (parts.length !== 3) {
|
|
29
|
+
throw new Error("credential_ciphertext_invalid_format");
|
|
30
|
+
}
|
|
31
|
+
const iv = Buffer.from(parts[0], "hex");
|
|
32
|
+
const authTag = Buffer.from(parts[1], "hex");
|
|
33
|
+
const encrypted = Buffer.from(parts[2], "hex");
|
|
34
|
+
const key = resolveKeyBuffer();
|
|
35
|
+
const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
|
|
36
|
+
decipher.setAuthTag(authTag);
|
|
37
|
+
return decipher.update(encrypted) + decipher.final("utf8");
|
|
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
|
+
}
|
|
57
|
+
export function createCredentialVault(db) {
|
|
58
|
+
return {
|
|
59
|
+
async saveCredentialContext(input) {
|
|
60
|
+
const encrypted = input.encryptedValue ? encryptCredentialAtRest(input.encryptedValue) : "";
|
|
61
|
+
await db.insert(credentialRecords).values({
|
|
62
|
+
platformId: input.platformId,
|
|
63
|
+
credentialType: input.credentialType,
|
|
64
|
+
encryptedValue: encrypted,
|
|
65
|
+
status: input.status,
|
|
66
|
+
verificationCode: input.verificationCode ?? null,
|
|
67
|
+
challengeText: input.challengeText ?? null,
|
|
68
|
+
expiresAt: input.expiresAt ?? null,
|
|
69
|
+
attemptsRemaining: input.attemptsRemaining ?? null,
|
|
70
|
+
updatedAt: new Date().toISOString(),
|
|
71
|
+
}).onConflictDoUpdate({
|
|
72
|
+
target: credentialRecords.platformId,
|
|
73
|
+
set: {
|
|
74
|
+
credentialType: input.credentialType,
|
|
75
|
+
encryptedValue: encrypted,
|
|
76
|
+
status: input.status,
|
|
77
|
+
verificationCode: input.verificationCode ?? null,
|
|
78
|
+
challengeText: input.challengeText ?? null,
|
|
79
|
+
expiresAt: input.expiresAt ?? null,
|
|
80
|
+
attemptsRemaining: input.attemptsRemaining ?? null,
|
|
81
|
+
updatedAt: new Date().toISOString(),
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
},
|
|
85
|
+
async loadCredentialContext(platformId) {
|
|
86
|
+
const record = await db.query.credentialRecords.findFirst({
|
|
87
|
+
where: (tbl) => eq(tbl.platformId, platformId),
|
|
88
|
+
});
|
|
89
|
+
if (!record)
|
|
90
|
+
return null;
|
|
91
|
+
let plain;
|
|
92
|
+
if (record.encryptedValue) {
|
|
93
|
+
if (!isCredentialCiphertext(record.encryptedValue)) {
|
|
94
|
+
throw new Error("credential_store_plaintext_or_invalid_legacy_record");
|
|
95
|
+
}
|
|
96
|
+
plain = decryptCredentialAtRest(record.encryptedValue);
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
platformId: record.platformId,
|
|
100
|
+
credentialType: record.credentialType,
|
|
101
|
+
status: record.status,
|
|
102
|
+
encryptedValue: plain,
|
|
103
|
+
verificationCode: record.verificationCode ?? undefined,
|
|
104
|
+
challengeText: record.challengeText ?? undefined,
|
|
105
|
+
verificationDeadline: record.expiresAt ?? undefined,
|
|
106
|
+
attemptsRemaining: record.attemptsRemaining ?? undefined,
|
|
107
|
+
};
|
|
108
|
+
},
|
|
109
|
+
async getCredentialState(platformId) {
|
|
110
|
+
const record = await db.query.credentialRecords.findFirst({
|
|
111
|
+
where: (tbl) => eq(tbl.platformId, platformId),
|
|
112
|
+
});
|
|
113
|
+
return record?.status || "missing";
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
}
|
|
@@ -1,9 +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>;
|
|
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>;
|
|
@@ -1,41 +1,41 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Continuity snapshot read model (T4.2.1) — lightweight bridge until full state feeds land.
|
|
3
|
-
*/
|
|
4
|
-
import * as crypto from "node:crypto";
|
|
5
|
-
import { desc } from "drizzle-orm";
|
|
6
|
-
import { decisionLedger } from "../../observability/db/schema/index.js";
|
|
7
|
-
import { repairStateIndexes } from "../bootstrap/repair-gate.js";
|
|
8
|
-
import { loadLifeEvidenceSnapshot } from "./life-evidence-snapshot.js";
|
|
9
|
-
export async function loadContinuitySnapshot(params) {
|
|
10
|
-
await repairStateIndexes(params.state, { startupGate: true, workspaceRoot: params.workspaceRoot });
|
|
11
|
-
const life = await loadLifeEvidenceSnapshot(params.state, params.workspaceRoot, { limit: 50 }, { runRepairGate: false });
|
|
12
|
-
let recentDecisionRefs = [];
|
|
13
|
-
if (params.observability) {
|
|
14
|
-
const rows = await params.observability.db
|
|
15
|
-
.select()
|
|
16
|
-
.from(decisionLedger)
|
|
17
|
-
.orderBy(desc(decisionLedger.createdAt))
|
|
18
|
-
.limit(5);
|
|
19
|
-
recentDecisionRefs = rows.map((r) => ({
|
|
20
|
-
id: r.id,
|
|
21
|
-
kind: "decision_record",
|
|
22
|
-
uri: `sn://decision/${r.id}`,
|
|
23
|
-
}));
|
|
24
|
-
}
|
|
25
|
-
const pendingCount = life.platformEvents.length + life.workEvents.length + life.userInteractionEvents.length;
|
|
26
|
-
const oldest = [...life.platformEvents, ...life.workEvents, ...life.userInteractionEvents]
|
|
27
|
-
.map((e) => e.timestamp)
|
|
28
|
-
.sort()[0];
|
|
29
|
-
return {
|
|
30
|
-
snapshotId: crypto.randomUUID(),
|
|
31
|
-
generatedAt: new Date().toISOString(),
|
|
32
|
-
recentDecisionRefs,
|
|
33
|
-
openObligations: [],
|
|
34
|
-
quietDebt: {
|
|
35
|
-
hasUnprocessedEvidence: !life.empty,
|
|
36
|
-
oldestUnprocessedEvidenceAt: oldest,
|
|
37
|
-
pendingCount,
|
|
38
|
-
},
|
|
39
|
-
fallbackRefs: [],
|
|
40
|
-
};
|
|
41
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Continuity snapshot read model (T4.2.1) — lightweight bridge until full state feeds land.
|
|
3
|
+
*/
|
|
4
|
+
import * as crypto from "node:crypto";
|
|
5
|
+
import { desc } from "drizzle-orm";
|
|
6
|
+
import { decisionLedger } from "../../observability/db/schema/index.js";
|
|
7
|
+
import { repairStateIndexes } from "../bootstrap/repair-gate.js";
|
|
8
|
+
import { loadLifeEvidenceSnapshot } from "./life-evidence-snapshot.js";
|
|
9
|
+
export async function loadContinuitySnapshot(params) {
|
|
10
|
+
await repairStateIndexes(params.state, { startupGate: true, workspaceRoot: params.workspaceRoot });
|
|
11
|
+
const life = await loadLifeEvidenceSnapshot(params.state, params.workspaceRoot, { limit: 50 }, { runRepairGate: false });
|
|
12
|
+
let recentDecisionRefs = [];
|
|
13
|
+
if (params.observability) {
|
|
14
|
+
const rows = await params.observability.db
|
|
15
|
+
.select()
|
|
16
|
+
.from(decisionLedger)
|
|
17
|
+
.orderBy(desc(decisionLedger.createdAt))
|
|
18
|
+
.limit(5);
|
|
19
|
+
recentDecisionRefs = rows.map((r) => ({
|
|
20
|
+
id: r.id,
|
|
21
|
+
kind: "decision_record",
|
|
22
|
+
uri: `sn://decision/${r.id}`,
|
|
23
|
+
}));
|
|
24
|
+
}
|
|
25
|
+
const pendingCount = life.platformEvents.length + life.workEvents.length + life.userInteractionEvents.length;
|
|
26
|
+
const oldest = [...life.platformEvents, ...life.workEvents, ...life.userInteractionEvents]
|
|
27
|
+
.map((e) => e.timestamp)
|
|
28
|
+
.sort()[0];
|
|
29
|
+
return {
|
|
30
|
+
snapshotId: crypto.randomUUID(),
|
|
31
|
+
generatedAt: new Date().toISOString(),
|
|
32
|
+
recentDecisionRefs,
|
|
33
|
+
openObligations: [],
|
|
34
|
+
quietDebt: {
|
|
35
|
+
hasUnprocessedEvidence: !life.empty,
|
|
36
|
+
oldestUnprocessedEvidenceAt: oldest,
|
|
37
|
+
pendingCount,
|
|
38
|
+
},
|
|
39
|
+
fallbackRefs: [],
|
|
40
|
+
};
|
|
41
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { StateDatabase } from "../db/index.js";
|
|
2
|
-
import type { LifeEvidenceQuery, LifeEvidenceSnapshot } from "./types.js";
|
|
3
|
-
export interface LoadLifeEvidenceSnapshotOptions {
|
|
4
|
-
runRepairGate?: boolean;
|
|
5
|
-
}
|
|
6
|
-
export declare function loadLifeEvidenceSnapshot(state: StateDatabase, workspaceRoot: string, query: LifeEvidenceQuery, options?: LoadLifeEvidenceSnapshotOptions): Promise<LifeEvidenceSnapshot>;
|
|
1
|
+
import type { StateDatabase } from "../db/index.js";
|
|
2
|
+
import type { LifeEvidenceQuery, LifeEvidenceSnapshot } from "./types.js";
|
|
3
|
+
export interface LoadLifeEvidenceSnapshotOptions {
|
|
4
|
+
runRepairGate?: boolean;
|
|
5
|
+
}
|
|
6
|
+
export declare function loadLifeEvidenceSnapshot(state: StateDatabase, workspaceRoot: string, query: LifeEvidenceQuery, options?: LoadLifeEvidenceSnapshotOptions): Promise<LifeEvidenceSnapshot>;
|