@gmickel/gno 1.28.0 → 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 (143) hide show
  1. package/README.md +46 -20
  2. package/assets/skill/SKILL.md +33 -0
  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 +1 -1
  10. package/spec/cli.md +41 -1
  11. package/spec/db/schema.sql +19 -0
  12. package/spec/mcp.md +66 -0
  13. package/spec/output-schemas/ask.schema.json +31 -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 +89 -2
  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/mcp-http-error.schema.json +113 -2
  22. package/spec/output-schemas/publish-artifact.schema.json +32 -0
  23. package/spec/output-schemas/record-import.schema.json +30 -0
  24. package/spec/output-schemas/retrieval-trace-common.schema.json +31 -0
  25. package/spec/output-schemas/retrieval-trace-export.schema.json +3 -0
  26. package/spec/output-schemas/retrieval-trace-qrels.schema.json +3 -0
  27. package/spec/output-schemas/retrieval-trace-summary.schema.json +4 -0
  28. package/spec/output-schemas/search-results.schema.json +30 -0
  29. package/src/app/context-runtime-contract.ts +16 -7
  30. package/src/cli/commands/ask.ts +7 -0
  31. package/src/cli/commands/collection/index.ts +5 -0
  32. package/src/cli/commands/collection/policy.ts +126 -0
  33. package/src/cli/commands/context-build.ts +5 -0
  34. package/src/cli/commands/daemon.ts +45 -3
  35. package/src/cli/commands/egress-audit.ts +68 -0
  36. package/src/cli/commands/embed.ts +2 -0
  37. package/src/cli/commands/query.ts +12 -0
  38. package/src/cli/commands/replay.ts +13 -3
  39. package/src/cli/commands/vsearch.ts +3 -1
  40. package/src/cli/program.ts +123 -0
  41. package/src/config/index.ts +9 -0
  42. package/src/config/types.ts +50 -0
  43. package/src/core/browser-clip.ts +16 -1
  44. package/src/core/collection-egress-policy-projection.ts +28 -0
  45. package/src/core/collection-egress-policy-service.ts +443 -0
  46. package/src/core/collection-egress-policy-validation.ts +242 -0
  47. package/src/core/config-mutation.ts +31 -19
  48. package/src/core/context-capsule-schema.ts +111 -1
  49. package/src/core/context-capsule-validation.ts +4 -4
  50. package/src/core/context-capsule-verification.ts +5 -1
  51. package/src/core/context-capsule.ts +177 -132
  52. package/src/core/context-evidence.ts +86 -14
  53. package/src/core/destination-classifier.ts +402 -0
  54. package/src/core/egress-audit.ts +239 -0
  55. package/src/core/egress-authorization.ts +50 -0
  56. package/src/core/egress-enforcement.ts +264 -0
  57. package/src/core/egress-policy.ts +440 -0
  58. package/src/core/egress-provenance.ts +336 -0
  59. package/src/core/job-manager.ts +37 -0
  60. package/src/core/network-boundary-inventory.ts +261 -0
  61. package/src/core/retrieval-qrels.ts +6 -0
  62. package/src/core/retrieval-replay.ts +18 -4
  63. package/src/core/retrieval-trace-export.ts +23 -4
  64. package/src/core/retrieval-trace-management-helpers.ts +1 -0
  65. package/src/core/retrieval-trace-management-types.ts +3 -0
  66. package/src/core/retrieval-trace-management.ts +9 -1
  67. package/src/core/retrieval-trace-session.ts +94 -0
  68. package/src/core/retrieval-trace.ts +22 -0
  69. package/src/ingestion/record-adapter.ts +12 -1
  70. package/src/ingestion/record-container.ts +38 -22
  71. package/src/ingestion/sync.ts +15 -0
  72. package/src/ingestion/types.ts +2 -0
  73. package/src/llm/errors.ts +10 -0
  74. package/src/llm/http-inference.ts +175 -0
  75. package/src/llm/http-policy.ts +537 -0
  76. package/src/llm/httpEmbedding.ts +47 -28
  77. package/src/llm/httpGeneration.ts +31 -18
  78. package/src/llm/httpRerank.ts +30 -18
  79. package/src/llm/index.ts +1 -0
  80. package/src/llm/nodeLlamaCpp/adapter.ts +55 -20
  81. package/src/llm/pinned-http-connection.ts +177 -0
  82. package/src/mcp/context.ts +53 -1
  83. package/src/mcp/http-egress.ts +220 -0
  84. package/src/mcp/http-security.ts +32 -21
  85. package/src/mcp/http-session.ts +13 -2
  86. package/src/mcp/http-transport.ts +96 -6
  87. package/src/mcp/sync-egress.ts +24 -0
  88. package/src/mcp/tools/add-collection.ts +4 -0
  89. package/src/mcp/tools/ask.ts +1 -1
  90. package/src/mcp/tools/context.ts +9 -2
  91. package/src/mcp/tools/egress.ts +247 -0
  92. package/src/mcp/tools/embed.ts +1 -0
  93. package/src/mcp/tools/index-cmd.ts +7 -0
  94. package/src/mcp/tools/index.ts +98 -0
  95. package/src/mcp/tools/query.ts +10 -0
  96. package/src/mcp/tools/remove-collection.ts +4 -0
  97. package/src/mcp/tools/sync.ts +6 -0
  98. package/src/mcp/tools/trace.ts +7 -1
  99. package/src/mcp/tools/vsearch.ts +1 -0
  100. package/src/pipeline/egress-lineage.ts +124 -0
  101. package/src/pipeline/hybrid.ts +14 -0
  102. package/src/pipeline/search.ts +12 -0
  103. package/src/pipeline/types.ts +3 -0
  104. package/src/pipeline/vsearch.ts +41 -20
  105. package/src/publish/artifact.ts +31 -3
  106. package/src/publish/export-service.ts +25 -0
  107. package/src/sdk/client.ts +119 -2
  108. package/src/sdk/embed.ts +1 -0
  109. package/src/sdk/types.ts +33 -0
  110. package/src/serve/clipper-capture.ts +5 -1
  111. package/src/serve/closed-json.ts +61 -0
  112. package/src/serve/config-sync.ts +4 -1
  113. package/src/serve/context.ts +1 -0
  114. package/src/serve/doc-events.ts +110 -39
  115. package/src/serve/public/components/CollectionEgressCheckPanel.tsx +411 -0
  116. package/src/serve/public/components/CollectionEgressPolicyEditor.tsx +78 -0
  117. package/src/serve/public/components/CollectionModelDialog.tsx +93 -9
  118. package/src/serve/public/components/EgressAuditPanel.tsx +278 -0
  119. package/src/serve/public/globals.built.css +1 -1
  120. package/src/serve/public/pages/Collections.tsx +3 -0
  121. package/src/serve/resident-request.ts +80 -3
  122. package/src/serve/resident-runtime.ts +63 -1
  123. package/src/serve/routes/api.ts +174 -0
  124. package/src/serve/routes/clipper.ts +64 -1
  125. package/src/serve/routes/mcp.ts +6 -0
  126. package/src/serve/routes/traces.ts +27 -4
  127. package/src/serve/server.ts +118 -7
  128. package/src/store/migrations/023-collection-egress-policy.ts +61 -0
  129. package/src/store/migrations/024-egress-derived-lineage.ts +121 -0
  130. package/src/store/migrations/025-collection-egress-policy-revision.ts +40 -0
  131. package/src/store/migrations/index.ts +6 -0
  132. package/src/store/retrieval-trace-codec.ts +15 -0
  133. package/src/store/sqlite/adapter.ts +170 -7
  134. package/src/store/sqlite/change-journal-store.ts +41 -2
  135. package/src/store/sqlite/egress-audit-store.ts +485 -0
  136. package/src/store/sqlite/retrieval-trace-management-store.ts +47 -6
  137. package/src/store/sqlite/retrieval-trace-retention.ts +2 -0
  138. package/src/store/sqlite/retrieval-trace-rows.ts +8 -1
  139. package/src/store/sqlite/retrieval-trace-store.ts +104 -5
  140. package/src/store/types.ts +126 -1
  141. package/browser-extension/artifacts/gno-browser-clipper-v1.28.0.zip +0 -0
  142. package/browser-extension/artifacts/gno-browser-clipper-v1.28.0.zip.sha256 +0 -1
  143. 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
  }
@@ -4709,6 +4862,8 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
4709
4862
  interface CollectionStat {
4710
4863
  name: string;
4711
4864
  path: string;
4865
+ egress_policy: EgressPolicy;
4866
+ egress_policy_source: EgressPolicySource;
4712
4867
  total: number;
4713
4868
  active: number;
4714
4869
  errored: number;
@@ -4760,6 +4915,8 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
4760
4915
  SELECT
4761
4916
  c.name,
4762
4917
  c.path,
4918
+ c.egress_policy,
4919
+ c.egress_policy_source,
4763
4920
  COALESCE(ds.total, 0) AS total,
4764
4921
  COALESCE(ds.active, 0) AS active,
4765
4922
  COALESCE(ds.errored, 0) AS errored,
@@ -4856,6 +5013,8 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
4856
5013
  collections: collectionStats.map((s) => ({
4857
5014
  name: s.name,
4858
5015
  path: s.path,
5016
+ egressPolicy: s.egress_policy,
5017
+ egressPolicySource: s.egress_policy_source,
4859
5018
  totalDocuments: s.total,
4860
5019
  activeDocuments: s.active,
4861
5020
  errorDocuments: s.errored,
@@ -5132,6 +5291,8 @@ interface DbCollectionRow {
5132
5291
  exclude: string | null;
5133
5292
  update_cmd: string | null;
5134
5293
  language_hint: string | null;
5294
+ egress_policy: EgressPolicy;
5295
+ egress_policy_source: EgressPolicySource;
5135
5296
  synced_at: string;
5136
5297
  }
5137
5298
 
@@ -5230,6 +5391,8 @@ function mapCollectionRow(row: DbCollectionRow): CollectionRow {
5230
5391
  exclude: row.exclude ? JSON.parse(row.exclude) : null,
5231
5392
  updateCmd: row.update_cmd,
5232
5393
  languageHint: row.language_hint,
5394
+ egressPolicy: row.egress_policy,
5395
+ egressPolicySource: row.egress_policy_source,
5233
5396
  syncedAt: row.synced_at,
5234
5397
  };
5235
5398
  }
@@ -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
  ]