@hawkeyexl/manni 2.0.0 → 2.1.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.
@@ -8,7 +8,7 @@ var __export = (target, all) => {
8
8
  // package.json
9
9
  var package_default = {
10
10
  name: "@hawkeyexl/manni",
11
- version: "2.0.0",
11
+ version: "2.1.0",
12
12
  description: "One command line for documentation that is meant to be checked. `manni meta` validates the presence and format of document metadata (Markdown frontmatter and more) against JSON Schema, built for CI. Formerly docmeta.",
13
13
  type: "module",
14
14
  bin: {
@@ -60,6 +60,8 @@ var package_default = {
60
60
  "docs:check-api": "node scripts/check-api-reference.mjs",
61
61
  "docs:check-links": "node scripts/check-doc-links.mjs",
62
62
  "docs:check-tables": "node scripts/check-table-rows.mjs",
63
+ "docs:check-versions": "node scripts/check-versions.mjs",
64
+ "docs:sync-versions": "node scripts/sync-versions.mjs",
63
65
  "schemas:sync": "node scripts/sync-builtin-schemas.mjs",
64
66
  "schemas:check": "node scripts/check-builtin-schemas.mjs",
65
67
  "schemas:check-published": "node scripts/check-published-schemas.mjs",
@@ -122,6 +124,85 @@ var package_default = {
122
124
  }
123
125
  };
124
126
 
127
+ // src/shared/run.ts
128
+ import { realpathSync } from "node:fs";
129
+ import { fileURLToPath } from "node:url";
130
+ import { CommanderError } from "commander";
131
+
132
+ // src/shared/errors.ts
133
+ var ToolError = class extends Error {
134
+ /** clig.dev: 2 is operational/usage; 1 is reserved for findings. */
135
+ exitCode = 2;
136
+ constructor(message) {
137
+ super(message);
138
+ this.name = "ToolError";
139
+ }
140
+ };
141
+ function errorMessage(err) {
142
+ if (err instanceof Error) return err.message;
143
+ if (typeof err === "string") return err;
144
+ if (typeof err === "number" || typeof err === "bigint") return String(err);
145
+ if (typeof err === "object" && err !== null && "message" in err && typeof err.message === "string") {
146
+ return err.message;
147
+ }
148
+ try {
149
+ const json = JSON.stringify(err);
150
+ if (json !== void 0) return json;
151
+ } catch {
152
+ }
153
+ return String(err);
154
+ }
155
+
156
+ // src/shared/program-name.ts
157
+ var name = "manni";
158
+ function programName() {
159
+ return name;
160
+ }
161
+ function setProgramName(value) {
162
+ name = value;
163
+ }
164
+
165
+ // src/shared/run.ts
166
+ function fail(err) {
167
+ const msg = err instanceof ToolError ? err.message : `Unexpected error: ${errorMessage(err)}`;
168
+ process.stderr.write(`${programName()}: ${msg}
169
+ `);
170
+ process.exit(2);
171
+ }
172
+ var STDIN_LINES_MARKER = "\0";
173
+ var STDIN_WITH_LINES = /^-(:[1-9][0-9]*(?:-[1-9][0-9]*)?)$/;
174
+ function normalizeStdinArgv(argv) {
175
+ return argv.map((arg) => {
176
+ const lines = STDIN_WITH_LINES.exec(arg)?.[1];
177
+ return lines === void 0 ? arg : `${STDIN_LINES_MARKER}${lines}`;
178
+ });
179
+ }
180
+ async function runProgram(program, argv = process.argv) {
181
+ setProgramName(program.name());
182
+ try {
183
+ await program.parseAsync(normalizeStdinArgv(argv));
184
+ } catch (err) {
185
+ if (err instanceof CommanderError) {
186
+ process.exitCode = err.exitCode === 0 ? 0 : 2;
187
+ return;
188
+ }
189
+ throw err;
190
+ }
191
+ }
192
+ function isMainModule(moduleUrl) {
193
+ const entry = process.argv[1];
194
+ if (!entry) return false;
195
+ try {
196
+ return realpathSync(entry) === fileURLToPath(moduleUrl);
197
+ } catch {
198
+ return false;
199
+ }
200
+ }
201
+ function runIfMain(moduleUrl, build) {
202
+ if (!isMainModule(moduleUrl)) return;
203
+ runProgram(build()).catch(fail);
204
+ }
205
+
125
206
  // src/shared/collections.ts
126
207
  import picomatch2 from "picomatch";
127
208
 
@@ -390,13 +471,53 @@ function isMember(collection, relPath) {
390
471
  return !collection.exclude.some((glob) => matchesFileGlob(relPath, glob));
391
472
  }
392
473
  function normalizeEntry(entry) {
393
- const posix2 = entry.replace(/\\/g, "/").replace(/^\.\//, "");
394
- return posix2.endsWith("/") ? posix2.slice(0, -1) : posix2;
474
+ const posix4 = entry.replace(/\\/g, "/").replace(/^\.\//, "");
475
+ return posix4.endsWith("/") ? posix4.slice(0, -1) : posix4;
395
476
  }
396
477
  function isGlob(entry) {
397
478
  return picomatch2.scan(entry).isGlob;
398
479
  }
399
480
 
481
+ // src/meta/core/integrity.ts
482
+ import { createHash } from "node:crypto";
483
+ var INTEGRITY_ALGORITHM = "sha256";
484
+ var INTEGRITY_SHAPE = `${INTEGRITY_ALGORITHM}-<64 hex characters>`;
485
+ var INTEGRITY_RE = new RegExp(`^${INTEGRITY_ALGORITHM}-([0-9a-f]{64})$`);
486
+ function integrityOf(bytes) {
487
+ return `${INTEGRITY_ALGORITHM}-${createHash(INTEGRITY_ALGORITHM).update(bytes).digest("hex")}`;
488
+ }
489
+ function isIntegrity(value) {
490
+ return INTEGRITY_RE.test(value);
491
+ }
492
+ function toLf(bytes) {
493
+ const out = new Uint8Array(bytes.length);
494
+ let n = 0;
495
+ for (let i = 0; i < bytes.length; i++) {
496
+ const byte = bytes[i];
497
+ if (byte === void 0) continue;
498
+ if (byte === 13 && bytes[i + 1] === 10) continue;
499
+ out[n++] = byte;
500
+ }
501
+ return out.subarray(0, n);
502
+ }
503
+ function toCrlf(bytes) {
504
+ const lf = toLf(bytes);
505
+ const out = new Uint8Array(lf.length * 2);
506
+ let n = 0;
507
+ for (let i = 0; i < lf.length; i++) {
508
+ const byte = lf[i];
509
+ if (byte === void 0) continue;
510
+ if (byte === 10) out[n++] = 13;
511
+ out[n++] = byte;
512
+ }
513
+ return out.subarray(0, n);
514
+ }
515
+ function diagnoseIntegrity(bytes, expected) {
516
+ if (integrityOf(toLf(bytes)) === expected) return "line-endings";
517
+ if (integrityOf(toCrlf(bytes)) === expected) return "line-endings";
518
+ return "content";
519
+ }
520
+
400
521
  // src/meta/extractors/frontmatter.ts
401
522
  import { parseDocument, LineCounter, isMap, isSeq, isScalar } from "yaml";
402
523
  import { parse as parseToml } from "smol-toml";
@@ -451,22 +572,22 @@ function locateFrontmatter(content) {
451
572
  while (cursor <= body.length) {
452
573
  const nl = body.indexOf("\n", cursor);
453
574
  const hasNl = nl !== -1;
454
- const lineEnd = hasNl ? nl : body.length;
455
- const hadCr = lineEnd > cursor && body.charCodeAt(lineEnd - 1) === 13;
456
- const line = body.slice(cursor, hadCr ? lineEnd - 1 : lineEnd);
575
+ const lineEnd2 = hasNl ? nl : body.length;
576
+ const hadCr = lineEnd2 > cursor && body.charCodeAt(lineEnd2 - 1) === 13;
577
+ const line = body.slice(cursor, hadCr ? lineEnd2 - 1 : lineEnd2);
457
578
  if (fence.isClose(line)) {
458
579
  return {
459
580
  flavor: fence.flavor,
460
581
  openStart: bom,
461
582
  innerStart: innerStart + bom,
462
583
  innerEnd: cursor + bom,
463
- closeEnd: (hasNl ? lineEnd + 1 : lineEnd) + bom,
584
+ closeEnd: (hasNl ? lineEnd2 + 1 : lineEnd2) + bom,
464
585
  eol,
465
586
  firstContentLine: 2
466
587
  };
467
588
  }
468
589
  if (!hasNl) break;
469
- cursor = lineEnd + 1;
590
+ cursor = lineEnd2 + 1;
470
591
  }
471
592
  return null;
472
593
  }
@@ -605,30 +726,6 @@ function extractFrontmatter(content, format) {
605
726
  }
606
727
  }
607
728
 
608
- // src/shared/errors.ts
609
- var ToolError = class extends Error {
610
- /** clig.dev: 2 is operational/usage; 1 is reserved for findings. */
611
- exitCode = 2;
612
- constructor(message) {
613
- super(message);
614
- this.name = "ToolError";
615
- }
616
- };
617
- function errorMessage(err) {
618
- if (err instanceof Error) return err.message;
619
- if (typeof err === "string") return err;
620
- if (typeof err === "number" || typeof err === "bigint") return String(err);
621
- if (typeof err === "object" && err !== null && "message" in err && typeof err.message === "string") {
622
- return err.message;
623
- }
624
- try {
625
- const json = JSON.stringify(err);
626
- if (json !== void 0) return json;
627
- } catch {
628
- }
629
- return String(err);
630
- }
631
-
632
729
  // src/meta/types.ts
633
730
  function isErrorSeverity(e) {
634
731
  return e.severity === void 0 || e.severity === "error";
@@ -1202,8 +1299,8 @@ function ditaShape(content, root, filePath) {
1202
1299
  const lower = filePath?.toLowerCase() ?? "";
1203
1300
  const byExtension2 = (lower.endsWith(".dita") || lower.endsWith(".ditamap")) && DITA_ROOTS.has(name2);
1204
1301
  if (!byDoctype && !byClass && !byArch && !byExtension2) return void 0;
1205
- const isMap6 = /^\s*[-+]\s+map\//.test(cls) || MAP_ROOTS.has(name2) || lower.endsWith(".ditamap");
1206
- return isMap6 ? { kind: "map", container: "topicmeta", preamble: /* @__PURE__ */ new Set(["title"]) } : {
1302
+ const isMap7 = /^\s*[-+]\s+map\//.test(cls) || MAP_ROOTS.has(name2) || lower.endsWith(".ditamap");
1303
+ return isMap7 ? { kind: "map", container: "topicmeta", preamble: /* @__PURE__ */ new Set(["title"]) } : {
1207
1304
  kind: "topic",
1208
1305
  container: "metadata",
1209
1306
  preamble: /* @__PURE__ */ new Set([
@@ -2631,24 +2728,13 @@ function listFormats() {
2631
2728
  // src/meta/core/write-file.ts
2632
2729
  import { writeFile, rename, rm, stat, chmod } from "node:fs/promises";
2633
2730
  import { dirname, join, basename } from "node:path";
2634
-
2635
- // src/shared/program-name.ts
2636
- var name = "manni";
2637
- function programName() {
2638
- return name;
2639
- }
2640
- function setProgramName(value) {
2641
- name = value;
2642
- }
2643
-
2644
- // src/meta/core/write-file.ts
2645
2731
  var LOCKED = /* @__PURE__ */ new Set(["EPERM", "EBUSY", "EACCES"]);
2646
2732
  var RENAME_ATTEMPTS = 3;
2647
2733
  function errorCode(err) {
2648
2734
  return typeof err === "object" && err !== null && "code" in err ? String(err.code) : void 0;
2649
2735
  }
2650
- var wait = (ms) => new Promise((resolve18) => {
2651
- setTimeout(resolve18, ms);
2736
+ var wait = (ms) => new Promise((resolve19) => {
2737
+ setTimeout(resolve19, ms);
2652
2738
  });
2653
2739
  async function writeFileAtomic(path, contents) {
2654
2740
  const tmp = join(
@@ -2689,7 +2775,7 @@ async function writeFileAtomic(path, contents) {
2689
2775
  }
2690
2776
 
2691
2777
  // src/meta/core/schema-cache.ts
2692
- import { createHash } from "node:crypto";
2778
+ import { createHash as createHash2 } from "node:crypto";
2693
2779
  import { mkdir, readFile, stat as stat2 } from "node:fs/promises";
2694
2780
  import { join as join2 } from "node:path";
2695
2781
  var SCHEMA_CACHE_DIR = ".manni/meta/schema-cache";
@@ -2719,7 +2805,7 @@ var SchemaCache = class {
2719
2805
  * directory.
2720
2806
  */
2721
2807
  entryPath(url) {
2722
- const key = createHash("sha256").update(url).digest("hex");
2808
+ const key = createHash2("sha256").update(url).digest("hex");
2723
2809
  return join2(this.dir, `${key}.json`);
2724
2810
  }
2725
2811
  /**
@@ -2796,46 +2882,6 @@ var SchemaCache = class {
2796
2882
  }
2797
2883
  };
2798
2884
 
2799
- // src/meta/core/integrity.ts
2800
- import { createHash as createHash2 } from "node:crypto";
2801
- var INTEGRITY_ALGORITHM = "sha256";
2802
- var INTEGRITY_SHAPE = `${INTEGRITY_ALGORITHM}-<64 hex characters>`;
2803
- var INTEGRITY_RE = new RegExp(`^${INTEGRITY_ALGORITHM}-([0-9a-f]{64})$`);
2804
- function integrityOf(bytes) {
2805
- return `${INTEGRITY_ALGORITHM}-${createHash2(INTEGRITY_ALGORITHM).update(bytes).digest("hex")}`;
2806
- }
2807
- function isIntegrity(value) {
2808
- return INTEGRITY_RE.test(value);
2809
- }
2810
- function toLf(bytes) {
2811
- const out = new Uint8Array(bytes.length);
2812
- let n = 0;
2813
- for (let i = 0; i < bytes.length; i++) {
2814
- const byte = bytes[i];
2815
- if (byte === void 0) continue;
2816
- if (byte === 13 && bytes[i + 1] === 10) continue;
2817
- out[n++] = byte;
2818
- }
2819
- return out.subarray(0, n);
2820
- }
2821
- function toCrlf(bytes) {
2822
- const lf = toLf(bytes);
2823
- const out = new Uint8Array(lf.length * 2);
2824
- let n = 0;
2825
- for (let i = 0; i < lf.length; i++) {
2826
- const byte = lf[i];
2827
- if (byte === void 0) continue;
2828
- if (byte === 10) out[n++] = 13;
2829
- out[n++] = byte;
2830
- }
2831
- return out.subarray(0, n);
2832
- }
2833
- function diagnoseIntegrity(bytes, expected) {
2834
- if (integrityOf(toLf(bytes)) === expected) return "line-endings";
2835
- if (integrityOf(toCrlf(bytes)) === expected) return "line-endings";
2836
- return "content";
2837
- }
2838
-
2839
2885
  // src/meta/core/schema-registry.ts
2840
2886
  import { readFile as readFile2 } from "node:fs/promises";
2841
2887
  import { resolve as resolvePath } from "node:path";
@@ -5241,8 +5287,8 @@ var SCHEMA_KEYS = [
5241
5287
  "multipleOf"
5242
5288
  ];
5243
5289
  var RETRY_DELAY_MS = 500;
5244
- var wait2 = (ms) => new Promise((resolve18) => {
5245
- setTimeout(resolve18, ms);
5290
+ var wait2 = (ms) => new Promise((resolve19) => {
5291
+ setTimeout(resolve19, ms);
5246
5292
  });
5247
5293
  var RetryableFetchError = class extends DocmetaError {
5248
5294
  };
@@ -5657,8 +5703,10 @@ var DERIVABLE_FIELDS = [
5657
5703
  "authors",
5658
5704
  "owner",
5659
5705
  "reviewed-by",
5660
- "last-reviewed"
5706
+ "last-reviewed",
5707
+ "provenance"
5661
5708
  ];
5709
+ var PROVENANCE_FIELD = "provenance";
5662
5710
  function isBuiltinField(x) {
5663
5711
  return DERIVABLE_FIELDS.includes(x);
5664
5712
  }
@@ -6143,7 +6191,7 @@ function asElementPaths(value, where, source) {
6143
6191
  }
6144
6192
  var OVERRIDE_KEYS = ["collection", "files", "schemas", "elements"];
6145
6193
  var CHECK_KEYS = ["name", "query"];
6146
- var DERIVE_KEYS = ["fields", "sources", "codeowners", "commands"];
6194
+ var DERIVE_KEYS = ["fields", "sources", "codeowners", "commands", "machines"];
6147
6195
  var DERIVE_COMMAND_KEYS = ["run", "timeout"];
6148
6196
  var BUILTIN_CLAIMED_BY = {
6149
6197
  created: "git",
@@ -6151,7 +6199,8 @@ var BUILTIN_CLAIMED_BY = {
6151
6199
  authors: "git",
6152
6200
  owner: "codeowners",
6153
6201
  "reviewed-by": "GitHub or GitLab",
6154
- "last-reviewed": "GitHub or GitLab"
6202
+ "last-reviewed": "GitHub or GitLab",
6203
+ provenance: "git"
6155
6204
  };
6156
6205
  var DERIVED_TABLE_RESERVED = /* @__PURE__ */ new Set(["_path", "_sources"]);
6157
6206
  var DERIVABLE_LIST = `Derivable fields: ${DERIVABLE_FIELDS.join(", ")}, or any key with an entry in derive.commands.`;
@@ -6396,6 +6445,7 @@ function assertManagedFieldsUnowned(config, collections, source, section) {
6396
6445
  if (owner !== void 0) refuse(`derive.commands.${key}`, owner);
6397
6446
  }
6398
6447
  for (const [i, field] of (config.derive?.fields ?? []).entries()) {
6448
+ if (field === PROVENANCE_FIELD) continue;
6399
6449
  const owner = manifestOwning(field, collections);
6400
6450
  if (owner !== void 0) refuse(`derive.fields[${i}] "${field}"`, owner);
6401
6451
  }
@@ -6408,9 +6458,9 @@ function parseDerive(raw, source) {
6408
6458
  }
6409
6459
  const e = raw;
6410
6460
  rejectUnknownKeys2(e, DERIVE_KEYS, '"derive"', source);
6411
- if (e.fields === void 0 && e.sources === void 0 && e.codeowners === void 0 && e.commands === void 0) {
6461
+ if (e.fields === void 0 && e.sources === void 0 && e.codeowners === void 0 && e.commands === void 0 && e.machines === void 0) {
6412
6462
  throw new DocmetaError(
6413
- `${source}: "derive" sets nothing. Give it \`fields:\` to manage, \`commands:\` to derive from a command, or \`sources:\` or \`codeowners:\` to shape the reads.`
6463
+ `${source}: "derive" sets nothing. Give it \`fields:\` to manage, \`commands:\` to derive from a command, or \`sources:\`, \`codeowners:\` or \`machines:\` to shape the reads.`
6414
6464
  );
6415
6465
  }
6416
6466
  const commands = e.commands === void 0 ? void 0 : parseDeriveCommands(e.commands, source);
@@ -6461,6 +6511,24 @@ function parseDerive(raw, source) {
6461
6511
  derive.codeowners = e.codeowners;
6462
6512
  }
6463
6513
  if (commands !== void 0) derive.commands = commands;
6514
+ if (e.machines !== void 0) {
6515
+ if (!Array.isArray(e.machines) || e.machines.length === 0 || e.machines.some((m) => typeof m !== "string")) {
6516
+ throw new DocmetaError(
6517
+ `${source}: derive.machines must be a non-empty list of globs, matched against a trailer's name and its email. Default: ["*[bot]"].`
6518
+ );
6519
+ }
6520
+ const machines = [];
6521
+ e.machines.forEach((m, i) => {
6522
+ if (m.trim() === "") {
6523
+ throw new DocmetaError(`${source}: derive.machines[${i}] is blank; a glob must name something.`);
6524
+ }
6525
+ if (machines.includes(m)) {
6526
+ throw new DocmetaError(`${source}: derive.machines lists "${m}" twice.`);
6527
+ }
6528
+ machines.push(m);
6529
+ });
6530
+ derive.machines = machines;
6531
+ }
6464
6532
  return derive;
6465
6533
  }
6466
6534
  function parseDeriveCommands(raw, source) {
@@ -7741,8 +7809,8 @@ var RENAME_ATTEMPTS2 = 3;
7741
7809
  function errorCode2(err) {
7742
7810
  return typeof err === "object" && err !== null && "code" in err ? String(err.code) : void 0;
7743
7811
  }
7744
- var wait3 = (ms) => new Promise((resolve18) => {
7745
- setTimeout(resolve18, ms);
7812
+ var wait3 = (ms) => new Promise((resolve19) => {
7813
+ setTimeout(resolve19, ms);
7746
7814
  });
7747
7815
  async function writeTextAtomic(path, text2) {
7748
7816
  const tmp = join5(
@@ -8968,8 +9036,8 @@ var ReviewCache = class {
8968
9036
  }
8969
9037
  if (!isRecord2(raw)) return void 0;
8970
9038
  if (raw.version !== REVIEW_CACHE_VERSION || raw.key !== key) return void 0;
8971
- const change2 = readChange(raw.change);
8972
- return change2 ?? void 0;
9039
+ const change = readChange(raw.change);
9040
+ return change ?? void 0;
8973
9041
  }
8974
9042
  set(key, value) {
8975
9043
  if (!this.enabled) return;
@@ -8998,9 +9066,9 @@ function cachedClient(client, cache, identity) {
8998
9066
  const key = `${identity.host}/${identity.project}/${sha}`;
8999
9067
  const hit = cache.get(key);
9000
9068
  if (hit !== void 0) return hit;
9001
- const change2 = await client.mergedChangeFor(sha);
9002
- if (change2 !== null) cache.set(key, change2);
9003
- return change2;
9069
+ const change = await client.mergedChangeFor(sha);
9070
+ if (change !== null) cache.set(key, change);
9071
+ return change;
9004
9072
  }
9005
9073
  };
9006
9074
  }
@@ -9035,7 +9103,7 @@ function run(bin, args, opts) {
9035
9103
  done = true;
9036
9104
  settle(r);
9037
9105
  };
9038
- const fail = (err) => {
9106
+ const fail2 = (err) => {
9039
9107
  if (done) return;
9040
9108
  done = true;
9041
9109
  reject(err);
@@ -9047,7 +9115,7 @@ function run(bin, args, opts) {
9047
9115
  windowsHide: true
9048
9116
  });
9049
9117
  } catch (err) {
9050
- fail(err instanceof Error ? err : new Error(String(err)));
9118
+ fail2(err instanceof Error ? err : new Error(String(err)));
9051
9119
  return;
9052
9120
  }
9053
9121
  let stdout = "";
@@ -9083,7 +9151,7 @@ function run(bin, args, opts) {
9083
9151
  }, opts.timeoutMs ?? DEFAULT_TIMEOUT_MS2);
9084
9152
  child.on("error", (err) => {
9085
9153
  clearTimeout(timer);
9086
- fail(err.code === "ENOENT" ? new BinMissing(err.message) : err);
9154
+ fail2(err.code === "ENOENT" ? new BinMissing(err.message) : err);
9087
9155
  });
9088
9156
  child.on("close", (code) => {
9089
9157
  clearTimeout(timer);
@@ -9494,17 +9562,17 @@ async function deriveFromReviews(inputs, client) {
9494
9562
  records.set(input.label, NOTHING);
9495
9563
  continue;
9496
9564
  }
9497
- const change2 = await changeFor(input.sha);
9498
- records.set(input.label, factsFrom(change2, kind));
9565
+ const change = await changeFor(input.sha);
9566
+ records.set(input.label, factsFrom(change, kind));
9499
9567
  }
9500
9568
  return { status, records };
9501
9569
  }
9502
- function factsFrom(change2, kind) {
9503
- if (change2 === null || change2.approvals.length === 0) return NOTHING;
9504
- const ordered = byTime(change2.approvals);
9570
+ function factsFrom(change, kind) {
9571
+ if (change === null || change.approvals.length === 0) return NOTHING;
9572
+ const ordered = byTime(change.approvals);
9505
9573
  const latest = ordered.at(-1);
9506
9574
  if (latest === void 0) return NOTHING;
9507
- const evidence = kind === "gitlab" ? `gitlab MR !${change2.id}` : `github PR #${change2.id}`;
9575
+ const evidence = kind === "gitlab" ? `gitlab MR !${change.id}` : `github PR #${change.id}`;
9508
9576
  const dateEvidence = kind === "gitlab" ? `${evidence} (merge date; GitLab records no approval time)` : evidence;
9509
9577
  return {
9510
9578
  "reviewed-by": {
@@ -9742,87 +9810,631 @@ function toPosix2(p) {
9742
9810
  // src/meta/core/derive/git.ts
9743
9811
  import { constants as bufferConstants } from "node:buffer";
9744
9812
  import { spawn as spawn3 } from "node:child_process";
9745
- import { dirname as dirname8, extname as extname2, relative as relative10, sep as sep5 } from "node:path";
9746
- var DEFAULT_BULK_THRESHOLD = 32;
9747
- var NULL_SHA = "0".repeat(40);
9748
- var MAX_GIT_OUTPUT_BYTES = Math.min(
9749
- 512 * 1024 * 1024,
9750
- bufferConstants.MAX_STRING_LENGTH
9751
- );
9752
- var HistoryTooLarge = class extends Error {
9753
- };
9754
- var BlobsUnreadable = class extends Error {
9755
- constructor(detail) {
9756
- super(detail);
9757
- this.detail = detail;
9813
+ import { dirname as dirname8, extname as extname2, isAbsolute as isAbsolute5, posix as posix2, relative as relative10, sep as sep5 } from "node:path";
9814
+ import { parse as parseYaml4 } from "yaml";
9815
+
9816
+ // src/shared/pin.ts
9817
+ var LineRangeError = class extends ToolError {
9818
+ constructor(message) {
9819
+ super(message);
9820
+ this.name = "LineRangeError";
9758
9821
  }
9759
- detail;
9760
9822
  };
9761
- function bodyOf(content, fenced) {
9762
- if (fenced !== true) return content;
9763
- const loc = locateFrontmatter(content);
9764
- if (!loc) return content;
9765
- return content.slice(0, loc.openStart) + content.slice(loc.closeEnd);
9823
+ function normalizeText(text2) {
9824
+ const body = text2.charCodeAt(0) === 65279 ? text2.slice(1) : text2;
9825
+ return body.replace(/\r\n/g, "\n");
9826
+ }
9827
+ function splitLines(text2) {
9828
+ const parts = normalizeText(text2).split("\n");
9829
+ if (parts.length > 0 && parts[parts.length - 1] === "") parts.pop();
9830
+ return parts;
9831
+ }
9832
+ function sliceLines(lines, range, label) {
9833
+ const start = range?.start;
9834
+ if (start === void 0) return lines.join("\n");
9835
+ const end = range?.end ?? start;
9836
+ if (end < start) {
9837
+ throw new LineRangeError(`${label ?? "the source"} range ${String(start)}-${String(end)} ends before it starts.`);
9838
+ }
9839
+ if (start > lines.length || end > lines.length) {
9840
+ const first = start > lines.length ? start : end;
9841
+ throw new LineRangeError(`${label ?? "the source"} has ${lines.length} lines; line ${first} is out of range.`);
9842
+ }
9843
+ return lines.slice(start - 1, end).join("\n");
9844
+ }
9845
+ function hashRange(text2, range, key) {
9846
+ return hashLines(sliceLines(splitLines(text2), range), key);
9847
+ }
9848
+ function hashLines(joined, key) {
9849
+ return key === void 0 ? integrityOf(Buffer.from(joined, "utf8")) : keyedPin(joined, key);
9850
+ }
9851
+ function isKeyedPin(pin) {
9852
+ return pin.startsWith(KEYED_PIN_PREFIX);
9853
+ }
9854
+ function pinOfLines(lines, range) {
9855
+ if (range.start < 1 || range.end > lines.length || range.end < range.start) return void 0;
9856
+ return hashLines(lines.slice(range.start - 1, range.end).join("\n"));
9857
+ }
9858
+ var MOVE_WINDOW_LINES = 2e3;
9859
+ var MOVE_BUDGET_BYTES = 64 * 1024 * 1024;
9860
+ var MAX_RANGE_LINES = 5e3;
9861
+ function findWindows(lines, length, pin, key, opts) {
9862
+ const starts = [];
9863
+ const lastStart = lines.length - length + 1;
9864
+ if (length < 1 || lastStart < 1) return { starts, truncated: false };
9865
+ const joined = (start) => lines.slice(start - 1, start - 1 + length).join("\n");
9866
+ const original = opts?.original;
9867
+ if (original !== void 0) {
9868
+ const first = original[0];
9869
+ for (let start = 1; start <= lastStart; start++) {
9870
+ if (lines[start - 1] !== first) continue;
9871
+ let same = true;
9872
+ for (let i = 1; i < length; i++) {
9873
+ if (lines[start - 1 + i] !== original[i]) {
9874
+ same = false;
9875
+ break;
9876
+ }
9877
+ }
9878
+ if (same && hashLines(joined(start), key) === pin) starts.push(start);
9879
+ }
9880
+ return { starts, truncated: false };
9881
+ }
9882
+ const budget = opts?.budget ?? MOVE_BUDGET_BYTES;
9883
+ const around = opts?.around ?? 1;
9884
+ const bandStart = Math.max(1, around - MOVE_WINDOW_LINES);
9885
+ const bandEnd = Math.min(lastStart, around + MOVE_WINDOW_LINES);
9886
+ const order = [];
9887
+ for (let start = bandStart; start <= bandEnd; start++) order.push(start);
9888
+ for (let start = 1; start < bandStart; start++) order.push(start);
9889
+ for (let start = bandEnd + 1; start <= lastStart; start++) order.push(start);
9890
+ let spent = 0;
9891
+ for (const start of order) {
9892
+ const text2 = joined(start);
9893
+ const bytes = Buffer.byteLength(text2, "utf8");
9894
+ if (spent + bytes > budget) return { starts, truncated: true };
9895
+ spent += bytes;
9896
+ if (hashLines(text2, key) === pin) starts.push(start);
9897
+ }
9898
+ return { starts, truncated: false };
9899
+ }
9900
+ var RANGE_SPEC = /^([1-9][0-9]*)(?:-([1-9][0-9]*))?$/;
9901
+ function parseLines(spec) {
9902
+ if (typeof spec === "number") {
9903
+ return Number.isInteger(spec) && spec >= 1 ? { start: spec, end: spec } : void 0;
9904
+ }
9905
+ const m = RANGE_SPEC.exec(spec);
9906
+ const startText = m?.[1];
9907
+ if (startText === void 0) return void 0;
9908
+ const start = Number(startText);
9909
+ const endText = m?.[2];
9910
+ const end = endText === void 0 ? start : Number(endText);
9911
+ return end < start ? void 0 : { start, end };
9912
+ }
9913
+ function lineSpec(lines) {
9914
+ return lines.start === lines.end ? lines.start : `${String(lines.start)}-${String(lines.end)}`;
9915
+ }
9916
+ function spellLines(lines) {
9917
+ return String(lineSpec(lines));
9918
+ }
9919
+ function toFileLines(lines, bodyLine) {
9920
+ return { start: bodyLine + lines.start - 1, end: bodyLine + lines.end - 1 };
9921
+ }
9922
+ function toBodyLines(lines, bodyLine) {
9923
+ return { start: lines.start - bodyLine + 1, end: lines.end - bodyLine + 1 };
9924
+ }
9925
+ function splitPageArgument(arg) {
9926
+ const text2 = arg.startsWith(STDIN_LINES_MARKER) ? `-${arg.slice(STDIN_LINES_MARKER.length)}` : arg;
9927
+ const m = /:([1-9][0-9]*)(?:-([1-9][0-9]*))?$/.exec(text2);
9928
+ const head = m === null ? text2 : text2.slice(0, m.index);
9929
+ if (m === null || head === "") return { page: arg };
9930
+ const start = Number(m[1]);
9931
+ const end = m[2] === void 0 ? start : Number(m[2]);
9932
+ if (end < start) {
9933
+ throw new LineRangeError(`Invalid range "${arg}": end line ${end} is before start line ${start}.`);
9934
+ }
9935
+ return { page: head, lines: { start, end } };
9766
9936
  }
9767
- async function deriveFromGit(inputs, opts) {
9768
- const records = /* @__PURE__ */ new Map();
9769
- const byRoot = groupByRoot(inputs);
9770
- if (byRoot.size === 0) {
9771
- return {
9772
- status: {
9773
- available: false,
9774
- reason: "no git repository contains the documents"
9775
- },
9776
- records
9777
- };
9937
+
9938
+ // src/meta/core/derive/provenance.ts
9939
+ import picomatch5 from "picomatch";
9940
+ var ZERO_SHA = "0000000000000000000000000000000000000000";
9941
+ var DEFAULT_MACHINES = ["*[bot]"];
9942
+ var PROVENANCE_POINTER = "/provenance";
9943
+ var PLAIN_PIN = /^sha256-[0-9a-f]{64}$/;
9944
+ function provenanceEntries(value) {
9945
+ if (!Array.isArray(value)) return [];
9946
+ const out = [];
9947
+ for (const item of value) {
9948
+ if (typeof item !== "object" || item === null || Array.isArray(item)) continue;
9949
+ const record = item;
9950
+ const machine = record["generated-by"];
9951
+ const lines = record.lines;
9952
+ const integrity = record.integrity;
9953
+ if (typeof machine !== "string" || machine === "") continue;
9954
+ if (typeof lines !== "string" && typeof lines !== "number") continue;
9955
+ if (parseLines(lines) === void 0) continue;
9956
+ if (typeof integrity !== "string" || !PLAIN_PIN.test(integrity)) continue;
9957
+ out.push({ ...record, "generated-by": machine, lines, integrity });
9778
9958
  }
9779
- const threshold = opts.bulkThreshold ?? DEFAULT_BULK_THRESHOLD;
9780
- const maxBytes = opts.maxOutputBytes ?? MAX_GIT_OUTPUT_BYTES;
9781
- const reasons = [];
9782
- for (const [root, bucket] of byRoot) {
9783
- const run2 = (args, stdin) => runGit(args, root, stdin, maxBytes);
9784
- const shallow = await run2(["rev-parse", "--is-shallow-repository"]);
9785
- if (shallow.missing) {
9786
- return {
9787
- status: { available: false, reason: "git is not on PATH" },
9788
- records: /* @__PURE__ */ new Map()
9789
- };
9959
+ return out;
9960
+ }
9961
+ function readProvenancePage(content, opts) {
9962
+ const lines = splitLines(content);
9963
+ const loc = opts?.fenced === false ? null : locateFrontmatter(content);
9964
+ if (loc === null) return { lines, bodyLine: 1, body: lines, stamp: [] };
9965
+ const head = content.slice(0, loc.closeEnd);
9966
+ const newlines = head.split("\n").length - 1;
9967
+ const bodyLine = newlines + (head.endsWith("\n") ? 1 : 2);
9968
+ let stamp = [];
9969
+ try {
9970
+ stamp = provenanceEntries(extractFrontmatter(content, "markdown").data.provenance);
9971
+ } catch {
9972
+ stamp = [];
9973
+ }
9974
+ return { lines, bodyLine, body: lines.slice(bodyLine - 1), stamp };
9975
+ }
9976
+ var HEADER = /^([0-9a-f]{40}(?:[0-9a-f]{24})?) ([1-9][0-9]*) ([1-9][0-9]*)(?: ([1-9][0-9]*))?$/;
9977
+ var ALL_ZERO = /^0+$/;
9978
+ function parseLinePorcelain(text2) {
9979
+ const rows = text2.split("\n");
9980
+ if (rows.length > 0 && rows[rows.length - 1] === "") rows.pop();
9981
+ const out = [];
9982
+ let i = 0;
9983
+ while (i < rows.length) {
9984
+ const headerRow = rows[i] ?? "";
9985
+ const m = HEADER.exec(headerRow);
9986
+ const sha = m?.[1];
9987
+ const orig = m?.[2];
9988
+ const final = m?.[3];
9989
+ if (sha === void 0 || orig === void 0 || final === void 0) {
9990
+ throw new Error(`porcelain: expected a header at row ${i + 1}, got ${JSON.stringify(headerRow)}`);
9991
+ }
9992
+ const finalLine = Number(final);
9993
+ const count = m?.[4];
9994
+ const fields = /* @__PURE__ */ new Map();
9995
+ i++;
9996
+ for (let row = rows[i]; row !== void 0 && !row.startsWith(" "); row = rows[i]) {
9997
+ const space2 = row.indexOf(" ");
9998
+ if (space2 === -1) fields.set(row, true);
9999
+ else fields.set(row.slice(0, space2), row.slice(space2 + 1));
10000
+ i++;
9790
10001
  }
9791
- if (shallow.code !== 0) {
9792
- reasons.push(`git could not read the repository at ${root}`);
9793
- continue;
10002
+ const contentRow = rows[i];
10003
+ if (contentRow === void 0) throw new Error(`porcelain: line ${finalLine} has no content row`);
10004
+ const author = fields.get("author");
10005
+ const filename = fields.get("filename");
10006
+ if (typeof author !== "string" || typeof filename !== "string") {
10007
+ throw new Error(`porcelain: line ${finalLine} has no full header; run blame with --line-porcelain`);
9794
10008
  }
9795
- if (text(shallow).trim() === "true") {
9796
- reasons.push(
9797
- `this checkout is shallow; use actions/checkout with fetch-depth: 0 (${root})`
9798
- );
10009
+ const mail = fields.get("author-mail");
10010
+ const previous = fields.get("previous");
10011
+ const space = typeof previous === "string" ? previous.indexOf(" ") : -1;
10012
+ out.push({
10013
+ sha,
10014
+ origLine: Number(orig),
10015
+ finalLine,
10016
+ ...count !== void 0 ? { groupCount: Number(count) } : {},
10017
+ author,
10018
+ authorMail: typeof mail === "string" ? mail.replace(/^<(.*)>$/, "$1") : "",
10019
+ filename,
10020
+ ...typeof previous === "string" && space !== -1 ? { previous: { sha: previous.slice(0, space), filename: previous.slice(space + 1) } } : {},
10021
+ boundary: fields.get("boundary") === true,
10022
+ uncommitted: ALL_ZERO.test(sha),
10023
+ content: contentRow.slice(1)
10024
+ });
10025
+ i++;
10026
+ }
10027
+ return out;
10028
+ }
10029
+ var IDENTITY_GLOB = { literalBrackets: true, dot: true, bash: true };
10030
+ function machineIdentity(value, machines) {
10031
+ const m = /^(.*?)\s*<([^>]*)>\s*$/.exec(value);
10032
+ const name2 = m === null ? value.trim() : m[1] ?? "";
10033
+ const email = m === null ? "" : m[2] ?? "";
10034
+ const match = (s) => s !== "" && machines.some((glob) => picomatch5.isMatch(s, glob, IDENTITY_GLOB));
10035
+ if (!match(name2) && !match(email)) return void 0;
10036
+ return name2 !== "" ? name2 : email;
10037
+ }
10038
+ function stampEvidence(commit, origLine, fenced) {
10039
+ if (commit.blob === void 0) return void 0;
10040
+ const page = readProvenancePage(commit.blob, fenced === void 0 ? void 0 : { fenced });
10041
+ const body = origLine - page.bodyLine + 1;
10042
+ if (body < 1) return void 0;
10043
+ for (const entry of commit.stamp === void 0 ? page.stamp : provenanceEntries(commit.stamp)) {
10044
+ const span = parseLines(entry.lines);
10045
+ if (span === void 0 || body < span.start || body > span.end) continue;
10046
+ if (pinOfLines(page.body, span) === entry.integrity) return entry["generated-by"];
10047
+ }
10048
+ return void 0;
10049
+ }
10050
+ function recordedEvidence(line, ctx) {
10051
+ if (line.uncommitted) return { rule: 5, sha: line.sha };
10052
+ const commit = ctx.commits.get(line.sha);
10053
+ if (commit === void 0) throw new Error(`no commit ${line.sha}`);
10054
+ const stamped = stampEvidence(commit, line.origLine, ctx.fenced);
10055
+ if (stamped !== void 0) return { machine: stamped, rule: 2, sha: line.sha };
10056
+ const generated = commit.trailers.generatedBy.map((v) => v.trim()).find((v) => v !== "");
10057
+ if (generated !== void 0) return { machine: generated, rule: 3, sha: line.sha };
10058
+ for (const value of commit.trailers.coAuthoredBy) {
10059
+ const machine = machineIdentity(value.trim(), ctx.machines);
10060
+ if (machine !== void 0) return { machine, rule: 4, sha: line.sha };
10061
+ }
10062
+ return { rule: 5, sha: line.sha };
10063
+ }
10064
+ function resolveLineEvidence(line, ctx) {
10065
+ if (line.uncommitted && ctx.generatedBy !== void 0 && ctx.generatedBy !== "") {
10066
+ return { machine: ctx.generatedBy, rule: 1, sha: line.sha };
10067
+ }
10068
+ return recordedEvidence(line, ctx);
10069
+ }
10070
+ function pinOf(lines, span) {
10071
+ const pin = pinOfLines(lines, span);
10072
+ if (pin === void 0) throw new Error(`provenance: lines ${spellLines(span)} fall outside the body`);
10073
+ return pin;
10074
+ }
10075
+ function contextOf(input) {
10076
+ return {
10077
+ commits: input.commits,
10078
+ machines: input.machines ?? DEFAULT_MACHINES,
10079
+ ...input.generatedBy !== void 0 ? { generatedBy: input.generatedBy } : {},
10080
+ ...input.fenced !== void 0 ? { fenced: input.fenced } : {}
10081
+ };
10082
+ }
10083
+ function readOptions(fenced) {
10084
+ return fenced === void 0 ? void 0 : { fenced };
10085
+ }
10086
+ function deriveProvenance(input) {
10087
+ const page = readProvenancePage(input.content, readOptions(input.fenced));
10088
+ const ctx = contextOf(input);
10089
+ const evidenceByLine = /* @__PURE__ */ new Map();
10090
+ for (const line of input.blame) {
10091
+ const body = line.finalLine - page.bodyLine + 1;
10092
+ if (body < 1) continue;
10093
+ evidenceByLine.set(body, resolveLineEvidence(line, ctx));
10094
+ }
10095
+ const groups = [];
10096
+ let open3;
10097
+ for (let n = 1; n <= page.body.length; n++) {
10098
+ const evidence = evidenceByLine.get(n);
10099
+ const machine = evidence?.machine;
10100
+ if (open3 !== void 0 && machine === open3.machine && n === open3.end + 1) {
10101
+ open3.end = n;
10102
+ continue;
10103
+ }
10104
+ if (open3 !== void 0) groups.push(open3);
10105
+ open3 = machine === void 0 || evidence === void 0 ? void 0 : { machine, start: n, end: n, first: evidence };
10106
+ }
10107
+ if (open3 !== void 0) groups.push(open3);
10108
+ return {
10109
+ page,
10110
+ evidenceByLine,
10111
+ derived: groups.map((g) => {
10112
+ const span = { start: g.start, end: g.end };
10113
+ return {
10114
+ entry: { "generated-by": g.machine, lines: lineSpec(span), integrity: pinOf(page.body, span) },
10115
+ span,
10116
+ evidence: g.first
10117
+ };
10118
+ })
10119
+ };
10120
+ }
10121
+ function nearest(candidates, recorded) {
10122
+ let best;
10123
+ for (const c of candidates) {
10124
+ const d = Math.abs(c.span.start - recorded.start);
10125
+ if (best === void 0 || d < best.d || d === best.d && c.span.start < best.c.span.start) best = { c, d };
10126
+ }
10127
+ return best?.c;
10128
+ }
10129
+ var sameSpan = (a, b) => a.start === b.start && a.end === b.end;
10130
+ function scanEvidence(evidenceByLine, span, machine) {
10131
+ let anyMachine = false;
10132
+ for (let n = span.start; n <= span.end; n++) {
10133
+ const evidence = evidenceByLine.get(n);
10134
+ if (evidence?.machine === void 0) continue;
10135
+ anyMachine = true;
10136
+ if (evidence.machine !== machine) return { contradiction: evidence, anyMachine };
10137
+ }
10138
+ return { anyMachine };
10139
+ }
10140
+ function compareProvenance(stamped, derivation) {
10141
+ const { page, evidenceByLine, derived } = derivation;
10142
+ const claimed = /* @__PURE__ */ new Set();
10143
+ const results = [];
10144
+ for (const entry of provenanceEntries(stamped)) {
10145
+ const recorded = parseLines(entry.lines);
10146
+ if (recorded === void 0) continue;
10147
+ const machine = entry["generated-by"];
10148
+ const takenHere = [...claimed].some((d) => sameSpan(d.span, recorded));
10149
+ if (!takenHere && pinOfLines(page.body, recorded) === entry.integrity) {
10150
+ const here = scanEvidence(evidenceByLine, recorded, machine);
10151
+ if (here.contradiction === void 0) {
10152
+ const exact = derived.find(
10153
+ (d) => !claimed.has(d) && sameSpan(d.span, recorded) && d.entry.integrity === entry.integrity
10154
+ );
10155
+ if (exact !== void 0) claimed.add(exact);
10156
+ results.push({ status: "current", entry, span: recorded, ...here.anyMachine ? {} : { noEvidence: true } });
10157
+ continue;
10158
+ }
10159
+ }
10160
+ const pool = derived.filter((d) => !claimed.has(d) && d.entry.integrity === entry.integrity);
10161
+ const hit = nearest(pool, recorded);
10162
+ if (hit !== void 0) {
10163
+ claimed.add(hit);
10164
+ if (hit.entry["generated-by"] !== machine) {
10165
+ results.push({ status: "stale", entry, span: hit.span, evidence: hit.evidence });
10166
+ } else {
10167
+ results.push({ status: sameSpan(hit.span, recorded) ? "current" : "moved", entry, span: hit.span });
10168
+ }
10169
+ continue;
10170
+ }
10171
+ const width = recorded.end - recorded.start + 1;
10172
+ const claimedSpans = [...claimed].map((d) => d.span);
10173
+ const windows = findWindows(page.body, width, entry.integrity, void 0, { around: recorded.start, budget: Infinity }).starts.map((start) => ({ span: { start, end: start + width - 1 } })).filter((w) => !claimedSpans.some((s) => sameSpan(s, w.span)));
10174
+ const found = nearest(windows, recorded);
10175
+ if (found === void 0) {
10176
+ results.push({ status: "changed", entry, span: recorded });
10177
+ continue;
10178
+ }
10179
+ const { contradiction, anyMachine } = scanEvidence(evidenceByLine, found.span, machine);
10180
+ if (contradiction !== void 0) {
10181
+ results.push({ status: "stale", entry, span: found.span, evidence: contradiction });
10182
+ continue;
10183
+ }
10184
+ results.push({
10185
+ status: sameSpan(found.span, recorded) ? "current" : "moved",
10186
+ entry,
10187
+ span: found.span,
10188
+ ...anyMachine ? {} : { noEvidence: true }
10189
+ });
10190
+ }
10191
+ const covering = results.map((r) => r.span);
10192
+ for (const d of derived) {
10193
+ if (claimed.has(d)) continue;
10194
+ let uncovered = false;
10195
+ for (let n = d.span.start; n <= d.span.end && !uncovered; n++) {
10196
+ uncovered = !covering.some((s) => n >= s.start && n <= s.end);
10197
+ }
10198
+ if (uncovered) results.push({ status: "unset", entry: d.entry, span: d.span, evidence: d.evidence });
10199
+ }
10200
+ return results;
10201
+ }
10202
+ function planProvenanceWrite(comparisons, derivation) {
10203
+ const kept = [];
10204
+ for (const r of comparisons) {
10205
+ if (r.status === "current") kept.push({ entry: r.entry, start: r.span.start });
10206
+ else if (r.status === "moved") kept.push({ entry: { ...r.entry, lines: lineSpec(r.span) }, start: r.span.start });
10207
+ }
10208
+ const same = (a, b) => a["generated-by"] === b["generated-by"] && String(a.lines) === String(b.lines) && a.integrity === b.integrity;
10209
+ const out = [...kept];
10210
+ for (const d of derivation.derived) {
10211
+ if (!kept.some((k) => same(k.entry, d.entry))) out.push({ entry: d.entry, start: d.span.start });
10212
+ }
10213
+ return out.sort((a, b) => a.start - b.start).map((k) => k.entry);
10214
+ }
10215
+ var short = (sha) => sha.slice(0, 7);
10216
+ function fileSpelling(span, bodyLine) {
10217
+ return spellLines(toFileLines(span, bodyLine));
10218
+ }
10219
+ function evidenceText(evidence) {
10220
+ if (evidence?.machine === void 0) return "pin";
10221
+ return evidence.rule === 1 ? "uncommitted" : `blame ${short(evidence.sha)}`;
10222
+ }
10223
+ function evidenceRef(evidence) {
10224
+ if (evidence === void 0) return "no evidence";
10225
+ return evidence.rule === 1 ? "uncommitted" : short(evidence.sha);
10226
+ }
10227
+ function rangeResults(comparisons, bodyLine) {
10228
+ return comparisons.map((r) => {
10229
+ const lines = fileSpelling(r.span, bodyLine);
10230
+ const recorded = parseLines(r.entry.lines);
10231
+ const from = recorded === void 0 ? void 0 : fileSpelling(recorded, bodyLine);
10232
+ const machine = r.entry["generated-by"];
10233
+ switch (r.status) {
10234
+ case "stale":
10235
+ return {
10236
+ lines,
10237
+ "generated-by": r.evidence?.machine ?? machine,
10238
+ integrity: r.entry.integrity,
10239
+ status: r.status,
10240
+ evidence: evidenceText(r.evidence),
10241
+ ...from !== void 0 ? { from: { lines: from, "generated-by": machine } } : {},
10242
+ written: false
10243
+ };
10244
+ case "moved":
10245
+ return {
10246
+ lines,
10247
+ "generated-by": machine,
10248
+ integrity: r.entry.integrity,
10249
+ status: r.status,
10250
+ evidence: "pin",
10251
+ ...from !== void 0 ? { from: { lines: from, "generated-by": machine } } : {},
10252
+ written: false
10253
+ };
10254
+ case "unset":
10255
+ return { lines, "generated-by": machine, integrity: r.entry.integrity, status: r.status, evidence: evidenceText(r.evidence), written: false };
10256
+ case "current":
10257
+ case "changed":
10258
+ return { lines, "generated-by": machine, integrity: r.entry.integrity, status: r.status, evidence: "pin", written: false };
10259
+ }
10260
+ });
10261
+ }
10262
+ function provenanceFindings(comparisons, bodyLine) {
10263
+ const findings = [];
10264
+ for (const r of comparisons) {
10265
+ const machine = r.entry["generated-by"];
10266
+ const lines = fileSpelling(r.span, bodyLine);
10267
+ let message;
10268
+ switch (r.status) {
10269
+ case "changed":
10270
+ message = `provenance lines ${lines} changed since ${machine} wrote them`;
10271
+ break;
10272
+ case "stale":
10273
+ message = `provenance lines ${lines} say ${machine}; blame says ${r.evidence?.machine ?? "another machine"} (${evidenceRef(r.evidence)})`;
10274
+ break;
10275
+ case "unset":
10276
+ message = `provenance is unset for lines ${lines}; blame says ${machine} (${evidenceRef(r.evidence)})`;
10277
+ break;
10278
+ case "current":
10279
+ case "moved":
10280
+ continue;
10281
+ }
10282
+ findings.push({
10283
+ schema: DERIVED_STALE_SCHEMA,
10284
+ keyword: DERIVED_KEYWORD,
10285
+ subject: `provenance ${r.entry.integrity}`,
10286
+ instancePath: PROVENANCE_POINTER,
10287
+ message: `${message} \u2014 run manni meta derive`,
10288
+ line: toFileLines(r.span, bodyLine).start
10289
+ });
10290
+ }
10291
+ return findings;
10292
+ }
10293
+ function renderProvenanceValue(entries, bodyLine) {
10294
+ return entries.map((e) => {
10295
+ const span = parseLines(e.lines);
10296
+ const lines = span === void 0 ? String(e.lines) : fileSpelling(span, bodyLine);
10297
+ return `lines ${lines} ${e["generated-by"]}`;
10298
+ }).join("; ");
10299
+ }
10300
+ function parseProvenanceTarget(arg) {
10301
+ try {
10302
+ return splitPageArgument(arg);
10303
+ } catch (err) {
10304
+ if (err instanceof LineRangeError) throw new DocmetaError(`${arg} ends before it starts.`);
10305
+ throw err;
10306
+ }
10307
+ }
10308
+ function attributeRange(input) {
10309
+ const { target } = input;
10310
+ const { page: path, lines: range } = parseProvenanceTarget(target);
10311
+ if (range === void 0) throw new Error(`provenance: ${target} names no lines`);
10312
+ const page = readProvenancePage(input.content, readOptions(input.fenced));
10313
+ if (range.end > page.lines.length) {
10314
+ throw new DocmetaError(
10315
+ `${path} has no lines ${spellLines(range)}: the file ends at line ${page.lines.length}.`
10316
+ );
10317
+ }
10318
+ if (range.start < page.bodyLine) {
10319
+ throw new DocmetaError(
10320
+ `${target} reaches into the frontmatter; provenance pins body lines, which start at line ${page.bodyLine}.`
10321
+ );
10322
+ }
10323
+ const ctx = contextOf(input);
10324
+ for (const line of input.blame) {
10325
+ if (line.finalLine < range.start || line.finalLine > range.end) continue;
10326
+ const evidence = recordedEvidence(line, ctx);
10327
+ if (evidence.machine !== void 0 && evidence.machine !== input.generatedBy) {
10328
+ throw new DocmetaError(
10329
+ `${target}: blame attributes these lines to ${evidence.machine} (${short(evidence.sha)}); --generated-by cannot overrule a recorded machine.`
10330
+ );
10331
+ }
10332
+ }
10333
+ const span = toBodyLines(range, page.bodyLine);
10334
+ return { "generated-by": input.generatedBy, lines: lineSpec(span), integrity: pinOf(page.body, span) };
10335
+ }
10336
+
10337
+ // src/meta/core/derive/git.ts
10338
+ var DEFAULT_BULK_THRESHOLD = 32;
10339
+ var NULL_SHA = "0".repeat(40);
10340
+ var MAX_GIT_OUTPUT_BYTES = Math.min(
10341
+ 512 * 1024 * 1024,
10342
+ bufferConstants.MAX_STRING_LENGTH
10343
+ );
10344
+ var HistoryTooLarge = class extends Error {
10345
+ };
10346
+ var BlobsUnreadable = class extends Error {
10347
+ constructor(detail) {
10348
+ super(detail);
10349
+ this.detail = detail;
10350
+ }
10351
+ detail;
10352
+ };
10353
+ var BlameUnreadable = class extends Error {
10354
+ constructor(rel, detail) {
10355
+ super(rel);
10356
+ this.rel = rel;
10357
+ this.detail = detail;
10358
+ }
10359
+ rel;
10360
+ detail;
10361
+ };
10362
+ function bodyOf(content, fenced) {
10363
+ if (fenced !== true) return content;
10364
+ const loc = locateFrontmatter(content);
10365
+ if (!loc) return content;
10366
+ return content.slice(0, loc.openStart) + content.slice(loc.closeEnd);
10367
+ }
10368
+ async function deriveFromGit(inputs, opts) {
10369
+ const records = /* @__PURE__ */ new Map();
10370
+ const byRoot = groupByRoot(inputs);
10371
+ if (byRoot.size === 0) {
10372
+ return {
10373
+ status: {
10374
+ available: false,
10375
+ reason: "no git repository contains the documents"
10376
+ },
10377
+ records
10378
+ };
10379
+ }
10380
+ const threshold = opts.bulkThreshold ?? DEFAULT_BULK_THRESHOLD;
10381
+ const machines = opts.machines ?? DEFAULT_MACHINES;
10382
+ const wantsProvenance = opts.fields?.includes(PROVENANCE_FIELD) ?? false;
10383
+ const maxBytes = opts.maxOutputBytes ?? MAX_GIT_OUTPUT_BYTES;
10384
+ const reasons = [];
10385
+ for (const [root, bucket] of byRoot) {
10386
+ const run2 = (args, stdin) => runGit(args, root, stdin, maxBytes);
10387
+ const shallow = await run2(["rev-parse", "--is-shallow-repository"]);
10388
+ if (shallow.missing) {
10389
+ return {
10390
+ status: { available: false, reason: "git is not on PATH" },
10391
+ records: /* @__PURE__ */ new Map()
10392
+ };
10393
+ }
10394
+ if (shallow.code !== 0) {
10395
+ reasons.push(`git could not read the repository at ${root}`);
10396
+ continue;
10397
+ }
10398
+ if (text(shallow).trim() === "true") {
10399
+ reasons.push(
10400
+ `this checkout is shallow; use actions/checkout with fetch-depth: 0 (${root})`
10401
+ );
9799
10402
  continue;
9800
10403
  }
9801
- let histories;
9802
- let blobs;
9803
10404
  try {
9804
- histories = bucket.length <= threshold ? await perFileHistories(run2, bucket) : await bulkHistories(run2, bucket);
10405
+ const histories = bucket.length <= threshold ? await perFileHistories(run2, bucket) : await bulkHistories(run2, bucket);
9805
10406
  if (histories === null) {
9806
10407
  reasons.push(`git could not read the history at ${root}`);
9807
10408
  continue;
9808
10409
  }
9809
- blobs = await fetchBlobs(run2, neededBlobs(histories));
10410
+ const blobs = await fetchBlobs(run2, neededBlobs(histories));
10411
+ for (const entry of bucket) {
10412
+ const history = histories.get(entry.rel) ?? [];
10413
+ const facts = judge(entry.input, root, history, blobs, opts.now, machines);
10414
+ if (wantsProvenance) {
10415
+ const derived = await provenanceFor(run2, root, entry, history, blobs, {
10416
+ machines,
10417
+ ...opts.generatedBy !== void 0 ? { generatedBy: opts.generatedBy } : {}
10418
+ });
10419
+ facts.provenance = derived.value;
10420
+ facts.provenanceDerivation = derived.derivation;
10421
+ }
10422
+ records.set(entry.input.label, facts);
10423
+ }
9810
10424
  } catch (err) {
9811
10425
  if (err instanceof BlobsUnreadable) {
9812
10426
  reasons.push(`git cat-file could not read the history (${root}): ${err.detail}`);
9813
10427
  continue;
9814
10428
  }
10429
+ if (err instanceof BlameUnreadable) {
10430
+ const why = err.detail === "" ? "" : `: ${err.detail}`;
10431
+ reasons.push(`git blame could not read ${err.rel} (${root})${why}`);
10432
+ continue;
10433
+ }
9815
10434
  if (!(err instanceof HistoryTooLarge)) throw err;
9816
10435
  reasons.push(`git history is too large to read in one pass (${root})`);
9817
10436
  continue;
9818
10437
  }
9819
- for (const entry of bucket) {
9820
- const history = histories.get(entry.rel) ?? [];
9821
- records.set(
9822
- entry.input.label,
9823
- judge(entry.input, root, history, blobs, opts.now)
9824
- );
9825
- }
9826
10438
  }
9827
10439
  if (reasons.length > 0) {
9828
10440
  return {
@@ -9850,7 +10462,7 @@ function groupByRoot(inputs) {
9850
10462
  }
9851
10463
  return byRoot;
9852
10464
  }
9853
- var RECORD_FORMAT = "--format=%x1e%H%x00%aI%x00%an%x00%ae%x00%(trailers:key=Co-authored-by,valueonly,unfold,separator=%x1f)%x00%(trailers:key=Reviewed-by,valueonly,unfold,separator=%x1f)";
10465
+ var RECORD_FORMAT = "--format=%x1e%H%x00%aI%x00%an%x00%ae%x00%(trailers:key=Co-authored-by,valueonly,unfold,separator=%x1f)%x00%(trailers:key=Reviewed-by,valueonly,unfold,separator=%x1f)%x00%(trailers:key=Generated-by,valueonly,unfold,separator=%x1f)";
9854
10466
  var LOG_ARGS = [
9855
10467
  "-c",
9856
10468
  "core.quotePath=false",
@@ -9916,6 +10528,7 @@ function parseLog(text2) {
9916
10528
  authorEmail: fields[3] ?? "",
9917
10529
  coAuthors: splitTrailers(fields[4]),
9918
10530
  reviewedBy: splitTrailers(fields[5]),
10531
+ generatedBy: splitTrailers(fields[6]),
9919
10532
  raw
9920
10533
  });
9921
10534
  }
@@ -10006,6 +10619,7 @@ function attribute(records, wanted) {
10006
10619
  authorEmail: record.authorEmail,
10007
10620
  coAuthors: record.coAuthors,
10008
10621
  reviewedBy: record.reviewedBy,
10622
+ generatedBy: record.generatedBy,
10009
10623
  oldBlob: entry.oldBlob === NULL_SHA ? null : entry.oldBlob,
10010
10624
  newBlob: entry.newBlob === NULL_SHA ? null : entry.newBlob,
10011
10625
  pathAtCommit: entry.newPath
@@ -10055,7 +10669,7 @@ var git = (value, evidence) => ({
10055
10669
  source: "git",
10056
10670
  evidence
10057
10671
  });
10058
- function judge(input, root, history, blobs, now) {
10672
+ function judge(input, root, history, blobs, now, machines) {
10059
10673
  const fenced = input.extracted.fenced;
10060
10674
  const extractor = extractorFor(input);
10061
10675
  const blobBody = (sha) => sha === null ? null : bodyOf(blobs.get(sha) ?? "", fenced);
@@ -10092,11 +10706,11 @@ function judge(input, root, history, blobs, now) {
10092
10706
  stampsOf(newestBody.oldBlob)
10093
10707
  );
10094
10708
  }
10095
- const authors = bodyChanging.length === 0 ? null : authorsOf(bodyChanging);
10709
+ const authors = bodyChanging.length === 0 ? null : authorsOf(bodyChanging, machines);
10096
10710
  let reviewedBy = null;
10097
10711
  let lastReviewed = null;
10098
10712
  if (!workingChanged && newestBody !== void 0 && newestBody.reviewedBy.length > 0) {
10099
- const evidence = `Reviewed-by trailer in ${short(newestBody.sha)}`;
10713
+ const evidence = `Reviewed-by trailer in ${short2(newestBody.sha)}`;
10100
10714
  reviewedBy = git(newestBody.reviewedBy.map(nameOf), evidence);
10101
10715
  lastReviewed = git(datePart(newestBody.authorDate), evidence);
10102
10716
  }
@@ -10106,6 +10720,7 @@ function judge(input, root, history, blobs, now) {
10106
10720
  authors,
10107
10721
  "reviewed-by": reviewedBy,
10108
10722
  "last-reviewed": lastReviewed,
10723
+ provenance: null,
10109
10724
  lastBodyCommit,
10110
10725
  root
10111
10726
  };
@@ -10113,24 +10728,26 @@ function judge(input, root, history, blobs, now) {
10113
10728
  function createdOf(oldest, stamps) {
10114
10729
  const stamp = stamps["created"];
10115
10730
  if (stamp !== void 0 && stamp !== null) {
10116
- return git(stamp, `stamped in ${short(oldest.sha)}`);
10731
+ return git(stamp, `stamped in ${short2(oldest.sha)}`);
10117
10732
  }
10118
10733
  const date = datePart(oldest.authorDate);
10119
- return git(date, `added in ${short(oldest.sha)} (${date})`);
10734
+ return git(date, `added in ${short2(oldest.sha)} (${date})`);
10120
10735
  }
10121
10736
  function lastUpdatedOf(commit, after, before) {
10122
10737
  const stamp = after["last-updated"];
10123
10738
  if (stamp !== void 0 && stamp !== null && !sameValue(stamp, before["last-updated"])) {
10124
- return git(stamp, `stamped in ${short(commit.sha)}`);
10739
+ return git(stamp, `stamped in ${short2(commit.sha)}`);
10125
10740
  }
10126
10741
  const date = datePart(commit.authorDate);
10127
- return git(date, `body changed in ${short(commit.sha)} (${date})`);
10742
+ return git(date, `body changed in ${short2(commit.sha)} (${date})`);
10128
10743
  }
10129
- function authorsOf(bodyChanging) {
10744
+ function authorsOf(bodyChanging, machines) {
10130
10745
  const seen = /* @__PURE__ */ new Set();
10131
10746
  const names = [];
10132
10747
  const add = (name2, email) => {
10133
- if (name2 === "" || name2.endsWith("[bot]")) return;
10748
+ if (name2 === "") return;
10749
+ const identity = email === null || email === "" ? name2 : `${name2} <${email}>`;
10750
+ if (machineIdentity(identity, machines) !== void 0) return;
10134
10751
  const key = email === null || email === "" ? `name:${name2.toLowerCase()}` : email.toLowerCase();
10135
10752
  if (seen.has(key)) return;
10136
10753
  seen.add(key);
@@ -10143,6 +10760,204 @@ function authorsOf(bodyChanging) {
10143
10760
  const n = bodyChanging.length;
10144
10761
  return git(names, `${n} body-changing ${n === 1 ? "commit" : "commits"}`);
10145
10762
  }
10763
+ function provenanceFenced(extracted) {
10764
+ return extracted.fenced === true || extracted.format === "markdown" || extracted.format === "mdx";
10765
+ }
10766
+ var OUTSIDE_RANGE = "outside-the-attributed-range";
10767
+ var ATTRIBUTED = ":attributed";
10768
+ async function provenanceFor(run2, root, entry, history, blobs, opts) {
10769
+ const { input, rel } = entry;
10770
+ const fenced = provenanceFenced(input.extracted);
10771
+ const blame = await blameOf(run2, rel, input.content);
10772
+ const commits = await commitEvidence(run2, root, entry, blame, history, blobs, input.provenanceManifest);
10773
+ const base = { content: input.content, commits, machines: opts.machines, fenced };
10774
+ let derivation;
10775
+ const attribution = input.attribution;
10776
+ if (attribution !== void 0) {
10777
+ const ranges = attribution.targets.flatMap((target) => {
10778
+ attributeRange({ ...base, blame, target, generatedBy: attribution.generatedBy });
10779
+ const range = parseProvenanceTarget(target).lines;
10780
+ return range === void 0 ? [] : [range];
10781
+ });
10782
+ const inRange = (line) => ranges.some((range) => line.finalLine >= range.start && line.finalLine <= range.end);
10783
+ const attributed = new Map(commits);
10784
+ const trailers = { generatedBy: [attribution.generatedBy], coAuthoredBy: [] };
10785
+ attributed.set(OUTSIDE_RANGE, { sha: OUTSIDE_RANGE, trailers: { generatedBy: [], coAuthoredBy: [] } });
10786
+ const rekeyed = blame.map((line) => {
10787
+ if (inRange(line)) {
10788
+ if (line.uncommitted) return line;
10789
+ const sha = `${line.sha}${ATTRIBUTED}`;
10790
+ attributed.set(sha, { sha, trailers });
10791
+ return { ...line, sha };
10792
+ }
10793
+ return line.uncommitted ? { ...line, sha: OUTSIDE_RANGE, uncommitted: false } : line;
10794
+ });
10795
+ derivation = deriveProvenance({
10796
+ ...base,
10797
+ commits: attributed,
10798
+ generatedBy: attribution.generatedBy,
10799
+ blame: rekeyed
10800
+ });
10801
+ } else {
10802
+ derivation = deriveProvenance({
10803
+ ...base,
10804
+ blame,
10805
+ ...opts.generatedBy !== void 0 ? { generatedBy: opts.generatedBy } : {}
10806
+ });
10807
+ }
10808
+ const entries = derivation.derived.map((d) => d.entry);
10809
+ return {
10810
+ value: entries.length === 0 ? null : git(entries, provenanceEvidence(derivation)),
10811
+ derivation
10812
+ };
10813
+ }
10814
+ function provenanceEvidence(derivation) {
10815
+ const shas = [];
10816
+ let uncommitted = false;
10817
+ for (const range of derivation.derived) {
10818
+ for (let n = range.span.start; n <= range.span.end; n++) {
10819
+ const evidence = derivation.evidenceByLine.get(n);
10820
+ if (evidence?.machine === void 0) continue;
10821
+ if (evidence.rule === 1) uncommitted = true;
10822
+ else if (!shas.includes(evidence.sha)) shas.push(evidence.sha);
10823
+ }
10824
+ }
10825
+ const first = shas[0];
10826
+ if (first === void 0) return "uncommitted";
10827
+ const parts = [`blame ${short2(first)}`];
10828
+ if (shas.length > 1) parts.push(`${String(shas.length)} commits`);
10829
+ if (uncommitted) parts.push("uncommitted");
10830
+ return parts.join(", ");
10831
+ }
10832
+ async function blameOf(run2, rel, content) {
10833
+ const out = await run2([
10834
+ "-c",
10835
+ "core.quotePath=false",
10836
+ "blame",
10837
+ "--no-ignore-revs-file",
10838
+ "--line-porcelain",
10839
+ "--",
10840
+ rel
10841
+ ]);
10842
+ if (out.tooLarge) throw new HistoryTooLarge();
10843
+ if (out.code !== 0) {
10844
+ const inHead = await run2(["cat-file", "-e", `HEAD:${rel}`]);
10845
+ if (inHead.code !== 0) {
10846
+ return splitLines(content).map((line, i) => ({
10847
+ sha: ZERO_SHA,
10848
+ origLine: i + 1,
10849
+ finalLine: i + 1,
10850
+ author: "Not Committed Yet",
10851
+ authorMail: "not.committed.yet",
10852
+ filename: rel,
10853
+ boundary: false,
10854
+ uncommitted: true,
10855
+ content: line
10856
+ }));
10857
+ }
10858
+ throw new BlameUnreadable(rel, out.stderr.trim());
10859
+ }
10860
+ return parseLinePorcelain(text(out));
10861
+ }
10862
+ async function commitEvidence(run2, root, entry, blame, history, blobs, manifest) {
10863
+ const pathAt = /* @__PURE__ */ new Map();
10864
+ for (const line of blame) {
10865
+ if (!line.uncommitted && !pathAt.has(line.sha)) pathAt.set(line.sha, unquotePath(line.filename));
10866
+ }
10867
+ const shas = [...pathAt.keys()];
10868
+ const commits = /* @__PURE__ */ new Map();
10869
+ if (shas.length === 0) return commits;
10870
+ const trailers = /* @__PURE__ */ new Map();
10871
+ const pageBlob = /* @__PURE__ */ new Map();
10872
+ for (const h of history) {
10873
+ trailers.set(h.sha, { generatedBy: h.generatedBy, coAuthoredBy: h.coAuthors });
10874
+ const blob = h.newBlob === null ? void 0 : blobs.get(h.newBlob);
10875
+ if (blob !== void 0) pageBlob.set(h.sha, blob);
10876
+ }
10877
+ const unlogged = shas.filter((sha) => !trailers.has(sha));
10878
+ if (unlogged.length > 0) {
10879
+ const out = await run2(["log", "--no-walk=unsorted", RECORD_FORMAT, ...unlogged]);
10880
+ if (out.tooLarge) throw new HistoryTooLarge();
10881
+ if (out.code !== 0) throw new BlobsUnreadable(`git log exit ${String(out.code)}`);
10882
+ for (const record of parseLog(text(out))) {
10883
+ trailers.set(record.sha, { generatedBy: record.generatedBy, coAuthoredBy: record.coAuthors });
10884
+ }
10885
+ }
10886
+ const manifestRel = manifest === void 0 ? void 0 : insideRoot(root, manifest.absPath);
10887
+ const specs = [];
10888
+ for (const sha of shas) {
10889
+ if (!pageBlob.has(sha)) specs.push(`${sha}:${pathAt.get(sha) ?? ""}`);
10890
+ if (manifestRel !== void 0) specs.push(`${sha}:${manifestRel}`);
10891
+ }
10892
+ const fetched = await fetchSpecs(run2, specs);
10893
+ for (const sha of shas) {
10894
+ const blob = pageBlob.get(sha) ?? fetched.get(`${sha}:${pathAt.get(sha) ?? ""}`);
10895
+ commits.set(sha, {
10896
+ sha,
10897
+ trailers: trailers.get(sha) ?? { generatedBy: [], coAuthoredBy: [] },
10898
+ ...blob !== void 0 ? { blob } : {},
10899
+ ...manifest !== void 0 ? {
10900
+ stamp: manifestRel === void 0 ? [] : manifestStamp(
10901
+ fetched.get(`${sha}:${manifestRel}`),
10902
+ manifest.join === "path" ? entryAt(manifest.entry, entry.rel, pathAt.get(sha) ?? entry.rel) : manifest.entry,
10903
+ manifest.join
10904
+ )
10905
+ } : {}
10906
+ });
10907
+ }
10908
+ return commits;
10909
+ }
10910
+ function insideRoot(root, abs) {
10911
+ const rel = relative10(root, abs);
10912
+ if (rel === "" || rel.startsWith("..") || isAbsolute5(rel)) return void 0;
10913
+ return rel.split(sep5).join("/");
10914
+ }
10915
+ function entryAt(entry, rel, relAtCommit) {
10916
+ if (relAtCommit === rel) return entry;
10917
+ const fromHere = posix2.relative(posix2.dirname(rel), relAtCommit);
10918
+ return posix2.join(posix2.dirname(entry), fromHere);
10919
+ }
10920
+ function manifestStamp(text2, key, join10) {
10921
+ if (text2 === void 0) return [];
10922
+ let data;
10923
+ try {
10924
+ data = parseYaml4(text2);
10925
+ } catch {
10926
+ return [];
10927
+ }
10928
+ if (!isRecord4(data)) return [];
10929
+ for (const [candidate, value] of Object.entries(data)) {
10930
+ const same = join10 === "path" ? posix2.normalize(candidate) === posix2.normalize(key) : candidate === key;
10931
+ if (same && isRecord4(value)) return provenanceEntries(value[PROVENANCE_FIELD]);
10932
+ }
10933
+ return [];
10934
+ }
10935
+ function isRecord4(x) {
10936
+ return typeof x === "object" && x !== null && !Array.isArray(x);
10937
+ }
10938
+ async function fetchSpecs(run2, specs) {
10939
+ const found = /* @__PURE__ */ new Map();
10940
+ if (specs.length === 0) return found;
10941
+ const out = await run2(["cat-file", "--batch"], `${specs.join("\n")}
10942
+ `);
10943
+ if (out.tooLarge) throw new HistoryTooLarge();
10944
+ if (out.code !== 0) throw new BlobsUnreadable(`exit ${String(out.code)}`);
10945
+ const buf = out.raw;
10946
+ let cursor = 0;
10947
+ for (const spec of specs) {
10948
+ const nl = buf.indexOf(10, cursor);
10949
+ if (nl === -1) break;
10950
+ const header = buf.toString("utf8", cursor, nl);
10951
+ cursor = nl + 1;
10952
+ if (header.endsWith(" missing") || header.endsWith(" ambiguous")) continue;
10953
+ const parts = header.split(" ");
10954
+ const size = Number(parts[2]);
10955
+ if (!Number.isFinite(size)) break;
10956
+ if (parts[1] === "blob") found.set(spec, buf.toString("utf8", cursor, cursor + size));
10957
+ cursor += size + 1;
10958
+ }
10959
+ return found;
10960
+ }
10146
10961
  function extractorFor(input) {
10147
10962
  return extractorByName(input.extracted.format) ?? extractorForExtension(extname2(input.absPath));
10148
10963
  }
@@ -10161,7 +10976,7 @@ function sameBody(a, b) {
10161
10976
  function sameValue(a, b) {
10162
10977
  return toJsonText(a) === toJsonText(b);
10163
10978
  }
10164
- var short = (sha) => sha.slice(0, 7);
10979
+ var short2 = (sha) => sha.slice(0, 7);
10165
10980
  var datePart = (iso) => iso.slice(0, 10);
10166
10981
  function localDate(d) {
10167
10982
  const pad2 = (n) => String(n).padStart(2, "0");
@@ -10175,6 +10990,7 @@ function emailOf(trailer) {
10175
10990
  const m = /<([^>]*)>\s*$/.exec(trailer);
10176
10991
  return m?.[1]?.trim() ?? null;
10177
10992
  }
10993
+ var MAX_STDERR_BYTES = 16 * 1024;
10178
10994
  function text(out) {
10179
10995
  try {
10180
10996
  return out.raw.toString("utf8");
@@ -10190,7 +11006,7 @@ function runGit(args, cwd, stdin, maxBytes) {
10190
11006
  done = true;
10191
11007
  settle(out);
10192
11008
  };
10193
- const fail = (err) => {
11009
+ const fail2 = (err) => {
10194
11010
  if (done) return;
10195
11011
  done = true;
10196
11012
  reject(err instanceof Error ? err : new Error(String(err)));
@@ -10199,7 +11015,8 @@ function runGit(args, cwd, stdin, maxBytes) {
10199
11015
  code: null,
10200
11016
  raw: Buffer.alloc(0),
10201
11017
  missing,
10202
- tooLarge: false
11018
+ tooLarge: false,
11019
+ stderr: ""
10203
11020
  });
10204
11021
  let child;
10205
11022
  try {
@@ -10222,7 +11039,14 @@ function runGit(args, cwd, stdin, maxBytes) {
10222
11039
  }
10223
11040
  chunks.push(chunk);
10224
11041
  });
10225
- child.stderr.resume();
11042
+ const errChunks = [];
11043
+ let errTotal = 0;
11044
+ child.stderr.on("data", (chunk) => {
11045
+ if (errTotal >= MAX_STDERR_BYTES) return;
11046
+ const kept = chunk.subarray(0, MAX_STDERR_BYTES - errTotal);
11047
+ errTotal += kept.length;
11048
+ errChunks.push(kept);
11049
+ });
10226
11050
  child.on("error", (err) => {
10227
11051
  finish(failed(err.code === "ENOENT"));
10228
11052
  });
@@ -10231,9 +11055,10 @@ function runGit(args, cwd, stdin, maxBytes) {
10231
11055
  child.on("close", (code) => {
10232
11056
  try {
10233
11057
  const raw = tooLarge ? Buffer.alloc(0) : Buffer.concat(chunks);
10234
- finish({ code, raw, missing: false, tooLarge });
11058
+ const stderr = Buffer.concat(errChunks).toString("utf8");
11059
+ finish({ code, raw, missing: false, tooLarge, stderr });
10235
11060
  } catch (err) {
10236
- fail(err);
11061
+ fail2(err);
10237
11062
  }
10238
11063
  });
10239
11064
  if (stdin === void 0) child.stdin.end();
@@ -10248,7 +11073,8 @@ var FIELD_SOURCES = {
10248
11073
  authors: ["git"],
10249
11074
  owner: ["codeowners"],
10250
11075
  "reviewed-by": ["github", "gitlab", "git"],
10251
- "last-reviewed": ["github", "gitlab", "git"]
11076
+ "last-reviewed": ["github", "gitlab", "git"],
11077
+ provenance: ["git"]
10252
11078
  };
10253
11079
  var REVIEW_SOURCES = ["github", "gitlab"];
10254
11080
  var HOST_NAME = {
@@ -10282,10 +11108,20 @@ async function deriveMetadata(inputs, ctx) {
10282
11108
  const sources = {};
10283
11109
  let git2 = /* @__PURE__ */ new Map();
10284
11110
  if (consulted.has("git")) {
10285
- const result = await deriveFromGit(inputs, { cwd: ctx.cwd, now: ctx.now });
11111
+ const result = await deriveFromGit(inputs, {
11112
+ cwd: ctx.cwd,
11113
+ now: ctx.now,
11114
+ fields: ctx.fields,
11115
+ ...ctx.generatedBy !== void 0 ? { generatedBy: ctx.generatedBy } : {},
11116
+ ...ctx.machines !== void 0 ? { machines: ctx.machines } : {}
11117
+ });
10286
11118
  sources.git = result.status;
10287
11119
  git2 = result.records;
10288
11120
  }
11121
+ const provenance = /* @__PURE__ */ new Map();
11122
+ for (const [label, facts] of git2) {
11123
+ if (facts.provenanceDerivation !== void 0) provenance.set(label, facts.provenanceDerivation);
11124
+ }
10289
11125
  const rootCache = /* @__PURE__ */ new Map();
10290
11126
  const rootOf = (absPath) => {
10291
11127
  const dir = dirname9(absPath);
@@ -10363,7 +11199,7 @@ async function deriveMetadata(inputs, ctx) {
10363
11199
  }
10364
11200
  records.set(input.label, { file: input.label, fields });
10365
11201
  }
10366
- return { records, sources };
11202
+ return { records, sources, ...provenance.size > 0 ? { provenance } : {} };
10367
11203
  }
10368
11204
  function valueFrom(source, field, git2, reviews, owner, commanded) {
10369
11205
  switch (source) {
@@ -10446,6 +11282,9 @@ function commandsOf(config) {
10446
11282
  }
10447
11283
  return out;
10448
11284
  }
11285
+ function machinesOf(config) {
11286
+ return config?.machines ?? DEFAULT_MACHINES;
11287
+ }
10449
11288
 
10450
11289
  // src/meta/core/derive/table.ts
10451
11290
  var DERIVED_VIEW = "derived";
@@ -10460,10 +11299,11 @@ function mentionsDerived(sql) {
10460
11299
  return /\b(?:from|join|update|into|table|view)\s+(?:"derived"|derived\b)/i.test(sql);
10461
11300
  }
10462
11301
  function fieldsForSql(sql, fields) {
10463
- if (sql.includes("*") || /\b_(?:sources|origin)\b/i.test(sql)) return [...fields];
10464
- return fields.filter(
10465
- (field) => new RegExp(`(?<![\\w-])${escapeRegExp(field)}(?![\\w-])`, "i").test(sql)
10466
- );
11302
+ const named = (field) => new RegExp(`(?<![\\w-])${escapeRegExp(field)}(?![\\w-])`, "i").test(sql);
11303
+ if (sql.includes("*") || /\b_(?:sources|origin)\b/i.test(sql)) {
11304
+ return fields.filter((field) => field !== PROVENANCE_FIELD || named(field));
11305
+ }
11306
+ return fields.filter(named);
10467
11307
  }
10468
11308
  function escapeRegExp(s) {
10469
11309
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -10557,16 +11397,18 @@ function createResolvedView(db, docsColumns, commands) {
10557
11397
  async function deriveForTable(inputs, ctx, hint, fields) {
10558
11398
  const derive = ctx.config?.derive;
10559
11399
  const commands = commandsOf(derive);
11400
+ const managesProvenance = derive?.fields.includes(PROVENANCE_FIELD) === true && !fields.includes(PROVENANCE_FIELD);
10560
11401
  const result = await deriveMetadata(inputs, {
10561
11402
  cwd: ctx.cwd,
10562
11403
  base: ctx.base,
10563
11404
  ...ctx.configDir !== void 0 ? { configDir: ctx.configDir } : {},
10564
11405
  sources: derive?.sources ?? [...DERIVE_SOURCES],
10565
- fields,
11406
+ fields: managesProvenance ? [...fields, PROVENANCE_FIELD] : fields,
10566
11407
  ...derive?.codeowners !== void 0 ? { codeowners: derive.codeowners } : {},
10567
11408
  ...commands !== void 0 ? { commands } : {},
10568
11409
  cache: ctx.cache,
10569
- now: ctx.now ?? (() => /* @__PURE__ */ new Date())
11410
+ now: ctx.now ?? (() => /* @__PURE__ */ new Date()),
11411
+ machines: machinesOf(derive)
10570
11412
  });
10571
11413
  assertSourcesAvailable(result.sources, hint);
10572
11414
  return result.records;
@@ -10574,7 +11416,7 @@ async function deriveForTable(inputs, ctx, hint, fields) {
10574
11416
 
10575
11417
  // src/meta/commands/validate.ts
10576
11418
  import { readFile as readFile8 } from "node:fs/promises";
10577
- import { resolve as resolve12, extname as extname3 } from "node:path";
11419
+ import { resolve as resolve13, extname as extname3 } from "node:path";
10578
11420
 
10579
11421
  // src/meta/core/checks.ts
10580
11422
  var CHECK_KEYWORD = "check";
@@ -10711,6 +11553,71 @@ async function runChecks(checks, entries, ctx = {}) {
10711
11553
  return merged;
10712
11554
  }
10713
11555
 
11556
+ // src/meta/core/derive/provenance-place.ts
11557
+ import { isAbsolute as isAbsolute6, relative as relative11, resolve as resolve12, sep as sep6 } from "node:path";
11558
+ var toPosix3 = (p) => p.split(sep6).join("/");
11559
+ function provenanceManifests(collections, configDir, base) {
11560
+ const out = [];
11561
+ for (const collection of collections) {
11562
+ for (const manifest of collection.externalMetadata) {
11563
+ if (!manifest.keys.includes(PROVENANCE_FIELD)) continue;
11564
+ if (classifyRef(manifest.file).kind === "url") {
11565
+ throw new DocmetaError(
11566
+ `collection ${collection.name}: ${PROVENANCE_FIELD} cannot come from a URL manifest, because manni meta derive writes it.`
11567
+ );
11568
+ }
11569
+ const absPath = isAbsolute6(manifest.file) ? manifest.file : resolve12(configDir, manifest.file);
11570
+ const rel = relative11(base, absPath);
11571
+ out.push({
11572
+ collection: collection.name,
11573
+ absPath,
11574
+ file: rel === "" ? "." : toPosix3(rel),
11575
+ join: externalMetadataJoin(manifest)
11576
+ });
11577
+ }
11578
+ }
11579
+ return out;
11580
+ }
11581
+ function provenancePlace(label, data, manifests, collections, configDir, base) {
11582
+ if (manifests.length === 0) return void 0;
11583
+ const members = new Set(memberOf(collections, configDir, base, label));
11584
+ const owning = manifests.filter((m) => members.has(m.collection));
11585
+ const [manifest, second] = owning;
11586
+ if (manifest === void 0) return void 0;
11587
+ if (second !== void 0) {
11588
+ throw new DocmetaError(
11589
+ `${label} is in collections ${manifest.collection} and ${second.collection}, and both keep ${PROVENANCE_FIELD} in a manifest.`
11590
+ );
11591
+ }
11592
+ if (manifest.join === PATH_JOIN) {
11593
+ const entry = toPosix3(relative11(configDir, resolve12(base, label)));
11594
+ return { manifest, absPath: manifest.absPath, entry, join: manifest.join };
11595
+ }
11596
+ const value = data[manifest.join];
11597
+ if (typeof value !== "string" && typeof value !== "number") {
11598
+ throw new DocmetaError(
11599
+ `${label} carries no ${manifest.join}, which ${manifest.file} joins on, so its ${PROVENANCE_FIELD} has no entry there.`
11600
+ );
11601
+ }
11602
+ return { manifest, absPath: manifest.absPath, entry: String(value), join: manifest.join };
11603
+ }
11604
+ function readerManifests(collections, configDir, base) {
11605
+ const local = collections.map((c) => ({
11606
+ ...c,
11607
+ externalMetadata: c.externalMetadata.filter((m) => classifyRef(m.file).kind !== "url")
11608
+ }));
11609
+ return provenanceManifests(local, configDir, base);
11610
+ }
11611
+ function readerPlace(label, data, manifests, collections, configDir, base) {
11612
+ try {
11613
+ const place = provenancePlace(label, data, manifests, collections, configDir, base);
11614
+ return place === void 0 ? void 0 : { absPath: place.absPath, entry: place.entry, join: place.join };
11615
+ } catch (err) {
11616
+ if (err instanceof DocmetaError) return void 0;
11617
+ throw err;
11618
+ }
11619
+ }
11620
+
10714
11621
  // src/meta/commands/validate.ts
10715
11622
  function parseErrorResult(file, format, message, keyword) {
10716
11623
  const err = {
@@ -10834,6 +11741,7 @@ async function runValidate(opts) {
10834
11741
  const deriveFields = new Set(deriveWillRun ? deriveConfig.fields : []);
10835
11742
  const readable = derivableFields(deriveCommands);
10836
11743
  for (const c of derivedChecks) for (const f of fieldsForSql(c.query, readable)) deriveFields.add(f);
11744
+ const provenanceManifests2 = keepDeriveInputs && deriveFields.has(PROVENANCE_FIELD) ? readerManifests(declaredCollections, configDir ?? cwd, base) : [];
10837
11745
  const derivedOnce = once(async () => {
10838
11746
  const derived = await deriveMetadata(deriveInputs, {
10839
11747
  cwd,
@@ -10844,7 +11752,8 @@ async function runValidate(opts) {
10844
11752
  ...deriveConfig?.codeowners !== void 0 ? { codeowners: deriveConfig.codeowners } : {},
10845
11753
  ...deriveCommands !== void 0 ? { commands: deriveCommands } : {},
10846
11754
  cache: opts.cache ?? true,
10847
- now: opts.now ?? (() => /* @__PURE__ */ new Date())
11755
+ now: opts.now ?? (() => /* @__PURE__ */ new Date()),
11756
+ machines: machinesOf(deriveConfig)
10848
11757
  });
10849
11758
  assertSourcesAvailable(
10850
11759
  derived.sources,
@@ -10878,7 +11787,14 @@ async function runValidate(opts) {
10878
11787
  return;
10879
11788
  }
10880
11789
  if (keepDeriveInputs && label !== STDIN_LABEL) {
10881
- deriveInputs.push({ label, absPath: resolve12(base, label), content, extracted });
11790
+ const place = provenanceManifests2.length === 0 ? void 0 : readerPlace(label, extracted.data, provenanceManifests2, declaredCollections, configDir ?? cwd, base);
11791
+ deriveInputs.push({
11792
+ label,
11793
+ absPath: resolve13(base, label),
11794
+ content,
11795
+ extracted,
11796
+ ...place !== void 0 ? { provenanceManifest: place } : {}
11797
+ });
10882
11798
  }
10883
11799
  const merged = mergeExternalMetadata(label, extracted, externalMetadata, members, base, {
10884
11800
  encryptionKey
@@ -10991,7 +11907,7 @@ async function runValidate(opts) {
10991
11907
  await processOne(STDIN_LABEL, content, forcedExtractor.extensions[0] ?? "");
10992
11908
  }
10993
11909
  for (const file of files) {
10994
- const content = await readFile8(resolve12(base, file), "utf8");
11910
+ const content = await readFile8(resolve13(base, file), "utf8");
10995
11911
  await processOne(file, content, extname3(file));
10996
11912
  }
10997
11913
  if (unverified > 0) opts.onNotice?.(unverifiedWarning(unverified));
@@ -11081,18 +11997,23 @@ async function runValidate(opts) {
11081
11997
  }
11082
11998
  marked = [...managed.marked, ...await validator.markedPointers(stamped, managed.refs)];
11083
11999
  }
11084
- const findings = staleFindings(
11085
- fields.map((field) => {
11086
- const compared = compareDerived(
11087
- field,
11088
- managed !== void 0 ? managed.data[field] : input.extracted.data[field],
11089
- record.fields[field]
12000
+ const asserted = (field) => managed !== void 0 ? managed.data[field] : input.extracted.data[field];
12001
+ const findings = fields.flatMap((field) => {
12002
+ if (field === PROVENANCE_FIELD) {
12003
+ const derivation = derived.provenance?.get(input.label);
12004
+ if (derivation === void 0) return [];
12005
+ return provenanceFindings(
12006
+ compareProvenance(provenanceEntries(asserted(field)), derivation),
12007
+ derivation.page.bodyLine
11090
12008
  );
11091
- const at2 = pointerOf(field);
11092
- return marked.some((p) => isAtOrUnder(p, at2)) ? redactedDerived(compared) : compared;
11093
- }),
11094
- input.extracted.lineFor
11095
- );
12009
+ }
12010
+ const compared = compareDerived(field, asserted(field), record.fields[field]);
12011
+ const at2 = pointerOf(field);
12012
+ return staleFindings(
12013
+ [marked.some((p) => isAtOrUnder(p, at2)) ? redactedDerived(compared) : compared],
12014
+ input.extracted.lineFor
12015
+ );
12016
+ });
11096
12017
  if (findings.length === 0) continue;
11097
12018
  result.errors.push(...findings);
11098
12019
  result.ok = false;
@@ -11130,13 +12051,13 @@ async function runValidate(opts) {
11130
12051
 
11131
12052
  // src/meta/commands/get.ts
11132
12053
  import { readFile as readFile9 } from "node:fs/promises";
11133
- import { resolve as resolve13, extname as extname4 } from "node:path";
12054
+ import { resolve as resolve14, extname as extname4 } from "node:path";
11134
12055
  async function runGet(opts) {
11135
12056
  const cwd = opts.cwd ?? process.cwd();
11136
12057
  if (opts.fields.length === 0) {
11137
12058
  throw new DocmetaError("Specify at least one field to get.");
11138
12059
  }
11139
- const { config, inputs, base, configDir, collections, fromCollections, configFile } = await resolveRunConfig({
12060
+ const { config, inputs, base, configDir, collections, declaredCollections, fromCollections, configFile } = await resolveRunConfig({
11140
12061
  cwd,
11141
12062
  configPath: opts.configPath,
11142
12063
  noConfig: opts.noConfig,
@@ -11195,7 +12116,10 @@ async function runGet(opts) {
11195
12116
  const deriving = opts.derived !== false;
11196
12117
  const derivableFields2 = deriving ? opts.fields.filter((f) => isBuiltinField(f) || Object.hasOwn(commands, f)) : [];
11197
12118
  const deriveInputs = [];
12119
+ const wantsProvenance = opts.fields.includes(PROVENANCE_FIELD);
12120
+ const provenanceManifests2 = deriving && derivableFields2.includes(PROVENANCE_FIELD) ? readerManifests(declaredCollections, configDir ?? cwd, base) : [];
11198
12121
  const carried = /* @__PURE__ */ new Map();
12122
+ const bodyLines = /* @__PURE__ */ new Map();
11199
12123
  const readOne = (label, content, extension) => {
11200
12124
  const extractor = forced ?? extractorForExtension(extension);
11201
12125
  if (!extractor) {
@@ -11210,13 +12134,18 @@ async function runGet(opts) {
11210
12134
  elements: resolveElements(label, config, members)
11211
12135
  });
11212
12136
  if (deriving && label !== STDIN_LABEL) {
12137
+ const place = provenanceManifests2.length === 0 ? void 0 : readerPlace(label, own2.data, provenanceManifests2, declaredCollections, configDir ?? cwd, base);
11213
12138
  deriveInputs.push({
11214
12139
  label,
11215
- absPath: resolve13(base, label),
12140
+ absPath: resolve14(base, label),
11216
12141
  content,
11217
- extracted: own2
12142
+ extracted: own2,
12143
+ ...place !== void 0 ? { provenanceManifest: place } : {}
11218
12144
  });
11219
12145
  }
12146
+ if (wantsProvenance) {
12147
+ bodyLines.set(label, readProvenancePage(content, { fenced: provenanceFenced(own2) }).bodyLine);
12148
+ }
11220
12149
  extracted = mergeExternalMetadata(label, own2, externalMetadata, members, base, {
11221
12150
  encryptionKey: joinKey
11222
12151
  }).extracted;
@@ -11233,7 +12162,13 @@ async function runGet(opts) {
11233
12162
  carries[f] = carriesField(extracted.data, f);
11234
12163
  }
11235
12164
  carried.set(label, carries);
11236
- out.push({ file: label, present: extracted.present, values });
12165
+ const bodyLine = bodyLines.get(label);
12166
+ out.push({
12167
+ file: label,
12168
+ present: extracted.present,
12169
+ values,
12170
+ ...bodyLine !== void 0 ? { provenance: { bodyLine } } : {}
12171
+ });
11237
12172
  };
11238
12173
  if (usingStdin) {
11239
12174
  if (!forced) {
@@ -11244,7 +12179,7 @@ async function runGet(opts) {
11244
12179
  readOne(STDIN_LABEL, opts.stdinContent ?? "", forced.extensions[0] ?? "");
11245
12180
  }
11246
12181
  for (const file of files) {
11247
- const content = await readFile9(resolve13(base, file), "utf8");
12182
+ const content = await readFile9(resolve14(base, file), "utf8");
11248
12183
  readOne(file, content, extname4(file));
11249
12184
  }
11250
12185
  if (deriving) {
@@ -11267,6 +12202,7 @@ async function runGet(opts) {
11267
12202
  async function attachResolved(out, inputs, opts) {
11268
12203
  const { run: run2 } = opts;
11269
12204
  const derive = run2.config?.derive;
12205
+ let provenance = /* @__PURE__ */ new Map();
11270
12206
  const commands = commandsOf(derive);
11271
12207
  const records = opts.derivable.length === 0 || inputs.length === 0 ? /* @__PURE__ */ new Map() : await (async () => {
11272
12208
  const result = await deriveMetadata(inputs, {
@@ -11278,12 +12214,14 @@ async function attachResolved(out, inputs, opts) {
11278
12214
  ...derive?.codeowners !== void 0 ? { codeowners: derive.codeowners } : {},
11279
12215
  ...commands !== void 0 ? { commands } : {},
11280
12216
  cache: run2.cache,
11281
- now: run2.now
12217
+ now: run2.now,
12218
+ machines: machinesOf(derive)
11282
12219
  });
11283
12220
  assertSourcesAvailable(
11284
12221
  result.sources,
11285
12222
  "pass --no-derived, or narrow derive.sources"
11286
12223
  );
12224
+ if (result.provenance !== void 0) provenance = result.provenance;
11287
12225
  return result.records;
11288
12226
  })();
11289
12227
  for (const r of out) {
@@ -11311,6 +12249,11 @@ async function attachResolved(out, inputs, opts) {
11311
12249
  if (opts.derivable.length > 0) r.derived = derived;
11312
12250
  r.resolved = resolved;
11313
12251
  r.origin = origin;
12252
+ const derivation = provenance.get(r.file);
12253
+ if (r.provenance !== void 0 && derivation !== void 0) {
12254
+ const comparisons = compareProvenance(provenanceEntries(r.values[PROVENANCE_FIELD]), derivation);
12255
+ r.provenance.current = comparisons.every((c) => c.status === "current");
12256
+ }
11314
12257
  }
11315
12258
  }
11316
12259
  function resolveField(data, field) {
@@ -11363,15 +12306,15 @@ function parseJsonPointer(pointer) {
11363
12306
  // src/meta/commands/schemas.ts
11364
12307
  import { existsSync as existsSync4 } from "node:fs";
11365
12308
  import { mkdir as mkdir2, readFile as readFile10 } from "node:fs/promises";
11366
- import { dirname as dirname10, extname as extname5, isAbsolute as isAbsolute5, join as join8, relative as relative11, resolve as resolve14, sep as sep6 } from "node:path";
11367
- import { parseDocument as parseDocument5, parse as parseYaml4, stringify as stringify2 } from "yaml";
12309
+ import { dirname as dirname10, extname as extname5, isAbsolute as isAbsolute7, join as join8, relative as relative12, resolve as resolve15, sep as sep7 } from "node:path";
12310
+ import { parseDocument as parseDocument5, parse as parseYaml5, stringify as stringify2 } from "yaml";
11368
12311
  function getSchemasInfo() {
11369
12312
  return { builtins: listBuiltins(), formats: listFormats() };
11370
12313
  }
11371
12314
  var DEFAULT_VENDOR_DIR = "./schema";
11372
12315
  var DEFAULT_CONFIG_NAME = "manni.config.yaml";
11373
12316
  function posixRelative(from, to) {
11374
- return relative11(from, to).split(sep6).join("/");
12317
+ return relative12(from, to).split(sep7).join("/");
11375
12318
  }
11376
12319
  function vendorFileName(url) {
11377
12320
  let parsed2;
@@ -11445,7 +12388,7 @@ function reparseConfig(text2, source, section) {
11445
12388
  parseConfig(text2, source);
11446
12389
  return;
11447
12390
  }
11448
- const doc = parseYaml4(text2);
12391
+ const doc = parseYaml5(text2);
11449
12392
  parseConfigValue(doc[section], source, section);
11450
12393
  }
11451
12394
  async function runVendorSchema(opts) {
@@ -11457,13 +12400,13 @@ async function runVendorSchema(opts) {
11457
12400
  );
11458
12401
  }
11459
12402
  const loaded = await loadConfig(opts.configPath, cwd);
11460
- const configPath = loaded?.path ?? resolve14(cwd, DEFAULT_CONFIG_NAME);
12403
+ const configPath = loaded?.path ?? resolve15(cwd, DEFAULT_CONFIG_NAME);
11461
12404
  const configDir = loaded?.dir ?? cwd;
11462
- const absDir = resolve14(cwd, opts.dir ?? DEFAULT_VENDOR_DIR);
12405
+ const absDir = resolve15(cwd, opts.dir ?? DEFAULT_VENDOR_DIR);
11463
12406
  const absFile = join8(absDir, vendorFileName(url));
11464
12407
  await assertNotIgnored(absFile, absDir, cwd, opts.onNotice);
11465
12408
  const fromConfig = posixRelative(configDir, absFile);
11466
- const ref = isAbsolute5(fromConfig) || fromConfig === "" ? absFile : fromConfig.startsWith(".") ? fromConfig : `./${fromConfig}`;
12409
+ const ref = isAbsolute7(fromConfig) || fromConfig === "" ? absFile : fromConfig.startsWith(".") ? fromConfig : `./${fromConfig}`;
11467
12410
  const { bytes } = await fetchSchemaBytes(url, {
11468
12411
  ...opts.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {},
11469
12412
  ...opts.maxBytes !== void 0 ? { maxBytes: opts.maxBytes } : {}
@@ -11640,7 +12583,7 @@ async function runInferSchema(opts) {
11640
12583
  }
11641
12584
  let absOut;
11642
12585
  if (opts.out !== void 0) {
11643
- absOut = resolve14(cwd, opts.out);
12586
+ absOut = resolve15(cwd, opts.out);
11644
12587
  await assertNotIgnored(
11645
12588
  absOut,
11646
12589
  dirname10(absOut),
@@ -11728,7 +12671,7 @@ async function runInferSchema(opts) {
11728
12671
  scanOne(STDIN_LABEL, opts.stdinContent ?? "", forced.extensions[0] ?? "");
11729
12672
  }
11730
12673
  for (const file of files) {
11731
- const content = await readFile10(resolve14(base, file), "utf8");
12674
+ const content = await readFile10(resolve15(base, file), "utf8");
11732
12675
  scanOne(file, content, extname5(file));
11733
12676
  }
11734
12677
  for (const u of unreadable) {
@@ -11790,7 +12733,7 @@ async function runInferSchema(opts) {
11790
12733
  import { randomBytes as randomBytes2 } from "node:crypto";
11791
12734
  import { existsSync as existsSync5 } from "node:fs";
11792
12735
  import { mkdir as mkdir3, open as open2, readFile as readFile11, rename as rename3, rm as rm3 } from "node:fs/promises";
11793
- import { dirname as dirname11, isAbsolute as isAbsolute6, resolve as resolve15, extname as extname6, sep as sep7 } from "node:path";
12736
+ import { dirname as dirname11, isAbsolute as isAbsolute8, resolve as resolve16, extname as extname6, sep as sep8 } from "node:path";
11794
12737
 
11795
12738
  // src/shared/prompt.ts
11796
12739
  import { createInterface } from "node:readline";
@@ -12007,10 +12950,10 @@ async function runQuery(opts) {
12007
12950
  readOne(STDIN_LABEL, opts.stdinContent ?? "", ext);
12008
12951
  }
12009
12952
  for (const file of files) {
12010
- const content = await readFile11(resolve15(base, file), "utf8");
12953
+ const content = await readFile11(resolve16(base, file), "utf8");
12011
12954
  readOne(file, content, extname6(file));
12012
12955
  }
12013
- const db = opts.db === void 0 ? void 0 : { resolved: resolve15(cwd, opts.db), display: opts.db };
12956
+ const db = opts.db === void 0 ? void 0 : { resolved: resolve16(cwd, opts.db), display: opts.db };
12014
12957
  const run2 = await runSql(sql, entries, {
12015
12958
  target: db,
12016
12959
  params: boundParams,
@@ -12141,13 +13084,21 @@ async function runSql(sql, entries, ctx) {
12141
13084
  if (target) buildViews();
12142
13085
  const buildDerived = async () => {
12143
13086
  if (derivedTable.built) return;
12144
- const inputs = entries.filter((e) => e.label !== STDIN_LABEL).map((e) => ({
12145
- label: e.label,
12146
- absPath: resolve15(ctx.base, e.label),
12147
- content: e.content,
12148
- extracted: e.own
12149
- }));
12150
13087
  const commands = commandsOf(ctx.config?.derive);
13088
+ const fields = fieldsForSql(sql, derivableFields(commands));
13089
+ const provenanceDerived = fields.includes(PROVENANCE_FIELD) || ctx.config?.derive?.fields.includes(PROVENANCE_FIELD) === true;
13090
+ const manifestRoot = ctx.configDir ?? ctx.cwd;
13091
+ const manifests = provenanceDerived ? readerManifests(ctx.declaredCollections, manifestRoot, ctx.base) : [];
13092
+ const inputs = entries.filter((e) => e.label !== STDIN_LABEL).map((e) => {
13093
+ const place = manifests.length === 0 ? void 0 : readerPlace(e.label, e.own.data, manifests, ctx.declaredCollections, manifestRoot, ctx.base);
13094
+ return {
13095
+ label: e.label,
13096
+ absPath: resolve16(ctx.base, e.label),
13097
+ content: e.content,
13098
+ extracted: e.own,
13099
+ ...place !== void 0 ? { provenanceManifest: place } : {}
13100
+ };
13101
+ });
12151
13102
  const records = await deriveForTable(
12152
13103
  inputs,
12153
13104
  {
@@ -12159,7 +13110,7 @@ async function runSql(sql, entries, ctx) {
12159
13110
  ...ctx.now !== void 0 ? { now: ctx.now } : {}
12160
13111
  },
12161
13112
  "narrow derive.sources in manni.config.yaml",
12162
- fieldsForSql(sql, derivableFields(commands))
13113
+ fields
12163
13114
  );
12164
13115
  createDerivedView(db, records, derivedColumns(commands));
12165
13116
  createResolvedView(db, dataColumns, commands);
@@ -12601,20 +13552,20 @@ function displayPath(ctx, abs) {
12601
13552
  }
12602
13553
  function assertSchemaWriteWithin(ctx, abs) {
12603
13554
  const within = posixRelative(ctx.trustRoot.dir, abs);
12604
- if (within !== "" && !within.startsWith("..") && !isAbsolute6(within)) return;
13555
+ if (within !== "" && !within.startsWith("..") && !isAbsolute8(within)) return;
12605
13556
  throw new DocmetaError(
12606
13557
  `The resolved schema "${abs}" lives outside ${ctx.trustRoot.dir}; DDL edits schemas inside the repository. Evolve it where it lives, or vendor a copy in.`
12607
13558
  );
12608
13559
  }
12609
13560
  function foldPath(p) {
12610
- const abs = resolve15(p);
13561
+ const abs = resolve16(p);
12611
13562
  return process.platform === "win32" ? abs.toLowerCase() : abs;
12612
13563
  }
12613
13564
  function filePinsByPath(ctx) {
12614
13565
  const byPath = /* @__PURE__ */ new Map();
12615
13566
  for (const [ref, pin] of collectSchemaPins(ctx.config)) {
12616
13567
  if (classifyRef(ref).kind !== "file") continue;
12617
- byPath.set(foldPath(resolve15(ctx.cwd, ref)), pin);
13568
+ byPath.set(foldPath(resolve16(ctx.cwd, ref)), pin);
12618
13569
  }
12619
13570
  return byPath;
12620
13571
  }
@@ -12642,7 +13593,7 @@ async function loadSetMembers(refs, ctx) {
12642
13593
  });
12643
13594
  continue;
12644
13595
  }
12645
- const abs = resolve15(ctx.cwd, ref);
13596
+ const abs = resolve16(ctx.cwd, ref);
12646
13597
  const canon = foldPath(abs);
12647
13598
  if (seenFiles.has(canon)) continue;
12648
13599
  seenFiles.add(canon);
@@ -12847,7 +13798,7 @@ async function planSchemaMutation(ops, entries, ctx) {
12847
13798
  const [, name2, ver] = target.builtinId.split(":");
12848
13799
  const safe = (s) => s.replace(/[^A-Za-z0-9._-]/g, "-");
12849
13800
  const forkName = `${safe(name2 ?? "schema")}-${safe(ver ?? "0")}.local.json`;
12850
- schemaAbs = resolve15(ctx.configDir ?? ctx.cwd, "schemas", forkName);
13801
+ schemaAbs = resolve16(ctx.configDir ?? ctx.cwd, "schemas", forkName);
12851
13802
  assertSchemaWriteWithin(ctx, schemaAbs);
12852
13803
  if (existsSync5(schemaAbs)) {
12853
13804
  throw new DocmetaError(
@@ -13014,7 +13965,7 @@ async function planConfigEdit(ctx, edit, runRefs) {
13014
13965
  }
13015
13966
  return void 0;
13016
13967
  };
13017
- const rebaseRaw = (raw) => classifyRef(raw).kind === "file" && !isAbsolute6(raw) ? resolve15(configDir, raw) : raw;
13968
+ const rebaseRaw = (raw) => classifyRef(raw).kind === "file" && !isAbsolute8(raw) ? resolve16(configDir, raw) : raw;
13018
13969
  const wantSet = runRefs.map(rebaseRaw).sort().join("\n");
13019
13970
  const seqResolvesToRunSet = (node) => {
13020
13971
  if (!isSeq3(node)) return false;
@@ -13059,7 +14010,7 @@ async function planConfigEdit(ctx, edit, runRefs) {
13059
14010
  if (!isMap5(item)) continue;
13060
14011
  const raw = rawRefOf(item);
13061
14012
  if (raw === void 0 || classifyRef(raw).kind !== "file") continue;
13062
- const abs = isAbsolute6(raw) ? raw : resolve15(configDir, raw);
14013
+ const abs = isAbsolute8(raw) ? raw : resolve16(configDir, raw);
13063
14014
  if (foldPath(abs) !== wantPath) continue;
13064
14015
  pinFrom = item.get("integrity");
13065
14016
  item.set("integrity", edit.integrity.value);
@@ -13280,7 +14231,7 @@ function buildChanges(diff, entries, sentinel, ctx, renameHints = [], booleanAdd
13280
14231
  `"${file}": no writable format for that extension.`
13281
14232
  );
13282
14233
  }
13283
- if (existsSync5(resolve15(ctx.base, file))) {
14234
+ if (existsSync5(resolve16(ctx.base, file))) {
13284
14235
  throw new DocmetaError(
13285
14236
  `"${file}" already exists; INSERT creates new files only.`
13286
14237
  );
@@ -13299,7 +14250,7 @@ function buildChanges(diff, entries, sentinel, ctx, renameHints = [], booleanAdd
13299
14250
  `"${from}" -> "${to}": a rename may not change the extension.`
13300
14251
  );
13301
14252
  }
13302
- if (existsSync5(resolve15(ctx.base, to))) {
14253
+ if (existsSync5(resolve16(ctx.base, to))) {
13303
14254
  throw new DocmetaError(
13304
14255
  `"${to}" already exists; the rename would overwrite it.`
13305
14256
  );
@@ -13328,13 +14279,13 @@ function validateNewPath(p, base) {
13328
14279
  const refuse = (why) => {
13329
14280
  throw new DocmetaError(`"${p}" is not a usable path: ${why}.`);
13330
14281
  };
13331
- if (isAbsolute6(p)) refuse("it is absolute");
14282
+ if (isAbsolute8(p)) refuse("it is absolute");
13332
14283
  if (p.split(/[\\/]/).some((segment) => segment === "..")) {
13333
14284
  refuse("it traverses upward");
13334
14285
  }
13335
- const abs = resolve15(base, p);
13336
- if (abs === resolve15(base)) refuse("it points at the corpus root, not a file");
13337
- if (!abs.startsWith(resolve15(base) + sep7)) refuse("it escapes the corpus");
14286
+ const abs = resolve16(base, p);
14287
+ if (abs === resolve16(base)) refuse("it points at the corpus root, not a file");
14288
+ if (!abs.startsWith(resolve16(base) + sep8)) refuse("it escapes the corpus");
13338
14289
  }
13339
14290
  function refuseExternalMetadataWrites(changes, entries, ctx) {
13340
14291
  const index = ctx.externalMetadata;
@@ -13363,7 +14314,7 @@ function refuseExternalMetadataWrites(changes, entries, ctx) {
13363
14314
  continue;
13364
14315
  }
13365
14316
  if ("renamed" in c) {
13366
- const entry = index.byPath.get(resolve15(ctx.base, c.file));
14317
+ const entry = index.byPath.get(resolve16(ctx.base, c.file));
13367
14318
  if (entry !== void 0 && entry.size > 0) {
13368
14319
  const manifest = [...entry.values()][0]?.file ?? "manifest";
13369
14320
  throw new DocmetaError(
@@ -13398,8 +14349,8 @@ function readableJoinValue(held, ctx) {
13398
14349
  return opened.ok ? opened.value : held;
13399
14350
  }
13400
14351
  var sealedWrites = /* @__PURE__ */ new WeakMap();
13401
- function writtenValue(change2, key, value) {
13402
- const sealed = sealedWrites.get(change2)?.get(key);
14352
+ function writtenValue(change, key, value) {
14353
+ const sealed = sealedWrites.get(change)?.get(key);
13403
14354
  return sealed === void 0 ? value : sealed;
13404
14355
  }
13405
14356
  async function sealMarkedWrites(changes, entries, ctx) {
@@ -13501,14 +14452,14 @@ async function applyChanges(changes, entries, ctx, schemaPlan) {
13501
14452
  const pendingWrites = [];
13502
14453
  const pendingRenames = [];
13503
14454
  for (const [label, ops] of grouped) {
13504
- const path = resolve15(ctx.base, label);
14455
+ const path = resolve16(ctx.base, label);
13505
14456
  if (ops.renamedTo !== void 0) {
13506
14457
  if (!existsSync5(path)) {
13507
14458
  throw new DocmetaError(
13508
14459
  `"${label}" is gone from disk since it was read; re-run the query.`
13509
14460
  );
13510
14461
  }
13511
- const toDisk = resolve15(ctx.base, ops.renamedTo);
14462
+ const toDisk = resolve16(ctx.base, ops.renamedTo);
13512
14463
  if (existsSync5(toDisk)) {
13513
14464
  throw new DocmetaError(
13514
14465
  `"${ops.renamedTo}" appeared on disk since the plan; re-run the query.`
@@ -13752,7 +14703,7 @@ function skipExpression(sql, from) {
13752
14703
 
13753
14704
  // src/meta/commands/fill.ts
13754
14705
  import { readFile as readFile12 } from "node:fs/promises";
13755
- import { resolve as resolve16, extname as extname7, join as join9 } from "node:path";
14706
+ import { resolve as resolve17, extname as extname7, join as join9 } from "node:path";
13756
14707
  import {
13757
14708
  completeValidatedJSON,
13758
14709
  makeProvider,
@@ -13767,7 +14718,7 @@ import {
13767
14718
  } from "@hawkeyexl/inference";
13768
14719
 
13769
14720
  // src/meta/commands/fill-prompt.ts
13770
- var FILL_PROMPT_VERSION = 3;
14721
+ var FILL_PROMPT_VERSION = 4;
13771
14722
  var DEFAULT_CHUNK_CHARS = 12e3;
13772
14723
  function splitBody(body, chunkChars) {
13773
14724
  if (body.length <= chunkChars) return [body];
@@ -13875,6 +14826,8 @@ function buildUserPrompt(params) {
13875
14826
  }
13876
14827
 
13877
14828
  // src/meta/commands/fill.ts
14829
+ var META_PROVENANCE_KEY = "meta-provenance";
14830
+ var PROVENANCE_KEYS = /* @__PURE__ */ new Set(["provenance", META_PROVENANCE_KEY]);
13878
14831
  var DEFAULT_THRESHOLD = 0.7;
13879
14832
  var DEFAULT_CONCURRENCY = 4;
13880
14833
  var DEFAULT_PROVIDER = "auto";
@@ -14155,7 +15108,7 @@ async function runFill(opts) {
14155
15108
  only
14156
15109
  );
14157
15110
  const managedSkips = proposed.filter((c) => managed.has(c.key)).map((c) => ({
14158
- field: `/${c.key}`,
15111
+ field: pointerOf(c.key),
14159
15112
  required: c.required,
14160
15113
  confidence: 0,
14161
15114
  reasoning: "managed by derive",
@@ -14179,6 +15132,7 @@ async function runFill(opts) {
14179
15132
  const inPlace = view.plain.flatMap((pointer) => {
14180
15133
  const key = topLevelKeyOf(pointer);
14181
15134
  if (key === void 0 || only !== void 0 && !only.has(key)) return [];
15135
+ if (PROVENANCE_KEYS.has(key)) return [];
14182
15136
  if (externalMetadata?.owners.has(key) === true) return [];
14183
15137
  if (candidates.some((c) => c.key === key)) return [];
14184
15138
  return [{ key, pointer, value: extracted.data[key] }];
@@ -14222,7 +15176,11 @@ async function runFill(opts) {
14222
15176
  sha256(content)
14223
15177
  ]);
14224
15178
  const hit = candidates.length > 0 ? cache?.get(cacheKey) : void 0;
14225
- const existing = redactForModel(extracted.data, [...view.marked, ...probeMarks]);
15179
+ const existing = Object.fromEntries(
15180
+ Object.entries(redactForModel(extracted.data, [...view.marked, ...probeMarks])).filter(
15181
+ ([key]) => !PROVENANCE_KEYS.has(key)
15182
+ )
15183
+ );
14226
15184
  let proposals = {};
14227
15185
  if (hit) {
14228
15186
  cachedCount++;
@@ -14375,7 +15333,8 @@ async function runFill(opts) {
14375
15333
  ...opts.includeContent ? { content } : {}
14376
15334
  };
14377
15335
  }
14378
- const finalMarks = writable.length > 0 ? await validator.markedPointers({ ...view.data, ...patchOf(writable) }, schemaSet) : /* @__PURE__ */ new Set();
15336
+ const plainPatch = patchOf(writable);
15337
+ const finalMarks = writable.length > 0 ? await validator.markedPointers({ ...view.data, ...plainPatch }, schemaSet) : /* @__PURE__ */ new Set();
14379
15338
  const patch = {};
14380
15339
  for (const f of writable) {
14381
15340
  const key = keyOf(f);
@@ -14401,6 +15360,23 @@ async function runFill(opts) {
14401
15360
  encrypted: true
14402
15361
  });
14403
15362
  }
15363
+ let metaProvenance;
15364
+ if (writable.length > 0) {
15365
+ const owner = externalMetadata?.owners.get(META_PROVENANCE_KEY)?.find((o) => members.includes(o.collection));
15366
+ const merged2 = owner === void 0 ? mergeMetaProvenance(extracted.data[META_PROVENANCE_KEY], identity.model, writable) : void 0;
15367
+ if (owner !== void 0) {
15368
+ metaProvenance = { written: false, skipReason: "manifest-owned", manifest: owner.file };
15369
+ } else if (merged2 === void 0 || await addsFindings(
15370
+ (data) => validator.validate(data, schemaSet, extracted.lineFor, extracted.colFor).then((errors) => settleFindings(errors, view, extracted)),
15371
+ { ...view.data, ...plainPatch },
15372
+ merged2.list
15373
+ )) {
15374
+ metaProvenance = { written: false, skipReason: "schema-mismatch" };
15375
+ } else {
15376
+ patch[META_PROVENANCE_KEY] = merged2.list;
15377
+ metaProvenance = { written: true, entry: merged2.entry };
15378
+ }
15379
+ }
14404
15380
  let next;
14405
15381
  try {
14406
15382
  next = extractor.apply(content, patch, {
@@ -14408,17 +15384,25 @@ async function runFill(opts) {
14408
15384
  elements
14409
15385
  });
14410
15386
  } catch (err) {
14411
- return errorResult(
14412
- label,
14413
- extractor.name,
14414
- errorMessage(err),
14415
- schemaSet,
14416
- fields
14417
- );
15387
+ const withoutEntry = metaProvenance?.written === true ? tryApply(extractor, content, withoutKey(patch, META_PROVENANCE_KEY), {
15388
+ filePath: label,
15389
+ elements
15390
+ }) : void 0;
15391
+ if (withoutEntry === void 0) {
15392
+ return errorResult(
15393
+ label,
15394
+ extractor.name,
15395
+ errorMessage(err),
15396
+ schemaSet,
15397
+ fields
15398
+ );
15399
+ }
15400
+ next = withoutEntry;
15401
+ metaProvenance = { written: false, skipReason: "unwritable" };
14418
15402
  }
14419
15403
  const changed = next !== content;
14420
15404
  if (changed && !dryRun && label !== "<stdin>") {
14421
- await writeFileAtomic(resolve16(base, label), next);
15405
+ await writeFileAtomic(resolve17(base, label), next);
14422
15406
  }
14423
15407
  return {
14424
15408
  file: label,
@@ -14426,7 +15410,8 @@ async function runFill(opts) {
14426
15410
  schemas: schemaSet,
14427
15411
  fields,
14428
15412
  changed,
14429
- ...opts.includeContent ? { content: next } : {}
15413
+ ...opts.includeContent ? { content: next } : {},
15414
+ ...metaProvenance === void 0 ? {} : { metaProvenance }
14430
15415
  };
14431
15416
  };
14432
15417
  const results = [];
@@ -14440,7 +15425,7 @@ async function runFill(opts) {
14440
15425
  );
14441
15426
  }
14442
15427
  const fileResults = await mapConcurrent(files, concurrency, async (file) => {
14443
- const content = await readFile12(resolve16(base, file), "utf8");
15428
+ const content = await readFile12(resolve17(base, file), "utf8");
14444
15429
  return processOne(file, content, extname7(file));
14445
15430
  });
14446
15431
  results.push(...fileResults);
@@ -14459,7 +15444,57 @@ function topKey(instancePath) {
14459
15444
  const seg = instancePath.slice(1).split("/")[0] ?? "";
14460
15445
  return seg.replace(/~1/g, "/").replace(/~0/g, "~");
14461
15446
  }
14462
- var keyOf = (f) => f.field.slice(1);
15447
+ var keyOf = (f) => topKey(f.field);
15448
+ function isPlainRecord(value) {
15449
+ return typeof value === "object" && value !== null && !Array.isArray(value);
15450
+ }
15451
+ function mergeMetaProvenance(held, model, written) {
15452
+ if (held !== void 0 && !Array.isArray(held)) return void 0;
15453
+ const list = held === void 0 ? [] : [...held];
15454
+ const at2 = list.findIndex((e) => isPlainRecord(e) && e["generated-by"] === model);
15455
+ const prior = list[at2];
15456
+ const kept = isPlainRecord(prior) ? prior : {};
15457
+ const fields = Array.isArray(kept.fields) ? kept.fields.filter((p) => typeof p === "string") : [];
15458
+ const confidence = {};
15459
+ if (isPlainRecord(kept.confidence)) {
15460
+ for (const [k, v] of Object.entries(kept.confidence)) {
15461
+ if (typeof v === "number") confidence[k] = v;
15462
+ }
15463
+ }
15464
+ for (const f of written) {
15465
+ if (!fields.includes(f.field)) fields.push(f.field);
15466
+ confidence[f.field] = f.confidence;
15467
+ }
15468
+ const entry = { ...kept, "generated-by": model, fields, confidence };
15469
+ if (at2 === -1) list.push(entry);
15470
+ else list[at2] = entry;
15471
+ return { list, entry };
15472
+ }
15473
+ async function addsFindings(validate, without, list) {
15474
+ const identity = (e) => JSON.stringify([e.schema, e.instancePath, e.keyword, e.subject ?? null, e.message]);
15475
+ const before = /* @__PURE__ */ new Map();
15476
+ for (const e of await validate(without)) {
15477
+ const key = identity(e);
15478
+ before.set(key, (before.get(key) ?? 0) + 1);
15479
+ }
15480
+ for (const e of await validate({ ...without, [META_PROVENANCE_KEY]: list })) {
15481
+ const key = identity(e);
15482
+ const left = before.get(key) ?? 0;
15483
+ if (left === 0) return true;
15484
+ before.set(key, left - 1);
15485
+ }
15486
+ return false;
15487
+ }
15488
+ function withoutKey(patch, key) {
15489
+ return Object.fromEntries(Object.entries(patch).filter(([k]) => k !== key));
15490
+ }
15491
+ function tryApply(extractor, content, patch, options) {
15492
+ try {
15493
+ return extractor.apply?.(content, patch, options);
15494
+ } catch {
15495
+ return void 0;
15496
+ }
15497
+ }
14463
15498
  var IN_PLACE_REASONING = "The page held this value in plain text; its schema marks it x-manni-encrypt, so it was encrypted.";
14464
15499
  var CIPHERTEXT_IN_TEXT = /~[A-Za-z0-9_-]{82,}/g;
14465
15500
  function contentForModel(content, extractor, existing, held, supplied, options) {
@@ -14468,10 +15503,13 @@ function contentForModel(content, extractor, existing, held, supplied, options)
14468
15503
  if (!Object.hasOwn(held, key) || supplied(pointerOf(key))) continue;
14469
15504
  if (toJsonText(value) !== toJsonText(held[key])) patch[key] = value;
14470
15505
  }
15506
+ const deletions = [...PROVENANCE_KEYS].filter(
15507
+ (key) => Object.hasOwn(held, key) && !supplied(pointerOf(key))
15508
+ );
14471
15509
  let text2 = content;
14472
- if (Object.keys(patch).length > 0 && extractor.apply !== void 0) {
15510
+ if ((Object.keys(patch).length > 0 || deletions.length > 0) && extractor.apply !== void 0) {
14473
15511
  try {
14474
- text2 = extractor.apply(content, patch, options);
15512
+ text2 = extractor.apply(content, patch, { ...options, deletions });
14475
15513
  } catch {
14476
15514
  }
14477
15515
  }
@@ -14509,7 +15547,7 @@ function collectCandidates(schemas, data, errors, only) {
14509
15547
  for (const [key, raw] of Object.entries(
14510
15548
  properties
14511
15549
  )) {
14512
- if (key === FILE_SCHEMA_KEY) continue;
15550
+ if (key === FILE_SCHEMA_KEY || PROVENANCE_KEYS.has(key)) continue;
14513
15551
  if (only && !only.has(key)) continue;
14514
15552
  if (typeof raw !== "object" || raw === null || Array.isArray(raw)) continue;
14515
15553
  const present = Object.prototype.hasOwnProperty.call(data, key);
@@ -14675,7 +15713,7 @@ function gate(candidates, proposals, threshold) {
14675
15713
  const proposal = proposals[c.key];
14676
15714
  if (proposal == null) {
14677
15715
  return {
14678
- field: `/${c.key}`,
15716
+ field: pointerOf(c.key),
14679
15717
  required: c.required,
14680
15718
  confidence: 0,
14681
15719
  reasoning: "",
@@ -14685,7 +15723,7 @@ function gate(candidates, proposals, threshold) {
14685
15723
  }
14686
15724
  const passes = proposal.confidence >= threshold;
14687
15725
  return {
14688
- field: `/${c.key}`,
15726
+ field: pointerOf(c.key),
14689
15727
  required: c.required,
14690
15728
  confidence: proposal.confidence,
14691
15729
  reasoning: proposal.reasoning,
@@ -14784,10 +15822,434 @@ async function mapConcurrent(items2, limit, fn) {
14784
15822
 
14785
15823
  // src/meta/commands/derive.ts
14786
15824
  import { readFile as readFile13 } from "node:fs/promises";
14787
- import { extname as extname8, resolve as resolve17 } from "node:path";
15825
+ import { extname as extname8, relative as relative13, resolve as resolve18, sep as sep9 } from "node:path";
15826
+
15827
+ // src/meta/core/external-metadata-write.ts
15828
+ import { posix as posix3 } from "node:path";
15829
+ import {
15830
+ LineCounter as LineCounter3,
15831
+ isMap as isMap6,
15832
+ isNode as isNode2,
15833
+ isPair,
15834
+ isScalar as isScalar4,
15835
+ isSeq as isSeq4,
15836
+ parseDocument as parseDocument7,
15837
+ stringify as stringify3
15838
+ } from "yaml";
15839
+ function spliceManifestValue(text2, options) {
15840
+ const { entry, key, value, file } = options;
15841
+ const join10 = options.join ?? PATH_JOIN;
15842
+ const at2 = (line2) => {
15843
+ const name2 = file === void 0 ? "Manifest" : `Manifest ${file}`;
15844
+ if (line2 === void 0) return name2;
15845
+ return file === void 0 ? `${name2} line ${String(line2)}` : `${name2}:${String(line2)}`;
15846
+ };
15847
+ if (key === FILE_SCHEMA_KEY) {
15848
+ throw new DocmetaError(
15849
+ `${at2()}: refusing to write "${FILE_SCHEMA_KEY}" for "${entry}"; a manifest never chooses the schema a document is judged by. Use "overrides" in the config.`
15850
+ );
15851
+ }
15852
+ if (value === void 0) {
15853
+ throw new DocmetaError(`${at2()}: refusing to write "${key}" for "${entry}" with no value.`);
15854
+ }
15855
+ const eol = detectEol(text2);
15856
+ const lc = new LineCounter3();
15857
+ const doc = parseDocument7(text2, { lineCounter: lc, uniqueKeys: false });
15858
+ const problem = doc.errors[0];
15859
+ if (problem) {
15860
+ throw new DocmetaError(`${at2()} is not valid YAML: ${problem.message}`);
15861
+ }
15862
+ const keyLine = (node) => {
15863
+ const r = rangeOf(node);
15864
+ return r ? lc.linePos(r[0]).line : void 0;
15865
+ };
15866
+ const same = join10 === PATH_JOIN ? (a, b) => posix3.normalize(a) === posix3.normalize(b) : (a, b) => a === b;
15867
+ const root = doc.contents;
15868
+ let spelled = entry;
15869
+ let edit;
15870
+ if (root === null || isScalar4(root) && root.value === null && emptySource(text2, root)) {
15871
+ edit = startManifest(text2, entry, key, value, eol);
15872
+ } else if (!isMap6(root)) {
15873
+ throw new DocmetaError(
15874
+ `${at2()}: the manifest must be a mapping from document ${join10 === PATH_JOIN ? "path" : `"${join10}"`} to owned keys.`
15875
+ );
15876
+ } else {
15877
+ const style = detectStyle(text2, root, eol);
15878
+ const matches = root.items.filter((p) => same(keyString(p.key), entry));
15879
+ const [first, second] = matches;
15880
+ if (first && second) {
15881
+ throw new DocmetaError(
15882
+ `${at2(keyLine(second.key))}: "${keyString(second.key)}" is named twice (first at line ${String(keyLine(first.key) ?? "?")}). Merge the two entries into one.`
15883
+ );
15884
+ }
15885
+ if (first === void 0) {
15886
+ if (root.flow === true && root.items.length > 0) {
15887
+ throw new DocmetaError(
15888
+ `${at2()}: the manifest is a flow mapping ({ \u2026 }), which this writer does not add entries to. Rewrite it in block style.`
15889
+ );
15890
+ }
15891
+ edit = appendEntry(text2, root, entry, key, value, style);
15892
+ } else {
15893
+ spelled = keyString(first.key);
15894
+ const where = at2(keyLine(first.key));
15895
+ const v = first.value;
15896
+ if (!isMap6(v)) {
15897
+ throw new DocmetaError(`${where}: "${spelled}" must be a mapping of owned keys to values.`);
15898
+ }
15899
+ const hits = v.items.filter((kv) => keyString(kv.key) === key);
15900
+ const [hit, again] = hits;
15901
+ if (hit && again) {
15902
+ throw new DocmetaError(
15903
+ `${at2(keyLine(again.key))}: "${spelled}" sets "${key}" twice (first at line ${String(keyLine(hit.key) ?? "?")}). Remove one.`
15904
+ );
15905
+ }
15906
+ const flowRefusal = () => new DocmetaError(
15907
+ `${where}: "${spelled}" is a flow mapping ({ \u2026 }), which this writer does not add keys to. Rewrite the entry in block style.`
15908
+ );
15909
+ const formRefusal = () => new DocmetaError(
15910
+ `${where}: "${spelled}" writes a key in a form this writer does not edit. Write it as "${key}: \u2026".`
15911
+ );
15912
+ let made;
15913
+ if (v.flow === true) {
15914
+ if (hit) made = replaceFlowValue(text2, hit, value);
15915
+ else if (v.items.length === 0) made = fillEmptyEntry(text2, first, key, value, style);
15916
+ else throw flowRefusal();
15917
+ if (!made) throw hit ? flowRefusal() : formRefusal();
15918
+ } else {
15919
+ made = hit ? replaceBlockValue(text2, hit, value, style) : insertKey(text2, v, key, value, style);
15920
+ if (!made) throw formRefusal();
15921
+ }
15922
+ edit = made;
15923
+ }
15924
+ }
15925
+ const out = text2.slice(0, edit.start) + edit.insert + text2.slice(edit.end);
15926
+ const line = readBack(
15927
+ doc,
15928
+ out,
15929
+ spelled,
15930
+ key,
15931
+ value,
15932
+ (why) => new DocmetaError(
15933
+ `${at2()}: writing "${key}" for "${entry}" did not read back as written, so nothing was written. ${why}`
15934
+ )
15935
+ );
15936
+ return { text: out, line };
15937
+ }
15938
+ function removeManifestKey(text2, options) {
15939
+ const { entry, key, file } = options;
15940
+ const join10 = options.join ?? PATH_JOIN;
15941
+ const at2 = (line) => {
15942
+ const name2 = file === void 0 ? "Manifest" : `Manifest ${file}`;
15943
+ if (line === void 0) return name2;
15944
+ return file === void 0 ? `${name2} line ${String(line)}` : `${name2}:${String(line)}`;
15945
+ };
15946
+ const eol = detectEol(text2);
15947
+ const lc = new LineCounter3();
15948
+ const doc = parseDocument7(text2, { lineCounter: lc, uniqueKeys: false });
15949
+ const problem = doc.errors[0];
15950
+ if (problem) {
15951
+ throw new DocmetaError(`${at2()} is not valid YAML: ${problem.message}`);
15952
+ }
15953
+ const keyLine = (node) => {
15954
+ const r = rangeOf(node);
15955
+ return r ? lc.linePos(r[0]).line : void 0;
15956
+ };
15957
+ const root = doc.contents;
15958
+ if (root === null || isScalar4(root) && root.value === null) return { text: text2 };
15959
+ if (!isMap6(root)) {
15960
+ throw new DocmetaError(
15961
+ `${at2()}: the manifest must be a mapping from document ${join10 === PATH_JOIN ? "path" : `"${join10}"`} to owned keys.`
15962
+ );
15963
+ }
15964
+ const same = join10 === PATH_JOIN ? (a, b) => posix3.normalize(a) === posix3.normalize(b) : (a, b) => a === b;
15965
+ const matches = root.items.filter((p) => same(keyString(p.key), entry));
15966
+ const [first, second] = matches;
15967
+ if (first && second) {
15968
+ throw new DocmetaError(
15969
+ `${at2(keyLine(second.key))}: "${keyString(second.key)}" is named twice (first at line ${String(keyLine(first.key) ?? "?")}). Merge the two entries into one.`
15970
+ );
15971
+ }
15972
+ if (first === void 0) return { text: text2 };
15973
+ const spelled = keyString(first.key);
15974
+ const where = at2(keyLine(first.key));
15975
+ const v = first.value;
15976
+ if (!isMap6(v)) {
15977
+ throw new DocmetaError(`${where}: "${spelled}" must be a mapping of owned keys to values.`);
15978
+ }
15979
+ const hits = v.items.filter((kv) => keyString(kv.key) === key);
15980
+ const [hit, again] = hits;
15981
+ if (hit && again) {
15982
+ throw new DocmetaError(
15983
+ `${at2(keyLine(again.key))}: "${spelled}" sets "${key}" twice (first at line ${String(keyLine(hit.key) ?? "?")}). Remove one.`
15984
+ );
15985
+ }
15986
+ if (hit === void 0) return { text: text2 };
15987
+ const whole = v.items.length === 1;
15988
+ if (!whole && v.flow === true) {
15989
+ throw new DocmetaError(
15990
+ `${where}: "${spelled}" is a flow mapping ({ \u2026 }), which this writer does not remove keys from. Rewrite the entry in block style.`
15991
+ );
15992
+ }
15993
+ const from = rangeOf(whole ? first.key : hit.key);
15994
+ const to = whole ? contentEnd(text2, v) : contentEnd(text2, hit.value) ?? contentEnd(text2, hit.key);
15995
+ if (!from || to === void 0) {
15996
+ throw new DocmetaError(
15997
+ `${where}: "${spelled}" writes a key in a form this writer does not edit. Write it as "${key}: \u2026".`
15998
+ );
15999
+ }
16000
+ let start = text2.lastIndexOf("\n", from[0] - 1) + 1;
16001
+ let end = lineEnd(text2, to);
16002
+ if (text2.startsWith(eol, end)) end += eol.length;
16003
+ else if (start >= eol.length) start -= eol.length;
16004
+ if (whole) {
16005
+ if (text2.slice(start - 2 * eol.length, start) === eol + eol) start -= eol.length;
16006
+ else if (start === 0 && text2.startsWith(eol, end)) end += eol.length;
16007
+ }
16008
+ const out = text2.slice(0, start) + text2.slice(end);
16009
+ const after = parseDocument7(out, { uniqueKeys: false });
16010
+ const fail2 = (why) => new DocmetaError(
16011
+ `${at2()}: removing "${key}" for "${entry}" did not read back as removed, so nothing was written. ${why}`
16012
+ );
16013
+ if (after.errors[0]) throw fail2(`The result is not valid YAML: ${after.errors[0].message}`);
16014
+ const was = doc.toJS({ maxAliasCount: 100 });
16015
+ const wasRoot = isRecord5(was) ? was : {};
16016
+ const wasEntry = wasRoot[spelled];
16017
+ const rest = Object.fromEntries(
16018
+ Object.entries(isRecord5(wasEntry) ? wasEntry : {}).filter(([k]) => k !== key)
16019
+ );
16020
+ const kept = Object.entries(wasRoot).filter(([k]) => k !== spelled);
16021
+ if (Object.keys(rest).length > 0) kept.push([spelled, rest]);
16022
+ const now = after.toJS({ maxAliasCount: 100 });
16023
+ const expected = kept.length === 0 ? null : Object.fromEntries(kept);
16024
+ if (!deepEqual(now ?? null, expected)) throw fail2("Another value in the manifest would have changed.");
16025
+ return { text: out };
16026
+ }
16027
+ function startManifest(text2, entry, key, value, eol) {
16028
+ const style = { step: 2, indentSeq: true, eol };
16029
+ const block = blockLines({ [entry]: { [key]: value } }, 0, style);
16030
+ let insert;
16031
+ if (text2 === "" || text2.endsWith("\n")) insert = block + eol;
16032
+ else insert = eol + block;
16033
+ return { start: text2.length, end: text2.length, insert };
16034
+ }
16035
+ function appendEntry(text2, root, entry, key, value, style) {
16036
+ const body = { [entry]: { [key]: value } };
16037
+ if (root.flow === true) {
16038
+ const r = rangeOf(root);
16039
+ const start = r ? r[0] : text2.length;
16040
+ const end = contentEnd(text2, root) ?? start;
16041
+ return { start, end, insert: blockLines(body, colOf(text2, start), style) };
16042
+ }
16043
+ const firstKey = rangeOf(root.items[0]?.key);
16044
+ const col = firstKey ? colOf(text2, firstKey[0]) : 0;
16045
+ const pos = lineEnd(text2, contentEnd(text2, root) ?? text2.length);
16046
+ const last = root.items.at(-1);
16047
+ const spaced = root.items.length > 1 && isNode2(last?.key) && last.key.spaceBefore === true;
16048
+ return {
16049
+ start: pos,
16050
+ end: pos,
16051
+ insert: style.eol + (spaced ? style.eol : "") + blockLines(body, col, style)
16052
+ };
16053
+ }
16054
+ function insertKey(text2, entryMap, key, value, style) {
16055
+ const firstKey = rangeOf(entryMap.items[0]?.key);
16056
+ const end = contentEnd(text2, entryMap);
16057
+ if (!firstKey || end === void 0) return void 0;
16058
+ const pos = lineEnd(text2, end);
16059
+ return {
16060
+ start: pos,
16061
+ end: pos,
16062
+ insert: style.eol + blockLines({ [key]: value }, colOf(text2, firstKey[0]), style)
16063
+ };
16064
+ }
16065
+ function fillEmptyEntry(text2, entryPair, key, value, style) {
16066
+ const start = colonEnd(text2, entryPair);
16067
+ const keyRange = rangeOf(entryPair.key);
16068
+ const end = contentEnd(text2, entryPair.value);
16069
+ if (start === void 0 || !keyRange || end === void 0) return void 0;
16070
+ const col = colOf(text2, keyRange[0]) + style.step;
16071
+ return { start, end, insert: style.eol + blockLines({ [key]: value }, col, style) };
16072
+ }
16073
+ function replaceBlockValue(text2, pair, value, style) {
16074
+ const colon = colonEnd(text2, pair);
16075
+ const keyRange = rangeOf(pair.key);
16076
+ if (colon === void 0 || !keyRange) return void 0;
16077
+ const keyCol = colOf(text2, keyRange[0]);
16078
+ let start = colon;
16079
+ let ownLine = false;
16080
+ let i = colon;
16081
+ while (text2.charAt(i) === " " || text2.charAt(i) === " ") i++;
16082
+ if (text2.charAt(i) === "#") {
16083
+ start = lineEnd(text2, i);
16084
+ ownLine = true;
16085
+ }
16086
+ const old = pair.value;
16087
+ const oldEnd = isScalar4(old) && old.value === null && emptySource(text2, old) ? start : contentEnd(text2, old) ?? start;
16088
+ const end = Math.max(start, oldEnd);
16089
+ let insert = afterColon(value, keyCol, style);
16090
+ if (ownLine && !insert.startsWith(style.eol)) {
16091
+ insert = style.eol + " ".repeat(keyCol + style.step) + insert.trimStart();
16092
+ }
16093
+ return { start, end, insert };
16094
+ }
16095
+ function replaceFlowValue(text2, pair, value) {
16096
+ const r = rangeOf(pair.value);
16097
+ if (!r || emptySource(text2, pair.value)) return void 0;
16098
+ const end = contentEnd(text2, pair.value) ?? r[1];
16099
+ return { start: r[0], end, insert: flowText(value) };
16100
+ }
16101
+ function readBack(before, out, spelled, key, value, fail2) {
16102
+ const lc = new LineCounter3();
16103
+ const after = parseDocument7(out, { lineCounter: lc, uniqueKeys: false });
16104
+ const problem = after.errors[0];
16105
+ if (problem) throw fail2(`The result is not valid YAML: ${problem.message}`);
16106
+ const was = before.toJS({ maxAliasCount: 100 });
16107
+ const now = after.toJS({ maxAliasCount: 100 });
16108
+ const wasRoot = isRecord5(was) ? was : {};
16109
+ const wasEntry = wasRoot[spelled];
16110
+ const expected = {
16111
+ ...wasRoot,
16112
+ [spelled]: { ...isRecord5(wasEntry) ? wasEntry : {}, [key]: value }
16113
+ };
16114
+ const nowEntry = isRecord5(now) ? now[spelled] : void 0;
16115
+ if (!isRecord5(nowEntry) || !deepEqual(nowEntry[key], value)) {
16116
+ throw fail2(
16117
+ "The value reads back differently. A Date, an undefined member, or another value YAML cannot hold does not survive the trip."
16118
+ );
16119
+ }
16120
+ if (!deepEqual(now, expected)) {
16121
+ throw fail2("Another value in the manifest would have changed.");
16122
+ }
16123
+ const root = after.contents;
16124
+ const pair = isMap6(root) ? root.items.find((p) => keyString(p.key) === spelled) : void 0;
16125
+ const entryMap = pair?.value;
16126
+ const kv = isMap6(entryMap) ? entryMap.items.find((p) => keyString(p.key) === key) : void 0;
16127
+ const r = rangeOf(kv?.value) ?? rangeOf(kv?.key);
16128
+ if (!r) throw fail2("The written value could not be found again.");
16129
+ return lc.linePos(r[0]).line;
16130
+ }
16131
+ function blockLines(pair, col, style) {
16132
+ const s = stringify3(pair, { lineWidth: 0, indent: style.step, indentSeq: style.indentSeq });
16133
+ return indentLines(s.replace(/\n$/, "").split("\n"), col, 0).join(style.eol);
16134
+ }
16135
+ function afterColon(value, col, style) {
16136
+ const s = stringify3({ k: value }, { lineWidth: 0, indent: style.step, indentSeq: style.indentSeq });
16137
+ const lines = s.replace(/\n$/, "").slice("k:".length).split("\n");
16138
+ return indentLines(lines, col, 1).join(style.eol);
16139
+ }
16140
+ function indentLines(lines, col, from) {
16141
+ const pad2 = " ".repeat(col);
16142
+ return lines.map((l, i) => i < from || l === "" ? l : pad2 + l);
16143
+ }
16144
+ function flowText(value) {
16145
+ const s = stringify3(value, { lineWidth: 0, collectionStyle: "flow" }).replace(/\n$/, "");
16146
+ return s.includes("\n") ? JSON.stringify(value) : s;
16147
+ }
16148
+ function detectEol(text2) {
16149
+ const i = text2.indexOf("\n");
16150
+ return i > 0 && text2.charAt(i - 1) === "\r" ? "\r\n" : "\n";
16151
+ }
16152
+ function detectStyle(text2, root, eol) {
16153
+ let step = 2;
16154
+ for (const pair of root.items) {
16155
+ const v = pair.value;
16156
+ const entryKey = rangeOf(pair.key);
16157
+ const ownedKey = isMap6(v) && v.flow !== true ? rangeOf(v.items[0]?.key) : void 0;
16158
+ if (entryKey && ownedKey) {
16159
+ const d = colOf(text2, ownedKey[0]) - colOf(text2, entryKey[0]);
16160
+ if (d > 0) {
16161
+ step = d;
16162
+ break;
16163
+ }
16164
+ }
16165
+ }
16166
+ return { step, indentSeq: seqIndented(text2, root) ?? true, eol };
16167
+ }
16168
+ function seqIndented(text2, node) {
16169
+ if (isMap6(node)) {
16170
+ for (const pair of node.items) {
16171
+ const v = pair.value;
16172
+ if (isSeq4(v) && v.flow !== true && v.items.length > 0) {
16173
+ const k = rangeOf(pair.key);
16174
+ const s = rangeOf(v);
16175
+ if (k && s) return colOf(text2, s[0]) > colOf(text2, k[0]);
16176
+ }
16177
+ const inner = seqIndented(text2, v);
16178
+ if (inner !== void 0) return inner;
16179
+ }
16180
+ } else if (isSeq4(node)) {
16181
+ for (const item of node.items) {
16182
+ const inner = seqIndented(text2, item);
16183
+ if (inner !== void 0) return inner;
16184
+ }
16185
+ }
16186
+ return void 0;
16187
+ }
16188
+ function contentEnd(text2, node) {
16189
+ if (isMap6(node) && node.flow !== true) {
16190
+ const last = node.items.at(-1);
16191
+ if (last) return contentEnd(text2, last.value) ?? contentEnd(text2, last.key);
16192
+ } else if (isSeq4(node) && node.flow !== true) {
16193
+ const last = node.items.at(-1);
16194
+ if (last !== void 0) {
16195
+ return isPair(last) ? contentEnd(text2, last.value) ?? contentEnd(text2, last.key) : contentEnd(text2, last);
16196
+ }
16197
+ }
16198
+ const r = rangeOf(node);
16199
+ if (!r) return void 0;
16200
+ let end = r[1];
16201
+ while (end > r[0] && /\s/.test(text2.charAt(end - 1))) end--;
16202
+ return end;
16203
+ }
16204
+ function colonEnd(text2, pair) {
16205
+ const r = rangeOf(pair.key);
16206
+ if (!r) return void 0;
16207
+ let i = r[1];
16208
+ while (text2.charAt(i) === " " || text2.charAt(i) === " ") i++;
16209
+ return text2.charAt(i) === ":" ? i + 1 : void 0;
16210
+ }
16211
+ function lineEnd(text2, i) {
16212
+ const j = text2.indexOf("\n", i);
16213
+ if (j < 0) return text2.length;
16214
+ return j > 0 && text2.charAt(j - 1) === "\r" ? j - 1 : j;
16215
+ }
16216
+ function colOf(text2, offset) {
16217
+ if (offset === 0) return 0;
16218
+ let start = text2.lastIndexOf("\n", offset - 1) + 1;
16219
+ if (start === 0 && text2.startsWith("\uFEFF")) start = 1;
16220
+ return offset - start;
16221
+ }
16222
+ function rangeOf(node) {
16223
+ return isNode2(node) ? node.range ?? void 0 : void 0;
16224
+ }
16225
+ function emptySource(text2, node) {
16226
+ const r = rangeOf(node);
16227
+ return !r || text2.slice(r[0], r[1]).trim() === "";
16228
+ }
16229
+ function keyString(key) {
16230
+ return isScalar4(key) ? String(key.value) : String(key);
16231
+ }
16232
+ function isRecord5(x) {
16233
+ return typeof x === "object" && x !== null && !Array.isArray(x);
16234
+ }
16235
+
16236
+ // src/meta/commands/derive.ts
16237
+ var GENERATED_BY_ENV = "MANNI_GENERATED_BY";
14788
16238
  var SOURCE_HINT = "narrow --sources or --fields";
14789
16239
  async function runDerive(opts) {
14790
16240
  const cwd = opts.cwd ?? process.cwd();
16241
+ const targets = [];
16242
+ const typedInputs = opts.inputs.map((arg) => {
16243
+ if (arg === STDIN_TOKEN) return arg;
16244
+ const { page, lines } = parseProvenanceTarget(arg);
16245
+ if (lines === void 0) return arg;
16246
+ if (page === STDIN_TOKEN) return STDIN_TOKEN;
16247
+ targets.push({ target: arg, page });
16248
+ return page;
16249
+ });
16250
+ const env = opts.env ?? process.env;
16251
+ const flagGeneratedBy = machineName(opts.generatedBy);
16252
+ const generatedBy = opts.generatedBy !== void 0 ? flagGeneratedBy : machineName(env[GENERATED_BY_ENV]);
14791
16253
  const {
14792
16254
  config,
14793
16255
  inputs,
@@ -14801,7 +16263,7 @@ async function runDerive(opts) {
14801
16263
  cwd,
14802
16264
  configPath: opts.configPath,
14803
16265
  noConfig: opts.noConfig,
14804
- inputs: opts.inputs,
16266
+ inputs: typedInputs,
14805
16267
  ...opts.collections !== void 0 ? { collections: opts.collections } : {},
14806
16268
  onConfigLoaded: opts.onConfigLoaded
14807
16269
  });
@@ -14815,7 +16277,20 @@ async function runDerive(opts) {
14815
16277
  }
14816
16278
  const commands = commandsOf(config?.derive);
14817
16279
  const fields = resolveFields(opts.fields, config?.derive?.fields, commands);
16280
+ const wantsProvenance = fields.includes(PROVENANCE_FIELD);
16281
+ const firstTarget = targets[0];
16282
+ if (firstTarget !== void 0 && generatedBy === void 0) {
16283
+ throw new DocmetaError(
16284
+ `${firstTarget.target} names lines, which only --generated-by uses. Pass --generated-by, or drop the range.`
16285
+ );
16286
+ }
16287
+ if ((flagGeneratedBy !== void 0 || firstTarget !== void 0) && !wantsProvenance) {
16288
+ throw new DocmetaError(
16289
+ "--generated-by attributes provenance, which is not in --fields. Add provenance, or drop --generated-by."
16290
+ );
16291
+ }
14818
16292
  for (const field of fields) {
16293
+ if (field === PROVENANCE_FIELD) continue;
14819
16294
  const owner = manifestOwning(field, declaredCollections);
14820
16295
  if (owner !== void 0) {
14821
16296
  throw new DocmetaError(
@@ -14878,6 +16353,18 @@ async function runDerive(opts) {
14878
16353
  })
14879
16354
  });
14880
16355
  const files = fromCollections ? retainMembers(walked, membersFor) : walked;
16356
+ const rangesByPage = /* @__PURE__ */ new Map();
16357
+ for (const t of targets) {
16358
+ const label = relative13(base, resolve18(base, t.page)).split(sep9).join("/");
16359
+ if (!files.includes(label)) {
16360
+ throw new DocmetaError(
16361
+ `${t.target} does not name one file that derive reads; a range names lines of one file.`
16362
+ );
16363
+ }
16364
+ const named = rangesByPage.get(label) ?? [];
16365
+ if (!named.includes(t.target)) named.push(t.target);
16366
+ rangesByPage.set(label, named);
16367
+ }
14881
16368
  assertNonEmpty({
14882
16369
  files,
14883
16370
  inputs,
@@ -14891,7 +16378,7 @@ async function runDerive(opts) {
14891
16378
  const loaded = /* @__PURE__ */ new Map();
14892
16379
  const errors = /* @__PURE__ */ new Map();
14893
16380
  for (const label of files) {
14894
- const absPath = resolve17(base, label);
16381
+ const absPath = resolve18(base, label);
14895
16382
  const extractor = forcedExtractor ?? extractorForExtension(extname8(label));
14896
16383
  if (!extractor) {
14897
16384
  errors.set(label, {
@@ -14916,6 +16403,38 @@ async function runDerive(opts) {
14916
16403
  errors.set(label, { format: extractor.name, message: errorMessage(err) });
14917
16404
  }
14918
16405
  }
16406
+ const provenanceRoot = configDir ?? cwd;
16407
+ const manifests = wantsProvenance ? provenanceManifests(declaredCollections, provenanceRoot, base) : [];
16408
+ const provenanceCollections = declaredCollections.map((c) => ({
16409
+ ...c,
16410
+ externalMetadata: c.externalMetadata.filter((m) => m.keys.includes(PROVENANCE_FIELD))
16411
+ })).filter((c) => c.externalMetadata.length > 0);
16412
+ const provenanceIndex = manifests.length === 0 ? null : await loadExternalMetadata(provenanceCollections, { configDir: provenanceRoot, base });
16413
+ const places = /* @__PURE__ */ new Map();
16414
+ for (const doc of loaded.values()) {
16415
+ if (!wantsProvenance) break;
16416
+ const place = provenancePlace(
16417
+ doc.label,
16418
+ doc.extracted.data,
16419
+ manifests,
16420
+ declaredCollections,
16421
+ provenanceRoot,
16422
+ base
16423
+ );
16424
+ if (place !== void 0) {
16425
+ places.set(doc.label, place);
16426
+ doc.provenanceManifest = { absPath: place.absPath, entry: place.entry, join: place.join };
16427
+ }
16428
+ const named = rangesByPage.get(doc.label);
16429
+ if (named !== void 0 && generatedBy !== void 0) {
16430
+ doc.attribution = { targets: named, generatedBy };
16431
+ }
16432
+ }
16433
+ const stampOf = (doc) => {
16434
+ if (!places.has(doc.label)) return doc.extracted.data[PROVENANCE_FIELD];
16435
+ const members = memberOf(provenanceCollections, provenanceRoot, base, doc.label);
16436
+ return mergeExternalMetadata(doc.label, doc.extracted, provenanceIndex, members, base).extracted.data[PROVENANCE_FIELD];
16437
+ };
14919
16438
  const derived = await deriveMetadata([...loaded.values()], {
14920
16439
  cwd,
14921
16440
  base,
@@ -14926,7 +16445,9 @@ async function runDerive(opts) {
14926
16445
  commands,
14927
16446
  cache: opts.cache ?? true,
14928
16447
  now: opts.now ?? (() => /* @__PURE__ */ new Date()),
14929
- reviews: opts.reviews
16448
+ reviews: opts.reviews,
16449
+ machines: machinesOf(config?.derive),
16450
+ ...generatedBy !== void 0 ? { generatedBy } : {}
14930
16451
  });
14931
16452
  assertSourcesAvailable(derived.sources, SOURCE_HINT);
14932
16453
  for (const [name2, status] of Object.entries(derived.sources)) {
@@ -14995,6 +16516,20 @@ async function runDerive(opts) {
14995
16516
  }
14996
16517
  return holder[field];
14997
16518
  };
16519
+ const heldManifests = /* @__PURE__ */ new Map();
16520
+ const holdManifest = async (manifest) => {
16521
+ const already = heldManifests.get(manifest.absPath);
16522
+ if (already !== void 0) return already;
16523
+ let before;
16524
+ try {
16525
+ before = await readFile13(manifest.absPath, "utf8");
16526
+ } catch (err) {
16527
+ throw new DocmetaError(`Manifest ${manifest.file} could not be read: ${errorMessage(err)}`);
16528
+ }
16529
+ const held = { path: manifest.absPath, before, text: before };
16530
+ heldManifests.set(manifest.absPath, held);
16531
+ return held;
16532
+ };
14998
16533
  const results = [];
14999
16534
  for (const label of files) {
15000
16535
  const failure = errors.get(label);
@@ -15014,12 +16549,49 @@ async function runDerive(opts) {
15014
16549
  const at2 = pointerOf(field);
15015
16550
  return marks.marked.some((p) => isAtOrUnder(p, at2));
15016
16551
  };
16552
+ const place = places.get(label);
16553
+ const provenance = wantsProvenance ? judgeProvenance(stampOf(doc), derived.provenance?.get(label), record?.fields[PROVENANCE_FIELD] ?? null, place) : void 0;
15017
16554
  const compared = fields.map(
15018
- (field) => compareDerived(field, marks.data[field], record?.fields[field])
16555
+ (field) => field === PROVENANCE_FIELD && provenance !== void 0 ? provenance.field : compareDerived(field, marks.data[field], record?.fields[field])
15019
16556
  );
15020
- const shown = () => compared.map((f) => isMarked(f.field) ? redactedDerived(f) : f);
15021
- const pending = compared.filter((f) => f.status === "stale" || f.status === "unset");
16557
+ const shown = () => compared.map(
16558
+ (f) => f.field !== PROVENANCE_FIELD && isMarked(f.field) ? redactedDerived(f) : f
16559
+ );
16560
+ const findingsOf = (reported2) => reported2.flatMap(
16561
+ (f) => f.field === PROVENANCE_FIELD && provenance?.judged !== void 0 ? provenanceFindings(provenance.judged.comparisons, provenance.judged.derivation.page.bodyLine) : staleFindings([f], doc.extracted.lineFor)
16562
+ );
16563
+ if (provenance?.judged !== void 0 && flagGeneratedBy !== void 0 && doc.attribution === void 0 && ![...provenance.judged.derivation.evidenceByLine.values()].some((e) => e.rule === 1)) {
16564
+ opts.onNotice?.(
16565
+ `${label}: no uncommitted body lines; --generated-by attributes only what is not yet committed.`
16566
+ );
16567
+ }
16568
+ const stale = (f) => f.status === "stale" || f.status === "unset";
16569
+ const toManifest = provenance !== void 0 && place !== void 0 && stale(provenance.field) ? provenance : void 0;
16570
+ const pending = compared.filter((f) => stale(f) && !(f.field === PROVENANCE_FIELD && toManifest !== void 0));
16571
+ const onPage = pending.find((f) => f.field === PROVENANCE_FIELD);
16572
+ if (onPage !== void 0 && !provenanceFenced(doc.extracted)) {
16573
+ results.push(
16574
+ errorResult2(
16575
+ label,
16576
+ doc.format,
16577
+ `provenance cannot be stamped into the page: in the "${doc.format}" format the metadata is part of the body it pins. Keep provenance in an externalMetadata manifest.`,
16578
+ check,
16579
+ shown()
16580
+ )
16581
+ );
16582
+ continue;
16583
+ }
15022
16584
  let changed = false;
16585
+ if (toManifest?.judged !== void 0 && place !== void 0) {
16586
+ const held = await holdManifest(place.manifest);
16587
+ const before = held.text;
16588
+ const where = { entry: place.entry, key: PROVENANCE_FIELD, join: place.join, file: place.manifest.file };
16589
+ held.text = toManifest.judged.plan.length === 0 ? removeManifestKey(held.text, where).text : spliceManifestValue(held.text, { ...where, value: toManifest.judged.plan }).text;
16590
+ if (held.text !== before) {
16591
+ changed = true;
16592
+ if (!dryRun) markWritten(toManifest.field);
16593
+ }
16594
+ }
15023
16595
  if (pending.length > 0) {
15024
16596
  if (typeof doc.apply !== "function") {
15025
16597
  if (!dryRun) {
@@ -15037,20 +16609,37 @@ async function runDerive(opts) {
15037
16609
  changed = true;
15038
16610
  } else {
15039
16611
  const patch = {};
16612
+ const deletions = [];
15040
16613
  for (const f of pending) {
16614
+ if (f.field === PROVENANCE_FIELD) {
16615
+ if (Array.isArray(f.derived) && f.derived.length === 0) deletions.push(f.field);
16616
+ else patch[f.field] = f.derived;
16617
+ continue;
16618
+ }
15041
16619
  patch[f.field] = isMarked(f.field) ? await sealField(f.field, f.derived, marks.marked) : f.derived;
15042
16620
  }
16621
+ const apply = doc.apply;
16622
+ const write = (value) => apply(doc.content, value, {
16623
+ filePath: label,
16624
+ elements: doc.elements,
16625
+ ...deletions.length > 0 ? { deletions } : {}
16626
+ });
15043
16627
  let next;
15044
16628
  try {
15045
- next = doc.apply(doc.content, patch, { filePath: label, elements: doc.elements });
16629
+ next = write(patch);
16630
+ const plan = patch[PROVENANCE_FIELD];
16631
+ const shift = bodyShift(doc.content, next);
16632
+ if (shift > 0 && Array.isArray(plan)) {
16633
+ next = write({ ...patch, [PROVENANCE_FIELD]: shiftEntries(provenanceEntries(plan), shift) });
16634
+ }
15046
16635
  } catch (err) {
15047
16636
  results.push(errorResult2(label, doc.format, errorMessage(err), check, shown()));
15048
16637
  continue;
15049
16638
  }
15050
- changed = next !== doc.content;
15051
- if (changed && !dryRun) {
16639
+ changed = changed || next !== doc.content;
16640
+ if (next !== doc.content && !dryRun) {
15052
16641
  await writeFileAtomic(doc.absPath, next);
15053
- for (const f of pending) f.written = true;
16642
+ for (const f of pending) markWritten(f);
15054
16643
  }
15055
16644
  }
15056
16645
  }
@@ -15060,18 +16649,32 @@ async function runDerive(opts) {
15060
16649
  format: doc.format,
15061
16650
  fields: reported,
15062
16651
  changed,
15063
- ...check ? { findings: staleFindings(reported, doc.extracted.lineFor) } : {}
16652
+ ...check ? { findings: findingsOf(reported) } : {}
15064
16653
  });
15065
16654
  }
16655
+ if (!dryRun) {
16656
+ for (const held of heldManifests.values()) {
16657
+ if (held.text !== held.before) await writeFileAtomic(held.path, held.text);
16658
+ }
16659
+ }
16660
+ const summary = summarize2(results);
15066
16661
  return {
15067
16662
  results,
15068
- summary: summarize2(results),
16663
+ summary: wantsProvenance ? { ...summary, ranges: countRanges(results) } : summary,
15069
16664
  dryRun,
15070
16665
  check,
15071
16666
  sources: derived.sources,
15072
16667
  frame: { cwd, base: configDir ?? cwd, runBase: base }
15073
16668
  };
15074
16669
  }
16670
+ function deriveFailed(run2) {
16671
+ if (run2.summary.errors > 0) return true;
16672
+ return run2.check && run2.results.some((r) => (r.findings ?? []).length > 0);
16673
+ }
16674
+ function machineName(value) {
16675
+ const name2 = value?.trim();
16676
+ return name2 === void 0 || name2 === "" ? void 0 : name2;
16677
+ }
15075
16678
  function resolveFields(flag, configured, commands) {
15076
16679
  const names = flag ?? configured;
15077
16680
  if (names === void 0 || names.length === 0) {
@@ -15139,6 +16742,59 @@ function summarize2(results) {
15139
16742
  }
15140
16743
  return summary;
15141
16744
  }
16745
+ function markWritten(field) {
16746
+ field.written = true;
16747
+ for (const range of field.ranges ?? []) {
16748
+ if (range.status !== "current") range.written = true;
16749
+ }
16750
+ }
16751
+ function countRanges(results) {
16752
+ let n = 0;
16753
+ for (const r of results) {
16754
+ for (const f of r.fields) n += (f.ranges ?? []).filter((range) => range.written).length;
16755
+ }
16756
+ return n;
16757
+ }
16758
+ function judgeProvenance(asserted, derivation, value, place) {
16759
+ const base = {
16760
+ field: PROVENANCE_FIELD,
16761
+ ...asserted !== void 0 ? { asserted } : {},
16762
+ ...place !== void 0 ? { manifest: place.manifest.file } : {}
16763
+ };
16764
+ if (derivation === void 0) {
16765
+ return { field: { ...base, derived: null, status: "unknown", written: false } };
16766
+ }
16767
+ const comparisons = compareProvenance(provenanceEntries(asserted), derivation);
16768
+ const plan = planProvenanceWrite(comparisons, derivation);
16769
+ const settled = comparisons.every((c) => c.status === "current");
16770
+ const status = settled ? "current" : asserted === void 0 || comparisons.every((c) => c.status === "unset") ? "unset" : "stale";
16771
+ return {
16772
+ field: {
16773
+ ...base,
16774
+ derived: plan,
16775
+ source: "git",
16776
+ ...value !== null ? { evidence: value.evidence } : {},
16777
+ status,
16778
+ written: false,
16779
+ ranges: rangeResults(comparisons, derivation.page.bodyLine)
16780
+ },
16781
+ judged: { derivation, comparisons, plan }
16782
+ };
16783
+ }
16784
+ function bodyShift(before, after) {
16785
+ const was = readProvenancePage(before).body;
16786
+ const now = readProvenancePage(after).body;
16787
+ const shift = now.length - was.length;
16788
+ if (shift <= 0) return 0;
16789
+ return now.slice(shift).join("\n") === was.join("\n") ? shift : 0;
16790
+ }
16791
+ function shiftEntries(entries, shift) {
16792
+ return entries.map((e) => {
16793
+ const span = parseLines(e.lines);
16794
+ if (span === void 0) return e;
16795
+ return { ...e, lines: lineSpec({ start: span.start + shift, end: span.end + shift }) };
16796
+ });
16797
+ }
15142
16798
 
15143
16799
  // src/meta/reporters/fill.ts
15144
16800
  var FILL_FORMATS = ["pretty", "json", "github"];
@@ -15174,6 +16830,26 @@ function renderFillPretty(run2, opts = {}) {
15174
16830
  ` ${c.cyan(f.field)} ${formatValue(f.value)} ${c.dim(score(f.confidence))}`
15175
16831
  );
15176
16832
  }
16833
+ const record = result.metaProvenance;
16834
+ if (record?.written === true) {
16835
+ lines.push(
16836
+ ` ${c.cyan("meta-provenance")} ${record.entry["generated-by"]}: ${record.entry.fields.join(", ")}`
16837
+ );
16838
+ } else if (record?.skipReason === "schema-mismatch") {
16839
+ lines.push(
16840
+ c.dim(" meta-provenance not written: this page's schemas do not allow it")
16841
+ );
16842
+ } else if (record?.skipReason === "manifest-owned") {
16843
+ lines.push(
16844
+ c.dim(
16845
+ ` meta-provenance not written: owned by manifest ${record.manifest}, which manni meta fill does not write`
16846
+ )
16847
+ );
16848
+ } else if (record?.skipReason === "unwritable") {
16849
+ lines.push(
16850
+ c.dim(` meta-provenance not written: ${result.format} metadata cannot hold it`)
16851
+ );
16852
+ }
15177
16853
  if (low.length > 0) {
15178
16854
  lines.push(
15179
16855
  c.dim(
@@ -15325,26 +17001,49 @@ function renderDerivePretty(run2, opts = {}) {
15325
17001
  lines.push(`${result.file} ${c.dim("current")}${unknownNote(result, c.dim)}`);
15326
17002
  continue;
15327
17003
  }
15328
- lines.push(result.file);
15329
- const nameWidth = Math.max(...pending.map((f) => f.field.length));
15330
- const changeWidth = Math.max(...pending.map((f) => change(f).length));
17004
+ const page = [];
17005
+ const held = /* @__PURE__ */ new Map();
15331
17006
  for (const f of pending) {
15332
- const name2 = c.cyan(f.field.padEnd(nameWidth));
15333
- const from = f.status === "unset" ? c.dim("(unset)") : c.dim(fmt2(f.asserted));
15334
- const to = c.green(fmt2(f.derived));
15335
- const pad2 = " ".repeat(changeWidth - change(f).length);
15336
- const trail = c.dim(`(${f.source ?? "a source"}: ${f.evidence ?? "no evidence"})`);
15337
- lines.push(` ${name2} ${from} \u2192 ${to}${pad2} ${trail}`);
17007
+ if (f.ranges === void 0) {
17008
+ page.push(fieldRow(f, c));
17009
+ continue;
17010
+ }
17011
+ const rows = f.ranges.filter((r) => r.status !== "current").map((r) => rangeRow(f, r, c));
17012
+ if (f.manifest === void 0) page.push(...rows);
17013
+ else held.set(f.manifest, [...held.get(f.manifest) ?? [], ...rows]);
17014
+ }
17015
+ const all = [...page, ...[...held.values()].flat()];
17016
+ const nameWidth = Math.max(...all.map((r) => r.name.length));
17017
+ const changeWidth = Math.max(...all.map((r) => r.plain.length));
17018
+ const print = (row) => {
17019
+ const pad2 = " ".repeat(changeWidth - row.plain.length);
17020
+ lines.push(` ${c.cyan(row.name.padEnd(nameWidth))} ${row.change}${pad2} ${row.trail}`);
17021
+ };
17022
+ if (page.length > 0 || held.size === 0) lines.push(result.file);
17023
+ page.forEach(print);
17024
+ for (const [manifest, rows] of held) {
17025
+ lines.push(`${manifest} ${c.dim(`(for ${result.file})`)}`);
17026
+ rows.forEach(print);
15338
17027
  }
15339
17028
  const note = unknownNote(result, c.dim);
15340
17029
  if (note !== "") lines.push(` ${note}`);
15341
17030
  }
15342
17031
  const s = run2.summary;
15343
17032
  const parts = [plural2(s.files, "file")];
17033
+ const provenance = run2.results.flatMap((r) => r.fields.filter((f) => f.ranges !== void 0));
17034
+ const provenanceFields = provenance.filter((f) => run2.dryRun ? f.status === "stale" || f.status === "unset" : f.written).length;
15344
17035
  if (run2.dryRun) {
15345
- parts.push(`${s.changed} would change`, plural2(s.stale + s.unset, "field"));
17036
+ const ranges = provenance.reduce((n, f) => n + (f.ranges ?? []).filter((r) => r.status !== "current").length, 0);
17037
+ const fields = s.stale + s.unset - provenanceFields;
17038
+ parts.push(`${s.changed} would change`);
17039
+ if (fields > 0 || ranges === 0) parts.push(plural2(fields, "field"));
17040
+ if (ranges > 0) parts.push(plural2(ranges, "range"));
15346
17041
  } else {
15347
- parts.push(`${s.changed} changed`, `${plural2(s.written, "field")} written`);
17042
+ const ranges = s.ranges ?? 0;
17043
+ const fields = s.written - provenanceFields;
17044
+ parts.push(`${s.changed} changed`);
17045
+ if (fields > 0 || ranges === 0) parts.push(`${plural2(fields, "field")} written`);
17046
+ if (ranges > 0) parts.push(`${plural2(ranges, "range")} written`);
15348
17047
  }
15349
17048
  if (s.errors > 0) parts.push(plural2(s.errors, "error"));
15350
17049
  let footer = parts.join(", ");
@@ -15353,9 +17052,47 @@ function renderDerivePretty(run2, opts = {}) {
15353
17052
  lines.push(s.errors > 0 ? c.yellow(footer) : footer);
15354
17053
  return lines.join("\n");
15355
17054
  }
15356
- function change(f) {
17055
+ function fieldRow(f, c) {
15357
17056
  const from = f.status === "unset" ? "(unset)" : fmt2(f.asserted);
15358
- return `${from} \u2192 ${fmt2(f.derived)}`;
17057
+ const to = fmt2(f.derived);
17058
+ return {
17059
+ name: f.field,
17060
+ change: `${c.dim(from)} \u2192 ${c.green(to)}`,
17061
+ plain: `${from} \u2192 ${to}`,
17062
+ trail: c.dim(`(${f.source ?? "a source"}: ${f.evidence ?? "no evidence"})`)
17063
+ };
17064
+ }
17065
+ function rangeRow(f, r, c) {
17066
+ const head = `lines ${r.lines}: `;
17067
+ const machine = r["generated-by"];
17068
+ let from;
17069
+ let to;
17070
+ switch (r.status) {
17071
+ case "moved":
17072
+ from = "";
17073
+ to = `moved from ${r.from?.lines ?? r.lines}`;
17074
+ break;
17075
+ case "stale":
17076
+ from = r.from?.["generated-by"] ?? "(unset)";
17077
+ to = machine;
17078
+ break;
17079
+ case "changed":
17080
+ from = machine;
17081
+ to = "re-derived";
17082
+ break;
17083
+ case "unset":
17084
+ case "current":
17085
+ from = "(unset)";
17086
+ to = machine;
17087
+ break;
17088
+ }
17089
+ const arrow = from === "" ? "" : `${from} \u2192 `;
17090
+ return {
17091
+ name: f.field,
17092
+ change: `${head}${from === "" ? "" : `${c.dim(from)} \u2192 `}${c.green(to)}`,
17093
+ plain: `${head}${arrow}${to}`,
17094
+ trail: c.dim(`(${f.source ?? "git"}: ${r.evidence})`)
17095
+ };
15359
17096
  }
15360
17097
  function unknownNote(result, dim) {
15361
17098
  const unknown = result.fields.filter((f) => f.status === "unknown").map((f) => f.field);
@@ -15373,6 +17110,15 @@ function stringifyValue(value) {
15373
17110
  if (typeof value === "string") return value;
15374
17111
  return JSON.stringify(value);
15375
17112
  }
17113
+ function valueOf2(r, field, value) {
17114
+ if (field !== PROVENANCE_FIELD || r.provenance === void 0) return stringifyValue(value);
17115
+ if (value === void 0 || value === null) return "(unset)";
17116
+ const entries = provenanceEntries(value);
17117
+ if (!Array.isArray(value) || entries.length !== value.length || entries.length === 0) {
17118
+ return stringifyValue(value);
17119
+ }
17120
+ return renderProvenanceValue(entries, r.provenance.bodyLine);
17121
+ }
15376
17122
  function evidenceOf(d) {
15377
17123
  const repeated = ` (${stringifyValue(d.value)})`;
15378
17124
  return d.evidence.endsWith(repeated) ? d.evidence.slice(0, -repeated.length) : d.evidence;
@@ -15384,6 +17130,10 @@ function annotation(r, field) {
15384
17130
  if (origin === "derived") {
15385
17131
  return d == null ? "" : ` (derived, ${d.source}: ${evidenceOf(d)})`;
15386
17132
  }
17133
+ if (field === PROVENANCE_FIELD && r.provenance !== void 0) {
17134
+ if (r.provenance.current !== false) return " (asserted)";
17135
+ return ` (asserted; ${d?.source ?? "git"} says ${valueOf2(r, field, d?.value)})`;
17136
+ }
15387
17137
  if (d == null) return " (asserted)";
15388
17138
  if (compareDerived(field, r.resolved?.[field], d).status === "current") {
15389
17139
  return " (asserted)";
@@ -15404,7 +17154,7 @@ function renderGet(results, fields, opts = {}) {
15404
17154
  const note = annotation(r, f);
15405
17155
  const suffix = note === "" ? "" : c.dim(note);
15406
17156
  lines.push(
15407
- `${c.dim(`${r.file}:`)} ${f}=${stringifyValue(effective[f])}${suffix}`
17157
+ `${c.dim(`${r.file}:`)} ${f}=${valueOf2(r, f, effective[f])}${suffix}`
15408
17158
  );
15409
17159
  }
15410
17160
  }
@@ -15518,7 +17268,8 @@ export {
15518
17268
  ToolError,
15519
17269
  errorMessage,
15520
17270
  programName,
15521
- setProgramName,
17271
+ fail,
17272
+ runIfMain,
15522
17273
  externalMetadataUrlProblem,
15523
17274
  parseCollections,
15524
17275
  selectCollections,
@@ -15528,8 +17279,6 @@ export {
15528
17279
  generateEncryptionKey,
15529
17280
  encryptValue,
15530
17281
  decryptValue,
15531
- KEYED_PIN_PREFIX,
15532
- keyedPin,
15533
17282
  findGitRoot,
15534
17283
  notice,
15535
17284
  findConfigFile,
@@ -15538,6 +17287,27 @@ export {
15538
17287
  readFamilyConfigFile,
15539
17288
  escapeWorkflowCommandMessage,
15540
17289
  escapeWorkflowCommandProperty,
17290
+ INTEGRITY_SHAPE,
17291
+ integrityOf,
17292
+ isIntegrity,
17293
+ LineRangeError,
17294
+ normalizeText,
17295
+ splitLines,
17296
+ sliceLines,
17297
+ hashRange,
17298
+ hashLines,
17299
+ isKeyedPin,
17300
+ pinOfLines,
17301
+ MOVE_WINDOW_LINES,
17302
+ MOVE_BUDGET_BYTES,
17303
+ MAX_RANGE_LINES,
17304
+ findWindows,
17305
+ parseLines,
17306
+ lineSpec,
17307
+ spellLines,
17308
+ toFileLines,
17309
+ toBodyLines,
17310
+ splitPageArgument,
15541
17311
  ENCRYPTION_KEY_ENV,
15542
17312
  resolveEncryptionKey,
15543
17313
  writeEncryptionKey,
@@ -15551,7 +17321,6 @@ export {
15551
17321
  extractFrontmatter,
15552
17322
  isErrorSeverity,
15553
17323
  DocmetaError,
15554
- deepEqual,
15555
17324
  toJsonText,
15556
17325
  applyFrontmatter,
15557
17326
  extractorForExtension,
@@ -15568,9 +17337,6 @@ export {
15568
17337
  DEFAULT_TTL_HOURS,
15569
17338
  schemaCacheDir,
15570
17339
  SchemaCache,
15571
- INTEGRITY_SHAPE,
15572
- integrityOf,
15573
- isIntegrity,
15574
17340
  listBuiltins,
15575
17341
  classifyRef,
15576
17342
  fetchSchemaBytes,
@@ -15579,7 +17345,6 @@ export {
15579
17345
  resolveBaselineRequest,
15580
17346
  settleBaseline,
15581
17347
  DEFAULT_SCHEMAS,
15582
- FILE_SCHEMA_KEY,
15583
17348
  schemaEntryRef,
15584
17349
  collectSchemaPins,
15585
17350
  rebaseConfigSchemaRefs,
@@ -15627,6 +17392,7 @@ export {
15627
17392
  orphanEntries,
15628
17393
  orphanJoins,
15629
17394
  orphanError,
17395
+ spliceManifestValue,
15630
17396
  isParamName,
15631
17397
  memberOf,
15632
17398
  Validator,
@@ -15667,6 +17433,7 @@ export {
15667
17433
  runQuery,
15668
17434
  runFill,
15669
17435
  runDerive,
17436
+ deriveFailed,
15670
17437
  FILL_FORMATS,
15671
17438
  FILL_FORMAT_LIST,
15672
17439
  isFillFormat,
@@ -15681,4 +17448,4 @@ export {
15681
17448
  renderQuery,
15682
17449
  renderQueryCsv
15683
17450
  };
15684
- //# sourceMappingURL=chunk-XU2QTJQQ.js.map
17451
+ //# sourceMappingURL=chunk-2DC2GSRH.js.map