@equationalapplications/core-llm-wiki 7.2.1 → 7.4.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.
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { __privateAdd, MetadataRepository, EmbeddingService, SearchService, JobManager, PromptService, IngestionService, MaintenanceService, ImportExportService, RetrievalService, WriteService, __privateGet, __privateSet, normalizeSourceRef, normalizeSourceHash, entitySummaryMetaKey, WikiSourceRefHashCollision, validateManifest, generateId, BaseRepository, WikiGraphNodeOwnershipConflict, emptyManifest, resolveNodeType, WikiStrictOntologyViolation, validateInlineEdges, resolveEdgeDefinitions, typeSatisfies, normalizeTitleKey, WikiTransactionError, extractSqliteCode } from './chunk-TTECJ3S3.mjs';
2
- export { DEFAULT_CHUNK_OVERLAP, DEFAULT_MAX_CHUNK_LENGTH, DEFAULT_MAX_EMBED_CHARS, EMBED_CHARS_CEILING, HEAL_ANCHORS_PER_CANDIDATE, HEAL_BATCH_SIZE, HEAL_MAX_FACT_BODY_CHARS_L3, HEAL_MAX_TASKS, HEAL_RECHECK_MS, HOOK_TIMEOUT_MARKER, ONTOLOGY_BACKFILL_BATCH_SIZE, ONTOLOGY_BACKFILL_MAX_PROMPT_CHARS, ONTOLOGY_BACKFILL_RECHECK_MS, ONTOLOGY_BACKFILL_SYSTEM_PROMPT, PromptService, PrunePartialFailureError, WikiBusyError, WikiDuplicateHashError, WikiGraphNodeOwnershipConflict, WikiIngestEmptyError, WikiInvalidReadOptions, WikiParseError, WikiSourceRefHashCollision, WikiStrictOntologyViolation, WikiTransactionError, chunkText, configureRandomSource, parseEmbedding, safeSlice, validateManifest } from './chunk-TTECJ3S3.mjs';
1
+ import { __privateAdd, MetadataRepository, EmbeddingService, SearchService, JobManager, PromptService, IngestionService, MaintenanceService, ImportExportService, RetrievalService, WriteService, __privateGet, __privateSet, normalizeSourceRef, normalizeSourceHash, entitySummaryMetaKey, WikiSourceRefHashCollision, DiagnosticBuffer, validateManifest, WikiDraftNotFound, generateId, BaseRepository, WikiGraphNodeOwnershipConflict, emptyManifest, resolveNodeType, WikiStrictOntologyViolation, validateInlineEdges, resolveEdgeDefinitions, typeSatisfies, normalizeTitleKey, WikiTransactionError, extractSqliteCode } from './chunk-G5OR2VWD.mjs';
2
+ export { DEFAULT_CHUNK_OVERLAP, DEFAULT_MAX_CHUNK_LENGTH, DEFAULT_MAX_EMBED_CHARS, EMBED_CHARS_CEILING, HEAL_ANCHORS_PER_CANDIDATE, HEAL_BATCH_SIZE, HEAL_MAX_FACT_BODY_CHARS_L3, HEAL_MAX_TASKS, HEAL_RECHECK_MS, HOOK_TIMEOUT_MARKER, ONTOLOGY_BACKFILL_BATCH_SIZE, ONTOLOGY_BACKFILL_MAX_PROMPT_CHARS, ONTOLOGY_BACKFILL_RECHECK_MS, ONTOLOGY_BACKFILL_SYSTEM_PROMPT, PromptService, PrunePartialFailureError, WikiBusyError, WikiDraftNotFound, WikiDuplicateHashError, WikiGraphNodeOwnershipConflict, WikiIngestEmptyError, WikiInvalidReadOptions, WikiParseError, WikiSourceRefHashCollision, WikiStrictOntologyViolation, WikiTransactionError, chunkText, configureRandomSource, parseEmbedding, safeSlice, validateManifest } from './chunk-G5OR2VWD.mjs';
3
3
  import { appendRelatedSection, buildConceptDocument, buildLogMd, buildEntityIndexMd, buildRootIndexMd, isAllowedOkfPath, parseRootIndexMd, parseEntityIndexMd, parseConcept, splitRelatedSection, extractMarkdownLinks, parseLogMd, parseEventIdComment, latestVerified, formatSourcesJson, appendEventIdComment, parseVerifiedFlexible, parseCitationsList, deriveTrustTier, isStaleAfter } from '@equationalapplications/core-okf';
4
4
 
5
5
  // src/db/schema.ts
@@ -1569,16 +1569,52 @@ var EntryRepository = class extends BaseRepository {
1569
1569
  `UPDATE ${this.prefix}entries SET source_type = 'librarian_inferred' WHERE source_type = 'agent_inferred';`
1570
1570
  ].join("\n");
1571
1571
  }
1572
- async findRecentByEntityIds(entityIds, limit, tx) {
1572
+ async findRecentByEntityIds(entityIds, limit, tx, opts) {
1573
1573
  if (entityIds.length === 0) return [];
1574
1574
  const executor = this.getExecutor(tx);
1575
1575
  const placeholders = entityIds.map(() => "?").join(",");
1576
+ const draftClause = opts?.excludeDrafts === true ? ` AND lifecycle_status != 'draft'` : "";
1576
1577
  const rows = await executor.getAllAsync(
1577
- `SELECT * FROM ${this.prefix}entries WHERE entity_id IN (${placeholders}) AND deleted_at IS NULL ORDER BY updated_at DESC LIMIT ?`,
1578
+ `SELECT * FROM ${this.prefix}entries WHERE entity_id IN (${placeholders}) AND deleted_at IS NULL${draftClause} ORDER BY updated_at DESC LIMIT ?`,
1578
1579
  [...entityIds, limit]
1579
1580
  );
1580
1581
  return rows.map(mapRowToFact);
1581
1582
  }
1583
+ /** IDs of live draft facts for the given entities (spec §5.1). Callers pass ≤ 100 entity ids. */
1584
+ async findDraftIdsByEntityIds(entityIds, tx) {
1585
+ if (entityIds.length === 0) return /* @__PURE__ */ new Set();
1586
+ const executor = this.getExecutor(tx);
1587
+ const placeholders = entityIds.map(() => "?").join(",");
1588
+ const rows = await executor.getAllAsync(
1589
+ `SELECT id FROM ${this.prefix}entries
1590
+ WHERE entity_id IN (${placeholders}) AND deleted_at IS NULL AND lifecycle_status = 'draft'`,
1591
+ [...entityIds]
1592
+ );
1593
+ return new Set(rows.map((r) => r.id));
1594
+ }
1595
+ /** Live drafts for one entity, newest first, keyset-paged by (created_at, id). */
1596
+ async listDraftsByEntityId(entityId, limit, after, tx) {
1597
+ const executor = this.getExecutor(tx);
1598
+ const afterClause = after ? ` AND (created_at < ? OR (created_at = ? AND id < ?))` : "";
1599
+ const args = after ? [entityId, after.createdAt, after.createdAt, after.id, limit] : [entityId, limit];
1600
+ const rows = await executor.getAllAsync(
1601
+ `SELECT * FROM ${this.prefix}entries
1602
+ WHERE entity_id = ? AND deleted_at IS NULL AND lifecycle_status = 'draft'${afterClause}
1603
+ ORDER BY created_at DESC, id DESC LIMIT ?`,
1604
+ args
1605
+ );
1606
+ return rows.map(mapRowToFact);
1607
+ }
1608
+ /** True iff `entryId` is a live draft owned by `entityId`. */
1609
+ async isLiveDraft(entryId, entityId, tx) {
1610
+ const executor = this.getExecutor(tx);
1611
+ const row = await executor.getFirstAsync(
1612
+ `SELECT id FROM ${this.prefix}entries
1613
+ WHERE id = ? AND entity_id = ? AND deleted_at IS NULL AND lifecycle_status = 'draft'`,
1614
+ [entryId, entityId]
1615
+ );
1616
+ return row != null;
1617
+ }
1582
1618
  /**
1583
1619
  * Live untyped facts eligible for ontology backfill, oldest first.
1584
1620
  * Skips facts checked within the recheck cooldown (ontology_checked_at > recheckCutoff).
@@ -2365,6 +2401,7 @@ var EdgeRepository = class extends BaseRepository {
2365
2401
  END
2366
2402
  ) >= ?
2367
2403
  AND n.source_type NOT IN (${excludeSourceTypesPlaceholders})
2404
+ AND (? = 0 OR n.lifecycle_status != 'draft')
2368
2405
  WHERE w.depth < ?
2369
2406
  AND instr(w.visited, ',' || (CASE WHEN e.source_id = w.node_id THEN e.target_id ELSE e.source_id END) || ',') = 0
2370
2407
  )
@@ -2384,6 +2421,7 @@ var EdgeRepository = class extends BaseRepository {
2384
2421
  entityId,
2385
2422
  minConfidenceRank,
2386
2423
  ...opts.excludeSourceTypes,
2424
+ opts.excludeDrafts === true ? 1 : 0,
2387
2425
  opts.maxDepth,
2388
2426
  opts.maxNodes
2389
2427
  ];
@@ -2499,6 +2537,17 @@ var OntologyService = class {
2499
2537
  const canonical = resolveNodeType(rawType, manifest);
2500
2538
  if (!canonical) {
2501
2539
  if (strict) throw new WikiStrictOntologyViolation(opts?.entityId ?? "", "node", rawType);
2540
+ if (opts?.drops && Array.isArray(fact.edges)) {
2541
+ for (const edge of fact.edges) {
2542
+ opts.drops.push({
2543
+ reason: "no_source_type",
2544
+ sourceId: null,
2545
+ edgeType: typeof edge?.edge_type === "string" ? edge.edge_type : null,
2546
+ sourceNodeType: null,
2547
+ targetNodeType: null
2548
+ });
2549
+ }
2550
+ }
2502
2551
  return { okf_type: null, edges: [] };
2503
2552
  }
2504
2553
  const edges = validateInlineEdges(canonical, null, fact.edges ?? [], manifest, opts);
@@ -2511,18 +2560,39 @@ var OntologyService = class {
2511
2560
  * writes inside their own transaction (e.g. `IngestionService.ingestDocument`
2512
2561
  * when delegating to `upsertGraphCore`).
2513
2562
  */
2514
- resolveEdges(entityId, sourceId, sourceType, edges, manifest, titleIndex, now) {
2515
- if (!sourceType || edges.length === 0) return [];
2563
+ resolveEdges(entityId, sourceId, sourceType, edges, manifest, titleIndex, now, drops) {
2564
+ if (edges.length === 0) return [];
2565
+ if (!sourceType) {
2566
+ for (const edge of edges) {
2567
+ drops?.push({ reason: "no_source_type", sourceId, edgeType: edge.edge_type, sourceNodeType: null, targetNodeType: null });
2568
+ }
2569
+ return [];
2570
+ }
2516
2571
  const out = [];
2517
2572
  for (const edge of edges) {
2518
2573
  const candidates = resolveEdgeDefinitions(edge.edge_type, manifest).filter((d) => typeSatisfies(d.source_type, sourceType, manifest));
2519
- if (candidates.length === 0) continue;
2574
+ if (candidates.length === 0) {
2575
+ drops?.push({ reason: "type_not_in_manifest", sourceId, edgeType: edge.edge_type, sourceNodeType: sourceType, targetNodeType: null });
2576
+ continue;
2577
+ }
2520
2578
  const targetKey = normalizeTitleKey(edge.target_title);
2521
2579
  const target = titleIndex.get(targetKey);
2522
- if (!target) continue;
2580
+ if (!target) {
2581
+ drops?.push({ reason: "target_not_found", sourceId, edgeType: edge.edge_type, sourceNodeType: sourceType, targetNodeType: null });
2582
+ continue;
2583
+ }
2523
2584
  const targetType = (target.okf_type ?? "").trim().toLowerCase();
2524
2585
  const def = candidates.find((d) => d.target_type.trim().toLowerCase() === targetType) ?? candidates.find((d) => typeSatisfies(d.target_type, targetType, manifest));
2525
- if (!def) continue;
2586
+ if (!def) {
2587
+ drops?.push({
2588
+ reason: "target_type_mismatch",
2589
+ sourceId,
2590
+ edgeType: edge.edge_type,
2591
+ sourceNodeType: sourceType,
2592
+ targetNodeType: target.okf_type ?? null
2593
+ });
2594
+ continue;
2595
+ }
2526
2596
  out.push({
2527
2597
  id: generateId(),
2528
2598
  entity_id: entityId,
@@ -2541,8 +2611,8 @@ var OntologyService = class {
2541
2611
  * want self-contained edge persistence (e.g. MaintenanceService heal /
2542
2612
  * backfill callers that do not have a separate edge-write step).
2543
2613
  */
2544
- async resolveAndPersistEdges(entityId, sourceId, sourceType, edges, manifest, titleIndex, tx, now) {
2545
- const resolved = this.resolveEdges(entityId, sourceId, sourceType, edges, manifest, titleIndex, now);
2614
+ async resolveAndPersistEdges(entityId, sourceId, sourceType, edges, manifest, titleIndex, tx, now, drops) {
2615
+ const resolved = this.resolveEdges(entityId, sourceId, sourceType, edges, manifest, titleIndex, now, drops);
2546
2616
  let persisted = 0;
2547
2617
  for (const edge of resolved) {
2548
2618
  const inserted = await this.edgeRepo.addIgnoreDuplicate(edge, tx);
@@ -2571,6 +2641,7 @@ var GraphTraversalService = class {
2571
2641
  edgeTypes: options.edgeTypes,
2572
2642
  minConfidence: options.minTraversalConfidence ?? this.config.minTraversalConfidence ?? "tentative",
2573
2643
  excludeSourceTypes: options.excludeSourceTypes ?? this.config.excludeSourceTypes ?? [],
2644
+ excludeDrafts: options.excludeDrafts ?? this.config.excludeDrafts ?? false,
2574
2645
  maxNodes
2575
2646
  };
2576
2647
  const { nodeIds, edges } = await this.edgeRepo.getNeighborhood(entityId, options.sourceId, opts);
@@ -2744,6 +2815,15 @@ var OkfTrustWritesRepository = class {
2744
2815
 
2745
2816
  // src/WikiMemory.ts
2746
2817
  var TABLE_PREFIX_PATTERN = /^[A-Za-z][A-Za-z0-9_]{0,30}_$/;
2818
+ function decodeDraftCursor(cursor) {
2819
+ const sep = typeof cursor === "string" ? cursor.indexOf(":") : -1;
2820
+ const createdAt = sep > 0 ? Number(cursor.slice(0, sep)) : Number.NaN;
2821
+ const id = sep > 0 ? cursor.slice(sep + 1) : "";
2822
+ if (!Number.isSafeInteger(createdAt) || id.length === 0) {
2823
+ throw new TypeError("Invalid listDrafts cursor.");
2824
+ }
2825
+ return { createdAt, id };
2826
+ }
2747
2827
  var _testAccessNonTestEnvWarned;
2748
2828
  var WikiMemory = class {
2749
2829
  constructor(db, options) {
@@ -3163,11 +3243,15 @@ var WikiMemory = class {
3163
3243
  attemptedSourceRef: sourceRef
3164
3244
  });
3165
3245
  }
3166
- return this.ingestionService.upsertGraphCore(
3246
+ const diagBuffer = new DiagnosticBuffer();
3247
+ const result = await this.ingestionService.upsertGraphCore(
3167
3248
  entityId,
3168
3249
  { sourceRef, sourceHash, nodes: params.nodes, edges: params.edges },
3169
- adapter
3250
+ adapter,
3251
+ { diag: { buffer: diagBuffer, operation: "upsertGraph" } }
3170
3252
  );
3253
+ diagBuffer.flush(this.options);
3254
+ return result;
3171
3255
  }
3172
3256
  /**
3173
3257
  * Returns up to `limit` unprocessed outbox events, oldest first.
@@ -3284,6 +3368,43 @@ var WikiMemory = class {
3284
3368
  async setLifecycleStatus(entryId, entityId, status) {
3285
3369
  return this.okfTrustWrites.setLifecycleStatus(entryId, entityId, status);
3286
3370
  }
3371
+ /**
3372
+ * Live draft facts for one entity, newest first (spec §5.2). Default page
3373
+ * size 50, clamped to [1, 500]. Pass `nextCursor` back unchanged for the next page.
3374
+ */
3375
+ async listDrafts(entityId, options) {
3376
+ const rawLimit = options?.limit;
3377
+ const limit = typeof rawLimit === "number" && Number.isFinite(rawLimit) ? Math.min(500, Math.max(1, Math.floor(rawLimit))) : 50;
3378
+ const after = options?.cursor === void 0 ? null : decodeDraftCursor(options.cursor);
3379
+ const rows = await this.entryRepo.listDraftsByEntityId(entityId, limit + 1, after);
3380
+ const facts = rows.slice(0, limit);
3381
+ const last = facts[facts.length - 1];
3382
+ return {
3383
+ facts,
3384
+ nextCursor: rows.length > limit && last ? `${last.created_at}:${last.id}` : null
3385
+ };
3386
+ }
3387
+ /**
3388
+ * Promote a draft to `stable` and record who reviewed it, atomically
3389
+ * (spec §5.2). Metadata writes only: `updated_at` is not bumped and no
3390
+ * outbox event is pushed. Pass `by: 'human:<id>'` so `trustTier` becomes
3391
+ * `'human-reviewed'`.
3392
+ *
3393
+ * @throws WikiDraftNotFound when no live draft with that id exists for the entity.
3394
+ */
3395
+ async promoteDraft(entryId, entityId, reviewer) {
3396
+ const by = reviewer?.by;
3397
+ if (typeof by !== "string" || by.trim().length === 0) {
3398
+ throw new TypeError("promoteDraft requires reviewer.by to be a non-empty string.");
3399
+ }
3400
+ await this.db.withTransactionAsync(async (tx) => {
3401
+ if (!await this.entryRepo.isLiveDraft(entryId, entityId, tx)) {
3402
+ throw new WikiDraftNotFound();
3403
+ }
3404
+ await this.okfTrustWrites.setLifecycleStatus(entryId, entityId, "stable", tx);
3405
+ await this.okfTrustWrites.writeOkfTrust(entryId, entityId, [{ by, at: (/* @__PURE__ */ new Date()).toISOString() }], tx);
3406
+ });
3407
+ }
3287
3408
  /** Set a fact's stale_after (epoch ms) or clear it. Does NOT touch `updated_at`. */
3288
3409
  async setStaleAfter(entryId, entityId, date) {
3289
3410
  return this.okfTrustWrites.setStaleAfter(entryId, entityId, date);