agent-kb 0.1.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.
@@ -0,0 +1,756 @@
1
+ /**
2
+ * Clock abstraction for deterministic timestamps in tests and production.
3
+ *
4
+ * SystemClock — real wall-clock time via Date.toISOString().
5
+ * FixedClock — frozen timestamp; call set(iso) to advance for tests.
6
+ */
7
+ interface Clock {
8
+ /** Returns an ISO-8601 timestamp string (e.g. '2024-01-15T10:30:00.000Z'). */
9
+ now(): string;
10
+ }
11
+
12
+ /**
13
+ * T09 — WriteLock: single-writer serialization via OS advisory lock.
14
+ *
15
+ * All write operations serialize through this lock so file + SQLite
16
+ * mutations never interleave. Acquisition is synchronous (the engine's
17
+ * write path is synchronous).
18
+ */
19
+ interface WriteLock {
20
+ /** Acquire the write lock (retry until timeoutMs), run fn, then release. */
21
+ withLock<T>(fn: () => T): T;
22
+ }
23
+
24
+ type Envelope<T> = {
25
+ ok: true;
26
+ data: T;
27
+ } | {
28
+ ok: false;
29
+ error: KbErrorBody;
30
+ };
31
+ interface KbErrorBody {
32
+ code: ErrorCode;
33
+ message: string;
34
+ details?: unknown[];
35
+ }
36
+ type ErrorCode = 'NOT_FOUND' | 'VALIDATION_ERROR' | 'SCHEMA_CONFLICT' | 'SCHEMA_VIOLATION' | 'DUPLICATE_ID' | 'BROKEN_LINK' | 'CARDINALITY_VIOLATION' | 'ILLEGAL_TRANSITION' | 'CONCURRENCY_CONFLICT' | 'LINK_CONSTRAINT' | 'CHANGESET_ALREADY_DECIDED' | 'POLICY_DENIED' | 'MIGRATION_ERROR' | 'FEATURE_DISABLED' | 'LOCK_TIMEOUT' | 'IO_ERROR' | 'PARSE_ERROR' | 'VECTOR_INDEX_FAILED' | 'VECTOR_DIM_MISMATCH' | 'INTERNAL';
37
+ type EntityId = string;
38
+ type Handle = string;
39
+ type Ref = EntityId | Handle;
40
+ declare const isHandle: (r: Ref) => r is Handle;
41
+ type FieldValue = string | number | boolean | null | FieldValue[] | {
42
+ [k: string]: unknown;
43
+ };
44
+ interface EntityRecord {
45
+ id: EntityId;
46
+ type: string;
47
+ rev: number;
48
+ fields: Record<string, FieldValue>;
49
+ meta: EntityMeta;
50
+ }
51
+ interface EntityMeta {
52
+ created_at: string;
53
+ created_by?: string;
54
+ updated_at: string;
55
+ updated_by?: string;
56
+ last_changeset?: string;
57
+ last_verified_at?: string;
58
+ source_refs?: string[];
59
+ }
60
+ interface Link {
61
+ from: EntityId;
62
+ relation: string;
63
+ to: EntityId;
64
+ created_at: string;
65
+ created_by?: string;
66
+ changeset_id?: string;
67
+ }
68
+ type Op = CreateOp | UpdateOp | DeleteOp | LinkOp | UnlinkOp;
69
+ interface CreateOp {
70
+ op: 'create';
71
+ entity: string;
72
+ handle: Handle;
73
+ fields: Record<string, FieldValue>;
74
+ }
75
+ interface UpdateOp {
76
+ op: 'update';
77
+ id: EntityId;
78
+ base_rev: number;
79
+ set: SetSpec;
80
+ }
81
+ interface DeleteOp {
82
+ op: 'delete';
83
+ id: EntityId;
84
+ base_rev: number;
85
+ }
86
+ interface LinkOp {
87
+ op: 'link';
88
+ from: Ref;
89
+ relation: string;
90
+ to: Ref;
91
+ base_rev?: number;
92
+ }
93
+ interface UnlinkOp {
94
+ op: 'unlink';
95
+ from: EntityId;
96
+ relation: string;
97
+ to: EntityId;
98
+ }
99
+ type SetSpec = Record<string, FieldValue | ListMutation>;
100
+ interface ListMutation {
101
+ add?: unknown[];
102
+ remove?: unknown[];
103
+ }
104
+ type ChangeSetStatus = 'proposed' | 'accepted' | 'rejected' | 'superseded';
105
+ interface ChangeSet {
106
+ id: string;
107
+ status: ChangeSetStatus;
108
+ by: string;
109
+ reason: string;
110
+ created_at: string;
111
+ decided_at?: string;
112
+ decided_by?: string;
113
+ ops: Op[];
114
+ result?: AcceptResult;
115
+ conflict?: ConflictReport;
116
+ reject_reason?: string;
117
+ }
118
+ interface NewChangeSet {
119
+ by: string;
120
+ reason: string;
121
+ ops: Op[];
122
+ }
123
+ interface AcceptResult {
124
+ handle_map: Record<Handle, EntityId>;
125
+ event_ids: string[];
126
+ }
127
+ interface ConflictReport {
128
+ stale: {
129
+ id: EntityId;
130
+ base_rev: number;
131
+ current_rev: number;
132
+ }[];
133
+ }
134
+ type EventType = 'ENTITY_CREATED' | 'ENTITY_UPDATED' | 'ENTITY_DELETED' | 'LINK_CREATED' | 'LINK_REMOVED' | 'CHANGESET_ACCEPTED' | 'CHANGESET_REJECTED' | 'CHANGESET_SUPERSEDED' | 'MIGRATION_APPLIED';
135
+ interface KbEvent {
136
+ seq: number;
137
+ id: string;
138
+ type: EventType;
139
+ timestamp: string;
140
+ actor?: string;
141
+ entity_type?: string;
142
+ entity_id?: EntityId;
143
+ changeset_id?: string;
144
+ payload?: unknown;
145
+ }
146
+ type FieldType = 'text' | 'longtext' | 'int' | 'number' | 'bool' | 'enum' | 'date' | 'ref' | 'list' | 'json';
147
+ type ScalarType = Exclude<FieldType, 'list'>;
148
+ interface FieldDef {
149
+ type: FieldType;
150
+ required?: boolean;
151
+ default?: FieldValue;
152
+ unique?: boolean;
153
+ values?: string[];
154
+ items?: ScalarType;
155
+ entity?: string;
156
+ }
157
+ interface Lifecycle {
158
+ field: string;
159
+ initial: string;
160
+ transitions: Record<string, string[]>;
161
+ }
162
+ interface EntityDef {
163
+ kind: 'entity';
164
+ name: string;
165
+ id_prefix: string;
166
+ fields: Record<string, FieldDef>;
167
+ lifecycle?: Lifecycle;
168
+ search_fields?: string[];
169
+ }
170
+ type Cardinality = 'one_to_one' | 'one_to_many' | 'many_to_many';
171
+ type OnDelete = 'block' | 'cascade' | 'detach';
172
+ interface RelationDef {
173
+ kind: 'relation';
174
+ name: string;
175
+ from: string;
176
+ to: string;
177
+ cardinality: Cardinality;
178
+ on_delete?: {
179
+ from: OnDelete;
180
+ to: OnDelete;
181
+ };
182
+ }
183
+ interface IndexDef {
184
+ kind: 'index';
185
+ entity: string;
186
+ fields: string[];
187
+ }
188
+ interface KnowledgeModel {
189
+ entities: Map<string, EntityDef>;
190
+ relations: Map<string, RelationDef>;
191
+ indexes: IndexDef[];
192
+ validators: Map<string, FieldsValidator>;
193
+ }
194
+ type Direction = 'out' | 'in' | 'both';
195
+ type ScalarOrArray = FieldValue | FieldValue[];
196
+ type OperatorClause = Partial<Record<'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte' | 'in' | 'contains', FieldValue | FieldValue[]>>;
197
+ type WhereClause = Record<string, ScalarOrArray | OperatorClause>;
198
+ type GroupedLinks = {
199
+ out: Record<string, EntityId[]>;
200
+ in: Record<string, EntityId[]>;
201
+ };
202
+ interface GetQuery {
203
+ id: EntityId;
204
+ include?: 'links'[];
205
+ }
206
+ interface FindQuery {
207
+ entity: string;
208
+ where?: WhereClause;
209
+ order_by?: string;
210
+ order?: 'asc' | 'desc';
211
+ limit?: number;
212
+ offset?: number;
213
+ }
214
+ interface RelatedQuery {
215
+ id: EntityId;
216
+ depth?: number;
217
+ relations?: string[];
218
+ direction?: Direction;
219
+ }
220
+ interface SearchQuery {
221
+ text: string;
222
+ types?: string[];
223
+ limit?: number;
224
+ mode?: 'fts' | 'semantic' | 'hybrid';
225
+ }
226
+ interface GraphNode {
227
+ id: EntityId;
228
+ type: string;
229
+ fields?: Record<string, FieldValue>;
230
+ via?: string;
231
+ }
232
+ interface GraphEdge {
233
+ from: EntityId;
234
+ relation: string;
235
+ to: EntityId;
236
+ }
237
+ interface RelatedResult {
238
+ nodes: GraphNode[];
239
+ edges: GraphEdge[];
240
+ }
241
+ interface TraceResult {
242
+ root: GraphNode;
243
+ branches: TraceBranch[];
244
+ }
245
+ interface TraceBranch {
246
+ node: GraphNode;
247
+ via: string;
248
+ children: TraceBranch[];
249
+ }
250
+ interface SearchHit {
251
+ id: EntityId;
252
+ type: string;
253
+ score: number;
254
+ snippet?: string;
255
+ }
256
+ interface Hop {
257
+ rel: string;
258
+ dir: Direction;
259
+ }
260
+ type SlotDef = TraversalSlot | EventSlot;
261
+ interface TraversalSlot {
262
+ name: string;
263
+ from: '$root' | string | (string | '$root')[];
264
+ self?: boolean;
265
+ via?: Hop[];
266
+ via_any?: Hop[][];
267
+ fields?: string[];
268
+ }
269
+ interface EventSlot {
270
+ name: string;
271
+ events_for: '$slots' | string[];
272
+ window_days?: number;
273
+ limit?: number;
274
+ }
275
+ interface Recipe {
276
+ recipe: string;
277
+ root: string;
278
+ slots: SlotDef[];
279
+ }
280
+ interface ContextPack {
281
+ root: {
282
+ type: string;
283
+ id: EntityId;
284
+ };
285
+ recipe: string;
286
+ generated_at: string;
287
+ fingerprint: string;
288
+ slots: Record<string, SlotResult>;
289
+ }
290
+ type SlotResult = ContextNode | ContextNode[] | EventRef[];
291
+ interface ContextNode {
292
+ id?: EntityId;
293
+ via?: string;
294
+ [field: string]: unknown;
295
+ }
296
+ interface EventRef {
297
+ event: EventType;
298
+ entity_id?: EntityId;
299
+ at: string;
300
+ changeset_id?: string;
301
+ }
302
+ interface Config {
303
+ project: {
304
+ name: string;
305
+ kb_version: number;
306
+ };
307
+ storage: {
308
+ entities_path: string;
309
+ links_path: string;
310
+ event_log_path: string;
311
+ sqlite_path: string;
312
+ };
313
+ search: {
314
+ fts_enabled: boolean;
315
+ vector_enabled: boolean;
316
+ embedding_provider: string | null;
317
+ embedding_model?: string;
318
+ embedding_host?: string;
319
+ embedding_dim?: number;
320
+ };
321
+ docs: {
322
+ generate_markdown: boolean;
323
+ docs_path: string;
324
+ };
325
+ change_sets: {
326
+ default_mode: 'propose' | 'direct';
327
+ require_acceptance: boolean;
328
+ };
329
+ trace_paths: Record<string, Hop[][]>;
330
+ context_recipes: Record<string, Recipe>;
331
+ policies: PolicyConfig;
332
+ concurrency: {
333
+ lock_timeout_ms: number;
334
+ };
335
+ mcp: {
336
+ enabled: boolean;
337
+ port: number;
338
+ };
339
+ }
340
+ interface PolicyConfig {
341
+ default_mode: 'propose' | 'direct';
342
+ direct_write: {
343
+ allowed_actors: string[];
344
+ };
345
+ change_set_required_for: string[];
346
+ protected_entities: string[];
347
+ }
348
+ interface Violation {
349
+ code: ErrorCode;
350
+ op?: number;
351
+ entity?: string;
352
+ field?: string;
353
+ message: string;
354
+ }
355
+ interface FieldsValidator {
356
+ validate(fields: Record<string, FieldValue>): Violation[];
357
+ }
358
+
359
+ /**
360
+ * FileStore — canonical file storage for Agent KB (T07).
361
+ *
362
+ * Owns every read/write of the canonical JSON/JSONL files that form the source
363
+ * of truth. All writes are atomic (via T03 utilities). This module may only
364
+ * import from @/types, @/errors, @/util, and Node builtins.
365
+ *
366
+ * Canonical layout (relative to kbDir):
367
+ * entities/<type-kebab>/<ID>.json
368
+ * entities/_links.jsonl — current link set (full rewrite)
369
+ * events/events.jsonl — append-only event history
370
+ * change-sets/{proposed,accepted,rejected,superseded}/<ID>.json
371
+ * schema/*.json
372
+ * snapshots/ — tar archives (NOT part of fingerprint)
373
+ */
374
+
375
+ interface ChangeSetSummary {
376
+ id: string;
377
+ status: ChangeSetStatus;
378
+ by: string;
379
+ reason: string;
380
+ created_at: string;
381
+ decided_at?: string;
382
+ }
383
+
384
+ /**
385
+ * validate — dry-run a ChangeSet against canonical state (T12 §18.2).
386
+ *
387
+ * Pure read path: acquires no lock, writes no file, bumps no counter.
388
+ * Returns a ValidateResult with all violations (from the 8-rule pipeline)
389
+ * and a previewHandleMap projecting handle→EntityId WITHOUT consuming IDs.
390
+ */
391
+
392
+ /** Returned by `validate`. Exported so the facade (T15) can use it. */
393
+ interface ValidateResult {
394
+ valid: boolean;
395
+ violations: Violation[];
396
+ previewHandleMap?: Record<Handle, EntityId>;
397
+ }
398
+
399
+ /**
400
+ * EmbeddingProvider seam (T32, design §21).
401
+ *
402
+ * Provides a pluggable, synchronous embedding interface. The default provider
403
+ * is Ollama (`embedding_provider: 'ollama'`). When no provider is configured,
404
+ * the factory returns `NoneProvider`, which throws `FEATURE_DISABLED` on
405
+ * `embed()` — the same pattern used by `DisabledVectorAdapter`.
406
+ *
407
+ * Extension points (NOT implemented — out of scope for T32):
408
+ * - `'transformers'`: in-process provider; needs a Worker+Atomics sync bridge.
409
+ * - `'openai'`: remote provider via the OpenAI embeddings API.
410
+ * Both can be wired into `createEmbeddingProvider` when selected.
411
+ */
412
+
413
+ /**
414
+ * Synchronous batch embedding provider.
415
+ *
416
+ * `embed` is synchronous — see `ollama.ts` for the rationale.
417
+ *
418
+ * @property id - Provider identifier (e.g. `'ollama'`, `'none'`).
419
+ * @property dim - Dimension of every returned vector (0 for `NoneProvider`).
420
+ */
421
+ interface EmbeddingProvider {
422
+ readonly id: string;
423
+ readonly dim: number;
424
+ /**
425
+ * Embed `texts` and return one vector per input, in input order.
426
+ *
427
+ * @param texts - Strings to embed. `[]` → `[]` (no network call).
428
+ * @returns Flat array of `number[]`, each of length `dim`.
429
+ * @throws `KbError { code: 'FEATURE_DISABLED' }` when `id === 'none'`.
430
+ */
431
+ embed(texts: string[]): number[][];
432
+ }
433
+
434
+ /**
435
+ * T19 — Verify service (design §21).
436
+ *
437
+ * A READ-ONLY whole-KB health check usable in CI. Surfaces drift and invariant
438
+ * violations; NEVER mutates or auto-fixes. The factory takes the stores and
439
+ * clock as dependencies — NO write lock, NO coordinator, NO coordinator.
440
+ *
441
+ * Checks (all run on every call; divergence only when opts.events is true):
442
+ * 1. broken_link — link whose from/to entity is absent from the index
443
+ * 2. orphan — entity of a relation-participating type with zero links
444
+ * 3. invalid_status — lifecycle field holds a value outside the declared enum
445
+ * 4. duplicate_id — same id under two different type directories
446
+ * 5. stuck_changeset — proposed changeset older than stuckDays
447
+ * 6. stale_code — CodeFile entity whose resolved path does not exist
448
+ * 7. stale_entity — entity linked to a CodeFile whose ENTITY_UPDATED postdates last_verified_at
449
+ * 8. divergence — event-log shadow vs on-disk entity set disagrees
450
+ *
451
+ * Dependency direction: imports @/types, @/util, @/storage/* (file + index).
452
+ * Does NOT import facade / CLI / MCP / changeset / coordinator / migration /
453
+ * context / search.
454
+ */
455
+
456
+ type FindingKind = 'broken_link' | 'orphan' | 'invalid_status' | 'duplicate_id' | 'stuck_changeset' | 'stale_code' | 'stale_entity' | 'divergence';
457
+ interface Finding {
458
+ kind: FindingKind;
459
+ message: string;
460
+ entity_id?: EntityId;
461
+ details?: Record<string, unknown>;
462
+ }
463
+ /** ok === findings.length === 0 */
464
+ interface VerifyReport {
465
+ findings: Finding[];
466
+ ok: boolean;
467
+ }
468
+
469
+ /**
470
+ * T21 — Scan service (detect → propose).
471
+ *
472
+ * Walks the working tree, classifies files, diffs against known CodeFile
473
+ * entities, and PROPOSES a changeset for review — never writes canonical
474
+ * state directly.
475
+ *
476
+ * Dependency direction: imports @/types, @/util, node:fs, node:path, and
477
+ * the detectors module. Does NOT import facade / CLI / MCP / coordinator /
478
+ * migration / verify / docs / context / search.
479
+ *
480
+ * DEFERRED: MODIFIES / EXPOSED_BY link inference (symbol analysis, V2).
481
+ */
482
+
483
+ interface DetectedFile {
484
+ /** Relative to root, POSIX separators. */
485
+ path: string;
486
+ kind: string;
487
+ language?: string;
488
+ }
489
+ interface Candidate {
490
+ kind: 'create' | 'update' | 'gone';
491
+ path: string;
492
+ id?: EntityId;
493
+ }
494
+ interface ScanResult {
495
+ detected: DetectedFile[];
496
+ candidates: Candidate[];
497
+ /** Present only when propose:true was passed AND ops were emitted. */
498
+ changeSetId?: string;
499
+ }
500
+
501
+ /**
502
+ * Export service — T43.
503
+ *
504
+ * Produces two read-only KB projections (json + graph) from the live model and
505
+ * storage reads. Read-only, lock-free (mirrors verify / docs / scan).
506
+ *
507
+ * Dependency direction: imports @/types, @/storage/file-store. Does NOT import
508
+ * facade / CLI / MCP / coordinator / changeset / migration.
509
+ */
510
+
511
+ /**
512
+ * Payload for `export --format json`.
513
+ * All entities (with their full fields) and all links (from/relation/to).
514
+ * Deterministic: entities sorted by id, links sorted by (from, relation, to).
515
+ */
516
+ interface JsonExportPayload {
517
+ entities: EntityRecord[];
518
+ links: {
519
+ from: EntityId;
520
+ relation: string;
521
+ to: EntityId;
522
+ }[];
523
+ }
524
+ /**
525
+ * Payload for `export --format graph`.
526
+ * Nodes (one per entity) and edges (one per link) for graph tooling.
527
+ * Deterministic: nodes sorted by id, edges sorted by (from, relation, to).
528
+ */
529
+ interface GraphExportPayload {
530
+ nodes: {
531
+ id: EntityId;
532
+ type: string;
533
+ /** Present when the entity has a `name` or `title` string field. */
534
+ label?: string;
535
+ }[];
536
+ edges: {
537
+ from: EntityId;
538
+ relation: string;
539
+ to: EntityId;
540
+ }[];
541
+ }
542
+
543
+ /**
544
+ * AgentKbEngine facade + construction helpers (T15, design §9 / §10).
545
+ *
546
+ * The single shared contract every transport (CLI, MCP, in-process API) calls —
547
+ * and only this. Every method returns an `Envelope` and NEVER throws across the
548
+ * boundary: the uniform `guard()` wrapper turns a `KbError` into a typed error
549
+ * envelope and any other throw into `INTERNAL`.
550
+ *
551
+ * This module sits at the TOP of the engine layer. It may import config / schema
552
+ * / storage / engine services / util / errors / types / yaml / node — but never a
553
+ * transport (`@/cli`, `@/mcp`, `@/api`). Transport-agnostic: NO `console` /
554
+ * `process` usage.
555
+ */
556
+
557
+ interface InitResult {
558
+ path: string;
559
+ created: string[];
560
+ }
561
+ interface StatusResult {
562
+ entities: Record<string, number>;
563
+ pending_change_sets: number;
564
+ index_healthy: boolean;
565
+ /** Whether semantic/vector search is effectively enabled (a real adapter is wired). */
566
+ vector_enabled: boolean;
567
+ }
568
+ interface ReindexResult {
569
+ fingerprint: string;
570
+ }
571
+ interface SchemaApplyResult {
572
+ applied: boolean;
573
+ violations?: Violation[];
574
+ }
575
+ interface MigrateResult {
576
+ applied: string[];
577
+ }
578
+ /**
579
+ * Discriminated union: the payload type narrows with `format`.
580
+ * No `unknown` reaches consumers.
581
+ */
582
+ type ExportResult = {
583
+ format: 'json';
584
+ data: JsonExportPayload;
585
+ } | {
586
+ format: 'graph';
587
+ data: GraphExportPayload;
588
+ };
589
+ interface SchemaFieldDesc {
590
+ name: string;
591
+ type: string;
592
+ required: boolean;
593
+ /** Allowed values for an `enum` field (agent-relevant; omitted otherwise). */
594
+ values?: string[];
595
+ /** Target entity type for a `ref` field (omitted otherwise). */
596
+ entity?: string;
597
+ /** Element scalar type for a `list` field (omitted otherwise). */
598
+ items?: string;
599
+ /** Whether the field is uniquely constrained (omitted when not set). */
600
+ unique?: boolean;
601
+ /** Declared default value (omitted when the field has none). */
602
+ default?: FieldValue;
603
+ }
604
+ interface SchemaEntityDesc {
605
+ name: string;
606
+ id_prefix: string;
607
+ fields: SchemaFieldDesc[];
608
+ search_fields?: string[];
609
+ }
610
+ interface SchemaRelationDesc {
611
+ name: string;
612
+ from: string;
613
+ to: string;
614
+ cardinality: string;
615
+ on_delete?: {
616
+ from: string;
617
+ to: string;
618
+ };
619
+ }
620
+ interface SchemaDescription {
621
+ entities: SchemaEntityDesc[];
622
+ relations: SchemaRelationDesc[];
623
+ /** Context recipe names, sorted alphabetically. */
624
+ recipes: string[];
625
+ }
626
+ interface AgentKbEngine {
627
+ init(opts?: {
628
+ force?: boolean;
629
+ }): Envelope<InitResult>;
630
+ status(): Envelope<StatusResult>;
631
+ applySchema(args: {
632
+ file: string;
633
+ dryRun?: boolean;
634
+ }): Envelope<SchemaApplyResult>;
635
+ migrate(opts?: {
636
+ allowDataLoss?: boolean;
637
+ }): Envelope<MigrateResult>;
638
+ reindex(): Envelope<ReindexResult>;
639
+ get(q: GetQuery): Envelope<{
640
+ entity: EntityRecord;
641
+ links?: GroupedLinks;
642
+ }>;
643
+ find(q: FindQuery): Envelope<{
644
+ items: EntityRecord[];
645
+ total: number;
646
+ }>;
647
+ related(q: RelatedQuery): Envelope<RelatedResult>;
648
+ trace(args: {
649
+ id: EntityId;
650
+ }): Envelope<TraceResult>;
651
+ search(q: SearchQuery): Envelope<{
652
+ hits: SearchHit[];
653
+ }>;
654
+ context(args: {
655
+ id: EntityId;
656
+ recipe?: string;
657
+ maxDepth?: number;
658
+ }): Envelope<ContextPack>;
659
+ why(args: {
660
+ id: EntityId;
661
+ root: EntityId;
662
+ recipe?: string;
663
+ }): Envelope<{
664
+ via: string | null;
665
+ }>;
666
+ propose(args: {
667
+ changeSet: ChangeSet | NewChangeSet;
668
+ }): Envelope<{
669
+ id: string;
670
+ status: ChangeSetStatus;
671
+ }>;
672
+ validate(args: {
673
+ id?: string;
674
+ changeSet?: ChangeSet;
675
+ }): Envelope<ValidateResult>;
676
+ accept(args: {
677
+ id: string;
678
+ by?: string;
679
+ }): Envelope<AcceptResult>;
680
+ reject(args: {
681
+ id: string;
682
+ reason: string;
683
+ by?: string;
684
+ }): Envelope<{
685
+ status: ChangeSetStatus;
686
+ }>;
687
+ listChangeSets(args?: {
688
+ status?: ChangeSetStatus;
689
+ }): Envelope<{
690
+ changeSets: ChangeSetSummary[];
691
+ }>;
692
+ generateDocs(): Envelope<{
693
+ written: string[];
694
+ }>;
695
+ scan(opts?: {
696
+ propose?: boolean;
697
+ }): Envelope<ScanResult>;
698
+ verify(opts?: {
699
+ events?: boolean;
700
+ }): Envelope<VerifyReport>;
701
+ snapshot(args?: {
702
+ name?: string;
703
+ }): Envelope<{
704
+ path: string;
705
+ }>;
706
+ export(args: {
707
+ format: 'json' | 'graph';
708
+ }): Envelope<ExportResult>;
709
+ describeSchema(): Envelope<SchemaDescription>;
710
+ }
711
+ /**
712
+ * The object `openKb` returns: the §9 contract plus a resource-lifecycle
713
+ * `close()` (releases the IndexStore DB handle). `close()` is deliberately NOT
714
+ * part of `AgentKbEngine` — that interface is the byte-for-byte §9 transport
715
+ * surface — so it lives on this superset instead.
716
+ */
717
+ interface OpenedKb extends AgentKbEngine {
718
+ close(): void;
719
+ }
720
+
721
+ /**
722
+ * openKb — construct the AgentKbEngine facade (design §10).
723
+ *
724
+ * Loads + validates config, opens FileStore / IndexStore / WriteLock, loads the
725
+ * schema into a MUTABLE model holder, builds the StorageCoordinator, runs startup
726
+ * recovery (§15: replay an interrupted journal, else reindex on fingerprint
727
+ * drift), wires the changeset + query services with the injected clock / lock,
728
+ * and returns the facade. The facade shares the SAME model holder the services
729
+ * read through, so `reindex()` can refresh the model in place.
730
+ *
731
+ * If `.agent-kb/` is absent, only `init` is permitted; every other call returns
732
+ * `NOT_FOUND`.
733
+ *
734
+ * Dependency direction: the facade is the TOP of the engine layer — it imports
735
+ * config / schema / storage / engine services / util, never a transport.
736
+ * Transport-agnostic: no `console` / `process` usage.
737
+ */
738
+
739
+ interface OpenKbDeps {
740
+ /** Project dir; `.agent-kb` lives at `<root>/.agent-kb`. */
741
+ root: string;
742
+ /** Default: system clock. Tests inject a fixed clock. */
743
+ clock?: Clock;
744
+ /** Default: file lock at `locks/write.lock`. */
745
+ lock?: WriteLock;
746
+ /**
747
+ * Embedding provider for the real vector adapter (T33). Injection seam for
748
+ * tests (a deterministic fake) — production resolves it from config via
749
+ * `createEmbeddingProvider(config.search)`. Only consulted when
750
+ * `search.vector_enabled` is true; a `none` provider keeps vectors disabled.
751
+ */
752
+ embeddingProvider?: EmbeddingProvider;
753
+ }
754
+ declare function openKb(deps: OpenKbDeps): OpenedKb;
755
+
756
+ export { type AcceptResult, type AgentKbEngine, type Candidate, type Cardinality, type ChangeSet, type ChangeSetStatus, type ChangeSetSummary, type Config, type ConflictReport, type ContextNode, type ContextPack, type CreateOp, type DeleteOp, type DetectedFile, type Direction, type EntityDef, type EntityId, type EntityMeta, type EntityRecord, type Envelope, type ErrorCode, type EventRef, type EventSlot, type EventType, type ExportResult, type FieldDef, type FieldType, type FieldValue, type FieldsValidator, type FindQuery, type Finding, type FindingKind, type GetQuery, type GraphEdge, type GraphExportPayload, type GraphNode, type GroupedLinks, type Handle, type Hop, type IndexDef, type InitResult, type JsonExportPayload, type KbErrorBody, type KbEvent, type KnowledgeModel, type Lifecycle, type Link, type LinkOp, type ListMutation, type MigrateResult, type NewChangeSet, type OnDelete, type Op, type OpenKbDeps, type OpenedKb, type OperatorClause, type PolicyConfig, type Recipe, type Ref, type ReindexResult, type RelatedQuery, type RelatedResult, type RelationDef, type ScalarOrArray, type ScalarType, type ScanResult, type SchemaApplyResult, type SearchHit, type SearchQuery, type SetSpec, type SlotDef, type SlotResult, type StatusResult, type TraceBranch, type TraceResult, type TraversalSlot, type UnlinkOp, type UpdateOp, type ValidateResult, type VerifyReport, type Violation, type WhereClause, isHandle, openKb };