@ponraaj/dimple-sdk 0.0.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/dist/config.d.ts +13 -0
  2. package/dist/dimple.d.ts +101 -0
  3. package/dist/errors.d.ts +63 -0
  4. package/dist/index.d.ts +4 -0
  5. package/dist/index.js +8430 -0
  6. package/dist/indexer.d.ts +43 -0
  7. package/dist/internal/config/index.d.ts +4 -0
  8. package/dist/internal/config/loader.d.ts +9 -0
  9. package/dist/internal/config/schema.d.ts +176 -0
  10. package/dist/internal/config/service.d.ts +10 -0
  11. package/dist/internal/core/core.d.ts +95 -0
  12. package/dist/internal/core/index.d.ts +3 -0
  13. package/dist/internal/core/layer.d.ts +21 -0
  14. package/dist/internal/embedder/index.d.ts +4 -0
  15. package/dist/internal/embedder/openai.d.ts +8 -0
  16. package/dist/internal/embedder/provider.d.ts +29 -0
  17. package/dist/internal/embedder/registry.d.ts +10 -0
  18. package/dist/internal/embedder/transformers.d.ts +18 -0
  19. package/dist/internal/extractor/code-extractor.d.ts +15 -0
  20. package/dist/internal/extractor/content-handler.d.ts +17 -0
  21. package/dist/internal/extractor/index.d.ts +4 -0
  22. package/dist/internal/extractor/languages/index.d.ts +24 -0
  23. package/dist/internal/extractor/languages/javascript.d.ts +22 -0
  24. package/dist/internal/extractor/languages/typescript.d.ts +20 -0
  25. package/dist/internal/extractor/models/extraction.d.ts +53 -0
  26. package/dist/internal/llm/dream-classifier.d.ts +14 -0
  27. package/dist/internal/llm/index.d.ts +4 -0
  28. package/dist/internal/llm/llm.d.ts +54 -0
  29. package/dist/internal/llm/provider.d.ts +14 -0
  30. package/dist/internal/llm/service.d.ts +29 -0
  31. package/dist/internal/logger/format.d.ts +6 -0
  32. package/dist/internal/logger/index.d.ts +4 -0
  33. package/dist/internal/logger/ring.d.ts +33 -0
  34. package/dist/internal/logger/service.d.ts +50 -0
  35. package/dist/internal/logger/types.d.ts +39 -0
  36. package/dist/internal/parser/code-parser.d.ts +12 -0
  37. package/dist/internal/parser/index.d.ts +7 -0
  38. package/dist/internal/parser/language-store.d.ts +12 -0
  39. package/dist/internal/parser/models/parseRequest.d.ts +17 -0
  40. package/dist/internal/parser/models/treeHandle.d.ts +23 -0
  41. package/dist/internal/parser/tree-sitter-engine.d.ts +15 -0
  42. package/dist/internal/retriever/hybrid.d.ts +48 -0
  43. package/dist/internal/retriever/index.d.ts +3 -0
  44. package/dist/internal/retriever/rrf.d.ts +11 -0
  45. package/dist/internal/shared/errors/index.d.ts +2 -0
  46. package/dist/internal/shared/errors/parser.d.ts +21 -0
  47. package/dist/internal/shared/errors/wasm.d.ts +28 -0
  48. package/dist/internal/shared/types/index.d.ts +1 -0
  49. package/dist/internal/shared/types/language.d.ts +3 -0
  50. package/dist/internal/store/audit.d.ts +21 -0
  51. package/dist/internal/store/db-backend.d.ts +111 -0
  52. package/dist/internal/store/drizzle-schema.d.ts +1 -0
  53. package/dist/internal/store/error.d.ts +19 -0
  54. package/dist/internal/store/fnv1a32.d.ts +14 -0
  55. package/dist/internal/store/generated-migrations.d.ts +15 -0
  56. package/dist/internal/store/graph/candidates.d.ts +93 -0
  57. package/dist/internal/store/graph/concepts.d.ts +169 -0
  58. package/dist/internal/store/graph/dream.d.ts +171 -0
  59. package/dist/internal/store/graph/errors.d.ts +25 -0
  60. package/dist/internal/store/graph/index.d.ts +15 -0
  61. package/dist/internal/store/graph/observability.d.ts +37 -0
  62. package/dist/internal/store/graph/repair.d.ts +85 -0
  63. package/dist/internal/store/graph/retrieval-post-pass.d.ts +58 -0
  64. package/dist/internal/store/graph/traversal.d.ts +39 -0
  65. package/dist/internal/store/index.d.ts +38 -0
  66. package/dist/internal/store/libsql-backend.d.ts +18 -0
  67. package/dist/internal/store/models.d.ts +122 -0
  68. package/dist/internal/store/schema/graph.d.ts +970 -0
  69. package/dist/internal/store/schema/index.d.ts +7 -0
  70. package/dist/internal/store/schema/libsql.d.ts +2356 -0
  71. package/dist/internal/store/schema/metadata.d.ts +51 -0
  72. package/dist/internal/store/schema/topics.d.ts +1966 -0
  73. package/dist/internal/store/service.d.ts +162 -0
  74. package/dist/internal/store/store.d.ts +70 -0
  75. package/dist/internal/store/topics/errors.d.ts +39 -0
  76. package/dist/internal/store/topics/index.d.ts +24 -0
  77. package/dist/internal/store/topics/jobs.d.ts +149 -0
  78. package/dist/internal/store/topics/lifecycle.d.ts +126 -0
  79. package/dist/internal/store/topics/maintenance.d.ts +110 -0
  80. package/dist/internal/store/topics/observability.d.ts +102 -0
  81. package/dist/internal/store/topics/query.d.ts +43 -0
  82. package/dist/internal/store/topics/routing.d.ts +90 -0
  83. package/dist/internal/store/topics/topic-vectors.d.ts +24 -0
  84. package/dist/internal/store/topics/tree.d.ts +181 -0
  85. package/dist/internal/wasm/grammar-store.d.ts +14 -0
  86. package/dist/internal/wasm/index.d.ts +5 -0
  87. package/dist/internal/wasm/wasm-cache.d.ts +17 -0
  88. package/dist/internal/wasm/wasm-downloader.d.ts +10 -0
  89. package/dist/internal/wasm/wasm-registry.d.ts +16 -0
  90. package/dist/internal/workflow/config.d.ts +39 -0
  91. package/dist/internal/workflow/engine.d.ts +54 -0
  92. package/dist/internal/workflow/errors.d.ts +77 -0
  93. package/dist/internal/workflow/executor.d.ts +53 -0
  94. package/dist/internal/workflow/index.d.ts +13 -0
  95. package/dist/internal/workflow/persistence.d.ts +122 -0
  96. package/dist/internal/workflow/queue-store.d.ts +5 -0
  97. package/dist/internal/workflow/queue.d.ts +298 -0
  98. package/dist/internal/workflow/registry.d.ts +57 -0
  99. package/dist/internal/workflow/runtime.d.ts +161 -0
  100. package/dist/make.d.ts +27 -0
  101. package/package.json +45 -0
@@ -0,0 +1,162 @@
1
+ import { Context, Effect } from "effect";
2
+ import type { MemoryUnit, MemoryEvent, WriteMemoryInput, SweepResult, Concept } from "./models.ts";
3
+ import type { StoreError, NotFound, DuplicateActiveKey } from "./error.ts";
4
+ import type { TopicError } from "./topics/errors.ts";
5
+ import type { MaintenanceOutcome, RebalanceOutcome } from "./topics/maintenance.ts";
6
+ import type { EmbeddingModelConfig } from "./internal/config/index.js";
7
+ import type { Topic, TopicChangeLog } from "./schema/topics.ts";
8
+ import type { ArtifactEdge, EdgeKind, EdgeSource } from "./schema/graph.ts";
9
+ import type { AugmentedMemory } from "./graph/retrieval-post-pass.ts";
10
+ import type { TopicMetrics, TopicChangeLogOptions, TopicHealth } from "./topics/observability.ts";
11
+ import type { SetActiveModelResult } from "./topics/lifecycle.ts";
12
+ import type { GraphNeighborsOptions } from "./db-backend.ts";
13
+ import type { EdgeNotFound, EdgeExists } from "./graph/errors.ts";
14
+ import type { MemoryMetadata } from "./schema/metadata.ts";
15
+ import type { GraphCandidatesOptions, GraphCandidatesResult } from "./graph/candidates.ts";
16
+ import type { GraphCoverage } from "./graph/observability.ts";
17
+ /** Shape of one topic-membership reference (topicMembers). */
18
+ export interface TopicMemberRef {
19
+ readonly memoryId: string;
20
+ }
21
+ /** Aggregate tree shape (topicStats). */
22
+ export interface TopicStats {
23
+ readonly depth: number;
24
+ readonly leafCount: number;
25
+ readonly internalCount: number;
26
+ readonly totalTopics: number;
27
+ }
28
+ /** Input for `Store.addEdge` — one memory↔memory edge (the only kinds
29
+ * the lean `artifact_edges` table holds). */
30
+ export interface AddEdgeInput {
31
+ readonly fromMemoryId: string;
32
+ readonly toMemoryId: string;
33
+ readonly edgeKind: EdgeKind;
34
+ readonly source: EdgeSource;
35
+ readonly confidence?: number;
36
+ readonly weight?: number;
37
+ /** Bypass pending — for the identity rule (same logicalKey) and confirmed API writes. */
38
+ readonly confirmed?: boolean;
39
+ }
40
+ /** Options for `Store.listEdges`. Expired edges are excluded by default
41
+ * (soft-delete — history stays queryable via `includeExpired`). */
42
+ export interface ListEdgesOptions {
43
+ /** Restrict to edges touching this memory (either endpoint). */
44
+ readonly memoryId?: string;
45
+ readonly edgeKind?: EdgeKind;
46
+ readonly status?: ArtifactEdge["status"];
47
+ readonly includeExpired?: boolean;
48
+ }
49
+ /** One reached node of an `explain` traversal (the other endpoint of a
50
+ * neighbor edge, deduplicated — first reach wins, so the shallowest depth). */
51
+ export interface ExplainNode {
52
+ readonly id: string;
53
+ readonly kind: string;
54
+ readonly edgeKind: string;
55
+ readonly depth: number;
56
+ }
57
+ export interface ExplainResult {
58
+ readonly seed: {
59
+ readonly kind: string;
60
+ readonly id: string;
61
+ };
62
+ readonly nodes: readonly ExplainNode[];
63
+ /** Pending conflicts surfaced even though they are not confirmed. */
64
+ readonly pendingConflicts: readonly string[];
65
+ }
66
+ export interface Interface {
67
+ readonly write: (input: WriteMemoryInput) => Effect.Effect<MemoryUnit, StoreError | DuplicateActiveKey>;
68
+ readonly get: (id: string) => Effect.Effect<MemoryUnit, NotFound | StoreError>;
69
+ readonly getByKey: (key: string, namespace: string) => Effect.Effect<MemoryUnit, NotFound | StoreError>;
70
+ readonly recency: (namespace: string, limit: number) => Effect.Effect<ReadonlyArray<AugmentedMemory>, StoreError>;
71
+ readonly searchFts5: (query: string, namespace: string, limit: number, opts?: {
72
+ readonly topicKeys?: ReadonlyArray<string>;
73
+ /** `false` suppresses the per-leg audit row (fused-row callers). */
74
+ readonly audit?: boolean;
75
+ }) => Effect.Effect<ReadonlyArray<AugmentedMemory>, StoreError>;
76
+ readonly searchVector: (query: ReadonlyArray<number>, namespace: string, limit: number, opts?: {
77
+ readonly topicKeys?: ReadonlyArray<string>;
78
+ /** Restrict to memories annotated with ANY of these concept ids. */
79
+ readonly conceptIds?: ReadonlyArray<string>;
80
+ /** `false` suppresses the per-leg audit row (fused-row callers). */
81
+ readonly audit?: boolean;
82
+ }) => Effect.Effect<ReadonlyArray<AugmentedMemory>, StoreError>;
83
+ /** One fused retrieval-audit row (the hybrid path's audit contract). */
84
+ readonly logAudit: (input: {
85
+ readonly query: string;
86
+ readonly namespace: string;
87
+ readonly mode: string;
88
+ readonly resultCount: number;
89
+ readonly startTime: number;
90
+ readonly queryProfile?: string | null;
91
+ readonly sources?: string | null;
92
+ readonly hashSeed?: string;
93
+ }) => Effect.Effect<void, StoreError>;
94
+ readonly supersede: (key: string, namespace: string) => Effect.Effect<void, NotFound | StoreError>;
95
+ readonly forget: (id: string) => Effect.Effect<void, NotFound | StoreError>;
96
+ readonly sweep: (namespace: string) => Effect.Effect<SweepResult, StoreError>;
97
+ readonly events: (memoryId: string) => Effect.Effect<readonly MemoryEvent[], StoreError>;
98
+ readonly updateEmbedding: (id: string, embedding: ReadonlyArray<number>, model: string) => Effect.Effect<void, NotFound | StoreError>;
99
+ readonly findStale: (model: string) => Effect.Effect<readonly string[], StoreError>;
100
+ readonly createConcept: (name: string, embedding: ReadonlyArray<number>) => Effect.Effect<Concept, StoreError>;
101
+ readonly getConcept: (id: string) => Effect.Effect<Concept, NotFound | StoreError>;
102
+ readonly listConcepts: () => Effect.Effect<readonly Concept[], StoreError>;
103
+ readonly annotateMemory: (memoryId: string, conceptId: string, confidence?: number) => Effect.Effect<void, NotFound | StoreError>;
104
+ readonly getMemoryAnnotations: (memoryId: string) => Effect.Effect<readonly string[], StoreError>;
105
+ readonly queryByMetadata: (filter: Record<string, unknown>) => Effect.Effect<readonly MemoryUnit[], StoreError>;
106
+ readonly getMemoryMetadata: (id: string) => Effect.Effect<MemoryMetadata | undefined, NotFound | StoreError>;
107
+ readonly setMemoryMetadata: (id: string, metadata: MemoryMetadata) => Effect.Effect<void, NotFound | StoreError>;
108
+ readonly ensureTopicTree: (modelKey: string) => Effect.Effect<{
109
+ rootKey: string;
110
+ unassignedKey: string;
111
+ }, TopicError | StoreError>;
112
+ readonly resolveTopics: (queryVector: ReadonlyArray<number>, opts?: {
113
+ readonly beamWidth?: number;
114
+ }) => Effect.Effect<readonly string[], TopicError | StoreError>;
115
+ readonly topicMembers: (topicKey: string) => Effect.Effect<readonly TopicMemberRef[], StoreError>;
116
+ readonly listTopics: (modelKey: string) => Effect.Effect<readonly Topic[], StoreError>;
117
+ readonly topicStats: (modelKey: string) => Effect.Effect<TopicStats, StoreError>;
118
+ readonly deleteTree: (modelKey: string) => Effect.Effect<void, TopicError | StoreError>;
119
+ readonly processSplits: (modelKey: string) => Effect.Effect<number, TopicError | StoreError>;
120
+ readonly mergeOrBorrow: (modelKey: string, topicKey: string) => Effect.Effect<MaintenanceOutcome, TopicError | StoreError>;
121
+ readonly rebalanceSiblings: (modelKey: string, topicKey: string) => Effect.Effect<RebalanceOutcome, TopicError | StoreError>;
122
+ readonly setActiveModel: (model: EmbeddingModelConfig) => Effect.Effect<SetActiveModelResult, TopicError | StoreError>;
123
+ readonly isActive: (modelKey: string) => Effect.Effect<boolean, TopicError | StoreError>;
124
+ readonly topicMetrics: (modelKey: string) => Effect.Effect<TopicMetrics, TopicError | StoreError>;
125
+ readonly topicChangeLog: (modelKey: string, opts?: TopicChangeLogOptions) => Effect.Effect<ReadonlyArray<TopicChangeLog>, TopicError | StoreError>;
126
+ readonly topicHealth: (modelKey: string) => Effect.Effect<TopicHealth, TopicError | StoreError>;
127
+ /** Insert one memory↔memory edge. A `(from, to, kind)` duplicate fails
128
+ * with `EdgeExists` (the composite PK). `confirmed` bypasses pending —
129
+ * the identity rule and explicit API writes. */
130
+ readonly addEdge: (input: AddEdgeInput) => Effect.Effect<ArtifactEdge, EdgeExists | StoreError | NotFound>;
131
+ /** Soft-delete (never a hard delete of history): sets `expired_at=now`.
132
+ * Expired edges are excluded from `listEdges` and traversal by default. */
133
+ readonly expireEdge: (id: string) => Effect.Effect<void, EdgeNotFound | StoreError>;
134
+ /** Promote a pending edge to confirmed. For `memory_supersedes` the OLD
135
+ * memory (the `from` endpoint) flips `active → superseded` in the SAME
136
+ * transaction (pending candidates never mutate state). */
137
+ readonly confirmEdge: (id: string) => Effect.Effect<void, EdgeNotFound | StoreError>;
138
+ readonly rejectEdge: (id: string) => Effect.Effect<void, EdgeNotFound | StoreError>;
139
+ readonly listEdges: (opts?: ListEdgesOptions) => Effect.Effect<readonly ArtifactEdge[], StoreError>;
140
+ /** The "what is near this exact artifact" API — per-hop traversal over the
141
+ * `graph_edges` view with caps + deterministic order, plus the seed's
142
+ * pending conflicts surfaced. */
143
+ readonly explain: (node: {
144
+ readonly kind: "memory" | "topic" | "concept";
145
+ readonly id: string;
146
+ }, opts?: GraphNeighborsOptions) => Effect.Effect<ExplainResult, StoreError>;
147
+ /** Read-only review surface for the write-path candidate rules: the
148
+ * newest pending `artifact_edges` rows (created_at desc, bounded —
149
+ * default 50) plus the isolated-memory survey (count + oldest examples:
150
+ * memories with no live incident edge on either endpoint). */
151
+ readonly graphCandidates: (opts?: GraphCandidatesOptions) => Effect.Effect<GraphCandidatesResult, StoreError>;
152
+ /** The artifact graph's reach over one model's memories: total active
153
+ * memories embedded by the model, how many have at least one live
154
+ * incident edge (confirmed or pending, not expired), the coverage
155
+ * percentage, the pending candidate backlog, concept + annotated-memory
156
+ * counts, and the isolated-memory count. */
157
+ readonly graphCoverage: (modelKey: string) => Effect.Effect<GraphCoverage, StoreError>;
158
+ }
159
+ declare const Service_base: Context.ServiceClass<Service, "@dimple/store/Store", Interface>;
160
+ export declare class Service extends Service_base {
161
+ }
162
+ export {};
@@ -0,0 +1,70 @@
1
+ import { Config, Effect, Layer } from "effect";
2
+ import type { Scope } from "effect/Scope";
3
+ import type { RetrievalAuditRow } from "./models.ts";
4
+ import { Service as DbBackend } from "./db-backend.ts";
5
+ import { Service } from "./service.ts";
6
+ import { StoreError } from "./error.ts";
7
+ import { Service as ConfigService } from "./internal/config/index.js";
8
+ import { type LogConfig, type Logging } from "./internal/logger/index.js";
9
+ import type { EmbeddingModelConfig } from "./internal/config/index.js";
10
+ import type { TopicConfig as TopicConfigType } from "./schema/topics.ts";
11
+ import { GraphConfig } from "./schema/graph.ts";
12
+ /**
13
+ * Topic-tree layer options. `enabled` gates the write hook only — the
14
+ * read-side methods (resolveTopics/topicMembers/listTopics/topicStats) are
15
+ * always available; the write hook routes every embedded memory into the tree
16
+ * and enqueues splits on overflow.
17
+ *
18
+ * `enqueueSplit` is the production-path injection seam:
19
+ * when set, the write hook delegates overflow to it instead of running splits
20
+ * inline. It receives the overflowed leaf key and the layer's model key; the
21
+ * app root wires it to `JobRuntime.enqueue(TopicSplit, ...)` — `@dimple/store`
22
+ * never imports `@dimple/workflow` (the seam is a plain effect callback). The
23
+ * hook may fail with any error; the store surfaces it as `StoreError`
24
+ * (post-commit reporting — the memory write + topic routing already
25
+ * committed by the time overflow fires). When unset, overflow keeps the
26
+ * inline `processSplits` fallback (sync convenience; production
27
+ * wiring always provides the seam).
28
+ */
29
+ export interface StoreTopicsOptions {
30
+ readonly enabled?: boolean;
31
+ readonly config?: TopicConfigType;
32
+ readonly enqueueSplit?: (topicKey: string, modelKey: string) => Effect.Effect<void, unknown>;
33
+ /** Production-path seam for underflow maintenance (mirrors `enqueueSplit`):
34
+ * fires with the underfilled leaf key after a forget drops it below
35
+ * `minLeafItems`. The app root wires it to
36
+ * `JobRuntime.enqueue(TopicMerge, ...)`. When unset, the forget path runs
37
+ * the inline drain instead. */
38
+ readonly enqueueMerge?: (topicKey: string, modelKey: string) => Effect.Effect<void, unknown>;
39
+ }
40
+ export declare const layer: (url: string, options?: {
41
+ migrationFolder?: string;
42
+ /** Close the underlying client when the layer's scope closes (the
43
+ * app-root/SDK path — dispose releases the fd). Bare-store
44
+ * compositions leave it open (services may outlive the scope). */
45
+ releaseClient?: boolean;
46
+ embeddingModelConfig?: EmbeddingModelConfig;
47
+ topics?: StoreTopicsOptions;
48
+ graphConfig?: GraphConfig;
49
+ /** Retrieval-audit seam: when present, `retrieval_log` rows are
50
+ * enqueued as `audit.write` jobs instead of fire-and-forget inserts
51
+ * (the `@dimple/core` default; the query path never blocks). */
52
+ enqueueAudit?: (row: RetrievalAuditRow) => Effect.Effect<void, unknown>;
53
+ /** Structured-logging config; when present the Logging service is
54
+ * installed and Effect's default logger is replaced (all `Effect.log*`
55
+ * output flows through `@dimple/logger` per this config). */
56
+ logging?: LogConfig;
57
+ /** Default TTL for writes that omit `ttlSecs` (the config's
58
+ * `defaultTtlSecs`; default 604800). */
59
+ defaultTtlSecs?: number;
60
+ }) => Layer.Layer<Logging | DbBackend | Service, StoreError, Scope>;
61
+ export declare const layerConfig: (config: Config.Wrap<{
62
+ url: string;
63
+ }>, options?: {
64
+ migrationFolder?: string;
65
+ embeddingModelConfig?: EmbeddingModelConfig;
66
+ }) => Layer.Layer<Logging | DbBackend | Service, Config.ConfigError | StoreError, Scope>;
67
+ export declare const layerFromConfig: (options?: {
68
+ migrationFolder?: string;
69
+ }) => Layer.Layer<Service | DbBackend | Logging, StoreError, ConfigService | Scope>;
70
+ export declare const loggingFromConfig: Layer.Layer<Logging, never, ConfigService | Scope>;
@@ -0,0 +1,39 @@
1
+ import { Schema } from "effect";
2
+ declare const TopicNotFound_base: Schema.Class<TopicNotFound, Schema.TaggedStruct<"topics.TopicNotFound", {
3
+ readonly topicKey: Schema.String;
4
+ }>, import("effect/Cause").YieldableError>;
5
+ /**
6
+ * Topic-tree error types. Each distinct failure mode gets its
7
+ * own `Schema.TaggedErrorClass` so callers can `catchTag` precisely.
8
+ * `TopicError` is the tagged union; individual classes are exported for
9
+ * pattern matching.
10
+ */
11
+ /** A topic that routing/structural operations needed does not exist. */
12
+ export declare class TopicNotFound extends TopicNotFound_base {
13
+ }
14
+ declare const InvariantViolation_base: Schema.Class<InvariantViolation, Schema.TaggedStruct<"topics.InvariantViolation", {
15
+ readonly topicKey: Schema.String;
16
+ readonly violations: Schema.$Array<Schema.String>;
17
+ }>, import("effect/Cause").YieldableError>;
18
+ /** A `TopicConfig` violates the structural invariants (`validateConfig`). */
19
+ export declare class InvariantViolation extends InvariantViolation_base {
20
+ }
21
+ declare const ModelMismatch_base: Schema.Class<ModelMismatch, Schema.TaggedStruct<"topics.ModelMismatch", {
22
+ readonly expected: Schema.String;
23
+ readonly actual: Schema.String;
24
+ }>, import("effect/Cause").YieldableError>;
25
+ /** Two embeddings/models that must agree do not (dimension or model mismatch). */
26
+ export declare class ModelMismatch extends ModelMismatch_base {
27
+ }
28
+ declare const RoutingFailure_base: Schema.Class<RoutingFailure, Schema.TaggedStruct<"topics.RoutingFailure", {
29
+ readonly cause: Schema.optional<Schema.Unknown>;
30
+ }>, import("effect/Cause").YieldableError>;
31
+ /**
32
+ * Unexpected failure inside routing/seed — wraps the underlying cause
33
+ * (backend `StoreError`, driver exception, ...).
34
+ */
35
+ export declare class RoutingFailure extends RoutingFailure_base {
36
+ }
37
+ /** Tagged union of all topic-tree errors. */
38
+ export type TopicError = TopicNotFound | InvariantViolation | ModelMismatch | RoutingFailure;
39
+ export {};
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Topic-tree barrel. Re-exports the pure algorithms, seeding/routing, the
3
+ * split job, query routing, maintenance (merge/rebalance), the multi-model
4
+ * lifecycle + `memory.embed` backfill handler, observability, canonical
5
+ * error types, and the `topics` schema tables + `TopicConfig`.
6
+ *
7
+ * The canonical error classes live in `topics/errors.ts`; the job handlers
8
+ * (`topicSplitHandler` / `topicMergeHandler` / `topicRebalanceHandler` /
9
+ * `memoryEmbedHandler`) take app-root-injected deps (see their module docs).
10
+ */
11
+ export * from "./tree.ts";
12
+ export * from "./routing.ts";
13
+ export * from "./query.ts";
14
+ export * from "./lifecycle.ts";
15
+ export { memoryEmbedHandler } from "./lifecycle.ts";
16
+ export type { MemoryEmbedHandler, MemoryEmbedHandlerDeps } from "./lifecycle.ts";
17
+ export * from "./errors.ts";
18
+ export { splitTopic, topicSplitHandler, shouldSplit } from "./jobs.ts";
19
+ export type { SplitOutcome, TopicSplitHandler, TopicSplitHandlerDeps } from "./jobs.ts";
20
+ export { mergeOrBorrow, rebalanceSiblings, topicMergeHandler, topicRebalanceHandler, } from "./maintenance.ts";
21
+ export type { MaintenanceOutcome, RebalanceOutcome, TopicMaintenanceHandler, TopicMaintenanceHandlerDeps, } from "./maintenance.ts";
22
+ export { topicMetrics, topicChangeLog, topicHealth } from "./observability.ts";
23
+ export type { TopicMetrics, TopicChangeLogOptions, TopicHealth } from "./observability.ts";
24
+ export * from "../schema/topics.ts";
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Topic-tree split job — the leaf-split store operation and its
3
+ * `@dimple/workflow` handler.
4
+ *
5
+ * `splitTopic` is the pure store operation: load a leaf's members + vectors,
6
+ * run the deterministic split plan (`tree.ts`), and materialize an accepted
7
+ * split in ONE transaction (child topics, per-model topic vectors, membership
8
+ * re-parenting, relations, growth state, change log). A rejected plan defers
9
+ * the split (`split_deferred` + `splitRejectedCount`); the hard-limit backstop force-
10
+ * splits a leaf once it exceeds `splitDeferredHardLimit`.
11
+ *
12
+ * `topicSplitHandler` wraps the operation for the frozen `topic.split` job
13
+ * definition (`packages/workflow/src/queue.ts`): payload `{ topicId, modelKey }`,
14
+ * success `Schema.Void`, error `Schema.String` (the executor maps
15
+ * `JobFailure.message` into the queue error channel — never wrap in
16
+ * `JobFailed` here).
17
+ *
18
+ * ## Errors (canonical union, topics/errors.ts)
19
+ *
20
+ * - `TopicNotFound` — the topic is missing, not a leaf, or retired (inactive).
21
+ * - `ModelMismatch` — the topic belongs to another model's tree.
22
+ * - `InvariantViolation` — the plan produced an invalid child shape; the
23
+ * transaction rolls back (nothing commits).
24
+ * - `StoreError` — backend/persistence failures.
25
+ *
26
+ * A rejected split is a **logged outcome, not an error**: `splitTopic` returns
27
+ * `{ accepted: false, reason }` and the handler completes successfully.
28
+ *
29
+ * ## Determinism
30
+ *
31
+ * Child keys are content-addressable: FNV-1a 32-bit hex over `{ parent,
32
+ * members }` where `members` is the child's vector fingerprint in
33
+ * content-canonical order (`childBaseKey` below) — vectors, never memory
34
+ * ids, so identical embeddings yield identical keys across runs and DBs
35
+ * (the `-2` suffix disambiguates collisions). No randomness; ties follow
36
+ * `tree.ts` (smaller index). Audit timestamps come from the injectable
37
+ * Effect Clock (captured once per transaction, outside the closure) —
38
+ * observability data only, they never influence the split decision.
39
+ *
40
+ * ## Backend dependency note
41
+ *
42
+ * The split transaction writes topic-vector rows through the Drizzle `tx`
43
+ * handle (the backend's `upsertTopicVector` uses the standalone client and
44
+ * cannot join the transaction), so the per-model topic-vector table name and
45
+ * the `vector32`-family convert map are mirrored here from `libsql-backend.ts`
46
+ * (the canonical source — keep both in sync).
47
+ */
48
+ import { Effect } from "effect";
49
+ import { JobFailure, type Handler, type JobContext } from "./internal/workflow/index.js";
50
+ import type { EmbeddingModelConfig } from "./internal/config/index.js";
51
+ import { Service as DbBackend } from "../db-backend.ts";
52
+ import type { Interface as DbBackendInterface } from "../db-backend.ts";
53
+ import { StoreError } from "../error.ts";
54
+ import type { TopicConfig } from "../schema/topics.ts";
55
+ import type { TopicError } from "./errors.ts";
56
+ /**
57
+ * Overflow predicate for the insert path (used by routing): a leaf needs a
58
+ * split job once its member count exceeds `maxLeafItems`.
59
+ */
60
+ export declare const shouldSplit: (leafMemberCount: number, config: TopicConfig) => boolean;
61
+ /**
62
+ * Result of {@link splitTopic}: an accepted split returns the two child keys
63
+ * and their member counts (the counts let callers detect an uneven split —
64
+ * e.g. the `onUnevenSplit` seam in {@link topicSplitHandler}).
65
+ */
66
+ export type SplitOutcome = {
67
+ readonly accepted: true;
68
+ readonly children: readonly [string, string];
69
+ readonly childCounts: readonly [number, number];
70
+ } | {
71
+ readonly accepted: false;
72
+ readonly reason: "unbalanced" | "no-improvement";
73
+ };
74
+ /**
75
+ * Deterministic 2-way split of a leaf topic.
76
+ *
77
+ * Loads the leaf + its members (sorted by `memory_id`) and their vectors,
78
+ * runs {@link splitPlan}, and materializes the split in one transaction:
79
+ * children + topic vectors + membership re-parenting + relations + growth
80
+ * state + change log. Rejections defer (status `split_deferred`,
81
+ * `splitRejectedCount` bumped, one change-log row on the 0→1 flip); the
82
+ * backstop force-splits past `splitDeferredHardLimit` (logged `split_forced`).
83
+ *
84
+ * Idempotency: a leaf at or below `maxLeafItems` with no pending deferral
85
+ * returns `{ accepted: false, reason: "no-improvement" }` without writing
86
+ * anything (the early no-op that keeps concurrent overflow checks harmless);
87
+ * an already-split parent is `TopicNotFound` (the workflow handler converts
88
+ * that into a stale-job success).
89
+ */
90
+ export declare const splitTopic: (topicKey: string, model: EmbeddingModelConfig, config: TopicConfig) => Effect.Effect<SplitOutcome, TopicError | StoreError, DbBackend>;
91
+ /** Dependencies for {@link topicSplitHandler}. */
92
+ export interface TopicSplitHandlerDeps {
93
+ /** Resolves a model key to its config (the model registry). */
94
+ readonly getModelConfig: (modelKey: string) => EmbeddingModelConfig | undefined;
95
+ /** Tree-shape config for the split decision. */
96
+ readonly config: TopicConfig;
97
+ /**
98
+ * Production-path seam for sibling rebalance (mirrors `enqueueSplit`):
99
+ * fired after an ACCEPTED split whose two children are uneven
100
+ * (`max(counts) > 2 × min(counts)` — the rebalanceSiblings criterion). The
101
+ * app root wires this to `JobRuntime.enqueue(TopicRebalance, …)`; the
102
+ * rebalance job resolves the parent internally, so the child key is the
103
+ * right trigger. The store cannot fire this itself: the split runs
104
+ * asynchronously in the worker, so the post-split imbalance is only
105
+ * observable here.
106
+ */
107
+ readonly onUnevenSplit?: (childKey: string, modelKey: string) => Effect.Effect<void, unknown>;
108
+ /**
109
+ * Self-healing seam: fired for every child of an ACCEPTED split that is
110
+ * STILL over `maxLeafItems`. The app root wires this to another
111
+ * `topic.split` job, so a worker that lagged behind a write burst splits
112
+ * the overgrown leaf AND keeps re-enqueuing its over-budget children —
113
+ * the maintenance chain converges every leaf to the page budget (the
114
+ * "keep compacting on current state" rule — the write path only enqueues
115
+ * splits for leaves that CURRENT writes overflow, so children left over
116
+ * budget by a lagged split would otherwise stall forever).
117
+ */
118
+ readonly onChildOverflow?: (childKey: string, modelKey: string) => Effect.Effect<void, unknown>;
119
+ /**
120
+ * Store backend captured at registration time. Required for the standard
121
+ * workflow worker path: worker fibers only see services declared in the
122
+ * worker layer's requirements, and the JobRegistry handler type erases
123
+ * requirements — so the backend must be captured in the closure (the
124
+ * registration site is a `Layer.effect` where `yield* DbBackend` works).
125
+ * When omitted, the handler effect requires `DbBackend` from its own
126
+ * environment (direct/in-process use).
127
+ */
128
+ readonly backend?: DbBackendInterface;
129
+ }
130
+ /** The `topic.split` handler shape — `unknown` success like `JobRegistry.Handler`. */
131
+ export type TopicSplitHandler = (payload: unknown, ctx: JobContext) => Effect.Effect<unknown, JobFailure, DbBackend>;
132
+ /**
133
+ * Factory for the `topic.split` job handler.
134
+ *
135
+ * Resolves the model via `deps.getModelConfig` (missing model → `JobFailure`,
136
+ * which the executor maps to the queue's `Schema.String` error channel),
137
+ * runs {@link splitTopic}, and returns `undefined` — the definition's success
138
+ * schema is `Schema.Void` (the outcome lives in the DB: topics/memberships/
139
+ * change log, not the job result). A rejected split is a success (the deferral
140
+ * is the recorded outcome,).
141
+ *
142
+ * Pass `deps.backend` (captured at registration) to get a plain
143
+ * `JobRegistry.Handler` runnable inside the standard worker; without it the
144
+ * handler requires `DbBackend` from its own environment.
145
+ */
146
+ export declare function topicSplitHandler(deps: TopicSplitHandlerDeps & {
147
+ readonly backend: DbBackendInterface;
148
+ }): Handler;
149
+ export declare function topicSplitHandler(deps: TopicSplitHandlerDeps): TopicSplitHandler;
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Multi-model lifecycle: `setActiveModel` + `isActive`, and the
3
+ * `memory.embed` backfill job handler (`memoryEmbedHandler`) that re-embeds
4
+ * one memory with a target model — the per-memory production path for the
5
+ * switch (see the switch description below).
6
+ *
7
+ * ## The invariant
8
+ *
9
+ * Routing NEVER mixes models: `routeInsert` / `resolveTopics` take an
10
+ * explicit model and read only that model's topic rows (`model_key` scoped)
11
+ * and that model's per-model vector tables (`vec_<dims>_<model>` /
12
+ * `topic_vec_<dims>_<model>`). Since the topic tables carry a
13
+ * `(topic_key, model_key)` composite PK — every model's tree has its own
14
+ * reserved rows, so ACTIVE and RETIRED trees coexist. "The active tree" is
15
+ * simply the tree whose `topics.status = 'active'` rows belong to the
16
+ * current model; `setActiveModel` flips statuses, nothing else.
17
+ *
18
+ * ## The switch
19
+ *
20
+ * 1. `ensureTopicTree(model, config)` — seed the new tree if absent
21
+ * (idempotent, model-scoped).
22
+ * 2. `listMemoryIds(model)` — every memory with a vector for the NEW model,
23
+ * sorted by `memory_id` (deterministic order — the task's "never iterate
24
+ * unordered sets").
25
+ * 3. For each memory (sorted): already a member of the new model's tree →
26
+ * `alreadyInTree`; otherwise fetch its vector and `routeInsert` →
27
+ * `routed`. Each `routeInsert` commits in its own transaction: the batch
28
+ * is per-memory-committed by design. The durable production path is the
29
+ * `memory.embed` job (this module's `memoryEmbedHandler`) — the app root
30
+ * enqueues one job per stale memory (via `findStale`) and the worker
31
+ * re-embeds + routes each. A future batch re-route JOB with resume could
32
+ * amortize the per-memory commits; the per-memory job is correct and
33
+ * resumable today.
34
+ * 4. Deactivate every OTHER model's trees: `status = 'inactive'` where
35
+ * `model_key != model.id` and not already inactive. The distinct affected
36
+ * model keys are returned as `deactivated`. Growth state is NOT touched:
37
+ * it is per-topic audit data (`lastSplitAt`, rejection counters, …) and
38
+ * remains valid if the model is ever re-activated.
39
+ * 5. One change-log entry on the new tree's root (`__root__`),
40
+ * operation `"activate"`, reason `{ model, deactivated }`. The entry is
41
+ * written only when the activation
42
+ * changed observable state (deactivated other trees or routed memories) —
43
+ * a pure re-activation of the already-active model writes nothing
44
+ * (idempotent, deterministic). Note: `topic_change_log` has no
45
+ * `model_key` column, and the reserved root key is the same string in
46
+ * every model's tree — the entry is unambiguous in practice because it is
47
+ * the ONLY `activate` row for the root written by this switch, but
48
+ * key-based log queries (topicChangeLog) can surface it for other models.
49
+ * Documented ambiguity.
50
+ *
51
+ * ## Determinism
52
+ *
53
+ * All iteration derives from persisted state: `listMemoryIds` is ordered by
54
+ * `memory_id`, topic rows by `topic_key`, and the deactivation query filters
55
+ * on status. The audit timestamp comes from the injectable Effect Clock
56
+ * (observability only — never read by routing).
57
+ */
58
+ import { Effect } from "effect";
59
+ import type { EmbeddingModelConfig } from "./internal/config/index.js";
60
+ import { Service as DbBackend } from "../db-backend.ts";
61
+ import type { TopicConfig } from "../schema/topics.ts";
62
+ import type { TopicError } from "./errors.ts";
63
+ import { NotFound, StoreError } from "../error.ts";
64
+ import { JobFailure, type Handler, type JobContext } from "./internal/workflow/index.js";
65
+ import type { Interface as DbBackendInterface } from "../db-backend.ts";
66
+ /** Result of {@link setActiveModel}. */
67
+ export interface SetActiveModelResult {
68
+ /** Memories routed into the new model's tree by this call. */
69
+ readonly routed: number;
70
+ /** Memories already members of the new model's tree (idempotent no-ops). */
71
+ readonly alreadyInTree: number;
72
+ /** Distinct model keys whose trees this call flipped to `inactive`. */
73
+ readonly deactivated: readonly string[];
74
+ }
75
+ /**
76
+ * True when the model's tree has at least one active topic row — i.e. the
77
+ * model is the current default for routing. A model whose tree was never
78
+ * seeded, or whose every row was deactivated, is not active.
79
+ */
80
+ export declare const isActive: (modelKey: string) => Effect.Effect<boolean, TopicError | StoreError, DbBackend>;
81
+ /**
82
+ * Switch the ACTIVE tree to `model`: seed the new
83
+ * tree, re-route every memory that has a vector for the new model, retire
84
+ * every other model's trees, and record the activation in the change log.
85
+ *
86
+ * Deterministic order everywhere: `listMemoryIds` returns ids sorted by
87
+ * `memory_id`; the already-in-tree check is per memory, in that order;
88
+ * deactivation is a single status flip filtered on persisted state.
89
+ *
90
+ * Idempotent: re-activating the already-active model routes nothing
91
+ * (memberships exist → `alreadyInTree`), flips nothing (`deactivated` is
92
+ * empty), and writes no log entry.
93
+ */
94
+ export declare const setActiveModel: (model: EmbeddingModelConfig, config: TopicConfig) => Effect.Effect<SetActiveModelResult, TopicError | StoreError, DbBackend>;
95
+ /**
96
+ * Dependencies for {@link memoryEmbedHandler}: the app root composes the
97
+ * embedder registry and the store backend (the store package does not
98
+ * depend on `@dimple/embedder` — the handler is the wiring seam).
99
+ */
100
+ export interface MemoryEmbedHandlerDeps {
101
+ /** Resolves a model key to its config (the model registry). */
102
+ readonly getModelConfig: (modelKey: string) => EmbeddingModelConfig | undefined;
103
+ /** Embeds one text with the TARGET model (the app root's embedder). */
104
+ readonly embedOne: (text: string, model: EmbeddingModelConfig) => Effect.Effect<Float32Array, unknown>;
105
+ /** Loads one memory's content by id (the store backend's read path).
106
+ * `undefined` = stale re-offer (memory gone) — the handler treats it as a
107
+ * success no-op. The error channel mirrors `Store.get` (`NotFound |
108
+ * StoreError`) so the handler can `catchTag("Store.NotFound")` cleanly. */
109
+ readonly getMemoryContent: (memoryId: string) => Effect.Effect<string | undefined, NotFound | StoreError>;
110
+ /** Tree-shape config for the routing decision. */
111
+ readonly config: TopicConfig;
112
+ /** Backend captured at registration (see {@link topicSplitHandler}). */
113
+ readonly backend?: DbBackendInterface;
114
+ }
115
+ /** The `memory.embed` handler shape (see jobs.ts). */
116
+ export type MemoryEmbedHandler = (payload: unknown, ctx: JobContext) => Effect.Effect<unknown, JobFailure, DbBackend>;
117
+ /**
118
+ * Factory for the `memory.embed` job handler: re-embeds one memory with the
119
+ * target model and routes it into that model's tree. The success schema is
120
+ * `Schema.Void` — the outcome lives in the DB (vector row + membership).
121
+ * A memory with no content is a stale re-offer → success no-op.
122
+ */
123
+ export declare function memoryEmbedHandler(deps: MemoryEmbedHandlerDeps & {
124
+ readonly backend: DbBackendInterface;
125
+ }): Handler;
126
+ export declare function memoryEmbedHandler(deps: MemoryEmbedHandlerDeps): MemoryEmbedHandler;