@adhd/backlog 0.1.8 → 1.0.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.
Files changed (61) hide show
  1. package/CHANGELOG.md +93 -44
  2. package/README.md +332 -81
  3. package/api.d.ts +146 -0
  4. package/cli.d.ts +45 -18
  5. package/env.d.ts +23 -3
  6. package/envelope.d.ts +163 -0
  7. package/index.d.ts +11 -10
  8. package/index.js +531 -173
  9. package/index.mjs +29814 -15647
  10. package/install-skill.d.ts +23 -0
  11. package/package.json +50 -15
  12. package/query/card.d.ts +31 -0
  13. package/query/get.d.ts +11 -0
  14. package/query/index.d.ts +67 -0
  15. package/query/markdown.d.ts +11 -0
  16. package/query/query.d.ts +131 -0
  17. package/query/resolve.d.ts +123 -0
  18. package/query/types.d.ts +450 -0
  19. package/query/views/registry.d.ts +43 -0
  20. package/query/views/semantic.d.ts +101 -0
  21. package/query/views/stats.d.ts +109 -0
  22. package/search-shortcut.d.ts +79 -0
  23. package/serve.d.ts +18 -0
  24. package/server.d.ts +139 -4
  25. package/skill/SKILL.md +619 -138
  26. package/store/graph-backlog-store.d.ts +80 -17
  27. package/store/immediate-retry.d.ts +24 -13
  28. package/store/type-policy.d.ts +4 -0
  29. package/store/vocabulary-guard.d.ts +52 -0
  30. package/version-info.d.ts +15 -0
  31. package/write/audit.d.ts +36 -0
  32. package/write/bootstrap.d.ts +123 -0
  33. package/write/catalog.d.ts +351 -0
  34. package/write/claim-lease.d.ts +21 -0
  35. package/write/claim.d.ts +80 -0
  36. package/write/create-issue.d.ts +250 -0
  37. package/write/delete.d.ts +39 -0
  38. package/write/embed-drain.d.ts +68 -0
  39. package/write/embedding-observer.d.ts +80 -0
  40. package/write/errors.d.ts +303 -0
  41. package/write/issue-status.d.ts +10 -0
  42. package/write/move.d.ts +70 -0
  43. package/write/relate.d.ts +52 -0
  44. package/write/transition.d.ts +60 -0
  45. package/write/tx.d.ts +344 -0
  46. package/write/update.d.ts +81 -0
  47. package/client.d.ts +0 -169
  48. package/markdown.d.ts +0 -75
  49. package/migration-admin.d.ts +0 -26
  50. package/model.d.ts +0 -437
  51. package/store/audit-log.d.ts +0 -16
  52. package/store/claim.d.ts +0 -24
  53. package/store/crud.d.ts +0 -62
  54. package/store/ids.d.ts +0 -24
  55. package/store/lifecycle.d.ts +0 -36
  56. package/store/mapping.d.ts +0 -101
  57. package/store/mutate-metadata.d.ts +0 -8
  58. package/store/query.d.ts +0 -68
  59. package/store/repo-migration.d.ts +0 -51
  60. package/store/serve-lock.d.ts +0 -42
  61. package/store/structure.d.ts +0 -66
@@ -0,0 +1,250 @@
1
+ import { IWriteStoreHandle } from './tx.js';
2
+ import { StoreSearchBackend } from '@adhd/sox-hybrid-search';
3
+ import { GraphBackend } from '@adhd/sox-graph-store';
4
+
5
+ /**
6
+ * A filing-time citation (§6.3.2, carried forward from the established `Citation` shape in
7
+ * spirit — `blastRadius` stays best-effort, `model.ts:110-120`). Named
8
+ * `ICitationInput` here (not the spec's bare `Citation`) per this repo's
9
+ * "prefix shared/data interfaces with `I`" convention.
10
+ */
11
+ export interface ICitationInput {
12
+ file: string;
13
+ lines?: string;
14
+ context?: string;
15
+ symbol?: string;
16
+ /** Best-effort enrichment payload — not re-specified here; carried through verbatim into the citation node's metadata. */
17
+ blastRadius?: unknown;
18
+ }
19
+ export interface ICreateIssueInput {
20
+ title: string;
21
+ body: string;
22
+ /** uid or name — resolved per §6.1; REQUIRED (every issue has a component chain). NEVER minted by this verb (§1/§6.1). */
23
+ project: string;
24
+ /** uid or name, scoped within `project` — RESOLVED ONLY, never created. Omitted (undefined) resolves to `project`'s reserved default component `(root)` (§3/§6.1/§8 AC-23) — a THIRD case, distinct from a resolved or an unresolved name. */
25
+ component?: string;
26
+ /** catalog name or uid; default is the project's configured `policy.defaultKind`, falling back to the global `"issue"` row. An unresolved NAME mints; a uid-shaped ref that does not resolve throws (§6.1). */
27
+ kind?: string;
28
+ /** catalog name or uid; default is `policy.defaultStatus`, falling back to the global `"open"` row (minted with `terminal:false` if it does not yet exist). An unresolved NAME mints with `terminal:false`; a uid-shaped ref that does not resolve throws (§6.1). */
29
+ status?: string;
30
+ /** catalog name or uid; genuinely OPTIONAL — §6.3.2 states minting behavior for a GIVEN unresolved name but, unlike `kind`/`status`, states no fallback for the omitted case; omitted therefore writes no `has_priority` edge at all (a deliberate reading of §6.3.2's more precise per-field text over §4's summary prose — see this project's own README/CHANGELOG note on this slice for the citation). An unresolved NAME mints with `rank` = one past the current max (lowest urgency); a uid-shaped ref that does not resolve throws. */
31
+ priority?: string;
32
+ citations?: ICitationInput[];
33
+ /** catalog agent name/uid; defaults to `by`. An unresolved NAME mints; a uid-shaped ref that does not resolve throws (§6.1). */
34
+ author?: string;
35
+ /** Plain metadata scalar (§6.2) — no edge. */
36
+ assignee?: string;
37
+ /**
38
+ * The item-level disclosure-contract git context — a plain metadata scalar
39
+ * (a sibling of {@link assignee}, no edge), persisted verbatim into the
40
+ * `issue` node's metadata and surfaced by reads as `IIssueCard.gitContext`.
41
+ *
42
+ * Repo `AGENTS.md`'s "Cite what you read" rule fixes the shape of a
43
+ * `Citations:` block as `Citations: [<active git context>, <agent name>,
44
+ * <active plan or task>, …]` — the git context is the block's FIRST
45
+ * element. This field carries it. It is ITEM-level, deliberately NOT a
46
+ * per-citation `ref`: the block renders it once, at its head, so a
47
+ * multi-citation item never repeats it. Omitted ⇒ nothing is stored, and
48
+ * every read/render path is byte-for-byte unchanged.
49
+ */
50
+ gitContext?: string;
51
+ /** The acting identity — agent or person (§6.3's opening rule) — REQUIRED on every mutating verb. A missing/blank value throws `InvalidArgumentError('by', ...)` before any write runs. */
52
+ by: string;
53
+ /**
54
+ * The duplicate-gate control (§6.3.2, resolved in full at §6.4). Default
55
+ * `'abort'`. Only meaningful when the pre-write similarity scan (§6.4
56
+ * point 1) surfaces ≥1 candidate at/above `project_policy.dedupe_threshold`
57
+ * — a zero-candidate scan proceeds to a normal create regardless of this
58
+ * value (§6.4 point 3, first sentence).
59
+ *
60
+ * - `'abort'` — nothing is written; `{created:false,
61
+ * reason:'duplicate-suppressed', duplicateCandidates}`.
62
+ * - `'force'` — the write proceeds to a genuinely new, distinct `uid`
63
+ * despite the match; `duplicateCandidates` is still reported.
64
+ * - `'comment'` — no new issue node is written; a `note` node is attached
65
+ * (`has_note`) to the TOP-scoring candidate instead, carrying the
66
+ * would-be issue's title+body verbatim.
67
+ */
68
+ duplicateAction?: 'abort' | 'force' | 'comment';
69
+ /**
70
+ * §4b/§6.2 — waits for the fire-and-forget on-write embedding round-trip
71
+ * (`embedding-observer.ts`'s `scheduleIssueEmbedding`) before `createIssue`
72
+ * returns, when `true` and `handle.embedding` is configured. Default
73
+ * (`false`/omitted): fire-and-forget — the embed/vector-upsert and its
74
+ * `embedding_upserted`/`embedding_failed` audit row happen in the
75
+ * background, after this function has already returned to its caller.
76
+ * A `true` value with NO `handle.embedding` configured is a harmless no-op
77
+ * (nothing to await — `scheduleIssueEmbedding` resolves immediately).
78
+ * Per-call, not per-handle — §6.2 specifies this field as kept verbatim
79
+ * on `create`/`update`, living on the input type for each verb
80
+ * (`ICreateIssueInput` here, `IUpdateIssueInput` in `update.ts`), not on
81
+ * `IDuplicateScanHandle`.
82
+ */
83
+ awaitEmbed?: boolean;
84
+ }
85
+ /**
86
+ * A dedupe candidate surfaced at filing time (§6.4), carrying the cosine
87
+ * similarity that produced it — see {@link scanForDuplicates}'s own doc
88
+ * comment for where that number comes from and why it is the only score
89
+ * this gate will accept.
90
+ */
91
+ export interface IDuplicateCandidate {
92
+ uid: string;
93
+ title: string;
94
+ /** Cosine similarity in `[0,1]`, straight off the vector channel — directly comparable to `project_policy.dedupe_threshold`. */
95
+ score: number;
96
+ }
97
+ /**
98
+ * The search substrate `createIssue`'s duplicate gate needs (§6.4 point 1),
99
+ * threaded alongside {@link IWriteStoreHandle} rather than folded into it:
100
+ * `IWriteStoreHandle` (tx.ts) is the write layer's OWN minimal dependency
101
+ * shape (`adapter`+`typePolicy`) and is not this slice's file to widen.
102
+ * Structurally — not nominally — compatible with `query/query.ts`'s
103
+ * `IQueryStoreHandle`: every real call site (`TestIssueStore` in tests,
104
+ * and the not-yet-built store-bootstrap module in production, §6.3.2's own
105
+ * `awaitEmbed` doc comment) already carries BOTH a `graph` and a `search`
106
+ * alongside the write handle's `adapter`/`typePolicy`, so a caller who
107
+ * already has an `IQueryStoreHandle`-shaped object satisfies this by
108
+ * construction — no adapter/wrapper needed.
109
+ *
110
+ * `search.embedQuery` is declared OPTIONAL here (unlike
111
+ * `IQueryStoreHandle.search.embedQuery`, which is mandatory) specifically to
112
+ * express §6.4 point 4's degraded case: a `StoreSearchBackend` can be wired
113
+ * (FTS/text always available, since it runs off the graph store directly)
114
+ * while no embedding model/vector space is configured — the search
115
+ * itself stays callable, just scoped to `signals:[{text}]` rather than
116
+ * `signals:[{text},{vec}]` (and, having no vector channel, surfacing no
117
+ * duplicate candidates — see {@link scanForDuplicates}). `search` itself stays OPTIONAL (no backend
118
+ * mounted at all) for the same "never silently go dark" posture §6.4 point 4
119
+ * states, but applied one layer further out: `scanForDuplicates` treats a
120
+ * wholly-absent backend as "scan unavailable" (zero candidates, `create`
121
+ * proceeds normally) rather than throwing — filing an issue must never hard-
122
+ * fail because the product-feature-only dedupe UX (§6.4's own framing: "a
123
+ * missed warning, not a correctness defect") happens to be unwired in a given
124
+ * environment.
125
+ */
126
+ export interface IDuplicateScanHandle {
127
+ readonly graph?: GraphBackend;
128
+ readonly search?: {
129
+ readonly backend: StoreSearchBackend;
130
+ embedQuery?(text: string): Promise<Float32Array>;
131
+ };
132
+ }
133
+ /**
134
+ * The "plain" card fields (§6.5) this verb already has in hand after a
135
+ * create — never field-projected, unlike a `query` response.
136
+ *
137
+ * BUG-APIGEN-CORE-CLIENT-BARE-NAME-COLLISION-001: named `ICreateIssueCard`,
138
+ * not `IIssueCard`, deliberately. `query/types.ts` also exports an
139
+ * `IIssueCard` (the fields-projected card `query`/`get` return, every field
140
+ * but `uid` optional) — both interfaces were reachable from `api.d.ts`'s
141
+ * type graph, and apigen's extraction (`ts-json-schema-generator`, invoked
142
+ * per-operation but apparently resolving/caching declarations by bare name
143
+ * across the whole extracted program) non-deterministically resolved the
144
+ * `IIssueCard` bare name to EITHER declaration depending on extraction
145
+ * order — confirmed empirically: `dist/index.js` (CJS) resolved `query`'s
146
+ * own `items: IIssueCard[]` to THIS file's stricter shape (wrongly requiring
147
+ * `project`/`component`/`createdAt`), while `dist/index.mjs` (ESM) failed to
148
+ * resolve it at all (`items: {}`, unconstrained) — from the exact same
149
+ * source, built in the same pass. This is what broke `backlog_query`'s MCP
150
+ * `oneOf` output-schema validation the moment `get()`'s return type union
151
+ * (AC-11) made the extractor visit both `IIssueCard` declarations. The
152
+ * correct, permanent fix is what's below: give the two interfaces distinct
153
+ * bare names so extraction can never conflate them, not a workaround in the
154
+ * `get()`/`query()` call sites. Filed as
155
+ * BUG-APIGEN-CORE-CLIENT-BARE-NAME-COLLISION-001 (apigen-core-client, out of
156
+ * this package's ownership) — this rename is the local mitigation; the
157
+ * extractor itself should also stop keying declarations by bare name.
158
+ *
159
+ * This rename fixes the CJS path (`dist/index.js`, the only bundle any real
160
+ * transport here — CLI, MCP-stdio, HTTP serve — ever spawns/requires;
161
+ * confirmed empirically, `dist/index.mjs` is loaded by none of them). It does
162
+ * NOT fix a second, DISTINCT defect also present in `dist/index.mjs`: every
163
+ * `$ref` to a NAMED exported interface (`ICreateIssueCard` here,
164
+ * `IDuplicateCandidate`, `IIssueCard`, …) dereferences to `{}` (empty/
165
+ * unconstrained) in the ESM build specifically, while inline/anonymous
166
+ * object types on the SAME operation (e.g. `ICreateIssueResult.commentedOn`)
167
+ * resolve correctly in both builds — ruling out a general extraction
168
+ * failure and pointing at `dereferenceSchema`'s named-`$ref` resolution
169
+ * specifically misbehaving under ESM. Reproduced on `create`'s `item`/
170
+ * `duplicateCandidates` fields, which this file's own diff never touched,
171
+ * so it predates and is independent of the collision above. Filed
172
+ * separately as BUG-APIGEN-CORE-CLIENT-ESM-DEREF-EMPTY-001 — not fixed
173
+ * here (out of this package's ownership, and no real consumer loads
174
+ * `dist/index.mjs` today), but must not be silently dropped.
175
+ */
176
+ export interface ICreateIssueCard {
177
+ uid: string;
178
+ title: string;
179
+ kind: string;
180
+ status: string;
181
+ priority?: string;
182
+ project: string;
183
+ component: string;
184
+ createdAt: string;
185
+ assignee?: string;
186
+ author?: string;
187
+ closedAt?: string;
188
+ /** The item-level disclosure-contract git context, echoed back from {@link ICreateIssueInput.gitContext} — present iff one was supplied. */
189
+ gitContext?: string;
190
+ }
191
+ /**
192
+ * `ICreateOutcome` (§6.3.2's Output section, verbatim shape — ONE interface
193
+ * with optional fields, deliberately NOT a discriminated union): `created`
194
+ * is the only field guaranteed present. Every other field's presence is
195
+ * conditional per §6.4/§6.3.2:
196
+ *
197
+ * - `uid`/`item` — present iff `created`.
198
+ * - `duplicateCandidates` — present iff the scan surfaced ≥1 candidate
199
+ * at/above threshold (§6.4 point 3) — on `'abort'` (suppressed) AND on
200
+ * `'force'` (written anyway, reported for audit) AND on `'comment'`.
201
+ * Absent entirely on a zero-candidate scan, regardless of
202
+ * `duplicateAction` — this is NOT an empty array in that case (§6.4 point
203
+ * 3, first sentence).
204
+ * - `reason` — present iff `!created` and the gate suppressed the write
205
+ * (`duplicateAction:'abort'`, the default).
206
+ * - `commentedOn` — present iff `duplicateAction:'comment'` fired.
207
+ * - `supersededUid` — always absent from `createIssue` alone; only the
208
+ * `supersedes` composition (§6.3.2, not yet built here) would set it.
209
+ */
210
+ export interface ICreateIssueResult {
211
+ created: boolean;
212
+ uid?: string;
213
+ item?: ICreateIssueCard;
214
+ duplicateCandidates?: IDuplicateCandidate[];
215
+ reason?: 'duplicate-suppressed';
216
+ supersededUid?: string;
217
+ commentedOn?: {
218
+ uid: string;
219
+ noteId: string;
220
+ };
221
+ }
222
+ /**
223
+ * Maximum length of the item-level `gitContext` disclosure scalar, enforced
224
+ * at WRITE time by every verb that accepts one (`create` here, `transition`).
225
+ * The field is free-form caller text that the markdown renderer interpolates
226
+ * inline (`query/markdown.ts`'s `sanitizeGitContext` is the render-side half
227
+ * of the same guarantee), so an unbounded value would let a single issue's
228
+ * citation block balloon arbitrarily. Shared by both write paths so the
229
+ * `create`/`transition` caps never drift.
230
+ */
231
+ export declare const MAX_GIT_CONTEXT_LENGTH = 512;
232
+ /** Rejects an over-long `gitContext` before any write runs (E_VALIDATION, never retried). A non-string (e.g. an untyped CLI/HTTP/MCP JSON `null`) is skipped here — the caller's own `typeof === 'string'` normalization treats it as absent. */
233
+ export declare function assertGitContextWithinCap(value: string | undefined): void;
234
+ /**
235
+ * Create a new issue (§4, §6.3.2). One `immediate` transaction; `skipDedupe:
236
+ * true` on every entity write (§1) via `writeNodeTx`.
237
+ *
238
+ * Errors: `InvalidArgumentError` (missing/blank `title`/`body`/`project`/`by`,
239
+ * or a blank `citations[i].file`), `CatalogNotFoundError('project'|'component'|
240
+ * 'kind'|'status'|'priority'|'agent', ref)` (`'component'` fires only when a
241
+ * name/uid was GIVEN and did not resolve — omitting `component` never throws
242
+ * it), `CitationUnverifiableError(file)` (policy-gated via
243
+ * `project_policy.citation_requires_sha`, and only when the project has a
244
+ * known `path` — a path-less project records `sha:"unverified"` verbatim),
245
+ * `InvalidArgumentError('duplicateAction', ...)`
246
+ * (an unrecognized value — §6.4), `WriteContentionError`/
247
+ * `WriteIOError` (§4c — an exhausted driver-level retry on the underlying
248
+ * `immediate` transaction).
249
+ */
250
+ export declare function createIssue(handle: IWriteStoreHandle & IDuplicateScanHandle, input: ICreateIssueInput): Promise<ICreateIssueResult>;
@@ -0,0 +1,39 @@
1
+ import { IWriteStoreHandle } from './tx.js';
2
+
3
+ export interface IDeleteIssueInput {
4
+ uid: string;
5
+ /** REQUIRED — the human-readable explanation for the invalidation, still a real requirement (§6.3.7). */
6
+ reason: string;
7
+ /** The acting agent or person, as a name (§6.3's opening rule). REQUIRED. */
8
+ by: string;
9
+ /**
10
+ * §4b/§6.2/§8 AC-4 ("invalidating an issue removes its vector") — waits for
11
+ * the fire-and-forget vector-deletion round-trip before `deleteIssue`
12
+ * returns, when `true` and `handle.embedding` is configured. Default
13
+ * (`false`/omitted): fire-and-forget, matching `create`/`update`'s own
14
+ * default.
15
+ */
16
+ awaitEmbed?: boolean;
17
+ }
18
+ export interface IDeleteIssueOutcome {
19
+ uid: string;
20
+ invalidated: true;
21
+ }
22
+ /**
23
+ * Soft-invalidate a live issue (§6.3.7). One `immediate` transaction: resolve
24
+ * `uid` → live `issue` node (tx-scoped, never `getNodeByUid` itself, §4c) →
25
+ * hand-composed `UPDATE node SET t_invalid = ?, meta = ? WHERE rowid = ?`
26
+ * (mirroring `invalidate`'s own SQL exactly, merging `invalidatedReason`/
27
+ * `invalidatedAt` into the EXISTING `meta` — never a wholesale replace, §4a)
28
+ * → `writeAudit` (§4a), against the SAME `tx` handle throughout. NEVER a hard
29
+ * row deletion — the row and its full audit trail survive untouched, only
30
+ * `t_invalid`/`meta` change.
31
+ *
32
+ * Errors: `InvalidArgumentError` (`uid`/`by`/`reason` missing or blank),
33
+ * `IssueNotFoundError` (no LIVE `issue` node carries `uid` — including an
34
+ * ALREADY-deleted `uid`, per this file's own doc comment on why that is a
35
+ * deliberate divergence from the library's bare `invalidate` mirror),
36
+ * `WriteContentionError`/`WriteIOError` (§4c — an exhausted driver-level
37
+ * retry on the underlying `immediate` transaction).
38
+ */
39
+ export declare function deleteIssue(handle: IWriteStoreHandle, input: IDeleteIssueInput): Promise<IDeleteIssueOutcome>;
@@ -0,0 +1,68 @@
1
+ import { StoreAdapter } from '@adhd/sox-store-adapter';
2
+
3
+ /** The round-trip an embed entry represents — mirrors `IScheduleEmbeddingInput.action`. */
4
+ export type EmbedAction = 'upsert' | 'delete';
5
+ /**
6
+ * `'pending'` until the producer settles the entry; then one of the four
7
+ * terminal outcomes. `'unrecorded'` means the round-trip failed AND its
8
+ * `embedding_failed` audit row could not be written — the outcome is not
9
+ * durably recorded anywhere, which is the loudest signal this module carries.
10
+ */
11
+ export type EmbedOutcome = 'pending' | 'upserted' | 'deleted' | 'failed' | 'unrecorded';
12
+ /** One scheduled embed, as the drain sees it. */
13
+ export interface IPendingEmbed {
14
+ /** The graph node's store-adapter `rowid` the vector is keyed on — never `uid`. */
15
+ readonly subjectRowid: number;
16
+ /** The issue's `uid` — carried for diagnostics and the `embedding_failed` audit row. */
17
+ readonly subjectUid: string;
18
+ /** The identity of whoever performed the subject write. */
19
+ readonly actor: string;
20
+ readonly action: EmbedAction;
21
+ /**
22
+ * The promise `scheduleIssueEmbedding` returned. Typed `unknown` rather than
23
+ * the producer's `Promise<EmbedOutcome>` because the drain only ever awaits
24
+ * settlement, never reads the value — the outcome is read off {@link outcome},
25
+ * which the producer sets in a `.then` on this same promise.
26
+ */
27
+ readonly settled: Promise<unknown>;
28
+ /** `'pending'` until the producer settles it. */
29
+ outcome: EmbedOutcome;
30
+ }
31
+ /** The result of a bounded drain. */
32
+ export interface IEmbedDrainResult {
33
+ /** How many entries settled (to any terminal outcome) during the drain. */
34
+ readonly drained: number;
35
+ /** Entries still `'pending'` when the bound elapsed — the close path records these durably. */
36
+ readonly stillPending: readonly IPendingEmbed[];
37
+ /** Of `stillPending`, how many the close path recorded as `embedding_failed` before closing. */
38
+ readonly recordedAsFailed: number;
39
+ /** Entries whose outcome could NOT be durably recorded — a loud, non-zero-exit signal. */
40
+ readonly unrecorded: readonly IPendingEmbed[];
41
+ }
42
+ export interface IEmbedDrainOptions {
43
+ /** Override the drain bound. A tuning threshold, never a feature gate (ADR-0013 D3). */
44
+ readonly timeoutMs?: number;
45
+ }
46
+ /** The per-adapter registry surface the producer and the close path share. */
47
+ export interface IEmbedDrainRegistry {
48
+ /** Add an entry; returns the disposer the producer calls once `settled` resolves to a recorded outcome. */
49
+ register(entry: IPendingEmbed): () => void;
50
+ /** Number of entries currently tracked (pending or retained-unrecorded). */
51
+ readonly size: number;
52
+ snapshot(): readonly IPendingEmbed[];
53
+ /** Await every in-flight embed, bounded; drain-until-empty. */
54
+ drain(opts?: IEmbedDrainOptions): Promise<IEmbedDrainResult>;
55
+ }
56
+ /**
57
+ * Tuning constant — a threshold, never a feature gate (ADR-0013 D3).
58
+ *
59
+ * The deleted `store/embed-queue.ts` drain was unbounded (no numeric guard in
60
+ * its history — verified via `git log -p` across every revision of that file),
61
+ * so there is no original value to recover. 30s is deliberately generous: a
62
+ * warm local fastembed ONNX embed is sub-second, and even a cold model load
63
+ * finishes well inside it; the bound exists only to stop a wedged round-trip
64
+ * from hanging process exit forever.
65
+ */
66
+ export declare const DEFAULT_EMBED_DRAIN_TIMEOUT_MS = 30000;
67
+ /** The registry for `adapter`, creating it on first use. */
68
+ export declare function embedDrainFor(adapter: StoreAdapter): IEmbedDrainRegistry;
@@ -0,0 +1,80 @@
1
+ import { IPendingEmbed } from './embed-drain.js';
2
+ import { IWriteStoreHandle } from './tx.js';
3
+
4
+ /**
5
+ * The `note` every close-time drain failure carries on its `embedding_failed`
6
+ * audit row — so a vector that was still in flight when the store closed is
7
+ * distinguishable, in the durable audit trail, from one whose round-trip
8
+ * itself failed (`scheduleIssueEmbedding`'s own failure note is the error
9
+ * message). See `graph-backlog-store.ts`'s `closeGraphBacklogStore`.
10
+ */
11
+ export declare const EMBED_DRAIN_TIMEOUT_NOTE = "embed did not settle before store close \u2014 recorded by the close-time drain (RAG-SPEC.md \u00A72.2)";
12
+ /**
13
+ * The exact text an issue is embedded from — `${title}\n${body}`.trim().
14
+ * **This MUST stay byte-identical to `create-issue.ts`'s `scanForDuplicates`
15
+ * text composition.** The two live in different files (this module vs.
16
+ * `create-issue.ts`) and compose text for two different purposes (the
17
+ * on-write vector vs. the pre-write duplicate-scan query vector), but both
18
+ * ultimately populate/query the SAME vector space under the SAME `modelId`.
19
+ * If the two ever diverge, `project_policy.dedupe_threshold`'s default (§6.4
20
+ * point 1's own doc comment: "the scale its own default was chosen against")
21
+ * silently stops meaning what it was calibrated against — AC-19's gate would
22
+ * drift without any test noticing, since both sides would still "work" in
23
+ * isolation. `create-issue.ts` imports this function rather than keeping its
24
+ * own copy, specifically so the two call sites cannot drift apart in source.
25
+ */
26
+ export declare function composeEmbedText(title: string, body: string): string;
27
+ interface IScheduleEmbeddingBase {
28
+ /** The graph node's store-adapter `rowid` the vector is keyed on — never `uid` (see {@link import('./tx.js').IEmbeddingBackend}'s own doc comment). */
29
+ subjectRowid: number;
30
+ /** The issue's `uid` — carried through only for the audit row's `target_uid` and diagnostic logging; never used to key the vector store. */
31
+ subjectUid: string;
32
+ /** The identity of whoever performed the write — the SAME `input.by` the verb's own subject-write audit row already recorded. */
33
+ actor: string;
34
+ }
35
+ export type IScheduleEmbeddingInput = (IScheduleEmbeddingBase & {
36
+ action: 'upsert';
37
+ content: string;
38
+ }) | (IScheduleEmbeddingBase & {
39
+ action: 'delete';
40
+ });
41
+ /**
42
+ * Schedules the embed-or-delete round-trip and REGISTERS its promise with the
43
+ * per-adapter drain registry (`write/embed-drain.ts`), so a short-lived
44
+ * process can await it before `close()` instead of losing it. A true no-op
45
+ * (no registration, no audit row) when `handle.embedding` is unconfigured —
46
+ * see `IWriteStoreHandle.embedding`'s own doc comment.
47
+ *
48
+ * **Non-async wrapper, deliberately.** The body lives in
49
+ * {@link runEmbedRoundTrip} so this function can register the promise it
50
+ * creates and read its outcome without re-awaiting it. The returned promise
51
+ * is still `Promise<void>` and still never rejects, so every existing
52
+ * `await`/fire-and-forget call site is unchanged. Callers that omit
53
+ * `awaitEmbed` (the default) get fire-and-forget — the close-time drain is
54
+ * the backstop that makes it durable anyway.
55
+ *
56
+ * Must be called strictly AFTER the caller's own `executeWriteTransaction`
57
+ * has resolved (i.e. after the subject transaction committed) — never from
58
+ * inside a transaction closure. `create-issue.ts`/`update.ts`/`delete.ts`
59
+ * each call this exactly once (twice for `update`'s body-change path — see
60
+ * this module's own doc comment) outside their `executeWriteTransaction`
61
+ * callback, and either await the returned promise (`awaitEmbed:true`) or
62
+ * let it run fire-and-forget (the default).
63
+ */
64
+ export declare function scheduleIssueEmbedding(handle: IWriteStoreHandle, input: IScheduleEmbeddingInput): Promise<void>;
65
+ /**
66
+ * Records each still-unsettled embed as a durable `embedding_failed` audit
67
+ * row, BEFORE the store connection closes — the whole point of the bounded
68
+ * drain. Called by `graph-backlog-store.ts`'s `closeGraphBacklogStore` with
69
+ * the `stillPending` list from a timed-out drain.
70
+ *
71
+ * Per entry: one `executeWriteTransaction` + `writeAudit` with the
72
+ * {@link EMBED_DRAIN_TIMEOUT_NOTE}. An entry whose audit write throws lands in
73
+ * `unrecorded` (its outcome is not durably recorded anywhere) rather than
74
+ * failing the whole loop — the remaining entries still get their chance.
75
+ */
76
+ export declare function recordUnsettledEmbedsAsFailed(handle: IWriteStoreHandle, pending: readonly IPendingEmbed[]): Promise<{
77
+ recorded: IPendingEmbed[];
78
+ unrecorded: IPendingEmbed[];
79
+ }>;
80
+ export {};