@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/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
- var MiniSearch = require('minisearch');
4
3
  var coreOkf = require('@equationalapplications/core-okf');
4
+ var MiniSearch = require('minisearch');
5
5
 
6
6
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
7
7
 
@@ -37,7 +37,15 @@ async function setupDatabase(db, prefix) {
37
37
  embedding_blob BLOB,
38
38
  okf_type TEXT,
39
39
  ontology_checked_at INTEGER,
40
- heal_checked_at INTEGER
40
+ heal_checked_at INTEGER,
41
+ lifecycle_status TEXT NOT NULL DEFAULT 'stable',
42
+ stale_after INTEGER,
43
+ generated_by TEXT,
44
+ last_verified_at INTEGER,
45
+ last_verified_by TEXT,
46
+ okf_sources TEXT,
47
+ okf_verified TEXT,
48
+ okf_usage_window TEXT
41
49
  );
42
50
 
43
51
  CREATE INDEX IF NOT EXISTS ${prefix}entries_entity_idx ON ${prefix}entries(entity_id);
@@ -73,7 +81,15 @@ async function setupDatabase(db, prefix) {
73
81
  updated_at INTEGER NOT NULL,
74
82
  resolved_at INTEGER,
75
83
  deleted_at INTEGER,
76
- okf_type TEXT
84
+ okf_type TEXT,
85
+ lifecycle_status TEXT NOT NULL DEFAULT 'stable',
86
+ stale_after INTEGER,
87
+ generated_by TEXT,
88
+ last_verified_at INTEGER,
89
+ last_verified_by TEXT,
90
+ okf_sources TEXT,
91
+ okf_verified TEXT,
92
+ okf_usage_window TEXT
77
93
  );
78
94
 
79
95
  CREATE INDEX IF NOT EXISTS ${prefix}tasks_entity_idx ON ${prefix}tasks(entity_id, status);
@@ -135,6 +151,24 @@ async function setupDatabase(db, prefix) {
135
151
  CREATE INDEX IF NOT EXISTS ${prefix}outbox_created_at
136
152
  ON ${prefix}outbox (created_at);
137
153
  `);
154
+ await createOkfV02IndexesIfColumnsExist(db, prefix);
155
+ }
156
+ async function createOkfV02IndexesIfColumnsExist(db, prefix) {
157
+ for (const table of ["entries", "tasks"]) {
158
+ const cols = await db.getAllAsync(
159
+ `PRAGMA table_info(${prefix}${table})`
160
+ );
161
+ const colNames = new Set(cols.map((c) => c.name));
162
+ if (colNames.has("lifecycle_status")) {
163
+ await db.execAsync(`CREATE INDEX IF NOT EXISTS ${prefix}${table}_lifecycle_status_idx ON ${prefix}${table}(lifecycle_status);`);
164
+ }
165
+ if (colNames.has("stale_after")) {
166
+ await db.execAsync(`CREATE INDEX IF NOT EXISTS ${prefix}${table}_stale_after_idx ON ${prefix}${table}(stale_after);`);
167
+ }
168
+ if (colNames.has("last_verified_at")) {
169
+ await db.execAsync(`CREATE INDEX IF NOT EXISTS ${prefix}${table}_last_verified_at_idx ON ${prefix}${table}(last_verified_at);`);
170
+ }
171
+ }
138
172
  }
139
173
 
140
174
  // src/db/sqliteCodes.ts
@@ -481,6 +515,39 @@ var MIGRATIONS = [
481
515
  WHERE rn = 1;`
482
516
  );
483
517
  }
518
+ },
519
+ {
520
+ version: 10,
521
+ description: "OKF v0.2: add lifecycle_status, stale_after, generated_by, last_verified_*, okf_sources, okf_verified, okf_usage_window to entries and tasks",
522
+ run: async (db, prefix) => {
523
+ for (const table of ["entries", "tasks"]) {
524
+ const cols = await db.getAllAsync(
525
+ `PRAGMA table_info(${prefix}${table})`
526
+ );
527
+ const existing = new Set(cols.map((c) => c.name));
528
+ const adds = [
529
+ ["lifecycle_status", `ALTER TABLE ${prefix}${table} ADD COLUMN lifecycle_status TEXT NOT NULL DEFAULT 'stable'`],
530
+ ["stale_after", `ALTER TABLE ${prefix}${table} ADD COLUMN stale_after INTEGER`],
531
+ ["generated_by", `ALTER TABLE ${prefix}${table} ADD COLUMN generated_by TEXT`],
532
+ ["last_verified_at", `ALTER TABLE ${prefix}${table} ADD COLUMN last_verified_at INTEGER`],
533
+ ["last_verified_by", `ALTER TABLE ${prefix}${table} ADD COLUMN last_verified_by TEXT`],
534
+ ["okf_sources", `ALTER TABLE ${prefix}${table} ADD COLUMN okf_sources TEXT`],
535
+ ["okf_verified", `ALTER TABLE ${prefix}${table} ADD COLUMN okf_verified TEXT`],
536
+ ["okf_usage_window", `ALTER TABLE ${prefix}${table} ADD COLUMN okf_usage_window TEXT`]
537
+ ];
538
+ for (const [name, sql] of adds) {
539
+ if (!existing.has(name)) await db.execAsync(sql);
540
+ }
541
+ }
542
+ await db.execAsync(`
543
+ CREATE INDEX IF NOT EXISTS ${prefix}entries_lifecycle_status_idx ON ${prefix}entries(lifecycle_status);
544
+ CREATE INDEX IF NOT EXISTS ${prefix}entries_stale_after_idx ON ${prefix}entries(stale_after);
545
+ CREATE INDEX IF NOT EXISTS ${prefix}entries_last_verified_at_idx ON ${prefix}entries(last_verified_at);
546
+ CREATE INDEX IF NOT EXISTS ${prefix}tasks_lifecycle_status_idx ON ${prefix}tasks(lifecycle_status);
547
+ CREATE INDEX IF NOT EXISTS ${prefix}tasks_stale_after_idx ON ${prefix}tasks(stale_after);
548
+ CREATE INDEX IF NOT EXISTS ${prefix}tasks_last_verified_at_idx ON ${prefix}tasks(last_verified_at);
549
+ `);
550
+ }
484
551
  }
485
552
  ];
486
553
  for (let i = 1; i < MIGRATIONS.length; i++) {
@@ -507,7 +574,29 @@ var BaseRepository = class {
507
574
  }
508
575
  };
509
576
 
510
- // src/repositories/EntryRepository.ts
577
+ // src/repositories/rowMappers.ts
578
+ function parseJsonArray(s, fallback) {
579
+ if (Array.isArray(s)) return s;
580
+ if (typeof s === "string" && s.length > 0) {
581
+ try {
582
+ const p = JSON.parse(s);
583
+ if (Array.isArray(p)) return p;
584
+ } catch {
585
+ }
586
+ }
587
+ return fallback;
588
+ }
589
+ function parseJsonObject(s, fallback = null) {
590
+ if (s && typeof s === "object" && !Array.isArray(s)) return s;
591
+ if (typeof s === "string" && s.length > 0) {
592
+ try {
593
+ const p = JSON.parse(s);
594
+ if (p && typeof p === "object" && !Array.isArray(p)) return p;
595
+ } catch {
596
+ }
597
+ }
598
+ return fallback;
599
+ }
511
600
  function mapRowToFact(row) {
512
601
  const tags = (() => {
513
602
  if (Array.isArray(row.tags)) return row.tags;
@@ -518,6 +607,9 @@ function mapRowToFact(row) {
518
607
  }
519
608
  return [];
520
609
  })();
610
+ const okf_verified = parseJsonArray(row.okf_verified, []);
611
+ const now = Date.now();
612
+ const staleAfterRaw = row.stale_after != null ? Number(row.stale_after) : null;
521
613
  return {
522
614
  id: row.id,
523
615
  entity_id: row.entity_id,
@@ -533,7 +625,19 @@ function mapRowToFact(row) {
533
625
  last_accessed_at: row.last_accessed_at === null || row.last_accessed_at === void 0 ? null : Number(row.last_accessed_at),
534
626
  deleted_at: row.deleted_at != null ? Number(row.deleted_at) : null,
535
627
  access_count: Number(row.access_count ?? 0),
536
- okf_type: row.okf_type ?? null
628
+ okf_type: row.okf_type ?? null,
629
+ // OKF v0.2
630
+ lifecycle_status: row.lifecycle_status ?? "stable",
631
+ stale_after: staleAfterRaw,
632
+ generated_by: row.generated_by ?? null,
633
+ okf_sources: parseJsonArray(row.okf_sources, []),
634
+ okf_verified,
635
+ okf_usage_window: parseJsonObject(row.okf_usage_window),
636
+ last_verified_at: row.last_verified_at != null ? Number(row.last_verified_at) : null,
637
+ last_verified_by: row.last_verified_by ?? null,
638
+ // Spec §2.7 + §5.3: hydrate so read() consumers don't re-call the helpers.
639
+ isStale: coreOkf.isStaleAfter(staleAfterRaw, now),
640
+ trustTier: coreOkf.deriveTrustTier(okf_verified)
537
641
  };
538
642
  }
539
643
  function normalizeEmbeddingBlobValue(blob) {
@@ -589,6 +693,21 @@ var EntryRepository = class extends BaseRepository {
589
693
  * Upsert a WikiFact. Nullable fields set to null when fact value is null.
590
694
  * Returns { changes, lastInsertRowId }.
591
695
  * `tx` is REQUIRED to ensure atomic outbox staging.
696
+ *
697
+ * OKF metadata discipline (spec §2.5 + DAO checklist §8): content writes
698
+ * MUST NOT silently overwrite OKF metadata that a caller didn't supply. The
699
+ * previous CASE WHEN IS NULL guards around `lifecycle_status`, etc. were
700
+ * dead because the bind always supplied a non-null value (notably
701
+ * `fact.lifecycle_status ?? 'stable'`), so excluded.X was never NULL and
702
+ * an UPDATE rewrote an existing `deprecated` row to `stable`. We now omit
703
+ * these columns from the SET clause entirely — SQLite's ON CONFLICT UPDATE
704
+ * leaves untouched columns at their existing row value, which is exactly
705
+ * the preservation semantics we want for content writes. Callers that
706
+ * intentionally want to overwrite OKF metadata should use
707
+ * {@link upsertForImport} (always-replace semantics for that path).
708
+ * New-row INSERTs still bind a value so the column is populated; if the
709
+ * caller supplied nothing, the schema DEFAULT 'stable' applies when the
710
+ * bind is bound as 'stable' explicitly here.
592
711
  */
593
712
  async upsert(fact, tx) {
594
713
  const executor = this.getExecutor(tx);
@@ -596,7 +715,10 @@ var EntryRepository = class extends BaseRepository {
596
715
  const tagsJson = JSON.stringify(fact.tags);
597
716
  const embeddingBlob = this.normalizeEmbeddingBlob(fact.embedding_blob);
598
717
  const existingRow = await executor.getFirstAsync(
599
- `SELECT id FROM ${this.prefix}entries WHERE id = ?`,
718
+ `SELECT id, lifecycle_status, stale_after, generated_by,
719
+ okf_sources, okf_verified, okf_usage_window,
720
+ last_verified_at, last_verified_by
721
+ FROM ${this.prefix}entries WHERE id = ?`,
600
722
  [fact.id]
601
723
  );
602
724
  const operation = fact.deleted_at ? "DELETE" : existingRow ? "UPDATE" : "INSERT";
@@ -604,8 +726,10 @@ var EntryRepository = class extends BaseRepository {
604
726
  `INSERT INTO ${this.prefix}entries (
605
727
  id, entity_id, title, body, tags, confidence, source_type,
606
728
  source_hash, source_ref, created_at, updated_at, last_accessed_at, access_count,
607
- deleted_at, embedding_blob, embedding, okf_type
608
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
729
+ deleted_at, embedding_blob, embedding, okf_type,
730
+ lifecycle_status, stale_after, generated_by, last_verified_at, last_verified_by,
731
+ okf_sources, okf_verified, okf_usage_window
732
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
609
733
  ON CONFLICT(id) DO UPDATE SET
610
734
  entity_id = excluded.entity_id,
611
735
  title = excluded.title,
@@ -639,15 +763,36 @@ var EntryRepository = class extends BaseRepository {
639
763
  fact.deleted_at ?? null,
640
764
  embeddingBlob ?? null,
641
765
  null,
642
- fact.okf_type ?? null
766
+ fact.okf_type ?? null,
767
+ // OKF v0.2 metadata — bound for INSERT only; ON CONFLICT UPDATE
768
+ // preserves the existing row values (these columns are intentionally
769
+ // absent from the SET clause above).
770
+ fact.lifecycle_status ?? "stable",
771
+ fact.stale_after ?? null,
772
+ fact.generated_by ?? null,
773
+ fact.last_verified_at ?? null,
774
+ fact.last_verified_by ?? null,
775
+ fact.okf_sources ? JSON.stringify(fact.okf_sources) : null,
776
+ fact.okf_verified ? JSON.stringify(fact.okf_verified) : null,
777
+ fact.okf_usage_window ? JSON.stringify(fact.okf_usage_window) : null
643
778
  ]
644
779
  );
780
+ const persistedOkf = existingRow ? {
781
+ lifecycle_status: existingRow.lifecycle_status ?? "stable",
782
+ stale_after: existingRow.stale_after != null ? Number(existingRow.stale_after) : null,
783
+ generated_by: existingRow.generated_by ?? null,
784
+ okf_sources: parseJsonArray(existingRow.okf_sources, []),
785
+ okf_verified: parseJsonArray(existingRow.okf_verified, []),
786
+ okf_usage_window: parseJsonObject(existingRow.okf_usage_window),
787
+ last_verified_at: existingRow.last_verified_at != null ? Number(existingRow.last_verified_at) : null,
788
+ last_verified_by: existingRow.last_verified_by ?? null
789
+ } : null;
645
790
  await this.outbox.push({
646
791
  entityId: fact.entity_id,
647
792
  tableName: "entries",
648
793
  recordId: fact.id,
649
794
  operation,
650
- payload: fact
795
+ payload: persistedOkf ? { ...fact, ...persistedOkf } : fact
651
796
  }, tx);
652
797
  return result;
653
798
  }
@@ -708,8 +853,10 @@ var EntryRepository = class extends BaseRepository {
708
853
  `INSERT INTO ${this.prefix}entries (
709
854
  id, entity_id, title, body, tags, confidence, source_type,
710
855
  source_hash, source_ref, created_at, updated_at, last_accessed_at, access_count,
711
- deleted_at, embedding_blob, embedding, okf_type
712
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
856
+ deleted_at, embedding_blob, embedding, okf_type,
857
+ lifecycle_status, stale_after, generated_by, last_verified_at, last_verified_by,
858
+ okf_sources, okf_verified, okf_usage_window
859
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
713
860
  ON CONFLICT(id) DO UPDATE SET
714
861
  entity_id = excluded.entity_id,
715
862
  title = excluded.title,
@@ -726,7 +873,15 @@ var EntryRepository = class extends BaseRepository {
726
873
  deleted_at = excluded.deleted_at,
727
874
  embedding_blob = excluded.embedding_blob,
728
875
  embedding = NULL,
729
- okf_type = excluded.okf_type`,
876
+ okf_type = excluded.okf_type,
877
+ lifecycle_status = excluded.lifecycle_status,
878
+ stale_after = excluded.stale_after,
879
+ generated_by = excluded.generated_by,
880
+ last_verified_at = excluded.last_verified_at,
881
+ last_verified_by = excluded.last_verified_by,
882
+ okf_sources = excluded.okf_sources,
883
+ okf_verified = excluded.okf_verified,
884
+ okf_usage_window = excluded.okf_usage_window`,
730
885
  [
731
886
  fact.id,
732
887
  fact.entity_id,
@@ -744,7 +899,16 @@ var EntryRepository = class extends BaseRepository {
744
899
  fact.deleted_at ?? null,
745
900
  embeddingBlob ?? null,
746
901
  null,
747
- fact.okf_type ?? null
902
+ fact.okf_type ?? null,
903
+ // Same NOT NULL DEFAULT note as in upsert above.
904
+ fact.lifecycle_status ?? "stable",
905
+ fact.stale_after ?? null,
906
+ fact.generated_by ?? null,
907
+ fact.last_verified_at ?? null,
908
+ fact.last_verified_by ?? null,
909
+ fact.okf_sources ? JSON.stringify(fact.okf_sources) : null,
910
+ fact.okf_verified ? JSON.stringify(fact.okf_verified) : null,
911
+ fact.okf_usage_window ? JSON.stringify(fact.okf_usage_window) : null
748
912
  ]
749
913
  );
750
914
  return result;
@@ -1696,9 +1860,10 @@ var SourceRefIndexRepository = class extends BaseRepository {
1696
1860
  return row?.source_ref ?? null;
1697
1861
  }
1698
1862
  };
1699
-
1700
- // src/repositories/TaskRepository.ts
1701
1863
  function mapRowToTask(row) {
1864
+ const okf_verified = parseJsonArray(row.okf_verified, []);
1865
+ const now = Date.now();
1866
+ const staleAfterRaw = row.stale_after != null ? Number(row.stale_after) : null;
1702
1867
  return {
1703
1868
  id: row.id,
1704
1869
  entity_id: row.entity_id,
@@ -1709,7 +1874,19 @@ function mapRowToTask(row) {
1709
1874
  updated_at: Number(row.updated_at),
1710
1875
  resolved_at: row.resolved_at != null ? Number(row.resolved_at) : null,
1711
1876
  deleted_at: row.deleted_at != null ? Number(row.deleted_at) : null,
1712
- okf_type: row.okf_type ?? null
1877
+ okf_type: row.okf_type ?? null,
1878
+ // OKF v0.2
1879
+ lifecycle_status: row.lifecycle_status ?? "stable",
1880
+ stale_after: staleAfterRaw,
1881
+ generated_by: row.generated_by ?? null,
1882
+ okf_sources: parseJsonArray(row.okf_sources, []),
1883
+ okf_verified,
1884
+ okf_usage_window: parseJsonObject(row.okf_usage_window),
1885
+ last_verified_at: row.last_verified_at != null ? Number(row.last_verified_at) : null,
1886
+ last_verified_by: row.last_verified_by ?? null,
1887
+ // Spec §2.7 + §5.3: hydrate so read() consumers don't re-call the helpers.
1888
+ isStale: coreOkf.isStaleAfter(staleAfterRaw, now),
1889
+ trustTier: coreOkf.deriveTrustTier(okf_verified)
1713
1890
  };
1714
1891
  }
1715
1892
  var TaskRepository = class extends BaseRepository {
@@ -1759,20 +1936,31 @@ var TaskRepository = class extends BaseRepository {
1759
1936
  * Uses ON CONFLICT(id) DO UPDATE (not INSERT OR REPLACE).
1760
1937
  * Stages an outbox entry in the same transaction.
1761
1938
  * `tx` is REQUIRED.
1939
+ *
1940
+ * OKF metadata (lifecycle_status, sources, etc.) is intentionally excluded
1941
+ * from the SET clause so content writes preserve any existing row values.
1942
+ * See {@link EntryRepository.upsert} for the rationale — the same applies
1943
+ * symmetrically to tasks (spec §2.5). Callers that need to overwrite OKF
1944
+ * metadata should use {@link upsertForImport}.
1762
1945
  */
1763
1946
  async upsert(task, tx, updatedAt) {
1764
1947
  const executor = this.getExecutor(tx);
1765
1948
  const now = Number.isFinite(updatedAt) ? updatedAt : Date.now();
1766
1949
  const existingRow = await executor.getFirstAsync(
1767
- `SELECT id FROM ${this.prefix}tasks WHERE id = ?`,
1950
+ `SELECT id, lifecycle_status, stale_after, generated_by,
1951
+ okf_sources, okf_verified, okf_usage_window,
1952
+ last_verified_at, last_verified_by
1953
+ FROM ${this.prefix}tasks WHERE id = ?`,
1768
1954
  [task.id]
1769
1955
  );
1770
1956
  const operation = task.deleted_at != null ? "DELETE" : existingRow ? "UPDATE" : "INSERT";
1771
1957
  await executor.runAsync(
1772
1958
  `INSERT INTO ${this.prefix}tasks (
1773
1959
  id, entity_id, description, status, priority,
1774
- created_at, updated_at, resolved_at, deleted_at
1775
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
1960
+ created_at, updated_at, resolved_at, deleted_at,
1961
+ lifecycle_status, stale_after, generated_by, last_verified_at, last_verified_by,
1962
+ okf_sources, okf_verified, okf_usage_window
1963
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1776
1964
  ON CONFLICT(id) DO UPDATE SET
1777
1965
  entity_id = excluded.entity_id,
1778
1966
  description = excluded.description,
@@ -1791,16 +1979,37 @@ var TaskRepository = class extends BaseRepository {
1791
1979
  now,
1792
1980
  // updated_at set by repo or import override
1793
1981
  task.resolved_at ?? null,
1794
- task.deleted_at ?? null
1982
+ task.deleted_at ?? null,
1983
+ // OKF v0.2 metadata — bound for INSERT only; ON CONFLICT UPDATE
1984
+ // preserves the existing row values (these columns are intentionally
1985
+ // absent from the SET clause above).
1986
+ task.lifecycle_status ?? "stable",
1987
+ task.stale_after ?? null,
1988
+ task.generated_by ?? null,
1989
+ task.last_verified_at ?? null,
1990
+ task.last_verified_by ?? null,
1991
+ task.okf_sources ? JSON.stringify(task.okf_sources) : null,
1992
+ task.okf_verified ? JSON.stringify(task.okf_verified) : null,
1993
+ task.okf_usage_window ? JSON.stringify(task.okf_usage_window) : null
1795
1994
  ]
1796
1995
  );
1996
+ const persistedOkf = existingRow ? {
1997
+ lifecycle_status: existingRow.lifecycle_status ?? "stable",
1998
+ stale_after: existingRow.stale_after != null ? Number(existingRow.stale_after) : null,
1999
+ generated_by: existingRow.generated_by ?? null,
2000
+ okf_sources: parseJsonArray(existingRow.okf_sources, []),
2001
+ okf_verified: parseJsonArray(existingRow.okf_verified, []),
2002
+ okf_usage_window: parseJsonObject(existingRow.okf_usage_window),
2003
+ last_verified_at: existingRow.last_verified_at != null ? Number(existingRow.last_verified_at) : null,
2004
+ last_verified_by: existingRow.last_verified_by ?? null
2005
+ } : null;
1797
2006
  await this.outbox.push(
1798
2007
  {
1799
2008
  entityId: task.entity_id,
1800
2009
  tableName: "tasks",
1801
2010
  recordId: task.id,
1802
2011
  operation,
1803
- payload: task
2012
+ payload: persistedOkf ? { ...task, ...persistedOkf } : task
1804
2013
  },
1805
2014
  tx
1806
2015
  );
@@ -1811,8 +2020,10 @@ var TaskRepository = class extends BaseRepository {
1811
2020
  await executor.runAsync(
1812
2021
  `INSERT INTO ${this.prefix}tasks (
1813
2022
  id, entity_id, description, status, priority,
1814
- created_at, updated_at, resolved_at, deleted_at, okf_type
1815
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2023
+ created_at, updated_at, resolved_at, deleted_at, okf_type,
2024
+ lifecycle_status, stale_after, generated_by, last_verified_at, last_verified_by,
2025
+ okf_sources, okf_verified, okf_usage_window
2026
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1816
2027
  ON CONFLICT(id) DO UPDATE SET
1817
2028
  entity_id = excluded.entity_id,
1818
2029
  description = excluded.description,
@@ -1821,7 +2032,15 @@ var TaskRepository = class extends BaseRepository {
1821
2032
  updated_at = excluded.updated_at,
1822
2033
  resolved_at = excluded.resolved_at,
1823
2034
  deleted_at = excluded.deleted_at,
1824
- okf_type = excluded.okf_type`,
2035
+ okf_type = excluded.okf_type,
2036
+ lifecycle_status = excluded.lifecycle_status,
2037
+ stale_after = excluded.stale_after,
2038
+ generated_by = excluded.generated_by,
2039
+ last_verified_at = excluded.last_verified_at,
2040
+ last_verified_by = excluded.last_verified_by,
2041
+ okf_sources = excluded.okf_sources,
2042
+ okf_verified = excluded.okf_verified,
2043
+ okf_usage_window = excluded.okf_usage_window`,
1825
2044
  [
1826
2045
  task.id,
1827
2046
  task.entity_id,
@@ -1832,7 +2051,15 @@ var TaskRepository = class extends BaseRepository {
1832
2051
  now,
1833
2052
  task.resolved_at ?? null,
1834
2053
  task.deleted_at ?? null,
1835
- task.okf_type ?? null
2054
+ task.okf_type ?? null,
2055
+ task.lifecycle_status ?? "stable",
2056
+ task.stale_after ?? null,
2057
+ task.generated_by ?? null,
2058
+ task.last_verified_at ?? null,
2059
+ task.last_verified_by ?? null,
2060
+ task.okf_sources ? JSON.stringify(task.okf_sources) : null,
2061
+ task.okf_verified ? JSON.stringify(task.okf_verified) : null,
2062
+ task.okf_usage_window ? JSON.stringify(task.okf_usage_window) : null
1836
2063
  ]
1837
2064
  );
1838
2065
  }
@@ -4760,8 +4987,6 @@ var MaintenanceService = class {
4760
4987
  return sanitizeRankerError(err, this.options.sanitizeRankerErrors);
4761
4988
  }
4762
4989
  };
4763
-
4764
- // src/services/ImportExportService.ts
4765
4990
  var MAX_EMBEDDING_BLOB_BYTES = 32 * 1024;
4766
4991
  var IMPORT_TITLE_MAX = 500;
4767
4992
  var IMPORT_BODY_MAX = 8e3;
@@ -4981,7 +5206,23 @@ var ImportExportService = class {
4981
5206
  access_count: fact.access_count,
4982
5207
  deleted_at: fact.deleted_at,
4983
5208
  embedding_blob: blobData ?? void 0,
4984
- okf_type: fact.okf_type ?? null
5209
+ okf_type: fact.okf_type ?? null,
5210
+ // OKF v0.2 — forwarded as-is; parseOkfBundle / the caller is responsible for
5211
+ // populating these per spec, importDump does not derive or validate them.
5212
+ lifecycle_status: fact.lifecycle_status ?? "stable",
5213
+ stale_after: fact.stale_after ?? null,
5214
+ generated_by: fact.generated_by ?? null,
5215
+ okf_sources: fact.okf_sources ?? [],
5216
+ okf_verified: fact.okf_verified ?? [],
5217
+ okf_usage_window: fact.okf_usage_window ?? null,
5218
+ // last_verified_at/_by are DERIVED mirrors of `okf_verified` per spec
5219
+ // §5.3. Recomputing here (instead of trusting the payload field) closes
5220
+ // the invariant that a raw importDump cannot carry a
5221
+ // last_verified_by that contradicts its own okf_verified list.
5222
+ ...(() => {
5223
+ const latest = coreOkf.latestVerified(fact.okf_verified ?? [], Date.now());
5224
+ return { last_verified_at: latest?.at ?? null, last_verified_by: latest?.by ?? null };
5225
+ })()
4985
5226
  };
4986
5227
  await this.entryRepo.upsertForImport(factObj, tx);
4987
5228
  if (blobData != null) {
@@ -5031,7 +5272,19 @@ var ImportExportService = class {
5031
5272
  updated_at: safeUpdatedAt,
5032
5273
  resolved_at: task.resolved_at,
5033
5274
  deleted_at: task.deleted_at,
5034
- okf_type: task.okf_type ?? null
5275
+ okf_type: task.okf_type ?? null,
5276
+ // OKF v0.2
5277
+ lifecycle_status: task.lifecycle_status ?? "stable",
5278
+ stale_after: task.stale_after ?? null,
5279
+ generated_by: task.generated_by ?? null,
5280
+ okf_sources: task.okf_sources ?? [],
5281
+ okf_verified: task.okf_verified ?? [],
5282
+ okf_usage_window: task.okf_usage_window ?? null,
5283
+ // Derived mirrors — see fact branch above; same invariant.
5284
+ ...(() => {
5285
+ const latest = coreOkf.latestVerified(task.okf_verified ?? [], Date.now());
5286
+ return { last_verified_at: latest?.at ?? null, last_verified_by: latest?.by ?? null };
5287
+ })()
5035
5288
  },
5036
5289
  tx,
5037
5290
  safeUpdatedAt
@@ -6157,6 +6410,164 @@ var GraphTraversalService = class {
6157
6410
  return { nodes, edges: filteredEdges };
6158
6411
  }
6159
6412
  };
6413
+ var OkfTrustWritesRepository = class {
6414
+ constructor(db, prefix) {
6415
+ this.db = db;
6416
+ this.prefix = prefix;
6417
+ }
6418
+ getExecutor(tx) {
6419
+ return tx ?? this.db;
6420
+ }
6421
+ /**
6422
+ * Append one or more verification events to a fact's `okf_verified` JSON
6423
+ * array, atomically, in a single SQL statement.
6424
+ *
6425
+ * The implementation uses json_each over the existing array UNION ALL'd
6426
+ * with json_each over the new events wrapped in json_group_array — a single
6427
+ * UPDATE expression. SQLite serializes this against concurrent writers on
6428
+ * the same row, so two callers appending simultaneously cannot lose each
6429
+ * other's events (the previous implementation serialized only the supplied
6430
+ * array and overwrote, dropping any pre-existing history).
6431
+ *
6432
+ * Sets the `last_verified_*` convenience columns from the latest event in
6433
+ * the union (computed in code; see {@link latestVerified}).
6434
+ */
6435
+ async writeOkfTrust(entryId, entityId, verified, tx) {
6436
+ const executor = this.getExecutor(tx);
6437
+ if (verified.length === 0) return;
6438
+ const latest = coreOkf.latestVerified(verified, Date.now());
6439
+ const newEventsJson = JSON.stringify(verified);
6440
+ await executor.runAsync(
6441
+ `UPDATE ${this.prefix}entries
6442
+ SET okf_verified = COALESCE(
6443
+ (SELECT json_group_array(json_object('by', s.by_val, 'at', s.at_val))
6444
+ FROM (
6445
+ SELECT json_extract(value, '$.by') AS by_val,
6446
+ json_extract(value, '$.at') AS at_val
6447
+ FROM json_each(okf_verified)
6448
+ UNION ALL
6449
+ SELECT json_extract(value, '$.by'),
6450
+ json_extract(value, '$.at')
6451
+ FROM json_each(?)
6452
+ ) AS s
6453
+ ORDER BY s.at_val),
6454
+ '[]'
6455
+ ),
6456
+ last_verified_by = ?,
6457
+ last_verified_at = ?
6458
+ WHERE id = ? AND entity_id = ?`,
6459
+ [newEventsJson, latest?.by ?? null, latest?.at ?? null, entryId, entityId]
6460
+ );
6461
+ }
6462
+ /** Replace the `sources` list (provenance). */
6463
+ async writeOkfSources(entryId, entityId, sources, tx) {
6464
+ const executor = this.getExecutor(tx);
6465
+ const json = coreOkf.formatSourcesJson(sources);
6466
+ await executor.runAsync(
6467
+ `UPDATE ${this.prefix}entries
6468
+ SET okf_sources = ?
6469
+ WHERE id = ? AND entity_id = ?`,
6470
+ [json, entryId, entityId]
6471
+ );
6472
+ }
6473
+ /** Set the OKF v0.2 lifecycle status. */
6474
+ async setLifecycleStatus(entryId, entityId, status, tx) {
6475
+ const executor = this.getExecutor(tx);
6476
+ await executor.runAsync(
6477
+ `UPDATE ${this.prefix}entries
6478
+ SET lifecycle_status = ?
6479
+ WHERE id = ? AND entity_id = ?`,
6480
+ [status, entryId, entityId]
6481
+ );
6482
+ }
6483
+ /** Set the absolute cutoff (epoch ms) for staleness, or NULL to clear. */
6484
+ async setStaleAfter(entryId, entityId, date, tx) {
6485
+ const executor = this.getExecutor(tx);
6486
+ await executor.runAsync(
6487
+ `UPDATE ${this.prefix}entries
6488
+ SET stale_after = ?
6489
+ WHERE id = ? AND entity_id = ?`,
6490
+ [date, entryId, entityId]
6491
+ );
6492
+ }
6493
+ /** Set the producer / verifier actor string. Rare. */
6494
+ async setGeneratedBy(entryId, entityId, actor, tx) {
6495
+ const executor = this.getExecutor(tx);
6496
+ await executor.runAsync(
6497
+ `UPDATE ${this.prefix}entries
6498
+ SET generated_by = ?
6499
+ WHERE id = ? AND entity_id = ?`,
6500
+ [actor, entryId, entityId]
6501
+ );
6502
+ }
6503
+ // The same five methods exist for tasks, with `tasks` instead of `entries`.
6504
+ // Tasks are symmetric per spec §2.5.
6505
+ /** Atomic append, task variant — mirrors {@link writeOkfTrust}. */
6506
+ async writeOkfTrustTask(taskId, entityId, verified, tx) {
6507
+ const executor = this.getExecutor(tx);
6508
+ if (verified.length === 0) return;
6509
+ const latest = coreOkf.latestVerified(verified, Date.now());
6510
+ const newEventsJson = JSON.stringify(verified);
6511
+ await executor.runAsync(
6512
+ `UPDATE ${this.prefix}tasks
6513
+ SET okf_verified = COALESCE(
6514
+ (SELECT json_group_array(json_object('by', s.by_val, 'at', s.at_val))
6515
+ FROM (
6516
+ SELECT json_extract(value, '$.by') AS by_val,
6517
+ json_extract(value, '$.at') AS at_val
6518
+ FROM json_each(okf_verified)
6519
+ UNION ALL
6520
+ SELECT json_extract(value, '$.by'),
6521
+ json_extract(value, '$.at')
6522
+ FROM json_each(?)
6523
+ ) AS s
6524
+ ORDER BY s.at_val),
6525
+ '[]'
6526
+ ),
6527
+ last_verified_by = ?,
6528
+ last_verified_at = ?
6529
+ WHERE id = ? AND entity_id = ?`,
6530
+ [newEventsJson, latest?.by ?? null, latest?.at ?? null, taskId, entityId]
6531
+ );
6532
+ }
6533
+ async writeOkfSourcesTask(taskId, entityId, sources, tx) {
6534
+ const executor = this.getExecutor(tx);
6535
+ const json = coreOkf.formatSourcesJson(sources);
6536
+ await executor.runAsync(
6537
+ `UPDATE ${this.prefix}tasks
6538
+ SET okf_sources = ?
6539
+ WHERE id = ? AND entity_id = ?`,
6540
+ [json, taskId, entityId]
6541
+ );
6542
+ }
6543
+ async setLifecycleStatusTask(taskId, entityId, status, tx) {
6544
+ const executor = this.getExecutor(tx);
6545
+ await executor.runAsync(
6546
+ `UPDATE ${this.prefix}tasks
6547
+ SET lifecycle_status = ?
6548
+ WHERE id = ? AND entity_id = ?`,
6549
+ [status, taskId, entityId]
6550
+ );
6551
+ }
6552
+ async setStaleAfterTask(taskId, entityId, date, tx) {
6553
+ const executor = this.getExecutor(tx);
6554
+ await executor.runAsync(
6555
+ `UPDATE ${this.prefix}tasks
6556
+ SET stale_after = ?
6557
+ WHERE id = ? AND entity_id = ?`,
6558
+ [date, taskId, entityId]
6559
+ );
6560
+ }
6561
+ async setGeneratedByTask(taskId, entityId, actor, tx) {
6562
+ const executor = this.getExecutor(tx);
6563
+ await executor.runAsync(
6564
+ `UPDATE ${this.prefix}tasks
6565
+ SET generated_by = ?
6566
+ WHERE id = ? AND entity_id = ?`,
6567
+ [actor, taskId, entityId]
6568
+ );
6569
+ }
6570
+ };
6160
6571
 
6161
6572
  // src/WikiMemory.ts
6162
6573
  var TABLE_PREFIX_PATTERN = /^[A-Za-z][A-Za-z0-9_]{0,30}_$/;
@@ -6251,6 +6662,7 @@ var WikiMemory = class {
6251
6662
  this.entryRepo,
6252
6663
  this.options.config ?? {}
6253
6664
  );
6665
+ this.okfTrustWrites = new OkfTrustWritesRepository(this.db, this.prefix);
6254
6666
  }
6255
6667
  /**
6256
6668
  * Explicit escape hatch for test suites: typed access to composed services for mocks/spies.
@@ -6618,6 +7030,42 @@ var WikiMemory = class {
6618
7030
  );
6619
7031
  this.ontologyService.invalidateCache(entityId);
6620
7032
  }
7033
+ /** Append a verification event to a fact. Does NOT touch `updated_at`. */
7034
+ async writeOkfTrust(entryId, entityId, verified) {
7035
+ return this.okfTrustWrites.writeOkfTrust(entryId, entityId, verified);
7036
+ }
7037
+ /** Replace a fact's source list. Does NOT touch `updated_at`. */
7038
+ async writeOkfSources(entryId, entityId, sources) {
7039
+ return this.okfTrustWrites.writeOkfSources(entryId, entityId, sources);
7040
+ }
7041
+ /** Set a fact's OKF v0.2 lifecycle status. Does NOT touch `updated_at`. */
7042
+ async setLifecycleStatus(entryId, entityId, status) {
7043
+ return this.okfTrustWrites.setLifecycleStatus(entryId, entityId, status);
7044
+ }
7045
+ /** Set a fact's stale_after (epoch ms) or clear it. Does NOT touch `updated_at`. */
7046
+ async setStaleAfter(entryId, entityId, date) {
7047
+ return this.okfTrustWrites.setStaleAfter(entryId, entityId, date);
7048
+ }
7049
+ /** Set a fact's generated_by actor string. Does NOT touch `updated_at`. */
7050
+ async setGeneratedBy(entryId, entityId, actor) {
7051
+ return this.okfTrustWrites.setGeneratedBy(entryId, entityId, actor);
7052
+ }
7053
+ /** Task variants of the same five DAO methods. Symmetric per spec §2.5. */
7054
+ async writeOkfTrustTask(taskId, entityId, verified) {
7055
+ return this.okfTrustWrites.writeOkfTrustTask(taskId, entityId, verified);
7056
+ }
7057
+ async writeOkfSourcesTask(taskId, entityId, sources) {
7058
+ return this.okfTrustWrites.writeOkfSourcesTask(taskId, entityId, sources);
7059
+ }
7060
+ async setLifecycleStatusTask(taskId, entityId, status) {
7061
+ return this.okfTrustWrites.setLifecycleStatusTask(taskId, entityId, status);
7062
+ }
7063
+ async setStaleAfterTask(taskId, entityId, date) {
7064
+ return this.okfTrustWrites.setStaleAfterTask(taskId, entityId, date);
7065
+ }
7066
+ async setGeneratedByTask(taskId, entityId, actor) {
7067
+ return this.okfTrustWrites.setGeneratedByTask(taskId, entityId, actor);
7068
+ }
6621
7069
  };
6622
7070
  _testAccessNonTestEnvWarned = new WeakMap();
6623
7071
 
@@ -6921,13 +7369,87 @@ function formatMemoryDump(dump) {
6921
7369
  files
6922
7370
  };
6923
7371
  }
6924
- var LLM_WIKI_PROFILE = "llm-wiki/1";
6925
- function factFrontmatter(f) {
7372
+ var DEFAULT_PROFILE = "llm-wiki/2";
7373
+ function isoOrFallback(updatedAt) {
7374
+ if (!Number.isFinite(updatedAt)) return (/* @__PURE__ */ new Date(0)).toISOString();
7375
+ const d = new Date(updatedAt);
7376
+ if (!Number.isFinite(d.getTime())) return (/* @__PURE__ */ new Date(0)).toISOString();
7377
+ return d.toISOString();
7378
+ }
7379
+ function isoDateOrOmit(ms) {
7380
+ if (ms == null || !Number.isFinite(ms)) return void 0;
7381
+ const d = new Date(ms);
7382
+ if (!Number.isFinite(d.getTime())) return void 0;
7383
+ return d.toISOString().slice(0, 10);
7384
+ }
7385
+ function factFrontmatterV2(f) {
6926
7386
  return {
6927
7387
  type: f.okf_type ?? "fact",
6928
7388
  title: f.title,
6929
7389
  tags: f.tags,
6930
- timestamp: new Date(f.updated_at).toISOString(),
7390
+ // `timestamp` rides along deliberately even on the v0.2 path — a naive
7391
+ // consumer that only understands the v0.1 key still gets a readable date
7392
+ // without needing flow-mapping support. `generated.at` is the canonical
7393
+ // v0.2 field and always wins on import when both are present (spec §2.4).
7394
+ timestamp: isoOrFallback(f.updated_at),
7395
+ resource: f.source_ref ?? void 0,
7396
+ id: f.id,
7397
+ entity_id: f.entity_id,
7398
+ confidence: f.confidence,
7399
+ source_type: f.source_type,
7400
+ source_hash: f.source_hash,
7401
+ created_at: f.created_at,
7402
+ access_count: f.access_count,
7403
+ last_accessed_at: f.last_accessed_at,
7404
+ deleted_at: f.deleted_at,
7405
+ // OKF v0.2
7406
+ status: f.lifecycle_status ?? "stable",
7407
+ ...isoDateOrOmit(f.stale_after) ? { stale_after: isoDateOrOmit(f.stale_after) } : {},
7408
+ // Omit `generated` entirely when there's no actor on record — never
7409
+ // fabricate a `generated_by` (mirrors the import side's rule in §4.8:
7410
+ // it never invents one either, so export must not manufacture one that
7411
+ // would then round-trip back in as if it had always been asserted).
7412
+ ...f.generated_by ? { generated: { by: f.generated_by, at: isoOrFallback(f.updated_at) } } : {},
7413
+ // Omit `verified`/`sources` when empty — an emitted `[]` is not the same
7414
+ // shape as an absent key (spec §5.3 derives `unverified` from "no
7415
+ // `verified` key", not from "empty `verified` list").
7416
+ ...f.okf_verified && f.okf_verified.length > 0 ? { verified: f.okf_verified } : {},
7417
+ ...f.okf_sources && f.okf_sources.length > 0 ? { sources: f.okf_sources } : {},
7418
+ ...f.okf_usage_window ? { usage_window: f.okf_usage_window } : {}
7419
+ };
7420
+ }
7421
+ function taskFrontmatterV2(t) {
7422
+ return {
7423
+ type: t.okf_type ?? "task",
7424
+ title: t.description,
7425
+ timestamp: isoOrFallback(t.updated_at),
7426
+ // see factFrontmatterV2 comment — deliberate back-compat duplication
7427
+ id: t.id,
7428
+ entity_id: t.entity_id,
7429
+ status: t.lifecycle_status ?? "stable",
7430
+ // OKF v0.2 lifecycle
7431
+ execution_status: t.status,
7432
+ // execution state under renamed key
7433
+ priority: t.priority,
7434
+ created_at: t.created_at,
7435
+ resolved_at: t.resolved_at,
7436
+ deleted_at: t.deleted_at,
7437
+ // OKF v0.2 — symmetric with facts (spec §2.5): stale_after applies to
7438
+ // tasks too (a hallucinated task needs a freshness signal as much as a
7439
+ // hallucinated fact does).
7440
+ ...isoDateOrOmit(t.stale_after) ? { stale_after: isoDateOrOmit(t.stale_after) } : {},
7441
+ ...t.generated_by ? { generated: { by: t.generated_by, at: isoOrFallback(t.updated_at) } } : {},
7442
+ ...t.okf_verified && t.okf_verified.length > 0 ? { verified: t.okf_verified } : {},
7443
+ ...t.okf_sources && t.okf_sources.length > 0 ? { sources: t.okf_sources } : {},
7444
+ ...t.okf_usage_window ? { usage_window: t.okf_usage_window } : {}
7445
+ };
7446
+ }
7447
+ function factFrontmatterV1(f) {
7448
+ return {
7449
+ type: f.okf_type ?? "fact",
7450
+ title: f.title,
7451
+ tags: f.tags,
7452
+ timestamp: isoOrFallback(f.updated_at),
6931
7453
  resource: f.source_ref ?? void 0,
6932
7454
  id: f.id,
6933
7455
  entity_id: f.entity_id,
@@ -6940,11 +7462,11 @@ function factFrontmatter(f) {
6940
7462
  deleted_at: f.deleted_at
6941
7463
  };
6942
7464
  }
6943
- function taskFrontmatter(t) {
7465
+ function taskFrontmatterV1(t) {
6944
7466
  return {
6945
7467
  type: t.okf_type ?? "task",
6946
7468
  title: t.description,
6947
- timestamp: new Date(t.updated_at).toISOString(),
7469
+ timestamp: isoOrFallback(t.updated_at),
6948
7470
  id: t.id,
6949
7471
  entity_id: t.entity_id,
6950
7472
  status: t.status,
@@ -6974,7 +7496,11 @@ function buildEventLogEntries(events, factIdToFilename) {
6974
7496
  return { date: formatLogDate(e.created_at), text: coreOkf.appendEventIdComment(text, e.id) };
6975
7497
  });
6976
7498
  }
6977
- function formatOkfBundle(dump) {
7499
+ function formatOkfBundle(dump, options) {
7500
+ const profile = options?.profile ?? DEFAULT_PROFILE;
7501
+ const okfVersion = profile === "llm-wiki/2" ? "0.2" : "0.1";
7502
+ const factFm = profile === "llm-wiki/2" ? factFrontmatterV2 : factFrontmatterV1;
7503
+ const taskFm = profile === "llm-wiki/2" ? taskFrontmatterV2 : taskFrontmatterV1;
6978
7504
  const files = [];
6979
7505
  const rootEntries = [];
6980
7506
  for (const [entityId, bundle] of Object.entries(dump.entities)) {
@@ -7011,7 +7537,7 @@ function formatOkfBundle(dump) {
7011
7537
  const factBody = coreOkf.appendRelatedSection(f.body, relatedLinksFor2(factPath, f.id));
7012
7538
  files.push({
7013
7539
  path: factPath,
7014
- content: coreOkf.buildConceptDocument(factFrontmatter(f), factBody)
7540
+ content: coreOkf.buildConceptDocument(factFm(f), factBody)
7015
7541
  });
7016
7542
  }
7017
7543
  const taskEntries = bundle.tasks.map((t) => ({
@@ -7023,7 +7549,7 @@ function formatOkfBundle(dump) {
7023
7549
  const taskBody = coreOkf.appendRelatedSection("", relatedLinksFor2(taskPath, t.id));
7024
7550
  files.push({
7025
7551
  path: taskPath,
7026
- content: coreOkf.buildConceptDocument(taskFrontmatter(t), taskBody)
7552
+ content: coreOkf.buildConceptDocument(taskFm(t), taskBody)
7027
7553
  });
7028
7554
  }
7029
7555
  files.push({
@@ -7046,9 +7572,9 @@ function formatOkfBundle(dump) {
7046
7572
  files.push({
7047
7573
  path: "index.md",
7048
7574
  content: coreOkf.buildRootIndexMd(
7049
- "0.1",
7575
+ okfVersion,
7050
7576
  rootEntries.length > 0 ? [{ heading: "Entities", entries: rootEntries }] : [],
7051
- { profile: LLM_WIKI_PROFILE }
7577
+ { profile }
7052
7578
  )
7053
7579
  });
7054
7580
  return { files };
@@ -7138,6 +7664,21 @@ function parseFrontmatterTimestamp(value, fallback) {
7138
7664
  }
7139
7665
  return fallback;
7140
7666
  }
7667
+ function parseStaleAfterRaw(raw) {
7668
+ if (typeof raw !== "string") return null;
7669
+ const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(raw);
7670
+ if (!m) return null;
7671
+ const year = Number(m[1]);
7672
+ const month = Number(m[2]);
7673
+ const day = Number(m[3]);
7674
+ const ms = Date.UTC(year, month - 1, day);
7675
+ if (!Number.isFinite(ms)) return null;
7676
+ const d = new Date(ms);
7677
+ if (d.getUTCFullYear() !== year || d.getUTCMonth() + 1 !== month || d.getUTCDate() !== day) {
7678
+ return null;
7679
+ }
7680
+ return ms;
7681
+ }
7141
7682
  function unescapeLogSummary(summary) {
7142
7683
  return summary.replace(/\\\]/g, "]").replace(/\\\[/g, "[").replace(/\\\\/g, "\\");
7143
7684
  }
@@ -7155,9 +7696,29 @@ function parseLogEntryText(text) {
7155
7696
  function frontmatterToFact(entityId, id, frontmatter, body, now) {
7156
7697
  const created_at = parseFrontmatterTimestamp(frontmatter.created_at, now);
7157
7698
  const updated_at = parseFrontmatterTimestamp(
7158
- frontmatter.timestamp,
7159
- parseFrontmatterTimestamp(frontmatter.updated_at, now)
7699
+ frontmatter.generated?.at,
7700
+ parseFrontmatterTimestamp(
7701
+ frontmatter.timestamp,
7702
+ parseFrontmatterTimestamp(frontmatter.updated_at, now)
7703
+ )
7160
7704
  );
7705
+ const verified = coreOkf.parseVerifiedFlexible(frontmatter.verified);
7706
+ const rawSources = frontmatter.sources;
7707
+ const hasSourcesKey = rawSources !== void 0;
7708
+ const sources = Array.isArray(rawSources) ? rawSources : [];
7709
+ const usageWindow = frontmatter.usage_window && typeof frontmatter.usage_window === "object" ? frontmatter.usage_window : null;
7710
+ const lastV = coreOkf.latestVerified(verified, now);
7711
+ const lifecycleRaw = frontmatter.status;
7712
+ const lifecycle_status = lifecycleRaw === "draft" || lifecycleRaw === "stable" || lifecycleRaw === "deprecated" ? lifecycleRaw : "stable";
7713
+ const staleAfterRaw = frontmatter.stale_after;
7714
+ const stale_after = parseStaleAfterRaw(staleAfterRaw);
7715
+ const generated_by = frontmatter.generated?.by ?? null;
7716
+ if (!hasSourcesKey) {
7717
+ const urls = coreOkf.parseCitationsList(body);
7718
+ for (const url of urls) {
7719
+ sources.push({ resource: url });
7720
+ }
7721
+ }
7161
7722
  return {
7162
7723
  id,
7163
7724
  entity_id: entityId,
@@ -7173,26 +7734,62 @@ function frontmatterToFact(entityId, id, frontmatter, body, now) {
7173
7734
  last_accessed_at: frontmatter.last_accessed_at != null ? parseFrontmatterTimestamp(frontmatter.last_accessed_at, now) : null,
7174
7735
  access_count: typeof frontmatter.access_count === "number" ? frontmatter.access_count : 0,
7175
7736
  deleted_at: frontmatter.deleted_at != null ? parseFrontmatterTimestamp(frontmatter.deleted_at, 0) : null,
7176
- okf_type: frontmatter.type
7737
+ okf_type: frontmatter.type,
7738
+ // OKF v0.2
7739
+ lifecycle_status,
7740
+ stale_after,
7741
+ generated_by,
7742
+ okf_sources: sources,
7743
+ okf_verified: verified,
7744
+ okf_usage_window: usageWindow,
7745
+ last_verified_at: lastV?.at ?? null,
7746
+ last_verified_by: lastV?.by ?? null
7177
7747
  };
7178
7748
  }
7179
- function frontmatterToTask(entityId, id, frontmatter, now) {
7749
+ function frontmatterToTask(entityId, id, frontmatter, now, usesV1StatusRule) {
7180
7750
  const created_at = parseFrontmatterTimestamp(frontmatter.created_at, now);
7181
7751
  const updated_at = parseFrontmatterTimestamp(
7182
- frontmatter.timestamp,
7183
- parseFrontmatterTimestamp(frontmatter.updated_at, now)
7752
+ frontmatter.generated?.at,
7753
+ parseFrontmatterTimestamp(
7754
+ frontmatter.timestamp,
7755
+ parseFrontmatterTimestamp(frontmatter.updated_at, now)
7756
+ )
7184
7757
  );
7758
+ const verified = coreOkf.parseVerifiedFlexible(frontmatter.verified);
7759
+ const sources = Array.isArray(frontmatter.sources) ? frontmatter.sources : [];
7760
+ const usageWindow = frontmatter.usage_window && typeof frontmatter.usage_window === "object" ? frontmatter.usage_window : null;
7761
+ const lastV = coreOkf.latestVerified(verified, now);
7762
+ const lifecycleRaw = frontmatter.status;
7763
+ const lifecycle_status = lifecycleRaw === "draft" || lifecycleRaw === "stable" || lifecycleRaw === "deprecated" ? lifecycleRaw : "stable";
7764
+ const staleAfterRaw = frontmatter.stale_after;
7765
+ const stale_after = parseStaleAfterRaw(staleAfterRaw);
7766
+ const executionRaw = frontmatter.execution_status;
7767
+ let executionState;
7768
+ if (usesV1StatusRule) {
7769
+ executionState = TASK_STATUSES.has(String(frontmatter.status)) ? frontmatter.status : "pending";
7770
+ } else {
7771
+ executionState = TASK_STATUSES.has(String(executionRaw)) ? executionRaw : TASK_STATUSES.has(String(frontmatter.status)) ? frontmatter.status : "pending";
7772
+ }
7185
7773
  return {
7186
7774
  id,
7187
7775
  entity_id: entityId,
7188
7776
  description: typeof frontmatter.title === "string" ? frontmatter.title : "",
7189
- status: TASK_STATUSES.has(String(frontmatter.status)) ? frontmatter.status : "pending",
7777
+ status: executionState,
7190
7778
  priority: typeof frontmatter.priority === "number" ? frontmatter.priority : 0,
7191
7779
  created_at,
7192
7780
  updated_at,
7193
7781
  resolved_at: frontmatter.resolved_at != null ? parseFrontmatterTimestamp(frontmatter.resolved_at, now) : null,
7194
7782
  deleted_at: frontmatter.deleted_at != null ? parseFrontmatterTimestamp(frontmatter.deleted_at, 0) : null,
7195
- okf_type: frontmatter.type
7783
+ okf_type: frontmatter.type,
7784
+ // OKF v0.2
7785
+ lifecycle_status,
7786
+ stale_after,
7787
+ generated_by: frontmatter.generated?.by ?? null,
7788
+ okf_sources: sources,
7789
+ okf_verified: verified,
7790
+ okf_usage_window: usageWindow,
7791
+ last_verified_at: lastV?.at ?? null,
7792
+ last_verified_by: lastV?.by ?? null
7196
7793
  };
7197
7794
  }
7198
7795
  function findLogMdPath(files) {
@@ -7239,10 +7836,17 @@ function parseOkfBundle(entityId, files, options) {
7239
7836
  );
7240
7837
  const rootIndex = allowedFiles.find((f) => f.path === "index.md");
7241
7838
  const profileMeta = rootIndex ? coreOkf.parseRootIndexMd(rootIndex.content) : {};
7242
- const isProfile1 = profileMeta.profile === "llm-wiki/1";
7839
+ const okfVersion = profileMeta.okf_version;
7840
+ const profile = profileMeta.profile;
7841
+ const isProfile1 = profile === "llm-wiki/1";
7842
+ const isProfile2 = profile === "llm-wiki/2";
7843
+ const isLegacyV1 = profile === void 0 && okfVersion === "0.1";
7844
+ const isVersionOnlyV2 = profile === void 0 && okfVersion === "0.2";
7845
+ const isUnknownProfileV2 = profile !== void 0 && profile !== "llm-wiki/1" && profile !== "llm-wiki/2" && okfVersion === "0.2";
7846
+ const summaryEligible = isProfile1 || isProfile2 || isLegacyV1 || isVersionOnlyV2 || isUnknownProfileV2;
7243
7847
  let entitySummary;
7244
7848
  const entityIndex = allowedFiles.find((f) => f.path === `${entityPrefix}index.md`);
7245
- if (isProfile1 && entityIndex) {
7849
+ if (summaryEligible && entityIndex) {
7246
7850
  const summary = coreOkf.parseEntityIndexMd(entityIndex.content).summary;
7247
7851
  entitySummary = summary || void 0;
7248
7852
  }
@@ -7271,11 +7875,12 @@ function parseOkfBundle(entityId, files, options) {
7271
7875
  if (route === "ignore") continue;
7272
7876
  const resolvedId = typeof frontmatter.id === "string" && frontmatter.id ? frontmatter.id : basenameMd(file.path);
7273
7877
  const { body: storedBody, relatedLinks } = coreOkf.splitRelatedSection(body);
7274
- const edgeLinks = isProfile1 ? relatedLinks : [...relatedLinks, ...coreOkf.extractMarkdownLinks(storedBody)];
7878
+ const edgeLinks = isProfile1 || isLegacyV1 ? relatedLinks : [...relatedLinks, ...coreOkf.extractMarkdownLinks(storedBody)];
7879
+ const usesV1StatusRule = isProfile1 || isLegacyV1 || profile === void 0 && okfVersion === void 0;
7275
7880
  if (route === "fact") {
7276
7881
  facts.push(frontmatterToFact(entityId, resolvedId, frontmatter, storedBody, now));
7277
7882
  } else {
7278
- tasks.push(frontmatterToTask(entityId, resolvedId, frontmatter, now));
7883
+ tasks.push(frontmatterToTask(entityId, resolvedId, frontmatter, now, usesV1StatusRule));
7279
7884
  }
7280
7885
  const seenEdges = /* @__PURE__ */ new Set();
7281
7886
  extractEdgesFromLinks(