@equationalapplications/core-llm-wiki 7.6.0 → 7.7.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.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { M as MemoryBundle, F as FormatContextOptions, G as GraphNeighborhood, a as MemoryDump, b as FormattedMemoryDump, R as ReadOptions, S as SQLiteAdapter, W as WikiOptions, c as WikiMemory } from './testing-d1SrpDvg.mjs';
2
- export { C as ChunkFailure, d as ClassifierAnswer, e as ClassifierQuestion, f as ClassifyRequest, g as ClassifyResponse, D as DegradedRecord, h as DraftPage, E as EmbedFactResult, i as EmbedFailureKind, j as EmbeddingMarkerKind, k as EntityStatus, l as ExtractedFact, m as ExtractedFactEdge, n as ExtractedFactWithOntology, o as ExtractedTask, p as GraphTraversalOptions, q as GroundingConfig, H as HEAL_BATCH_SIZE, r as HEAL_RECHECK_MS, s as HOOK_TIMEOUT_MARKER, t as HealResult, I as IngestDocumentResult, L as LLMProvider, O as ONTOLOGY_BACKFILL_BATCH_SIZE, u as ONTOLOGY_BACKFILL_MAX_PROMPT_CHARS, v as ONTOLOGY_BACKFILL_RECHECK_MS, w as OntologyBackfillResult, x as OntologyConfig, y as OntologyEdgeType, z as OntologyManifest, A as OntologyMode, B as OntologyNodeType, J as OntologyPromptContext, K as OntologyUpdates, P as PromptOverrides, N as PromptService, Q as PrunePartialFailureError, T as ReembedResult, V as VectorRanker, U as VectorRankerFallback, X as VectorRankerRankArgs, Y as VectorRankerSemanticResult, Z as WikiBusyError, _ as WikiBusyOperation, $ as WikiCheckpoint, a0 as WikiConfig, a1 as WikiDiagnostic, a2 as WikiDiagnosticCode, a3 as WikiDiagnosticDetail, a4 as WikiDiagnosticOperation, a5 as WikiDiagnosticSeverity, a6 as WikiDiagnosticTrigger, a7 as WikiDraftNotFound, a8 as WikiDuplicateHashError, a9 as WikiEdge, aa as WikiEvent, ab as WikiFact, ac as WikiGraphNodeOwnershipConflict, ad as WikiIngestEmptyError, ae as WikiInvalidReadOptions, af as WikiMemoryTestAccess, ag as WikiOutboxEvent, ah as WikiParseError, ai as WikiSourceRefHashCollision, aj as WikiStrictOntologyViolation, ak as WikiTask, al as WikiTransactionError, am as validateManifest } from './testing-d1SrpDvg.mjs';
1
+ import { M as MemoryBundle, F as FormatContextOptions, G as GraphNeighborhood, a as MemoryDump, b as FormattedMemoryDump, R as ReadOptions, S as SQLiteAdapter, W as WikiOptions, c as WikiMemory } from './testing-A4Sofxyo.mjs';
2
+ export { C as ChunkFailure, d as ClassifierAnswer, e as ClassifierQuestion, f as ClassifyRequest, g as ClassifyResponse, D as DegradedRecord, h as DraftPage, E as EmbedFactResult, i as EmbedFailureKind, j as EmbeddingMarkerKind, k as EntityStatus, l as ExtractedFact, m as ExtractedFactEdge, n as ExtractedFactWithOntology, o as ExtractedTask, p as GraphTraversalOptions, q as GroundingConfig, H as HEAL_BATCH_SIZE, r as HEAL_RECHECK_MS, s as HOOK_TIMEOUT_MARKER, t as HealResult, I as IngestDocumentResult, L as LLMProvider, O as ONTOLOGY_BACKFILL_BATCH_SIZE, u as ONTOLOGY_BACKFILL_MAX_PROMPT_CHARS, v as ONTOLOGY_BACKFILL_RECHECK_MS, w as OntologyBackfillResult, x as OntologyConfig, y as OntologyEdgeType, z as OntologyManifest, A as OntologyMode, B as OntologyNodeType, J as OntologyPromptContext, K as OntologyUpdates, P as PendingSourceStatus, N as PromptOverrides, Q as PromptService, T as PrunePartialFailureError, U as ReembedResult, V as VectorRanker, X as VectorRankerFallback, Y as VectorRankerRankArgs, Z as VectorRankerSemanticResult, _ as WikiBusyError, $ as WikiBusyOperation, a0 as WikiCheckpoint, a1 as WikiConfig, a2 as WikiDiagnostic, a3 as WikiDiagnosticCode, a4 as WikiDiagnosticDetail, a5 as WikiDiagnosticOperation, a6 as WikiDiagnosticSeverity, a7 as WikiDiagnosticTrigger, a8 as WikiDraftNotFound, a9 as WikiDuplicateHashError, aa as WikiEdge, ab as WikiEvent, ac as WikiFact, ad as WikiGraphNodeOwnershipConflict, ae as WikiIngestEmptyError, af as WikiInstructions, ag as WikiInvalidReadOptions, ah as WikiLintReport, ai as WikiMemoryTestAccess, aj as WikiOutboxEvent, ak as WikiParseError, al as WikiSourceRefHashCollision, am as WikiStrictOntologyViolation, an as WikiTask, ao as WikiTransactionError, ap as validateManifest } from './testing-A4Sofxyo.mjs';
3
3
  import { OkfFile } from '@equationalapplications/core-okf';
4
4
  import 'minisearch';
5
5
 
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { M as MemoryBundle, F as FormatContextOptions, G as GraphNeighborhood, a as MemoryDump, b as FormattedMemoryDump, R as ReadOptions, S as SQLiteAdapter, W as WikiOptions, c as WikiMemory } from './testing-d1SrpDvg.js';
2
- export { C as ChunkFailure, d as ClassifierAnswer, e as ClassifierQuestion, f as ClassifyRequest, g as ClassifyResponse, D as DegradedRecord, h as DraftPage, E as EmbedFactResult, i as EmbedFailureKind, j as EmbeddingMarkerKind, k as EntityStatus, l as ExtractedFact, m as ExtractedFactEdge, n as ExtractedFactWithOntology, o as ExtractedTask, p as GraphTraversalOptions, q as GroundingConfig, H as HEAL_BATCH_SIZE, r as HEAL_RECHECK_MS, s as HOOK_TIMEOUT_MARKER, t as HealResult, I as IngestDocumentResult, L as LLMProvider, O as ONTOLOGY_BACKFILL_BATCH_SIZE, u as ONTOLOGY_BACKFILL_MAX_PROMPT_CHARS, v as ONTOLOGY_BACKFILL_RECHECK_MS, w as OntologyBackfillResult, x as OntologyConfig, y as OntologyEdgeType, z as OntologyManifest, A as OntologyMode, B as OntologyNodeType, J as OntologyPromptContext, K as OntologyUpdates, P as PromptOverrides, N as PromptService, Q as PrunePartialFailureError, T as ReembedResult, V as VectorRanker, U as VectorRankerFallback, X as VectorRankerRankArgs, Y as VectorRankerSemanticResult, Z as WikiBusyError, _ as WikiBusyOperation, $ as WikiCheckpoint, a0 as WikiConfig, a1 as WikiDiagnostic, a2 as WikiDiagnosticCode, a3 as WikiDiagnosticDetail, a4 as WikiDiagnosticOperation, a5 as WikiDiagnosticSeverity, a6 as WikiDiagnosticTrigger, a7 as WikiDraftNotFound, a8 as WikiDuplicateHashError, a9 as WikiEdge, aa as WikiEvent, ab as WikiFact, ac as WikiGraphNodeOwnershipConflict, ad as WikiIngestEmptyError, ae as WikiInvalidReadOptions, af as WikiMemoryTestAccess, ag as WikiOutboxEvent, ah as WikiParseError, ai as WikiSourceRefHashCollision, aj as WikiStrictOntologyViolation, ak as WikiTask, al as WikiTransactionError, am as validateManifest } from './testing-d1SrpDvg.js';
1
+ import { M as MemoryBundle, F as FormatContextOptions, G as GraphNeighborhood, a as MemoryDump, b as FormattedMemoryDump, R as ReadOptions, S as SQLiteAdapter, W as WikiOptions, c as WikiMemory } from './testing-A4Sofxyo.js';
2
+ export { C as ChunkFailure, d as ClassifierAnswer, e as ClassifierQuestion, f as ClassifyRequest, g as ClassifyResponse, D as DegradedRecord, h as DraftPage, E as EmbedFactResult, i as EmbedFailureKind, j as EmbeddingMarkerKind, k as EntityStatus, l as ExtractedFact, m as ExtractedFactEdge, n as ExtractedFactWithOntology, o as ExtractedTask, p as GraphTraversalOptions, q as GroundingConfig, H as HEAL_BATCH_SIZE, r as HEAL_RECHECK_MS, s as HOOK_TIMEOUT_MARKER, t as HealResult, I as IngestDocumentResult, L as LLMProvider, O as ONTOLOGY_BACKFILL_BATCH_SIZE, u as ONTOLOGY_BACKFILL_MAX_PROMPT_CHARS, v as ONTOLOGY_BACKFILL_RECHECK_MS, w as OntologyBackfillResult, x as OntologyConfig, y as OntologyEdgeType, z as OntologyManifest, A as OntologyMode, B as OntologyNodeType, J as OntologyPromptContext, K as OntologyUpdates, P as PendingSourceStatus, N as PromptOverrides, Q as PromptService, T as PrunePartialFailureError, U as ReembedResult, V as VectorRanker, X as VectorRankerFallback, Y as VectorRankerRankArgs, Z as VectorRankerSemanticResult, _ as WikiBusyError, $ as WikiBusyOperation, a0 as WikiCheckpoint, a1 as WikiConfig, a2 as WikiDiagnostic, a3 as WikiDiagnosticCode, a4 as WikiDiagnosticDetail, a5 as WikiDiagnosticOperation, a6 as WikiDiagnosticSeverity, a7 as WikiDiagnosticTrigger, a8 as WikiDraftNotFound, a9 as WikiDuplicateHashError, aa as WikiEdge, ab as WikiEvent, ac as WikiFact, ad as WikiGraphNodeOwnershipConflict, ae as WikiIngestEmptyError, af as WikiInstructions, ag as WikiInvalidReadOptions, ah as WikiLintReport, ai as WikiMemoryTestAccess, aj as WikiOutboxEvent, ak as WikiParseError, al as WikiSourceRefHashCollision, am as WikiStrictOntologyViolation, an as WikiTask, ao as WikiTransactionError, ap as validateManifest } from './testing-A4Sofxyo.js';
3
3
  import { OkfFile } from '@equationalapplications/core-okf';
4
4
  import 'minisearch';
5
5
 
package/dist/index.js CHANGED
@@ -1592,6 +1592,35 @@ var EntryRepository = class extends BaseRepository {
1592
1592
  }
1593
1593
  return out;
1594
1594
  }
1595
+ /**
1596
+ * Per source ref, the hash of the latest live row (same ordering as
1597
+ * {@link findLatestSourceHashes}) and whether ANY live row carries a hash.
1598
+ * Refs with no live row are absent. Chunked under the bind-variable limit.
1599
+ */
1600
+ async findSourceStates(entityId, sourceRefs, tx) {
1601
+ const out = /* @__PURE__ */ new Map();
1602
+ const deduped = Array.from(new Set(sourceRefs));
1603
+ if (deduped.length === 0) return out;
1604
+ const executor = this.getExecutor(tx);
1605
+ const chunkLimit = Math.max(1, this.chunkSize - 1);
1606
+ for (let i = 0; i < deduped.length; i += chunkLimit) {
1607
+ const chunk = deduped.slice(i, i + chunkLimit);
1608
+ const placeholders = chunk.map(() => "?").join(",");
1609
+ const rows = await executor.getAllAsync(
1610
+ `WITH ranked AS (
1611
+ SELECT source_ref, source_hash,
1612
+ ROW_NUMBER() OVER (PARTITION BY source_ref ORDER BY updated_at DESC, id ASC) AS rn,
1613
+ MAX(CASE WHEN source_hash IS NOT NULL THEN 1 ELSE 0 END) OVER (PARTITION BY source_ref) AS any_hashed
1614
+ FROM ${this.prefix}entries
1615
+ WHERE entity_id = ? AND source_ref IN (${placeholders}) AND deleted_at IS NULL
1616
+ )
1617
+ SELECT source_ref, source_hash, any_hashed FROM ranked WHERE rn = 1`,
1618
+ [entityId, ...chunk]
1619
+ );
1620
+ for (const r of rows) out.set(r.source_ref, { latestHash: r.source_hash, anyHashed: Number(r.any_hashed) === 1 });
1621
+ }
1622
+ return out;
1623
+ }
1595
1624
  /**
1596
1625
  * Return the live source_refs for an entity that hold the given source_hash.
1597
1626
  * Used by the ingestDocument duplicate-hash guard and by hosts auditing
@@ -2704,6 +2733,10 @@ function typeSatisfies(declaredType, concreteType, manifest) {
2704
2733
  const parent = typeof def?.parent_type === "string" ? def.parent_type.trim().toLowerCase() : "";
2705
2734
  return parent !== "" && parent === declared;
2706
2735
  }
2736
+ function edgeTripleAllowed(manifest, edgeType, sourceType, targetType) {
2737
+ const wanted = edgeType.trim().toLowerCase();
2738
+ return (manifest.edge_types ?? []).some((d) => typeof d?.type === "string" && d.type.trim().toLowerCase() === wanted && typeof d.source_type === "string" && typeSatisfies(d.source_type, sourceType, manifest) && typeof d.target_type === "string" && typeSatisfies(d.target_type, targetType, manifest));
2739
+ }
2707
2740
  function validateManifest(manifest) {
2708
2741
  const nodeSlugs = /* @__PURE__ */ new Set();
2709
2742
  for (const node of manifest.node_types ?? []) {
@@ -4607,6 +4640,21 @@ ${JSON.stringify(facts, null, 2)}`
4607
4640
  ${JSON.stringify(facts, null, 2)}`
4608
4641
  };
4609
4642
  }
4643
+ /**
4644
+ * The system prompt each writer would send, without hydrating data (spec
4645
+ * §8.3). `buildSystemPrompt` with no variables hydrates only ontology
4646
+ * placeholders and leaves data placeholders verbatim; heal never receives
4647
+ * ontology context, matching `buildHealPrompt`.
4648
+ */
4649
+ buildInstructionTemplates(ontologyContext) {
4650
+ const o = this.globalOverrides;
4651
+ return {
4652
+ ingest: this.appendGrounding(this.buildSystemPrompt(o?.ingestSystemPrompt ?? INGEST_SYSTEM_PROMPT, {}, ontologyContext), "ingest"),
4653
+ librarian: this.appendGrounding(this.buildSystemPrompt(o?.librarianSystemPrompt ?? LIBRARIAN_SYSTEM_PROMPT, {}, ontologyContext), "librarian"),
4654
+ heal: this.appendGrounding(o?.healSystemPrompt ?? HEAL_SYSTEM_PROMPT, "heal"),
4655
+ ontologyBackfill: this.buildSystemPrompt(o?.ontologyBackfillSystemPrompt ?? ONTOLOGY_BACKFILL_SYSTEM_PROMPT, {}, ontologyContext)
4656
+ };
4657
+ }
4610
4658
  };
4611
4659
  function applyBodyTruncation(candidates, attemptLevel, bodyTruncationChars) {
4612
4660
  if (attemptLevel < 3) {
@@ -8265,6 +8313,105 @@ var GraphTraversalService = class {
8265
8313
  return { nodes, edges: filteredEdges };
8266
8314
  }
8267
8315
  };
8316
+
8317
+ // src/repositories/LintRepository.ts
8318
+ var LintRepository = class extends BaseRepository {
8319
+ async countFactHealth(entityId) {
8320
+ const row = await this.db.getFirstAsync(
8321
+ `SELECT
8322
+ SUM(CASE WHEN okf_type IS NULL THEN 1 ELSE 0 END) AS untyped,
8323
+ SUM(CASE WHEN lifecycle_status = 'draft' THEN 1 ELSE 0 END) AS drafts,
8324
+ SUM(CASE WHEN source_type = 'librarian_inferred'
8325
+ AND COALESCE(json_array_length(CASE WHEN json_valid(okf_verified) THEN okf_verified END), 0) = 0
8326
+ THEN 1 ELSE 0 END) AS unverified
8327
+ FROM ${this.prefix}entries
8328
+ WHERE entity_id = ? AND deleted_at IS NULL`,
8329
+ [entityId]
8330
+ );
8331
+ return {
8332
+ untypedFacts: Number(row?.untyped ?? 0),
8333
+ drafts: Number(row?.drafts ?? 0),
8334
+ unverifiedInferred: Number(row?.unverified ?? 0)
8335
+ };
8336
+ }
8337
+ danglingWhere() {
8338
+ const live = (col) => `EXISTS (SELECT 1 FROM ${this.prefix}entries n WHERE n.id = e.${col} AND n.entity_id = e.entity_id AND n.deleted_at IS NULL)`;
8339
+ return `e.entity_id = ? AND (NOT ${live("source_id")} OR NOT ${live("target_id")})`;
8340
+ }
8341
+ async countDanglingEdges(entityId) {
8342
+ const row = await this.db.getFirstAsync(
8343
+ `SELECT COUNT(*) AS n FROM ${this.prefix}edges e WHERE ${this.danglingWhere()}`,
8344
+ [entityId]
8345
+ );
8346
+ return Number(row?.n ?? 0);
8347
+ }
8348
+ async sampleDanglingEdgeIds(entityId, limit) {
8349
+ const rows = await this.db.getAllAsync(
8350
+ `SELECT e.id FROM ${this.prefix}edges e WHERE ${this.danglingWhere()} ORDER BY e.id ASC LIMIT ?`,
8351
+ [entityId, limit]
8352
+ );
8353
+ return rows.map((r) => r.id);
8354
+ }
8355
+ /** Keyset page of edges whose endpoints are both live in this entity, with endpoint types. */
8356
+ async pageLiveEdges(entityId, afterId, limit) {
8357
+ return this.db.getAllAsync(
8358
+ `SELECT e.id, e.edge_type, s.okf_type AS source_type, t.okf_type AS target_type
8359
+ FROM ${this.prefix}edges e
8360
+ JOIN ${this.prefix}entries s ON s.id = e.source_id AND s.entity_id = e.entity_id AND s.deleted_at IS NULL
8361
+ JOIN ${this.prefix}entries t ON t.id = e.target_id AND t.entity_id = e.entity_id AND t.deleted_at IS NULL
8362
+ WHERE e.entity_id = ? AND e.id > ?
8363
+ ORDER BY e.id ASC
8364
+ LIMIT ?`,
8365
+ [entityId, afterId, limit]
8366
+ );
8367
+ }
8368
+ };
8369
+
8370
+ // src/services/LintService.ts
8371
+ var LINT_SAMPLE_SIZE = 20;
8372
+ var LINT_PAGE_SIZE = 500;
8373
+ var LintService = class {
8374
+ constructor(lintRepo, ontologyService) {
8375
+ this.lintRepo = lintRepo;
8376
+ this.ontologyService = ontologyService;
8377
+ }
8378
+ /**
8379
+ * Read-only (spec §8.1). Manifest violations stream the entity's live-endpoint
8380
+ * edges in keyset pages and check each triple in JS against the effective
8381
+ * manifest. `getEffectiveState` is called without a transaction, so a seed
8382
+ * manifest is cached, never persisted. Counts come from separate statements,
8383
+ * not one snapshot.
8384
+ */
8385
+ async lint(entityId) {
8386
+ const health = await this.lintRepo.countFactHealth(entityId);
8387
+ const danglingEdges = await this.lintRepo.countDanglingEdges(entityId);
8388
+ const danglingEdgeIds = danglingEdges > 0 ? await this.lintRepo.sampleDanglingEdgeIds(entityId, LINT_SAMPLE_SIZE) : [];
8389
+ const { mode, manifest } = await this.ontologyService.getEffectiveState(entityId);
8390
+ const constrained = mode !== "off" && ((manifest.node_types?.length ?? 0) > 0 || (manifest.edge_types?.length ?? 0) > 0);
8391
+ let manifestViolations = 0;
8392
+ const manifestViolationEdgeIds = [];
8393
+ if (constrained) {
8394
+ let after = "";
8395
+ for (; ; ) {
8396
+ const page = await this.lintRepo.pageLiveEdges(entityId, after, LINT_PAGE_SIZE);
8397
+ for (const e of page) {
8398
+ if (!edgeTripleAllowed(manifest, e.edge_type, e.source_type ?? "", e.target_type ?? "")) {
8399
+ manifestViolations++;
8400
+ if (manifestViolationEdgeIds.length < LINT_SAMPLE_SIZE) manifestViolationEdgeIds.push(e.id);
8401
+ }
8402
+ }
8403
+ if (page.length < LINT_PAGE_SIZE) break;
8404
+ after = page[page.length - 1].id;
8405
+ }
8406
+ }
8407
+ return {
8408
+ danglingEdges,
8409
+ manifestViolations,
8410
+ ...health,
8411
+ sample: { danglingEdgeIds, manifestViolationEdgeIds }
8412
+ };
8413
+ }
8414
+ };
8268
8415
  var OkfTrustWritesRepository = class {
8269
8416
  constructor(db, prefix) {
8270
8417
  this.db = db;
@@ -8455,11 +8602,13 @@ var WikiMemory = class {
8455
8602
  this.eventRepo = new EventRepository(this.db, this.prefix);
8456
8603
  this.edgeRepo = new EdgeRepository(this.db, this.prefix);
8457
8604
  this.metadataRepo = new MetadataRepository(this.db, this.prefix);
8605
+ this.lintRepo = new LintRepository(this.db, this.prefix);
8458
8606
  this.ontologyService = new OntologyService(
8459
8607
  this.metadataRepo,
8460
8608
  this.edgeRepo,
8461
8609
  options.config?.ontology
8462
8610
  );
8611
+ this.lintService = new LintService(this.lintRepo, this.ontologyService);
8463
8612
  this.embeddingService = new EmbeddingService(this.db, this.options, this.entryRepo, this.metadataRepo);
8464
8613
  this.searchService = new SearchService(this.entryRepo);
8465
8614
  this.jobManager = new JobManager(this.prefix);
@@ -8642,6 +8791,61 @@ var WikiMemory = class {
8642
8791
  return { sourceRef: e.rawSourceRef, changed, duplicateOf: canonical };
8643
8792
  });
8644
8793
  }
8794
+ /**
8795
+ * Batch pending state per source (spec §8.2). `current` exactly when
8796
+ * `hasChanged` is false; `partial` when live rows exist but none carries a
8797
+ * hash (a first ingest with a failed chunk, or imported rows without a
8798
+ * hash). A failed re-ingest of an already-hashed ref reports `changed`. Validation and raw-ref echo match
8799
+ * batched `hasChanged`. Input order and duplicates are preserved. A
8800
+ * non-array `sources`, or an entry that is not an object (including a hole
8801
+ * in a sparse array), throws a `TypeError` before any SQL runs.
8802
+ */
8803
+ async pendingSources(entityId, sources) {
8804
+ assertEntityId(entityId);
8805
+ if (!Array.isArray(sources)) {
8806
+ throw new TypeError("Invalid sources: must be an array of { sourceRef, sourceHash } objects.");
8807
+ }
8808
+ if (sources.length === 0) return [];
8809
+ const normalized = Array.from(sources, (s, i) => {
8810
+ if (s === null || typeof s !== "object") {
8811
+ throw new TypeError(`Invalid sources[${i}]: must be a { sourceRef, sourceHash } object.`);
8812
+ }
8813
+ const { sourceRef: rawSourceRef, sourceHash: rawSourceHash } = s;
8814
+ const sourceRef = normalizeSourceRef(rawSourceRef);
8815
+ if (!sourceRef) {
8816
+ const shown = typeof rawSourceRef === "string" ? JSON.stringify(rawSourceRef) : `<${typeof rawSourceRef}>`;
8817
+ throw new Error(`Invalid sourceRef: ${shown}`);
8818
+ }
8819
+ const sourceHash = normalizeSourceHash(rawSourceHash);
8820
+ if (!sourceHash) throw new Error("Invalid sourceHash: must be a 64-character hex string (normalized to lowercase)");
8821
+ return { rawSourceRef, sourceRef, sourceHash };
8822
+ });
8823
+ const states = await this.entryRepo.findSourceStates(entityId, normalized.map((n) => n.sourceRef));
8824
+ return normalized.map((n) => {
8825
+ const state = states.get(n.sourceRef);
8826
+ let status;
8827
+ if (!state) status = "new";
8828
+ else if (!state.anyHashed) status = "partial";
8829
+ else if (state.latestHash !== null && normalizeSourceHash(state.latestHash) === n.sourceHash) status = "current";
8830
+ else status = "changed";
8831
+ return { sourceRef: n.rawSourceRef, status };
8832
+ });
8833
+ }
8834
+ /** Read-only maintenance report for one entity (spec §8.1). Reports, never repairs. */
8835
+ async lint(entityId) {
8836
+ assertEntityId(entityId);
8837
+ return this.lintService.lint(entityId);
8838
+ }
8839
+ /**
8840
+ * Effective system prompts for ingest, librarian, heal and ontology backfill
8841
+ * (spec §8.3). Backs the `wiki_get_instructions` tool. Templates only;
8842
+ * overrides are returned verbatim, so keep secrets out of `WikiConfig.prompts`.
8843
+ */
8844
+ async getInstructions(entityId) {
8845
+ assertEntityId(entityId);
8846
+ const ontologyContext = await this.ontologyService.buildPromptContext(entityId);
8847
+ return this.promptService.buildInstructionTemplates(ontologyContext);
8848
+ }
8645
8849
  /**
8646
8850
  * Returns the live source_refs for an entity, one row per ref, with the most
8647
8851
  * recently-updated live `source_hash` and a live fact count. Refs are sorted
@@ -9042,6 +9246,11 @@ var WikiMemory = class {
9042
9246
  }
9043
9247
  };
9044
9248
  _testAccessNonTestEnvWarned = new WeakMap();
9249
+ function assertEntityId(entityId) {
9250
+ if (typeof entityId !== "string" || entityId.length === 0) {
9251
+ throw new TypeError("Invalid entityId: must be a non-empty string.");
9252
+ }
9253
+ }
9045
9254
 
9046
9255
  // src/utils/formatContext.ts
9047
9256
  function validateMaxOption(value, name) {