@equationalapplications/core-llm-wiki 5.3.0 → 5.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/README.md +24 -0
- package/dist/{chunk-KIJINNYU.mjs → chunk-N2NXN6PO.mjs} +33 -6
- package/dist/chunk-N2NXN6PO.mjs.map +1 -0
- package/dist/index.d.mts +9 -4
- package/dist/index.d.ts +9 -4
- package/dist/index.js +657 -52
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +629 -50
- package/dist/index.mjs.map +1 -1
- package/dist/{testing-zf3Jp_RL.d.mts → testing-C71mnMxq.d.mts} +104 -0
- package/dist/{testing-zf3Jp_RL.d.ts → testing-C71mnMxq.d.ts} +104 -0
- package/dist/testing.d.mts +2 -1
- package/dist/testing.d.ts +2 -1
- package/dist/testing.js +31 -4
- package/dist/testing.js.map +1 -1
- package/dist/testing.mjs +1 -1
- package/package.json +3 -3
- package/dist/chunk-KIJINNYU.mjs.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { __privateAdd, MetadataRepository, EmbeddingService, SearchService, JobManager, PromptService, IngestionService, MaintenanceService, ImportExportService, RetrievalService, WriteService, __privateGet, __privateSet, normalizeSourceRef, normalizeSourceHash, entitySummaryMetaKey, WikiSourceRefHashCollision, generateId, BaseRepository, emptyManifest, resolveNodeType, WikiStrictOntologyViolation, validateInlineEdges, resolveEdgeDefinitions, normalizeTitleKey, WikiTransactionError, extractSqliteCode } from './chunk-
|
|
2
|
-
export { DEFAULT_CHUNK_OVERLAP, DEFAULT_MAX_CHUNK_LENGTH, HEAL_BATCH_SIZE, 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, WikiSourceRefHashCollision, WikiStrictOntologyViolation, WikiTransactionError, chunkText, configureRandomSource, parseEmbedding, safeSlice, validateManifest } from './chunk-
|
|
3
|
-
import { appendRelatedSection, buildConceptDocument, buildLogMd, buildEntityIndexMd, buildRootIndexMd, isAllowedOkfPath, parseRootIndexMd, parseEntityIndexMd, parseConcept, splitRelatedSection, extractMarkdownLinks, parseLogMd, parseEventIdComment, appendEventIdComment } from '@equationalapplications/core-okf';
|
|
1
|
+
import { __privateAdd, MetadataRepository, EmbeddingService, SearchService, JobManager, PromptService, IngestionService, MaintenanceService, ImportExportService, RetrievalService, WriteService, __privateGet, __privateSet, normalizeSourceRef, normalizeSourceHash, entitySummaryMetaKey, WikiSourceRefHashCollision, generateId, BaseRepository, emptyManifest, resolveNodeType, WikiStrictOntologyViolation, validateInlineEdges, resolveEdgeDefinitions, normalizeTitleKey, WikiTransactionError, extractSqliteCode } from './chunk-N2NXN6PO.mjs';
|
|
2
|
+
export { DEFAULT_CHUNK_OVERLAP, DEFAULT_MAX_CHUNK_LENGTH, HEAL_BATCH_SIZE, 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, WikiSourceRefHashCollision, WikiStrictOntologyViolation, WikiTransactionError, chunkText, configureRandomSource, parseEmbedding, safeSlice, validateManifest } from './chunk-N2NXN6PO.mjs';
|
|
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
|
|
6
6
|
async function setupDatabase(db, prefix) {
|
|
@@ -24,7 +24,15 @@ async function setupDatabase(db, prefix) {
|
|
|
24
24
|
embedding_blob BLOB,
|
|
25
25
|
okf_type TEXT,
|
|
26
26
|
ontology_checked_at INTEGER,
|
|
27
|
-
heal_checked_at INTEGER
|
|
27
|
+
heal_checked_at INTEGER,
|
|
28
|
+
lifecycle_status TEXT NOT NULL DEFAULT 'stable',
|
|
29
|
+
stale_after INTEGER,
|
|
30
|
+
generated_by TEXT,
|
|
31
|
+
last_verified_at INTEGER,
|
|
32
|
+
last_verified_by TEXT,
|
|
33
|
+
okf_sources TEXT,
|
|
34
|
+
okf_verified TEXT,
|
|
35
|
+
okf_usage_window TEXT
|
|
28
36
|
);
|
|
29
37
|
|
|
30
38
|
CREATE INDEX IF NOT EXISTS ${prefix}entries_entity_idx ON ${prefix}entries(entity_id);
|
|
@@ -60,7 +68,15 @@ async function setupDatabase(db, prefix) {
|
|
|
60
68
|
updated_at INTEGER NOT NULL,
|
|
61
69
|
resolved_at INTEGER,
|
|
62
70
|
deleted_at INTEGER,
|
|
63
|
-
okf_type TEXT
|
|
71
|
+
okf_type TEXT,
|
|
72
|
+
lifecycle_status TEXT NOT NULL DEFAULT 'stable',
|
|
73
|
+
stale_after INTEGER,
|
|
74
|
+
generated_by TEXT,
|
|
75
|
+
last_verified_at INTEGER,
|
|
76
|
+
last_verified_by TEXT,
|
|
77
|
+
okf_sources TEXT,
|
|
78
|
+
okf_verified TEXT,
|
|
79
|
+
okf_usage_window TEXT
|
|
64
80
|
);
|
|
65
81
|
|
|
66
82
|
CREATE INDEX IF NOT EXISTS ${prefix}tasks_entity_idx ON ${prefix}tasks(entity_id, status);
|
|
@@ -122,6 +138,24 @@ async function setupDatabase(db, prefix) {
|
|
|
122
138
|
CREATE INDEX IF NOT EXISTS ${prefix}outbox_created_at
|
|
123
139
|
ON ${prefix}outbox (created_at);
|
|
124
140
|
`);
|
|
141
|
+
await createOkfV02IndexesIfColumnsExist(db, prefix);
|
|
142
|
+
}
|
|
143
|
+
async function createOkfV02IndexesIfColumnsExist(db, prefix) {
|
|
144
|
+
for (const table of ["entries", "tasks"]) {
|
|
145
|
+
const cols = await db.getAllAsync(
|
|
146
|
+
`PRAGMA table_info(${prefix}${table})`
|
|
147
|
+
);
|
|
148
|
+
const colNames = new Set(cols.map((c) => c.name));
|
|
149
|
+
if (colNames.has("lifecycle_status")) {
|
|
150
|
+
await db.execAsync(`CREATE INDEX IF NOT EXISTS ${prefix}${table}_lifecycle_status_idx ON ${prefix}${table}(lifecycle_status);`);
|
|
151
|
+
}
|
|
152
|
+
if (colNames.has("stale_after")) {
|
|
153
|
+
await db.execAsync(`CREATE INDEX IF NOT EXISTS ${prefix}${table}_stale_after_idx ON ${prefix}${table}(stale_after);`);
|
|
154
|
+
}
|
|
155
|
+
if (colNames.has("last_verified_at")) {
|
|
156
|
+
await db.execAsync(`CREATE INDEX IF NOT EXISTS ${prefix}${table}_last_verified_at_idx ON ${prefix}${table}(last_verified_at);`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
125
159
|
}
|
|
126
160
|
|
|
127
161
|
// src/db/serializedAdapter.ts
|
|
@@ -351,6 +385,39 @@ var MIGRATIONS = [
|
|
|
351
385
|
WHERE rn = 1;`
|
|
352
386
|
);
|
|
353
387
|
}
|
|
388
|
+
},
|
|
389
|
+
{
|
|
390
|
+
version: 10,
|
|
391
|
+
description: "OKF v0.2: add lifecycle_status, stale_after, generated_by, last_verified_*, okf_sources, okf_verified, okf_usage_window to entries and tasks",
|
|
392
|
+
run: async (db, prefix) => {
|
|
393
|
+
for (const table of ["entries", "tasks"]) {
|
|
394
|
+
const cols = await db.getAllAsync(
|
|
395
|
+
`PRAGMA table_info(${prefix}${table})`
|
|
396
|
+
);
|
|
397
|
+
const existing = new Set(cols.map((c) => c.name));
|
|
398
|
+
const adds = [
|
|
399
|
+
["lifecycle_status", `ALTER TABLE ${prefix}${table} ADD COLUMN lifecycle_status TEXT NOT NULL DEFAULT 'stable'`],
|
|
400
|
+
["stale_after", `ALTER TABLE ${prefix}${table} ADD COLUMN stale_after INTEGER`],
|
|
401
|
+
["generated_by", `ALTER TABLE ${prefix}${table} ADD COLUMN generated_by TEXT`],
|
|
402
|
+
["last_verified_at", `ALTER TABLE ${prefix}${table} ADD COLUMN last_verified_at INTEGER`],
|
|
403
|
+
["last_verified_by", `ALTER TABLE ${prefix}${table} ADD COLUMN last_verified_by TEXT`],
|
|
404
|
+
["okf_sources", `ALTER TABLE ${prefix}${table} ADD COLUMN okf_sources TEXT`],
|
|
405
|
+
["okf_verified", `ALTER TABLE ${prefix}${table} ADD COLUMN okf_verified TEXT`],
|
|
406
|
+
["okf_usage_window", `ALTER TABLE ${prefix}${table} ADD COLUMN okf_usage_window TEXT`]
|
|
407
|
+
];
|
|
408
|
+
for (const [name, sql] of adds) {
|
|
409
|
+
if (!existing.has(name)) await db.execAsync(sql);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
await db.execAsync(`
|
|
413
|
+
CREATE INDEX IF NOT EXISTS ${prefix}entries_lifecycle_status_idx ON ${prefix}entries(lifecycle_status);
|
|
414
|
+
CREATE INDEX IF NOT EXISTS ${prefix}entries_stale_after_idx ON ${prefix}entries(stale_after);
|
|
415
|
+
CREATE INDEX IF NOT EXISTS ${prefix}entries_last_verified_at_idx ON ${prefix}entries(last_verified_at);
|
|
416
|
+
CREATE INDEX IF NOT EXISTS ${prefix}tasks_lifecycle_status_idx ON ${prefix}tasks(lifecycle_status);
|
|
417
|
+
CREATE INDEX IF NOT EXISTS ${prefix}tasks_stale_after_idx ON ${prefix}tasks(stale_after);
|
|
418
|
+
CREATE INDEX IF NOT EXISTS ${prefix}tasks_last_verified_at_idx ON ${prefix}tasks(last_verified_at);
|
|
419
|
+
`);
|
|
420
|
+
}
|
|
354
421
|
}
|
|
355
422
|
];
|
|
356
423
|
for (let i = 1; i < MIGRATIONS.length; i++) {
|
|
@@ -362,7 +429,29 @@ for (let i = 1; i < MIGRATIONS.length; i++) {
|
|
|
362
429
|
}
|
|
363
430
|
var CURRENT_SCHEMA_VERSION = MIGRATIONS.length > 0 ? MIGRATIONS[MIGRATIONS.length - 1].version : 0;
|
|
364
431
|
|
|
365
|
-
// src/repositories/
|
|
432
|
+
// src/repositories/rowMappers.ts
|
|
433
|
+
function parseJsonArray(s, fallback) {
|
|
434
|
+
if (Array.isArray(s)) return s;
|
|
435
|
+
if (typeof s === "string" && s.length > 0) {
|
|
436
|
+
try {
|
|
437
|
+
const p = JSON.parse(s);
|
|
438
|
+
if (Array.isArray(p)) return p;
|
|
439
|
+
} catch {
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
return fallback;
|
|
443
|
+
}
|
|
444
|
+
function parseJsonObject(s, fallback = null) {
|
|
445
|
+
if (s && typeof s === "object" && !Array.isArray(s)) return s;
|
|
446
|
+
if (typeof s === "string" && s.length > 0) {
|
|
447
|
+
try {
|
|
448
|
+
const p = JSON.parse(s);
|
|
449
|
+
if (p && typeof p === "object" && !Array.isArray(p)) return p;
|
|
450
|
+
} catch {
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
return fallback;
|
|
454
|
+
}
|
|
366
455
|
function mapRowToFact(row) {
|
|
367
456
|
const tags = (() => {
|
|
368
457
|
if (Array.isArray(row.tags)) return row.tags;
|
|
@@ -373,6 +462,9 @@ function mapRowToFact(row) {
|
|
|
373
462
|
}
|
|
374
463
|
return [];
|
|
375
464
|
})();
|
|
465
|
+
const okf_verified = parseJsonArray(row.okf_verified, []);
|
|
466
|
+
const now = Date.now();
|
|
467
|
+
const staleAfterRaw = row.stale_after != null ? Number(row.stale_after) : null;
|
|
376
468
|
return {
|
|
377
469
|
id: row.id,
|
|
378
470
|
entity_id: row.entity_id,
|
|
@@ -388,7 +480,19 @@ function mapRowToFact(row) {
|
|
|
388
480
|
last_accessed_at: row.last_accessed_at === null || row.last_accessed_at === void 0 ? null : Number(row.last_accessed_at),
|
|
389
481
|
deleted_at: row.deleted_at != null ? Number(row.deleted_at) : null,
|
|
390
482
|
access_count: Number(row.access_count ?? 0),
|
|
391
|
-
okf_type: row.okf_type ?? null
|
|
483
|
+
okf_type: row.okf_type ?? null,
|
|
484
|
+
// OKF v0.2
|
|
485
|
+
lifecycle_status: row.lifecycle_status ?? "stable",
|
|
486
|
+
stale_after: staleAfterRaw,
|
|
487
|
+
generated_by: row.generated_by ?? null,
|
|
488
|
+
okf_sources: parseJsonArray(row.okf_sources, []),
|
|
489
|
+
okf_verified,
|
|
490
|
+
okf_usage_window: parseJsonObject(row.okf_usage_window),
|
|
491
|
+
last_verified_at: row.last_verified_at != null ? Number(row.last_verified_at) : null,
|
|
492
|
+
last_verified_by: row.last_verified_by ?? null,
|
|
493
|
+
// Spec §2.7 + §5.3: hydrate so read() consumers don't re-call the helpers.
|
|
494
|
+
isStale: isStaleAfter(staleAfterRaw, now),
|
|
495
|
+
trustTier: deriveTrustTier(okf_verified)
|
|
392
496
|
};
|
|
393
497
|
}
|
|
394
498
|
function normalizeEmbeddingBlobValue(blob) {
|
|
@@ -444,6 +548,21 @@ var EntryRepository = class extends BaseRepository {
|
|
|
444
548
|
* Upsert a WikiFact. Nullable fields set to null when fact value is null.
|
|
445
549
|
* Returns { changes, lastInsertRowId }.
|
|
446
550
|
* `tx` is REQUIRED to ensure atomic outbox staging.
|
|
551
|
+
*
|
|
552
|
+
* OKF metadata discipline (spec §2.5 + DAO checklist §8): content writes
|
|
553
|
+
* MUST NOT silently overwrite OKF metadata that a caller didn't supply. The
|
|
554
|
+
* previous CASE WHEN IS NULL guards around `lifecycle_status`, etc. were
|
|
555
|
+
* dead because the bind always supplied a non-null value (notably
|
|
556
|
+
* `fact.lifecycle_status ?? 'stable'`), so excluded.X was never NULL and
|
|
557
|
+
* an UPDATE rewrote an existing `deprecated` row to `stable`. We now omit
|
|
558
|
+
* these columns from the SET clause entirely — SQLite's ON CONFLICT UPDATE
|
|
559
|
+
* leaves untouched columns at their existing row value, which is exactly
|
|
560
|
+
* the preservation semantics we want for content writes. Callers that
|
|
561
|
+
* intentionally want to overwrite OKF metadata should use
|
|
562
|
+
* {@link upsertForImport} (always-replace semantics for that path).
|
|
563
|
+
* New-row INSERTs still bind a value so the column is populated; if the
|
|
564
|
+
* caller supplied nothing, the schema DEFAULT 'stable' applies when the
|
|
565
|
+
* bind is bound as 'stable' explicitly here.
|
|
447
566
|
*/
|
|
448
567
|
async upsert(fact, tx) {
|
|
449
568
|
const executor = this.getExecutor(tx);
|
|
@@ -451,7 +570,10 @@ var EntryRepository = class extends BaseRepository {
|
|
|
451
570
|
const tagsJson = JSON.stringify(fact.tags);
|
|
452
571
|
const embeddingBlob = this.normalizeEmbeddingBlob(fact.embedding_blob);
|
|
453
572
|
const existingRow = await executor.getFirstAsync(
|
|
454
|
-
`SELECT id
|
|
573
|
+
`SELECT id, lifecycle_status, stale_after, generated_by,
|
|
574
|
+
okf_sources, okf_verified, okf_usage_window,
|
|
575
|
+
last_verified_at, last_verified_by
|
|
576
|
+
FROM ${this.prefix}entries WHERE id = ?`,
|
|
455
577
|
[fact.id]
|
|
456
578
|
);
|
|
457
579
|
const operation = fact.deleted_at ? "DELETE" : existingRow ? "UPDATE" : "INSERT";
|
|
@@ -459,8 +581,10 @@ var EntryRepository = class extends BaseRepository {
|
|
|
459
581
|
`INSERT INTO ${this.prefix}entries (
|
|
460
582
|
id, entity_id, title, body, tags, confidence, source_type,
|
|
461
583
|
source_hash, source_ref, created_at, updated_at, last_accessed_at, access_count,
|
|
462
|
-
deleted_at, embedding_blob, embedding, okf_type
|
|
463
|
-
|
|
584
|
+
deleted_at, embedding_blob, embedding, okf_type,
|
|
585
|
+
lifecycle_status, stale_after, generated_by, last_verified_at, last_verified_by,
|
|
586
|
+
okf_sources, okf_verified, okf_usage_window
|
|
587
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
464
588
|
ON CONFLICT(id) DO UPDATE SET
|
|
465
589
|
entity_id = excluded.entity_id,
|
|
466
590
|
title = excluded.title,
|
|
@@ -494,15 +618,36 @@ var EntryRepository = class extends BaseRepository {
|
|
|
494
618
|
fact.deleted_at ?? null,
|
|
495
619
|
embeddingBlob ?? null,
|
|
496
620
|
null,
|
|
497
|
-
fact.okf_type ?? null
|
|
621
|
+
fact.okf_type ?? null,
|
|
622
|
+
// OKF v0.2 metadata — bound for INSERT only; ON CONFLICT UPDATE
|
|
623
|
+
// preserves the existing row values (these columns are intentionally
|
|
624
|
+
// absent from the SET clause above).
|
|
625
|
+
fact.lifecycle_status ?? "stable",
|
|
626
|
+
fact.stale_after ?? null,
|
|
627
|
+
fact.generated_by ?? null,
|
|
628
|
+
fact.last_verified_at ?? null,
|
|
629
|
+
fact.last_verified_by ?? null,
|
|
630
|
+
fact.okf_sources ? JSON.stringify(fact.okf_sources) : null,
|
|
631
|
+
fact.okf_verified ? JSON.stringify(fact.okf_verified) : null,
|
|
632
|
+
fact.okf_usage_window ? JSON.stringify(fact.okf_usage_window) : null
|
|
498
633
|
]
|
|
499
634
|
);
|
|
635
|
+
const persistedOkf = existingRow ? {
|
|
636
|
+
lifecycle_status: existingRow.lifecycle_status ?? "stable",
|
|
637
|
+
stale_after: existingRow.stale_after != null ? Number(existingRow.stale_after) : null,
|
|
638
|
+
generated_by: existingRow.generated_by ?? null,
|
|
639
|
+
okf_sources: parseJsonArray(existingRow.okf_sources, []),
|
|
640
|
+
okf_verified: parseJsonArray(existingRow.okf_verified, []),
|
|
641
|
+
okf_usage_window: parseJsonObject(existingRow.okf_usage_window),
|
|
642
|
+
last_verified_at: existingRow.last_verified_at != null ? Number(existingRow.last_verified_at) : null,
|
|
643
|
+
last_verified_by: existingRow.last_verified_by ?? null
|
|
644
|
+
} : null;
|
|
500
645
|
await this.outbox.push({
|
|
501
646
|
entityId: fact.entity_id,
|
|
502
647
|
tableName: "entries",
|
|
503
648
|
recordId: fact.id,
|
|
504
649
|
operation,
|
|
505
|
-
payload: fact
|
|
650
|
+
payload: persistedOkf ? { ...fact, ...persistedOkf } : fact
|
|
506
651
|
}, tx);
|
|
507
652
|
return result;
|
|
508
653
|
}
|
|
@@ -563,8 +708,10 @@ var EntryRepository = class extends BaseRepository {
|
|
|
563
708
|
`INSERT INTO ${this.prefix}entries (
|
|
564
709
|
id, entity_id, title, body, tags, confidence, source_type,
|
|
565
710
|
source_hash, source_ref, created_at, updated_at, last_accessed_at, access_count,
|
|
566
|
-
deleted_at, embedding_blob, embedding, okf_type
|
|
567
|
-
|
|
711
|
+
deleted_at, embedding_blob, embedding, okf_type,
|
|
712
|
+
lifecycle_status, stale_after, generated_by, last_verified_at, last_verified_by,
|
|
713
|
+
okf_sources, okf_verified, okf_usage_window
|
|
714
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
568
715
|
ON CONFLICT(id) DO UPDATE SET
|
|
569
716
|
entity_id = excluded.entity_id,
|
|
570
717
|
title = excluded.title,
|
|
@@ -581,7 +728,15 @@ var EntryRepository = class extends BaseRepository {
|
|
|
581
728
|
deleted_at = excluded.deleted_at,
|
|
582
729
|
embedding_blob = excluded.embedding_blob,
|
|
583
730
|
embedding = NULL,
|
|
584
|
-
okf_type = excluded.okf_type
|
|
731
|
+
okf_type = excluded.okf_type,
|
|
732
|
+
lifecycle_status = excluded.lifecycle_status,
|
|
733
|
+
stale_after = excluded.stale_after,
|
|
734
|
+
generated_by = excluded.generated_by,
|
|
735
|
+
last_verified_at = excluded.last_verified_at,
|
|
736
|
+
last_verified_by = excluded.last_verified_by,
|
|
737
|
+
okf_sources = excluded.okf_sources,
|
|
738
|
+
okf_verified = excluded.okf_verified,
|
|
739
|
+
okf_usage_window = excluded.okf_usage_window`,
|
|
585
740
|
[
|
|
586
741
|
fact.id,
|
|
587
742
|
fact.entity_id,
|
|
@@ -599,7 +754,16 @@ var EntryRepository = class extends BaseRepository {
|
|
|
599
754
|
fact.deleted_at ?? null,
|
|
600
755
|
embeddingBlob ?? null,
|
|
601
756
|
null,
|
|
602
|
-
fact.okf_type ?? null
|
|
757
|
+
fact.okf_type ?? null,
|
|
758
|
+
// Same NOT NULL DEFAULT note as in upsert above.
|
|
759
|
+
fact.lifecycle_status ?? "stable",
|
|
760
|
+
fact.stale_after ?? null,
|
|
761
|
+
fact.generated_by ?? null,
|
|
762
|
+
fact.last_verified_at ?? null,
|
|
763
|
+
fact.last_verified_by ?? null,
|
|
764
|
+
fact.okf_sources ? JSON.stringify(fact.okf_sources) : null,
|
|
765
|
+
fact.okf_verified ? JSON.stringify(fact.okf_verified) : null,
|
|
766
|
+
fact.okf_usage_window ? JSON.stringify(fact.okf_usage_window) : null
|
|
603
767
|
]
|
|
604
768
|
);
|
|
605
769
|
return result;
|
|
@@ -1527,9 +1691,10 @@ var SourceRefIndexRepository = class extends BaseRepository {
|
|
|
1527
1691
|
return row?.source_ref ?? null;
|
|
1528
1692
|
}
|
|
1529
1693
|
};
|
|
1530
|
-
|
|
1531
|
-
// src/repositories/TaskRepository.ts
|
|
1532
1694
|
function mapRowToTask(row) {
|
|
1695
|
+
const okf_verified = parseJsonArray(row.okf_verified, []);
|
|
1696
|
+
const now = Date.now();
|
|
1697
|
+
const staleAfterRaw = row.stale_after != null ? Number(row.stale_after) : null;
|
|
1533
1698
|
return {
|
|
1534
1699
|
id: row.id,
|
|
1535
1700
|
entity_id: row.entity_id,
|
|
@@ -1540,7 +1705,19 @@ function mapRowToTask(row) {
|
|
|
1540
1705
|
updated_at: Number(row.updated_at),
|
|
1541
1706
|
resolved_at: row.resolved_at != null ? Number(row.resolved_at) : null,
|
|
1542
1707
|
deleted_at: row.deleted_at != null ? Number(row.deleted_at) : null,
|
|
1543
|
-
okf_type: row.okf_type ?? null
|
|
1708
|
+
okf_type: row.okf_type ?? null,
|
|
1709
|
+
// OKF v0.2
|
|
1710
|
+
lifecycle_status: row.lifecycle_status ?? "stable",
|
|
1711
|
+
stale_after: staleAfterRaw,
|
|
1712
|
+
generated_by: row.generated_by ?? null,
|
|
1713
|
+
okf_sources: parseJsonArray(row.okf_sources, []),
|
|
1714
|
+
okf_verified,
|
|
1715
|
+
okf_usage_window: parseJsonObject(row.okf_usage_window),
|
|
1716
|
+
last_verified_at: row.last_verified_at != null ? Number(row.last_verified_at) : null,
|
|
1717
|
+
last_verified_by: row.last_verified_by ?? null,
|
|
1718
|
+
// Spec §2.7 + §5.3: hydrate so read() consumers don't re-call the helpers.
|
|
1719
|
+
isStale: isStaleAfter(staleAfterRaw, now),
|
|
1720
|
+
trustTier: deriveTrustTier(okf_verified)
|
|
1544
1721
|
};
|
|
1545
1722
|
}
|
|
1546
1723
|
var TaskRepository = class extends BaseRepository {
|
|
@@ -1590,20 +1767,31 @@ var TaskRepository = class extends BaseRepository {
|
|
|
1590
1767
|
* Uses ON CONFLICT(id) DO UPDATE (not INSERT OR REPLACE).
|
|
1591
1768
|
* Stages an outbox entry in the same transaction.
|
|
1592
1769
|
* `tx` is REQUIRED.
|
|
1770
|
+
*
|
|
1771
|
+
* OKF metadata (lifecycle_status, sources, etc.) is intentionally excluded
|
|
1772
|
+
* from the SET clause so content writes preserve any existing row values.
|
|
1773
|
+
* See {@link EntryRepository.upsert} for the rationale — the same applies
|
|
1774
|
+
* symmetrically to tasks (spec §2.5). Callers that need to overwrite OKF
|
|
1775
|
+
* metadata should use {@link upsertForImport}.
|
|
1593
1776
|
*/
|
|
1594
1777
|
async upsert(task, tx, updatedAt) {
|
|
1595
1778
|
const executor = this.getExecutor(tx);
|
|
1596
1779
|
const now = Number.isFinite(updatedAt) ? updatedAt : Date.now();
|
|
1597
1780
|
const existingRow = await executor.getFirstAsync(
|
|
1598
|
-
`SELECT id
|
|
1781
|
+
`SELECT id, lifecycle_status, stale_after, generated_by,
|
|
1782
|
+
okf_sources, okf_verified, okf_usage_window,
|
|
1783
|
+
last_verified_at, last_verified_by
|
|
1784
|
+
FROM ${this.prefix}tasks WHERE id = ?`,
|
|
1599
1785
|
[task.id]
|
|
1600
1786
|
);
|
|
1601
1787
|
const operation = task.deleted_at != null ? "DELETE" : existingRow ? "UPDATE" : "INSERT";
|
|
1602
1788
|
await executor.runAsync(
|
|
1603
1789
|
`INSERT INTO ${this.prefix}tasks (
|
|
1604
1790
|
id, entity_id, description, status, priority,
|
|
1605
|
-
created_at, updated_at, resolved_at, deleted_at
|
|
1606
|
-
|
|
1791
|
+
created_at, updated_at, resolved_at, deleted_at,
|
|
1792
|
+
lifecycle_status, stale_after, generated_by, last_verified_at, last_verified_by,
|
|
1793
|
+
okf_sources, okf_verified, okf_usage_window
|
|
1794
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1607
1795
|
ON CONFLICT(id) DO UPDATE SET
|
|
1608
1796
|
entity_id = excluded.entity_id,
|
|
1609
1797
|
description = excluded.description,
|
|
@@ -1622,16 +1810,37 @@ var TaskRepository = class extends BaseRepository {
|
|
|
1622
1810
|
now,
|
|
1623
1811
|
// updated_at set by repo or import override
|
|
1624
1812
|
task.resolved_at ?? null,
|
|
1625
|
-
task.deleted_at ?? null
|
|
1813
|
+
task.deleted_at ?? null,
|
|
1814
|
+
// OKF v0.2 metadata — bound for INSERT only; ON CONFLICT UPDATE
|
|
1815
|
+
// preserves the existing row values (these columns are intentionally
|
|
1816
|
+
// absent from the SET clause above).
|
|
1817
|
+
task.lifecycle_status ?? "stable",
|
|
1818
|
+
task.stale_after ?? null,
|
|
1819
|
+
task.generated_by ?? null,
|
|
1820
|
+
task.last_verified_at ?? null,
|
|
1821
|
+
task.last_verified_by ?? null,
|
|
1822
|
+
task.okf_sources ? JSON.stringify(task.okf_sources) : null,
|
|
1823
|
+
task.okf_verified ? JSON.stringify(task.okf_verified) : null,
|
|
1824
|
+
task.okf_usage_window ? JSON.stringify(task.okf_usage_window) : null
|
|
1626
1825
|
]
|
|
1627
1826
|
);
|
|
1827
|
+
const persistedOkf = existingRow ? {
|
|
1828
|
+
lifecycle_status: existingRow.lifecycle_status ?? "stable",
|
|
1829
|
+
stale_after: existingRow.stale_after != null ? Number(existingRow.stale_after) : null,
|
|
1830
|
+
generated_by: existingRow.generated_by ?? null,
|
|
1831
|
+
okf_sources: parseJsonArray(existingRow.okf_sources, []),
|
|
1832
|
+
okf_verified: parseJsonArray(existingRow.okf_verified, []),
|
|
1833
|
+
okf_usage_window: parseJsonObject(existingRow.okf_usage_window),
|
|
1834
|
+
last_verified_at: existingRow.last_verified_at != null ? Number(existingRow.last_verified_at) : null,
|
|
1835
|
+
last_verified_by: existingRow.last_verified_by ?? null
|
|
1836
|
+
} : null;
|
|
1628
1837
|
await this.outbox.push(
|
|
1629
1838
|
{
|
|
1630
1839
|
entityId: task.entity_id,
|
|
1631
1840
|
tableName: "tasks",
|
|
1632
1841
|
recordId: task.id,
|
|
1633
1842
|
operation,
|
|
1634
|
-
payload: task
|
|
1843
|
+
payload: persistedOkf ? { ...task, ...persistedOkf } : task
|
|
1635
1844
|
},
|
|
1636
1845
|
tx
|
|
1637
1846
|
);
|
|
@@ -1642,8 +1851,10 @@ var TaskRepository = class extends BaseRepository {
|
|
|
1642
1851
|
await executor.runAsync(
|
|
1643
1852
|
`INSERT INTO ${this.prefix}tasks (
|
|
1644
1853
|
id, entity_id, description, status, priority,
|
|
1645
|
-
created_at, updated_at, resolved_at, deleted_at, okf_type
|
|
1646
|
-
|
|
1854
|
+
created_at, updated_at, resolved_at, deleted_at, okf_type,
|
|
1855
|
+
lifecycle_status, stale_after, generated_by, last_verified_at, last_verified_by,
|
|
1856
|
+
okf_sources, okf_verified, okf_usage_window
|
|
1857
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1647
1858
|
ON CONFLICT(id) DO UPDATE SET
|
|
1648
1859
|
entity_id = excluded.entity_id,
|
|
1649
1860
|
description = excluded.description,
|
|
@@ -1652,7 +1863,15 @@ var TaskRepository = class extends BaseRepository {
|
|
|
1652
1863
|
updated_at = excluded.updated_at,
|
|
1653
1864
|
resolved_at = excluded.resolved_at,
|
|
1654
1865
|
deleted_at = excluded.deleted_at,
|
|
1655
|
-
okf_type = excluded.okf_type
|
|
1866
|
+
okf_type = excluded.okf_type,
|
|
1867
|
+
lifecycle_status = excluded.lifecycle_status,
|
|
1868
|
+
stale_after = excluded.stale_after,
|
|
1869
|
+
generated_by = excluded.generated_by,
|
|
1870
|
+
last_verified_at = excluded.last_verified_at,
|
|
1871
|
+
last_verified_by = excluded.last_verified_by,
|
|
1872
|
+
okf_sources = excluded.okf_sources,
|
|
1873
|
+
okf_verified = excluded.okf_verified,
|
|
1874
|
+
okf_usage_window = excluded.okf_usage_window`,
|
|
1656
1875
|
[
|
|
1657
1876
|
task.id,
|
|
1658
1877
|
task.entity_id,
|
|
@@ -1663,7 +1882,15 @@ var TaskRepository = class extends BaseRepository {
|
|
|
1663
1882
|
now,
|
|
1664
1883
|
task.resolved_at ?? null,
|
|
1665
1884
|
task.deleted_at ?? null,
|
|
1666
|
-
task.okf_type ?? null
|
|
1885
|
+
task.okf_type ?? null,
|
|
1886
|
+
task.lifecycle_status ?? "stable",
|
|
1887
|
+
task.stale_after ?? null,
|
|
1888
|
+
task.generated_by ?? null,
|
|
1889
|
+
task.last_verified_at ?? null,
|
|
1890
|
+
task.last_verified_by ?? null,
|
|
1891
|
+
task.okf_sources ? JSON.stringify(task.okf_sources) : null,
|
|
1892
|
+
task.okf_verified ? JSON.stringify(task.okf_verified) : null,
|
|
1893
|
+
task.okf_usage_window ? JSON.stringify(task.okf_usage_window) : null
|
|
1667
1894
|
]
|
|
1668
1895
|
);
|
|
1669
1896
|
}
|
|
@@ -2239,6 +2466,164 @@ var GraphTraversalService = class {
|
|
|
2239
2466
|
return { nodes, edges: filteredEdges };
|
|
2240
2467
|
}
|
|
2241
2468
|
};
|
|
2469
|
+
var OkfTrustWritesRepository = class {
|
|
2470
|
+
constructor(db, prefix) {
|
|
2471
|
+
this.db = db;
|
|
2472
|
+
this.prefix = prefix;
|
|
2473
|
+
}
|
|
2474
|
+
getExecutor(tx) {
|
|
2475
|
+
return tx ?? this.db;
|
|
2476
|
+
}
|
|
2477
|
+
/**
|
|
2478
|
+
* Append one or more verification events to a fact's `okf_verified` JSON
|
|
2479
|
+
* array, atomically, in a single SQL statement.
|
|
2480
|
+
*
|
|
2481
|
+
* The implementation uses json_each over the existing array UNION ALL'd
|
|
2482
|
+
* with json_each over the new events wrapped in json_group_array — a single
|
|
2483
|
+
* UPDATE expression. SQLite serializes this against concurrent writers on
|
|
2484
|
+
* the same row, so two callers appending simultaneously cannot lose each
|
|
2485
|
+
* other's events (the previous implementation serialized only the supplied
|
|
2486
|
+
* array and overwrote, dropping any pre-existing history).
|
|
2487
|
+
*
|
|
2488
|
+
* Sets the `last_verified_*` convenience columns from the latest event in
|
|
2489
|
+
* the union (computed in code; see {@link latestVerified}).
|
|
2490
|
+
*/
|
|
2491
|
+
async writeOkfTrust(entryId, entityId, verified, tx) {
|
|
2492
|
+
const executor = this.getExecutor(tx);
|
|
2493
|
+
if (verified.length === 0) return;
|
|
2494
|
+
const latest = latestVerified(verified, Date.now());
|
|
2495
|
+
const newEventsJson = JSON.stringify(verified);
|
|
2496
|
+
await executor.runAsync(
|
|
2497
|
+
`UPDATE ${this.prefix}entries
|
|
2498
|
+
SET okf_verified = COALESCE(
|
|
2499
|
+
(SELECT json_group_array(json_object('by', s.by_val, 'at', s.at_val))
|
|
2500
|
+
FROM (
|
|
2501
|
+
SELECT json_extract(value, '$.by') AS by_val,
|
|
2502
|
+
json_extract(value, '$.at') AS at_val
|
|
2503
|
+
FROM json_each(okf_verified)
|
|
2504
|
+
UNION ALL
|
|
2505
|
+
SELECT json_extract(value, '$.by'),
|
|
2506
|
+
json_extract(value, '$.at')
|
|
2507
|
+
FROM json_each(?)
|
|
2508
|
+
) AS s
|
|
2509
|
+
ORDER BY s.at_val),
|
|
2510
|
+
'[]'
|
|
2511
|
+
),
|
|
2512
|
+
last_verified_by = ?,
|
|
2513
|
+
last_verified_at = ?
|
|
2514
|
+
WHERE id = ? AND entity_id = ?`,
|
|
2515
|
+
[newEventsJson, latest?.by ?? null, latest?.at ?? null, entryId, entityId]
|
|
2516
|
+
);
|
|
2517
|
+
}
|
|
2518
|
+
/** Replace the `sources` list (provenance). */
|
|
2519
|
+
async writeOkfSources(entryId, entityId, sources, tx) {
|
|
2520
|
+
const executor = this.getExecutor(tx);
|
|
2521
|
+
const json = formatSourcesJson(sources);
|
|
2522
|
+
await executor.runAsync(
|
|
2523
|
+
`UPDATE ${this.prefix}entries
|
|
2524
|
+
SET okf_sources = ?
|
|
2525
|
+
WHERE id = ? AND entity_id = ?`,
|
|
2526
|
+
[json, entryId, entityId]
|
|
2527
|
+
);
|
|
2528
|
+
}
|
|
2529
|
+
/** Set the OKF v0.2 lifecycle status. */
|
|
2530
|
+
async setLifecycleStatus(entryId, entityId, status, tx) {
|
|
2531
|
+
const executor = this.getExecutor(tx);
|
|
2532
|
+
await executor.runAsync(
|
|
2533
|
+
`UPDATE ${this.prefix}entries
|
|
2534
|
+
SET lifecycle_status = ?
|
|
2535
|
+
WHERE id = ? AND entity_id = ?`,
|
|
2536
|
+
[status, entryId, entityId]
|
|
2537
|
+
);
|
|
2538
|
+
}
|
|
2539
|
+
/** Set the absolute cutoff (epoch ms) for staleness, or NULL to clear. */
|
|
2540
|
+
async setStaleAfter(entryId, entityId, date, tx) {
|
|
2541
|
+
const executor = this.getExecutor(tx);
|
|
2542
|
+
await executor.runAsync(
|
|
2543
|
+
`UPDATE ${this.prefix}entries
|
|
2544
|
+
SET stale_after = ?
|
|
2545
|
+
WHERE id = ? AND entity_id = ?`,
|
|
2546
|
+
[date, entryId, entityId]
|
|
2547
|
+
);
|
|
2548
|
+
}
|
|
2549
|
+
/** Set the producer / verifier actor string. Rare. */
|
|
2550
|
+
async setGeneratedBy(entryId, entityId, actor, tx) {
|
|
2551
|
+
const executor = this.getExecutor(tx);
|
|
2552
|
+
await executor.runAsync(
|
|
2553
|
+
`UPDATE ${this.prefix}entries
|
|
2554
|
+
SET generated_by = ?
|
|
2555
|
+
WHERE id = ? AND entity_id = ?`,
|
|
2556
|
+
[actor, entryId, entityId]
|
|
2557
|
+
);
|
|
2558
|
+
}
|
|
2559
|
+
// The same five methods exist for tasks, with `tasks` instead of `entries`.
|
|
2560
|
+
// Tasks are symmetric per spec §2.5.
|
|
2561
|
+
/** Atomic append, task variant — mirrors {@link writeOkfTrust}. */
|
|
2562
|
+
async writeOkfTrustTask(taskId, entityId, verified, tx) {
|
|
2563
|
+
const executor = this.getExecutor(tx);
|
|
2564
|
+
if (verified.length === 0) return;
|
|
2565
|
+
const latest = latestVerified(verified, Date.now());
|
|
2566
|
+
const newEventsJson = JSON.stringify(verified);
|
|
2567
|
+
await executor.runAsync(
|
|
2568
|
+
`UPDATE ${this.prefix}tasks
|
|
2569
|
+
SET okf_verified = COALESCE(
|
|
2570
|
+
(SELECT json_group_array(json_object('by', s.by_val, 'at', s.at_val))
|
|
2571
|
+
FROM (
|
|
2572
|
+
SELECT json_extract(value, '$.by') AS by_val,
|
|
2573
|
+
json_extract(value, '$.at') AS at_val
|
|
2574
|
+
FROM json_each(okf_verified)
|
|
2575
|
+
UNION ALL
|
|
2576
|
+
SELECT json_extract(value, '$.by'),
|
|
2577
|
+
json_extract(value, '$.at')
|
|
2578
|
+
FROM json_each(?)
|
|
2579
|
+
) AS s
|
|
2580
|
+
ORDER BY s.at_val),
|
|
2581
|
+
'[]'
|
|
2582
|
+
),
|
|
2583
|
+
last_verified_by = ?,
|
|
2584
|
+
last_verified_at = ?
|
|
2585
|
+
WHERE id = ? AND entity_id = ?`,
|
|
2586
|
+
[newEventsJson, latest?.by ?? null, latest?.at ?? null, taskId, entityId]
|
|
2587
|
+
);
|
|
2588
|
+
}
|
|
2589
|
+
async writeOkfSourcesTask(taskId, entityId, sources, tx) {
|
|
2590
|
+
const executor = this.getExecutor(tx);
|
|
2591
|
+
const json = formatSourcesJson(sources);
|
|
2592
|
+
await executor.runAsync(
|
|
2593
|
+
`UPDATE ${this.prefix}tasks
|
|
2594
|
+
SET okf_sources = ?
|
|
2595
|
+
WHERE id = ? AND entity_id = ?`,
|
|
2596
|
+
[json, taskId, entityId]
|
|
2597
|
+
);
|
|
2598
|
+
}
|
|
2599
|
+
async setLifecycleStatusTask(taskId, entityId, status, tx) {
|
|
2600
|
+
const executor = this.getExecutor(tx);
|
|
2601
|
+
await executor.runAsync(
|
|
2602
|
+
`UPDATE ${this.prefix}tasks
|
|
2603
|
+
SET lifecycle_status = ?
|
|
2604
|
+
WHERE id = ? AND entity_id = ?`,
|
|
2605
|
+
[status, taskId, entityId]
|
|
2606
|
+
);
|
|
2607
|
+
}
|
|
2608
|
+
async setStaleAfterTask(taskId, entityId, date, tx) {
|
|
2609
|
+
const executor = this.getExecutor(tx);
|
|
2610
|
+
await executor.runAsync(
|
|
2611
|
+
`UPDATE ${this.prefix}tasks
|
|
2612
|
+
SET stale_after = ?
|
|
2613
|
+
WHERE id = ? AND entity_id = ?`,
|
|
2614
|
+
[date, taskId, entityId]
|
|
2615
|
+
);
|
|
2616
|
+
}
|
|
2617
|
+
async setGeneratedByTask(taskId, entityId, actor, tx) {
|
|
2618
|
+
const executor = this.getExecutor(tx);
|
|
2619
|
+
await executor.runAsync(
|
|
2620
|
+
`UPDATE ${this.prefix}tasks
|
|
2621
|
+
SET generated_by = ?
|
|
2622
|
+
WHERE id = ? AND entity_id = ?`,
|
|
2623
|
+
[actor, taskId, entityId]
|
|
2624
|
+
);
|
|
2625
|
+
}
|
|
2626
|
+
};
|
|
2242
2627
|
|
|
2243
2628
|
// src/WikiMemory.ts
|
|
2244
2629
|
var TABLE_PREFIX_PATTERN = /^[A-Za-z][A-Za-z0-9_]{0,30}_$/;
|
|
@@ -2333,6 +2718,7 @@ var WikiMemory = class {
|
|
|
2333
2718
|
this.entryRepo,
|
|
2334
2719
|
this.options.config ?? {}
|
|
2335
2720
|
);
|
|
2721
|
+
this.okfTrustWrites = new OkfTrustWritesRepository(this.db, this.prefix);
|
|
2336
2722
|
}
|
|
2337
2723
|
/**
|
|
2338
2724
|
* Explicit escape hatch for test suites: typed access to composed services for mocks/spies.
|
|
@@ -2700,6 +3086,42 @@ var WikiMemory = class {
|
|
|
2700
3086
|
);
|
|
2701
3087
|
this.ontologyService.invalidateCache(entityId);
|
|
2702
3088
|
}
|
|
3089
|
+
/** Append a verification event to a fact. Does NOT touch `updated_at`. */
|
|
3090
|
+
async writeOkfTrust(entryId, entityId, verified) {
|
|
3091
|
+
return this.okfTrustWrites.writeOkfTrust(entryId, entityId, verified);
|
|
3092
|
+
}
|
|
3093
|
+
/** Replace a fact's source list. Does NOT touch `updated_at`. */
|
|
3094
|
+
async writeOkfSources(entryId, entityId, sources) {
|
|
3095
|
+
return this.okfTrustWrites.writeOkfSources(entryId, entityId, sources);
|
|
3096
|
+
}
|
|
3097
|
+
/** Set a fact's OKF v0.2 lifecycle status. Does NOT touch `updated_at`. */
|
|
3098
|
+
async setLifecycleStatus(entryId, entityId, status) {
|
|
3099
|
+
return this.okfTrustWrites.setLifecycleStatus(entryId, entityId, status);
|
|
3100
|
+
}
|
|
3101
|
+
/** Set a fact's stale_after (epoch ms) or clear it. Does NOT touch `updated_at`. */
|
|
3102
|
+
async setStaleAfter(entryId, entityId, date) {
|
|
3103
|
+
return this.okfTrustWrites.setStaleAfter(entryId, entityId, date);
|
|
3104
|
+
}
|
|
3105
|
+
/** Set a fact's generated_by actor string. Does NOT touch `updated_at`. */
|
|
3106
|
+
async setGeneratedBy(entryId, entityId, actor) {
|
|
3107
|
+
return this.okfTrustWrites.setGeneratedBy(entryId, entityId, actor);
|
|
3108
|
+
}
|
|
3109
|
+
/** Task variants of the same five DAO methods. Symmetric per spec §2.5. */
|
|
3110
|
+
async writeOkfTrustTask(taskId, entityId, verified) {
|
|
3111
|
+
return this.okfTrustWrites.writeOkfTrustTask(taskId, entityId, verified);
|
|
3112
|
+
}
|
|
3113
|
+
async writeOkfSourcesTask(taskId, entityId, sources) {
|
|
3114
|
+
return this.okfTrustWrites.writeOkfSourcesTask(taskId, entityId, sources);
|
|
3115
|
+
}
|
|
3116
|
+
async setLifecycleStatusTask(taskId, entityId, status) {
|
|
3117
|
+
return this.okfTrustWrites.setLifecycleStatusTask(taskId, entityId, status);
|
|
3118
|
+
}
|
|
3119
|
+
async setStaleAfterTask(taskId, entityId, date) {
|
|
3120
|
+
return this.okfTrustWrites.setStaleAfterTask(taskId, entityId, date);
|
|
3121
|
+
}
|
|
3122
|
+
async setGeneratedByTask(taskId, entityId, actor) {
|
|
3123
|
+
return this.okfTrustWrites.setGeneratedByTask(taskId, entityId, actor);
|
|
3124
|
+
}
|
|
2703
3125
|
};
|
|
2704
3126
|
_testAccessNonTestEnvWarned = new WeakMap();
|
|
2705
3127
|
|
|
@@ -3003,13 +3425,87 @@ function formatMemoryDump(dump) {
|
|
|
3003
3425
|
files
|
|
3004
3426
|
};
|
|
3005
3427
|
}
|
|
3006
|
-
var
|
|
3007
|
-
function
|
|
3428
|
+
var DEFAULT_PROFILE = "llm-wiki/2";
|
|
3429
|
+
function isoOrFallback(updatedAt) {
|
|
3430
|
+
if (!Number.isFinite(updatedAt)) return (/* @__PURE__ */ new Date(0)).toISOString();
|
|
3431
|
+
const d = new Date(updatedAt);
|
|
3432
|
+
if (!Number.isFinite(d.getTime())) return (/* @__PURE__ */ new Date(0)).toISOString();
|
|
3433
|
+
return d.toISOString();
|
|
3434
|
+
}
|
|
3435
|
+
function isoDateOrOmit(ms) {
|
|
3436
|
+
if (ms == null || !Number.isFinite(ms)) return void 0;
|
|
3437
|
+
const d = new Date(ms);
|
|
3438
|
+
if (!Number.isFinite(d.getTime())) return void 0;
|
|
3439
|
+
return d.toISOString().slice(0, 10);
|
|
3440
|
+
}
|
|
3441
|
+
function factFrontmatterV2(f) {
|
|
3008
3442
|
return {
|
|
3009
3443
|
type: f.okf_type ?? "fact",
|
|
3010
3444
|
title: f.title,
|
|
3011
3445
|
tags: f.tags,
|
|
3012
|
-
timestamp
|
|
3446
|
+
// `timestamp` rides along deliberately even on the v0.2 path — a naive
|
|
3447
|
+
// consumer that only understands the v0.1 key still gets a readable date
|
|
3448
|
+
// without needing flow-mapping support. `generated.at` is the canonical
|
|
3449
|
+
// v0.2 field and always wins on import when both are present (spec §2.4).
|
|
3450
|
+
timestamp: isoOrFallback(f.updated_at),
|
|
3451
|
+
resource: f.source_ref ?? void 0,
|
|
3452
|
+
id: f.id,
|
|
3453
|
+
entity_id: f.entity_id,
|
|
3454
|
+
confidence: f.confidence,
|
|
3455
|
+
source_type: f.source_type,
|
|
3456
|
+
source_hash: f.source_hash,
|
|
3457
|
+
created_at: f.created_at,
|
|
3458
|
+
access_count: f.access_count,
|
|
3459
|
+
last_accessed_at: f.last_accessed_at,
|
|
3460
|
+
deleted_at: f.deleted_at,
|
|
3461
|
+
// OKF v0.2
|
|
3462
|
+
status: f.lifecycle_status ?? "stable",
|
|
3463
|
+
...isoDateOrOmit(f.stale_after) ? { stale_after: isoDateOrOmit(f.stale_after) } : {},
|
|
3464
|
+
// Omit `generated` entirely when there's no actor on record — never
|
|
3465
|
+
// fabricate a `generated_by` (mirrors the import side's rule in §4.8:
|
|
3466
|
+
// it never invents one either, so export must not manufacture one that
|
|
3467
|
+
// would then round-trip back in as if it had always been asserted).
|
|
3468
|
+
...f.generated_by ? { generated: { by: f.generated_by, at: isoOrFallback(f.updated_at) } } : {},
|
|
3469
|
+
// Omit `verified`/`sources` when empty — an emitted `[]` is not the same
|
|
3470
|
+
// shape as an absent key (spec §5.3 derives `unverified` from "no
|
|
3471
|
+
// `verified` key", not from "empty `verified` list").
|
|
3472
|
+
...f.okf_verified && f.okf_verified.length > 0 ? { verified: f.okf_verified } : {},
|
|
3473
|
+
...f.okf_sources && f.okf_sources.length > 0 ? { sources: f.okf_sources } : {},
|
|
3474
|
+
...f.okf_usage_window ? { usage_window: f.okf_usage_window } : {}
|
|
3475
|
+
};
|
|
3476
|
+
}
|
|
3477
|
+
function taskFrontmatterV2(t) {
|
|
3478
|
+
return {
|
|
3479
|
+
type: t.okf_type ?? "task",
|
|
3480
|
+
title: t.description,
|
|
3481
|
+
timestamp: isoOrFallback(t.updated_at),
|
|
3482
|
+
// see factFrontmatterV2 comment — deliberate back-compat duplication
|
|
3483
|
+
id: t.id,
|
|
3484
|
+
entity_id: t.entity_id,
|
|
3485
|
+
status: t.lifecycle_status ?? "stable",
|
|
3486
|
+
// OKF v0.2 lifecycle
|
|
3487
|
+
execution_status: t.status,
|
|
3488
|
+
// execution state under renamed key
|
|
3489
|
+
priority: t.priority,
|
|
3490
|
+
created_at: t.created_at,
|
|
3491
|
+
resolved_at: t.resolved_at,
|
|
3492
|
+
deleted_at: t.deleted_at,
|
|
3493
|
+
// OKF v0.2 — symmetric with facts (spec §2.5): stale_after applies to
|
|
3494
|
+
// tasks too (a hallucinated task needs a freshness signal as much as a
|
|
3495
|
+
// hallucinated fact does).
|
|
3496
|
+
...isoDateOrOmit(t.stale_after) ? { stale_after: isoDateOrOmit(t.stale_after) } : {},
|
|
3497
|
+
...t.generated_by ? { generated: { by: t.generated_by, at: isoOrFallback(t.updated_at) } } : {},
|
|
3498
|
+
...t.okf_verified && t.okf_verified.length > 0 ? { verified: t.okf_verified } : {},
|
|
3499
|
+
...t.okf_sources && t.okf_sources.length > 0 ? { sources: t.okf_sources } : {},
|
|
3500
|
+
...t.okf_usage_window ? { usage_window: t.okf_usage_window } : {}
|
|
3501
|
+
};
|
|
3502
|
+
}
|
|
3503
|
+
function factFrontmatterV1(f) {
|
|
3504
|
+
return {
|
|
3505
|
+
type: f.okf_type ?? "fact",
|
|
3506
|
+
title: f.title,
|
|
3507
|
+
tags: f.tags,
|
|
3508
|
+
timestamp: isoOrFallback(f.updated_at),
|
|
3013
3509
|
resource: f.source_ref ?? void 0,
|
|
3014
3510
|
id: f.id,
|
|
3015
3511
|
entity_id: f.entity_id,
|
|
@@ -3022,11 +3518,11 @@ function factFrontmatter(f) {
|
|
|
3022
3518
|
deleted_at: f.deleted_at
|
|
3023
3519
|
};
|
|
3024
3520
|
}
|
|
3025
|
-
function
|
|
3521
|
+
function taskFrontmatterV1(t) {
|
|
3026
3522
|
return {
|
|
3027
3523
|
type: t.okf_type ?? "task",
|
|
3028
3524
|
title: t.description,
|
|
3029
|
-
timestamp:
|
|
3525
|
+
timestamp: isoOrFallback(t.updated_at),
|
|
3030
3526
|
id: t.id,
|
|
3031
3527
|
entity_id: t.entity_id,
|
|
3032
3528
|
status: t.status,
|
|
@@ -3056,7 +3552,11 @@ function buildEventLogEntries(events, factIdToFilename) {
|
|
|
3056
3552
|
return { date: formatLogDate(e.created_at), text: appendEventIdComment(text, e.id) };
|
|
3057
3553
|
});
|
|
3058
3554
|
}
|
|
3059
|
-
function formatOkfBundle(dump) {
|
|
3555
|
+
function formatOkfBundle(dump, options) {
|
|
3556
|
+
const profile = options?.profile ?? DEFAULT_PROFILE;
|
|
3557
|
+
const okfVersion = profile === "llm-wiki/2" ? "0.2" : "0.1";
|
|
3558
|
+
const factFm = profile === "llm-wiki/2" ? factFrontmatterV2 : factFrontmatterV1;
|
|
3559
|
+
const taskFm = profile === "llm-wiki/2" ? taskFrontmatterV2 : taskFrontmatterV1;
|
|
3060
3560
|
const files = [];
|
|
3061
3561
|
const rootEntries = [];
|
|
3062
3562
|
for (const [entityId, bundle] of Object.entries(dump.entities)) {
|
|
@@ -3093,7 +3593,7 @@ function formatOkfBundle(dump) {
|
|
|
3093
3593
|
const factBody = appendRelatedSection(f.body, relatedLinksFor2(factPath, f.id));
|
|
3094
3594
|
files.push({
|
|
3095
3595
|
path: factPath,
|
|
3096
|
-
content: buildConceptDocument(
|
|
3596
|
+
content: buildConceptDocument(factFm(f), factBody)
|
|
3097
3597
|
});
|
|
3098
3598
|
}
|
|
3099
3599
|
const taskEntries = bundle.tasks.map((t) => ({
|
|
@@ -3105,7 +3605,7 @@ function formatOkfBundle(dump) {
|
|
|
3105
3605
|
const taskBody = appendRelatedSection("", relatedLinksFor2(taskPath, t.id));
|
|
3106
3606
|
files.push({
|
|
3107
3607
|
path: taskPath,
|
|
3108
|
-
content: buildConceptDocument(
|
|
3608
|
+
content: buildConceptDocument(taskFm(t), taskBody)
|
|
3109
3609
|
});
|
|
3110
3610
|
}
|
|
3111
3611
|
files.push({
|
|
@@ -3128,9 +3628,9 @@ function formatOkfBundle(dump) {
|
|
|
3128
3628
|
files.push({
|
|
3129
3629
|
path: "index.md",
|
|
3130
3630
|
content: buildRootIndexMd(
|
|
3131
|
-
|
|
3631
|
+
okfVersion,
|
|
3132
3632
|
rootEntries.length > 0 ? [{ heading: "Entities", entries: rootEntries }] : [],
|
|
3133
|
-
{ profile
|
|
3633
|
+
{ profile }
|
|
3134
3634
|
)
|
|
3135
3635
|
});
|
|
3136
3636
|
return { files };
|
|
@@ -3220,6 +3720,21 @@ function parseFrontmatterTimestamp(value, fallback) {
|
|
|
3220
3720
|
}
|
|
3221
3721
|
return fallback;
|
|
3222
3722
|
}
|
|
3723
|
+
function parseStaleAfterRaw(raw) {
|
|
3724
|
+
if (typeof raw !== "string") return null;
|
|
3725
|
+
const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(raw);
|
|
3726
|
+
if (!m) return null;
|
|
3727
|
+
const year = Number(m[1]);
|
|
3728
|
+
const month = Number(m[2]);
|
|
3729
|
+
const day = Number(m[3]);
|
|
3730
|
+
const ms = Date.UTC(year, month - 1, day);
|
|
3731
|
+
if (!Number.isFinite(ms)) return null;
|
|
3732
|
+
const d = new Date(ms);
|
|
3733
|
+
if (d.getUTCFullYear() !== year || d.getUTCMonth() + 1 !== month || d.getUTCDate() !== day) {
|
|
3734
|
+
return null;
|
|
3735
|
+
}
|
|
3736
|
+
return ms;
|
|
3737
|
+
}
|
|
3223
3738
|
function unescapeLogSummary(summary) {
|
|
3224
3739
|
return summary.replace(/\\\]/g, "]").replace(/\\\[/g, "[").replace(/\\\\/g, "\\");
|
|
3225
3740
|
}
|
|
@@ -3237,9 +3752,29 @@ function parseLogEntryText(text) {
|
|
|
3237
3752
|
function frontmatterToFact(entityId, id, frontmatter, body, now) {
|
|
3238
3753
|
const created_at = parseFrontmatterTimestamp(frontmatter.created_at, now);
|
|
3239
3754
|
const updated_at = parseFrontmatterTimestamp(
|
|
3240
|
-
frontmatter.
|
|
3241
|
-
parseFrontmatterTimestamp(
|
|
3755
|
+
frontmatter.generated?.at,
|
|
3756
|
+
parseFrontmatterTimestamp(
|
|
3757
|
+
frontmatter.timestamp,
|
|
3758
|
+
parseFrontmatterTimestamp(frontmatter.updated_at, now)
|
|
3759
|
+
)
|
|
3242
3760
|
);
|
|
3761
|
+
const verified = parseVerifiedFlexible(frontmatter.verified);
|
|
3762
|
+
const rawSources = frontmatter.sources;
|
|
3763
|
+
const hasSourcesKey = rawSources !== void 0;
|
|
3764
|
+
const sources = Array.isArray(rawSources) ? rawSources : [];
|
|
3765
|
+
const usageWindow = frontmatter.usage_window && typeof frontmatter.usage_window === "object" ? frontmatter.usage_window : null;
|
|
3766
|
+
const lastV = latestVerified(verified, now);
|
|
3767
|
+
const lifecycleRaw = frontmatter.status;
|
|
3768
|
+
const lifecycle_status = lifecycleRaw === "draft" || lifecycleRaw === "stable" || lifecycleRaw === "deprecated" ? lifecycleRaw : "stable";
|
|
3769
|
+
const staleAfterRaw = frontmatter.stale_after;
|
|
3770
|
+
const stale_after = parseStaleAfterRaw(staleAfterRaw);
|
|
3771
|
+
const generated_by = frontmatter.generated?.by ?? null;
|
|
3772
|
+
if (!hasSourcesKey) {
|
|
3773
|
+
const urls = parseCitationsList(body);
|
|
3774
|
+
for (const url of urls) {
|
|
3775
|
+
sources.push({ resource: url });
|
|
3776
|
+
}
|
|
3777
|
+
}
|
|
3243
3778
|
return {
|
|
3244
3779
|
id,
|
|
3245
3780
|
entity_id: entityId,
|
|
@@ -3255,26 +3790,62 @@ function frontmatterToFact(entityId, id, frontmatter, body, now) {
|
|
|
3255
3790
|
last_accessed_at: frontmatter.last_accessed_at != null ? parseFrontmatterTimestamp(frontmatter.last_accessed_at, now) : null,
|
|
3256
3791
|
access_count: typeof frontmatter.access_count === "number" ? frontmatter.access_count : 0,
|
|
3257
3792
|
deleted_at: frontmatter.deleted_at != null ? parseFrontmatterTimestamp(frontmatter.deleted_at, 0) : null,
|
|
3258
|
-
okf_type: frontmatter.type
|
|
3793
|
+
okf_type: frontmatter.type,
|
|
3794
|
+
// OKF v0.2
|
|
3795
|
+
lifecycle_status,
|
|
3796
|
+
stale_after,
|
|
3797
|
+
generated_by,
|
|
3798
|
+
okf_sources: sources,
|
|
3799
|
+
okf_verified: verified,
|
|
3800
|
+
okf_usage_window: usageWindow,
|
|
3801
|
+
last_verified_at: lastV?.at ?? null,
|
|
3802
|
+
last_verified_by: lastV?.by ?? null
|
|
3259
3803
|
};
|
|
3260
3804
|
}
|
|
3261
|
-
function frontmatterToTask(entityId, id, frontmatter, now) {
|
|
3805
|
+
function frontmatterToTask(entityId, id, frontmatter, now, usesV1StatusRule) {
|
|
3262
3806
|
const created_at = parseFrontmatterTimestamp(frontmatter.created_at, now);
|
|
3263
3807
|
const updated_at = parseFrontmatterTimestamp(
|
|
3264
|
-
frontmatter.
|
|
3265
|
-
parseFrontmatterTimestamp(
|
|
3808
|
+
frontmatter.generated?.at,
|
|
3809
|
+
parseFrontmatterTimestamp(
|
|
3810
|
+
frontmatter.timestamp,
|
|
3811
|
+
parseFrontmatterTimestamp(frontmatter.updated_at, now)
|
|
3812
|
+
)
|
|
3266
3813
|
);
|
|
3814
|
+
const verified = parseVerifiedFlexible(frontmatter.verified);
|
|
3815
|
+
const sources = Array.isArray(frontmatter.sources) ? frontmatter.sources : [];
|
|
3816
|
+
const usageWindow = frontmatter.usage_window && typeof frontmatter.usage_window === "object" ? frontmatter.usage_window : null;
|
|
3817
|
+
const lastV = latestVerified(verified, now);
|
|
3818
|
+
const lifecycleRaw = frontmatter.status;
|
|
3819
|
+
const lifecycle_status = lifecycleRaw === "draft" || lifecycleRaw === "stable" || lifecycleRaw === "deprecated" ? lifecycleRaw : "stable";
|
|
3820
|
+
const staleAfterRaw = frontmatter.stale_after;
|
|
3821
|
+
const stale_after = parseStaleAfterRaw(staleAfterRaw);
|
|
3822
|
+
const executionRaw = frontmatter.execution_status;
|
|
3823
|
+
let executionState;
|
|
3824
|
+
if (usesV1StatusRule) {
|
|
3825
|
+
executionState = TASK_STATUSES.has(String(frontmatter.status)) ? frontmatter.status : "pending";
|
|
3826
|
+
} else {
|
|
3827
|
+
executionState = TASK_STATUSES.has(String(executionRaw)) ? executionRaw : TASK_STATUSES.has(String(frontmatter.status)) ? frontmatter.status : "pending";
|
|
3828
|
+
}
|
|
3267
3829
|
return {
|
|
3268
3830
|
id,
|
|
3269
3831
|
entity_id: entityId,
|
|
3270
3832
|
description: typeof frontmatter.title === "string" ? frontmatter.title : "",
|
|
3271
|
-
status:
|
|
3833
|
+
status: executionState,
|
|
3272
3834
|
priority: typeof frontmatter.priority === "number" ? frontmatter.priority : 0,
|
|
3273
3835
|
created_at,
|
|
3274
3836
|
updated_at,
|
|
3275
3837
|
resolved_at: frontmatter.resolved_at != null ? parseFrontmatterTimestamp(frontmatter.resolved_at, now) : null,
|
|
3276
3838
|
deleted_at: frontmatter.deleted_at != null ? parseFrontmatterTimestamp(frontmatter.deleted_at, 0) : null,
|
|
3277
|
-
okf_type: frontmatter.type
|
|
3839
|
+
okf_type: frontmatter.type,
|
|
3840
|
+
// OKF v0.2
|
|
3841
|
+
lifecycle_status,
|
|
3842
|
+
stale_after,
|
|
3843
|
+
generated_by: frontmatter.generated?.by ?? null,
|
|
3844
|
+
okf_sources: sources,
|
|
3845
|
+
okf_verified: verified,
|
|
3846
|
+
okf_usage_window: usageWindow,
|
|
3847
|
+
last_verified_at: lastV?.at ?? null,
|
|
3848
|
+
last_verified_by: lastV?.by ?? null
|
|
3278
3849
|
};
|
|
3279
3850
|
}
|
|
3280
3851
|
function findLogMdPath(files) {
|
|
@@ -3321,10 +3892,17 @@ function parseOkfBundle(entityId, files, options) {
|
|
|
3321
3892
|
);
|
|
3322
3893
|
const rootIndex = allowedFiles.find((f) => f.path === "index.md");
|
|
3323
3894
|
const profileMeta = rootIndex ? parseRootIndexMd(rootIndex.content) : {};
|
|
3324
|
-
const
|
|
3895
|
+
const okfVersion = profileMeta.okf_version;
|
|
3896
|
+
const profile = profileMeta.profile;
|
|
3897
|
+
const isProfile1 = profile === "llm-wiki/1";
|
|
3898
|
+
const isProfile2 = profile === "llm-wiki/2";
|
|
3899
|
+
const isLegacyV1 = profile === void 0 && okfVersion === "0.1";
|
|
3900
|
+
const isVersionOnlyV2 = profile === void 0 && okfVersion === "0.2";
|
|
3901
|
+
const isUnknownProfileV2 = profile !== void 0 && profile !== "llm-wiki/1" && profile !== "llm-wiki/2" && okfVersion === "0.2";
|
|
3902
|
+
const summaryEligible = isProfile1 || isProfile2 || isLegacyV1 || isVersionOnlyV2 || isUnknownProfileV2;
|
|
3325
3903
|
let entitySummary;
|
|
3326
3904
|
const entityIndex = allowedFiles.find((f) => f.path === `${entityPrefix}index.md`);
|
|
3327
|
-
if (
|
|
3905
|
+
if (summaryEligible && entityIndex) {
|
|
3328
3906
|
const summary = parseEntityIndexMd(entityIndex.content).summary;
|
|
3329
3907
|
entitySummary = summary || void 0;
|
|
3330
3908
|
}
|
|
@@ -3353,11 +3931,12 @@ function parseOkfBundle(entityId, files, options) {
|
|
|
3353
3931
|
if (route === "ignore") continue;
|
|
3354
3932
|
const resolvedId = typeof frontmatter.id === "string" && frontmatter.id ? frontmatter.id : basenameMd(file.path);
|
|
3355
3933
|
const { body: storedBody, relatedLinks } = splitRelatedSection(body);
|
|
3356
|
-
const edgeLinks = isProfile1 ? relatedLinks : [...relatedLinks, ...extractMarkdownLinks(storedBody)];
|
|
3934
|
+
const edgeLinks = isProfile1 || isLegacyV1 ? relatedLinks : [...relatedLinks, ...extractMarkdownLinks(storedBody)];
|
|
3935
|
+
const usesV1StatusRule = isProfile1 || isLegacyV1 || profile === void 0 && okfVersion === void 0;
|
|
3357
3936
|
if (route === "fact") {
|
|
3358
3937
|
facts.push(frontmatterToFact(entityId, resolvedId, frontmatter, storedBody, now));
|
|
3359
3938
|
} else {
|
|
3360
|
-
tasks.push(frontmatterToTask(entityId, resolvedId, frontmatter, now));
|
|
3939
|
+
tasks.push(frontmatterToTask(entityId, resolvedId, frontmatter, now, usesV1StatusRule));
|
|
3361
3940
|
}
|
|
3362
3941
|
const seenEdges = /* @__PURE__ */ new Set();
|
|
3363
3942
|
extractEdgesFromLinks(
|