@gmickel/gno 1.27.1 → 1.29.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 (195) hide show
  1. package/README.md +46 -20
  2. package/assets/skill/SKILL.md +59 -1
  3. package/browser-extension/artifacts/gno-browser-clipper-v1.29.0.zip +0 -0
  4. package/browser-extension/artifacts/gno-browser-clipper-v1.29.0.zip.sha256 +1 -0
  5. package/browser-extension/dist/chunk-b2zm0jjd.js +50 -0
  6. package/browser-extension/dist/manifest.json +1 -1
  7. package/browser-extension/dist/preview.html +1 -1
  8. package/browser-extension/dist/service-worker.js +6 -6
  9. package/package.json +2 -2
  10. package/spec/cli.md +82 -5
  11. package/spec/db/schema.sql +31 -0
  12. package/spec/mcp.md +71 -0
  13. package/spec/output-schemas/ask.schema.json +156 -1
  14. package/spec/output-schemas/browser-clip-preview.schema.json +30 -0
  15. package/spec/output-schemas/collection-egress-check.schema.json +91 -0
  16. package/spec/output-schemas/collection-egress-policy-set.schema.json +56 -0
  17. package/spec/output-schemas/collection-egress-policy.schema.json +41 -0
  18. package/spec/output-schemas/context-capsule-v1.schema.json +183 -3
  19. package/spec/output-schemas/context-capsule-verification.schema.json +1 -1
  20. package/spec/output-schemas/egress-audit-management.schema.json +88 -0
  21. package/spec/output-schemas/get.schema.json +94 -0
  22. package/spec/output-schemas/mcp-http-error.schema.json +113 -2
  23. package/spec/output-schemas/mcp-job-status.schema.json +28 -0
  24. package/spec/output-schemas/multi-get.schema.json +128 -0
  25. package/spec/output-schemas/publish-artifact.schema.json +32 -0
  26. package/spec/output-schemas/record-import.schema.json +223 -0
  27. package/spec/output-schemas/retrieval-trace-common.schema.json +31 -0
  28. package/spec/output-schemas/retrieval-trace-export.schema.json +3 -0
  29. package/spec/output-schemas/retrieval-trace-qrels.schema.json +3 -0
  30. package/spec/output-schemas/retrieval-trace-summary.schema.json +4 -0
  31. package/spec/output-schemas/search-result.schema.json +94 -0
  32. package/spec/output-schemas/search-results.schema.json +155 -0
  33. package/spec/project-profile.schema.json +66 -0
  34. package/src/app/context-format.ts +1 -1
  35. package/src/app/context-runtime-contract.ts +16 -7
  36. package/src/cli/commands/ask.ts +7 -0
  37. package/src/cli/commands/collection/index.ts +5 -0
  38. package/src/cli/commands/collection/policy.ts +126 -0
  39. package/src/cli/commands/context-build.ts +5 -0
  40. package/src/cli/commands/daemon.ts +45 -3
  41. package/src/cli/commands/egress-audit.ts +68 -0
  42. package/src/cli/commands/embed.ts +2 -0
  43. package/src/cli/commands/get.ts +12 -2
  44. package/src/cli/commands/index-cmd.ts +13 -0
  45. package/src/cli/commands/multi-get.ts +9 -2
  46. package/src/cli/commands/query.ts +12 -0
  47. package/src/cli/commands/replay.ts +13 -3
  48. package/src/cli/commands/shared.ts +28 -0
  49. package/src/cli/commands/update.ts +5 -0
  50. package/src/cli/commands/vsearch.ts +3 -1
  51. package/src/cli/program.ts +127 -0
  52. package/src/config/index.ts +9 -0
  53. package/src/config/project-profile.ts +2 -0
  54. package/src/config/types.ts +66 -0
  55. package/src/converters/adapters/browser-export/adapter.ts +199 -0
  56. package/src/converters/adapters/browser-export/formats.ts +358 -0
  57. package/src/converters/adapters/email/adapter.ts +429 -0
  58. package/src/converters/adapters/email/html.ts +162 -0
  59. package/src/converters/adapters/email/mime.ts +454 -0
  60. package/src/converters/adapters/email/parameters.ts +77 -0
  61. package/src/converters/adapters/ical/adapter.ts +475 -0
  62. package/src/converters/adapters/ical/recurrence.ts +186 -0
  63. package/src/converters/adapters/jsonl/adapter.ts +238 -0
  64. package/src/converters/adapters/jsonl/config.ts +105 -0
  65. package/src/converters/adapters/shared/html-text.ts +165 -0
  66. package/src/converters/adapters/shared/record-utils.ts +79 -0
  67. package/src/converters/adapters/shared/utf8-lines.ts +141 -0
  68. package/src/converters/adapters/transcript/adapter.ts +295 -0
  69. package/src/converters/adapters/transcript/json.ts +184 -0
  70. package/src/converters/adapters/transcript/model.ts +171 -0
  71. package/src/converters/adapters/transcript/text.ts +58 -0
  72. package/src/converters/adapters/transcript/timed.ts +152 -0
  73. package/src/converters/index.ts +11 -1
  74. package/src/converters/mime.ts +8 -0
  75. package/src/converters/pipeline.ts +15 -1
  76. package/src/converters/registry.ts +37 -1
  77. package/src/converters/types.ts +136 -0
  78. package/src/core/browser-clip.ts +16 -1
  79. package/src/core/collection-egress-policy-projection.ts +28 -0
  80. package/src/core/collection-egress-policy-service.ts +443 -0
  81. package/src/core/collection-egress-policy-validation.ts +242 -0
  82. package/src/core/config-mutation.ts +31 -19
  83. package/src/core/context-capsule-schema.ts +198 -1
  84. package/src/core/context-capsule-validation.ts +4 -4
  85. package/src/core/context-capsule-verification.ts +5 -1
  86. package/src/core/context-capsule.ts +177 -112
  87. package/src/core/context-evidence.ts +93 -15
  88. package/src/core/destination-classifier.ts +402 -0
  89. package/src/core/document-capabilities.ts +12 -0
  90. package/src/core/egress-audit.ts +239 -0
  91. package/src/core/egress-authorization.ts +50 -0
  92. package/src/core/egress-enforcement.ts +264 -0
  93. package/src/core/egress-policy.ts +440 -0
  94. package/src/core/egress-provenance.ts +336 -0
  95. package/src/core/job-manager.ts +37 -0
  96. package/src/core/network-boundary-inventory.ts +261 -0
  97. package/src/core/project-profile-apply-state.ts +5 -0
  98. package/src/core/project-profile.ts +4 -0
  99. package/src/core/record-metadata.ts +49 -0
  100. package/src/core/retrieval-qrels.ts +6 -0
  101. package/src/core/retrieval-replay.ts +18 -4
  102. package/src/core/retrieval-trace-export.ts +23 -4
  103. package/src/core/retrieval-trace-management-helpers.ts +1 -0
  104. package/src/core/retrieval-trace-management-types.ts +3 -0
  105. package/src/core/retrieval-trace-management.ts +9 -1
  106. package/src/core/retrieval-trace-session.ts +94 -0
  107. package/src/core/retrieval-trace.ts +22 -0
  108. package/src/ingestion/record-adapter-canonical.ts +433 -0
  109. package/src/ingestion/record-adapter.ts +448 -0
  110. package/src/ingestion/record-container.ts +704 -0
  111. package/src/ingestion/record-path.ts +20 -0
  112. package/src/ingestion/record-sync.ts +70 -0
  113. package/src/ingestion/sync.ts +243 -36
  114. package/src/ingestion/types.ts +71 -1
  115. package/src/ingestion/walker.ts +31 -11
  116. package/src/llm/errors.ts +10 -0
  117. package/src/llm/http-inference.ts +175 -0
  118. package/src/llm/http-policy.ts +537 -0
  119. package/src/llm/httpEmbedding.ts +47 -28
  120. package/src/llm/httpGeneration.ts +31 -18
  121. package/src/llm/httpRerank.ts +30 -18
  122. package/src/llm/index.ts +1 -0
  123. package/src/llm/nodeLlamaCpp/adapter.ts +55 -20
  124. package/src/llm/pinned-http-connection.ts +177 -0
  125. package/src/mcp/context.ts +53 -1
  126. package/src/mcp/http-egress.ts +220 -0
  127. package/src/mcp/http-security.ts +32 -21
  128. package/src/mcp/http-session.ts +13 -2
  129. package/src/mcp/http-transport.ts +96 -6
  130. package/src/mcp/sync-egress.ts +24 -0
  131. package/src/mcp/tools/add-collection.ts +4 -0
  132. package/src/mcp/tools/ask.ts +1 -1
  133. package/src/mcp/tools/context.ts +9 -2
  134. package/src/mcp/tools/egress.ts +247 -0
  135. package/src/mcp/tools/embed.ts +1 -0
  136. package/src/mcp/tools/get.ts +10 -2
  137. package/src/mcp/tools/index-cmd.ts +7 -0
  138. package/src/mcp/tools/index.ts +98 -0
  139. package/src/mcp/tools/multi-get.ts +9 -2
  140. package/src/mcp/tools/query.ts +10 -0
  141. package/src/mcp/tools/remove-collection.ts +4 -0
  142. package/src/mcp/tools/sync.ts +6 -0
  143. package/src/mcp/tools/trace.ts +7 -1
  144. package/src/mcp/tools/vsearch.ts +1 -0
  145. package/src/mcp/tools/workspace-write.ts +2 -0
  146. package/src/pipeline/egress-lineage.ts +124 -0
  147. package/src/pipeline/filters.ts +1 -1
  148. package/src/pipeline/graph-retrieval.ts +7 -4
  149. package/src/pipeline/hybrid.ts +21 -4
  150. package/src/pipeline/result-context.ts +12 -4
  151. package/src/pipeline/search.ts +23 -4
  152. package/src/pipeline/types.ts +6 -0
  153. package/src/pipeline/vsearch.ts +67 -28
  154. package/src/publish/artifact.ts +31 -3
  155. package/src/publish/export-service.ts +25 -0
  156. package/src/sdk/client.ts +119 -2
  157. package/src/sdk/documents.ts +13 -5
  158. package/src/sdk/embed.ts +1 -0
  159. package/src/sdk/types.ts +33 -0
  160. package/src/serve/browse-tree.ts +4 -2
  161. package/src/serve/clipper-capture.ts +5 -1
  162. package/src/serve/closed-json.ts +61 -0
  163. package/src/serve/config-sync.ts +4 -1
  164. package/src/serve/context.ts +1 -0
  165. package/src/serve/doc-events.ts +110 -39
  166. package/src/serve/public/components/CollectionEgressCheckPanel.tsx +411 -0
  167. package/src/serve/public/components/CollectionEgressPolicyEditor.tsx +78 -0
  168. package/src/serve/public/components/CollectionModelDialog.tsx +93 -9
  169. package/src/serve/public/components/EgressAuditPanel.tsx +278 -0
  170. package/src/serve/public/globals.built.css +1 -1
  171. package/src/serve/public/pages/Collections.tsx +3 -0
  172. package/src/serve/resident-request.ts +80 -3
  173. package/src/serve/resident-runtime.ts +63 -1
  174. package/src/serve/routes/api.ts +237 -65
  175. package/src/serve/routes/clipper.ts +64 -1
  176. package/src/serve/routes/mcp.ts +6 -0
  177. package/src/serve/routes/traces.ts +27 -4
  178. package/src/serve/server.ts +118 -7
  179. package/src/store/migrations/022-record-export-lineage.ts +42 -0
  180. package/src/store/migrations/023-collection-egress-policy.ts +61 -0
  181. package/src/store/migrations/024-egress-derived-lineage.ts +121 -0
  182. package/src/store/migrations/025-collection-egress-policy-revision.ts +40 -0
  183. package/src/store/migrations/index.ts +8 -0
  184. package/src/store/retrieval-trace-codec.ts +15 -0
  185. package/src/store/sqlite/adapter.ts +284 -14
  186. package/src/store/sqlite/change-journal-store.ts +41 -2
  187. package/src/store/sqlite/egress-audit-store.ts +485 -0
  188. package/src/store/sqlite/retrieval-trace-management-store.ts +47 -6
  189. package/src/store/sqlite/retrieval-trace-retention.ts +2 -0
  190. package/src/store/sqlite/retrieval-trace-rows.ts +8 -1
  191. package/src/store/sqlite/retrieval-trace-store.ts +104 -5
  192. package/src/store/types.ts +166 -1
  193. package/browser-extension/artifacts/gno-browser-clipper-v1.27.1.zip +0 -0
  194. package/browser-extension/artifacts/gno-browser-clipper-v1.27.1.zip.sha256 +0 -1
  195. package/browser-extension/dist/chunk-vn5f663b.js +0 -50
@@ -15,7 +15,7 @@ import { AsyncLocalStorage } from "node:async_hooks";
15
15
  // node:path basename: no Bun path utilities.
16
16
  import { basename } from "node:path";
17
17
 
18
- import type { Collection, Context, FtsTokenizer } from "../../config/types";
18
+ import type { EgressLineage } from "../../core/egress-provenance";
19
19
  import type {
20
20
  ActivationIndexDocument,
21
21
  ActivationIndexIdentity,
@@ -44,6 +44,15 @@ import type {
44
44
  DocumentInput,
45
45
  DocumentRow,
46
46
  EmbeddingCleanupStats,
47
+ EgressAuditCursor,
48
+ EgressAuditDeleteResult,
49
+ EgressAuditPage,
50
+ EgressAuditPurgeResult,
51
+ EgressAuditReceiptInput,
52
+ EgressAuditReceiptRow,
53
+ EgressAuditRetentionPolicy,
54
+ EgressAuditRetentionResult,
55
+ EgressAuditStatusResult,
47
56
  FtsResult,
48
57
  FtsSearchOptions,
49
58
  GetGraphOptions,
@@ -93,6 +102,14 @@ import type {
93
102
  import type { SqliteDbProvider } from "./types";
94
103
 
95
104
  import { buildUri, deriveDocid, stripUriIndex } from "../../app/constants";
105
+ import {
106
+ type Collection,
107
+ type Context,
108
+ type EgressPolicy,
109
+ type EgressPolicySource,
110
+ type FtsTokenizer,
111
+ resolveConfiguredEgressPolicy,
112
+ } from "../../config/types";
96
113
  import { analyzeGraphCommunities } from "../../core/graph-analysis";
97
114
  import {
98
115
  buildWikiBestMatchSubquery,
@@ -127,6 +144,16 @@ import {
127
144
  purgeDocumentChanges as purgeStoredDocumentChanges,
128
145
  snapshotDocumentChange,
129
146
  } from "./change-journal-store";
147
+ import {
148
+ appendEgressAuditReceipt as appendStoredEgressAuditReceipt,
149
+ appendEgressAuditReceiptWithRetention as appendStoredEgressAuditReceiptWithRetention,
150
+ deleteEgressAuditReceipt as deleteStoredEgressAuditReceipt,
151
+ enforceEgressAuditRetention as enforceStoredEgressAuditRetention,
152
+ getEgressAuditReceipt as getStoredEgressAuditReceipt,
153
+ getEgressAuditStatus as getStoredEgressAuditStatus,
154
+ listEgressAuditReceipts as listStoredEgressAuditReceipts,
155
+ purgeEgressAuditReceipts as purgeStoredEgressAuditReceipts,
156
+ } from "./egress-audit-store";
130
157
  import { loadFts5Snowball } from "./fts5-snowball";
131
158
  import {
132
159
  appendExportManifest as appendStoredTraceExportManifest,
@@ -149,6 +176,7 @@ import {
149
176
  finalizeTrace as finalizeStoredTrace,
150
177
  getTrace as getStoredTrace,
151
178
  listTraces as listStoredTraces,
179
+ mergeTraceEgressLineage as mergeStoredTraceEgressLineage,
152
180
  } from "./retrieval-trace-store";
153
181
 
154
182
  // ─────────────────────────────────────────────────────────────────────────────
@@ -589,8 +617,12 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
589
617
 
590
618
  // Upsert collections
591
619
  const stmt = db.prepare(`
592
- INSERT INTO collections (name, path, pattern, include, exclude, update_cmd, language_hint, synced_at)
593
- VALUES (?, ?, ?, ?, ?, ?, ?, datetime('now'))
620
+ INSERT INTO collections (
621
+ name, path, pattern, include, exclude, update_cmd, language_hint,
622
+ egress_policy, egress_policy_source, egress_policy_revision,
623
+ synced_at
624
+ )
625
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))
594
626
  ON CONFLICT(name) DO UPDATE SET
595
627
  path = excluded.path,
596
628
  pattern = excluded.pattern,
@@ -598,10 +630,29 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
598
630
  exclude = excluded.exclude,
599
631
  update_cmd = excluded.update_cmd,
600
632
  language_hint = excluded.language_hint,
633
+ egress_policy = CASE
634
+ WHEN excluded.egress_policy_source = 'config_default'
635
+ AND collections.egress_policy_source = 'legacy_default'
636
+ THEN collections.egress_policy
637
+ ELSE excluded.egress_policy
638
+ END,
639
+ egress_policy_source = CASE
640
+ WHEN excluded.egress_policy_source = 'config_default'
641
+ AND collections.egress_policy_source = 'legacy_default'
642
+ THEN collections.egress_policy_source
643
+ ELSE excluded.egress_policy_source
644
+ END,
645
+ egress_policy_revision = CASE
646
+ WHEN excluded.egress_policy_source = 'config_default'
647
+ AND collections.egress_policy_source = 'legacy_default'
648
+ THEN collections.egress_policy_revision
649
+ ELSE excluded.egress_policy_revision
650
+ END,
601
651
  synced_at = datetime('now')
602
652
  `);
603
653
 
604
654
  for (const c of collections) {
655
+ const egress = resolveConfiguredEgressPolicy(c);
605
656
  stmt.run(
606
657
  c.name,
607
658
  c.path,
@@ -609,7 +660,10 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
609
660
  c.include.length > 0 ? JSON.stringify(c.include) : null,
610
661
  c.exclude.length > 0 ? JSON.stringify(c.exclude) : null,
611
662
  c.updateCmd ?? null,
612
- c.languageHint ?? null
663
+ c.languageHint ?? null,
664
+ egress.policy,
665
+ egress.source,
666
+ c.egressPolicyRevision ?? 0
613
667
  );
614
668
  }
615
669
  });
@@ -631,8 +685,12 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
631
685
  try {
632
686
  const db = this.ensureOpen();
633
687
  const stmt = db.prepare(`
634
- INSERT INTO collections (name, path, pattern, include, exclude, update_cmd, language_hint, synced_at)
635
- VALUES (?, ?, ?, ?, ?, ?, ?, datetime('now'))
688
+ INSERT INTO collections (
689
+ name, path, pattern, include, exclude, update_cmd, language_hint,
690
+ egress_policy, egress_policy_source, egress_policy_revision,
691
+ synced_at
692
+ )
693
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))
636
694
  ON CONFLICT(name) DO UPDATE SET
637
695
  path = excluded.path,
638
696
  pattern = excluded.pattern,
@@ -640,6 +698,24 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
640
698
  exclude = excluded.exclude,
641
699
  update_cmd = excluded.update_cmd,
642
700
  language_hint = excluded.language_hint,
701
+ egress_policy = CASE
702
+ WHEN excluded.egress_policy_source = 'config_default'
703
+ AND collections.egress_policy_source = 'legacy_default'
704
+ THEN collections.egress_policy
705
+ ELSE excluded.egress_policy
706
+ END,
707
+ egress_policy_source = CASE
708
+ WHEN excluded.egress_policy_source = 'config_default'
709
+ AND collections.egress_policy_source = 'legacy_default'
710
+ THEN collections.egress_policy_source
711
+ ELSE excluded.egress_policy_source
712
+ END,
713
+ egress_policy_revision = CASE
714
+ WHEN excluded.egress_policy_source = 'config_default'
715
+ AND collections.egress_policy_source = 'legacy_default'
716
+ THEN collections.egress_policy_revision
717
+ ELSE excluded.egress_policy_revision
718
+ END,
643
719
  synced_at = datetime('now')
644
720
  WHERE collections.path IS NOT excluded.path
645
721
  OR collections.pattern IS NOT excluded.pattern
@@ -647,9 +723,21 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
647
723
  OR collections.exclude IS NOT excluded.exclude
648
724
  OR collections.update_cmd IS NOT excluded.update_cmd
649
725
  OR collections.language_hint IS NOT excluded.language_hint
726
+ OR (
727
+ NOT (
728
+ excluded.egress_policy_source = 'config_default'
729
+ AND collections.egress_policy_source = 'legacy_default'
730
+ )
731
+ AND (
732
+ collections.egress_policy IS NOT excluded.egress_policy
733
+ OR collections.egress_policy_source IS NOT excluded.egress_policy_source
734
+ OR collections.egress_policy_revision IS NOT excluded.egress_policy_revision
735
+ )
736
+ )
650
737
  `);
651
738
  const transaction = db.transaction(() => {
652
739
  for (const collection of collections) {
740
+ const egress = resolveConfiguredEgressPolicy(collection);
653
741
  stmt.run(
654
742
  collection.name,
655
743
  collection.path,
@@ -661,7 +749,10 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
661
749
  ? JSON.stringify(collection.exclude)
662
750
  : null,
663
751
  collection.updateCmd ?? null,
664
- collection.languageHint ?? null
752
+ collection.languageHint ?? null,
753
+ egress.policy,
754
+ egress.source,
755
+ collection.egressPolicyRevision ?? 0
665
756
  );
666
757
  }
667
758
  });
@@ -943,6 +1034,13 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
943
1034
  return createStoredTrace(this.ensureOpen(), input);
944
1035
  }
945
1036
 
1037
+ async mergeRetrievalTraceEgressLineage(
1038
+ traceId: string,
1039
+ lineage: EgressLineage
1040
+ ): Promise<StoreResult<RetrievalTraceAppendResult>> {
1041
+ return mergeStoredTraceEgressLineage(this.ensureOpen(), traceId, lineage);
1042
+ }
1043
+
946
1044
  async getRetrievalTrace(
947
1045
  traceId: string
948
1046
  ): Promise<StoreResult<RetrievalTraceBundle | null>> {
@@ -1038,6 +1136,61 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
1038
1136
  return enforceStoredTraceRetention(this.ensureOpen(), policy, nowMs);
1039
1137
  }
1040
1138
 
1139
+ async appendEgressAuditReceipt(
1140
+ receipt: EgressAuditReceiptInput
1141
+ ): Promise<StoreResult<RetrievalTraceAppendResult>> {
1142
+ return appendStoredEgressAuditReceipt(this.ensureOpen(), receipt);
1143
+ }
1144
+
1145
+ async appendEgressAuditReceiptWithRetention(
1146
+ receipt: EgressAuditReceiptInput,
1147
+ policy: EgressAuditRetentionPolicy,
1148
+ nowMs: number
1149
+ ): Promise<StoreResult<RetrievalTraceAppendResult>> {
1150
+ return appendStoredEgressAuditReceiptWithRetention(
1151
+ this.ensureOpen(),
1152
+ receipt,
1153
+ policy,
1154
+ nowMs
1155
+ );
1156
+ }
1157
+
1158
+ async listEgressAuditReceipts(
1159
+ limit: number,
1160
+ cursor?: EgressAuditCursor
1161
+ ): Promise<StoreResult<EgressAuditPage>> {
1162
+ return listStoredEgressAuditReceipts(this.ensureOpen(), limit, cursor);
1163
+ }
1164
+
1165
+ async getEgressAuditReceipt(
1166
+ auditId: string
1167
+ ): Promise<StoreResult<EgressAuditReceiptRow | null>> {
1168
+ return getStoredEgressAuditReceipt(this.ensureOpen(), auditId);
1169
+ }
1170
+
1171
+ async deleteEgressAuditReceipt(
1172
+ auditId: string
1173
+ ): Promise<StoreResult<EgressAuditDeleteResult>> {
1174
+ return deleteStoredEgressAuditReceipt(this.ensureOpen(), auditId);
1175
+ }
1176
+
1177
+ async getEgressAuditStatus(): Promise<StoreResult<EgressAuditStatusResult>> {
1178
+ return getStoredEgressAuditStatus(this.ensureOpen());
1179
+ }
1180
+
1181
+ async enforceEgressAuditRetention(
1182
+ policy: EgressAuditRetentionPolicy,
1183
+ nowMs: number
1184
+ ): Promise<StoreResult<EgressAuditRetentionResult>> {
1185
+ return enforceStoredEgressAuditRetention(this.ensureOpen(), policy, nowMs);
1186
+ }
1187
+
1188
+ async purgeEgressAuditReceipts(): Promise<
1189
+ StoreResult<EgressAuditPurgeResult>
1190
+ > {
1191
+ return purgeStoredEgressAuditReceipts(this.ensureOpen());
1192
+ }
1193
+
1041
1194
  getContextGeneration(): number {
1042
1195
  return this.contextGeneration;
1043
1196
  }
@@ -1067,10 +1220,13 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
1067
1220
  collection, rel_path, source_hash, source_mime, source_ext,
1068
1221
  source_size, source_mtime, source_ctime, docid, uri, title, mirror_hash,
1069
1222
  converter_id, converter_version, language_hint, content_type, categories,
1070
- content_type_source, author, frontmatter_date, date_fields, content_type_rules_fingerprint,
1223
+ content_type_source, author, frontmatter_date, date_fields,
1224
+ record_key, record_source_path, record_source_locator, record_metadata, record_anchors,
1225
+ record_adapter_fingerprint,
1226
+ content_type_rules_fingerprint,
1071
1227
  active, indexed_at, last_error_code, last_error_message, last_error_at,
1072
1228
  ingest_version, updated_at
1073
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, datetime('now'), ?, ?, ?, ?, datetime('now'))
1229
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, datetime('now'), ?, ?, ?, ?, datetime('now'))
1074
1230
  ON CONFLICT(collection, rel_path) DO UPDATE SET
1075
1231
  source_hash = excluded.source_hash,
1076
1232
  source_mime = excluded.source_mime,
@@ -1091,6 +1247,12 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
1091
1247
  author = excluded.author,
1092
1248
  frontmatter_date = excluded.frontmatter_date,
1093
1249
  date_fields = excluded.date_fields,
1250
+ record_key = excluded.record_key,
1251
+ record_source_path = excluded.record_source_path,
1252
+ record_source_locator = excluded.record_source_locator,
1253
+ record_metadata = excluded.record_metadata,
1254
+ record_anchors = excluded.record_anchors,
1255
+ record_adapter_fingerprint = excluded.record_adapter_fingerprint,
1094
1256
  content_type_rules_fingerprint = excluded.content_type_rules_fingerprint,
1095
1257
  active = 1,
1096
1258
  indexed_at = datetime('now'),
@@ -1122,6 +1284,12 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
1122
1284
  doc.author ?? null,
1123
1285
  doc.frontmatterDate ?? null,
1124
1286
  doc.dateFields ? JSON.stringify(doc.dateFields) : null,
1287
+ doc.recordKey ?? null,
1288
+ doc.recordSourcePath ?? null,
1289
+ doc.recordSourceLocator ?? null,
1290
+ doc.recordMetadata ? JSON.stringify(doc.recordMetadata) : null,
1291
+ doc.recordAnchors ? JSON.stringify(doc.recordAnchors) : null,
1292
+ doc.recordAdapterFingerprint ?? null,
1125
1293
  doc.contentTypeRulesFingerprint ?? null,
1126
1294
  doc.lastErrorCode ?? null,
1127
1295
  doc.lastErrorMessage ?? null,
@@ -1366,6 +1534,32 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
1366
1534
  }
1367
1535
  }
1368
1536
 
1537
+ async listRecordDocuments(
1538
+ collection: string,
1539
+ sourcePath: string
1540
+ ): Promise<StoreResult<DocumentRow[]>> {
1541
+ try {
1542
+ const db = this.ensureOpen();
1543
+ const rows = db
1544
+ .query<DbDocumentRow, [string, string]>(
1545
+ `SELECT * FROM documents
1546
+ WHERE collection = ? AND record_source_path = ?
1547
+ ORDER BY rel_path ASC, id ASC`
1548
+ )
1549
+ .all(collection, sourcePath);
1550
+
1551
+ return ok(rows.map(mapDocumentRow));
1552
+ } catch (cause) {
1553
+ return err(
1554
+ "QUERY_FAILED",
1555
+ cause instanceof Error
1556
+ ? cause.message
1557
+ : "Failed to list record documents",
1558
+ cause
1559
+ );
1560
+ }
1561
+ }
1562
+
1369
1563
  async listActiveDocumentsForBrowse(
1370
1564
  collection?: string
1371
1565
  ): Promise<StoreResult<DocumentRow[]>> {
@@ -1547,17 +1741,18 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
1547
1741
  const normalizedPathPrefix = pathPrefix
1548
1742
  ?.replaceAll("\\", "/")
1549
1743
  .replace(/^\/+|\/+$/g, "");
1744
+ const browsePathSql = "COALESCE(d.record_source_path, d.rel_path)";
1550
1745
 
1551
1746
  if (normalizedPathPrefix) {
1552
- conditions.push("d.rel_path LIKE ?");
1747
+ conditions.push(`${browsePathSql} LIKE ?`);
1553
1748
  params.push(`${normalizedPathPrefix}/%`);
1554
1749
 
1555
1750
  if (directChildrenOnly) {
1556
- conditions.push("substr(d.rel_path, ?) NOT LIKE '%/%'");
1751
+ conditions.push(`substr(${browsePathSql}, ?) NOT LIKE '%/%'`);
1557
1752
  params.push(normalizedPathPrefix.length + 2);
1558
1753
  }
1559
1754
  } else if (directChildrenOnly) {
1560
- conditions.push("d.rel_path NOT LIKE '%/%'");
1755
+ conditions.push(`${browsePathSql} NOT LIKE '%/%'`);
1561
1756
  }
1562
1757
 
1563
1758
  if (options.since) {
@@ -2162,7 +2357,7 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
2162
2357
  ${options.collection ? "AND collection = ?" : ""}
2163
2358
  ${
2164
2359
  options.relPathPrefix !== undefined
2165
- ? "AND (rel_path = ? OR substr(rel_path, 1, length(?) + 1) = ? || '/')"
2360
+ ? "AND (COALESCE(NULLIF(record_source_path, ''), rel_path) = ? OR substr(COALESCE(NULLIF(record_source_path, ''), rel_path), 1, length(?) + 1) = ? || '/')"
2166
2361
  : ""
2167
2362
  }
2168
2363
  )
@@ -2187,7 +2382,15 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
2187
2382
  d.source_hash,
2188
2383
  d.content_type,
2189
2384
  d.content_type_source,
2190
- d.categories
2385
+ d.categories,
2386
+ d.converter_id,
2387
+ d.converter_version,
2388
+ d.record_key,
2389
+ d.record_source_path,
2390
+ d.record_source_locator,
2391
+ d.record_metadata,
2392
+ d.record_anchors,
2393
+ d.record_adapter_fingerprint
2191
2394
  FROM fts_matches fm
2192
2395
  JOIN documents d ON d.id = fm.rowid AND d.active = 1
2193
2396
  WHERE 1 = 1
@@ -2215,6 +2418,14 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
2215
2418
  content_type: string | null;
2216
2419
  content_type_source: string | null;
2217
2420
  categories: string | null;
2421
+ converter_id: string | null;
2422
+ converter_version: string | null;
2423
+ record_key: string | null;
2424
+ record_source_path: string | null;
2425
+ record_source_locator: string | null;
2426
+ record_metadata: string | null;
2427
+ record_anchors: string | null;
2428
+ record_adapter_fingerprint: string | null;
2218
2429
  }
2219
2430
 
2220
2431
  const queryParams = [
@@ -2254,6 +2465,15 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
2254
2465
  contentType: r.content_type ?? undefined,
2255
2466
  contentTypeSource: r.content_type_source ?? undefined,
2256
2467
  categories: parseCategoriesJson(r.categories) ?? undefined,
2468
+ converterId: r.converter_id ?? undefined,
2469
+ converterVersion: r.converter_version ?? undefined,
2470
+ recordKey: r.record_key ?? undefined,
2471
+ recordSourcePath: r.record_source_path ?? undefined,
2472
+ recordSourceLocator: r.record_source_locator ?? undefined,
2473
+ recordMetadata:
2474
+ parseRecordMetadataJson(r.record_metadata) ?? undefined,
2475
+ recordAnchors: parseRecordAnchorsJson(r.record_anchors) ?? undefined,
2476
+ recordAdapterFingerprint: r.record_adapter_fingerprint ?? undefined,
2257
2477
  }))
2258
2478
  );
2259
2479
  } catch (cause) {
@@ -4642,6 +4862,8 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
4642
4862
  interface CollectionStat {
4643
4863
  name: string;
4644
4864
  path: string;
4865
+ egress_policy: EgressPolicy;
4866
+ egress_policy_source: EgressPolicySource;
4645
4867
  total: number;
4646
4868
  active: number;
4647
4869
  errored: number;
@@ -4693,6 +4915,8 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
4693
4915
  SELECT
4694
4916
  c.name,
4695
4917
  c.path,
4918
+ c.egress_policy,
4919
+ c.egress_policy_source,
4696
4920
  COALESCE(ds.total, 0) AS total,
4697
4921
  COALESCE(ds.active, 0) AS active,
4698
4922
  COALESCE(ds.errored, 0) AS errored,
@@ -4789,6 +5013,8 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
4789
5013
  collections: collectionStats.map((s) => ({
4790
5014
  name: s.name,
4791
5015
  path: s.path,
5016
+ egressPolicy: s.egress_policy,
5017
+ egressPolicySource: s.egress_policy_source,
4792
5018
  totalDocuments: s.total,
4793
5019
  activeDocuments: s.active,
4794
5020
  errorDocuments: s.errored,
@@ -5065,6 +5291,8 @@ interface DbCollectionRow {
5065
5291
  exclude: string | null;
5066
5292
  update_cmd: string | null;
5067
5293
  language_hint: string | null;
5294
+ egress_policy: EgressPolicy;
5295
+ egress_policy_source: EgressPolicySource;
5068
5296
  synced_at: string;
5069
5297
  }
5070
5298
 
@@ -5098,6 +5326,12 @@ interface DbDocumentRow {
5098
5326
  author: string | null;
5099
5327
  frontmatter_date: string | null;
5100
5328
  date_fields: string | null;
5329
+ record_key: string | null;
5330
+ record_source_path: string | null;
5331
+ record_source_locator: string | null;
5332
+ record_metadata: string | null;
5333
+ record_anchors: string | null;
5334
+ record_adapter_fingerprint: string | null;
5101
5335
  content_type_rules_fingerprint: string | null;
5102
5336
  indexed_at: string | null;
5103
5337
  active: number;
@@ -5157,6 +5391,8 @@ function mapCollectionRow(row: DbCollectionRow): CollectionRow {
5157
5391
  exclude: row.exclude ? JSON.parse(row.exclude) : null,
5158
5392
  updateCmd: row.update_cmd,
5159
5393
  languageHint: row.language_hint,
5394
+ egressPolicy: row.egress_policy,
5395
+ egressPolicySource: row.egress_policy_source,
5160
5396
  syncedAt: row.synced_at,
5161
5397
  };
5162
5398
  }
@@ -5185,6 +5421,34 @@ function parseCategoriesJson(raw: string | null): string[] | null {
5185
5421
  return null;
5186
5422
  }
5187
5423
 
5424
+ function parseRecordMetadataJson(
5425
+ raw: string | null
5426
+ ): DocumentRow["recordMetadata"] {
5427
+ if (!raw) return null;
5428
+ try {
5429
+ const parsed: unknown = JSON.parse(raw);
5430
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed)
5431
+ ? (parsed as NonNullable<DocumentRow["recordMetadata"]>)
5432
+ : null;
5433
+ } catch {
5434
+ return null;
5435
+ }
5436
+ }
5437
+
5438
+ function parseRecordAnchorsJson(
5439
+ raw: string | null
5440
+ ): DocumentRow["recordAnchors"] {
5441
+ if (!raw) return null;
5442
+ try {
5443
+ const parsed: unknown = JSON.parse(raw);
5444
+ return Array.isArray(parsed)
5445
+ ? (parsed as NonNullable<DocumentRow["recordAnchors"]>)
5446
+ : null;
5447
+ } catch {
5448
+ return null;
5449
+ }
5450
+ }
5451
+
5188
5452
  function mapDocumentRow(row: DbDocumentRow): DocumentRow {
5189
5453
  const categories = parseCategoriesJson(row.categories);
5190
5454
  let dateFields: Record<string, string> | null = null;
@@ -5230,6 +5494,12 @@ function mapDocumentRow(row: DbDocumentRow): DocumentRow {
5230
5494
  author: row.author,
5231
5495
  frontmatterDate: row.frontmatter_date,
5232
5496
  dateFields,
5497
+ recordKey: row.record_key,
5498
+ recordSourcePath: row.record_source_path,
5499
+ recordSourceLocator: row.record_source_locator,
5500
+ recordMetadata: parseRecordMetadataJson(row.record_metadata),
5501
+ recordAnchors: parseRecordAnchorsJson(row.record_anchors),
5502
+ recordAdapterFingerprint: row.record_adapter_fingerprint,
5233
5503
  indexedAt: row.indexed_at,
5234
5504
  active: row.active === 1,
5235
5505
  ingestVersion: row.ingest_version,
@@ -22,6 +22,10 @@ import {
22
22
  serializeDocumentChangeStructureDelta,
23
23
  validateDocumentChangeRetentionPolicy,
24
24
  } from "../../core/change-journal";
25
+ import {
26
+ createEgressLineage,
27
+ egressLineageSchema,
28
+ } from "../../core/egress-provenance";
25
29
  import { err, ok } from "../types";
26
30
 
27
31
  const DAY_MS = 86_400_000;
@@ -74,6 +78,8 @@ interface DbDocumentChangeRow {
74
78
  structure_truncated: number;
75
79
  observed_at_ms: number;
76
80
  byte_size: number;
81
+ egress_lineage_json: string;
82
+ egress_lineage_bytes: number;
77
83
  }
78
84
 
79
85
  const utf8ByteLength = (value: string | null): number =>
@@ -127,6 +133,7 @@ const mapRow = (row: DbDocumentChangeRow): DocumentChangeRow => ({
127
133
  dates: JSON.parse(row.date_delta_json),
128
134
  truncated: row.structure_truncated === 1,
129
135
  },
136
+ egressLineage: egressLineageSchema.parse(JSON.parse(row.egress_lineage_json)),
130
137
  observedAtMs: row.observed_at_ms,
131
138
  byteSize: row.byte_size,
132
139
  });
@@ -249,11 +256,38 @@ export const appendDocumentChange = (
249
256
  } = serializeDocumentChangeStructureDelta(draft.structureDelta);
250
257
  const old = draft.oldSnapshot;
251
258
  const next = draft.newSnapshot;
259
+ const collectionPolicy = db
260
+ .query<
261
+ {
262
+ egress_policy: "local_only" | "lan" | "remote";
263
+ egress_policy_source: "explicit" | "config_default" | "legacy_default";
264
+ },
265
+ [string]
266
+ >(
267
+ `SELECT egress_policy, egress_policy_source
268
+ FROM collections WHERE name = ?`
269
+ )
270
+ .get(draft.collection);
271
+ if (!collectionPolicy) {
272
+ throw new Error(
273
+ `Document change policy lineage references unknown collection ${draft.collection}`
274
+ );
275
+ }
276
+ const egressLineage = createEgressLineage([
277
+ {
278
+ collection: draft.collection,
279
+ policy: collectionPolicy.egress_policy,
280
+ source: collectionPolicy.egress_policy_source,
281
+ },
282
+ ]);
283
+ const egressLineageJson = JSON.stringify(egressLineage);
284
+ const egressLineageBytes = UTF8_ENCODER.encode(egressLineageJson).byteLength;
252
285
  const storedByteSize = byteSize(draft, [
253
286
  headingDeltaJson,
254
287
  linkDeltaJson,
255
288
  typedEdgeDeltaJson,
256
289
  dateDeltaJson,
290
+ egressLineageJson,
257
291
  ]);
258
292
  const inserted = db.run(
259
293
  `INSERT INTO document_changes (
@@ -262,8 +296,9 @@ export const appendDocumentChange = (
262
296
  old_source_hash, new_source_hash, old_mirror_hash, new_mirror_hash,
263
297
  old_active, new_active, heading_delta_json, link_delta_json,
264
298
  typed_edge_delta_json, date_delta_json, structure_truncated,
265
- observed_at_ms, byte_size
266
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
299
+ effective_egress_policy, egress_lineage_digest, egress_lineage_json,
300
+ egress_lineage_bytes, observed_at_ms, byte_size
301
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
267
302
  [
268
303
  draft.documentId,
269
304
  draft.collection,
@@ -285,6 +320,10 @@ export const appendDocumentChange = (
285
320
  typedEdgeDeltaJson,
286
321
  dateDeltaJson,
287
322
  delta.truncated ? 1 : 0,
323
+ egressLineage.effectivePolicy,
324
+ egressLineage.digest,
325
+ egressLineageJson,
326
+ egressLineageBytes,
288
327
  draft.observedAtMs,
289
328
  storedByteSize,
290
329
  ]