@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
@@ -173,6 +173,7 @@ import {
173
173
  listEgressAuditReceipts as listStoredEgressAuditReceipts,
174
174
  purgeEgressAuditReceipts as purgeStoredEgressAuditReceipts,
175
175
  } from "./egress-audit-store";
176
+ import { buildEligibleDocumentQuery } from "./eligibility";
176
177
  import {
177
178
  advanceFileRefactorReceipt as advanceStoredFileRefactorReceipt,
178
179
  createFileRefactorPreparedReceipt as createStoredFileRefactorPreparedReceipt,
@@ -180,8 +181,17 @@ import {
180
181
  getLatestFileRefactorReceiptByPlanDigest as getStoredLatestFileRefactorReceiptByPlanDigest,
181
182
  } from "./file-refactor-journal-store";
182
183
  import { loadFts5Snowball } from "./fts5-snowball";
184
+ import {
185
+ applyGraphEdges,
186
+ type DesiredGraphEdge,
187
+ } from "./graph-edge-application";
183
188
  import { resolveGraphLinkTargets } from "./graph-link-resolver";
184
189
  import { queryGraphNeighborsForSeeds } from "./graph-neighbors";
190
+ import { createGraphReferenceStore } from "./graph-reference-state";
191
+ import {
192
+ snapshotLegacyTitles,
193
+ reconcileLegacyTitles,
194
+ } from "./legacy-vector-ownership";
185
195
  import {
186
196
  appendExportManifest as appendStoredTraceExportManifest,
187
197
  getBoundedTrace as getBoundedStoredTrace,
@@ -512,8 +522,17 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
512
522
  private ftsTokenizer: FtsTokenizer = "unicode61";
513
523
  private configPath = ""; // Set by CLI layer for status output
514
524
  private txCounter = 0; // Savepoint counter for unique names
515
- private readonly txContext = new AsyncLocalStorage<{ depth: number }>();
525
+ private readonly txContext = new AsyncLocalStorage<{
526
+ depth: number;
527
+ token: { revoked: boolean };
528
+ }>();
516
529
  private txTail: Promise<void> = Promise.resolve();
530
+ private activeTransaction?: {
531
+ token: { revoked: boolean };
532
+ release: () => void;
533
+ };
534
+ private shutdownFenced = false;
535
+ private shutdownDeadline?: number;
517
536
  private contextGeneration = 0;
518
537
 
519
538
  // ─────────────────────────────────────────────────────────────────────────
@@ -527,6 +546,8 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
527
546
  ): Promise<StoreResult<MigrationResult>> {
528
547
  try {
529
548
  this.db = new Database(dbPath, { create: true });
549
+ this.shutdownFenced = false;
550
+ this.shutdownDeadline = undefined;
530
551
  this.dbPath = dbPath;
531
552
  this.ftsTokenizer = ftsTokenizer;
532
553
 
@@ -597,6 +618,8 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
597
618
  ): StoreResult<void> {
598
619
  try {
599
620
  this.db = new Database(dbPath, { readonly: true, strict: true });
621
+ this.shutdownFenced = false;
622
+ this.shutdownDeadline = undefined;
600
623
  this.dbPath = dbPath;
601
624
  this.db.exec("PRAGMA query_only = ON");
602
625
  this.db.exec(
@@ -618,12 +641,44 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
618
641
  }
619
642
 
620
643
  async close(): Promise<void> {
644
+ this.fenceForShutdown();
621
645
  if (this.db) {
622
646
  this.db.close();
623
647
  this.db = null;
624
648
  }
625
649
  }
626
650
 
651
+ /** Cap subsequent SQLite lock waits to the resident settlement deadline. */
652
+ beginShutdown(deadline: number): void {
653
+ this.shutdownDeadline = deadline;
654
+ if (this.db) this.capShutdownBusyWait(this.db);
655
+ }
656
+
657
+ private capShutdownBusyWait(db: Database): void {
658
+ if (this.shutdownDeadline === undefined) return;
659
+ const remaining = Math.max(
660
+ 0,
661
+ Math.floor(this.shutdownDeadline - performance.now())
662
+ );
663
+ const current =
664
+ db.query<{ timeout: number }, []>("PRAGMA busy_timeout").get()?.timeout ??
665
+ 0;
666
+ db.exec(`PRAGMA busy_timeout = ${Math.min(current, remaining)}`);
667
+ }
668
+
669
+ /** Revoke suspended transaction callbacks before closing their connection. */
670
+ fenceForShutdown(): void {
671
+ this.shutdownFenced = true;
672
+ const transaction = this.activeTransaction;
673
+ if (!transaction) return;
674
+ // JS cannot interleave this synchronous rollback with a running callback.
675
+ // A callback suspended at await must never commit or use the store again.
676
+ transaction.token.revoked = true;
677
+ this.db?.exec("ROLLBACK");
678
+ transaction.release();
679
+ this.activeTransaction = undefined;
680
+ }
681
+
627
682
  isOpen(): boolean {
628
683
  return this.db !== null;
629
684
  }
@@ -636,27 +691,39 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
636
691
  * explicit BEGIN/COMMIT to support async callbacks.
637
692
  */
638
693
  async withTransaction<T>(fn: () => Promise<T>): Promise<StoreResult<T>> {
639
- const db = this.ensureOpen();
640
694
  const parent = this.txContext.getStore();
695
+ const connection = this.ensureOpen();
641
696
  const isOuter = parent === undefined;
642
697
  const savepoint = `sp_${++this.txCounter}`;
643
698
  const releaseWriter = isOuter
644
699
  ? await this.acquireTransactionWriter()
645
700
  : null;
701
+ const token = parent?.token ?? { revoked: false };
702
+ let db: Database | undefined;
646
703
 
647
704
  try {
705
+ const current = this.ensureOpen();
706
+ if (current !== connection)
707
+ throw new Error("Transaction connection retired before admission");
708
+ db = current;
648
709
  if (isOuter) {
649
710
  // IMMEDIATE reduces lock churn for bulk writes
650
711
  db.exec("BEGIN IMMEDIATE");
712
+ this.activeTransaction = { token, release: releaseWriter! };
651
713
  } else {
652
714
  db.exec(`SAVEPOINT ${savepoint}`);
653
715
  }
654
716
 
655
717
  const value = await this.txContext.run(
656
- { depth: (parent?.depth ?? 0) + 1 },
718
+ { depth: (parent?.depth ?? 0) + 1, token },
657
719
  fn
658
720
  );
659
721
 
722
+ if (token.revoked || this.shutdownFenced)
723
+ throw new Error("Transaction revoked by resident shutdown");
724
+
725
+ this.capShutdownBusyWait(db);
726
+
660
727
  if (isOuter) {
661
728
  db.exec("COMMIT");
662
729
  } else {
@@ -666,7 +733,9 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
666
733
  return ok(value);
667
734
  } catch (cause) {
668
735
  try {
669
- if (isOuter) {
736
+ if (!db || token.revoked) {
737
+ // Shutdown already rolled back. Never touch a replacement connection.
738
+ } else if (isOuter) {
670
739
  db.exec("ROLLBACK");
671
740
  } else {
672
741
  db.exec(`ROLLBACK TO ${savepoint}`);
@@ -680,6 +749,8 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
680
749
  cause instanceof Error ? cause.message : "Transaction failed";
681
750
  return err("TRANSACTION_FAILED", message, cause);
682
751
  } finally {
752
+ if (isOuter && this.activeTransaction?.token === token)
753
+ this.activeTransaction = undefined;
683
754
  releaseWriter?.();
684
755
  }
685
756
  }
@@ -709,10 +780,18 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
709
780
  return this.ensureOpen();
710
781
  }
711
782
 
783
+ graphReferenceStore() {
784
+ return createGraphReferenceStore(this.ensureOpen());
785
+ }
786
+
712
787
  private ensureOpen(): Database {
788
+ if (this.shutdownFenced || this.txContext.getStore()?.token.revoked) {
789
+ throw new Error("Database fenced by resident shutdown");
790
+ }
713
791
  if (!this.db) {
714
792
  throw new Error("Database not open");
715
793
  }
794
+ this.capShutdownBusyWait(this.db);
716
795
  return this.db;
717
796
  }
718
797
 
@@ -1340,6 +1419,15 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
1340
1419
  "SELECT * FROM documents WHERE collection = ? AND rel_path = ?"
1341
1420
  )
1342
1421
  .get(doc.collection, doc.relPath);
1422
+ const legacyTitles = snapshotLegacyTitles(
1423
+ db,
1424
+ !previousRow ||
1425
+ !previousRow.active ||
1426
+ previousRow.title !== (doc.title ?? null) ||
1427
+ previousRow.mirror_hash !== (doc.mirrorHash ?? null)
1428
+ ? [previousRow?.mirror_hash, doc.mirrorHash]
1429
+ : []
1430
+ );
1343
1431
 
1344
1432
  db.run(
1345
1433
  `
@@ -1436,6 +1524,18 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
1436
1524
  throw new Error("Failed to get document id after upsert");
1437
1525
  }
1438
1526
 
1527
+ reconcileLegacyTitles(db, legacyTitles);
1528
+
1529
+ // Owner bindings describe the exact current source input. Keep inactive
1530
+ // bindings for identical restoration, but invalidate changed ownership.
1531
+ if (
1532
+ previousRow &&
1533
+ (previousRow.mirror_hash !== (doc.mirrorHash ?? null) ||
1534
+ previousRow.title !== (doc.title ?? null))
1535
+ ) {
1536
+ db.run("DELETE FROM vector_owners WHERE document_id = ?", [idRow.id]);
1537
+ }
1538
+
1439
1539
  // Conversion failures deliberately drop mirror ownership. Remove the
1440
1540
  // old lexical projection in the same transaction so stale content can
1441
1541
  // never join against the newly updated document metadata.
@@ -1599,7 +1699,7 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
1599
1699
  const db = this.ensureOpen();
1600
1700
  const row = db
1601
1701
  .query<DbDocumentRow, [string]>(
1602
- "SELECT * FROM documents WHERE docid = ?"
1702
+ "SELECT * FROM documents WHERE docid = ? ORDER BY active DESC, id ASC LIMIT 1"
1603
1703
  )
1604
1704
  .get(docid);
1605
1705
 
@@ -2229,13 +2329,22 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
2229
2329
  if (activeRows.length === 0) {
2230
2330
  return 0;
2231
2331
  }
2232
- const result = db.run(
2332
+ const legacyTitles = snapshotLegacyTitles(
2333
+ db,
2334
+ activeRows.map((row) => row.mirror_hash)
2335
+ );
2336
+ db.run(
2233
2337
  `UPDATE documents SET active = 0, updated_at = datetime('now')
2234
2338
  WHERE collection = ?
2235
2339
  AND active = 1
2236
2340
  AND rel_path IN (${placeholders})`,
2237
2341
  [collection, ...uniquePaths]
2238
2342
  );
2343
+ // Bun run().changes includes trigger writes; report logical document rows.
2344
+ const changed =
2345
+ db.query<{ count: number }, []>("SELECT changes() AS count").get()
2346
+ ?.count ?? 0;
2347
+ reconcileLegacyTitles(db, legacyTitles);
2239
2348
  const observedAtMs = Date.now();
2240
2349
  for (const activeRow of activeRows) {
2241
2350
  const previous = snapshotDocumentChange(mapDocumentRow(activeRow));
@@ -2248,7 +2357,7 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
2248
2357
  observedAtMs,
2249
2358
  });
2250
2359
  }
2251
- return result.changes;
2360
+ return changed;
2252
2361
  });
2253
2362
 
2254
2363
  return ok(transaction());
@@ -2527,15 +2636,42 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
2527
2636
  const db = this.ensureOpen();
2528
2637
 
2529
2638
  const transaction = db.transaction(() => {
2530
- // Delete existing chunks for this hash
2531
- db.run("DELETE FROM content_chunks WHERE mirror_hash = ?", [
2532
- mirrorHash,
2533
- ]);
2639
+ // Retain stable rows: DELETE cascades erase valid legacy vectors even
2640
+ // when duplicate ingestion produces exactly the same embedding input.
2641
+ const nextBySequence = new Map(
2642
+ chunks.map((chunk) => [chunk.seq, chunk])
2643
+ );
2644
+ const existing = db
2645
+ .query<{ seq: number; text: string }, [string]>(
2646
+ "SELECT seq, text FROM content_chunks WHERE mirror_hash = ?"
2647
+ )
2648
+ .all(mirrorHash);
2649
+ for (const old of existing) {
2650
+ const next = nextBySequence.get(old.seq);
2651
+ if (!next || next.text !== old.text) {
2652
+ db.run(
2653
+ "DELETE FROM vector_owners WHERE mirror_hash = ? AND seq = ?",
2654
+ [mirrorHash, old.seq]
2655
+ );
2656
+ db.run(
2657
+ "DELETE FROM content_chunks WHERE mirror_hash = ? AND seq = ?",
2658
+ [mirrorHash, old.seq]
2659
+ );
2660
+ }
2661
+ }
2534
2662
 
2535
- // Insert new chunks
2536
2663
  const stmt = db.prepare(`
2537
2664
  INSERT INTO content_chunks (mirror_hash, seq, pos, text, start_line, end_line, language, token_count)
2538
2665
  VALUES (?, ?, ?, ?, ?, ?, ?, ?)
2666
+ ON CONFLICT(mirror_hash, seq) DO UPDATE SET
2667
+ pos = excluded.pos, start_line = excluded.start_line,
2668
+ end_line = excluded.end_line, language = excluded.language,
2669
+ token_count = excluded.token_count
2670
+ WHERE content_chunks.pos IS NOT excluded.pos
2671
+ OR content_chunks.start_line IS NOT excluded.start_line
2672
+ OR content_chunks.end_line IS NOT excluded.end_line
2673
+ OR content_chunks.language IS NOT excluded.language
2674
+ OR content_chunks.token_count IS NOT excluded.token_count
2539
2675
  `);
2540
2676
 
2541
2677
  for (const chunk of chunks) {
@@ -2638,6 +2774,52 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
2638
2774
  }
2639
2775
  }
2640
2776
 
2777
+ async getChunksBySequenceBatch(
2778
+ keys: { mirrorHash: string; seq: number }[]
2779
+ ): Promise<StoreResult<Map<string, ChunkRow[]>>> {
2780
+ try {
2781
+ const unique = new Map<string, { mirrorHash: string; seq: number }>();
2782
+ for (const key of keys) {
2783
+ if (key.mirrorHash)
2784
+ unique.set(JSON.stringify([key.mirrorHash, key.seq]), key);
2785
+ }
2786
+ const pairs = [...unique.values()];
2787
+ const result = new Map<string, ChunkRow[]>();
2788
+ if (pairs.length === 0) return ok(result);
2789
+ const db = this.ensureOpen();
2790
+ const batchSize = Math.floor(SQLITE_SAFE_PARAMETER_BATCH_SIZE / 2);
2791
+ for (let offset = 0; offset < pairs.length; offset += batchSize) {
2792
+ const batch = pairs.slice(offset, offset + batchSize);
2793
+ const values = batch.map(() => "(?, ?)").join(",");
2794
+ const rows = db
2795
+ .query<DbChunkRow, (string | number)[]>(`
2796
+ WITH requested(mirror_hash, seq) AS (VALUES ${values})
2797
+ SELECT c.* FROM requested r JOIN content_chunks c
2798
+ ON c.mirror_hash = r.mirror_hash AND c.seq = r.seq
2799
+ ORDER BY c.mirror_hash, c.seq
2800
+ `)
2801
+ .all(...batch.flatMap(({ mirrorHash, seq }) => [mirrorHash, seq]));
2802
+ for (const row of rows) {
2803
+ const mapped = mapChunkRow(row);
2804
+ const chunks = result.get(mapped.mirrorHash) ?? [];
2805
+ chunks.push(mapped);
2806
+ result.set(mapped.mirrorHash, chunks);
2807
+ }
2808
+ }
2809
+ for (const chunks of result.values())
2810
+ chunks.sort((a, b) => a.seq - b.seq);
2811
+ return ok(result);
2812
+ } catch (cause) {
2813
+ return err(
2814
+ "QUERY_FAILED",
2815
+ cause instanceof Error
2816
+ ? cause.message
2817
+ : "Failed to get targeted chunks batch",
2818
+ cause
2819
+ );
2820
+ }
2821
+ }
2822
+
2641
2823
  // ─────────────────────────────────────────────────────────────────────────
2642
2824
  // FTS Search
2643
2825
  // ─────────────────────────────────────────────────────────────────────────
@@ -2654,67 +2836,7 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
2654
2836
  return err("INVALID_INPUT", builtQuery.error);
2655
2837
  }
2656
2838
 
2657
- // Build tag filter conditions using EXISTS subqueries
2658
- const tagConditions: string[] = [];
2659
- const params: (string | number)[] = [];
2660
-
2661
- // tagsAny: document has at least one of these tags
2662
- if (options.tagsAny && options.tagsAny.length > 0) {
2663
- const placeholders = options.tagsAny.map(() => "?").join(",");
2664
- tagConditions.push(
2665
- `EXISTS (SELECT 1 FROM doc_tags dt WHERE dt.document_id = d.id AND dt.tag IN (${placeholders}))`
2666
- );
2667
- params.push(...options.tagsAny);
2668
- }
2669
-
2670
- // tagsAll: document has all of these tags
2671
- if (options.tagsAll && options.tagsAll.length > 0) {
2672
- for (const tag of options.tagsAll) {
2673
- tagConditions.push(
2674
- "EXISTS (SELECT 1 FROM doc_tags dt WHERE dt.document_id = d.id AND dt.tag = ?)"
2675
- );
2676
- params.push(tag);
2677
- }
2678
- }
2679
-
2680
- if (options.since) {
2681
- tagConditions.push("d.source_mtime >= ?");
2682
- params.push(options.since);
2683
- }
2684
- if (options.until) {
2685
- tagConditions.push("d.source_mtime <= ?");
2686
- params.push(options.until);
2687
- }
2688
- if (options.categories && options.categories.length > 0) {
2689
- const placeholders = options.categories.map(() => "?").join(",");
2690
- tagConditions.push(
2691
- `(d.content_type IN (${placeholders}) OR EXISTS (SELECT 1 FROM json_each(COALESCE(d.categories, '[]')) jc WHERE jc.value IN (${placeholders})))`
2692
- );
2693
- params.push(...options.categories, ...options.categories);
2694
- }
2695
- if (options.author) {
2696
- tagConditions.push("LOWER(COALESCE(d.author, '')) LIKE ?");
2697
- params.push(`%${options.author.toLowerCase()}%`);
2698
- }
2699
-
2700
- // Scope and supersession filters run inside the candidate subquery so
2701
- // they narrow the corpus before the FTS LIMIT (never a post-filter).
2702
- const innerConditions: string[] = [];
2703
- const innerParams: string[] = [];
2704
- if (options.memoryScopesAny && options.memoryScopesAny.length > 0) {
2705
- const placeholders = options.memoryScopesAny.map(() => "?").join(",");
2706
- innerConditions.push(
2707
- `AND EXISTS (SELECT 1 FROM doc_memory_scopes ms WHERE ms.document_id = documents.id AND ms.scope IN (${placeholders}))`
2708
- );
2709
- innerParams.push(...options.memoryScopesAny);
2710
- }
2711
- if (options.excludeSuperseded) {
2712
- innerConditions.push(SUPERSEDED_EXCLUSION_SQL("documents.id"));
2713
- }
2714
-
2715
- const hasOuterFilters = tagConditions.length > 0;
2716
- const ftsLimit = hasOuterFilters ? limit * 10 : limit;
2717
- params.push(limit);
2839
+ const eligible = buildEligibleDocumentQuery(options, db);
2718
2840
 
2719
2841
  // Document-level FTS search using an FTS-first CTE to keep collection and
2720
2842
  // metadata filters from degrading the query plan into a broad scan.
@@ -2731,23 +2853,16 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
2731
2853
  ) as score
2732
2854
  FROM documents_fts
2733
2855
  WHERE documents_fts MATCH ?
2734
- AND rowid IN (
2735
- SELECT id FROM documents
2736
- WHERE active = 1
2737
- ${options.collection ? "AND collection = ?" : ""}
2738
- ${
2739
- options.relPathPrefix !== undefined
2740
- ? "AND (COALESCE(NULLIF(record_source_path, ''), rel_path) = ? OR substr(COALESCE(NULLIF(record_source_path, ''), rel_path), 1, length(?) + 1) = ? || '/')"
2741
- : ""
2742
- }
2743
- ${innerConditions.join("\n ")}
2856
+ AND EXISTS (
2857
+ SELECT 1 FROM (${eligible.sql}) eligible_docs
2858
+ WHERE eligible_docs.id = documents_fts.rowid
2744
2859
  )
2745
2860
  ORDER BY score
2746
2861
  LIMIT ?
2747
2862
  )
2748
2863
  SELECT
2749
2864
  d.mirror_hash,
2750
- 0 as seq,
2865
+ ${options.chunkLanguage ? "(SELECT min(lc.seq) FROM content_chunks lc WHERE lc.mirror_hash = d.mirror_hash AND lc.language = ?) as seq," : "0 as seq,"}
2751
2866
  fm.score as score,
2752
2867
  ${options.snippet ? "fm.snippet as snippet," : ""}
2753
2868
  d.docid,
@@ -2775,7 +2890,6 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
2775
2890
  FROM fts_matches fm
2776
2891
  JOIN documents d ON d.id = fm.rowid AND d.active = 1
2777
2892
  WHERE 1 = 1
2778
- ${tagConditions.length > 0 ? `AND ${tagConditions.join(" AND ")}` : ""}
2779
2893
  ORDER BY fm.score
2780
2894
  LIMIT ?
2781
2895
  `;
@@ -2811,17 +2925,10 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
2811
2925
 
2812
2926
  const queryParams = [
2813
2927
  builtQuery.query,
2814
- ...(options.collection ? [options.collection] : []),
2815
- ...(options.relPathPrefix !== undefined
2816
- ? [
2817
- options.relPathPrefix,
2818
- options.relPathPrefix,
2819
- options.relPathPrefix,
2820
- ]
2821
- : []),
2822
- ...innerParams,
2823
- ftsLimit,
2824
- ...params,
2928
+ ...eligible.params,
2929
+ limit,
2930
+ ...(options.chunkLanguage ? [options.chunkLanguage] : []),
2931
+ limit,
2825
2932
  ];
2826
2933
  const rows = db
2827
2934
  .query<FtsRow, (string | number)[]>(sql)
@@ -3354,6 +3461,17 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
3354
3461
  const db = this.ensureOpen();
3355
3462
 
3356
3463
  const transaction = db.transaction(() => {
3464
+ // Link edits without a changed source identity cannot be located from
3465
+ // the prior document inventory, especially alongside unrelated changes.
3466
+ // Persist full-recovery authority in the same transaction as those edits.
3467
+ db.run(
3468
+ `UPDATE graph_projection_state SET dirty = 1, in_progress = 1
3469
+ WHERE id = 1 AND EXISTS (
3470
+ SELECT 1 FROM graph_reference_documents r JOIN documents d ON d.id = r.document_id
3471
+ WHERE d.id = ? AND r.source_hash IS d.source_hash AND r.mirror_hash IS d.mirror_hash
3472
+ )`,
3473
+ [documentId]
3474
+ );
3357
3475
  // Delete existing links from this source
3358
3476
  db.run("DELETE FROM doc_links WHERE source_doc_id = ? AND source = ?", [
3359
3477
  documentId,
@@ -4253,36 +4371,18 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
4253
4371
  try {
4254
4372
  const db = this.ensureOpen();
4255
4373
 
4256
- const transaction = db.transaction(() => {
4257
- db.run("DELETE FROM doc_edges WHERE src_doc_id = ? AND source = ?", [
4258
- documentId,
4374
+ applyGraphEdges(
4375
+ db,
4376
+ edges.map((edge) => ({
4377
+ sourceId: documentId,
4378
+ targetId: edge.targetDocId,
4379
+ edgeType: normalizeDocEdgeType(edge.edgeType),
4380
+ confidence: edge.confidence,
4259
4381
  source,
4260
- ]);
4261
-
4262
- if (edges.length === 0) {
4263
- return;
4264
- }
4265
-
4266
- const stmt = db.prepare(`
4267
- INSERT INTO doc_edges (
4268
- src_doc_id, dst_doc_id, edge_type, confidence, source
4269
- ) VALUES (?, ?, ?, ?, ?)
4270
- ON CONFLICT(src_doc_id, dst_doc_id, edge_type, source) DO UPDATE SET
4271
- confidence = excluded.confidence
4272
- `);
4273
-
4274
- for (const edge of edges) {
4275
- stmt.run(
4276
- documentId,
4277
- edge.targetDocId,
4278
- normalizeDocEdgeType(edge.edgeType),
4279
- edge.confidence,
4280
- source
4281
- );
4282
- }
4283
- });
4284
-
4285
- transaction();
4382
+ })),
4383
+ [source],
4384
+ [documentId]
4385
+ );
4286
4386
  return ok(undefined);
4287
4387
  } catch (cause) {
4288
4388
  return err(
@@ -4947,74 +5047,42 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
4947
5047
  if (sourceIds?.length === 0) {
4948
5048
  return ok({ inserted: 0 });
4949
5049
  }
4950
- const sourcePlaceholders = sourceIds
4951
- ? sourceIds.map(() => "?").join(", ")
4952
- : "";
4953
5050
  const sourceFilter = sourceIds
4954
- ? `AND src.id IN (${sourcePlaceholders})`
5051
+ ? "AND src.id IN (SELECT value FROM json_each(?))"
4955
5052
  : "";
4956
- let inserted = 0;
4957
-
4958
- const transaction = db.transaction(() => {
4959
- db.run(
4960
- `DELETE FROM doc_edges
4961
- WHERE source IN (?, ?)
4962
- ${sourceIds ? `AND src_doc_id IN (${sourcePlaceholders})` : ""}`,
4963
- ["wikilink", "markdown-link", ...(sourceIds ?? [])]
4964
- );
4965
-
4966
- const insertWiki = db.run(
4967
- `
4968
- INSERT OR IGNORE INTO doc_edges (
4969
- src_doc_id, dst_doc_id, edge_type, confidence, source
4970
- )
4971
- SELECT
4972
- src.id,
4973
- tgt.id,
4974
- 'mentions',
4975
- 'parsed',
4976
- 'wikilink'
4977
- FROM documents src
4978
- JOIN doc_links dl ON dl.source_doc_id = src.id
5053
+ const params = sourceIds ? [JSON.stringify(sourceIds)] : [];
5054
+ const inserted = db.transaction(() => {
5055
+ const wiki = db
5056
+ .query<DesiredGraphEdge, string[]>(`
5057
+ SELECT DISTINCT src.id AS sourceId, tgt.id AS targetId,
5058
+ 'mentions' AS edgeType, 'parsed' AS confidence, 'wikilink' AS source
5059
+ FROM documents src JOIN doc_links dl ON dl.source_doc_id = src.id
4979
5060
  JOIN documents tgt ON tgt.id = (${buildWikiBestMatchSubquery(
4980
5061
  "COALESCE(dl.target_collection, src.collection)",
4981
5062
  "dl.target_ref_norm"
4982
5063
  )})
4983
- WHERE src.active = 1
4984
- AND tgt.active = 1
4985
- AND dl.link_type = 'wiki'
4986
- ${sourceFilter}
4987
- `,
4988
- sourceIds ?? []
4989
- );
4990
-
4991
- const insertMarkdown = db.run(
4992
- `
4993
- INSERT OR IGNORE INTO doc_edges (
4994
- src_doc_id, dst_doc_id, edge_type, confidence, source
4995
- )
4996
- SELECT
4997
- src.id,
4998
- tgt.id,
4999
- 'related_to',
5000
- 'parsed',
5001
- 'markdown-link'
5002
- FROM documents src
5003
- JOIN doc_links dl ON dl.source_doc_id = src.id
5064
+ WHERE src.active = 1 AND tgt.active = 1 AND dl.link_type = 'wiki'
5065
+ ${sourceFilter}
5066
+ `)
5067
+ .all(...params);
5068
+ const markdown = db
5069
+ .query<DesiredGraphEdge, string[]>(`
5070
+ SELECT DISTINCT src.id AS sourceId, tgt.id AS targetId,
5071
+ 'related_to' AS edgeType, 'parsed' AS confidence, 'markdown-link' AS source
5072
+ FROM documents src JOIN doc_links dl ON dl.source_doc_id = src.id
5004
5073
  JOIN documents tgt ON tgt.active = 1
5005
5074
  AND tgt.collection = COALESCE(dl.target_collection, src.collection)
5006
5075
  AND tgt.rel_path = dl.target_ref_norm
5007
- WHERE src.active = 1
5008
- AND dl.link_type = 'markdown'
5009
- ${sourceFilter}
5010
- `,
5011
- sourceIds ?? []
5076
+ WHERE src.active = 1 AND dl.link_type = 'markdown' ${sourceFilter}
5077
+ `)
5078
+ .all(...params);
5079
+ return applyGraphEdges(
5080
+ db,
5081
+ [...wiki, ...markdown],
5082
+ ["wikilink", "markdown-link"],
5083
+ sourceIds
5012
5084
  );
5013
-
5014
- inserted = insertWiki.changes + insertMarkdown.changes;
5015
- });
5016
-
5017
- transaction();
5085
+ })();
5018
5086
  return ok({ inserted });
5019
5087
  } catch (cause) {
5020
5088
  return err(