@hasna/knowledge 0.2.11 → 0.2.13

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.
@@ -89,6 +89,21 @@ file revisions, hashes, extraction state, permissions, and storage metadata.
89
89
  Direct `s3://`, `file://`, and `https://` refs are useful for bootstrap and
90
90
  interop, but should be normalized into source records when possible.
91
91
 
92
+ ## Provenance Contract
93
+
94
+ Every durable search/wiki artifact should carry a provenance object in metadata:
95
+ `source_owner`, `source_ref`, `source_uri`, `source_kind`, `source_revision_id`,
96
+ `revision`, `hash`, optional `chunk_id`, offsets, `read_only`,
97
+ `citation_required`, resolver name, and stale status. For generated artifacts
98
+ that are not source-backed yet, metadata still records that `open-files` owns
99
+ source bytes and that citations are required before durable facts are filed.
100
+
101
+ `wiki init` now catalogs the starter `wiki/README.md` and `indexes/root.md`
102
+ records with generated-artifact provenance. Source ingestion stores source
103
+ provenance on every chunk, and source resolution returns that provenance with
104
+ chunks and citations so semantic search can pass through trustworthy evidence
105
+ without reconstructing it later.
106
+
92
107
  ## Resolver Boundary
93
108
 
94
109
  The local resolver is exposed through:
@@ -133,6 +148,18 @@ Raw files still route through `open-files`. Knowledge S3 storage is for derived
133
148
  artifacts such as wiki pages, index shards, schema versions, logs, exports, and
134
149
  run outputs.
135
150
 
151
+ The storage contract is inspectable through:
152
+
153
+ ```bash
154
+ open-knowledge storage status --scope project --json
155
+ ```
156
+
157
+ That contract names the local app path, SQLite catalog, generated artifact
158
+ classes, S3 bucket/prefix when configured, and the source ownership rule that
159
+ raw source bytes stay in `open-files`. The `storage_objects` table catalogs
160
+ generated artifacts by URI, kind, hash, size, and metadata so local mode and
161
+ remote/S3 mode share the same DB-facing shape.
162
+
136
163
  ## Wiki Model
137
164
 
138
165
  The Karpathy-style wiki pattern is implemented as scalable artifacts, not three
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/knowledge",
3
- "version": "0.2.11",
3
+ "version": "0.2.13",
4
4
  "description": "Agent-friendly local knowledge CLI with JSON output, pagination, and safe destructive actions",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.ts CHANGED
@@ -60,7 +60,7 @@ interface ParseResult {
60
60
  flags: Flags;
61
61
  }
62
62
 
63
- const COMMANDS = ['add', 'list', 'get', 'delete', 'update', 'archive', 'restore', 'upsert', 'untag', 'export', 'prune', 'dedupe', 'stats', 'paths', 'db', 'wiki', 'source', 'ingest', 'reindex', 'providers', 'safety', 'help'];
63
+ const COMMANDS = ['add', 'list', 'get', 'delete', 'update', 'archive', 'restore', 'upsert', 'untag', 'export', 'prune', 'dedupe', 'stats', 'paths', 'storage', 'db', 'wiki', 'source', 'ingest', 'reindex', 'providers', 'safety', 'help'];
64
64
  const COMMAND_ALIASES: Record<string, string> = {
65
65
  ls: 'list',
66
66
  rm: 'delete',
@@ -162,6 +162,7 @@ Commands:
162
162
  dedupe Remove duplicate items by title+content (requires --yes)
163
163
  stats Show knowledge base statistics
164
164
  paths Show resolved workspace/store paths
165
+ storage status|validate Inspect local/S3 artifact storage contract
165
166
  db init|stats Initialize or inspect local knowledge.db
166
167
  wiki init Initialize scalable wiki/schema/index/log artifacts
167
168
  source resolve <source-ref> Resolve read-only source content and citation evidence
@@ -230,6 +231,7 @@ function printCommandHelp(command: string): void {
230
231
  if (command === 'dedupe') { console.log('Usage: open-knowledge dedupe --yes [--json]'); return; }
231
232
  if (command === 'stats') { console.log('Usage: open-knowledge stats [--json]'); return; }
232
233
  if (command === 'paths') { console.log('Usage: open-knowledge paths [--scope local|global|project] [--json]'); return; }
234
+ if (command === 'storage') { console.log('Usage: open-knowledge storage status|validate [--scope local|global|project] [--json]'); return; }
233
235
  if (command === 'db') { console.log('Usage: open-knowledge db init|stats [--scope local|global|project] [--json]'); return; }
234
236
  if (command === 'wiki') { console.log('Usage: open-knowledge wiki init [--scope local|global|project] [--json]'); return; }
235
237
  if (command === 'source') { console.log('Usage: open-knowledge source resolve <source-ref> [--purpose knowledge_answer|knowledge_index] [--limit <n>] [--scope local|global|project] [--json]'); return; }
@@ -281,11 +283,11 @@ async function run(argv: string[]): Promise<void> {
281
283
  if (flags.completions) {
282
284
  const shell = flags.completions;
283
285
  if (shell === 'bash') {
284
- console.log(`_open_knowledge() { local cur; cur="${"$"}{COMP_WORDS[COMP_CWORD]}"; COMPREPLY=($(compgen -W "add list get update archive restore upsert untag delete export prune dedupe stats paths db wiki source ingest reindex providers safety help ls rm edit unarchive --json --yes --help --version --desc --page --limit --search --sort --id --store --title --content --url --tag --format --completions --purpose --no-color --scope --archived --include-archived" -- "$cur")); }; complete -F _open_knowledge open-knowledge`);
286
+ console.log(`_open_knowledge() { local cur; cur="${"$"}{COMP_WORDS[COMP_CWORD]}"; COMPREPLY=($(compgen -W "add list get update archive restore upsert untag delete export prune dedupe stats paths storage db wiki source ingest reindex providers safety help ls rm edit unarchive --json --yes --help --version --desc --page --limit --search --sort --id --store --title --content --url --tag --format --completions --purpose --no-color --scope --archived --include-archived" -- "$cur")); }; complete -F _open_knowledge open-knowledge`);
285
287
  } else if (shell === 'zsh') {
286
- console.log(`#compdef open-knowledge\n_open_knowledge() { _arguments -C "1: :(add list get update archive restore upsert untag delete export prune dedupe stats paths db wiki source ingest reindex providers safety help ls rm edit unarchive)" "(--json)--json" "(--yes)-y" "(--help)--help" "(--version)--version" "(--desc)--desc" "(--archived)--archived" "(--include-archived)--include-archived" "(-p --page)"{-p,--page}"[page number]:number:" "(-l --limit)"{-l,--limit}"[items per page]:number:" "(-s --search)"{-s,--search}"[search text]:text:" "(--sort)--sort"\{created,title\}:" "(--id)--id[item id]:id:" "(--store)--store[store path]:path:" "(--title)--title[new title]:" "(--content)--content[new content]:" "(--url)--url[source url]:" "(-t --tag)"{-t,--tag}"[tag]:tag:" "(--format)--format[json|jsonl]:" "(--completions)--completions[output completions]:shell:(bash zsh fish):" "(--purpose)--purpose[purpose]:" "(--no-color)--no-color[disable color]" "(--scope)--scope"\{local,global,project\}:" }; _open_knowledge`);
288
+ console.log(`#compdef open-knowledge\n_open_knowledge() { _arguments -C "1: :(add list get update archive restore upsert untag delete export prune dedupe stats paths storage db wiki source ingest reindex providers safety help ls rm edit unarchive)" "(--json)--json" "(--yes)-y" "(--help)--help" "(--version)--version" "(--desc)--desc" "(--archived)--archived" "(--include-archived)--include-archived" "(-p --page)"{-p,--page}"[page number]:number:" "(-l --limit)"{-l,--limit}"[items per page]:number:" "(-s --search)"{-s,--search}"[search text]:text:" "(--sort)--sort"\{created,title\}:" "(--id)--id[item id]:id:" "(--store)--store[store path]:path:" "(--title)--title[new title]:" "(--content)--content[new content]:" "(--url)--url[source url]:" "(-t --tag)"{-t,--tag}"[tag]:tag:" "(--format)--format[json|jsonl]:" "(--completions)--completions[output completions]:shell:(bash zsh fish):" "(--purpose)--purpose[purpose]:" "(--no-color)--no-color[disable color]" "(--scope)--scope"\{local,global,project\}:" }; _open_knowledge`);
287
289
  } else if (shell === 'fish') {
288
- console.log(`complete -c open-knowledge -f; complete -c open-knowledge -a "add list get update archive restore upsert untag delete export prune dedupe stats paths db wiki source ingest reindex providers safety help ls rm edit unarchive"; complete -c open-knowledge -l json; complete -c open-knowledge -l yes -s y; complete -c open-knowledge -l help -s h; complete -c open-knowledge -l version -s v; complete -c open-knowledge -l desc; complete -c open-knowledge -l archived; complete -c open-knowledge -l include-archived; complete -c open-knowledge -s p -l page; complete -c open-knowledge -s l -l limit; complete -c open-knowledge -s s -l search; complete -c open-knowledge -l sort; complete -c open-knowledge -l id; complete -c open-knowledge -l store; complete -c open-knowledge -l title; complete -c open-knowledge -l content; complete -c open-knowledge -l url; complete -c open-knowledge -s t -l tag; complete -c open-knowledge -l format; complete -c open-knowledge -l completions; complete -c open-knowledge -l purpose; complete -c open-knowledge -l no-color; complete -c open-knowledge -l scope -a "local global project"`);
290
+ console.log(`complete -c open-knowledge -f; complete -c open-knowledge -a "add list get update archive restore upsert untag delete export prune dedupe stats paths storage db wiki source ingest reindex providers safety help ls rm edit unarchive"; complete -c open-knowledge -l json; complete -c open-knowledge -l yes -s y; complete -c open-knowledge -l help -s h; complete -c open-knowledge -l version -s v; complete -c open-knowledge -l desc; complete -c open-knowledge -l archived; complete -c open-knowledge -l include-archived; complete -c open-knowledge -s p -l page; complete -c open-knowledge -s l -l limit; complete -c open-knowledge -s s -l search; complete -c open-knowledge -l sort; complete -c open-knowledge -l id; complete -c open-knowledge -l store; complete -c open-knowledge -l title; complete -c open-knowledge -l content; complete -c open-knowledge -l url; complete -c open-knowledge -s t -l tag; complete -c open-knowledge -l format; complete -c open-knowledge -l completions; complete -c open-knowledge -l purpose; complete -c open-knowledge -l no-color; complete -c open-knowledge -l scope -a "local global project"`);
289
291
  } else {
290
292
  throw new Error("Invalid --completions value. Use 'bash', 'zsh', or 'fish'.");
291
293
  }
@@ -311,6 +313,31 @@ async function run(argv: string[]): Promise<void> {
311
313
  return;
312
314
  }
313
315
 
316
+ if (command === 'storage') {
317
+ const action = positional[1] ?? 'status';
318
+ if (action === 'status') {
319
+ const contract = service.storageContract();
320
+ const validation = service.validateStorage();
321
+ output({
322
+ ok: validation.ok,
323
+ ...contract,
324
+ validation,
325
+ message: `${contract.storage_type} artifact storage at ${contract.artifact_store.uri_prefix}`,
326
+ }, flags.json);
327
+ return;
328
+ }
329
+ if (action === 'validate') {
330
+ const validation = service.validateStorage();
331
+ output({
332
+ ok: validation.ok,
333
+ validation,
334
+ message: validation.ok ? 'Storage contract valid' : `Storage contract invalid: ${validation.errors.join('; ')}`,
335
+ }, flags.json);
336
+ return;
337
+ }
338
+ throw new Error("Invalid storage action. Use 'status' or 'validate'.");
339
+ }
340
+
314
341
  if (command === 'db') {
315
342
  const action = positional[1] ?? 'init';
316
343
  if (action !== 'init' && action !== 'stats') {
@@ -16,6 +16,7 @@ export interface KnowledgeDbStats {
16
16
  redaction_findings: number;
17
17
  audit_events: number;
18
18
  approval_gates: number;
19
+ storage_objects: number;
19
20
  }
20
21
 
21
22
  const MIGRATION_1 = `
@@ -239,6 +240,7 @@ export function openKnowledgeDb(path: string): Database {
239
240
  ensureParentDir(path);
240
241
  const db = new Database(path);
241
242
  db.exec('PRAGMA foreign_keys = ON;');
243
+ db.exec('PRAGMA busy_timeout = 5000;');
242
244
  return db;
243
245
  }
244
246
 
@@ -280,6 +282,7 @@ export function getKnowledgeDbStats(path: string): KnowledgeDbStats {
280
282
  redaction_findings: count(db, 'redaction_findings'),
281
283
  audit_events: count(db, 'audit_events'),
282
284
  approval_gates: count(db, 'approval_gates'),
285
+ storage_objects: count(db, 'storage_objects'),
283
286
  };
284
287
  } finally {
285
288
  db.close();
@@ -4,6 +4,7 @@ import { basename } from 'node:path';
4
4
  import type { Database } from 'bun:sqlite';
5
5
  import { migrateKnowledgeDb, openKnowledgeDb } from './knowledge-db';
6
6
  import { parseSourceRef, type SourceRef } from './source-ref';
7
+ import { sourceProvenance, withProvenance } from './provenance';
7
8
  import type { KnowledgeConfig } from './workspace';
8
9
  import {
9
10
  assertS3ReadAllowed,
@@ -382,15 +383,31 @@ function insertChunks(db: Database, sourceRevisionId: string, item: NormalizedMa
382
383
  const chunks = chunkText(redacted.text, maxChars, overlapChars);
383
384
  for (const chunk of chunks) {
384
385
  const chunkId = stableId('chk', `${sourceRevisionId}\u0000${chunk.ordinal}\u0000${chunk.text}`);
385
- const metadata = {
386
+ const provenance = sourceProvenance({
386
387
  source_ref: item.sourceRef,
387
388
  source_uri: item.sourceUri,
389
+ source_kind: item.kind,
390
+ source_revision_id: sourceRevisionId,
391
+ revision: item.revision,
392
+ hash: item.hash,
393
+ chunk_id: chunkId,
394
+ start_offset: chunk.startOffset,
395
+ end_offset: chunk.endOffset,
396
+ status: item.status,
397
+ resolver: 'open-files-read-only',
398
+ });
399
+ const metadata = withProvenance({
400
+ source_ref: item.sourceRef,
401
+ source_uri: item.sourceUri,
402
+ source_kind: item.kind,
403
+ source_revision_id: sourceRevisionId,
404
+ revision: item.revision,
388
405
  hash: item.hash,
389
406
  status: item.status,
390
407
  path: asString(item.raw.path) ?? null,
391
408
  mime: asString(item.raw.mime) ?? asString(item.raw.content_type) ?? null,
392
409
  size: asNumber(item.raw.size) ?? null,
393
- };
410
+ }, provenance);
394
411
  db.run(
395
412
  `INSERT INTO chunks (id, source_revision_id, kind, ordinal, text, token_count, start_offset, end_offset, metadata_json, created_at)
396
413
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
package/src/mcp.js CHANGED
@@ -77,6 +77,18 @@ export function buildServer() {
77
77
  return jsonText(createKnowledgeService({ scope }).paths());
78
78
  });
79
79
 
80
+ registerTool(server, 'ok_storage_status', 'Knowledge storage status', 'Inspect local/S3 artifact storage, source ownership, and scalability contract', {
81
+ scope: scopeField,
82
+ }, async ({ scope }) => {
83
+ const service = createKnowledgeService({ scope });
84
+ const validation = service.validateStorage();
85
+ return jsonText({
86
+ ok: validation.ok,
87
+ ...service.storageContract(),
88
+ validation,
89
+ });
90
+ });
91
+
80
92
  registerTool(server, 'ok_parse_source_ref', 'Parse source reference', 'Parse and validate an open-files, S3, file, or web source ref', {
81
93
  uri: z.string().describe('Source reference URI'),
82
94
  }, async ({ uri }) => {
@@ -0,0 +1,93 @@
1
+ export interface KnowledgeProvenance {
2
+ source_owner: 'open-files';
3
+ source_ref: string | null;
4
+ source_uri: string | null;
5
+ source_kind: string | null;
6
+ source_revision_id: string | null;
7
+ revision: string | null;
8
+ hash: string | null;
9
+ chunk_id: string | null;
10
+ start_offset: number | null;
11
+ end_offset: number | null;
12
+ status: string | null;
13
+ read_only: true;
14
+ citation_required: boolean;
15
+ resolver: string | null;
16
+ stale: boolean;
17
+ }
18
+
19
+ export interface GeneratedArtifactProvenance {
20
+ source_owner: 'open-files';
21
+ generated_from: string;
22
+ artifact_key: string;
23
+ source_refs: string[];
24
+ read_only_sources: true;
25
+ citation_required: boolean;
26
+ raw_source_bytes_stored_in_open_knowledge: false;
27
+ }
28
+
29
+ export interface SourceProvenanceInput {
30
+ source_ref?: string | null;
31
+ source_uri?: string | null;
32
+ source_kind?: string | null;
33
+ source_revision_id?: string | null;
34
+ revision?: string | null;
35
+ hash?: string | null;
36
+ chunk_id?: string | null;
37
+ start_offset?: number | null;
38
+ end_offset?: number | null;
39
+ status?: string | null;
40
+ resolver?: string | null;
41
+ }
42
+
43
+ export function isStaleStatus(status: string | null | undefined): boolean {
44
+ return ['deleted', 'stale', 'invalidated', 'reindex_required'].includes((status ?? '').toLowerCase());
45
+ }
46
+
47
+ export function sourceProvenance(input: SourceProvenanceInput): KnowledgeProvenance {
48
+ const status = input.status ?? null;
49
+ return {
50
+ source_owner: 'open-files',
51
+ source_ref: input.source_ref ?? null,
52
+ source_uri: input.source_uri ?? null,
53
+ source_kind: input.source_kind ?? null,
54
+ source_revision_id: input.source_revision_id ?? null,
55
+ revision: input.revision ?? null,
56
+ hash: input.hash ?? null,
57
+ chunk_id: input.chunk_id ?? null,
58
+ start_offset: input.start_offset ?? null,
59
+ end_offset: input.end_offset ?? null,
60
+ status,
61
+ read_only: true,
62
+ citation_required: true,
63
+ resolver: input.resolver ?? null,
64
+ stale: isStaleStatus(status),
65
+ };
66
+ }
67
+
68
+ export function generatedArtifactProvenance(input: {
69
+ generated_from: string;
70
+ artifact_key: string;
71
+ source_refs?: string[];
72
+ citation_required?: boolean;
73
+ }): GeneratedArtifactProvenance {
74
+ return {
75
+ source_owner: 'open-files',
76
+ generated_from: input.generated_from,
77
+ artifact_key: input.artifact_key,
78
+ source_refs: input.source_refs ?? [],
79
+ read_only_sources: true,
80
+ citation_required: input.citation_required ?? true,
81
+ raw_source_bytes_stored_in_open_knowledge: false,
82
+ };
83
+ }
84
+
85
+ export function withProvenance<T extends Record<string, unknown>>(
86
+ metadata: T,
87
+ provenance: KnowledgeProvenance | GeneratedArtifactProvenance,
88
+ ): T & { provenance: KnowledgeProvenance | GeneratedArtifactProvenance } {
89
+ return {
90
+ ...metadata,
91
+ provenance,
92
+ };
93
+ }
package/src/service.ts CHANGED
@@ -1,12 +1,19 @@
1
1
  import { createArtifactStore } from './artifact-store';
2
2
  import { consumeOpenFilesOutbox } from './outbox-consume';
3
- import { getKnowledgeDbStats, migrateKnowledgeDb } from './knowledge-db';
3
+ import { getKnowledgeDbStats, migrateKnowledgeDb, openKnowledgeDb } from './knowledge-db';
4
4
  import { ingestOpenFilesManifest } from './manifest-ingest';
5
5
  import { ingestSourceRef } from './source-ingest';
6
6
  import { resolveOpenFilesSource } from './source-resolver';
7
7
  import { providerStatus, listModelRegistry, type ProviderStatusResult, type ModelRegistryEntry } from './providers';
8
8
  import { resolveSafetyPolicy } from './safety';
9
- import { initializeWikiLayout } from './wiki-layout';
9
+ import {
10
+ recordStorageObjects,
11
+ resolveStorageContract,
12
+ validateStorageConfig,
13
+ type StorageContract,
14
+ type StorageValidationResult,
15
+ } from './storage-contract';
16
+ import { initializeWikiLayout, recordWikiLayoutCatalog } from './wiki-layout';
10
17
  import {
11
18
  ensureKnowledgeWorkspace,
12
19
  readKnowledgeConfig,
@@ -76,6 +83,14 @@ export class KnowledgeService {
76
83
  return createArtifactStore(this.config(), this.ensureWorkspace());
77
84
  }
78
85
 
86
+ storageContract(): StorageContract {
87
+ return resolveStorageContract(this.config(), this.ensureWorkspace(), this.scope);
88
+ }
89
+
90
+ validateStorage(): StorageValidationResult {
91
+ return validateStorageConfig(this.config(), this.ensureWorkspace());
92
+ }
93
+
79
94
  paths(): KnowledgePathsResult {
80
95
  const workspace = this.ensureWorkspace();
81
96
  return {
@@ -107,7 +122,17 @@ export class KnowledgeService {
107
122
  }
108
123
 
109
124
  async initWiki() {
110
- return initializeWikiLayout(this.artifactStore());
125
+ const workspace = this.ensureWorkspace();
126
+ migrateKnowledgeDb(workspace.knowledgeDbPath);
127
+ const result = await initializeWikiLayout(this.artifactStore());
128
+ const db = openKnowledgeDb(workspace.knowledgeDbPath);
129
+ try {
130
+ recordStorageObjects(db, result.artifacts);
131
+ recordWikiLayoutCatalog(db, result.artifacts);
132
+ } finally {
133
+ db.close();
134
+ }
135
+ return result;
111
136
  }
112
137
 
113
138
  async ingestManifest(input: string) {
@@ -1,5 +1,6 @@
1
1
  import type { Database } from 'bun:sqlite';
2
2
  import { migrateKnowledgeDb, openKnowledgeDb } from './knowledge-db';
3
+ import { sourceProvenance, type KnowledgeProvenance } from './provenance';
3
4
  import { catalogSourceUriForRef, parseSourceRef, revisionIdForSourceRef } from './source-ref';
4
5
  import { assertWriteAllowed, recordAuditEvent, type SafetyPolicy } from './safety';
5
6
 
@@ -38,6 +39,7 @@ export interface ResolvedSourceChunk {
38
39
  end_offset: number | null;
39
40
  metadata: Record<string, unknown>;
40
41
  evidence: SourceResolverEvidence;
42
+ provenance: KnowledgeProvenance;
41
43
  }
42
44
 
43
45
  export interface ResolvedSourceCitation {
@@ -48,6 +50,7 @@ export interface ResolvedSourceCitation {
48
50
  start_offset: number | null;
49
51
  end_offset: number | null;
50
52
  evidence: SourceResolverEvidence;
53
+ provenance: KnowledgeProvenance;
51
54
  }
52
55
 
53
56
  export interface SourceResolveResult {
@@ -326,6 +329,19 @@ export async function resolveOpenFilesSource(options: SourceResolveOptions): Pro
326
329
  end_offset: row.end_offset,
327
330
  resolved_at: resolvedAt,
328
331
  };
332
+ const provenance = sourceProvenance({
333
+ source_ref: evidence.source_ref,
334
+ source_uri: evidence.source_uri,
335
+ source_kind: source.kind,
336
+ source_revision_id: evidence.source_revision_id,
337
+ revision: evidence.revision,
338
+ hash: evidence.hash,
339
+ chunk_id: row.id,
340
+ start_offset: row.start_offset,
341
+ end_offset: row.end_offset,
342
+ status: metadataString(metadata, ['status']),
343
+ resolver: evidence.resolver,
344
+ });
329
345
  return {
330
346
  id: row.id,
331
347
  kind: row.kind,
@@ -336,6 +352,7 @@ export async function resolveOpenFilesSource(options: SourceResolveOptions): Pro
336
352
  end_offset: row.end_offset,
337
353
  metadata,
338
354
  evidence,
355
+ provenance,
339
356
  };
340
357
  });
341
358
 
@@ -347,6 +364,7 @@ export async function resolveOpenFilesSource(options: SourceResolveOptions): Pro
347
364
  start_offset: chunk.start_offset,
348
365
  end_offset: chunk.end_offset,
349
366
  evidence: chunk.evidence,
367
+ provenance: chunk.provenance,
350
368
  }));
351
369
 
352
370
  recordAuditEvent(db, {