@tangle-network/agent-knowledge 6.2.0 → 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +1 -1
  3. package/dist/benchmarks/index.d.ts +1 -1
  4. package/dist/benchmarks/index.js +1 -1
  5. package/dist/{benchmarks-CmW6iORW.js → benchmarks-C8L7HJb4.js} +2 -2
  6. package/dist/{benchmarks-CmW6iORW.js.map → benchmarks-C8L7HJb4.js.map} +1 -1
  7. package/dist/cli.js +1 -1
  8. package/dist/{ids-DRqPZ42_.js → ids-Bevz_pXV.js} +6 -2
  9. package/dist/ids-Bevz_pXV.js.map +1 -0
  10. package/dist/{index-3z3lFJnE.d.ts → index-D0wc4GYg.d.ts} +2 -2
  11. package/dist/{index-3z3lFJnE.d.ts.map → index-D0wc4GYg.d.ts.map} +1 -1
  12. package/dist/{index-DHebFLgU.d.ts → index-Dwp3Mx-w.d.ts} +3 -3
  13. package/dist/{index-DHebFLgU.d.ts.map → index-Dwp3Mx-w.d.ts.map} +1 -1
  14. package/dist/index.d.ts +42 -16
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +86 -35
  17. package/dist/index.js.map +1 -1
  18. package/dist/{inspect-LUMkas6A.js → inspect-CJQGYuKa.js} +156 -33
  19. package/dist/inspect-CJQGYuKa.js.map +1 -0
  20. package/dist/memory/index.d.ts +2 -2
  21. package/dist/memory/index.js +2 -2
  22. package/dist/{memory-C6KPRhoU.js → memory-CIYRB_Q8.js} +3 -3
  23. package/dist/{memory-C6KPRhoU.js.map → memory-CIYRB_Q8.js.map} +1 -1
  24. package/dist/sources/index.js +1 -1
  25. package/dist/{types-Cd57BPRt.d.ts → types-BOfmvDe-.d.ts} +21 -6
  26. package/dist/{types-Cd57BPRt.d.ts.map → types-BOfmvDe-.d.ts.map} +1 -1
  27. package/dist/viz/index.d.ts +1 -1
  28. package/docs/architecture.md +4 -2
  29. package/package.json +1 -1
  30. package/dist/ids-DRqPZ42_.js.map +0 -1
  31. package/dist/inspect-LUMkas6A.js.map +0 -1
@@ -1,4 +1,4 @@
1
- import { n as slugify, r as stableId, t as sha256 } from "./ids-DRqPZ42_.js";
1
+ import { i as textSourceId, n as slugify, r as stableId, t as sha256 } from "./ids-Bevz_pXV.js";
2
2
  import { n as searchKnowledge } from "./search-CP0QtBJZ.js";
3
3
  import { createHash, randomUUID } from "node:crypto";
4
4
  import { lstat, mkdir, mkdtemp, open, readFile, readdir, realpath, rename, rm } from "node:fs/promises";
@@ -1241,10 +1241,20 @@ function claimId(text) {
1241
1241
  function claimEvidenceId(claim) {
1242
1242
  return `e_${sha256(JSON.stringify([
1243
1243
  claim.claimId,
1244
+ claim.sourceId,
1244
1245
  claim.sourceUri,
1246
+ claim.sourceContentHash,
1245
1247
  claim.contradictsClaimId ?? null
1246
1248
  ])).slice(0, 16)}`;
1247
1249
  }
1250
+ /** Canonical key for one exact registry-id + original-URI + hash source version. */
1251
+ function researchSourceVersionKey(source) {
1252
+ return JSON.stringify([
1253
+ source.sourceId,
1254
+ source.uri,
1255
+ source.contentHash
1256
+ ]);
1257
+ }
1248
1258
  /** Case-, whitespace-, and stylistic-punctuation-insensitive claim identity form. */
1249
1259
  function normalizeClaimText(text) {
1250
1260
  return text.normalize("NFKC").toLowerCase().replace(/<=|≤/gu, " symbol_less_than_or_equal ").replace(/>=|≥/gu, " symbol_greater_than_or_equal ").replace(/!=|≠/gu, " symbol_not_equal ").replace(/==|=/gu, " symbol_equal ").replace(/±/gu, " symbol_plus_or_minus ").replace(/[≈~]/gu, " symbol_approximately ").replace(/<|←|⇐/gu, " symbol_less_or_left ").replace(/>|→|⇒/gu, " symbol_greater_or_right ").replace(/[+]/gu, " symbol_plus_or_positive ").replace(/[-−]/gu, " symbol_minus_or_negative ").replace(/[^\p{L}\p{N}\s]+/gu, " ").replace(/\s+/g, " ").trim();
@@ -1279,6 +1289,7 @@ function deepQuestionId(kind, text) {
1279
1289
  function assertTrackedClaimIntegrity(claim) {
1280
1290
  if (claim.id !== claimId(claim.text)) throw new Error(`claim '${claim.id}' does not match its text-derived identity`);
1281
1291
  if (claim.text !== claim.text.trim()) throw new Error(`claim '${claim.id}' text must not have surrounding whitespace`);
1292
+ if (claim.supportingUris.length === 0) throw new Error(`claim '${claim.id}' must have registered supporting evidence`);
1282
1293
  assertSortedUnique(`claim '${claim.id}' supportingUris`, claim.supportingUris);
1283
1294
  assertSortedUnique(`claim '${claim.id}' supportingHosts`, claim.supportingHosts);
1284
1295
  assertSortedUnique(`claim '${claim.id}' contradicts`, claim.contradicts);
@@ -1286,6 +1297,7 @@ function assertTrackedClaimIntegrity(claim) {
1286
1297
  if (!sameStrings(claim.supportingHosts, expectedHosts)) throw new Error(`claim '${claim.id}' supportingHosts must equal the hosts derived from supportingUris`);
1287
1298
  if (claim.contradicts.includes(claim.id)) throw new Error(`claim '${claim.id}' cannot contradict itself`);
1288
1299
  if (claim.contradicts.length > 0 && !claim.contested) throw new Error(`claim '${claim.id}' with a contradiction must be contested`);
1300
+ if (claim.contested && claim.contradicts.length === 0) throw new Error(`claim '${claim.id}' cannot be contested without a contradiction`);
1289
1301
  }
1290
1302
  /** Refuse a deep question whose stable identity or set fields are malformed. */
1291
1303
  function assertDeepQuestionIntegrity(question) {
@@ -1298,25 +1310,50 @@ function assertResearchClaimEvidenceIntegrity(evidence) {
1298
1310
  if (evidence.claimId !== claimId(evidence.text)) throw new Error(`claim evidence '${evidence.id}' does not match its text-derived claim identity`);
1299
1311
  if (evidence.id !== claimEvidenceId(evidence)) throw new Error(`claim evidence '${evidence.id}' does not match its content-derived identity`);
1300
1312
  if (evidence.text !== evidence.text.trim()) throw new Error(`claim evidence '${evidence.id}' text must not have surrounding whitespace`);
1313
+ if (!/^[a-f0-9]{64}$/.test(evidence.sourceContentHash)) throw new Error(`claim evidence '${evidence.id}' sourceContentHash must be a SHA-256 digest`);
1314
+ const expectedSourceId = textSourceId(evidence.sourceUri, evidence.sourceContentHash);
1315
+ if (evidence.sourceId !== expectedSourceId) throw new Error(`claim evidence '${evidence.id}' sourceId does not match URI-and-content identity`);
1301
1316
  if (evidence.contradictsClaimId === evidence.claimId) throw new Error(`claim evidence '${evidence.id}' cannot contradict its own claim`);
1302
1317
  }
1303
1318
  /** Refuse a ledger that is not one canonical, internally consistent record. */
1304
1319
  function assertResearchClaimLedgerIntegrity(ledger) {
1320
+ if (ledger.schemaVersion !== 2) throw new Error(`claim ledger '${ledger.id}' must use schema version 2`);
1305
1321
  if (ledger.preparedRounds !== void 0 && ledger.preparedRounds <= ledger.rounds) throw new Error(`claim ledger '${ledger.id}' preparedRounds must be greater than completed rounds`);
1306
1322
  assertSortedUnique(`claim ledger '${ledger.id}' claimEvidence`, ledger.claimEvidence.map((evidence) => evidence.id));
1307
- assertSortedUnique(`claim ledger '${ledger.id}' registeredSourceUris`, ledger.registeredSourceUris);
1323
+ for (const source of ledger.registeredSources) {
1324
+ if (source.sourceId.length === 0 || source.uri.length === 0 || !/^[a-f0-9]{64}$/.test(source.contentHash)) throw new Error(`claim ledger '${ledger.id}' contains an invalid registered source version`);
1325
+ const expectedSourceId = textSourceId(source.uri, source.contentHash);
1326
+ if (source.sourceId !== expectedSourceId) throw new Error(`registered source '${source.sourceId}' does not match URI-and-content identity '${expectedSourceId}'`);
1327
+ }
1328
+ assertSortedUnique(`claim ledger '${ledger.id}' registeredSources`, ledger.registeredSources.map((source) => source.sourceId));
1308
1329
  assertSortedUnique(`claim ledger '${ledger.id}' claims`, ledger.claims.map((claim) => claim.id));
1309
1330
  assertSortedUnique(`claim ledger '${ledger.id}' questions`, ledger.questions.map((question) => question.id));
1310
1331
  for (const evidence of ledger.claimEvidence) assertResearchClaimEvidenceIntegrity(evidence);
1311
- const registeredSourceUris = new Set(ledger.registeredSourceUris);
1332
+ const registeredSources = new Set(ledger.registeredSources.map(researchSourceVersionKey));
1333
+ const registeredEvidence = ledger.claimEvidence.filter((evidence) => registeredSources.has(researchSourceVersionKey({
1334
+ sourceId: evidence.sourceId,
1335
+ uri: evidence.sourceUri,
1336
+ contentHash: evidence.sourceContentHash
1337
+ })));
1338
+ const materializedClaims = new Map(ledger.claims.map((claim) => [claim.id, claim]));
1312
1339
  for (const claim of ledger.claims) {
1313
1340
  assertTrackedClaimIntegrity(claim);
1314
- for (const sourceUri of claim.supportingUris) if (!registeredSourceUris.has(sourceUri)) throw new Error(`claim '${claim.id}' counts source '${sourceUri}' before its registration is confirmed`);
1341
+ for (const sourceUri of claim.supportingUris) if (!registeredEvidence.some((evidence) => evidence.claimId === claim.id && evidence.sourceUri === sourceUri)) throw new Error(`claim '${claim.id}' counts source '${sourceUri}' without exact registered evidence`);
1342
+ for (const contradictedClaimId of claim.contradicts) {
1343
+ const contradictedClaim = materializedClaims.get(contradictedClaimId);
1344
+ if (!contradictedClaim) throw new Error(`claim '${claim.id}' contradicts unmaterialized claim '${contradictedClaimId}'`);
1345
+ if (!contradictedClaim.contradicts.includes(claim.id)) throw new Error(`claim '${claim.id}' has an asymmetric contradiction with '${contradictedClaimId}'`);
1346
+ if (!registeredEvidence.some((evidence) => evidence.claimId === claim.id && evidence.contradictsClaimId === contradictedClaimId || evidence.claimId === contradictedClaimId && evidence.contradictsClaimId === claim.id)) throw new Error(`claim '${claim.id}' contradicts '${contradictedClaimId}' without exact registered evidence`);
1347
+ }
1315
1348
  }
1316
- const claimsById = new Map(ledger.claims.map((claim) => [claim.id, claim]));
1349
+ const claimsById = materializedClaims;
1317
1350
  const claimIds = new Set(claimsById.keys());
1318
1351
  for (const evidence of ledger.claimEvidence) {
1319
- if (!registeredSourceUris.has(evidence.sourceUri)) continue;
1352
+ if (!registeredSources.has(researchSourceVersionKey({
1353
+ sourceId: evidence.sourceId,
1354
+ uri: evidence.sourceUri,
1355
+ contentHash: evidence.sourceContentHash
1356
+ }))) continue;
1320
1357
  const claim = claimsById.get(evidence.claimId);
1321
1358
  if (!claim?.supportingUris.includes(evidence.sourceUri)) throw new Error(`registered claim evidence '${evidence.id}' must be materialized in its claim`);
1322
1359
  if (evidence.contradictsClaimId !== void 0 && claimsById.has(evidence.contradictsClaimId) && !claim.contradicts.includes(evidence.contradictsClaimId)) throw new Error(`registered claim evidence '${evidence.id}' must materialize its contradiction`);
@@ -1329,12 +1366,13 @@ function assertResearchClaimLedgerIntegrity(ledger) {
1329
1366
  /** A ledger with nothing in it yet. */
1330
1367
  function emptyClaimLedger(id, goal) {
1331
1368
  return {
1369
+ schemaVersion: 2,
1332
1370
  id,
1333
1371
  ...goal === void 0 ? {} : { goal },
1334
1372
  updatedAt: (/* @__PURE__ */ new Date(0)).toISOString(),
1335
1373
  rounds: 0,
1336
1374
  claimEvidence: [],
1337
- registeredSourceUris: [],
1375
+ registeredSources: [],
1338
1376
  claims: [],
1339
1377
  questions: []
1340
1378
  };
@@ -1347,10 +1385,10 @@ function emptyClaimLedger(id, goal) {
1347
1385
  * writer and an evidence-producing writer can arrive in either order.
1348
1386
  */
1349
1387
  function materializeRegisteredClaimEvidence(ledger) {
1350
- const registered = new Set(ledger.registeredSourceUris);
1388
+ const registered = new Set(ledger.registeredSources.map(researchSourceVersionKey));
1351
1389
  const claims = new Map(ledger.claims.map((claim) => [claim.id, claim]));
1352
1390
  for (const evidence of ledger.claimEvidence) {
1353
- if (!registered.has(evidence.sourceUri)) continue;
1391
+ if (!registered.has(sourceVersionKeyOfEvidence(evidence))) continue;
1354
1392
  const host = claimSourceHost(evidence.sourceUri);
1355
1393
  const observed = {
1356
1394
  id: evidence.claimId,
@@ -1366,7 +1404,7 @@ function materializeRegisteredClaimEvidence(ledger) {
1366
1404
  }
1367
1405
  for (const evidence of ledger.claimEvidence) {
1368
1406
  const otherId = evidence.contradictsClaimId;
1369
- if (!registered.has(evidence.sourceUri) || !otherId || !claims.has(otherId)) continue;
1407
+ if (!registered.has(sourceVersionKeyOfEvidence(evidence)) || !otherId || !claims.has(otherId)) continue;
1370
1408
  const claim = claims.get(evidence.claimId);
1371
1409
  if (!claim) continue;
1372
1410
  claims.set(claim.id, {
@@ -1380,6 +1418,13 @@ function materializeRegisteredClaimEvidence(ledger) {
1380
1418
  claims: [...claims.values()].sort((left, right) => left.id.localeCompare(right.id))
1381
1419
  });
1382
1420
  }
1421
+ function sourceVersionKeyOfEvidence(evidence) {
1422
+ return researchSourceVersionKey({
1423
+ sourceId: evidence.sourceId,
1424
+ uri: evidence.sourceUri,
1425
+ contentHash: evidence.sourceContentHash
1426
+ });
1427
+ }
1383
1428
  /**
1384
1429
  * Raised when two ledgers that accumulated evidence for DIFFERENT goals are
1385
1430
  * combined. Merging them would pool two questions' evidence into one
@@ -1461,13 +1506,14 @@ function mergeClaimLedgers(base, incoming) {
1461
1506
  const rounds = Math.max(base.rounds, incoming.rounds);
1462
1507
  const preparedRounds = Math.max(base.preparedRounds ?? base.rounds, incoming.preparedRounds ?? incoming.rounds);
1463
1508
  return materializeRegisteredClaimEvidence({
1509
+ schemaVersion: 2,
1464
1510
  id: base.id,
1465
1511
  ...goal === void 0 ? {} : { goal },
1466
1512
  updatedAt: incoming.updatedAt.localeCompare(base.updatedAt) > 0 ? incoming.updatedAt : base.updatedAt,
1467
1513
  rounds,
1468
1514
  ...preparedRounds > rounds ? { preparedRounds } : {},
1469
1515
  claimEvidence: [...claimEvidence.values()].sort((left, right) => left.id.localeCompare(right.id)),
1470
- registeredSourceUris: union(base.registeredSourceUris, incoming.registeredSourceUris),
1516
+ registeredSources: mergeSourceVersions(base.registeredSources, incoming.registeredSources),
1471
1517
  claims: [...claims.values()].sort((a, b) => a.id.localeCompare(b.id)),
1472
1518
  questions: [...questions.values()].sort((a, b) => a.id.localeCompare(b.id))
1473
1519
  });
@@ -1475,7 +1521,7 @@ function mergeClaimLedgers(base, incoming) {
1475
1521
  function mergeClaimEvidence(base, incoming) {
1476
1522
  assertResearchClaimEvidenceIntegrity(base);
1477
1523
  assertResearchClaimEvidenceIntegrity(incoming);
1478
- if (base.id !== incoming.id || base.claimId !== incoming.claimId || base.sourceUri !== incoming.sourceUri || base.contradictsClaimId !== incoming.contradictsClaimId) throw new Error(`claim evidence '${base.id}' has conflicting immutable content`);
1524
+ if (base.id !== incoming.id || base.claimId !== incoming.claimId || base.sourceId !== incoming.sourceId || base.sourceUri !== incoming.sourceUri || base.sourceContentHash !== incoming.sourceContentHash || base.contradictsClaimId !== incoming.contradictsClaimId) throw new Error(`claim evidence '${base.id}' has conflicting immutable content`);
1479
1525
  const text = incoming.firstSeenRound < base.firstSeenRound ? incoming.text : incoming.firstSeenRound > base.firstSeenRound ? base.text : incoming.text < base.text ? incoming.text : base.text;
1480
1526
  return {
1481
1527
  ...base,
@@ -1483,6 +1529,15 @@ function mergeClaimEvidence(base, incoming) {
1483
1529
  firstSeenRound: Math.min(base.firstSeenRound, incoming.firstSeenRound)
1484
1530
  };
1485
1531
  }
1532
+ function mergeSourceVersions(base, incoming) {
1533
+ const versions = /* @__PURE__ */ new Map();
1534
+ for (const source of [...base, ...incoming]) {
1535
+ const existing = versions.get(source.sourceId);
1536
+ if (existing && researchSourceVersionKey(existing) !== researchSourceVersionKey(source)) throw new Error(`registered source '${source.sourceId}' has conflicting immutable content`);
1537
+ versions.set(source.sourceId, source);
1538
+ }
1539
+ return [...versions.values()].sort((left, right) => left.sourceId.localeCompare(right.sourceId));
1540
+ }
1486
1541
  /**
1487
1542
  * Make every contradiction edge symmetric and mark both ends contested.
1488
1543
  *
@@ -1494,16 +1549,16 @@ function mergeClaimEvidence(base, incoming) {
1494
1549
  * the same rule stated over a whole ledger, for writers that assemble one from
1495
1550
  * events rather than from a live loop.
1496
1551
  *
1497
- * Idempotent and monotone like every other rule here: edges only appear and
1498
- * `contested` only latches on, so applying it twice changes nothing. An edge
1499
- * pointing at a claim this ledger does not hold is KEPT — the other side may
1500
- * arrive from another writer later, and discarding evidence of disagreement
1501
- * because the counterpart has not shown up yet is the failure this prevents.
1552
+ * One-sided observations stay in `claimEvidence` until both claims are backed
1553
+ * by registered source versions. The materialized claim projection contains
1554
+ * only closed pairs, so it cannot report a lone weak claim as settled merely
1555
+ * because its evidence named a claim that never arrived.
1502
1556
  */
1503
1557
  function linkClaimContradictions(ledger) {
1558
+ const claimIds = new Set(ledger.claims.map((claim) => claim.id));
1504
1559
  const inbound = /* @__PURE__ */ new Map();
1505
1560
  for (const claim of ledger.claims) for (const other of claim.contradicts) {
1506
- if (other === claim.id) continue;
1561
+ if (other === claim.id || !claimIds.has(other)) continue;
1507
1562
  const edges = inbound.get(other);
1508
1563
  if (edges) edges.push(claim.id);
1509
1564
  else inbound.set(other, [claim.id]);
@@ -1511,11 +1566,11 @@ function linkClaimContradictions(ledger) {
1511
1566
  return {
1512
1567
  ...ledger,
1513
1568
  claims: ledger.claims.map((claim) => {
1514
- const contradicts = union(claim.contradicts.filter((other) => other !== claim.id), inbound.get(claim.id) ?? []);
1569
+ const contradicts = union(claim.contradicts.filter((other) => other !== claim.id && claimIds.has(other)), inbound.get(claim.id) ?? []);
1515
1570
  return {
1516
1571
  ...claim,
1517
1572
  contradicts,
1518
- contested: claim.contested || contradicts.length > 0
1573
+ contested: contradicts.length > 0
1519
1574
  };
1520
1575
  }).sort((a, b) => a.id.localeCompare(b.id))
1521
1576
  };
@@ -1655,24 +1710,32 @@ const ResearchClaimRecordSchema = z.object({
1655
1710
  }).strict().superRefine((claim, context) => {
1656
1711
  reportIntegrityError(context, () => assertTrackedClaimIntegrity(claim));
1657
1712
  });
1713
+ const ResearchSourceVersionSchema = z.object({
1714
+ sourceId: z.string().min(1),
1715
+ uri: z.string().min(1),
1716
+ contentHash: z.string().regex(/^[a-f0-9]{64}$/)
1717
+ }).strict();
1658
1718
  const ResearchClaimEvidenceSchema = z.object({
1659
1719
  id: z.string().min(1),
1660
1720
  claimId: z.string().min(1),
1661
1721
  text: z.string().min(1),
1722
+ sourceId: z.string().min(1),
1662
1723
  sourceUri: z.string().min(1),
1724
+ sourceContentHash: z.string().regex(/^[a-f0-9]{64}$/),
1663
1725
  contradictsClaimId: z.string().min(1).optional(),
1664
1726
  firstSeenRound: z.number().int().nonnegative()
1665
1727
  }).strict().superRefine((evidence, context) => {
1666
1728
  reportIntegrityError(context, () => assertResearchClaimEvidenceIntegrity(evidence));
1667
1729
  });
1668
1730
  const ResearchClaimLedgerSchema = z.object({
1731
+ schemaVersion: z.literal(2),
1669
1732
  id: z.string().min(1),
1670
1733
  goal: z.string().trim().min(1).optional(),
1671
1734
  updatedAt: z.iso.datetime(),
1672
1735
  rounds: z.number().int().nonnegative(),
1673
1736
  preparedRounds: z.number().int().nonnegative().optional(),
1674
1737
  claimEvidence: z.array(ResearchClaimEvidenceSchema),
1675
- registeredSourceUris: z.array(z.string().min(1)),
1738
+ registeredSources: z.array(ResearchSourceVersionSchema),
1676
1739
  claims: z.array(ResearchClaimRecordSchema),
1677
1740
  questions: z.array(DeepQuestionSchema)
1678
1741
  }).strict().superRefine((ledger, context) => {
@@ -1756,6 +1819,17 @@ function assertClaimLedgerId(id) {
1756
1819
  if (!LEDGER_ID_PATTERN.test(id) || id === "." || id === "..") throw new Error(`claim ledger id must match ${LEDGER_ID_PATTERN} and cannot be a path segment: ${id}`);
1757
1820
  return id;
1758
1821
  }
1822
+ /** A URI-only ledger cannot be upgraded without re-verifying its source bytes. */
1823
+ var ClaimLedgerMigrationRequiredError = class extends Error {
1824
+ ledgerId;
1825
+ foundSchemaVersion;
1826
+ constructor(ledgerId, foundSchemaVersion) {
1827
+ super(`claim ledger '${ledgerId}' uses source identity schema '${foundSchemaVersion}'; archive it and re-verify its sources into a new ledger before use`);
1828
+ this.ledgerId = ledgerId;
1829
+ this.foundSchemaVersion = foundSchemaVersion;
1830
+ this.name = "ClaimLedgerMigrationRequiredError";
1831
+ }
1832
+ };
1759
1833
  var MemoryKbStore = class {
1760
1834
  sources = /* @__PURE__ */ new Map();
1761
1835
  pages = /* @__PURE__ */ new Map();
@@ -1806,7 +1880,7 @@ var MemoryKbStore = class {
1806
1880
  return out.slice(-(query.limit ?? out.length)).map(clone);
1807
1881
  }
1808
1882
  async putClaimLedger(ledger) {
1809
- const parsed = ResearchClaimLedgerSchema.parse(ledger);
1883
+ const parsed = parseResearchClaimLedger(ledger);
1810
1884
  this.claimLedgers.set(assertClaimLedgerId(parsed.id), clone(parsed));
1811
1885
  }
1812
1886
  async getClaimLedger(id) {
@@ -1902,13 +1976,16 @@ var FileSystemKbStore = class {
1902
1976
  });
1903
1977
  }
1904
1978
  async putClaimLedger(ledger) {
1905
- const parsed = ResearchClaimLedgerSchema.parse(ledger);
1979
+ const parsed = parseResearchClaimLedger(ledger);
1906
1980
  const path = this.claimLedgerPath(parsed.id);
1907
- await withKnowledgeMutation(this.root, () => writeJsonDurableWithinRoot(this.root, path, parsed));
1981
+ await withKnowledgeMutation(this.root, async () => {
1982
+ await readJsonFile(this.root, path, researchClaimLedgerParser);
1983
+ await writeJsonDurableWithinRoot(this.root, path, parsed);
1984
+ });
1908
1985
  }
1909
1986
  async getClaimLedger(id) {
1910
1987
  const path = this.claimLedgerPath(id);
1911
- return withKnowledgeRead(this.root, () => readJsonFile(this.root, path, ResearchClaimLedgerSchema));
1988
+ return withKnowledgeRead(this.root, () => readJsonFile(this.root, path, researchClaimLedgerParser));
1912
1989
  }
1913
1990
  async listClaimLedgers() {
1914
1991
  return withKnowledgeRead(this.root, async () => {
@@ -1922,7 +1999,7 @@ var FileSystemKbStore = class {
1922
1999
  const ledgers = [];
1923
2000
  for (const file of files) {
1924
2001
  if (!file.path.endsWith(".json")) continue;
1925
- ledgers.push(ResearchClaimLedgerSchema.parse(JSON.parse(file.bytes.toString("utf8"))));
2002
+ ledgers.push(parseResearchClaimLedger(JSON.parse(file.bytes.toString("utf8"))));
1926
2003
  }
1927
2004
  return ledgers.sort((a, b) => a.id.localeCompare(b.id));
1928
2005
  });
@@ -1930,7 +2007,7 @@ var FileSystemKbStore = class {
1930
2007
  async mergeClaimLedger(id, merge) {
1931
2008
  const key = assertClaimLedgerId(id);
1932
2009
  return withKnowledgeMutation(this.root, async () => {
1933
- const current = await readJsonFile(this.root, this.claimLedgerPath(key), ResearchClaimLedgerSchema);
2010
+ const current = await readJsonFile(this.root, this.claimLedgerPath(key), researchClaimLedgerParser);
1934
2011
  const next = assertMergedLedgerId(key, merge(current));
1935
2012
  await this.putClaimLedger(next);
1936
2013
  return next;
@@ -1983,6 +2060,42 @@ async function readJsonFile(root, relativePath, schema) {
1983
2060
  throw error;
1984
2061
  }
1985
2062
  }
2063
+ const researchClaimLedgerParser = { parse: parseResearchClaimLedger };
2064
+ const legacyResearchClaimLedgerSchema = z.object({
2065
+ id: z.string().min(1),
2066
+ goal: z.string().trim().min(1).optional(),
2067
+ updatedAt: z.iso.datetime(),
2068
+ rounds: z.number().int().nonnegative(),
2069
+ preparedRounds: z.number().int().nonnegative().optional(),
2070
+ claimEvidence: z.array(z.object({
2071
+ id: z.string().min(1),
2072
+ claimId: z.string().min(1),
2073
+ text: z.string().min(1),
2074
+ sourceUri: z.string().min(1),
2075
+ contradictsClaimId: z.string().min(1).optional(),
2076
+ firstSeenRound: z.number().int().nonnegative()
2077
+ }).strict()),
2078
+ registeredSourceUris: z.array(z.string().min(1)),
2079
+ claims: z.array(z.object({
2080
+ id: z.string().min(1),
2081
+ text: z.string().min(1),
2082
+ supportingHosts: z.array(z.string().min(1)),
2083
+ supportingUris: z.array(z.string().min(1)),
2084
+ contradicts: z.array(z.string().min(1)),
2085
+ contested: z.boolean(),
2086
+ firstSeenRound: z.number().int().nonnegative()
2087
+ }).strict()),
2088
+ questions: z.array(DeepQuestionSchema)
2089
+ }).strict();
2090
+ function parseResearchClaimLedger(value) {
2091
+ const legacy = legacyResearchClaimLedgerSchema.safeParse(value);
2092
+ if (legacy.success) throw new ClaimLedgerMigrationRequiredError(legacy.data.id, "unversioned");
2093
+ if (isRecord(value) && value.schemaVersion !== 2 && "registeredSourceUris" in value) throw new ClaimLedgerMigrationRequiredError(typeof value.id === "string" ? value.id : "unknown", typeof value.schemaVersion === "number" ? value.schemaVersion : "unversioned");
2094
+ return ResearchClaimLedgerSchema.parse(value);
2095
+ }
2096
+ function isRecord(value) {
2097
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2098
+ }
1986
2099
  function clone(value) {
1987
2100
  return value == null ? value : JSON.parse(JSON.stringify(value));
1988
2101
  }
@@ -1992,6 +2105,10 @@ const sourceRegistrySchema = z.object({
1992
2105
  generatedAt: z.string().min(1),
1993
2106
  sources: z.array(SourceRecordSchema.passthrough())
1994
2107
  }).strict();
2108
+ /** Copy and freeze an untrusted source proposal before any asynchronous work. */
2109
+ function snapshotSourceTextInput(input) {
2110
+ return deepFreeze(structuredClone(input));
2111
+ }
1995
2112
  async function loadSourceRegistry(root) {
1996
2113
  return withKnowledgeRead(root, () => loadSourceRegistryUnlocked(root));
1997
2114
  }
@@ -2070,7 +2187,7 @@ async function preparePathSource(root, sourcePath, mode, options) {
2070
2187
  };
2071
2188
  }
2072
2189
  async function addSourceText(root, input, options = {}) {
2073
- const [record] = await commitSourceBatch(root, [await prepareTextSource(input, options)], options);
2190
+ const [record] = await commitSourceBatch(root, [await prepareTextSource(snapshotSourceTextInput(input), options)], options);
2074
2191
  return record;
2075
2192
  }
2076
2193
  async function prepareTextSource(input, options) {
@@ -2084,9 +2201,9 @@ async function prepareTextSource(input, options) {
2084
2201
  };
2085
2202
  const adapter = (options.adapters ?? [textSourceAdapter]).find((candidate) => candidate.canLoad(adapterInput));
2086
2203
  const loaded = adapter ? await adapter.load(adapterInput) : {};
2087
- const id = stableId("src", `${contentHash}:${input.uri}`);
2204
+ const id = textSourceId(input.uri, contentHash);
2088
2205
  const targetRel = rawSourcePath(fileName, contentHash, ".txt");
2089
- const rawContent = text.endsWith("\n") ? text : `${text}\n`;
2206
+ const rawContent = text;
2090
2207
  return {
2091
2208
  record: {
2092
2209
  id,
@@ -2161,6 +2278,12 @@ function sameMutation(left, right) {
2161
2278
  if (Buffer.isBuffer(left.content) && Buffer.isBuffer(right.content)) return left.content.equals(right.content);
2162
2279
  return left.content === right.content;
2163
2280
  }
2281
+ function deepFreeze(value, seen = /* @__PURE__ */ new WeakSet()) {
2282
+ if (typeof value !== "object" || value === null || seen.has(value)) return value;
2283
+ seen.add(value);
2284
+ for (const nested of Object.values(value)) deepFreeze(nested, seen);
2285
+ return Object.freeze(value);
2286
+ }
2164
2287
  async function listSourceFiles(root) {
2165
2288
  const entries = await readdir(root, { withFileTypes: true });
2166
2289
  const out = [];
@@ -2177,7 +2300,7 @@ async function listSourceFiles(root) {
2177
2300
  return out;
2178
2301
  }
2179
2302
  function rawSourcePath(fileName, contentHash, extension) {
2180
- return join("raw", "sources", `${slugify(fileName.replace(/\.[^.]+$/, ""))}-${contentHash.slice(0, 8)}${extension}`).replace(/\\/g, "/");
2303
+ return join("raw", "sources", `${slugify(fileName.replace(/\.[^.]+$/, ""))}-${contentHash}${extension}`).replace(/\\/g, "/");
2181
2304
  }
2182
2305
  function ext(fileName) {
2183
2306
  const idx = fileName.lastIndexOf(".");
@@ -2534,6 +2657,6 @@ function explainKnowledgeTarget(index, target) {
2534
2657
  };
2535
2658
  }
2536
2659
  //#endregion
2537
- export { SCAFFOLD_PAGE_BASENAMES as $, KnowledgePageSchema as A, withSafeDirectory as At, assertResearchClaimLedgerIntegrity as B, assertClaimLedgerId as C, listRegularFilesWithinRoot as Ct, KnowledgeGraphEdgeSchema as D, renameDurable as Dt, KnowledgeEventSchema as E, removeDurable as Et, SourceRecordSchema as F, mediaTypeFor$1 as Ft, deepQuestionId as G, claimEvidenceId as H, KNOWLEDGE_EVENT_TYPES as I, textSourceAdapter as It, materializeRegisteredClaimEvidence as J, emptyClaimLedger as K, ClaimLedgerGoalConflictError as L, ResearchClaimLedgerSchema as M, writeFileDurableWithinRoot as Mt, ResearchClaimRecordSchema as N, writeJsonDurable as Nt, KnowledgeGraphNodeSchema as O, syncDirectory as Ot, SourceAnchorSchema as P, writeJsonDurableWithinRoot as Pt, buildKnowledgeGraph as Q, assertDeepQuestionIntegrity as R, MemoryKbStore as S, isMissingFile as St, KnowledgeBaseCandidateSchema as T, readRegularFileWithinRoot as Tt, claimId as U, assertTrackedClaimIntegrity as V, claimSourceHost as W, mergeTrackedClaims as X, mergeClaimLedgers as Y, normalizeClaimText as Z, FileSystemKbStore as _, finishKnowledgeFileTransaction as _t, applyKnowledgeWriteBlocksFile as a, WIKILINK_REGEX as at, KB_INDEX_PATH as b, rollbackKnowledgeFileTransaction as bt, validateKnowledgeIndex as c, formatFrontmatter as ct, writeKnowledgeIndex as d, inspectPendingKnowledgeMutation as dt, initKnowledgeBase as et, addSourcePath as f, recoverPendingKnowledgeMutation as ft, writeSourceRegistry as g, assertKnowledgeMutationPath as gt, sourceRegistryPath as h, applyKnowledgeFileTransaction as ht, applyKnowledgeWriteBlocks as i, writeJson as it, ResearchClaimEvidenceSchema as j, writeFileDurable as jt, KnowledgeIndexSchema as k, withSafeDescendant as kt, lintKnowledgeIndex as l, parseFrontmatter as lt, loadSourceRegistry as m, withKnowledgeRead as mt, inspectKnowledgeIndex as n, layoutFor as nt, isSafeKnowledgePath as o, extractWikilinks as ot, addSourceText as p, withKnowledgeMutation as pt, linkClaimContradictions as q, stringMetadata as r, loadKnowledgePages as rt, parseKnowledgeWriteBlocks as s, normalizeLinkTarget as st, explainKnowledgeTarget as t, isScaffoldPath as tt, buildKnowledgeIndex as u, acquireDurableFileLock as ut, KB_CLAIM_LEDGER_DIR as v, knowledgeFileTransactionPlanHash as vt, DeepQuestionSchema as w, readRegularFileNoFollow as wt, KB_STORE_DIR as x, isKernelAnchoredPath as xt, KB_EVENTS_PATH as y, prepareKnowledgeFileTransaction as yt, assertResearchClaimEvidenceIntegrity as z };
2660
+ export { mergeTrackedClaims as $, KnowledgeGraphNodeSchema as A, removeDurable as At, ClaimLedgerGoalConflictError as B, textSourceAdapter as Bt, KB_STORE_DIR as C, prepareKnowledgeFileTransaction as Ct, KnowledgeBaseCandidateSchema as D, listRegularFilesWithinRoot as Dt, DeepQuestionSchema as E, isMissingFile as Et, ResearchClaimRecordSchema as F, writeFileDurable as Ft, claimEvidenceId as G, assertResearchClaimEvidenceIntegrity as H, ResearchSourceVersionSchema as I, writeFileDurableWithinRoot as It, deepQuestionId as J, claimId as K, SourceAnchorSchema as L, writeJsonDurable as Lt, KnowledgePageSchema as M, syncDirectory as Mt, ResearchClaimEvidenceSchema as N, withSafeDescendant as Nt, KnowledgeEventSchema as O, readRegularFileNoFollow as Ot, ResearchClaimLedgerSchema as P, withSafeDirectory as Pt, mergeClaimLedgers as Q, SourceRecordSchema as R, writeJsonDurableWithinRoot as Rt, KB_INDEX_PATH as S, knowledgeFileTransactionPlanHash as St, assertClaimLedgerId as T, isKernelAnchoredPath as Tt, assertResearchClaimLedgerIntegrity as U, assertDeepQuestionIntegrity as V, assertTrackedClaimIntegrity as W, linkClaimContradictions as X, emptyClaimLedger as Y, materializeRegisteredClaimEvidence as Z, writeSourceRegistry as _, withKnowledgeMutation as _t, applyKnowledgeWriteBlocksFile as a, isScaffoldPath as at, KB_CLAIM_LEDGER_DIR as b, assertKnowledgeMutationPath as bt, validateKnowledgeIndex as c, writeJson as ct, writeKnowledgeIndex as d, normalizeLinkTarget as dt, normalizeClaimText as et, addSourcePath as f, formatFrontmatter as ft, sourceRegistryPath as g, recoverPendingKnowledgeMutation as gt, snapshotSourceTextInput as h, inspectPendingKnowledgeMutation as ht, applyKnowledgeWriteBlocks as i, initKnowledgeBase as it, KnowledgeIndexSchema as j, renameDurable as jt, KnowledgeGraphEdgeSchema as k, readRegularFileWithinRoot as kt, lintKnowledgeIndex as l, WIKILINK_REGEX as lt, loadSourceRegistry as m, acquireDurableFileLock as mt, inspectKnowledgeIndex as n, buildKnowledgeGraph as nt, isSafeKnowledgePath as o, layoutFor as ot, addSourceText as p, parseFrontmatter as pt, claimSourceHost as q, stringMetadata as r, SCAFFOLD_PAGE_BASENAMES as rt, parseKnowledgeWriteBlocks as s, loadKnowledgePages as st, explainKnowledgeTarget as t, researchSourceVersionKey as tt, buildKnowledgeIndex as u, extractWikilinks as ut, ClaimLedgerMigrationRequiredError as v, withKnowledgeRead as vt, MemoryKbStore as w, rollbackKnowledgeFileTransaction as wt, KB_EVENTS_PATH as x, finishKnowledgeFileTransaction as xt, FileSystemKbStore as y, applyKnowledgeFileTransaction as yt, KNOWLEDGE_EVENT_TYPES as z, mediaTypeFor$1 as zt };
2538
2661
 
2539
- //# sourceMappingURL=inspect-LUMkas6A.js.map
2662
+ //# sourceMappingURL=inspect-CJQGYuKa.js.map