@gmickel/gno 1.45.1 → 2.0.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 (236) hide show
  1. package/README.md +1 -1
  2. package/THIRD_PARTY_NOTICES.md +46 -0
  3. package/assets/skill/SKILL.md +7 -6
  4. package/assets/skill/cli-reference.md +14 -6
  5. package/assets/skill/mcp-reference.md +4 -1
  6. package/assets/spa-production.json.gz +0 -0
  7. package/browser-extension/artifacts/gno-browser-clipper-v2.0.0.zip +0 -0
  8. package/browser-extension/artifacts/gno-browser-clipper-v2.0.0.zip.sha256 +1 -0
  9. package/browser-extension/dist/chunk-4tc9v0ja.js +74 -0
  10. package/browser-extension/dist/manifest.json +1 -1
  11. package/browser-extension/dist/preview.html +1 -1
  12. package/browser-extension/dist/service-worker.js +32 -33
  13. package/bunfig.toml +2 -0
  14. package/package.json +40 -26
  15. package/spec/cli.md +30 -11
  16. package/spec/db/schema.sql +146 -1
  17. package/spec/mcp.md +26 -0
  18. package/src/app/context-runtime-types.ts +3 -0
  19. package/src/app/context-runtime.ts +2 -0
  20. package/src/cli/commands/ask.ts +6 -1
  21. package/src/cli/commands/daemon.ts +21 -8
  22. package/src/cli/commands/embed.ts +77 -41
  23. package/src/cli/commands/mcp/install.ts +20 -0
  24. package/src/cli/commands/mcp/paths.ts +25 -0
  25. package/src/cli/commands/mcp/status.ts +6 -0
  26. package/src/cli/detach.ts +3 -2
  27. package/src/cli/program.ts +6 -0
  28. package/src/config/types.ts +3 -3
  29. package/src/converters/adapters/markitdownTs/adapter.ts +1 -2
  30. package/src/converters/adapters/officeparser/adapter.ts +1 -2
  31. package/src/converters/versions.ts +6 -8
  32. package/src/core/context-evidence.ts +8 -4
  33. package/src/core/job-manager.ts +95 -13
  34. package/src/core/network-boundary-inventory.ts +10 -0
  35. package/src/core/shutdown-budget.ts +45 -0
  36. package/src/embed/backlog.ts +107 -4
  37. package/src/embed/batch.ts +42 -2
  38. package/src/embed/fingerprint.ts +16 -0
  39. package/src/embed/retry.ts +113 -5
  40. package/src/embed/variant-backlog.ts +105 -0
  41. package/src/embed/variant-plan.ts +62 -0
  42. package/src/embed/variant-retry.ts +113 -0
  43. package/src/ingestion/graph-reconciliation.ts +327 -0
  44. package/src/ingestion/sync.ts +9 -272
  45. package/src/llm/http-inference.ts +6 -0
  46. package/src/llm/httpEmbedding.ts +37 -6
  47. package/src/llm/httpGeneration.ts +18 -3
  48. package/src/llm/httpRerank.ts +23 -5
  49. package/src/llm/inference-cancellation.ts +168 -0
  50. package/src/llm/inference-scope.ts +202 -0
  51. package/src/llm/lazy-ports.ts +115 -0
  52. package/src/llm/native-worker/client.ts +541 -0
  53. package/src/llm/native-worker/dispatcher.ts +228 -0
  54. package/src/llm/native-worker/embedding-identity.ts +33 -0
  55. package/src/llm/native-worker/entry.ts +173 -0
  56. package/src/llm/native-worker/errors.ts +32 -0
  57. package/src/llm/native-worker/evaluation.ts +16 -0
  58. package/src/llm/native-worker/owned-exit.ts +108 -0
  59. package/src/llm/native-worker/owner.ts +141 -0
  60. package/src/llm/native-worker/ports.ts +317 -0
  61. package/src/llm/native-worker/protocol.ts +442 -0
  62. package/src/llm/native-worker/runtime-config.ts +92 -0
  63. package/src/llm/nodeLlamaCpp/adapter.ts +77 -20
  64. package/src/llm/nodeLlamaCpp/embedding.ts +130 -46
  65. package/src/llm/nodeLlamaCpp/generation.ts +34 -5
  66. package/src/llm/nodeLlamaCpp/lifecycle-options.ts +99 -0
  67. package/src/llm/nodeLlamaCpp/lifecycle.ts +209 -204
  68. package/src/llm/nodeLlamaCpp/rerank-capacity.ts +111 -0
  69. package/src/llm/nodeLlamaCpp/rerank.ts +118 -27
  70. package/src/llm/nodeLlamaCpp/simulator-handle.ts +73 -0
  71. package/src/llm/nodeLlamaCpp/simulator-install.ts +124 -0
  72. package/src/llm/nodeLlamaCpp/simulator-session.ts +240 -0
  73. package/src/llm/nodeLlamaCpp/simulator-types.ts +80 -0
  74. package/src/llm/types.ts +35 -5
  75. package/src/mcp/context.ts +27 -0
  76. package/src/mcp/http-transport.ts +12 -10
  77. package/src/mcp/server.ts +3 -0
  78. package/src/mcp/tool-profile.ts +30 -8
  79. package/src/mcp/tools/context.ts +8 -11
  80. package/src/mcp/tools/embed.ts +1 -1
  81. package/src/mcp/tools/index-cmd.ts +1 -1
  82. package/src/mcp/tools/index.ts +10 -8
  83. package/src/mcp/tools/query.ts +14 -30
  84. package/src/mcp/tools/vsearch.ts +1 -1
  85. package/src/pipeline/answer.ts +23 -3
  86. package/src/pipeline/claim-verifier.ts +6 -0
  87. package/src/pipeline/expansion.ts +43 -40
  88. package/src/pipeline/explain.ts +6 -2
  89. package/src/pipeline/filters.ts +63 -0
  90. package/src/pipeline/fusion.ts +29 -9
  91. package/src/pipeline/graph-retrieval.ts +29 -9
  92. package/src/pipeline/hybrid.ts +198 -55
  93. package/src/pipeline/hydration.ts +161 -0
  94. package/src/pipeline/owner-fusion.ts +87 -0
  95. package/src/pipeline/rerank.ts +35 -11
  96. package/src/pipeline/search.ts +13 -2
  97. package/src/pipeline/types.ts +5 -3
  98. package/src/pipeline/vsearch.ts +87 -7
  99. package/src/sdk/client.ts +47 -3
  100. package/src/sdk/embed.ts +63 -39
  101. package/src/serve/background-runtime.ts +1 -1
  102. package/src/serve/context.ts +41 -56
  103. package/src/serve/embed-scheduler.ts +58 -35
  104. package/src/serve/public/components/IndexingProgress.tsx +46 -60
  105. package/src/serve/public/globals.built.css +1 -1
  106. package/src/serve/public/lib/shiki-language-ids.ts +14 -0
  107. package/src/serve/resident-admission.ts +36 -36
  108. package/src/serve/resident-background-work.ts +20 -2
  109. package/src/serve/resident-request.ts +11 -5
  110. package/src/serve/resident-runtime.ts +97 -61
  111. package/src/serve/resident-shutdown.ts +153 -0
  112. package/src/serve/routes/api.ts +3 -1
  113. package/src/serve/server.ts +47 -26
  114. package/src/store/migrations/028-vector-variants.ts +54 -0
  115. package/src/store/migrations/029-graph-reference-state.ts +77 -0
  116. package/src/store/migrations/index.ts +4 -0
  117. package/src/store/sqlite/adapter.ts +251 -183
  118. package/src/store/sqlite/eligibility.ts +174 -0
  119. package/src/store/sqlite/graph-edge-application.ts +66 -0
  120. package/src/store/sqlite/graph-reference-state.ts +194 -0
  121. package/src/store/sqlite/legacy-vector-ownership.ts +79 -0
  122. package/src/store/types.ts +80 -12
  123. package/src/store/vector/eligibility.ts +36 -0
  124. package/src/store/vector/freshness.ts +33 -6
  125. package/src/store/vector/lazy.ts +81 -0
  126. package/src/store/vector/sqlite-vec.ts +106 -54
  127. package/src/store/vector/stats.ts +14 -3
  128. package/src/store/vector/types.ts +35 -2
  129. package/src/store/vector/variant-search.ts +192 -0
  130. package/src/store/vector/variants.ts +451 -0
  131. package/vendor/converters/markitdown-ts/LICENSE +21 -0
  132. package/vendor/converters/markitdown-ts/dist/index.cjs +1180 -0
  133. package/vendor/converters/markitdown-ts/dist/index.d.cts +46 -0
  134. package/vendor/converters/markitdown-ts/dist/index.d.mts +46 -0
  135. package/vendor/converters/markitdown-ts/dist/index.d.ts +46 -0
  136. package/vendor/converters/markitdown-ts/dist/index.mjs +1152 -0
  137. package/vendor/converters/markitdown-ts/package.json +77 -0
  138. package/vendor/converters/officeparser/LICENSE +21 -0
  139. package/vendor/converters/officeparser/dist/OfficeConverter.d.ts +47 -0
  140. package/vendor/converters/officeparser/dist/OfficeConverter.js +76 -0
  141. package/vendor/converters/officeparser/dist/OfficeGenerator.d.ts +23 -0
  142. package/vendor/converters/officeparser/dist/OfficeGenerator.js +73 -0
  143. package/vendor/converters/officeparser/dist/OfficeParser.d.ts +106 -0
  144. package/vendor/converters/officeparser/dist/OfficeParser.js +332 -0
  145. package/vendor/converters/officeparser/dist/cli.d.ts +28 -0
  146. package/vendor/converters/officeparser/dist/cli.js +381 -0
  147. package/vendor/converters/officeparser/dist/defaults.d.ts +41 -0
  148. package/vendor/converters/officeparser/dist/defaults.js +218 -0
  149. package/vendor/converters/officeparser/dist/generators/BaseGenerator.d.ts +107 -0
  150. package/vendor/converters/officeparser/dist/generators/BaseGenerator.js +248 -0
  151. package/vendor/converters/officeparser/dist/generators/ChunkingGenerator.d.ts +82 -0
  152. package/vendor/converters/officeparser/dist/generators/ChunkingGenerator.js +797 -0
  153. package/vendor/converters/officeparser/dist/generators/CsvGenerator.d.ts +38 -0
  154. package/vendor/converters/officeparser/dist/generators/CsvGenerator.js +245 -0
  155. package/vendor/converters/officeparser/dist/generators/EpubGenerator.d.ts +43 -0
  156. package/vendor/converters/officeparser/dist/generators/EpubGenerator.js +315 -0
  157. package/vendor/converters/officeparser/dist/generators/HtmlGenerator.d.ts +59 -0
  158. package/vendor/converters/officeparser/dist/generators/HtmlGenerator.js +1942 -0
  159. package/vendor/converters/officeparser/dist/generators/MarkdownGenerator.d.ts +96 -0
  160. package/vendor/converters/officeparser/dist/generators/MarkdownGenerator.js +1175 -0
  161. package/vendor/converters/officeparser/dist/generators/PdfGenerator.d.ts +22 -0
  162. package/vendor/converters/officeparser/dist/generators/PdfGenerator.js +194 -0
  163. package/vendor/converters/officeparser/dist/generators/RtfGenerator.d.ts +29 -0
  164. package/vendor/converters/officeparser/dist/generators/RtfGenerator.js +316 -0
  165. package/vendor/converters/officeparser/dist/generators/TextGenerator.d.ts +13 -0
  166. package/vendor/converters/officeparser/dist/generators/TextGenerator.js +201 -0
  167. package/vendor/converters/officeparser/dist/index.d.ts +60 -0
  168. package/vendor/converters/officeparser/dist/index.js +72 -0
  169. package/vendor/converters/officeparser/dist/index.mjs +18 -0
  170. package/vendor/converters/officeparser/dist/officeparser.browser.d.ts +2621 -0
  171. package/vendor/converters/officeparser/dist/officeparser.browser.iife.js +1336 -0
  172. package/vendor/converters/officeparser/dist/officeparser.browser.mjs +1335 -0
  173. package/vendor/converters/officeparser/dist/officeparser.browser.slim.d.ts +2621 -0
  174. package/vendor/converters/officeparser/dist/officeparser.browser.slim.iife.js +1336 -0
  175. package/vendor/converters/officeparser/dist/officeparser.browser.slim.mjs +1335 -0
  176. package/vendor/converters/officeparser/dist/parsers/CsvParser.d.ts +9 -0
  177. package/vendor/converters/officeparser/dist/parsers/CsvParser.js +115 -0
  178. package/vendor/converters/officeparser/dist/parsers/EpubParser.d.ts +8 -0
  179. package/vendor/converters/officeparser/dist/parsers/EpubParser.js +217 -0
  180. package/vendor/converters/officeparser/dist/parsers/ExcelParser.d.ts +32 -0
  181. package/vendor/converters/officeparser/dist/parsers/ExcelParser.js +736 -0
  182. package/vendor/converters/officeparser/dist/parsers/HtmlParser.d.ts +2 -0
  183. package/vendor/converters/officeparser/dist/parsers/HtmlParser.js +1287 -0
  184. package/vendor/converters/officeparser/dist/parsers/MarkdownParser.d.ts +2 -0
  185. package/vendor/converters/officeparser/dist/parsers/MarkdownParser.js +1272 -0
  186. package/vendor/converters/officeparser/dist/parsers/OpenOfficeParser.d.ts +31 -0
  187. package/vendor/converters/officeparser/dist/parsers/OpenOfficeParser.js +1819 -0
  188. package/vendor/converters/officeparser/dist/parsers/PdfParser.d.ts +67 -0
  189. package/vendor/converters/officeparser/dist/parsers/PdfParser.js +848 -0
  190. package/vendor/converters/officeparser/dist/parsers/PowerPointParser.d.ts +32 -0
  191. package/vendor/converters/officeparser/dist/parsers/PowerPointParser.js +950 -0
  192. package/vendor/converters/officeparser/dist/parsers/RtfParser.d.ts +187 -0
  193. package/vendor/converters/officeparser/dist/parsers/RtfParser.js +1801 -0
  194. package/vendor/converters/officeparser/dist/parsers/WordParser.d.ts +79 -0
  195. package/vendor/converters/officeparser/dist/parsers/WordParser.js +1177 -0
  196. package/vendor/converters/officeparser/dist/sbom.cdx.json +1763 -0
  197. package/vendor/converters/officeparser/dist/types.d.ts +2507 -0
  198. package/vendor/converters/officeparser/dist/types.js +107 -0
  199. package/vendor/converters/officeparser/dist/utils/astUtils.d.ts +16 -0
  200. package/vendor/converters/officeparser/dist/utils/astUtils.js +33 -0
  201. package/vendor/converters/officeparser/dist/utils/chartUtils.d.ts +6 -0
  202. package/vendor/converters/officeparser/dist/utils/chartUtils.js +257 -0
  203. package/vendor/converters/officeparser/dist/utils/configUtils.d.ts +44 -0
  204. package/vendor/converters/officeparser/dist/utils/configUtils.js +315 -0
  205. package/vendor/converters/officeparser/dist/utils/dateUtils.d.ts +17 -0
  206. package/vendor/converters/officeparser/dist/utils/dateUtils.js +69 -0
  207. package/vendor/converters/officeparser/dist/utils/envUtils.d.ts +29 -0
  208. package/vendor/converters/officeparser/dist/utils/envUtils.js +152 -0
  209. package/vendor/converters/officeparser/dist/utils/errorUtils.d.ts +72 -0
  210. package/vendor/converters/officeparser/dist/utils/errorUtils.js +245 -0
  211. package/vendor/converters/officeparser/dist/utils/imageUtils.d.ts +66 -0
  212. package/vendor/converters/officeparser/dist/utils/imageUtils.js +133 -0
  213. package/vendor/converters/officeparser/dist/utils/mathUtils.d.ts +42 -0
  214. package/vendor/converters/officeparser/dist/utils/mathUtils.js +385 -0
  215. package/vendor/converters/officeparser/dist/utils/moduleLoader.d.ts +18 -0
  216. package/vendor/converters/officeparser/dist/utils/moduleLoader.js +106 -0
  217. package/vendor/converters/officeparser/dist/utils/ocrUtils.d.ts +42 -0
  218. package/vendor/converters/officeparser/dist/utils/ocrUtils.js +428 -0
  219. package/vendor/converters/officeparser/dist/utils/sanitize.d.ts +148 -0
  220. package/vendor/converters/officeparser/dist/utils/sanitize.js +344 -0
  221. package/vendor/converters/officeparser/dist/utils/sheetUtils.d.ts +7 -0
  222. package/vendor/converters/officeparser/dist/utils/sheetUtils.js +35 -0
  223. package/vendor/converters/officeparser/dist/utils/styleMapper.d.ts +36 -0
  224. package/vendor/converters/officeparser/dist/utils/styleMapper.js +224 -0
  225. package/vendor/converters/officeparser/dist/utils/xmlUtils.d.ts +163 -0
  226. package/vendor/converters/officeparser/dist/utils/xmlUtils.js +461 -0
  227. package/vendor/converters/officeparser/dist/utils/zipUtils.d.ts +134 -0
  228. package/vendor/converters/officeparser/dist/utils/zipUtils.js +337 -0
  229. package/vendor/converters/officeparser/package.json +147 -0
  230. package/vendor/converters/upstream-manifest.json +124 -0
  231. package/vendor/dependency-fixes/README.md +77 -0
  232. package/vendor/dependency-fixes/vendor-converters.py +83 -0
  233. package/browser-extension/artifacts/gno-browser-clipper-v1.45.1.zip +0 -0
  234. package/browser-extension/artifacts/gno-browser-clipper-v1.45.1.zip.sha256 +0 -1
  235. package/browser-extension/dist/chunk-627emwpj.js +0 -75
  236. /package/browser-extension/dist/{chunk-ydfx5d7p.css → chunk-z74y8n8c.css} +0 -0
@@ -0,0 +1,327 @@
1
+ /** Incremental graph inventory closure; full projection remains the repair oracle. */
2
+ import type {
3
+ DocEdgeInput,
4
+ DocumentRow,
5
+ GraphReferenceDocument,
6
+ GraphReferenceInventory,
7
+ StorePort,
8
+ } from "../store/types";
9
+ import type { SyncOptions } from "./types";
10
+
11
+ import {
12
+ isRelationMap,
13
+ normalizeRelationEdgeType,
14
+ normalizeRelationTarget,
15
+ } from "../core/change-diff";
16
+ import {
17
+ normalizeMarkdownPath,
18
+ normalizeWikiName,
19
+ parseTargetParts,
20
+ } from "../core/links";
21
+ import { parseFrontmatter } from "./frontmatter";
22
+
23
+ const VERSION = 1;
24
+ const EDGE_TYPE = /^[a-z][a-z0-9_]*$/;
25
+ type ProjectionError = { relPath: string; code: string; message: string };
26
+
27
+ function identity(doc: DocumentRow): GraphReferenceDocument {
28
+ return {
29
+ documentId: doc.id,
30
+ collection: doc.collection,
31
+ relPath: doc.relPath,
32
+ docid: doc.docid,
33
+ uri: doc.uri,
34
+ title: doc.title,
35
+ mirrorHash: doc.mirrorHash,
36
+ sourceHash: doc.sourceHash,
37
+ contentType: doc.contentType ?? null,
38
+ };
39
+ }
40
+
41
+ /** Insert once in catalog order: preserves legacy Array.find ambiguity precedence. */
42
+ function relationResolver(docs: GraphReferenceDocument[]) {
43
+ const docids = new Map<string, GraphReferenceDocument>();
44
+ const uris = new Map<string, GraphReferenceDocument>();
45
+ const paths = new Map<string, GraphReferenceDocument>();
46
+ const wiki = new Map<string, GraphReferenceDocument>();
47
+ const localWiki = new Map<string, GraphReferenceDocument>();
48
+ const put = (
49
+ map: Map<string, GraphReferenceDocument>,
50
+ key: string,
51
+ doc: GraphReferenceDocument
52
+ ) => {
53
+ if (!map.has(key)) map.set(key, doc);
54
+ };
55
+ for (const doc of docs) {
56
+ put(docids, doc.docid, doc);
57
+ put(uris, doc.uri, doc);
58
+ put(paths, `${doc.collection}/${doc.relPath}`, doc);
59
+ const title = doc.title ?? doc.relPath.split("/").pop() ?? doc.relPath;
60
+ for (const value of [
61
+ title,
62
+ doc.relPath,
63
+ doc.relPath.replace(/\.[^/.]+$/, ""),
64
+ ]) {
65
+ const key = normalizeWikiName(value);
66
+ put(wiki, key, doc);
67
+ put(localWiki, `${doc.collection}\0${key}`, doc);
68
+ }
69
+ }
70
+ return (
71
+ source: GraphReferenceDocument,
72
+ raw: string
73
+ ): GraphReferenceDocument | undefined => {
74
+ const target = normalizeRelationTarget(raw);
75
+ if (!target) return;
76
+ if (target.startsWith("#")) return docids.get(target);
77
+ if (target.startsWith("gno://")) return uris.get(target);
78
+ const parts = parseTargetParts(target);
79
+ if (!parts.ref) return;
80
+ const key = normalizeWikiName(parts.ref);
81
+ if (parts.collection)
82
+ return (
83
+ paths.get(`${parts.collection}/${parts.ref}`) ??
84
+ localWiki.get(`${parts.collection}\0${key}`)
85
+ );
86
+ const relativePath = normalizeMarkdownPath(parts.ref, source.relPath);
87
+ return (
88
+ (relativePath
89
+ ? paths.get(`${source.collection}/${relativePath}`)
90
+ : undefined) ??
91
+ paths.get(parts.ref) ??
92
+ localWiki.get(`${source.collection}\0${key}`) ??
93
+ wiki.get(key)
94
+ );
95
+ };
96
+ }
97
+
98
+ export async function projectGraph(
99
+ store: StorePort,
100
+ options: SyncOptions,
101
+ requestedSources?: Set<number>,
102
+ forceFull = false
103
+ ): Promise<ProjectionError[]> {
104
+ const errors: ProjectionError[] = [];
105
+ try {
106
+ const graph = store.graphReferenceStore?.();
107
+ let fingerprint = "";
108
+ if (graph) {
109
+ const collections = await store.getCollections();
110
+ if (!collections.ok) throw new Error(collections.error.message);
111
+ fingerprint = new Bun.CryptoHasher("sha256")
112
+ .update(
113
+ JSON.stringify({
114
+ rules: options.contentTypeRules ?? [],
115
+ collections: collections.value
116
+ .map(({ syncedAt: _syncedAt, ...config }) => config)
117
+ .sort((a, b) => a.name.localeCompare(b.name)),
118
+ })
119
+ )
120
+ .digest("hex");
121
+ }
122
+ const state = graph?.state(VERSION, fingerprint);
123
+ if (!forceFull && state?.complete) return [];
124
+ const documents = await store.listDocuments();
125
+ if (!documents.ok) throw new Error(documents.error.message);
126
+ const activeDocs = documents.value.filter((doc) => doc.active);
127
+ const current = activeDocs.map(identity);
128
+ const currentById = new Map(current.map((doc) => [doc.documentId, doc]));
129
+ // Read before begin: a new version/config deliberately resets the old inventory.
130
+ const previous = graph?.readInventory() ?? [];
131
+ const previousById = new Map(
132
+ previous.map((row) => [row.document.documentId, row])
133
+ );
134
+ const full =
135
+ forceFull ||
136
+ !graph ||
137
+ !state ||
138
+ state.inProgress ||
139
+ state.version !== VERSION ||
140
+ state.configFingerprint !== fingerprint;
141
+ const resolve = relationResolver(current);
142
+ let selected: Set<number> | undefined;
143
+ if (!full && graph) {
144
+ selected = new Set(requestedSources);
145
+ const changed = new Map<number, GraphReferenceDocument[]>();
146
+ for (const doc of current) {
147
+ const old = previousById.get(doc.documentId)?.document;
148
+ if (!old || JSON.stringify(old) !== JSON.stringify(doc)) {
149
+ changed.set(doc.documentId, old ? [old, doc] : [doc]);
150
+ selected.add(doc.documentId);
151
+ }
152
+ }
153
+ for (const row of previous) {
154
+ if (!currentById.has(row.document.documentId)) {
155
+ changed.set(row.document.documentId, [row.document]);
156
+ selected.add(row.document.documentId);
157
+ }
158
+ }
159
+ // Input mutation without a changed identity can be a direct parsed-link edit.
160
+ // No durable per-source link journal exists; conservatively rebuild in that case.
161
+ if (changed.size === 0 && state.dirty) selected = undefined;
162
+ else {
163
+ for (const id of graph.incomingLinkSources(
164
+ [...changed.values()].flat()
165
+ ))
166
+ selected.add(id);
167
+ const resolveOld = relationResolver(
168
+ previous.map((row) => row.document)
169
+ );
170
+ for (const row of previous) {
171
+ const source = currentById.get(row.document.documentId);
172
+ if (!source || selected.has(source.documentId)) continue;
173
+ if (
174
+ row.references.some(
175
+ (ref) =>
176
+ resolveOld(row.document, ref.target)?.documentId !==
177
+ resolve(source, ref.target)?.documentId
178
+ )
179
+ )
180
+ selected.add(source.documentId);
181
+ }
182
+ }
183
+ }
184
+ // Persist interruption authority before starting the projection transaction.
185
+ const epoch = graph?.begin(VERSION, fingerprint);
186
+ if (state && epoch !== state.epoch + 1)
187
+ throw new Error("Graph inputs changed while computing affected sources");
188
+ const work = async () => {
189
+ const targets = selected
190
+ ? documents.value.filter((doc) => selected.has(doc.id))
191
+ : documents.value;
192
+ // Configured sources get their final sets below. Applying defaults first
193
+ // would delete/recreate an unchanged configured edge on every repair pass.
194
+ const defaults = targets.filter((doc) => {
195
+ const hint = options.contentTypeRules?.find(
196
+ (rule) => rule.id === doc.contentType
197
+ )?.graphHints?.[0];
198
+ return !doc.active || !hint || !EDGE_TYPE.test(hint);
199
+ });
200
+ const backfill = await store.backfillDocEdges(
201
+ defaults.map((doc) => doc.id)
202
+ );
203
+ if (!backfill.ok) throw new Error(backfill.error.message);
204
+ for (const [index, doc] of targets.entries()) {
205
+ if (index > 0 && index % 25 === 0) await Bun.sleep(0);
206
+ if (!doc.active) {
207
+ const cleared = await store.setDocEdges(
208
+ doc.id,
209
+ [],
210
+ "frontmatter-relation"
211
+ );
212
+ if (!cleared.ok)
213
+ errors.push({ relPath: doc.relPath, ...cleared.error });
214
+ continue;
215
+ }
216
+ const snapshot = currentById.get(doc.id)!;
217
+ const cached = selected ? previousById.get(doc.id) : undefined;
218
+ let references: GraphReferenceInventory["references"];
219
+ if (
220
+ cached &&
221
+ cached.document.mirrorHash === doc.mirrorHash &&
222
+ cached.document.sourceHash === doc.sourceHash
223
+ )
224
+ references = cached.references;
225
+ else {
226
+ references = [];
227
+ if (doc.mirrorHash) {
228
+ const content = await store.getContent(doc.mirrorHash);
229
+ if (!content.ok || content.value === null) {
230
+ errors.push({
231
+ relPath: doc.relPath,
232
+ code: "QUERY_FAILED",
233
+ message: content.ok
234
+ ? "Missing graph source content"
235
+ : content.error.message,
236
+ });
237
+ continue;
238
+ }
239
+ const relations = parseFrontmatter(content.value).metadata
240
+ .relations;
241
+ if (isRelationMap(relations)) {
242
+ for (const [rawType, refs] of Object.entries(relations)) {
243
+ const edgeType = normalizeRelationEdgeType(rawType);
244
+ if (EDGE_TYPE.test(edgeType)) {
245
+ for (const target of refs)
246
+ references.push({ edgeType, target });
247
+ }
248
+ }
249
+ }
250
+ }
251
+ }
252
+ const relationEdges: DocEdgeInput[] = [];
253
+ for (const ref of references) {
254
+ const target = resolve(snapshot, ref.target);
255
+ if (target)
256
+ relationEdges.push({
257
+ targetDocId: target.documentId,
258
+ edgeType: ref.edgeType,
259
+ confidence: "manual",
260
+ });
261
+ }
262
+ const relationsResult = await store.setDocEdges(
263
+ doc.id,
264
+ relationEdges,
265
+ "frontmatter-relation"
266
+ );
267
+ if (!relationsResult.ok)
268
+ errors.push({ relPath: doc.relPath, ...relationsResult.error });
269
+ graph?.writeInventory({ document: snapshot, references });
270
+ const hint = options.contentTypeRules?.find(
271
+ (rule) => rule.id === doc.contentType
272
+ )?.graphHints?.[0];
273
+ if (!hint || !EDGE_TYPE.test(hint)) continue;
274
+ const links = await store.getLinksForDoc(doc.id);
275
+ if (!links.ok) {
276
+ errors.push({ relPath: doc.relPath, ...links.error });
277
+ continue;
278
+ }
279
+ const relationIds = new Set(
280
+ relationEdges.map((edge) => edge.targetDocId)
281
+ );
282
+ const wikiEdges: DocEdgeInput[] = [];
283
+ const markdownEdges: DocEdgeInput[] = [];
284
+ for (const link of links.value) {
285
+ const ref =
286
+ link.linkType === "markdown"
287
+ ? `${doc.collection}/${link.targetRefNorm}`
288
+ : link.targetCollection
289
+ ? `${link.targetCollection}:${link.targetRef}`
290
+ : link.targetRefNorm;
291
+ const target = resolve(snapshot, ref);
292
+ if (!target || relationIds.has(target.documentId)) continue;
293
+ const edge: DocEdgeInput = {
294
+ targetDocId: target.documentId,
295
+ edgeType: hint,
296
+ confidence: "configured",
297
+ };
298
+ (link.linkType === "wiki" ? wikiEdges : markdownEdges).push(edge);
299
+ }
300
+ for (const [source, edges] of [
301
+ ["wikilink", wikiEdges],
302
+ ["markdown-link", markdownEdges],
303
+ ] as const) {
304
+ const result = await store.setDocEdges(doc.id, edges, source);
305
+ if (!result.ok)
306
+ errors.push({ relPath: doc.relPath, ...result.error });
307
+ }
308
+ }
309
+ if (errors.length) throw new Error("Graph projection incomplete");
310
+ if (graph && epoch !== undefined) graph.complete(epoch);
311
+ };
312
+ if (store.withTransaction) {
313
+ const result = await store.withTransaction(work);
314
+ if (!result.ok && errors.length === 0)
315
+ throw new Error(result.error.message);
316
+ } else await work();
317
+ } catch (cause) {
318
+ if (errors.length === 0)
319
+ errors.push({
320
+ relPath: "(typed edge projection)",
321
+ code: "QUERY_FAILED",
322
+ message:
323
+ cause instanceof Error ? cause.message : "Graph projection failed",
324
+ });
325
+ }
326
+ return errors;
327
+ }
@@ -14,7 +14,6 @@ import type { NormalizedContentTypeRule } from "../config";
14
14
  import type { Collection } from "../config/types";
15
15
  import type {
16
16
  ChunkInput,
17
- DocEdgeInput,
18
17
  DocLinkInput,
19
18
  DocumentInput,
20
19
  DocumentRow,
@@ -49,16 +48,12 @@ import { DEFAULT_LIMITS, type RecordAdapter } from "../converters/types";
49
48
  import {
50
49
  diffDocumentStructure,
51
50
  extractDocumentStructure,
52
- isRelationMap,
53
- normalizeRelationEdgeType,
54
- normalizeRelationTarget,
55
51
  } from "../core/change-diff";
56
52
  import { enforceCollectionEgress } from "../core/egress-enforcement";
57
53
  import {
58
54
  normalizeMarkdownPath,
59
55
  normalizeWikiName,
60
56
  parseLinks,
61
- parseTargetParts,
62
57
  } from "../core/links";
63
58
  import { extractMemoryScopes } from "../core/memory-record";
64
59
  import { normalizeTag, validateTag } from "../core/tags";
@@ -68,6 +63,7 @@ import {
68
63
  parseFrontmatter,
69
64
  stripFrontmatter,
70
65
  } from "./frontmatter";
66
+ import { projectGraph } from "./graph-reconciliation";
71
67
  import { buildLineOffsets } from "./position";
72
68
  import { processRecordContainer } from "./record-container";
73
69
  import {
@@ -104,8 +100,6 @@ const MAX_CONCURRENCY = 16;
104
100
  export const INGEST_VERSION = 6;
105
101
  const EMPTY_CONTENT_TYPE_RULES_FINGERPRINT =
106
102
  fingerprintContentTypeMetadataRules([]);
107
- const RELATION_EDGE_TYPE_PATTERN = /^[a-z][a-z0-9_]*$/;
108
- const PROJECTION_YIELD_INTERVAL = 25;
109
103
  const NON_RETRYABLE_CONVERSION_ERROR_CODES = new Set([
110
104
  "CORRUPT",
111
105
  "PERMISSION",
@@ -113,98 +107,6 @@ const NON_RETRYABLE_CONVERSION_ERROR_CODES = new Set([
113
107
  "UNSUPPORTED",
114
108
  ]);
115
109
 
116
- function findDocByWikiRef(
117
- docs: DocumentRow[],
118
- targetRef: string,
119
- collection?: string
120
- ): DocumentRow | undefined {
121
- const normalized = normalizeWikiName(targetRef);
122
- const candidates = collection
123
- ? docs.filter((doc) => doc.collection === collection)
124
- : docs;
125
-
126
- return candidates.find((doc) => {
127
- const title = doc.title ?? doc.relPath.split("/").pop() ?? doc.relPath;
128
- const relStem = doc.relPath.replace(/\.[^/.]+$/, "");
129
- return (
130
- normalizeWikiName(title) === normalized ||
131
- normalizeWikiName(doc.relPath) === normalized ||
132
- normalizeWikiName(relStem) === normalized
133
- );
134
- });
135
- }
136
-
137
- function resolveRelationTarget(
138
- docs: DocumentRow[],
139
- sourceDoc: DocumentRow,
140
- rawTarget: string
141
- ): DocumentRow | undefined {
142
- const target = normalizeRelationTarget(rawTarget);
143
- if (!target) {
144
- return undefined;
145
- }
146
-
147
- if (target.startsWith("#")) {
148
- return docs.find((doc) => doc.docid === target);
149
- }
150
-
151
- if (target.startsWith("gno://")) {
152
- return docs.find((doc) => doc.uri === target);
153
- }
154
-
155
- const parts = parseTargetParts(target);
156
- const targetCollection = parts.collection;
157
- const targetRef = parts.ref;
158
- if (!targetRef) {
159
- return undefined;
160
- }
161
-
162
- if (targetCollection) {
163
- const exact = docs.find(
164
- (doc) => doc.collection === targetCollection && doc.relPath === targetRef
165
- );
166
- return exact ?? findDocByWikiRef(docs, targetRef, targetCollection);
167
- }
168
-
169
- const sameCollectionPath = normalizeMarkdownPath(
170
- targetRef,
171
- sourceDoc.relPath
172
- );
173
- if (sameCollectionPath) {
174
- const exact = docs.find(
175
- (doc) =>
176
- doc.collection === sourceDoc.collection &&
177
- doc.relPath === sameCollectionPath
178
- );
179
- if (exact) {
180
- return exact;
181
- }
182
- }
183
-
184
- const explicitCollPath = docs.find(
185
- (doc) => `${doc.collection}/${doc.relPath}` === targetRef
186
- );
187
- if (explicitCollPath) {
188
- return explicitCollPath;
189
- }
190
-
191
- return (
192
- findDocByWikiRef(docs, targetRef, sourceDoc.collection) ??
193
- findDocByWikiRef(docs, targetRef)
194
- );
195
- }
196
-
197
- function getPrimaryGraphHint(
198
- contentType: string | null | undefined,
199
- rules: NormalizedContentTypeRule[]
200
- ): string | undefined {
201
- if (!contentType) {
202
- return undefined;
203
- }
204
- const rule = rules.find((candidate) => candidate.id === contentType);
205
- return rule?.graphHints?.[0];
206
- }
207
-
208
110
  /**
209
111
  * Decide whether to process a file or skip it.
210
112
  * Handles repair cases where sourceHash matches but content is incomplete.
@@ -219,6 +121,11 @@ function decideAction(
219
121
  if (!existing) {
220
122
  return { kind: "process", reason: "new file" };
221
123
  }
124
+ // Identical bytes can reappear after deletion; restore through the normal
125
+ // conversion/upsert path so identity, derived data and journal agree.
126
+ if (!existing.active) {
127
+ return { kind: "repair", reason: "source restored" };
128
+ }
222
129
 
223
130
  // Source hash changed - must process
224
131
  if (existing.sourceHash !== sourceHash) {
@@ -1784,182 +1691,12 @@ export class SyncService {
1784
1691
  return null;
1785
1692
  }
1786
1693
 
1787
- private async projectTypedEdges(
1694
+ private projectTypedEdges(
1788
1695
  store: StorePort,
1789
1696
  options: SyncOptions,
1790
1697
  sourceDocumentIds?: Set<number>
1791
1698
  ): Promise<Array<{ relPath: string; code: string; message: string }>> {
1792
- const errors: Array<{ relPath: string; code: string; message: string }> =
1793
- [];
1794
-
1795
- const selectedSourceIds = sourceDocumentIds
1796
- ? [...sourceDocumentIds]
1797
- : undefined;
1798
- const backfillResult = await store.backfillDocEdges(selectedSourceIds);
1799
- if (!backfillResult.ok) {
1800
- return [
1801
- {
1802
- relPath: "(typed edge backfill)",
1803
- code: backfillResult.error.code,
1804
- message: backfillResult.error.message,
1805
- },
1806
- ];
1807
- }
1808
-
1809
- const docsResult = await store.listDocuments();
1810
- if (!docsResult.ok) {
1811
- return [
1812
- {
1813
- relPath: "(typed edge projection)",
1814
- code: docsResult.error.code,
1815
- message: docsResult.error.message,
1816
- },
1817
- ];
1818
- }
1819
-
1820
- const activeDocs = docsResult.value.filter((doc) => doc.active);
1821
- const activeIds = new Set(activeDocs.map((doc) => doc.id));
1822
- if (selectedSourceIds) {
1823
- for (const documentId of selectedSourceIds) {
1824
- if (!activeIds.has(documentId)) {
1825
- // Clear typed edges for inactivated projection sources.
1826
- const clearResult = await store.setDocEdges(
1827
- documentId,
1828
- [],
1829
- "frontmatter-relation"
1830
- );
1831
- if (!clearResult.ok) {
1832
- errors.push({
1833
- relPath: `(doc:${documentId})`,
1834
- code: clearResult.error.code,
1835
- message: clearResult.error.message,
1836
- });
1837
- }
1838
- }
1839
- }
1840
- }
1841
- const projectedDocs = sourceDocumentIds
1842
- ? activeDocs.filter((doc) => sourceDocumentIds.has(doc.id))
1843
- : activeDocs;
1844
-
1845
- for (const [docIndex, doc] of projectedDocs.entries()) {
1846
- if (docIndex > 0 && docIndex % PROJECTION_YIELD_INTERVAL === 0) {
1847
- await Bun.sleep(0);
1848
- }
1849
- if (!doc.mirrorHash) {
1850
- continue;
1851
- }
1852
-
1853
- const contentResult = await store.getContent(doc.mirrorHash);
1854
- if (!contentResult.ok || contentResult.value === null) {
1855
- continue;
1856
- }
1857
-
1858
- const relationsValue = parseFrontmatter(contentResult.value).metadata
1859
- .relations;
1860
- const relationEdges: DocEdgeInput[] = [];
1861
-
1862
- if (isRelationMap(relationsValue)) {
1863
- for (const [rawEdgeType, targets] of Object.entries(relationsValue)) {
1864
- const edgeType = normalizeRelationEdgeType(rawEdgeType);
1865
- if (!RELATION_EDGE_TYPE_PATTERN.test(edgeType)) {
1866
- continue;
1867
- }
1868
- for (const target of targets) {
1869
- const targetDoc = resolveRelationTarget(activeDocs, doc, target);
1870
- if (targetDoc) {
1871
- relationEdges.push({
1872
- targetDocId: targetDoc.id,
1873
- edgeType,
1874
- confidence: "manual",
1875
- });
1876
- }
1877
- }
1878
- }
1879
- }
1880
- const relationTargetIds = new Set(
1881
- relationEdges.map((edge) => edge.targetDocId)
1882
- );
1883
-
1884
- const relationsResult = await store.setDocEdges(
1885
- doc.id,
1886
- relationEdges,
1887
- "frontmatter-relation"
1888
- );
1889
- if (!relationsResult.ok) {
1890
- errors.push({
1891
- relPath: doc.relPath,
1892
- code: relationsResult.error.code,
1893
- message: relationsResult.error.message,
1894
- });
1895
- }
1896
-
1897
- const primaryHint = getPrimaryGraphHint(
1898
- doc.contentType,
1899
- options.contentTypeRules ?? []
1900
- );
1901
- if (!primaryHint || !RELATION_EDGE_TYPE_PATTERN.test(primaryHint)) {
1902
- continue;
1903
- }
1904
-
1905
- const linksResult = await store.getLinksForDoc(doc.id);
1906
- if (!linksResult.ok) {
1907
- errors.push({
1908
- relPath: doc.relPath,
1909
- code: linksResult.error.code,
1910
- message: linksResult.error.message,
1911
- });
1912
- continue;
1913
- }
1914
-
1915
- const wikiEdges: DocEdgeInput[] = [];
1916
- const markdownEdges: DocEdgeInput[] = [];
1917
- for (const link of linksResult.value) {
1918
- const targetRef =
1919
- link.linkType === "markdown"
1920
- ? `${doc.collection}/${link.targetRefNorm}`
1921
- : link.targetCollection
1922
- ? `${link.targetCollection}:${link.targetRef}`
1923
- : link.targetRefNorm;
1924
- const targetDoc = resolveRelationTarget(activeDocs, doc, targetRef);
1925
- if (!targetDoc || relationTargetIds.has(targetDoc.id)) {
1926
- continue;
1927
- }
1928
- const edge = {
1929
- targetDocId: targetDoc.id,
1930
- edgeType: primaryHint,
1931
- confidence: "configured" as const,
1932
- };
1933
- if (link.linkType === "wiki") {
1934
- wikiEdges.push(edge);
1935
- } else {
1936
- markdownEdges.push(edge);
1937
- }
1938
- }
1939
-
1940
- const wikiResult = await store.setDocEdges(doc.id, wikiEdges, "wikilink");
1941
- if (!wikiResult.ok) {
1942
- errors.push({
1943
- relPath: doc.relPath,
1944
- code: wikiResult.error.code,
1945
- message: wikiResult.error.message,
1946
- });
1947
- }
1948
- const markdownResult = await store.setDocEdges(
1949
- doc.id,
1950
- markdownEdges,
1951
- "markdown-link"
1952
- );
1953
- if (!markdownResult.ok) {
1954
- errors.push({
1955
- relPath: doc.relPath,
1956
- code: markdownResult.error.code,
1957
- message: markdownResult.error.message,
1958
- });
1959
- }
1960
- }
1961
-
1962
- return errors;
1699
+ return projectGraph(store, options, sourceDocumentIds);
1963
1700
  }
1964
1701
 
1965
1702
  /** Run an exact global typed-edge reconciliation with cooperative yields. */
@@ -1967,7 +1704,7 @@ export class SyncService {
1967
1704
  store: StorePort,
1968
1705
  options: SyncOptions = {}
1969
1706
  ): Promise<Array<{ relPath: string; code: string; message: string }>> {
1970
- return this.projectTypedEdges(store, options);
1707
+ return projectGraph(store, options, undefined, true);
1971
1708
  }
1972
1709
 
1973
1710
  /**
@@ -21,6 +21,8 @@ import { classifyHttpDestination, prepareHttpDestination } from "./http-policy";
21
21
  const REDIRECT_STATUSES = new Set([301, 302, 307, 308]);
22
22
 
23
23
  export interface HttpInferenceOptions {
24
+ /** Resolved models.inferenceTimeout; measured across the complete HTTP call. */
25
+ inferenceTimeout?: number;
24
26
  collections: readonly Collection[];
25
27
  collectionNames: readonly string[];
26
28
  authenticated?: boolean;
@@ -64,6 +66,7 @@ export const requestHttpInference = async (
64
66
  init: BunFetchRequestInit,
65
67
  options: HttpInferenceOptions
66
68
  ): Promise<Response> => {
69
+ init.signal?.throwIfAborted();
67
70
  const states = scopedStates(options);
68
71
  if (states.length === 0) {
69
72
  enforce(states, "remote", options);
@@ -118,6 +121,7 @@ export const requestHttpInference = async (
118
121
 
119
122
  let currentUrl = url;
120
123
  for (;;) {
124
+ init.signal?.throwIfAborted();
121
125
  enforce(states, prepared.value.classification.zone, options);
122
126
  const connection = await prepared.value.acquireConnection();
123
127
  if (!connection.ok) {
@@ -134,7 +138,9 @@ export const requestHttpInference = async (
134
138
  })
135
139
  );
136
140
  }
141
+ init.signal?.throwIfAborted();
137
142
  const response = await connection.value.request(init, options.fetchFn);
143
+ init.signal?.throwIfAborted();
138
144
  if (!REDIRECT_STATUSES.has(response.status)) return response;
139
145
 
140
146
  const location = response.headers.get("location");