@equationalapplications/core-llm-wiki 7.6.0 → 7.7.1
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/README.md +64 -0
- package/dist/{chunk-YBWE26XR.mjs → chunk-NWODQRSM.mjs} +52 -18
- package/dist/chunk-NWODQRSM.mjs.map +1 -0
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +250 -16
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +203 -3
- package/dist/index.mjs.map +1 -1
- package/dist/{testing-d1SrpDvg.d.mts → testing-A4Sofxyo.d.mts} +85 -2
- package/dist/{testing-d1SrpDvg.d.ts → testing-A4Sofxyo.d.ts} +85 -2
- package/dist/testing.d.mts +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +45 -15
- package/dist/testing.js.map +1 -1
- package/dist/testing.mjs +1 -1
- package/package.json +2 -2
- package/dist/chunk-YBWE26XR.mjs.map +0 -1
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-
|
|
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
|
|
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-
|
|
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
|
|
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
|
@@ -107,7 +107,7 @@ async function setupDatabase(db, prefix) {
|
|
|
107
107
|
UNIQUE(entity_id, source_id, target_id, edge_type)
|
|
108
108
|
);
|
|
109
109
|
|
|
110
|
-
CREATE INDEX IF NOT EXISTS ${prefix}
|
|
110
|
+
CREATE INDEX IF NOT EXISTS ${prefix}edges_entity_id_idx ON ${prefix}edges(entity_id, id);
|
|
111
111
|
|
|
112
112
|
CREATE TABLE IF NOT EXISTS ${prefix}events (
|
|
113
113
|
id TEXT PRIMARY KEY,
|
|
@@ -620,6 +620,16 @@ var MIGRATIONS = [
|
|
|
620
620
|
if (!existing.has(name)) await db.execAsync(sql);
|
|
621
621
|
}
|
|
622
622
|
}
|
|
623
|
+
},
|
|
624
|
+
{
|
|
625
|
+
version: 12,
|
|
626
|
+
description: "Replace edges_entity_idx with composite edges(entity_id, id) for keyset paging",
|
|
627
|
+
run: async (db, prefix) => {
|
|
628
|
+
await db.execAsync(`
|
|
629
|
+
CREATE INDEX IF NOT EXISTS ${prefix}edges_entity_id_idx ON ${prefix}edges(entity_id, id);
|
|
630
|
+
DROP INDEX IF EXISTS ${prefix}edges_entity_idx;
|
|
631
|
+
`);
|
|
632
|
+
}
|
|
623
633
|
}
|
|
624
634
|
];
|
|
625
635
|
for (let i = 1; i < MIGRATIONS.length; i++) {
|
|
@@ -1592,6 +1602,35 @@ var EntryRepository = class extends BaseRepository {
|
|
|
1592
1602
|
}
|
|
1593
1603
|
return out;
|
|
1594
1604
|
}
|
|
1605
|
+
/**
|
|
1606
|
+
* Per source ref, the hash of the latest live row (same ordering as
|
|
1607
|
+
* {@link findLatestSourceHashes}) and whether ANY live row carries a hash.
|
|
1608
|
+
* Refs with no live row are absent. Chunked under the bind-variable limit.
|
|
1609
|
+
*/
|
|
1610
|
+
async findSourceStates(entityId, sourceRefs, tx) {
|
|
1611
|
+
const out = /* @__PURE__ */ new Map();
|
|
1612
|
+
const deduped = Array.from(new Set(sourceRefs));
|
|
1613
|
+
if (deduped.length === 0) return out;
|
|
1614
|
+
const executor = this.getExecutor(tx);
|
|
1615
|
+
const chunkLimit = Math.max(1, this.chunkSize - 1);
|
|
1616
|
+
for (let i = 0; i < deduped.length; i += chunkLimit) {
|
|
1617
|
+
const chunk = deduped.slice(i, i + chunkLimit);
|
|
1618
|
+
const placeholders = chunk.map(() => "?").join(",");
|
|
1619
|
+
const rows = await executor.getAllAsync(
|
|
1620
|
+
`WITH ranked AS (
|
|
1621
|
+
SELECT source_ref, source_hash,
|
|
1622
|
+
ROW_NUMBER() OVER (PARTITION BY source_ref ORDER BY updated_at DESC, id ASC) AS rn,
|
|
1623
|
+
MAX(CASE WHEN source_hash IS NOT NULL THEN 1 ELSE 0 END) OVER (PARTITION BY source_ref) AS any_hashed
|
|
1624
|
+
FROM ${this.prefix}entries
|
|
1625
|
+
WHERE entity_id = ? AND source_ref IN (${placeholders}) AND deleted_at IS NULL
|
|
1626
|
+
)
|
|
1627
|
+
SELECT source_ref, source_hash, any_hashed FROM ranked WHERE rn = 1`,
|
|
1628
|
+
[entityId, ...chunk]
|
|
1629
|
+
);
|
|
1630
|
+
for (const r of rows) out.set(r.source_ref, { latestHash: r.source_hash, anyHashed: Number(r.any_hashed) === 1 });
|
|
1631
|
+
}
|
|
1632
|
+
return out;
|
|
1633
|
+
}
|
|
1595
1634
|
/**
|
|
1596
1635
|
* Return the live source_refs for an entity that hold the given source_hash.
|
|
1597
1636
|
* Used by the ingestDocument duplicate-hash guard and by hosts auditing
|
|
@@ -2704,6 +2743,10 @@ function typeSatisfies(declaredType, concreteType, manifest) {
|
|
|
2704
2743
|
const parent = typeof def?.parent_type === "string" ? def.parent_type.trim().toLowerCase() : "";
|
|
2705
2744
|
return parent !== "" && parent === declared;
|
|
2706
2745
|
}
|
|
2746
|
+
function edgeTripleAllowed(manifest, edgeType, sourceType, targetType) {
|
|
2747
|
+
const wanted = edgeType.trim().toLowerCase();
|
|
2748
|
+
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));
|
|
2749
|
+
}
|
|
2707
2750
|
function validateManifest(manifest) {
|
|
2708
2751
|
const nodeSlugs = /* @__PURE__ */ new Set();
|
|
2709
2752
|
for (const node of manifest.node_types ?? []) {
|
|
@@ -4607,6 +4650,21 @@ ${JSON.stringify(facts, null, 2)}`
|
|
|
4607
4650
|
${JSON.stringify(facts, null, 2)}`
|
|
4608
4651
|
};
|
|
4609
4652
|
}
|
|
4653
|
+
/**
|
|
4654
|
+
* The system prompt each writer would send, without hydrating data (spec
|
|
4655
|
+
* §8.3). `buildSystemPrompt` with no variables hydrates only ontology
|
|
4656
|
+
* placeholders and leaves data placeholders verbatim; heal never receives
|
|
4657
|
+
* ontology context, matching `buildHealPrompt`.
|
|
4658
|
+
*/
|
|
4659
|
+
buildInstructionTemplates(ontologyContext) {
|
|
4660
|
+
const o = this.globalOverrides;
|
|
4661
|
+
return {
|
|
4662
|
+
ingest: this.appendGrounding(this.buildSystemPrompt(o?.ingestSystemPrompt ?? INGEST_SYSTEM_PROMPT, {}, ontologyContext), "ingest"),
|
|
4663
|
+
librarian: this.appendGrounding(this.buildSystemPrompt(o?.librarianSystemPrompt ?? LIBRARIAN_SYSTEM_PROMPT, {}, ontologyContext), "librarian"),
|
|
4664
|
+
heal: this.appendGrounding(o?.healSystemPrompt ?? HEAL_SYSTEM_PROMPT, "heal"),
|
|
4665
|
+
ontologyBackfill: this.buildSystemPrompt(o?.ontologyBackfillSystemPrompt ?? ONTOLOGY_BACKFILL_SYSTEM_PROMPT, {}, ontologyContext)
|
|
4666
|
+
};
|
|
4667
|
+
}
|
|
4610
4668
|
};
|
|
4611
4669
|
function applyBodyTruncation(candidates, attemptLevel, bodyTruncationChars) {
|
|
4612
4670
|
if (attemptLevel < 3) {
|
|
@@ -4789,11 +4847,16 @@ var IngestionService = class {
|
|
|
4789
4847
|
let ingestedChunks = 0;
|
|
4790
4848
|
let failedChunks = 0;
|
|
4791
4849
|
const failures = [];
|
|
4792
|
-
const
|
|
4793
|
-
const orderedChunkFacts = [];
|
|
4794
|
-
const groundingLedger = /* @__PURE__ */ new Map();
|
|
4850
|
+
const winners = /* @__PURE__ */ new Map();
|
|
4795
4851
|
const diagBuffer = new DiagnosticBuffer();
|
|
4796
4852
|
const diagBase = { entityId, operation: "ingest", trigger: "call" };
|
|
4853
|
+
const pushDeduplicated = (chunkIndex, itemIndex) => {
|
|
4854
|
+
diagBuffer.push({
|
|
4855
|
+
...diagBase,
|
|
4856
|
+
code: "fact_deduplicated",
|
|
4857
|
+
detail: { sourceRef, chunkIndex, itemIndex, reason: "exact_title" }
|
|
4858
|
+
});
|
|
4859
|
+
};
|
|
4797
4860
|
for (const [chunkIndex, slot] of chunkResults.entries()) {
|
|
4798
4861
|
if (slot.status === "failed") {
|
|
4799
4862
|
failedChunks++;
|
|
@@ -4804,22 +4867,32 @@ var IngestionService = class {
|
|
|
4804
4867
|
for (const r of slot.rejected) {
|
|
4805
4868
|
diagBuffer.push({ ...diagBase, code: "fact_rejected", detail: { sourceRef, chunkIndex, itemIndex: r.itemIndex, reason: r.reason } });
|
|
4806
4869
|
}
|
|
4807
|
-
const dedupedFacts = [];
|
|
4808
4870
|
slot.facts.forEach((fact, k) => {
|
|
4809
|
-
const
|
|
4810
|
-
|
|
4811
|
-
|
|
4812
|
-
|
|
4813
|
-
|
|
4871
|
+
const key = normalizeTitleKey(fact.title);
|
|
4872
|
+
const candidate = { chunkIndex, k, itemIndex: slot.itemIndexes[k], rank: slot.verdicts[k]?.status === "grounded" ? 1 : 0 };
|
|
4873
|
+
const current = winners.get(key);
|
|
4874
|
+
if (!current) {
|
|
4875
|
+
winners.set(key, candidate);
|
|
4876
|
+
} else if (candidate.rank > current.rank) {
|
|
4877
|
+
pushDeduplicated(current.chunkIndex, current.itemIndex);
|
|
4878
|
+
winners.set(key, candidate);
|
|
4814
4879
|
} else {
|
|
4815
|
-
|
|
4816
|
-
...diagBase,
|
|
4817
|
-
code: "fact_deduplicated",
|
|
4818
|
-
detail: { sourceRef, chunkIndex, itemIndex: slot.itemIndexes[k], reason: "exact_title" }
|
|
4819
|
-
});
|
|
4880
|
+
pushDeduplicated(candidate.chunkIndex, candidate.itemIndex);
|
|
4820
4881
|
}
|
|
4821
4882
|
});
|
|
4822
|
-
|
|
4883
|
+
}
|
|
4884
|
+
const orderedChunkFacts = [];
|
|
4885
|
+
const groundingLedger = /* @__PURE__ */ new Map();
|
|
4886
|
+
for (const [chunkIndex, slot] of chunkResults.entries()) {
|
|
4887
|
+
if (slot.status === "failed") continue;
|
|
4888
|
+
const keptFacts = [];
|
|
4889
|
+
slot.facts.forEach((fact, k) => {
|
|
4890
|
+
const winner = winners.get(normalizeTitleKey(fact.title));
|
|
4891
|
+
if (winner?.chunkIndex !== chunkIndex || winner.k !== k) return;
|
|
4892
|
+
keptFacts.push(fact);
|
|
4893
|
+
if (ingestGrounding) groundingLedger.set(fact, { verdict: slot.verdicts[k], chunkIndex, itemIndex: winner.itemIndex });
|
|
4894
|
+
});
|
|
4895
|
+
orderedChunkFacts.push({ facts: keptFacts, ontology_updates: slot.ontology_updates });
|
|
4823
4896
|
}
|
|
4824
4897
|
for (const reason of ["parse", "llm"]) {
|
|
4825
4898
|
const ofReason = failures.filter((f) => f.source === reason);
|
|
@@ -8265,6 +8338,105 @@ var GraphTraversalService = class {
|
|
|
8265
8338
|
return { nodes, edges: filteredEdges };
|
|
8266
8339
|
}
|
|
8267
8340
|
};
|
|
8341
|
+
|
|
8342
|
+
// src/repositories/LintRepository.ts
|
|
8343
|
+
var LintRepository = class extends BaseRepository {
|
|
8344
|
+
async countFactHealth(entityId) {
|
|
8345
|
+
const row = await this.db.getFirstAsync(
|
|
8346
|
+
`SELECT
|
|
8347
|
+
SUM(CASE WHEN okf_type IS NULL THEN 1 ELSE 0 END) AS untyped,
|
|
8348
|
+
SUM(CASE WHEN lifecycle_status = 'draft' THEN 1 ELSE 0 END) AS drafts,
|
|
8349
|
+
SUM(CASE WHEN source_type = 'librarian_inferred'
|
|
8350
|
+
AND COALESCE(json_array_length(CASE WHEN json_valid(okf_verified) THEN okf_verified END), 0) = 0
|
|
8351
|
+
THEN 1 ELSE 0 END) AS unverified
|
|
8352
|
+
FROM ${this.prefix}entries
|
|
8353
|
+
WHERE entity_id = ? AND deleted_at IS NULL`,
|
|
8354
|
+
[entityId]
|
|
8355
|
+
);
|
|
8356
|
+
return {
|
|
8357
|
+
untypedFacts: Number(row?.untyped ?? 0),
|
|
8358
|
+
drafts: Number(row?.drafts ?? 0),
|
|
8359
|
+
unverifiedInferred: Number(row?.unverified ?? 0)
|
|
8360
|
+
};
|
|
8361
|
+
}
|
|
8362
|
+
danglingWhere() {
|
|
8363
|
+
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)`;
|
|
8364
|
+
return `e.entity_id = ? AND (NOT ${live("source_id")} OR NOT ${live("target_id")})`;
|
|
8365
|
+
}
|
|
8366
|
+
async countDanglingEdges(entityId) {
|
|
8367
|
+
const row = await this.db.getFirstAsync(
|
|
8368
|
+
`SELECT COUNT(*) AS n FROM ${this.prefix}edges e WHERE ${this.danglingWhere()}`,
|
|
8369
|
+
[entityId]
|
|
8370
|
+
);
|
|
8371
|
+
return Number(row?.n ?? 0);
|
|
8372
|
+
}
|
|
8373
|
+
async sampleDanglingEdgeIds(entityId, limit) {
|
|
8374
|
+
const rows = await this.db.getAllAsync(
|
|
8375
|
+
`SELECT e.id FROM ${this.prefix}edges e WHERE ${this.danglingWhere()} ORDER BY e.id ASC LIMIT ?`,
|
|
8376
|
+
[entityId, limit]
|
|
8377
|
+
);
|
|
8378
|
+
return rows.map((r) => r.id);
|
|
8379
|
+
}
|
|
8380
|
+
/** Keyset page of edges whose endpoints are both live in this entity, with endpoint types. */
|
|
8381
|
+
async pageLiveEdges(entityId, afterId, limit) {
|
|
8382
|
+
return this.db.getAllAsync(
|
|
8383
|
+
`SELECT e.id, e.edge_type, s.okf_type AS source_type, t.okf_type AS target_type
|
|
8384
|
+
FROM ${this.prefix}edges e
|
|
8385
|
+
JOIN ${this.prefix}entries s ON s.id = e.source_id AND s.entity_id = e.entity_id AND s.deleted_at IS NULL
|
|
8386
|
+
JOIN ${this.prefix}entries t ON t.id = e.target_id AND t.entity_id = e.entity_id AND t.deleted_at IS NULL
|
|
8387
|
+
WHERE e.entity_id = ? AND e.id > ?
|
|
8388
|
+
ORDER BY e.id ASC
|
|
8389
|
+
LIMIT ?`,
|
|
8390
|
+
[entityId, afterId, limit]
|
|
8391
|
+
);
|
|
8392
|
+
}
|
|
8393
|
+
};
|
|
8394
|
+
|
|
8395
|
+
// src/services/LintService.ts
|
|
8396
|
+
var LINT_SAMPLE_SIZE = 20;
|
|
8397
|
+
var LINT_PAGE_SIZE = 500;
|
|
8398
|
+
var LintService = class {
|
|
8399
|
+
constructor(lintRepo, ontologyService) {
|
|
8400
|
+
this.lintRepo = lintRepo;
|
|
8401
|
+
this.ontologyService = ontologyService;
|
|
8402
|
+
}
|
|
8403
|
+
/**
|
|
8404
|
+
* Read-only (spec §8.1). Manifest violations stream the entity's live-endpoint
|
|
8405
|
+
* edges in keyset pages and check each triple in JS against the effective
|
|
8406
|
+
* manifest. `getEffectiveState` is called without a transaction, so a seed
|
|
8407
|
+
* manifest is cached, never persisted. Counts come from separate statements,
|
|
8408
|
+
* not one snapshot.
|
|
8409
|
+
*/
|
|
8410
|
+
async lint(entityId) {
|
|
8411
|
+
const health = await this.lintRepo.countFactHealth(entityId);
|
|
8412
|
+
const danglingEdges = await this.lintRepo.countDanglingEdges(entityId);
|
|
8413
|
+
const danglingEdgeIds = danglingEdges > 0 ? await this.lintRepo.sampleDanglingEdgeIds(entityId, LINT_SAMPLE_SIZE) : [];
|
|
8414
|
+
const { mode, manifest } = await this.ontologyService.getEffectiveState(entityId);
|
|
8415
|
+
const constrained = mode !== "off" && ((manifest.node_types?.length ?? 0) > 0 || (manifest.edge_types?.length ?? 0) > 0);
|
|
8416
|
+
let manifestViolations = 0;
|
|
8417
|
+
const manifestViolationEdgeIds = [];
|
|
8418
|
+
if (constrained) {
|
|
8419
|
+
let after = "";
|
|
8420
|
+
for (; ; ) {
|
|
8421
|
+
const page = await this.lintRepo.pageLiveEdges(entityId, after, LINT_PAGE_SIZE);
|
|
8422
|
+
for (const e of page) {
|
|
8423
|
+
if (!edgeTripleAllowed(manifest, e.edge_type, e.source_type ?? "", e.target_type ?? "")) {
|
|
8424
|
+
manifestViolations++;
|
|
8425
|
+
if (manifestViolationEdgeIds.length < LINT_SAMPLE_SIZE) manifestViolationEdgeIds.push(e.id);
|
|
8426
|
+
}
|
|
8427
|
+
}
|
|
8428
|
+
if (page.length < LINT_PAGE_SIZE) break;
|
|
8429
|
+
after = page[page.length - 1].id;
|
|
8430
|
+
}
|
|
8431
|
+
}
|
|
8432
|
+
return {
|
|
8433
|
+
danglingEdges,
|
|
8434
|
+
manifestViolations,
|
|
8435
|
+
...health,
|
|
8436
|
+
sample: { danglingEdgeIds, manifestViolationEdgeIds }
|
|
8437
|
+
};
|
|
8438
|
+
}
|
|
8439
|
+
};
|
|
8268
8440
|
var OkfTrustWritesRepository = class {
|
|
8269
8441
|
constructor(db, prefix) {
|
|
8270
8442
|
this.db = db;
|
|
@@ -8455,11 +8627,13 @@ var WikiMemory = class {
|
|
|
8455
8627
|
this.eventRepo = new EventRepository(this.db, this.prefix);
|
|
8456
8628
|
this.edgeRepo = new EdgeRepository(this.db, this.prefix);
|
|
8457
8629
|
this.metadataRepo = new MetadataRepository(this.db, this.prefix);
|
|
8630
|
+
this.lintRepo = new LintRepository(this.db, this.prefix);
|
|
8458
8631
|
this.ontologyService = new OntologyService(
|
|
8459
8632
|
this.metadataRepo,
|
|
8460
8633
|
this.edgeRepo,
|
|
8461
8634
|
options.config?.ontology
|
|
8462
8635
|
);
|
|
8636
|
+
this.lintService = new LintService(this.lintRepo, this.ontologyService);
|
|
8463
8637
|
this.embeddingService = new EmbeddingService(this.db, this.options, this.entryRepo, this.metadataRepo);
|
|
8464
8638
|
this.searchService = new SearchService(this.entryRepo);
|
|
8465
8639
|
this.jobManager = new JobManager(this.prefix);
|
|
@@ -8642,6 +8816,61 @@ var WikiMemory = class {
|
|
|
8642
8816
|
return { sourceRef: e.rawSourceRef, changed, duplicateOf: canonical };
|
|
8643
8817
|
});
|
|
8644
8818
|
}
|
|
8819
|
+
/**
|
|
8820
|
+
* Batch pending state per source (spec §8.2). `current` exactly when
|
|
8821
|
+
* `hasChanged` is false; `partial` when live rows exist but none carries a
|
|
8822
|
+
* hash (a first ingest with a failed chunk, or imported rows without a
|
|
8823
|
+
* hash). A failed re-ingest of an already-hashed ref reports `changed`. Validation and raw-ref echo match
|
|
8824
|
+
* batched `hasChanged`. Input order and duplicates are preserved. A
|
|
8825
|
+
* non-array `sources`, or an entry that is not an object (including a hole
|
|
8826
|
+
* in a sparse array), throws a `TypeError` before any SQL runs.
|
|
8827
|
+
*/
|
|
8828
|
+
async pendingSources(entityId, sources) {
|
|
8829
|
+
assertEntityId(entityId);
|
|
8830
|
+
if (!Array.isArray(sources)) {
|
|
8831
|
+
throw new TypeError("Invalid sources: must be an array of { sourceRef, sourceHash } objects.");
|
|
8832
|
+
}
|
|
8833
|
+
if (sources.length === 0) return [];
|
|
8834
|
+
const normalized = Array.from(sources, (s, i) => {
|
|
8835
|
+
if (s === null || typeof s !== "object") {
|
|
8836
|
+
throw new TypeError(`Invalid sources[${i}]: must be a { sourceRef, sourceHash } object.`);
|
|
8837
|
+
}
|
|
8838
|
+
const { sourceRef: rawSourceRef, sourceHash: rawSourceHash } = s;
|
|
8839
|
+
const sourceRef = normalizeSourceRef(rawSourceRef);
|
|
8840
|
+
if (!sourceRef) {
|
|
8841
|
+
const shown = typeof rawSourceRef === "string" ? JSON.stringify(rawSourceRef) : `<${typeof rawSourceRef}>`;
|
|
8842
|
+
throw new Error(`Invalid sourceRef: ${shown}`);
|
|
8843
|
+
}
|
|
8844
|
+
const sourceHash = normalizeSourceHash(rawSourceHash);
|
|
8845
|
+
if (!sourceHash) throw new Error("Invalid sourceHash: must be a 64-character hex string (normalized to lowercase)");
|
|
8846
|
+
return { rawSourceRef, sourceRef, sourceHash };
|
|
8847
|
+
});
|
|
8848
|
+
const states = await this.entryRepo.findSourceStates(entityId, normalized.map((n) => n.sourceRef));
|
|
8849
|
+
return normalized.map((n) => {
|
|
8850
|
+
const state = states.get(n.sourceRef);
|
|
8851
|
+
let status;
|
|
8852
|
+
if (!state) status = "new";
|
|
8853
|
+
else if (!state.anyHashed) status = "partial";
|
|
8854
|
+
else if (state.latestHash !== null && normalizeSourceHash(state.latestHash) === n.sourceHash) status = "current";
|
|
8855
|
+
else status = "changed";
|
|
8856
|
+
return { sourceRef: n.rawSourceRef, status };
|
|
8857
|
+
});
|
|
8858
|
+
}
|
|
8859
|
+
/** Read-only maintenance report for one entity (spec §8.1). Reports, never repairs. */
|
|
8860
|
+
async lint(entityId) {
|
|
8861
|
+
assertEntityId(entityId);
|
|
8862
|
+
return this.lintService.lint(entityId);
|
|
8863
|
+
}
|
|
8864
|
+
/**
|
|
8865
|
+
* Effective system prompts for ingest, librarian, heal and ontology backfill
|
|
8866
|
+
* (spec §8.3). Backs the `wiki_get_instructions` tool. Templates only;
|
|
8867
|
+
* overrides are returned verbatim, so keep secrets out of `WikiConfig.prompts`.
|
|
8868
|
+
*/
|
|
8869
|
+
async getInstructions(entityId) {
|
|
8870
|
+
assertEntityId(entityId);
|
|
8871
|
+
const ontologyContext = await this.ontologyService.buildPromptContext(entityId);
|
|
8872
|
+
return this.promptService.buildInstructionTemplates(ontologyContext);
|
|
8873
|
+
}
|
|
8645
8874
|
/**
|
|
8646
8875
|
* Returns the live source_refs for an entity, one row per ref, with the most
|
|
8647
8876
|
* recently-updated live `source_hash` and a live fact count. Refs are sorted
|
|
@@ -9042,6 +9271,11 @@ var WikiMemory = class {
|
|
|
9042
9271
|
}
|
|
9043
9272
|
};
|
|
9044
9273
|
_testAccessNonTestEnvWarned = new WeakMap();
|
|
9274
|
+
function assertEntityId(entityId) {
|
|
9275
|
+
if (typeof entityId !== "string" || entityId.length === 0) {
|
|
9276
|
+
throw new TypeError("Invalid entityId: must be a non-empty string.");
|
|
9277
|
+
}
|
|
9278
|
+
}
|
|
9045
9279
|
|
|
9046
9280
|
// src/utils/formatContext.ts
|
|
9047
9281
|
function validateMaxOption(value, name) {
|