@cairnkeep/cli 2.3.1 → 2.4.0
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/CHANGELOG.md +69 -0
- package/README.md +221 -6
- package/bin/cairn +43 -2
- package/claude/capability-contract/hooks/capability-command-finish.sh +81 -0
- package/claude/capability-contract/hooks/capability-command-start.sh +76 -0
- package/claude/hooks/compaction-capture.sh +34 -0
- package/claude/hooks/memory-capture.sh +47 -5
- package/claude/hooks/memory-wakeup.sh +55 -2
- package/docs/building-an-overlay.md +1 -1
- package/docs/design/meta-agent.md +439 -0
- package/docs/learning/lessons/L00-why-cairnkeep.md +1 -1
- package/docs/learning/lessons/L01-safe-trial.md +3 -3
- package/docs/learning/lessons/L02-installation.md +2 -2
- package/docs/learning/lessons/L03-first-project.md +1 -1
- package/docs/operating.md +531 -4
- package/docs/privacy-and-data-flow.md +417 -0
- package/docs/storage.md +252 -0
- package/examples/eval/README.md +42 -0
- package/examples/eval/adapter.json +12 -0
- package/examples/eval/bundled-fake.json +1 -0
- package/examples/eval/task-set.json +1 -0
- package/mcp-memory-server/dist/artifact-cli.js +184 -0
- package/mcp-memory-server/dist/artifact-schema.js +218 -0
- package/mcp-memory-server/dist/artifact-store.js +812 -0
- package/mcp-memory-server/dist/capability-adapter.js +286 -0
- package/mcp-memory-server/dist/capability-cli.js +361 -0
- package/mcp-memory-server/dist/capability-config.js +310 -0
- package/mcp-memory-server/dist/capability-harness.js +496 -0
- package/mcp-memory-server/dist/capability-registry.js +123 -0
- package/mcp-memory-server/dist/capability-schema.js +65 -0
- package/mcp-memory-server/dist/capability-store.js +431 -0
- package/mcp-memory-server/dist/compaction-normalize.js +267 -0
- package/mcp-memory-server/dist/eval-cli.js +523 -0
- package/mcp-memory-server/dist/eval-plan.js +346 -0
- package/mcp-memory-server/dist/eval-process.js +273 -0
- package/mcp-memory-server/dist/eval-report.js +609 -0
- package/mcp-memory-server/dist/eval-runner.js +827 -0
- package/mcp-memory-server/dist/eval-schema.js +368 -0
- package/mcp-memory-server/dist/eval-statistics.js +161 -0
- package/mcp-memory-server/dist/eval-workspace.js +313 -0
- package/mcp-memory-server/dist/failure-signature.js +141 -0
- package/mcp-memory-server/dist/index.js +600 -55
- package/mcp-memory-server/dist/node-cli.js +84 -0
- package/mcp-memory-server/dist/node-schema.js +170 -0
- package/mcp-memory-server/dist/node-store.js +455 -0
- package/mcp-memory-server/dist/note-cli.js +235 -0
- package/mcp-memory-server/dist/note-distiller.js +177 -0
- package/mcp-memory-server/dist/note-enrichment.js +93 -0
- package/mcp-memory-server/dist/note-schema.js +4 -0
- package/mcp-memory-server/dist/note-store.js +985 -0
- package/mcp-memory-server/dist/trajectory-cli.js +141 -0
- package/mcp-memory-server/dist/trajectory-normalize.js +448 -0
- package/mcp-memory-server/dist/trajectory-redaction.js +155 -0
- package/mcp-memory-server/dist/trajectory-schema.js +73 -0
- package/mcp-memory-server/dist/trajectory-store.js +339 -0
- package/mcp-memory-server/package.json +24 -2
- package/mcp-memory-server/scripts/fixtures/compaction-claude-code-2.1.219.json +9 -0
- package/mcp-memory-server/scripts/fixtures/compaction-claude-code-2.1.220.json +9 -0
- package/mcp-memory-server/scripts/fixtures/compaction-opencode-1.17.20-event.json +6 -0
- package/mcp-memory-server/scripts/fixtures/compaction-opencode-1.17.20-messages.json +119 -0
- package/mcp-memory-server/scripts/fixtures/compaction-unknown-version.json +17 -0
- package/opencode/capability-contract/plugins/capability-command.ts +319 -0
- package/opencode/command/graphify.md +25 -14
- package/opencode/plugins/memory-capture.ts +76 -10
- package/opencode/plugins/memory-wakeup.ts +58 -6
- package/package.json +8 -4
- package/pi/extensions/cairnkeep-trajectory.ts +55 -0
- package/schemas/artifact.schema.json +133 -0
- package/schemas/capability-callback.schema.json +68 -0
- package/schemas/capability-contract.schema.json +126 -0
- package/schemas/eval-adapter.schema.json +43 -0
- package/schemas/eval-protocol.schema.json +114 -0
- package/schemas/eval-report.schema.json +423 -0
- package/schemas/eval-task-set.schema.json +114 -0
- package/schemas/memory-node.schema.json +90 -0
- package/schemas/note.schema.json +112 -0
- package/schemas/trajectory-redaction.schema.json +25 -0
- package/schemas/trajectory.schema.json +82 -0
- package/scripts/bootstrap.sh +4 -0
- package/scripts/completion.sh +119 -5
- package/scripts/doctor.sh +284 -0
- package/scripts/fake-eval-adapter.mjs +120 -0
- package/scripts/fixtures/capability-harness-contracts.json +408 -0
- package/scripts/install-audit-timer.sh +3 -1
- package/scripts/lib/capability-opencode-plugin-harness.mjs +291 -0
- package/scripts/lib/trajectory-opencode-plugin-harness.mjs +25 -0
- package/scripts/lib/trajectory-pi-extension-harness.mjs +42 -0
- package/scripts/memory-wiki-audit.sh +49 -0
- package/scripts/smoke-bash32.sh +9 -0
- package/scripts/sync-claude-assets.sh +99 -6
- package/scripts/sync-opencode-graphify-assets.sh +24 -3
- package/scripts/sync-opencode-plugin-assets.sh +31 -5
- package/scripts/sync-opencode-security-assets.sh +24 -3
- package/scripts/sync-opencode-wiki-assets.sh +24 -3
- package/scripts/sync-pi-assets.sh +72 -0
- package/scripts/test-audit-timer.sh +98 -0
- package/scripts/test-bootstrap-untracked.sh +48 -0
- package/scripts/test-cli-dispatch.sh +41 -2
- package/scripts/test-compaction-hooks.sh +495 -0
- package/scripts/test-completion.sh +77 -0
- package/scripts/test-doctor.sh +348 -1
- package/scripts/test-eval-cli.sh +175 -0
- package/scripts/test-launcher-hooks.sh +602 -3
- package/scripts/test-notes-cli.sh +114 -0
- package/scripts/test-package-install.sh +251 -0
- package/scripts/test-phase16-node-lifecycle.sh +50 -0
- package/scripts/test-phase17-artifact-lifecycle.sh +370 -0
- package/scripts/test-phase18-capability-lifecycle.sh +504 -0
- package/scripts/test-phase18-harness-boundary.sh +1060 -0
- package/scripts/test-phase19-eval-lifecycle.sh +1015 -0
- package/scripts/test-trajectory-cli.sh +87 -0
- package/scripts/test-trajectory-hooks.sh +142 -0
- package/scripts/test-uninstall.sh +231 -5
- package/scripts/uninstall.sh +37 -5
- package/scripts/verify-dependency-security.mjs +121 -0
- package/scripts/verify-docs-parity.sh +527 -1
- package/scripts/verify-phase18-runtime-evidence.mjs +895 -0
- package/scripts/verify-phase19-runtime-evidence.mjs +947 -0
- package/templates/capabilities.json.template +7 -0
- package/templates/env.example.template +51 -0
- package/templates/memory-wiki-audit.service.template +1 -1
- package/templates/start-claude.sh.template +36 -0
- package/templates/start-opencode.sh.template +47 -5
- package/templates/start-pi.sh.template +30 -0
- package/templates/trajectory-redaction.json.template +4 -0
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { chmodSync, existsSync, mkdirSync } from "node:fs";
|
|
3
|
+
import { dirname, resolve } from "node:path";
|
|
4
|
+
import { AgentFS } from "agentfs-sdk";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { capabilityIdSchema, capabilitySourceSchema, } from "./capability-schema.js";
|
|
7
|
+
import { getTrajectoryLimits } from "./trajectory-schema.js";
|
|
8
|
+
export const CAPABILITY_CALLBACK_SCHEMA_VERSION = 1;
|
|
9
|
+
export const CAPABILITY_CALLBACK_RECORD_PREFIX = "capability-callback/v1/record/";
|
|
10
|
+
export const CAPABILITY_CALLBACK_PENDING_PREFIX = "capability-callback/v1/pending/";
|
|
11
|
+
export const CAPABILITY_CALLBACK_CORRELATION_PREFIX = "capability-callback/v1/correlation/";
|
|
12
|
+
export const CAPABILITY_CALLBACK_RECORD_MAX_COUNT = 10_000;
|
|
13
|
+
const META_KEY = "capability-callback/meta/schema-version";
|
|
14
|
+
const DAY_MS = 86_400_000;
|
|
15
|
+
export const CAPABILITY_CALLBACK_OUTCOMES = [
|
|
16
|
+
"success",
|
|
17
|
+
"error",
|
|
18
|
+
"timeout",
|
|
19
|
+
"disabled",
|
|
20
|
+
];
|
|
21
|
+
export const CAPABILITY_CALLBACK_ERROR_CODES = [
|
|
22
|
+
"callback-error",
|
|
23
|
+
"callback-timeout",
|
|
24
|
+
"result-error",
|
|
25
|
+
"result-timeout",
|
|
26
|
+
"capability-disabled",
|
|
27
|
+
];
|
|
28
|
+
export const capabilityCallbackOutcomeSchema = z.enum(CAPABILITY_CALLBACK_OUTCOMES);
|
|
29
|
+
export const capabilityCallbackErrorCodeSchema = z.enum(CAPABILITY_CALLBACK_ERROR_CODES);
|
|
30
|
+
const invocationIdSchema = z.string().regex(/^cap:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i);
|
|
31
|
+
const correlationIdSchema = z.string()
|
|
32
|
+
.min(1)
|
|
33
|
+
.max(256)
|
|
34
|
+
.regex(/^(?:cairn:)?[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/)
|
|
35
|
+
.refine((value) => value !== "unknown");
|
|
36
|
+
const operatingCapabilityIssuanceSchema = z.strictObject({
|
|
37
|
+
schema_version: z.literal(CAPABILITY_CALLBACK_SCHEMA_VERSION),
|
|
38
|
+
capability_id: capabilityIdSchema,
|
|
39
|
+
invocation_id: invocationIdSchema,
|
|
40
|
+
correlation_id: correlationIdSchema,
|
|
41
|
+
harness: z.enum(["claude-code", "opencode", "pi", "other"]),
|
|
42
|
+
source: z.enum(["mcp", "notes-cli", "audit-timer", "operating-command", "operating-workflow"]),
|
|
43
|
+
transport: z.enum(["stdio", "http", "local-process", "harness-command"]),
|
|
44
|
+
started_at: z.iso.datetime(),
|
|
45
|
+
state_source: capabilitySourceSchema,
|
|
46
|
+
configuration_digest: z.string().regex(/^[a-f0-9]{64}$/),
|
|
47
|
+
});
|
|
48
|
+
const operatingCapabilityCorrelationSchema = z.strictObject({
|
|
49
|
+
schema_version: z.literal(CAPABILITY_CALLBACK_SCHEMA_VERSION),
|
|
50
|
+
invocation_id: invocationIdSchema,
|
|
51
|
+
correlation_id: correlationIdSchema,
|
|
52
|
+
});
|
|
53
|
+
export const capabilityCallbackRecordSchema = z.strictObject({
|
|
54
|
+
schema_version: z.literal(CAPABILITY_CALLBACK_SCHEMA_VERSION),
|
|
55
|
+
capability_id: capabilityIdSchema,
|
|
56
|
+
invocation_id: invocationIdSchema,
|
|
57
|
+
correlation_id: correlationIdSchema,
|
|
58
|
+
harness: z.enum(["claude-code", "opencode", "pi", "other"]),
|
|
59
|
+
source: z.enum(["mcp", "notes-cli", "audit-timer", "operating-command", "operating-workflow"]),
|
|
60
|
+
transport: z.enum(["stdio", "http", "local-process", "harness-command"]),
|
|
61
|
+
started_at: z.iso.datetime(),
|
|
62
|
+
finished_at: z.iso.datetime(),
|
|
63
|
+
duration_ms: z.number().nonnegative().finite(),
|
|
64
|
+
outcome: capabilityCallbackOutcomeSchema,
|
|
65
|
+
error_code: capabilityCallbackErrorCodeSchema.optional(),
|
|
66
|
+
state_source: capabilitySourceSchema,
|
|
67
|
+
configuration_digest: z.string().regex(/^[a-f0-9]{64}$/),
|
|
68
|
+
}).superRefine((record, context) => {
|
|
69
|
+
if (record.outcome === "success" && record.error_code !== undefined) {
|
|
70
|
+
context.addIssue({ code: "custom", path: ["error_code"], message: "Success records cannot have an error code." });
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (record.outcome !== "success" && record.error_code === undefined) {
|
|
74
|
+
context.addIssue({ code: "custom", path: ["error_code"], message: "Non-success records require an error code." });
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (record.outcome === "disabled" && record.error_code !== "capability-disabled") {
|
|
78
|
+
context.addIssue({ code: "custom", path: ["error_code"], message: "Disabled records require the disabled code." });
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
function capabilityDbPath(projectRoot = process.cwd()) {
|
|
82
|
+
return resolve(projectRoot, ".agentfs", "trajectory.db");
|
|
83
|
+
}
|
|
84
|
+
async function openCapabilityStore(projectRoot, create, fault) {
|
|
85
|
+
if (fault === "open")
|
|
86
|
+
throw new Error("Injected capability store open fault.");
|
|
87
|
+
const dbPath = capabilityDbPath(projectRoot);
|
|
88
|
+
if (!create && !existsSync(dbPath))
|
|
89
|
+
return null;
|
|
90
|
+
if (create)
|
|
91
|
+
mkdirSync(dirname(dbPath), { recursive: true });
|
|
92
|
+
const agent = await AgentFS.open({ id: "trajectory", path: dbPath });
|
|
93
|
+
if (create) {
|
|
94
|
+
try {
|
|
95
|
+
chmodSync(dbPath, 0o600);
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
await agent.close();
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return agent;
|
|
103
|
+
}
|
|
104
|
+
function recordKey(record) {
|
|
105
|
+
const epoch = Date.parse(record.finished_at);
|
|
106
|
+
if (!Number.isFinite(epoch))
|
|
107
|
+
throw new Error("Capability callback finish time is invalid.");
|
|
108
|
+
return `${CAPABILITY_CALLBACK_RECORD_PREFIX}${String(epoch).padStart(16, "0")}/${record.invocation_id}`;
|
|
109
|
+
}
|
|
110
|
+
function pendingKey(issuance) {
|
|
111
|
+
return `${CAPABILITY_CALLBACK_PENDING_PREFIX}${issuance.invocation_id}`;
|
|
112
|
+
}
|
|
113
|
+
function correlationKey(issuance) {
|
|
114
|
+
const digest = createHash("sha256")
|
|
115
|
+
.update([
|
|
116
|
+
issuance.capability_id,
|
|
117
|
+
issuance.invocation_id,
|
|
118
|
+
issuance.correlation_id,
|
|
119
|
+
issuance.harness,
|
|
120
|
+
issuance.source,
|
|
121
|
+
issuance.transport,
|
|
122
|
+
].join("\0"), "utf8")
|
|
123
|
+
.digest("hex");
|
|
124
|
+
return `${CAPABILITY_CALLBACK_CORRELATION_PREFIX}${digest}`;
|
|
125
|
+
}
|
|
126
|
+
async function inImmediateTransaction(agent, operation) {
|
|
127
|
+
const transaction = agent.getDatabase().transaction(operation);
|
|
128
|
+
const immediate = transaction.immediate;
|
|
129
|
+
return immediate();
|
|
130
|
+
}
|
|
131
|
+
function sortedRecordRows(rows) {
|
|
132
|
+
return rows.map(({ key, value }) => ({ key, record: capabilityCallbackRecordSchema.parse(value) }))
|
|
133
|
+
.sort((left, right) => left.key.localeCompare(right.key));
|
|
134
|
+
}
|
|
135
|
+
function sortedPendingRows(rows) {
|
|
136
|
+
return rows.map(({ key, value }) => ({ key, issuance: operatingCapabilityIssuanceSchema.parse(value) }))
|
|
137
|
+
.sort((left, right) => {
|
|
138
|
+
const byStart = left.issuance.started_at.localeCompare(right.issuance.started_at);
|
|
139
|
+
return byStart === 0 ? left.key.localeCompare(right.key) : byStart;
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
function sameIssuance(left, right) {
|
|
143
|
+
return left.schema_version === right.schema_version
|
|
144
|
+
&& left.capability_id === right.capability_id
|
|
145
|
+
&& left.invocation_id === right.invocation_id
|
|
146
|
+
&& left.correlation_id === right.correlation_id
|
|
147
|
+
&& left.harness === right.harness
|
|
148
|
+
&& left.source === right.source
|
|
149
|
+
&& left.transport === right.transport
|
|
150
|
+
&& left.started_at === right.started_at
|
|
151
|
+
&& left.state_source === right.state_source
|
|
152
|
+
&& left.configuration_digest === right.configuration_digest;
|
|
153
|
+
}
|
|
154
|
+
function recordMatchesIssuance(record, issuance) {
|
|
155
|
+
return record.schema_version === issuance.schema_version
|
|
156
|
+
&& record.capability_id === issuance.capability_id
|
|
157
|
+
&& record.invocation_id === issuance.invocation_id
|
|
158
|
+
&& record.correlation_id === issuance.correlation_id
|
|
159
|
+
&& record.harness === issuance.harness
|
|
160
|
+
&& record.source === issuance.source
|
|
161
|
+
&& record.transport === issuance.transport
|
|
162
|
+
&& record.started_at === issuance.started_at
|
|
163
|
+
&& record.state_source === issuance.state_source
|
|
164
|
+
&& record.configuration_digest === issuance.configuration_digest;
|
|
165
|
+
}
|
|
166
|
+
async function assertCompatibleSchema(agent, allowMissing) {
|
|
167
|
+
const version = await agent.kv.get(META_KEY);
|
|
168
|
+
if (version === undefined && allowMissing)
|
|
169
|
+
return;
|
|
170
|
+
if (version !== CAPABILITY_CALLBACK_SCHEMA_VERSION) {
|
|
171
|
+
throw new Error("Capability callback store schema is incompatible; run `cairn doctor`.");
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
function recordLimit(value) {
|
|
175
|
+
const maxRecords = value ?? CAPABILITY_CALLBACK_RECORD_MAX_COUNT;
|
|
176
|
+
if (!Number.isSafeInteger(maxRecords) || maxRecords < 1 || maxRecords > CAPABILITY_CALLBACK_RECORD_MAX_COUNT) {
|
|
177
|
+
throw new Error("Capability callback record limit is invalid.");
|
|
178
|
+
}
|
|
179
|
+
return maxRecords;
|
|
180
|
+
}
|
|
181
|
+
async function appendParsedRecord(agent, parsed, maxRecords, cutoff, fault) {
|
|
182
|
+
const rows = sortedRecordRows(await agent.kv.list(CAPABILITY_CALLBACK_RECORD_PREFIX));
|
|
183
|
+
if (rows.some(({ record: existing }) => existing.invocation_id === parsed.invocation_id))
|
|
184
|
+
return;
|
|
185
|
+
const retained = [];
|
|
186
|
+
for (const row of rows) {
|
|
187
|
+
if (Date.parse(row.record.finished_at) < cutoff)
|
|
188
|
+
await agent.kv.delete(row.key);
|
|
189
|
+
else
|
|
190
|
+
retained.push(row);
|
|
191
|
+
}
|
|
192
|
+
if (fault === "write")
|
|
193
|
+
throw new Error("Injected capability store write fault.");
|
|
194
|
+
await agent.kv.set(META_KEY, CAPABILITY_CALLBACK_SCHEMA_VERSION);
|
|
195
|
+
if (Date.parse(parsed.finished_at) >= cutoff) {
|
|
196
|
+
const key = recordKey(parsed);
|
|
197
|
+
await agent.kv.set(key, parsed);
|
|
198
|
+
retained.push({ key, record: parsed });
|
|
199
|
+
}
|
|
200
|
+
retained.sort((left, right) => left.key.localeCompare(right.key));
|
|
201
|
+
while (retained.length > maxRecords) {
|
|
202
|
+
const oldest = retained.shift();
|
|
203
|
+
if (oldest)
|
|
204
|
+
await agent.kv.delete(oldest.key);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
export async function appendCapabilityRecord(projectRoot, record, options = {}) {
|
|
208
|
+
const parsed = capabilityCallbackRecordSchema.parse(record);
|
|
209
|
+
const maxRecords = recordLimit(options.testMaxRecords);
|
|
210
|
+
const agent = await openCapabilityStore(projectRoot, true, options.testStoreFault);
|
|
211
|
+
if (!agent)
|
|
212
|
+
throw new Error("Unable to open the local capability callback store.");
|
|
213
|
+
try {
|
|
214
|
+
if (options.testStoreFault === "lock")
|
|
215
|
+
throw new Error("Injected capability store lock fault.");
|
|
216
|
+
await inImmediateTransaction(agent, async () => {
|
|
217
|
+
await assertCompatibleSchema(agent, true);
|
|
218
|
+
if (options.testStoreFault === "schema")
|
|
219
|
+
throw new Error("Injected capability store schema fault.");
|
|
220
|
+
const cutoff = (options.nowMs ?? Date.now()) - getTrajectoryLimits().retentionDays * DAY_MS;
|
|
221
|
+
await appendParsedRecord(agent, parsed, maxRecords, cutoff, options.testStoreFault);
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
finally {
|
|
225
|
+
await agent.close();
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
export async function issueOperatingCapability(projectRoot, rawIssuance, options = {}) {
|
|
229
|
+
try {
|
|
230
|
+
const issuance = operatingCapabilityIssuanceSchema.parse(rawIssuance);
|
|
231
|
+
const agent = await openCapabilityStore(projectRoot, true, options.testStoreFault);
|
|
232
|
+
if (!agent)
|
|
233
|
+
return false;
|
|
234
|
+
try {
|
|
235
|
+
if (options.testStoreFault === "lock")
|
|
236
|
+
throw new Error("Injected capability store lock fault.");
|
|
237
|
+
return await inImmediateTransaction(agent, async () => {
|
|
238
|
+
await assertCompatibleSchema(agent, true);
|
|
239
|
+
if (options.testStoreFault === "schema")
|
|
240
|
+
throw new Error("Injected capability store schema fault.");
|
|
241
|
+
const cutoff = (options.nowMs ?? Date.now()) - getTrajectoryLimits().retentionDays * DAY_MS;
|
|
242
|
+
const rows = sortedPendingRows(await agent.kv.list(CAPABILITY_CALLBACK_PENDING_PREFIX));
|
|
243
|
+
const finalRows = sortedRecordRows(await agent.kv.list(CAPABILITY_CALLBACK_RECORD_PREFIX));
|
|
244
|
+
const retained = [];
|
|
245
|
+
for (const row of rows) {
|
|
246
|
+
if (Date.parse(row.issuance.started_at) < cutoff) {
|
|
247
|
+
await agent.kv.delete(row.key);
|
|
248
|
+
await agent.kv.delete(correlationKey(row.issuance));
|
|
249
|
+
}
|
|
250
|
+
else
|
|
251
|
+
retained.push(row);
|
|
252
|
+
}
|
|
253
|
+
if (retained.some(({ issuance: existing }) => existing.invocation_id === issuance.invocation_id)) {
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
if (finalRows.some(({ record }) => record.invocation_id === issuance.invocation_id))
|
|
257
|
+
return false;
|
|
258
|
+
const correlation = await agent.kv.get(correlationKey(issuance));
|
|
259
|
+
if (correlation !== undefined)
|
|
260
|
+
return false;
|
|
261
|
+
if (options.testStoreFault === "write")
|
|
262
|
+
throw new Error("Injected capability store write fault.");
|
|
263
|
+
await agent.kv.set(META_KEY, CAPABILITY_CALLBACK_SCHEMA_VERSION);
|
|
264
|
+
if (Date.parse(issuance.started_at) < cutoff)
|
|
265
|
+
return false;
|
|
266
|
+
const key = pendingKey(issuance);
|
|
267
|
+
await agent.kv.set(correlationKey(issuance), {
|
|
268
|
+
schema_version: CAPABILITY_CALLBACK_SCHEMA_VERSION,
|
|
269
|
+
invocation_id: issuance.invocation_id,
|
|
270
|
+
correlation_id: issuance.correlation_id,
|
|
271
|
+
});
|
|
272
|
+
await agent.kv.set(key, issuance);
|
|
273
|
+
retained.push({ key, issuance });
|
|
274
|
+
retained.sort((left, right) => {
|
|
275
|
+
const byStart = left.issuance.started_at.localeCompare(right.issuance.started_at);
|
|
276
|
+
return byStart === 0 ? left.key.localeCompare(right.key) : byStart;
|
|
277
|
+
});
|
|
278
|
+
while (retained.length > CAPABILITY_CALLBACK_RECORD_MAX_COUNT) {
|
|
279
|
+
const oldest = retained.shift();
|
|
280
|
+
if (oldest) {
|
|
281
|
+
await agent.kv.delete(oldest.key);
|
|
282
|
+
await agent.kv.delete(correlationKey(oldest.issuance));
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return retained.some(({ issuance: retainedIssuance }) => retainedIssuance.invocation_id === issuance.invocation_id);
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
finally {
|
|
289
|
+
await agent.close();
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
catch {
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
export async function settleOperatingCapability(projectRoot, rawIssuance, rawRecord, options = {}) {
|
|
297
|
+
try {
|
|
298
|
+
const issuance = operatingCapabilityIssuanceSchema.parse(rawIssuance);
|
|
299
|
+
const record = rawRecord === undefined ? undefined : capabilityCallbackRecordSchema.parse(rawRecord);
|
|
300
|
+
if (record !== undefined && !recordMatchesIssuance(record, issuance))
|
|
301
|
+
return false;
|
|
302
|
+
const agent = await openCapabilityStore(projectRoot, false, options.testStoreFault);
|
|
303
|
+
if (!agent)
|
|
304
|
+
return false;
|
|
305
|
+
try {
|
|
306
|
+
if (options.testStoreFault === "lock")
|
|
307
|
+
throw new Error("Injected capability store lock fault.");
|
|
308
|
+
return await inImmediateTransaction(agent, async () => {
|
|
309
|
+
await assertCompatibleSchema(agent, false);
|
|
310
|
+
if (options.testStoreFault === "schema")
|
|
311
|
+
throw new Error("Injected capability store schema fault.");
|
|
312
|
+
const key = pendingKey(issuance);
|
|
313
|
+
const stored = operatingCapabilityIssuanceSchema.safeParse(await agent.kv.get(key));
|
|
314
|
+
if (!stored.success || !sameIssuance(stored.data, issuance))
|
|
315
|
+
return false;
|
|
316
|
+
const storedCorrelation = operatingCapabilityCorrelationSchema.safeParse(await agent.kv.get(correlationKey(issuance)));
|
|
317
|
+
if (!storedCorrelation.success
|
|
318
|
+
|| storedCorrelation.data.invocation_id !== issuance.invocation_id
|
|
319
|
+
|| storedCorrelation.data.correlation_id !== issuance.correlation_id)
|
|
320
|
+
return false;
|
|
321
|
+
if (options.testStoreFault === "write")
|
|
322
|
+
throw new Error("Injected capability store write fault.");
|
|
323
|
+
const cutoff = (options.nowMs ?? Date.now()) - getTrajectoryLimits().retentionDays * DAY_MS;
|
|
324
|
+
await agent.kv.delete(key);
|
|
325
|
+
await agent.kv.delete(correlationKey(issuance));
|
|
326
|
+
if (Date.parse(stored.data.started_at) < cutoff)
|
|
327
|
+
return false;
|
|
328
|
+
if (record !== undefined) {
|
|
329
|
+
await appendParsedRecord(agent, record, CAPABILITY_CALLBACK_RECORD_MAX_COUNT, cutoff);
|
|
330
|
+
}
|
|
331
|
+
return true;
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
finally {
|
|
335
|
+
await agent.close();
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
catch {
|
|
339
|
+
return false;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
export async function isOperatingCapabilitySettled(projectRoot, rawIssuance) {
|
|
343
|
+
try {
|
|
344
|
+
const issuance = operatingCapabilityIssuanceSchema.parse(rawIssuance);
|
|
345
|
+
const agent = await openCapabilityStore(projectRoot, false);
|
|
346
|
+
if (!agent)
|
|
347
|
+
return false;
|
|
348
|
+
try {
|
|
349
|
+
await assertCompatibleSchema(agent, false);
|
|
350
|
+
const rows = sortedRecordRows(await agent.kv.list(CAPABILITY_CALLBACK_RECORD_PREFIX));
|
|
351
|
+
return rows.some(({ record }) => recordMatchesIssuance(record, issuance));
|
|
352
|
+
}
|
|
353
|
+
finally {
|
|
354
|
+
await agent.close();
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
catch {
|
|
358
|
+
return false;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
export async function listCapabilityRecords(projectRoot = process.cwd()) {
|
|
362
|
+
const agent = await openCapabilityStore(projectRoot, false);
|
|
363
|
+
if (!agent)
|
|
364
|
+
return { schema_version: CAPABILITY_CALLBACK_SCHEMA_VERSION, records: [] };
|
|
365
|
+
try {
|
|
366
|
+
const rows = await agent.kv.list(CAPABILITY_CALLBACK_RECORD_PREFIX);
|
|
367
|
+
if (rows.length === 0)
|
|
368
|
+
return { schema_version: CAPABILITY_CALLBACK_SCHEMA_VERSION, records: [] };
|
|
369
|
+
await assertCompatibleSchema(agent, false);
|
|
370
|
+
return {
|
|
371
|
+
schema_version: CAPABILITY_CALLBACK_SCHEMA_VERSION,
|
|
372
|
+
records: sortedRecordRows(rows).reverse().map(({ record }) => record),
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
finally {
|
|
376
|
+
await agent.close();
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
export function getCapabilityDbPath(projectRoot = process.cwd()) {
|
|
380
|
+
return capabilityDbPath(projectRoot);
|
|
381
|
+
}
|
|
382
|
+
export async function doctorCapabilityRecords(projectRoot = process.cwd()) {
|
|
383
|
+
const agent = await openCapabilityStore(projectRoot, false);
|
|
384
|
+
if (!agent) {
|
|
385
|
+
return {
|
|
386
|
+
schema_version: CAPABILITY_CALLBACK_SCHEMA_VERSION,
|
|
387
|
+
exists: false,
|
|
388
|
+
ok: true,
|
|
389
|
+
integrity: "not_present",
|
|
390
|
+
valid_records: 0,
|
|
391
|
+
issues: [],
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
try {
|
|
395
|
+
const integrityRows = await agent.getDatabase().pragma("integrity_check", {});
|
|
396
|
+
const integrityOk = Array.isArray(integrityRows)
|
|
397
|
+
&& integrityRows.length > 0
|
|
398
|
+
&& integrityRows.every((row) => Object.values(row).includes("ok"));
|
|
399
|
+
const storedVersion = await agent.kv.get(META_KEY);
|
|
400
|
+
const rows = await agent.kv.list(CAPABILITY_CALLBACK_RECORD_PREFIX);
|
|
401
|
+
let validRecords = 0;
|
|
402
|
+
let invalidRecords = 0;
|
|
403
|
+
for (const { value } of rows) {
|
|
404
|
+
if (capabilityCallbackRecordSchema.safeParse(value).success)
|
|
405
|
+
validRecords += 1;
|
|
406
|
+
else
|
|
407
|
+
invalidRecords += 1;
|
|
408
|
+
}
|
|
409
|
+
const issues = [];
|
|
410
|
+
if (!integrityOk)
|
|
411
|
+
issues.push("sqlite-integrity-failed");
|
|
412
|
+
if (storedVersion === undefined && rows.length > 0)
|
|
413
|
+
issues.push("schema-missing");
|
|
414
|
+
else if (storedVersion !== undefined && storedVersion !== CAPABILITY_CALLBACK_SCHEMA_VERSION)
|
|
415
|
+
issues.push("schema-unsupported");
|
|
416
|
+
if (invalidRecords > 0)
|
|
417
|
+
issues.push("invalid-record");
|
|
418
|
+
return {
|
|
419
|
+
schema_version: CAPABILITY_CALLBACK_SCHEMA_VERSION,
|
|
420
|
+
exists: true,
|
|
421
|
+
ok: issues.length === 0,
|
|
422
|
+
integrity: integrityOk ? "ok" : "failed",
|
|
423
|
+
...(storedVersion === undefined ? {} : { stored_schema_version: storedVersion }),
|
|
424
|
+
valid_records: validRecords,
|
|
425
|
+
issues,
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
finally {
|
|
429
|
+
await agent.close();
|
|
430
|
+
}
|
|
431
|
+
}
|