@haaaiawd/second-nature 0.1.26 → 0.1.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/SKILL.md +35 -0
- package/agent-inner-guide.md +144 -0
- package/index.js +280 -2
- package/openclaw.plugin.json +2 -2
- package/package.json +4 -1
- package/runtime/cli/commands/connector-behavior.d.ts +20 -0
- package/runtime/cli/commands/connector-behavior.js +160 -0
- package/runtime/cli/commands/index.js +8 -0
- package/runtime/cli/index.js +9 -2
- package/runtime/cli/ops/manual-run-dispatcher.d.ts +79 -0
- package/runtime/cli/ops/manual-run-dispatcher.js +110 -0
- package/runtime/cli/ops/ops-router.d.ts +45 -4
- package/runtime/cli/ops/ops-router.js +543 -2
- package/runtime/cli/read-models/index.js +35 -18
- package/runtime/cli/read-models/types.d.ts +1 -0
- package/runtime/connectors/agent-network/agent-world/adapter.d.ts +1 -0
- package/runtime/connectors/agent-network/agent-world/adapter.js +2 -2
- package/runtime/connectors/base/contract.d.ts +4 -1
- package/runtime/connectors/base/contract.js +5 -1
- package/runtime/connectors/base/effect-commit-ledger-sqlite.d.ts +31 -0
- package/runtime/connectors/base/effect-commit-ledger-sqlite.js +86 -0
- package/runtime/connectors/base/failure-taxonomy.js +5 -0
- package/runtime/connectors/base/manifest-v7.d.ts +151 -0
- package/runtime/connectors/base/manifest-v7.js +170 -0
- package/runtime/connectors/base/manifest.d.ts +3 -13
- package/runtime/connectors/base/manifest.js +7 -7
- package/runtime/connectors/base/route-planner.js +11 -8
- package/runtime/connectors/base/structured-unavailable-reason.d.ts +59 -0
- package/runtime/connectors/base/structured-unavailable-reason.js +113 -0
- package/runtime/connectors/base/wet-probe-runner.d.ts +40 -0
- package/runtime/connectors/base/wet-probe-runner.js +132 -0
- package/runtime/connectors/manifest/manifest-schema.d.ts +4 -0
- package/runtime/connectors/manifest/manifest-schema.js +2 -0
- package/runtime/connectors/services/connector-executor-adapter.d.ts +1 -0
- package/runtime/connectors/services/connector-executor-adapter.js +132 -26
- package/runtime/core/second-nature/body/behavior-promotion/behavior-promotion-loop.d.ts +45 -0
- package/runtime/core/second-nature/body/behavior-promotion/behavior-promotion-loop.js +132 -0
- package/runtime/core/second-nature/body/circuit-breaker/circuit-breaker-manager.d.ts +60 -0
- package/runtime/core/second-nature/body/circuit-breaker/circuit-breaker-manager.js +174 -0
- package/runtime/core/second-nature/body/probe-signal-adapter.d.ts +38 -0
- package/runtime/core/second-nature/body/probe-signal-adapter.js +60 -0
- package/runtime/core/second-nature/body/tool-affordance/affordance-assembler.d.ts +51 -0
- package/runtime/core/second-nature/body/tool-affordance/affordance-assembler.js +129 -0
- package/runtime/core/second-nature/body/tool-affordance/affordance-context-scope.d.ts +30 -0
- package/runtime/core/second-nature/body/tool-affordance/affordance-context-scope.js +92 -0
- package/runtime/core/second-nature/body/tool-experience/experience-writer.d.ts +34 -0
- package/runtime/core/second-nature/body/tool-experience/experience-writer.js +67 -0
- package/runtime/core/second-nature/body/tool-experience/pain-signal-query.d.ts +37 -0
- package/runtime/core/second-nature/body/tool-experience/pain-signal-query.js +62 -0
- package/runtime/core/second-nature/heartbeat/decision-trace-emitter.d.ts +29 -0
- package/runtime/core/second-nature/heartbeat/decision-trace-emitter.js +28 -0
- package/runtime/core/second-nature/heartbeat/embodied-context-assembler.d.ts +54 -0
- package/runtime/core/second-nature/heartbeat/embodied-context-assembler.js +164 -0
- package/runtime/core/second-nature/heartbeat/goal-lifecycle-policy.d.ts +37 -0
- package/runtime/core/second-nature/heartbeat/goal-lifecycle-policy.js +61 -0
- package/runtime/core/second-nature/heartbeat/idle-curiosity-policy.d.ts +37 -0
- package/runtime/core/second-nature/heartbeat/idle-curiosity-policy.js +60 -0
- package/runtime/core/second-nature/heartbeat/index.d.ts +4 -0
- package/runtime/core/second-nature/heartbeat/index.js +5 -0
- package/runtime/core/second-nature/heartbeat/run-heartbeat-cycle-v7.d.ts +63 -0
- package/runtime/core/second-nature/heartbeat/run-heartbeat-cycle-v7.js +118 -0
- package/runtime/core/second-nature/orchestrator/downstream-intent-orchestrator.d.ts +41 -0
- package/runtime/core/second-nature/orchestrator/downstream-intent-orchestrator.js +43 -0
- package/runtime/core/second-nature/orchestrator/effect-dispatcher.d.ts +2 -1
- package/runtime/core/second-nature/orchestrator/effect-dispatcher.js +2 -0
- package/runtime/core/second-nature/orchestrator/hard-guard-evaluator.d.ts +31 -0
- package/runtime/core/second-nature/orchestrator/hard-guard-evaluator.js +102 -0
- package/runtime/core/second-nature/orchestrator/index.d.ts +5 -0
- package/runtime/core/second-nature/orchestrator/index.js +7 -0
- package/runtime/core/second-nature/quiet/claim-synthesizer.d.ts +53 -0
- package/runtime/core/second-nature/quiet/claim-synthesizer.js +153 -0
- package/runtime/core/second-nature/quiet/daily-diary-writer.d.ts +29 -0
- package/runtime/core/second-nature/quiet/daily-diary-writer.js +92 -0
- package/runtime/core/second-nature/quiet/index.d.ts +5 -0
- package/runtime/core/second-nature/quiet/index.js +5 -0
- package/runtime/core/second-nature/quiet/run-source-backed-quiet.js +19 -12
- package/runtime/core/second-nature/types.d.ts +2 -0
- package/runtime/guidance/channel-feedback-ingestion-service.d.ts +88 -0
- package/runtime/guidance/channel-feedback-ingestion-service.js +231 -0
- package/runtime/guidance/guidance-draft-service.d.ts +60 -0
- package/runtime/guidance/guidance-draft-service.js +80 -0
- package/runtime/guidance/index.d.ts +3 -0
- package/runtime/guidance/index.js +3 -0
- package/runtime/guidance/outreach-draft-schema.d.ts +8 -8
- package/runtime/guidance/outreach-strategy-selector.d.ts +77 -0
- package/runtime/guidance/outreach-strategy-selector.js +211 -0
- package/runtime/observability/audit/append-only-audit-store.d.ts +20 -2
- package/runtime/observability/audit/append-only-audit-store.js +32 -6
- package/runtime/observability/audit/audit-envelope.d.ts +2 -1
- package/runtime/observability/audit/audit-envelope.js +8 -7
- package/runtime/observability/audit/audit-family-registry.json +66 -0
- package/runtime/observability/audit/family-registry.d.ts +43 -0
- package/runtime/observability/audit/family-registry.js +70 -0
- package/runtime/observability/index.d.ts +6 -1
- package/runtime/observability/index.js +6 -1
- package/runtime/observability/redaction/policy.d.ts +24 -3
- package/runtime/observability/redaction/policy.js +74 -0
- package/runtime/observability/services/heartbeat-digest-assembler.d.ts +152 -0
- package/runtime/observability/services/heartbeat-digest-assembler.js +248 -0
- package/runtime/observability/services/lived-experience-audit.js +6 -6
- package/runtime/observability/services/narrative-timeline-query-service.d.ts +136 -0
- package/runtime/observability/services/narrative-timeline-query-service.js +169 -0
- package/runtime/observability/services/restore-audit-service.d.ts +74 -0
- package/runtime/observability/services/restore-audit-service.js +79 -0
- package/runtime/observability/services/runtime-secret-anchor-view.d.ts +77 -0
- package/runtime/observability/services/runtime-secret-anchor-view.js +168 -0
- package/runtime/observability/services/self-health-snapshot.d.ts +92 -0
- package/runtime/observability/services/self-health-snapshot.js +251 -0
- package/runtime/shared/types/goal.d.ts +62 -0
- package/runtime/shared/types/goal.js +20 -0
- package/runtime/shared/types/index.d.ts +3 -0
- package/runtime/shared/types/index.js +3 -0
- package/runtime/shared/types/source-ref.d.ts +14 -0
- package/runtime/shared/types/source-ref.js +1 -0
- package/runtime/shared/types/v7-entities.d.ts +206 -0
- package/runtime/shared/types/v7-entities.js +27 -0
- package/runtime/storage/db/index.js +3 -0
- package/runtime/storage/db/migration-runner.d.ts +30 -0
- package/runtime/storage/db/migration-runner.js +93 -0
- package/runtime/storage/db/migrations/index.d.ts +5 -0
- package/runtime/storage/db/migrations/index.js +13 -0
- package/runtime/storage/db/migrations/v7-001-foundation.d.ts +13 -0
- package/runtime/storage/db/migrations/v7-001-foundation.js +144 -0
- package/runtime/storage/db/migrations/v7-002-effect-commit-ledger.d.ts +8 -0
- package/runtime/storage/db/migrations/v7-002-effect-commit-ledger.js +27 -0
- package/runtime/storage/db/migrations/v7-003-circuit-breaker.d.ts +7 -0
- package/runtime/storage/db/migrations/v7-003-circuit-breaker.js +26 -0
- package/runtime/storage/db/migrations/v7-004-behavior-promotion.d.ts +7 -0
- package/runtime/storage/db/migrations/v7-004-behavior-promotion.js +26 -0
- package/runtime/storage/db/schema/agent-goal.d.ts +38 -0
- package/runtime/storage/db/schema/agent-goal.js +2 -0
- package/runtime/storage/db/transaction-utils.d.ts +14 -0
- package/runtime/storage/db/transaction-utils.js +29 -0
- package/runtime/storage/db/write-queue.d.ts +38 -0
- package/runtime/storage/db/write-queue.js +97 -0
- package/runtime/storage/quiet/persist-quiet-artifact.js +2 -1
- package/runtime/storage/services/credential-vault.js +31 -17
- package/runtime/storage/services/diary-dream-store.d.ts +35 -0
- package/runtime/storage/services/diary-dream-store.js +165 -0
- package/runtime/storage/services/embodied-context-state-port.d.ts +77 -0
- package/runtime/storage/services/embodied-context-state-port.js +115 -0
- package/runtime/storage/services/goal-lifecycle-store.d.ts +42 -0
- package/runtime/storage/services/goal-lifecycle-store.js +181 -0
- package/runtime/storage/services/history-digest-store.d.ts +33 -0
- package/runtime/storage/services/history-digest-store.js +140 -0
- package/runtime/storage/services/identity-profile-store.d.ts +25 -0
- package/runtime/storage/services/identity-profile-store.js +81 -0
- package/runtime/storage/services/interaction-snapshot-projector.d.ts +15 -0
- package/runtime/storage/services/interaction-snapshot-projector.js +35 -0
- package/runtime/storage/services/restore-snapshot-store.d.ts +52 -0
- package/runtime/storage/services/restore-snapshot-store.js +193 -0
- package/runtime/storage/services/runtime-secret-anchor-store.d.ts +26 -0
- package/runtime/storage/services/runtime-secret-anchor-store.js +82 -0
- package/runtime/storage/services/tool-experience-store.d.ts +25 -0
- package/runtime/storage/services/tool-experience-store.js +116 -0
- package/runtime/storage/services/write-validation-gate.d.ts +46 -0
- package/runtime/storage/services/write-validation-gate.js +200 -0
- package/workspace-ops-bridge.js +16 -1
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RestoreSnapshotStore — T-SMS.C.6
|
|
3
|
+
*
|
|
4
|
+
* Core logic: Capture entity snapshots with a whitelist of 6 restorable kinds.
|
|
5
|
+
* Automatically excludes sensitive kinds (credential, raw_private_message,
|
|
6
|
+
* raw_prompt, encryption_key, session_token) per DR-017. Retains only the
|
|
7
|
+
* most recent 3 snapshots by default.
|
|
8
|
+
*
|
|
9
|
+
* Dependencies:
|
|
10
|
+
* - `StateDatabase` from `../db/index.js`
|
|
11
|
+
* - `RestoreSnapshot`, `RestorableEntityKind`, `SensitiveExcludedKind`
|
|
12
|
+
* from `../../shared/types/v7-entities.js`
|
|
13
|
+
*
|
|
14
|
+
* Boundary:
|
|
15
|
+
* - `captureSnapshot` silently drops any requested sensitive kinds.
|
|
16
|
+
* - `loadLatestSnapshot` returns the most recent capture.
|
|
17
|
+
* - `listSnapshots` orders by `captured_at` descending.
|
|
18
|
+
*
|
|
19
|
+
* Test coverage: tests/unit/storage/restore-snapshot-store.test.ts
|
|
20
|
+
*/
|
|
21
|
+
import type { StateDatabase } from "../db/index.js";
|
|
22
|
+
import type { RestoreSnapshot, RestorableEntityKind } from "../../shared/types/v7-entities.js";
|
|
23
|
+
export interface RestoreSnapshotStore {
|
|
24
|
+
captureSnapshot(input: {
|
|
25
|
+
snapshotId: string;
|
|
26
|
+
entityWhitelist?: RestorableEntityKind[];
|
|
27
|
+
payload: Record<string, unknown>;
|
|
28
|
+
capturedAt?: string;
|
|
29
|
+
}): Promise<RestoreSnapshot>;
|
|
30
|
+
loadLatestSnapshot(): Promise<RestoreSnapshot | undefined>;
|
|
31
|
+
listSnapshots(limit?: number): Promise<RestoreSnapshot[]>;
|
|
32
|
+
/**
|
|
33
|
+
* Apply a bounded restore from a captured snapshot.
|
|
34
|
+
* Loads the matching snapshot (by restoreTarget id, or latest fallback),
|
|
35
|
+
* then attempts to write each whitelisted entity back into its table.
|
|
36
|
+
* Sensitive kinds are always skipped. Never restores credential fields.
|
|
37
|
+
*/
|
|
38
|
+
applyBoundedRestore(input: {
|
|
39
|
+
restoreTarget: string;
|
|
40
|
+
fromVersion: string;
|
|
41
|
+
toVersion: string;
|
|
42
|
+
entityWhitelist?: RestorableEntityKind[];
|
|
43
|
+
}): Promise<{
|
|
44
|
+
ok: boolean;
|
|
45
|
+
completedEntities: string[];
|
|
46
|
+
failedEntities: string[];
|
|
47
|
+
warnings: string[];
|
|
48
|
+
}>;
|
|
49
|
+
}
|
|
50
|
+
export declare function createRestoreSnapshotStore(database: StateDatabase, options?: {
|
|
51
|
+
retentionCount?: number;
|
|
52
|
+
}): RestoreSnapshotStore;
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RestoreSnapshotStore — T-SMS.C.6
|
|
3
|
+
*
|
|
4
|
+
* Core logic: Capture entity snapshots with a whitelist of 6 restorable kinds.
|
|
5
|
+
* Automatically excludes sensitive kinds (credential, raw_private_message,
|
|
6
|
+
* raw_prompt, encryption_key, session_token) per DR-017. Retains only the
|
|
7
|
+
* most recent 3 snapshots by default.
|
|
8
|
+
*
|
|
9
|
+
* Dependencies:
|
|
10
|
+
* - `StateDatabase` from `../db/index.js`
|
|
11
|
+
* - `RestoreSnapshot`, `RestorableEntityKind`, `SensitiveExcludedKind`
|
|
12
|
+
* from `../../shared/types/v7-entities.js`
|
|
13
|
+
*
|
|
14
|
+
* Boundary:
|
|
15
|
+
* - `captureSnapshot` silently drops any requested sensitive kinds.
|
|
16
|
+
* - `loadLatestSnapshot` returns the most recent capture.
|
|
17
|
+
* - `listSnapshots` orders by `captured_at` descending.
|
|
18
|
+
*
|
|
19
|
+
* Test coverage: tests/unit/storage/restore-snapshot-store.test.ts
|
|
20
|
+
*/
|
|
21
|
+
const ALL_RESTORABLE_KINDS = [
|
|
22
|
+
"identity_profile",
|
|
23
|
+
"agent_goal",
|
|
24
|
+
"tool_experience",
|
|
25
|
+
"daily_diary",
|
|
26
|
+
"dream_output",
|
|
27
|
+
"narrative_timeline",
|
|
28
|
+
];
|
|
29
|
+
const DEFAULT_EXCLUDED_KINDS = [
|
|
30
|
+
"credential",
|
|
31
|
+
"raw_private_message",
|
|
32
|
+
"raw_prompt",
|
|
33
|
+
"encryption_key",
|
|
34
|
+
"session_token",
|
|
35
|
+
];
|
|
36
|
+
const DEFAULT_RETENTION_COUNT = 3;
|
|
37
|
+
function safeParseJson(json, fallback) {
|
|
38
|
+
try {
|
|
39
|
+
return JSON.parse(json);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return fallback;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function parseSnapshotRow(cols, row) {
|
|
46
|
+
const get = (name) => row[cols.indexOf(name)];
|
|
47
|
+
return {
|
|
48
|
+
snapshotId: get("snapshot_id"),
|
|
49
|
+
entityWhitelist: safeParseJson(get("entity_whitelist_json") ?? "[]", []),
|
|
50
|
+
excludedSensitiveKinds: safeParseJson(get("excluded_sensitive_kinds_json") ?? "[]", []),
|
|
51
|
+
capturedAt: get("captured_at"),
|
|
52
|
+
payload: safeParseJson(get("payload_json") ?? "{}", {}),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
export function createRestoreSnapshotStore(database, options = {}) {
|
|
56
|
+
const { sqlite } = database;
|
|
57
|
+
const retentionCount = options.retentionCount ?? DEFAULT_RETENTION_COUNT;
|
|
58
|
+
function trimOldSnapshots() {
|
|
59
|
+
const countResult = sqlite.exec(`SELECT COUNT(*) as cnt FROM restore_snapshot`);
|
|
60
|
+
if (countResult.length === 0 ||
|
|
61
|
+
countResult[0].values.length === 0) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const total = countResult[0].values[0][0] ?? 0;
|
|
65
|
+
if (total <= retentionCount)
|
|
66
|
+
return;
|
|
67
|
+
const toDelete = total - retentionCount;
|
|
68
|
+
sqlite.exec(`DELETE FROM restore_snapshot
|
|
69
|
+
WHERE snapshot_id IN (
|
|
70
|
+
SELECT snapshot_id FROM restore_snapshot
|
|
71
|
+
ORDER BY captured_at ASC
|
|
72
|
+
LIMIT ${toDelete}
|
|
73
|
+
)`);
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
async captureSnapshot(input) {
|
|
77
|
+
const whitelist = input.entityWhitelist && input.entityWhitelist.length > 0
|
|
78
|
+
? input.entityWhitelist.filter((k) => ALL_RESTORABLE_KINDS.includes(k))
|
|
79
|
+
: [...ALL_RESTORABLE_KINDS];
|
|
80
|
+
const excluded = [...DEFAULT_EXCLUDED_KINDS];
|
|
81
|
+
const capturedAt = input.capturedAt ?? new Date().toISOString();
|
|
82
|
+
sqlite.run(`INSERT INTO restore_snapshot
|
|
83
|
+
(snapshot_id, entity_whitelist_json, excluded_sensitive_kinds_json, captured_at, payload_json)
|
|
84
|
+
VALUES (?, ?, ?, ?, ?)`, [
|
|
85
|
+
input.snapshotId,
|
|
86
|
+
JSON.stringify(whitelist),
|
|
87
|
+
JSON.stringify(excluded),
|
|
88
|
+
capturedAt,
|
|
89
|
+
JSON.stringify(input.payload),
|
|
90
|
+
]);
|
|
91
|
+
trimOldSnapshots();
|
|
92
|
+
return {
|
|
93
|
+
snapshotId: input.snapshotId,
|
|
94
|
+
entityWhitelist: whitelist,
|
|
95
|
+
excludedSensitiveKinds: [...excluded],
|
|
96
|
+
capturedAt,
|
|
97
|
+
payload: input.payload,
|
|
98
|
+
};
|
|
99
|
+
},
|
|
100
|
+
async loadLatestSnapshot() {
|
|
101
|
+
const result = sqlite.exec(`SELECT * FROM restore_snapshot
|
|
102
|
+
ORDER BY captured_at DESC
|
|
103
|
+
LIMIT 1`);
|
|
104
|
+
if (result.length === 0 || result[0].values.length === 0) {
|
|
105
|
+
return undefined;
|
|
106
|
+
}
|
|
107
|
+
return parseSnapshotRow(result[0].columns, result[0].values[0]);
|
|
108
|
+
},
|
|
109
|
+
async listSnapshots(limit = 10) {
|
|
110
|
+
const result = sqlite.exec(`SELECT * FROM restore_snapshot
|
|
111
|
+
ORDER BY captured_at DESC
|
|
112
|
+
LIMIT ${limit}`);
|
|
113
|
+
if (result.length === 0 || result[0].values.length === 0) {
|
|
114
|
+
return [];
|
|
115
|
+
}
|
|
116
|
+
return result[0].values.map((row) => parseSnapshotRow(result[0].columns, row));
|
|
117
|
+
},
|
|
118
|
+
async applyBoundedRestore(input) {
|
|
119
|
+
const warnings = [];
|
|
120
|
+
const completedEntities = [];
|
|
121
|
+
const failedEntities = [];
|
|
122
|
+
// 1. Find matching snapshot by exact id, then fallback to latest
|
|
123
|
+
let snapshot;
|
|
124
|
+
const exactMatch = sqlite.exec(`SELECT * FROM restore_snapshot WHERE snapshot_id = ? LIMIT 1`, [input.restoreTarget]);
|
|
125
|
+
if (exactMatch.length > 0 &&
|
|
126
|
+
exactMatch[0].values.length > 0) {
|
|
127
|
+
snapshot = parseSnapshotRow(exactMatch[0].columns, exactMatch[0].values[0]);
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
const latestResult = sqlite.exec(`SELECT * FROM restore_snapshot ORDER BY captured_at DESC LIMIT 1`);
|
|
131
|
+
if (latestResult.length > 0 &&
|
|
132
|
+
latestResult[0].values.length > 0) {
|
|
133
|
+
snapshot = parseSnapshotRow(latestResult[0].columns, latestResult[0].values[0]);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (!snapshot) {
|
|
137
|
+
return {
|
|
138
|
+
ok: false,
|
|
139
|
+
completedEntities: [],
|
|
140
|
+
failedEntities: [input.restoreTarget],
|
|
141
|
+
warnings: ["snapshot_not_found"],
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
const whitelist = input.entityWhitelist && input.entityWhitelist.length > 0
|
|
145
|
+
? input.entityWhitelist.filter((k) => ALL_RESTORABLE_KINDS.includes(k))
|
|
146
|
+
: [...snapshot.entityWhitelist];
|
|
147
|
+
for (const kind of whitelist) {
|
|
148
|
+
// Never restore sensitive kinds (DR-017)
|
|
149
|
+
if (DEFAULT_EXCLUDED_KINDS.includes(kind)) {
|
|
150
|
+
warnings.push(`skipped_sensitive_kind:${kind}`);
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
const kindData = snapshot.payload[kind];
|
|
154
|
+
if (!kindData) {
|
|
155
|
+
// Snapshot whitelist includes this kind but payload has no data for it.
|
|
156
|
+
// This is not a failure — the entity simply had no state at capture time.
|
|
157
|
+
warnings.push(`payload_missing:${kind}`);
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
try {
|
|
161
|
+
const rows = Array.isArray(kindData) ? kindData : [kindData];
|
|
162
|
+
if (rows.length === 0) {
|
|
163
|
+
warnings.push(`empty_payload:${kind}`);
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
for (const row of rows) {
|
|
167
|
+
if (!row || typeof row !== "object")
|
|
168
|
+
continue;
|
|
169
|
+
const keys = Object.keys(row);
|
|
170
|
+
if (keys.length === 0)
|
|
171
|
+
continue;
|
|
172
|
+
const columns = keys.join(", ");
|
|
173
|
+
const placeholders = keys.map(() => "?").join(", ");
|
|
174
|
+
const values = keys.map((k) => row[k]);
|
|
175
|
+
sqlite.run(`INSERT OR REPLACE INTO ${kind} (${columns}) VALUES (${placeholders})`, values);
|
|
176
|
+
}
|
|
177
|
+
completedEntities.push(kind);
|
|
178
|
+
}
|
|
179
|
+
catch (err) {
|
|
180
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
181
|
+
warnings.push(`restore_failed:${kind}:${msg}`);
|
|
182
|
+
failedEntities.push(kind);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return {
|
|
186
|
+
ok: failedEntities.length === 0,
|
|
187
|
+
completedEntities,
|
|
188
|
+
failedEntities,
|
|
189
|
+
warnings,
|
|
190
|
+
};
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RuntimeSecretAnchorStore — T-SMS.C.6
|
|
3
|
+
*
|
|
4
|
+
* Core logic: Stores only locationRef/health/rotationPolicyRef for runtime
|
|
5
|
+
* secrets. Key plaintext is NEVER persisted (ADR-007). WriteValidationGate
|
|
6
|
+
* rejects any payload that contains key material.
|
|
7
|
+
*
|
|
8
|
+
* Dependencies:
|
|
9
|
+
* - `StateDatabase` from `../db/index.js`
|
|
10
|
+
* - `RuntimeSecretAnchor`, `SecretAnchorHealth` from `../../shared/types/v7-entities.js`
|
|
11
|
+
* - `validateWritePayload` from `./write-validation-gate.js`
|
|
12
|
+
*
|
|
13
|
+
* Boundary:
|
|
14
|
+
* - No key/credential/token/encryption_key field is ever written.
|
|
15
|
+
* - Gate scans for sensitive field keys and rejects on hit.
|
|
16
|
+
*
|
|
17
|
+
* Test coverage: tests/unit/storage/runtime-secret-anchor-store.test.ts
|
|
18
|
+
*/
|
|
19
|
+
import type { StateDatabase } from "../db/index.js";
|
|
20
|
+
import type { RuntimeSecretAnchor } from "../../shared/types/v7-entities.js";
|
|
21
|
+
export interface RuntimeSecretAnchorStore {
|
|
22
|
+
upsertAnchor(anchor: RuntimeSecretAnchor): Promise<void>;
|
|
23
|
+
loadAnchor(anchorId: string): Promise<RuntimeSecretAnchor | undefined>;
|
|
24
|
+
listAnchors(): Promise<RuntimeSecretAnchor[]>;
|
|
25
|
+
}
|
|
26
|
+
export declare function createRuntimeSecretAnchorStore(database: StateDatabase): RuntimeSecretAnchorStore;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RuntimeSecretAnchorStore — T-SMS.C.6
|
|
3
|
+
*
|
|
4
|
+
* Core logic: Stores only locationRef/health/rotationPolicyRef for runtime
|
|
5
|
+
* secrets. Key plaintext is NEVER persisted (ADR-007). WriteValidationGate
|
|
6
|
+
* rejects any payload that contains key material.
|
|
7
|
+
*
|
|
8
|
+
* Dependencies:
|
|
9
|
+
* - `StateDatabase` from `../db/index.js`
|
|
10
|
+
* - `RuntimeSecretAnchor`, `SecretAnchorHealth` from `../../shared/types/v7-entities.js`
|
|
11
|
+
* - `validateWritePayload` from `./write-validation-gate.js`
|
|
12
|
+
*
|
|
13
|
+
* Boundary:
|
|
14
|
+
* - No key/credential/token/encryption_key field is ever written.
|
|
15
|
+
* - Gate scans for sensitive field keys and rejects on hit.
|
|
16
|
+
*
|
|
17
|
+
* Test coverage: tests/unit/storage/runtime-secret-anchor-store.test.ts
|
|
18
|
+
*/
|
|
19
|
+
import { validateWritePayload } from "./write-validation-gate.js";
|
|
20
|
+
export function createRuntimeSecretAnchorStore(database) {
|
|
21
|
+
const { sqlite } = database;
|
|
22
|
+
return {
|
|
23
|
+
async upsertAnchor(anchor) {
|
|
24
|
+
// Guard: reject any payload that hints at key material
|
|
25
|
+
const gate = validateWritePayload({
|
|
26
|
+
anchorId: anchor.anchorId,
|
|
27
|
+
locationRef: anchor.locationRef,
|
|
28
|
+
health: anchor.health,
|
|
29
|
+
rotationPolicyRef: anchor.rotationPolicyRef,
|
|
30
|
+
updatedAt: anchor.updatedAt,
|
|
31
|
+
});
|
|
32
|
+
if (!gate.ok) {
|
|
33
|
+
throw new Error(`RuntimeSecretAnchor rejected by gate: ${gate.reason}`);
|
|
34
|
+
}
|
|
35
|
+
sqlite.run(`INSERT INTO runtime_secret_anchor
|
|
36
|
+
(anchor_id, location_ref, health, rotation_policy_ref, updated_at)
|
|
37
|
+
VALUES (?, ?, ?, ?, ?)
|
|
38
|
+
ON CONFLICT(anchor_id) DO UPDATE SET
|
|
39
|
+
location_ref = excluded.location_ref,
|
|
40
|
+
health = excluded.health,
|
|
41
|
+
rotation_policy_ref = excluded.rotation_policy_ref,
|
|
42
|
+
updated_at = excluded.updated_at`, [
|
|
43
|
+
anchor.anchorId,
|
|
44
|
+
anchor.locationRef,
|
|
45
|
+
anchor.health,
|
|
46
|
+
anchor.rotationPolicyRef ?? null,
|
|
47
|
+
anchor.updatedAt,
|
|
48
|
+
]);
|
|
49
|
+
},
|
|
50
|
+
async loadAnchor(anchorId) {
|
|
51
|
+
const result = sqlite.exec(`SELECT * FROM runtime_secret_anchor WHERE anchor_id = ?`, [anchorId]);
|
|
52
|
+
if (result.length === 0 || result[0].values.length === 0) {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
const cols = result[0].columns;
|
|
56
|
+
const get = (row, name) => row[cols.indexOf(name)];
|
|
57
|
+
const row = result[0].values[0];
|
|
58
|
+
return {
|
|
59
|
+
anchorId: get(row, "anchor_id"),
|
|
60
|
+
locationRef: get(row, "location_ref"),
|
|
61
|
+
health: get(row, "health"),
|
|
62
|
+
rotationPolicyRef: get(row, "rotation_policy_ref") ?? undefined,
|
|
63
|
+
updatedAt: get(row, "updated_at"),
|
|
64
|
+
};
|
|
65
|
+
},
|
|
66
|
+
async listAnchors() {
|
|
67
|
+
const result = sqlite.exec(`SELECT * FROM runtime_secret_anchor ORDER BY updated_at DESC`);
|
|
68
|
+
if (result.length === 0 || result[0].values.length === 0) {
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
const cols = result[0].columns;
|
|
72
|
+
const get = (row, name) => row[cols.indexOf(name)];
|
|
73
|
+
return result[0].values.map((row) => ({
|
|
74
|
+
anchorId: get(row, "anchor_id"),
|
|
75
|
+
locationRef: get(row, "location_ref"),
|
|
76
|
+
health: get(row, "health"),
|
|
77
|
+
rotationPolicyRef: get(row, "rotation_policy_ref") ?? undefined,
|
|
78
|
+
updatedAt: get(row, "updated_at"),
|
|
79
|
+
}));
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ToolExperienceStore + CapabilityProbeResultStore — T-SMS.C.5
|
|
3
|
+
*
|
|
4
|
+
* Core logic: Append-only ToolExperience rows (outcome/failureClass/latencyMs/
|
|
5
|
+
* evidenceQuality/sourceRefs/triggerSource). Raw payload rejected by gate.
|
|
6
|
+
* CapabilityProbeResult stored with capabilityId/actualStatus/httpStatus.
|
|
7
|
+
* DR-007: failureClass directly from ConnectorResult.
|
|
8
|
+
* DR-010: triggerSource mandatory.
|
|
9
|
+
*/
|
|
10
|
+
import type { StateDatabase } from "../db/index.js";
|
|
11
|
+
import type { ToolExperience, CapabilityProbeResult } from "../../shared/types/v7-entities.js";
|
|
12
|
+
export interface ToolExperienceStore {
|
|
13
|
+
appendToolExperience(exp: ToolExperience): Promise<void>;
|
|
14
|
+
listToolExperience(query: {
|
|
15
|
+
connectorId?: string;
|
|
16
|
+
capabilityId?: string;
|
|
17
|
+
limit?: number;
|
|
18
|
+
}): Promise<ToolExperience[]>;
|
|
19
|
+
}
|
|
20
|
+
export interface CapabilityProbeResultStore {
|
|
21
|
+
appendProbeResult(result: CapabilityProbeResult): Promise<void>;
|
|
22
|
+
listProbeResults(connectorId: string, limit?: number): Promise<CapabilityProbeResult[]>;
|
|
23
|
+
}
|
|
24
|
+
export declare function createToolExperienceStore(database: StateDatabase): ToolExperienceStore;
|
|
25
|
+
export declare function createCapabilityProbeResultStore(database: StateDatabase): CapabilityProbeResultStore;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ToolExperienceStore + CapabilityProbeResultStore — T-SMS.C.5
|
|
3
|
+
*
|
|
4
|
+
* Core logic: Append-only ToolExperience rows (outcome/failureClass/latencyMs/
|
|
5
|
+
* evidenceQuality/sourceRefs/triggerSource). Raw payload rejected by gate.
|
|
6
|
+
* CapabilityProbeResult stored with capabilityId/actualStatus/httpStatus.
|
|
7
|
+
* DR-007: failureClass directly from ConnectorResult.
|
|
8
|
+
* DR-010: triggerSource mandatory.
|
|
9
|
+
*/
|
|
10
|
+
import { validateWritePayload } from "./write-validation-gate.js";
|
|
11
|
+
function safeParseJson(json, fallback) {
|
|
12
|
+
try {
|
|
13
|
+
return JSON.parse(json);
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return fallback;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export function createToolExperienceStore(database) {
|
|
20
|
+
const { sqlite } = database;
|
|
21
|
+
return {
|
|
22
|
+
async appendToolExperience(exp) {
|
|
23
|
+
const gate = validateWritePayload({
|
|
24
|
+
...exp,
|
|
25
|
+
sourceRefs: exp.sourceRefs,
|
|
26
|
+
}, { runSensitivityScan: false });
|
|
27
|
+
if (!gate.ok)
|
|
28
|
+
throw new Error(gate.reason ?? "write_validation_failed");
|
|
29
|
+
sqlite.run(`INSERT INTO tool_experience
|
|
30
|
+
(experience_id, connector_id, capability_id, outcome, failure_class,
|
|
31
|
+
latency_ms, evidence_quality, source_refs_json, trigger_source, created_at)
|
|
32
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
33
|
+
exp.experienceId,
|
|
34
|
+
exp.connectorId,
|
|
35
|
+
exp.capabilityId,
|
|
36
|
+
exp.outcome,
|
|
37
|
+
exp.failureClass ?? null,
|
|
38
|
+
exp.latencyMs,
|
|
39
|
+
exp.evidenceQuality,
|
|
40
|
+
JSON.stringify(exp.sourceRefs),
|
|
41
|
+
exp.triggerSource,
|
|
42
|
+
exp.createdAt,
|
|
43
|
+
]);
|
|
44
|
+
},
|
|
45
|
+
async listToolExperience(query = {}) {
|
|
46
|
+
let sql = `SELECT * FROM tool_experience WHERE 1=1`;
|
|
47
|
+
const params = [];
|
|
48
|
+
if (query.connectorId) {
|
|
49
|
+
sql += ` AND connector_id = ?`;
|
|
50
|
+
params.push(query.connectorId);
|
|
51
|
+
}
|
|
52
|
+
if (query.capabilityId) {
|
|
53
|
+
sql += ` AND capability_id = ?`;
|
|
54
|
+
params.push(query.capabilityId);
|
|
55
|
+
}
|
|
56
|
+
sql += ` ORDER BY created_at DESC LIMIT ${query.limit ?? 100}`;
|
|
57
|
+
const result = sqlite.exec(sql, params);
|
|
58
|
+
if (result.length === 0 || result[0].values.length === 0)
|
|
59
|
+
return [];
|
|
60
|
+
const cols = result[0].columns;
|
|
61
|
+
const get = (row, name) => row[cols.indexOf(name)];
|
|
62
|
+
return result[0].values.map((row) => ({
|
|
63
|
+
experienceId: get(row, "experience_id"),
|
|
64
|
+
connectorId: get(row, "connector_id"),
|
|
65
|
+
capabilityId: get(row, "capability_id"),
|
|
66
|
+
outcome: get(row, "outcome"),
|
|
67
|
+
failureClass: get(row, "failure_class") ?? undefined,
|
|
68
|
+
latencyMs: get(row, "latency_ms"),
|
|
69
|
+
evidenceQuality: get(row, "evidence_quality"),
|
|
70
|
+
sourceRefs: safeParseJson(get(row, "source_refs_json") ?? "[]", ["store:default"]),
|
|
71
|
+
triggerSource: get(row, "trigger_source"),
|
|
72
|
+
createdAt: get(row, "created_at"),
|
|
73
|
+
}));
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
export function createCapabilityProbeResultStore(database) {
|
|
78
|
+
const { sqlite } = database;
|
|
79
|
+
return {
|
|
80
|
+
async appendProbeResult(result) {
|
|
81
|
+
sqlite.run(`INSERT INTO capability_probe_result
|
|
82
|
+
(probe_result_id, capability_id, connector_id, actual_status,
|
|
83
|
+
http_status, sample_response_ref, probe_config_ref, created_at)
|
|
84
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
85
|
+
result.probeResultId,
|
|
86
|
+
result.capabilityId,
|
|
87
|
+
result.connectorId,
|
|
88
|
+
result.actualStatus,
|
|
89
|
+
result.httpStatus ?? null,
|
|
90
|
+
result.sampleResponseRef ?? null,
|
|
91
|
+
result.probeConfigRef,
|
|
92
|
+
result.createdAt,
|
|
93
|
+
]);
|
|
94
|
+
},
|
|
95
|
+
async listProbeResults(connectorId, limit = 10) {
|
|
96
|
+
const result = sqlite.exec(`SELECT * FROM capability_probe_result
|
|
97
|
+
WHERE connector_id = ?
|
|
98
|
+
ORDER BY created_at DESC
|
|
99
|
+
LIMIT ${limit}`, [connectorId]);
|
|
100
|
+
if (result.length === 0 || result[0].values.length === 0)
|
|
101
|
+
return [];
|
|
102
|
+
const cols = result[0].columns;
|
|
103
|
+
const get = (row, name) => row[cols.indexOf(name)];
|
|
104
|
+
return result[0].values.map((row) => ({
|
|
105
|
+
probeResultId: get(row, "probe_result_id"),
|
|
106
|
+
capabilityId: get(row, "capability_id"),
|
|
107
|
+
connectorId: get(row, "connector_id"),
|
|
108
|
+
actualStatus: get(row, "actual_status"),
|
|
109
|
+
httpStatus: get(row, "http_status") ?? undefined,
|
|
110
|
+
sampleResponseRef: get(row, "sample_response_ref") ?? undefined,
|
|
111
|
+
probeConfigRef: get(row, "probe_config_ref"),
|
|
112
|
+
createdAt: get(row, "created_at"),
|
|
113
|
+
}));
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WriteValidationGate — T-SMS.C.1
|
|
3
|
+
*
|
|
4
|
+
* Core logic: All write paths MUST pass through this gate before persisting.
|
|
5
|
+
* Rejects payloads containing sensitive fields, missing source refs on fact
|
|
6
|
+
* claims, or failing schema/sensitivity scans. Returns structured machine-
|
|
7
|
+
* readable reason codes (DR-022).
|
|
8
|
+
*
|
|
9
|
+
* Dependencies:
|
|
10
|
+
* - `SourceRef` type from `../../shared/types/source-ref.js`
|
|
11
|
+
* - v7 entity types for shape awareness
|
|
12
|
+
*
|
|
13
|
+
* Boundary:
|
|
14
|
+
* - This gate is stateless; it inspects payloads but never writes.
|
|
15
|
+
* - Callers must handle rejection before any DB/artifact write.
|
|
16
|
+
* - Does NOT redact — redaction is the caller's responsibility after gate
|
|
17
|
+
* approval.
|
|
18
|
+
*
|
|
19
|
+
* Test coverage: tests/unit/storage/write-validation-gate.test.ts
|
|
20
|
+
*/
|
|
21
|
+
export type WriteValidationFailureReason = "write_validation_failed:credential_detected" | "write_validation_failed:token_detected" | "write_validation_failed:raw_private_content_detected" | "write_validation_failed:raw_prompt_detected" | "write_validation_failed:source_refs_missing" | "write_validation_failed:source_refs_empty" | "write_validation_failed:sensitivity_scan_failed" | "write_validation_failed:schema_validation_failed" | "write_validation_failed:encryption_key_detected" | "write_validation_failed:session_token_detected";
|
|
22
|
+
export interface WriteValidationResult {
|
|
23
|
+
ok: boolean;
|
|
24
|
+
reason?: WriteValidationFailureReason;
|
|
25
|
+
details?: string;
|
|
26
|
+
}
|
|
27
|
+
export interface WriteValidationGateOptions {
|
|
28
|
+
/** If true, fact-claim-like payloads require sourceRefs. Default true. */
|
|
29
|
+
requireSourceRefs?: boolean;
|
|
30
|
+
/** If true, run deep sensitivity scan. Default true. */
|
|
31
|
+
runSensitivityScan?: boolean;
|
|
32
|
+
/** If true, scan for sensitive field keys. Default true. */
|
|
33
|
+
scanFieldKeys?: boolean;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Validate a write payload before persistence.
|
|
37
|
+
*
|
|
38
|
+
* @param payload — the object about to be written
|
|
39
|
+
* @param options — gate behavior tuning
|
|
40
|
+
* @returns WriteValidationResult: ok=true to proceed, ok=false with reason
|
|
41
|
+
*/
|
|
42
|
+
export declare function validateWritePayload(payload: unknown, options?: WriteValidationGateOptions): WriteValidationResult;
|
|
43
|
+
/**
|
|
44
|
+
* Convenience: assert that payload passes gate, else throw.
|
|
45
|
+
*/
|
|
46
|
+
export declare function assertWritePayload(payload: unknown, options?: WriteValidationGateOptions): void;
|