@wirecat/cli-core 0.372.0 → 0.374.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.
Files changed (38) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/dist/files/bounded-utf8.d.ts +4 -0
  3. package/dist/files/bounded-utf8.js +32 -0
  4. package/dist/files/index.d.ts +1 -0
  5. package/dist/files/index.js +1 -0
  6. package/dist/store/index.d.ts +4 -0
  7. package/dist/store/index.js +3 -0
  8. package/dist/store/project-transfer/manifest.d.ts +1 -0
  9. package/dist/store/project-transfer/manifest.js +6 -2
  10. package/dist/store/project-transfer/plan.d.ts +17 -0
  11. package/dist/store/project-transfer/plan.js +18 -7
  12. package/dist/store/project-transfer/preview.d.ts +11 -0
  13. package/dist/store/project-transfer/preview.js +29 -0
  14. package/dist/store/project-transfer/request.d.ts +76 -0
  15. package/dist/store/project-transfer/request.js +22 -0
  16. package/dist/store/project-transfer/snapshot-model.d.ts +117 -0
  17. package/dist/store/project-transfer/snapshot-model.js +99 -0
  18. package/dist/store/project-transfer/snapshot-plan.d.ts +12 -0
  19. package/dist/store/project-transfer/snapshot-plan.js +4 -0
  20. package/dist/store/project-transfer/snapshot.d.ts +17 -0
  21. package/dist/store/project-transfer/snapshot.js +29 -0
  22. package/dist/store/project-transfer/store.d.ts +25 -0
  23. package/dist/store/project-transfer/store.js +1 -0
  24. package/dist/store/sqlite/project-transfer/budget.d.ts +9 -0
  25. package/dist/store/sqlite/project-transfer/budget.js +27 -0
  26. package/dist/store/sqlite/project-transfer/preview.d.ts +12 -0
  27. package/dist/store/sqlite/project-transfer/preview.js +74 -0
  28. package/dist/store/sqlite/project-transfer/records.d.ts +5 -0
  29. package/dist/store/sqlite/project-transfer/records.js +53 -0
  30. package/dist/store/sqlite/project-transfer/snapshot.d.ts +78 -0
  31. package/dist/store/sqlite/project-transfer/snapshot.js +183 -0
  32. package/dist/store/sqlite/project-transfer/sources.d.ts +18 -0
  33. package/dist/store/sqlite/project-transfer/sources.js +148 -0
  34. package/dist/store/sqlite/project-transfer/store.d.ts +3 -0
  35. package/dist/store/sqlite/project-transfer/store.js +35 -0
  36. package/dist/store/store.d.ts +2 -0
  37. package/dist/store/store.js +5 -0
  38. package/package.json +1 -1
@@ -0,0 +1,29 @@
1
+ import { CliError } from "@wirecat/cli-base";
2
+ import { openStoreContext } from "../sqlite/open-store.js";
3
+ import { SnapshotBudget } from "../sqlite/project-transfer/budget.js";
4
+ import { projectAnnotationSnapshot } from "../sqlite/project-transfer/snapshot.js";
5
+ import { projectAnnotationSnapshotFingerprint } from "./snapshot-model.js";
6
+ export { decodeProjectAnnotationSnapshot, projectAnnotationSnapshotFingerprint, projectAnnotationSnapshotSchema, } from "./snapshot-model.js";
7
+ /** No source maintenance, source fetching or output-file publication belongs to a snapshot read. */
8
+ export const snapshotProjectAnnotations = async (options) => {
9
+ if (!options || typeof options !== "object" || Array.isArray(options))
10
+ throw new CliError("validation_error", "snapshot options are required");
11
+ for (const field of Object.keys(options))
12
+ if (!["source", "project", "env", "maxReadBytes"].includes(field))
13
+ throw new CliError("validation_error", `unknown snapshot field ${field}`);
14
+ if (typeof options.source !== "string" || !options.source.trim() || options.source.length > 4096)
15
+ throw new CliError("validation_error", "snapshot source is an explicit existing store path");
16
+ if (typeof options.project !== "string" ||
17
+ !/^project:[1-9]\d*$/.test(options.project) ||
18
+ !Number.isSafeInteger(Number(options.project.slice(8))))
19
+ throw new CliError("validation_error", "snapshot project is a project:<safe integer id> reference");
20
+ const budget = new SnapshotBudget(options.maxReadBytes ?? 4 * 1024 * 1024);
21
+ const context = await openStoreContext({ path: options.source, env: options.env, readOnly: true });
22
+ try {
23
+ const result = projectAnnotationSnapshot(context.database, Number(options.project.slice(8)), budget);
24
+ return { ...result, fingerprint: projectAnnotationSnapshotFingerprint(JSON.stringify(result.snapshot)) };
25
+ }
26
+ finally {
27
+ context.database.close();
28
+ }
29
+ };
@@ -0,0 +1,25 @@
1
+ import type { ProjectAnnotationPreview } from "./snapshot-plan.js";
2
+ export interface ProjectAnnotationTargetRequest {
3
+ snapshot: string;
4
+ project: string;
5
+ bindings: Record<string, string>;
6
+ maxReadBytes?: number;
7
+ }
8
+ export interface ProjectAnnotationRestoreRequest extends ProjectAnnotationTargetRequest {
9
+ fingerprint: string;
10
+ }
11
+ export interface ProjectAnnotationRestoreReceipt {
12
+ project: string;
13
+ fingerprint: string;
14
+ mappings: Record<string, string>;
15
+ counts: {
16
+ aliases: number;
17
+ facts: number;
18
+ links: number;
19
+ };
20
+ }
21
+ /** Hosts authorize restore before calling; historical accepted state is not a new agent decision. */
22
+ export interface ProjectAnnotationStore {
23
+ preview(request: ProjectAnnotationTargetRequest): Promise<ProjectAnnotationPreview>;
24
+ restore(request: ProjectAnnotationRestoreRequest): Promise<ProjectAnnotationRestoreReceipt>;
25
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,9 @@
1
+ import type { CacheDatabase, SqlValue } from "../../driver.js";
2
+ export declare class SnapshotBudget {
3
+ readonly maxBytes: number;
4
+ records: number;
5
+ bytes: number;
6
+ constructor(maxBytes: number);
7
+ reserve(records: number, bytes: number): void;
8
+ rows(database: CacheDatabase, from: string, fields: string[], parameters: SqlValue[]): void;
9
+ }
@@ -0,0 +1,27 @@
1
+ import { CliError } from "@wirecat/cli-base";
2
+ export class SnapshotBudget {
3
+ maxBytes;
4
+ records = 0;
5
+ bytes = 0;
6
+ constructor(maxBytes) {
7
+ this.maxBytes = maxBytes;
8
+ if (!Number.isSafeInteger(maxBytes) || maxBytes < 1 || maxBytes > 4 * 1024 * 1024)
9
+ throw new CliError("validation_error", "snapshot maxReadBytes takes 1–4194304 bytes");
10
+ }
11
+ reserve(records, bytes) {
12
+ if (!Number.isSafeInteger(records) ||
13
+ records < 0 ||
14
+ !Number.isSafeInteger(bytes) ||
15
+ bytes < 0 ||
16
+ this.records + records > 1000 ||
17
+ this.bytes + bytes > this.maxBytes)
18
+ throw new CliError("validation_error", "project annotation snapshot exceeds its record or input byte budget");
19
+ this.records += records;
20
+ this.bytes += bytes;
21
+ }
22
+ rows(database, from, fields, parameters) {
23
+ const bytes = fields.map((field) => `coalesce(length(CAST(${field} AS BLOB)),0)`).join("+");
24
+ const sized = database.prepare(`SELECT ${bytes} AS bytes ${from} LIMIT 1001`).all(...parameters);
25
+ this.reserve(sized.length, sized.reduce((sum, row) => sum + Number(row.bytes) + 128, 0));
26
+ }
27
+ }
@@ -0,0 +1,12 @@
1
+ import type { CacheDatabase } from "../../driver.js";
2
+ import { type ProjectAnnotationSnapshot } from "../../project-transfer/snapshot-model.js";
3
+ import type { SnapshotBudget } from "./budget.js";
4
+ export declare const previewAnnotationSnapshot: (database: CacheDatabase, projectId: number, manifest: ProjectAnnotationSnapshot, bindings: Record<string, string>, budget: SnapshotBudget) => {
5
+ plan: import("../../index.js").ProjectAnnotationsPlan;
6
+ descriptor_conflicts: ("key" | "name")[];
7
+ namespace_conflicts: string[];
8
+ ready: boolean;
9
+ fingerprint: string;
10
+ read_bytes: number;
11
+ records: number;
12
+ };
@@ -0,0 +1,74 @@
1
+ import { createHash } from "node:crypto";
2
+ import { normalizeVocabularyName } from "../../../domain/vocabulary.js";
3
+ import { projectAnnotationSnapshotFingerprint, } from "../../project-transfer/snapshot-model.js";
4
+ import { planProjectAnnotationSnapshot } from "../../project-transfer/snapshot-plan.js";
5
+ import { thingOf } from "../things.js";
6
+ import { read } from "../transaction.js";
7
+ import { projectAnnotationSnapshot } from "./snapshot.js";
8
+ import { ProjectSnapshotSources } from "./sources.js";
9
+ export const previewAnnotationSnapshot = (database, projectId, manifest, bindings, budget) => read(database, () => {
10
+ const current = projectAnnotationSnapshot(database, projectId, budget).snapshot;
11
+ const sources = new ProjectSnapshotSources(database, budget);
12
+ for (const ref of new Set(Object.values(bindings))) {
13
+ if (ref === current.project.ref)
14
+ continue;
15
+ const thing = thingOf(database, ref);
16
+ if (thing)
17
+ sources.ref(thing.type, thing.id);
18
+ }
19
+ const external = sources.external(new Set([current.project.ref]));
20
+ const projectFields = ["id", "key", "name", "account_id", "deleted_at"];
21
+ const aliasFields = ["aliasable_id", "name", "account_id"];
22
+ const projectFrom = "FROM projects ORDER BY id";
23
+ const aliasFrom = "FROM aliases WHERE aliasable_type='project' ORDER BY id";
24
+ budget.rows(database, projectFrom, projectFields, []);
25
+ budget.rows(database, aliasFrom, aliasFields, []);
26
+ const projects = database.prepare(`SELECT ${projectFields.join(",")} ${projectFrom} LIMIT 1001`).all();
27
+ const aliases = database.prepare(`SELECT ${aliasFields.join(",")} ${aliasFrom} LIMIT 1001`).all();
28
+ const names = new Set([manifest.project.key, manifest.project.name, ...manifest.aliases.map((a) => a.name)].map(normalizeVocabularyName));
29
+ const namespace = new Set();
30
+ for (const project of projects) {
31
+ if (project.id === projectId)
32
+ continue;
33
+ const keyMatches = names.has(normalizeVocabularyName(String(project.key)));
34
+ const nameMatches = project.account_id === null &&
35
+ project.deleted_at === null &&
36
+ names.has(normalizeVocabularyName(String(project.name)));
37
+ if (keyMatches || nameMatches)
38
+ namespace.add(`project:${project.id}`);
39
+ }
40
+ for (const alias of aliases) {
41
+ if (alias.aliasable_id === projectId || alias.account_id !== null)
42
+ continue;
43
+ const project = projects.find((p) => p.id === alias.aliasable_id && p.account_id === null && p.deleted_at === null);
44
+ if (project && names.has(normalizeVocabularyName(String(alias.name))))
45
+ namespace.add(`project:${project.id}`);
46
+ }
47
+ const descriptor_conflicts = ["key", "name"].filter((field) => current.project[field] !== manifest.project[field]);
48
+ const target = {
49
+ project: current.project.ref,
50
+ annotation_count: current.aliases.length + current.facts.length + current.links.length,
51
+ revision_sha256: createHash("sha256")
52
+ .update(JSON.stringify({
53
+ snapshot: projectAnnotationSnapshotFingerprint(JSON.stringify(current)),
54
+ projects,
55
+ aliases,
56
+ external: external.toSorted((a, b) => (a.ref < b.ref ? -1 : a.ref > b.ref ? 1 : 0)),
57
+ }))
58
+ .digest("hex"),
59
+ external,
60
+ };
61
+ const plan = planProjectAnnotationSnapshot(JSON.stringify(manifest), target, bindings);
62
+ const namespace_conflicts = [...namespace].sort();
63
+ return {
64
+ plan,
65
+ descriptor_conflicts,
66
+ namespace_conflicts,
67
+ ready: plan.ready && descriptor_conflicts.length === 0 && namespace_conflicts.length === 0,
68
+ fingerprint: createHash("sha256")
69
+ .update(JSON.stringify({ plan: plan.fingerprint, descriptor_conflicts, namespace_conflicts }))
70
+ .digest("hex"),
71
+ read_bytes: budget.bytes,
72
+ records: budget.records,
73
+ };
74
+ });
@@ -0,0 +1,5 @@
1
+ import type { CacheDatabase } from "../../driver.js";
2
+ import type { ProjectAnnotationSnapshot } from "../../project-transfer/snapshot-model.js";
3
+ export declare const restoreAnnotationRecords: (database: CacheDatabase, projectId: number, snapshot: ProjectAnnotationSnapshot, bindings: Record<string, string>) => {
4
+ [x: string]: string;
5
+ };
@@ -0,0 +1,53 @@
1
+ import { CliError } from "@wirecat/cli-base";
2
+ import { fold } from "../../normalize.js";
3
+ import { thingOf } from "../things.js";
4
+ export const restoreAnnotationRecords = (database, projectId, snapshot, bindings) => {
5
+ const mappings = { ...bindings };
6
+ const resolve = (ref) => {
7
+ if (ref === null)
8
+ return null;
9
+ const mapped = mappings[ref], thing = mapped === undefined ? undefined : thingOf(database, mapped);
10
+ if (!thing)
11
+ throw new CliError("validation_error", "project annotation binding became unavailable");
12
+ return thing;
13
+ };
14
+ const organization = resolve(snapshot.project.organization), owner = resolve(snapshot.project.owner);
15
+ database
16
+ .prepare("UPDATE projects SET description=?,type=?,scope=?,organization_id=?,owner_type=?,owner_id=?,status=?,created_at=?,updated_at=? WHERE id=?")
17
+ .run(snapshot.project.description, snapshot.project.type, snapshot.project.scope, organization?.id ?? null, owner?.type ?? null, owner?.id ?? null, snapshot.project.status, snapshot.project.created_at, snapshot.project.updated_at, projectId);
18
+ for (const alias of snapshot.aliases)
19
+ database
20
+ .prepare("INSERT INTO aliases(aliasable_type,aliasable_id,account_id,name,name_folded,display,source,created_at,updated_at) VALUES('project',?,NULL,?,?,?,?,?,?)")
21
+ .run(projectId, alias.name, fold(alias.name), Number(alias.display), alias.source, alias.created_at, alias.updated_at);
22
+ for (const fact of snapshot.facts) {
23
+ const author = resolve(fact.author), confirmed = resolve(fact.confirmed_by);
24
+ if (!author)
25
+ throw new CliError("validation_error", "project annotation author is required");
26
+ const row = database
27
+ .prepare("INSERT INTO facts(type,statement,status,project_id,subject_type,subject_id,occurred_at,supersedes_id,author_type,author_id,model,confirmed_by_type,confirmed_by_id,scope,source,created_at,updated_at) VALUES(?,?,?,?,NULL,NULL,?,NULL,?,?,?,?,?,?,?,?,?) RETURNING id")
28
+ .get(fact.type, fact.statement, fact.status, projectId, fact.occurred_at, author.type, author.id, fact.model, confirmed?.type ?? null, confirmed?.id ?? null, fact.scope, fact.source, fact.created_at, fact.updated_at);
29
+ if (!row)
30
+ throw new CliError("configuration_error", "project annotation fact insertion failed");
31
+ mappings[fact.ref] = `fact:${row.id}`;
32
+ }
33
+ for (const fact of snapshot.facts) {
34
+ const subject = resolve(fact.subject), previous = resolve(fact.supersedes), target = resolve(fact.ref);
35
+ if (!target)
36
+ throw new CliError("configuration_error", "project annotation fact mapping is missing");
37
+ database
38
+ .prepare("UPDATE facts SET subject_type=?,subject_id=?,supersedes_id=? WHERE id=?")
39
+ .run(subject?.type ?? null, subject?.id ?? null, previous?.id ?? null, target.id);
40
+ }
41
+ for (const link of snapshot.links) {
42
+ const from = resolve(link.from), to = resolve(link.to);
43
+ if (!from)
44
+ throw new CliError("validation_error", "project annotation link source is required");
45
+ const row = database
46
+ .prepare("INSERT INTO links(from_type,from_id,to_type,to_id,kind,anchor,source,target_text,target_folded,role,evidence,metadata,confirmed,created_at,author,updated_at) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) RETURNING id")
47
+ .get(from.type, from.id, to?.type ?? null, to?.id ?? null, link.kind, link.anchor, link.origin, link.target_text, link.target_text === null ? null : fold(link.target_text), link.role, link.evidence, link.provenance === null ? null : JSON.stringify({ provenance: link.provenance }), Number(link.confirmed), link.created_at, link.author, link.updated_at);
48
+ if (!row)
49
+ throw new CliError("configuration_error", "project annotation link insertion failed");
50
+ mappings[link.ref] = `link:${row.id}`;
51
+ }
52
+ return mappings;
53
+ };
@@ -0,0 +1,78 @@
1
+ import type { CacheDatabase } from "../../driver.js";
2
+ import type { SnapshotBudget } from "./budget.js";
3
+ export declare const projectAnnotationSnapshot: (database: CacheDatabase, projectId: number, budget: SnapshotBudget) => {
4
+ snapshot: {
5
+ format: "wirecat.project-annotations";
6
+ facts: {
7
+ ref: string;
8
+ type: "decision" | "fact" | "promise" | "question" | "statement";
9
+ statement: string;
10
+ status: "accepted" | "proposed" | "reversed" | "superseded";
11
+ subject: string | null;
12
+ occurred_at: number | null;
13
+ supersedes: string | null;
14
+ author: string;
15
+ model: string | null;
16
+ confirmed_by: string | null;
17
+ scope: "personal" | "work" | null;
18
+ source: string;
19
+ created_at: number;
20
+ updated_at: number;
21
+ }[];
22
+ links: {
23
+ ref: string;
24
+ from: string;
25
+ to: string | null;
26
+ kind: "about" | "answered-by" | "assigned-to" | "blocked-by" | "created-from" | "duplicate-of" | "evidence" | "follow-up-of" | "labelled" | "links-to" | "member-of" | "related-to";
27
+ origin: "file" | "message" | "owner" | "suggested";
28
+ anchor: string | null;
29
+ target_text: string | null;
30
+ role: string | null;
31
+ evidence: string | null;
32
+ confirmed: boolean;
33
+ provenance: string | null;
34
+ created_at: number;
35
+ updated_at: number;
36
+ author: string | null;
37
+ }[];
38
+ external: {
39
+ ref: string;
40
+ content_sha256: string | null;
41
+ }[];
42
+ version: 2;
43
+ coverage: {
44
+ aliases: "complete";
45
+ facts: "complete";
46
+ links: "complete";
47
+ memories: "omitted";
48
+ tasks: "omitted";
49
+ labels: "omitted";
50
+ findings: "omitted";
51
+ meeting_memberships: "omitted";
52
+ notes: "omitted";
53
+ };
54
+ project: {
55
+ ref: string;
56
+ key: string;
57
+ name: string;
58
+ type: "client" | "oss" | "other" | "personal" | "work";
59
+ scope: "personal" | "work";
60
+ organization: string | null;
61
+ created_at: number;
62
+ description: string | null;
63
+ status: string | null;
64
+ owner: string | null;
65
+ updated_at: number;
66
+ account_scope: "owner";
67
+ };
68
+ aliases: {
69
+ name: string;
70
+ source: string;
71
+ display: boolean;
72
+ created_at: number;
73
+ updated_at: number;
74
+ }[];
75
+ };
76
+ read_bytes: number;
77
+ records: number;
78
+ };
@@ -0,0 +1,183 @@
1
+ import { CliError } from "@wirecat/cli-base";
2
+ import { MIGRATIONS } from "../../migrations.js";
3
+ import { decodeProjectAnnotationSnapshot } from "../../project-transfer/snapshot-model.js";
4
+ import { read } from "../transaction.js";
5
+ import { ProjectSnapshotSources } from "./sources.js";
6
+ const unsupported = () => {
7
+ throw new CliError("configuration_error", "project snapshot has unsupported metadata, deletion or account scope");
8
+ };
9
+ const nullableText = (value) => (value === null ? null : String(value));
10
+ const flag = (value) => {
11
+ if (value !== 0 && value !== 1)
12
+ return unsupported();
13
+ return value === 1;
14
+ };
15
+ const provenance = (raw) => {
16
+ if (raw === null)
17
+ return null;
18
+ let value;
19
+ try {
20
+ value = JSON.parse(String(raw));
21
+ }
22
+ catch {
23
+ return unsupported();
24
+ }
25
+ if (!value ||
26
+ typeof value !== "object" ||
27
+ Array.isArray(value) ||
28
+ Object.keys(value).some((key) => key !== "provenance"))
29
+ return unsupported();
30
+ const text = value.provenance;
31
+ if (text !== undefined && text !== null && typeof text !== "string")
32
+ return unsupported();
33
+ return text ?? null;
34
+ };
35
+ export const projectAnnotationSnapshot = (database, projectId, budget) => read(database, () => {
36
+ if (database.prepare("SELECT max(version) AS version FROM schema_migrations").get()?.version !==
37
+ MIGRATIONS.at(-1)?.version)
38
+ throw new CliError("configuration_error", "project snapshots require the SDK's current store schema without source migration");
39
+ const sources = new ProjectSnapshotSources(database, budget);
40
+ const rows = (from, fields, params) => {
41
+ budget.rows(database, from, fields, params);
42
+ return database.prepare(`SELECT ${fields.join(",")} ${from} LIMIT 1001`).all(...params);
43
+ };
44
+ const descriptor = database.prepare("SELECT account_id,deleted_at FROM projects WHERE id=?").get(projectId);
45
+ if (!descriptor)
46
+ throw new CliError("not_found", "the source project does not exist");
47
+ if (descriptor.account_id !== null || descriptor.deleted_at !== null)
48
+ return unsupported();
49
+ const project = rows("FROM projects WHERE id=?", [
50
+ "id",
51
+ "key",
52
+ "name",
53
+ "description",
54
+ "type",
55
+ "scope",
56
+ "organization_id",
57
+ "owner_type",
58
+ "owner_id",
59
+ "status",
60
+ "created_at",
61
+ "updated_at",
62
+ ], [projectId])[0];
63
+ if (!project)
64
+ throw new CliError("not_found", "the source project does not exist");
65
+ const aliases = rows("FROM aliases WHERE aliasable_type='project' AND aliasable_id=? ORDER BY id", ["name", "source", "display", "account_id", "created_at", "updated_at"], [projectId]);
66
+ if (aliases.some((alias) => alias.account_id !== null))
67
+ return unsupported();
68
+ if (database
69
+ .prepare("SELECT 1 FROM facts WHERE project_id=? AND (metadata IS NOT NULL OR deleted_at IS NOT NULL) LIMIT 1")
70
+ .get(projectId))
71
+ return unsupported();
72
+ const facts = rows("FROM facts WHERE project_id=? ORDER BY id", [
73
+ "id",
74
+ "type",
75
+ "statement",
76
+ "status",
77
+ "subject_type",
78
+ "subject_id",
79
+ "occurred_at",
80
+ "supersedes_id",
81
+ "author_type",
82
+ "author_id",
83
+ "model",
84
+ "confirmed_by_type",
85
+ "confirmed_by_id",
86
+ "scope",
87
+ "source",
88
+ "created_at",
89
+ "updated_at",
90
+ ], [projectId]);
91
+ const links = rows("FROM links WHERE (from_type='project' AND from_id=?) OR (to_type='project' AND to_id=?) OR " +
92
+ "(from_type='fact' AND from_id IN (SELECT id FROM facts WHERE project_id=?)) OR " +
93
+ "(to_type='fact' AND to_id IN (SELECT id FROM facts WHERE project_id=?)) ORDER BY id", [
94
+ "id",
95
+ "from_type",
96
+ "from_id",
97
+ "to_type",
98
+ "to_id",
99
+ "kind",
100
+ "anchor",
101
+ "source",
102
+ "target_text",
103
+ "role",
104
+ "evidence",
105
+ "metadata",
106
+ "confirmed",
107
+ "author",
108
+ "created_at",
109
+ "updated_at",
110
+ ], [projectId, projectId, projectId, projectId]);
111
+ const projectRef = `project:${projectId}`;
112
+ const owned = new Set([projectRef, ...facts.map((row) => `fact:${row.id}`)]);
113
+ const data = {
114
+ format: "wirecat.project-annotations",
115
+ version: 2,
116
+ project: {
117
+ ref: projectRef,
118
+ key: project.key,
119
+ name: project.name,
120
+ type: project.type,
121
+ scope: project.scope,
122
+ organization: project.organization_id === null ? null : sources.ref("organization", project.organization_id),
123
+ created_at: project.created_at,
124
+ description: project.description,
125
+ status: project.status,
126
+ owner: sources.ref(project.owner_type, project.owner_id),
127
+ updated_at: project.updated_at,
128
+ account_scope: "owner",
129
+ },
130
+ coverage: {
131
+ aliases: "complete",
132
+ facts: "complete",
133
+ links: "complete",
134
+ memories: "omitted",
135
+ tasks: "omitted",
136
+ labels: "omitted",
137
+ findings: "omitted",
138
+ meeting_memberships: "omitted",
139
+ notes: "omitted",
140
+ },
141
+ aliases: aliases.map((row) => ({
142
+ name: row.name,
143
+ source: row.source,
144
+ display: flag(row.display),
145
+ created_at: row.created_at,
146
+ updated_at: row.updated_at,
147
+ })),
148
+ facts: facts.map((row) => ({
149
+ ref: `fact:${row.id}`,
150
+ type: row.type,
151
+ statement: row.statement,
152
+ status: row.status,
153
+ subject: sources.ref(row.subject_type, row.subject_id),
154
+ occurred_at: row.occurred_at,
155
+ supersedes: row.supersedes_id === null ? null : sources.ref("fact", row.supersedes_id),
156
+ author: sources.ref(row.author_type, row.author_id),
157
+ model: row.model,
158
+ confirmed_by: sources.ref(row.confirmed_by_type, row.confirmed_by_id),
159
+ scope: row.scope,
160
+ source: row.source,
161
+ created_at: row.created_at,
162
+ updated_at: row.updated_at,
163
+ })),
164
+ links: links.map((row) => ({
165
+ ref: `link:${row.id}`,
166
+ from: sources.ref(row.from_type, row.from_id),
167
+ to: sources.ref(row.to_type, row.to_id),
168
+ kind: row.kind,
169
+ origin: row.source,
170
+ anchor: nullableText(row.anchor),
171
+ target_text: nullableText(row.target_text),
172
+ role: nullableText(row.role),
173
+ evidence: nullableText(row.evidence),
174
+ confirmed: flag(row.confirmed),
175
+ provenance: provenance(row.metadata),
176
+ created_at: row.created_at,
177
+ updated_at: row.updated_at,
178
+ author: nullableText(row.author),
179
+ })),
180
+ };
181
+ const snapshot = decodeProjectAnnotationSnapshot(JSON.stringify({ ...data, external: sources.external(owned) }));
182
+ return { snapshot, read_bytes: budget.bytes, records: budget.records };
183
+ });
@@ -0,0 +1,18 @@
1
+ import type { CacheDatabase } from "../../driver.js";
2
+ import { type Thing } from "../things.js";
3
+ import type { SnapshotBudget } from "./budget.js";
4
+ export declare class ProjectSnapshotSources {
5
+ private readonly database;
6
+ private readonly budget;
7
+ readonly references: Map<string, Thing>;
8
+ private readonly byThing;
9
+ constructor(database: CacheDatabase, budget: SnapshotBudget);
10
+ ref(type: unknown, id: unknown): string | null;
11
+ external(owned: Set<string>): {
12
+ ref: string;
13
+ content_sha256: string | null;
14
+ }[];
15
+ private fingerprint;
16
+ private transcript;
17
+ private digest;
18
+ }