@equationalapplications/core-llm-wiki 7.1.2 → 7.2.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.
@@ -8,6 +8,18 @@ import { SearchResult } from 'minisearch';
8
8
  */
9
9
  interface SQLiteAdapter {
10
10
  execAsync(sql: string): Promise<void>;
11
+ /**
12
+ * `changes` must report the number of rows the statement actually wrote.
13
+ * It must be 0 when a conflict clause suppresses a write (INSERT OR IGNORE,
14
+ * or ON CONFLICT DO UPDATE ... WHERE), and must never be nonzero for a
15
+ * suppressed write. A permitted write that modifies no column value still
16
+ * counts the row, as SQLite does.
17
+ *
18
+ * This count is load-bearing beyond ownership checks: edge insertion
19
+ * (and therefore upsertGraph's public edgesWritten), metadata compare-and-set,
20
+ * soft-delete success reporting, and maintenance sweep counts all read it.
21
+ * An adapter that under-reports silently corrupts those results.
22
+ */
11
23
  runAsync(sql: string, params?: unknown[]): Promise<{
12
24
  changes: number;
13
25
  lastInsertRowId: number;
@@ -740,6 +752,33 @@ declare class WikiSourceRefHashCollision extends Error {
740
752
  attemptedSourceRef: string;
741
753
  });
742
754
  }
755
+ /**
756
+ * Thrown when a graph node ID is already owned by another entity.
757
+ *
758
+ * Raised by `WikiMemory.upsertGraph`'s node-ownership pre-flight and,
759
+ * independently, by `EntryRepository.upsert` for every caller — which is why
760
+ * `ingestDocument` can surface it too (it shares `upsertGraphCore` on its full
761
+ * path and `EntryRepository.upsert` on its partial path).
762
+ *
763
+ * Deliberately carries NO context: no owner entity, node ID, stored content,
764
+ * provenance, or `cause`. A rejection is reachable by whatever supplies the
765
+ * node IDs, so the error must not become a channel for reading another
766
+ * entity's identifiers. The `WIKI_`-prefixed code and fixed message also keep
767
+ * it clear of `extractSqliteCode`, so it passes through the serialized
768
+ * transaction wrapper unwrapped.
769
+ */
770
+ declare class WikiGraphNodeOwnershipConflict extends Error {
771
+ readonly code: "WIKI_GRAPH_NODE_OWNERSHIP_CONFLICT";
772
+ /**
773
+ * Privacy-preserving constructor: accepts no arguments, so neither the foreign
774
+ * owner, the conflicting node ID, the stored content, nor any provenance can
775
+ * leak through this error. The fixed message and `code` are the entire signal.
776
+ *
777
+ * @returns a contextless `WikiGraphNodeOwnershipConflict` instance. See the
778
+ * class-level JSDoc for the privacy rationale.
779
+ */
780
+ constructor();
781
+ }
743
782
  /**
744
783
  * Thrown by the serialized transaction wrapper when a SQLite driver error
745
784
  * escapes a transaction callback (nested BEGIN, SQLITE_BUSY, constraint
@@ -965,6 +1004,26 @@ declare class EntryRepository extends BaseRepository {
965
1004
  * diagnostic that inflates runReembed's permanentlyFailed counter. A conflict
966
1005
  * with no blob leaves marker state alone, matching the "absent means don't
967
1006
  * touch" semantics used for embedding_blob itself.
1007
+ *
1008
+ * Graph node ownership (spec §2.4 + §3, REQ-OWN-01 + REQ-GRAPH-01): entry
1009
+ * IDs share one database-wide namespace, so an ID stays owned by its
1010
+ * existing entity for as long as the row exists — soft deletion included.
1011
+ * The pre-write lookup already reads the existing row for OKF metadata; we
1012
+ * project `entity_id` off that same SELECT and reject the write before
1013
+ * any outbox payload construction when the stored owner differs from
1014
+ * `fact.entity_id`. Soft-deleted foreign rows keep their IDs reserved;
1015
+ * permanent reservation is NOT introduced, so a physically absent ID may
1016
+ * still be inserted under the requested entity. The ON CONFLICT UPDATE SET
1017
+ * clause carries a final `WHERE entries.entity_id = excluded.entity_id`
1018
+ * guard so the database itself refuses a cross-owner row overwrite even if
1019
+ * a future caller bypasses the pre-write check. When `runAsync` reports
1020
+ * 0 changes (a legitimate conflict-suppressed write, OR an adapter that
1021
+ * under-reports a permitted write) we re-read the stored owner; only a
1022
+ * foreign owner throws — this is local defensive behavior, not permission
1023
+ * for adapters to under-report. The thrown error is
1024
+ * `WikiGraphNodeOwnershipConflict` (no owner, node ID, or stored content
1025
+ * is disclosed). Upserts that pass both gates retain existing same-entity
1026
+ * update + resurrection semantics.
968
1027
  */
969
1028
  upsert(fact: WikiFact, tx: SQLiteAdapter): Promise<{
970
1029
  changes: number;
@@ -1814,6 +1873,28 @@ declare class IngestionService {
1814
1873
  }, opts?: {
1815
1874
  onDuplicateHash?: 'ingest' | 'skip' | 'throw';
1816
1875
  }): Promise<IngestDocumentResult>;
1876
+ /**
1877
+ * Reject the whole write when any incoming node ID is already owned by a
1878
+ * different entity. Runs entirely inside the supplied `tx`, through the
1879
+ * repository abstraction, and — being the first thing `upsertGraphCore`
1880
+ * does — before ontology resolution, which can itself persist a configured
1881
+ * seed manifest.
1882
+ *
1883
+ * The lookup has no deleted-row filter, so a soft-deleted foreign row keeps
1884
+ * its ID reserved. Only the lookup IDs are deduplicated; `params.nodes` and
1885
+ * the later write loops keep their ordering and counts.
1886
+ *
1887
+ * The error is deliberately contextless — see WikiGraphNodeOwnershipConflict.
1888
+ *
1889
+ * @param entityId - the entity the caller is writing on behalf of. Foreign
1890
+ * rows under a different entity trigger the rejection.
1891
+ * @param nodes - incoming node set; only the IDs are inspected, so callers
1892
+ * may pass the public `params.nodes` shape directly without slicing.
1893
+ * @param tx - the open transaction. Reuses the same handle every later
1894
+ * statement in `upsertGraphCore` runs against, so atomicity is enforced
1895
+ * by the host transaction's commit/rollback, not by this method.
1896
+ */
1897
+ private assertGraphNodeOwnership;
1817
1898
  /**
1818
1899
  * Implements spec §2 data flow steps a–j with the host-facing
1819
1900
  * `{sourceRef, sourceHash, nodes, edges}` parameter shape. Shared between
@@ -1832,6 +1913,16 @@ declare class IngestionService {
1832
1913
  * @returns Counts: nodesWritten (validated nodes persisted), edgesWritten
1833
1914
  * (manifest-valid edges persisted), superseded (prior facts soft-deleted
1834
1915
  * plus prior source-ref edges hard-deleted).
1916
+ *
1917
+ * Node-ownership pre-flight (spec §3, REQ-GRAPH-01): the first statement
1918
+ * is `assertGraphNodeOwnership(entityId, params.nodes, tx)`, which runs
1919
+ * before ontology resolution, seed-manifest persistence, and every other
1920
+ * write. A foreign-owned ID throws `WikiGraphNodeOwnershipConflict` with
1921
+ * no disclosed owner, node ID, or content; the caller's transaction
1922
+ * remains open and must be rolled back by the host. Soft-deleted foreign
1923
+ * rows keep their IDs reserved. Duplicate incoming IDs are deduplicated
1924
+ * only for the lookup; `params.nodes` and the write loops keep their
1925
+ * ordering and counts so success counts match the pre-change contract.
1835
1926
  */
1836
1927
  upsertGraphCore(entityId: string, params: {
1837
1928
  sourceRef: string;
@@ -2451,6 +2542,11 @@ declare class WikiMemory {
2451
2542
  metadataReset?: boolean;
2452
2543
  }>;
2453
2544
  /**
2545
+ * Full and partial ingestion both reject foreign-owned generated fact IDs with
2546
+ * `WikiGraphNodeOwnershipConflict`. Such collisions are unexpected. Ingestion
2547
+ * may already have persisted ontology or other state before rejection; unlike
2548
+ * direct graph preflight, it relies on transaction rollback for atomicity.
2549
+ *
2454
2550
  * @param params.promptOverride - Overrides the system prompt for this ingest call only.
2455
2551
  * For persistent customization, set `options.config.prompts.ingestSystemPrompt` at
2456
2552
  * WikiMemory construction time.
@@ -2490,6 +2586,19 @@ declare class WikiMemory {
2490
2586
  * or edge `type` throws `WikiStrictOntologyViolation` (pre-flight,
2491
2587
  * all-or-nothing — NONE written on failure).
2492
2588
  *
2589
+ * Entry IDs share one database-wide namespace. Existing rows, including
2590
+ * soft-deleted rows, reserve their IDs for their entity; same-entity updates
2591
+ * and resurrection remain supported. Hosts authenticate callers and select
2592
+ * the authorized entity ID; entity-qualified IDs are optional, not required.
2593
+ * After the live-only C2 probe above, a foreign node ID throws the fixed,
2594
+ * privacy-preserving `WikiGraphNodeOwnershipConflict` before persistent writes
2595
+ * (including seed manifests). Deleted source mappings do not trigger C2.
2596
+ *
2597
+ * Propagate later failures to roll back the host transaction. In particular,
2598
+ * foreign edge-ID collisions remain late bare `Error`s: the foreign edge is
2599
+ * not overwritten, but catching and committing can retain earlier writes.
2600
+ * This method neither commits nor rolls back the caller's transaction.
2601
+ *
2493
2602
  * @returns Counts: nodesWritten (validated nodes persisted), edgesWritten
2494
2603
  * (manifest-valid edges persisted), superseded (prior facts soft-deleted
2495
2604
  * plus prior source-ref edges hard-deleted).
@@ -2604,4 +2713,4 @@ declare class WikiMemory {
2604
2713
  setGeneratedByTask(taskId: string, entityId: string, actor: string): Promise<void>;
2605
2714
  }
2606
2715
 
2607
- export { WikiIngestEmptyError as $, type ReembedResult as A, type VectorRankerFallback as B, type ChunkFailure as C, type DegradedRecord as D, type EmbedFactResult as E, type FormatContextOptions as F, type GraphNeighborhood as G, HEAL_BATCH_SIZE as H, type IngestDocumentResult as I, type VectorRankerRankArgs as J, type VectorRankerSemanticResult as K, type LLMProvider as L, type MemoryBundle as M, WikiBusyError as N, type OntologyManifest as O, type PromptOverrides as P, type WikiBusyOperation as Q, type ReadOptions as R, type SQLiteAdapter as S, type WikiCheckpoint as T, type WikiConfig as U, type VectorRanker as V, type WikiOptions as W, WikiDuplicateHashError as X, type WikiEdge as Y, type WikiEvent as Z, type WikiFact as _, type MemoryDump as a, WikiInvalidReadOptions as a0, type WikiMemoryTestAccess as a1, type WikiOutboxEvent as a2, WikiParseError as a3, WikiSourceRefHashCollision as a4, WikiStrictOntologyViolation as a5, type WikiTask as a6, WikiTransactionError as a7, EmbeddingService as a8, ImportExportService as a9, IngestionService as aa, JobManager as ab, MaintenanceService as ac, RetrievalService as ad, SearchService as ae, WriteService as af, type FormattedMemoryDump as b, WikiMemory as c, type EmbedFailureKind as d, type EmbeddingMarkerKind as e, type EntityStatus as f, type ExtractedFact as g, type ExtractedFactEdge as h, type ExtractedFactWithOntology as i, type ExtractedTask as j, type GraphTraversalOptions as k, HEAL_RECHECK_MS as l, HOOK_TIMEOUT_MARKER as m, type HealResult as n, ONTOLOGY_BACKFILL_BATCH_SIZE as o, ONTOLOGY_BACKFILL_MAX_PROMPT_CHARS as p, ONTOLOGY_BACKFILL_RECHECK_MS as q, type OntologyBackfillResult as r, type OntologyConfig as s, type OntologyEdgeType as t, type OntologyMode as u, type OntologyNodeType as v, type OntologyPromptContext as w, type OntologyUpdates as x, PromptService as y, PrunePartialFailureError as z };
2716
+ export { WikiGraphNodeOwnershipConflict as $, type ReembedResult as A, type VectorRankerFallback as B, type ChunkFailure as C, type DegradedRecord as D, type EmbedFactResult as E, type FormatContextOptions as F, type GraphNeighborhood as G, HEAL_BATCH_SIZE as H, type IngestDocumentResult as I, type VectorRankerRankArgs as J, type VectorRankerSemanticResult as K, type LLMProvider as L, type MemoryBundle as M, WikiBusyError as N, type OntologyManifest as O, type PromptOverrides as P, type WikiBusyOperation as Q, type ReadOptions as R, type SQLiteAdapter as S, type WikiCheckpoint as T, type WikiConfig as U, type VectorRanker as V, type WikiOptions as W, WikiDuplicateHashError as X, type WikiEdge as Y, type WikiEvent as Z, type WikiFact as _, type MemoryDump as a, WikiIngestEmptyError as a0, WikiInvalidReadOptions as a1, type WikiMemoryTestAccess as a2, type WikiOutboxEvent as a3, WikiParseError as a4, WikiSourceRefHashCollision as a5, WikiStrictOntologyViolation as a6, type WikiTask as a7, WikiTransactionError as a8, EmbeddingService as a9, ImportExportService as aa, IngestionService as ab, JobManager as ac, MaintenanceService as ad, RetrievalService as ae, SearchService as af, WriteService as ag, type FormattedMemoryDump as b, WikiMemory as c, type EmbedFailureKind as d, type EmbeddingMarkerKind as e, type EntityStatus as f, type ExtractedFact as g, type ExtractedFactEdge as h, type ExtractedFactWithOntology as i, type ExtractedTask as j, type GraphTraversalOptions as k, HEAL_RECHECK_MS as l, HOOK_TIMEOUT_MARKER as m, type HealResult as n, ONTOLOGY_BACKFILL_BATCH_SIZE as o, ONTOLOGY_BACKFILL_MAX_PROMPT_CHARS as p, ONTOLOGY_BACKFILL_RECHECK_MS as q, type OntologyBackfillResult as r, type OntologyConfig as s, type OntologyEdgeType as t, type OntologyMode as u, type OntologyNodeType as v, type OntologyPromptContext as w, type OntologyUpdates as x, PromptService as y, PrunePartialFailureError as z };
@@ -8,6 +8,18 @@ import { SearchResult } from 'minisearch';
8
8
  */
9
9
  interface SQLiteAdapter {
10
10
  execAsync(sql: string): Promise<void>;
11
+ /**
12
+ * `changes` must report the number of rows the statement actually wrote.
13
+ * It must be 0 when a conflict clause suppresses a write (INSERT OR IGNORE,
14
+ * or ON CONFLICT DO UPDATE ... WHERE), and must never be nonzero for a
15
+ * suppressed write. A permitted write that modifies no column value still
16
+ * counts the row, as SQLite does.
17
+ *
18
+ * This count is load-bearing beyond ownership checks: edge insertion
19
+ * (and therefore upsertGraph's public edgesWritten), metadata compare-and-set,
20
+ * soft-delete success reporting, and maintenance sweep counts all read it.
21
+ * An adapter that under-reports silently corrupts those results.
22
+ */
11
23
  runAsync(sql: string, params?: unknown[]): Promise<{
12
24
  changes: number;
13
25
  lastInsertRowId: number;
@@ -740,6 +752,33 @@ declare class WikiSourceRefHashCollision extends Error {
740
752
  attemptedSourceRef: string;
741
753
  });
742
754
  }
755
+ /**
756
+ * Thrown when a graph node ID is already owned by another entity.
757
+ *
758
+ * Raised by `WikiMemory.upsertGraph`'s node-ownership pre-flight and,
759
+ * independently, by `EntryRepository.upsert` for every caller — which is why
760
+ * `ingestDocument` can surface it too (it shares `upsertGraphCore` on its full
761
+ * path and `EntryRepository.upsert` on its partial path).
762
+ *
763
+ * Deliberately carries NO context: no owner entity, node ID, stored content,
764
+ * provenance, or `cause`. A rejection is reachable by whatever supplies the
765
+ * node IDs, so the error must not become a channel for reading another
766
+ * entity's identifiers. The `WIKI_`-prefixed code and fixed message also keep
767
+ * it clear of `extractSqliteCode`, so it passes through the serialized
768
+ * transaction wrapper unwrapped.
769
+ */
770
+ declare class WikiGraphNodeOwnershipConflict extends Error {
771
+ readonly code: "WIKI_GRAPH_NODE_OWNERSHIP_CONFLICT";
772
+ /**
773
+ * Privacy-preserving constructor: accepts no arguments, so neither the foreign
774
+ * owner, the conflicting node ID, the stored content, nor any provenance can
775
+ * leak through this error. The fixed message and `code` are the entire signal.
776
+ *
777
+ * @returns a contextless `WikiGraphNodeOwnershipConflict` instance. See the
778
+ * class-level JSDoc for the privacy rationale.
779
+ */
780
+ constructor();
781
+ }
743
782
  /**
744
783
  * Thrown by the serialized transaction wrapper when a SQLite driver error
745
784
  * escapes a transaction callback (nested BEGIN, SQLITE_BUSY, constraint
@@ -965,6 +1004,26 @@ declare class EntryRepository extends BaseRepository {
965
1004
  * diagnostic that inflates runReembed's permanentlyFailed counter. A conflict
966
1005
  * with no blob leaves marker state alone, matching the "absent means don't
967
1006
  * touch" semantics used for embedding_blob itself.
1007
+ *
1008
+ * Graph node ownership (spec §2.4 + §3, REQ-OWN-01 + REQ-GRAPH-01): entry
1009
+ * IDs share one database-wide namespace, so an ID stays owned by its
1010
+ * existing entity for as long as the row exists — soft deletion included.
1011
+ * The pre-write lookup already reads the existing row for OKF metadata; we
1012
+ * project `entity_id` off that same SELECT and reject the write before
1013
+ * any outbox payload construction when the stored owner differs from
1014
+ * `fact.entity_id`. Soft-deleted foreign rows keep their IDs reserved;
1015
+ * permanent reservation is NOT introduced, so a physically absent ID may
1016
+ * still be inserted under the requested entity. The ON CONFLICT UPDATE SET
1017
+ * clause carries a final `WHERE entries.entity_id = excluded.entity_id`
1018
+ * guard so the database itself refuses a cross-owner row overwrite even if
1019
+ * a future caller bypasses the pre-write check. When `runAsync` reports
1020
+ * 0 changes (a legitimate conflict-suppressed write, OR an adapter that
1021
+ * under-reports a permitted write) we re-read the stored owner; only a
1022
+ * foreign owner throws — this is local defensive behavior, not permission
1023
+ * for adapters to under-report. The thrown error is
1024
+ * `WikiGraphNodeOwnershipConflict` (no owner, node ID, or stored content
1025
+ * is disclosed). Upserts that pass both gates retain existing same-entity
1026
+ * update + resurrection semantics.
968
1027
  */
969
1028
  upsert(fact: WikiFact, tx: SQLiteAdapter): Promise<{
970
1029
  changes: number;
@@ -1814,6 +1873,28 @@ declare class IngestionService {
1814
1873
  }, opts?: {
1815
1874
  onDuplicateHash?: 'ingest' | 'skip' | 'throw';
1816
1875
  }): Promise<IngestDocumentResult>;
1876
+ /**
1877
+ * Reject the whole write when any incoming node ID is already owned by a
1878
+ * different entity. Runs entirely inside the supplied `tx`, through the
1879
+ * repository abstraction, and — being the first thing `upsertGraphCore`
1880
+ * does — before ontology resolution, which can itself persist a configured
1881
+ * seed manifest.
1882
+ *
1883
+ * The lookup has no deleted-row filter, so a soft-deleted foreign row keeps
1884
+ * its ID reserved. Only the lookup IDs are deduplicated; `params.nodes` and
1885
+ * the later write loops keep their ordering and counts.
1886
+ *
1887
+ * The error is deliberately contextless — see WikiGraphNodeOwnershipConflict.
1888
+ *
1889
+ * @param entityId - the entity the caller is writing on behalf of. Foreign
1890
+ * rows under a different entity trigger the rejection.
1891
+ * @param nodes - incoming node set; only the IDs are inspected, so callers
1892
+ * may pass the public `params.nodes` shape directly without slicing.
1893
+ * @param tx - the open transaction. Reuses the same handle every later
1894
+ * statement in `upsertGraphCore` runs against, so atomicity is enforced
1895
+ * by the host transaction's commit/rollback, not by this method.
1896
+ */
1897
+ private assertGraphNodeOwnership;
1817
1898
  /**
1818
1899
  * Implements spec §2 data flow steps a–j with the host-facing
1819
1900
  * `{sourceRef, sourceHash, nodes, edges}` parameter shape. Shared between
@@ -1832,6 +1913,16 @@ declare class IngestionService {
1832
1913
  * @returns Counts: nodesWritten (validated nodes persisted), edgesWritten
1833
1914
  * (manifest-valid edges persisted), superseded (prior facts soft-deleted
1834
1915
  * plus prior source-ref edges hard-deleted).
1916
+ *
1917
+ * Node-ownership pre-flight (spec §3, REQ-GRAPH-01): the first statement
1918
+ * is `assertGraphNodeOwnership(entityId, params.nodes, tx)`, which runs
1919
+ * before ontology resolution, seed-manifest persistence, and every other
1920
+ * write. A foreign-owned ID throws `WikiGraphNodeOwnershipConflict` with
1921
+ * no disclosed owner, node ID, or content; the caller's transaction
1922
+ * remains open and must be rolled back by the host. Soft-deleted foreign
1923
+ * rows keep their IDs reserved. Duplicate incoming IDs are deduplicated
1924
+ * only for the lookup; `params.nodes` and the write loops keep their
1925
+ * ordering and counts so success counts match the pre-change contract.
1835
1926
  */
1836
1927
  upsertGraphCore(entityId: string, params: {
1837
1928
  sourceRef: string;
@@ -2451,6 +2542,11 @@ declare class WikiMemory {
2451
2542
  metadataReset?: boolean;
2452
2543
  }>;
2453
2544
  /**
2545
+ * Full and partial ingestion both reject foreign-owned generated fact IDs with
2546
+ * `WikiGraphNodeOwnershipConflict`. Such collisions are unexpected. Ingestion
2547
+ * may already have persisted ontology or other state before rejection; unlike
2548
+ * direct graph preflight, it relies on transaction rollback for atomicity.
2549
+ *
2454
2550
  * @param params.promptOverride - Overrides the system prompt for this ingest call only.
2455
2551
  * For persistent customization, set `options.config.prompts.ingestSystemPrompt` at
2456
2552
  * WikiMemory construction time.
@@ -2490,6 +2586,19 @@ declare class WikiMemory {
2490
2586
  * or edge `type` throws `WikiStrictOntologyViolation` (pre-flight,
2491
2587
  * all-or-nothing — NONE written on failure).
2492
2588
  *
2589
+ * Entry IDs share one database-wide namespace. Existing rows, including
2590
+ * soft-deleted rows, reserve their IDs for their entity; same-entity updates
2591
+ * and resurrection remain supported. Hosts authenticate callers and select
2592
+ * the authorized entity ID; entity-qualified IDs are optional, not required.
2593
+ * After the live-only C2 probe above, a foreign node ID throws the fixed,
2594
+ * privacy-preserving `WikiGraphNodeOwnershipConflict` before persistent writes
2595
+ * (including seed manifests). Deleted source mappings do not trigger C2.
2596
+ *
2597
+ * Propagate later failures to roll back the host transaction. In particular,
2598
+ * foreign edge-ID collisions remain late bare `Error`s: the foreign edge is
2599
+ * not overwritten, but catching and committing can retain earlier writes.
2600
+ * This method neither commits nor rolls back the caller's transaction.
2601
+ *
2493
2602
  * @returns Counts: nodesWritten (validated nodes persisted), edgesWritten
2494
2603
  * (manifest-valid edges persisted), superseded (prior facts soft-deleted
2495
2604
  * plus prior source-ref edges hard-deleted).
@@ -2604,4 +2713,4 @@ declare class WikiMemory {
2604
2713
  setGeneratedByTask(taskId: string, entityId: string, actor: string): Promise<void>;
2605
2714
  }
2606
2715
 
2607
- export { WikiIngestEmptyError as $, type ReembedResult as A, type VectorRankerFallback as B, type ChunkFailure as C, type DegradedRecord as D, type EmbedFactResult as E, type FormatContextOptions as F, type GraphNeighborhood as G, HEAL_BATCH_SIZE as H, type IngestDocumentResult as I, type VectorRankerRankArgs as J, type VectorRankerSemanticResult as K, type LLMProvider as L, type MemoryBundle as M, WikiBusyError as N, type OntologyManifest as O, type PromptOverrides as P, type WikiBusyOperation as Q, type ReadOptions as R, type SQLiteAdapter as S, type WikiCheckpoint as T, type WikiConfig as U, type VectorRanker as V, type WikiOptions as W, WikiDuplicateHashError as X, type WikiEdge as Y, type WikiEvent as Z, type WikiFact as _, type MemoryDump as a, WikiInvalidReadOptions as a0, type WikiMemoryTestAccess as a1, type WikiOutboxEvent as a2, WikiParseError as a3, WikiSourceRefHashCollision as a4, WikiStrictOntologyViolation as a5, type WikiTask as a6, WikiTransactionError as a7, EmbeddingService as a8, ImportExportService as a9, IngestionService as aa, JobManager as ab, MaintenanceService as ac, RetrievalService as ad, SearchService as ae, WriteService as af, type FormattedMemoryDump as b, WikiMemory as c, type EmbedFailureKind as d, type EmbeddingMarkerKind as e, type EntityStatus as f, type ExtractedFact as g, type ExtractedFactEdge as h, type ExtractedFactWithOntology as i, type ExtractedTask as j, type GraphTraversalOptions as k, HEAL_RECHECK_MS as l, HOOK_TIMEOUT_MARKER as m, type HealResult as n, ONTOLOGY_BACKFILL_BATCH_SIZE as o, ONTOLOGY_BACKFILL_MAX_PROMPT_CHARS as p, ONTOLOGY_BACKFILL_RECHECK_MS as q, type OntologyBackfillResult as r, type OntologyConfig as s, type OntologyEdgeType as t, type OntologyMode as u, type OntologyNodeType as v, type OntologyPromptContext as w, type OntologyUpdates as x, PromptService as y, PrunePartialFailureError as z };
2716
+ export { WikiGraphNodeOwnershipConflict as $, type ReembedResult as A, type VectorRankerFallback as B, type ChunkFailure as C, type DegradedRecord as D, type EmbedFactResult as E, type FormatContextOptions as F, type GraphNeighborhood as G, HEAL_BATCH_SIZE as H, type IngestDocumentResult as I, type VectorRankerRankArgs as J, type VectorRankerSemanticResult as K, type LLMProvider as L, type MemoryBundle as M, WikiBusyError as N, type OntologyManifest as O, type PromptOverrides as P, type WikiBusyOperation as Q, type ReadOptions as R, type SQLiteAdapter as S, type WikiCheckpoint as T, type WikiConfig as U, type VectorRanker as V, type WikiOptions as W, WikiDuplicateHashError as X, type WikiEdge as Y, type WikiEvent as Z, type WikiFact as _, type MemoryDump as a, WikiIngestEmptyError as a0, WikiInvalidReadOptions as a1, type WikiMemoryTestAccess as a2, type WikiOutboxEvent as a3, WikiParseError as a4, WikiSourceRefHashCollision as a5, WikiStrictOntologyViolation as a6, type WikiTask as a7, WikiTransactionError as a8, EmbeddingService as a9, ImportExportService as aa, IngestionService as ab, JobManager as ac, MaintenanceService as ad, RetrievalService as ae, SearchService as af, WriteService as ag, type FormattedMemoryDump as b, WikiMemory as c, type EmbedFailureKind as d, type EmbeddingMarkerKind as e, type EntityStatus as f, type ExtractedFact as g, type ExtractedFactEdge as h, type ExtractedFactWithOntology as i, type ExtractedTask as j, type GraphTraversalOptions as k, HEAL_RECHECK_MS as l, HOOK_TIMEOUT_MARKER as m, type HealResult as n, ONTOLOGY_BACKFILL_BATCH_SIZE as o, ONTOLOGY_BACKFILL_MAX_PROMPT_CHARS as p, ONTOLOGY_BACKFILL_RECHECK_MS as q, type OntologyBackfillResult as r, type OntologyConfig as s, type OntologyEdgeType as t, type OntologyMode as u, type OntologyNodeType as v, type OntologyPromptContext as w, type OntologyUpdates as x, PromptService as y, PrunePartialFailureError as z };
@@ -1,3 +1,3 @@
1
- export { a8 as EmbeddingService, a9 as ImportExportService, aa as IngestionService, ab as JobManager, ab as JobManagerType, ac as MaintenanceService, ad as RetrievalService, ae as SearchService, ae as SearchServiceType, a1 as WikiMemoryTestAccess, af as WriteService } from './testing-CA7723qk.mjs';
1
+ export { a9 as EmbeddingService, aa as ImportExportService, ab as IngestionService, ac as JobManager, ac as JobManagerType, ad as MaintenanceService, ae as RetrievalService, af as SearchService, af as SearchServiceType, a2 as WikiMemoryTestAccess, ag as WriteService } from './testing-DYeMd9d1.mjs';
2
2
  import '@equationalapplications/core-okf';
3
3
  import 'minisearch';
package/dist/testing.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- export { a8 as EmbeddingService, a9 as ImportExportService, aa as IngestionService, ab as JobManager, ab as JobManagerType, ac as MaintenanceService, ad as RetrievalService, ae as SearchService, ae as SearchServiceType, a1 as WikiMemoryTestAccess, af as WriteService } from './testing-CA7723qk.js';
1
+ export { a9 as EmbeddingService, aa as ImportExportService, ab as IngestionService, ac as JobManager, ac as JobManagerType, ad as MaintenanceService, ae as RetrievalService, af as SearchService, af as SearchServiceType, a2 as WikiMemoryTestAccess, ag as WriteService } from './testing-DYeMd9d1.js';
2
2
  import '@equationalapplications/core-okf';
3
3
  import 'minisearch';
package/dist/testing.js CHANGED
@@ -90,6 +90,21 @@ var WikiStrictOntologyViolation = class extends Error {
90
90
  this.name = "WikiStrictOntologyViolation";
91
91
  }
92
92
  };
93
+ var WikiGraphNodeOwnershipConflict = class extends Error {
94
+ /**
95
+ * Privacy-preserving constructor: accepts no arguments, so neither the foreign
96
+ * owner, the conflicting node ID, the stored content, nor any provenance can
97
+ * leak through this error. The fixed message and `code` are the entire signal.
98
+ *
99
+ * @returns a contextless `WikiGraphNodeOwnershipConflict` instance. See the
100
+ * class-level JSDoc for the privacy rationale.
101
+ */
102
+ constructor() {
103
+ super("Graph write rejected because a node ID is unavailable for this entity.");
104
+ this.code = "WIKI_GRAPH_NODE_OWNERSHIP_CONFLICT";
105
+ this.name = "WikiGraphNodeOwnershipConflict";
106
+ }
107
+ };
93
108
  var WikiTransactionError = class extends Error {
94
109
  constructor(message, options) {
95
110
  super(message, options);
@@ -1738,6 +1753,36 @@ var IngestionService = class {
1738
1753
  releaseIngestLocks();
1739
1754
  }
1740
1755
  }
1756
+ /**
1757
+ * Reject the whole write when any incoming node ID is already owned by a
1758
+ * different entity. Runs entirely inside the supplied `tx`, through the
1759
+ * repository abstraction, and — being the first thing `upsertGraphCore`
1760
+ * does — before ontology resolution, which can itself persist a configured
1761
+ * seed manifest.
1762
+ *
1763
+ * The lookup has no deleted-row filter, so a soft-deleted foreign row keeps
1764
+ * its ID reserved. Only the lookup IDs are deduplicated; `params.nodes` and
1765
+ * the later write loops keep their ordering and counts.
1766
+ *
1767
+ * The error is deliberately contextless — see WikiGraphNodeOwnershipConflict.
1768
+ *
1769
+ * @param entityId - the entity the caller is writing on behalf of. Foreign
1770
+ * rows under a different entity trigger the rejection.
1771
+ * @param nodes - incoming node set; only the IDs are inspected, so callers
1772
+ * may pass the public `params.nodes` shape directly without slicing.
1773
+ * @param tx - the open transaction. Reuses the same handle every later
1774
+ * statement in `upsertGraphCore` runs against, so atomicity is enforced
1775
+ * by the host transaction's commit/rollback, not by this method.
1776
+ */
1777
+ async assertGraphNodeOwnership(entityId, nodes, tx) {
1778
+ const ids = [...new Set(nodes.map((node) => node.id))];
1779
+ const existing = await this.entryRepo.findExistingMetadataByIds(ids, tx);
1780
+ for (const row of existing) {
1781
+ if (row.entity_id !== entityId) {
1782
+ throw new WikiGraphNodeOwnershipConflict();
1783
+ }
1784
+ }
1785
+ }
1741
1786
  /**
1742
1787
  * Implements spec §2 data flow steps a–j with the host-facing
1743
1788
  * `{sourceRef, sourceHash, nodes, edges}` parameter shape. Shared between
@@ -1756,8 +1801,19 @@ var IngestionService = class {
1756
1801
  * @returns Counts: nodesWritten (validated nodes persisted), edgesWritten
1757
1802
  * (manifest-valid edges persisted), superseded (prior facts soft-deleted
1758
1803
  * plus prior source-ref edges hard-deleted).
1804
+ *
1805
+ * Node-ownership pre-flight (spec §3, REQ-GRAPH-01): the first statement
1806
+ * is `assertGraphNodeOwnership(entityId, params.nodes, tx)`, which runs
1807
+ * before ontology resolution, seed-manifest persistence, and every other
1808
+ * write. A foreign-owned ID throws `WikiGraphNodeOwnershipConflict` with
1809
+ * no disclosed owner, node ID, or content; the caller's transaction
1810
+ * remains open and must be rolled back by the host. Soft-deleted foreign
1811
+ * rows keep their IDs reserved. Duplicate incoming IDs are deduplicated
1812
+ * only for the lookup; `params.nodes` and the write loops keep their
1813
+ * ordering and counts so success counts match the pre-change contract.
1759
1814
  */
1760
1815
  async upsertGraphCore(entityId, params, tx, opts) {
1816
+ await this.assertGraphNodeOwnership(entityId, params.nodes, tx);
1761
1817
  const now = Date.now();
1762
1818
  const ontologyState = await this.ontologyService?.getEffectiveState(entityId, tx) ?? { mode: "off", manifest: { node_types: [], edge_types: [] } };
1763
1819
  let { mode, manifest } = ontologyState;