@adhd/backlog 0.1.9 → 1.0.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.
- package/CHANGELOG.md +128 -47
- package/README.md +369 -81
- package/api.d.ts +196 -0
- package/api.ir.json +1 -0
- package/cli.d.ts +45 -18
- package/env.d.ts +23 -3
- package/envelope.d.ts +163 -0
- package/extract-live.d.ts +56 -0
- package/index.d.ts +11 -10
- package/index.js +255 -197
- package/index.mjs +11348 -19043
- package/install-skill.d.ts +23 -0
- package/ir-artifact.d.ts +86 -0
- package/package.json +51 -15
- package/query/card.d.ts +31 -0
- package/query/get.d.ts +11 -0
- package/query/index.d.ts +67 -0
- package/query/markdown.d.ts +11 -0
- package/query/query.d.ts +131 -0
- package/query/resolve.d.ts +123 -0
- package/query/types.d.ts +450 -0
- package/query/views/registry.d.ts +43 -0
- package/query/views/semantic.d.ts +101 -0
- package/query/views/stats.d.ts +109 -0
- package/search-shortcut.d.ts +79 -0
- package/serve.d.ts +18 -0
- package/server.d.ts +139 -4
- package/skill/SKILL.md +632 -138
- package/store/graph-backlog-store.d.ts +80 -17
- package/store/immediate-retry.d.ts +24 -13
- package/store/type-policy.d.ts +4 -0
- package/store/vocabulary-guard.d.ts +52 -0
- package/write/audit.d.ts +36 -0
- package/write/bootstrap.d.ts +161 -0
- package/write/catalog.d.ts +369 -0
- package/write/citation-path.d.ts +133 -0
- package/write/claim-lease.d.ts +21 -0
- package/write/claim.d.ts +80 -0
- package/write/create-issue.d.ts +253 -0
- package/write/delete.d.ts +39 -0
- package/write/embed-drain.d.ts +68 -0
- package/write/embedding-config.d.ts +81 -0
- package/write/embedding-observer.d.ts +80 -0
- package/write/errors.d.ts +365 -0
- package/write/issue-status.d.ts +10 -0
- package/write/move.d.ts +70 -0
- package/write/relate.d.ts +52 -0
- package/write/transition.d.ts +64 -0
- package/write/tx.d.ts +344 -0
- package/write/update.d.ts +81 -0
- package/client.d.ts +0 -174
- package/markdown.d.ts +0 -75
- package/migration-admin.d.ts +0 -26
- package/model.d.ts +0 -437
- package/store/audit-log.d.ts +0 -16
- package/store/claim.d.ts +0 -24
- package/store/crud.d.ts +0 -62
- package/store/ids.d.ts +0 -24
- package/store/lifecycle.d.ts +0 -36
- package/store/mapping.d.ts +0 -101
- package/store/mutate-metadata.d.ts +0 -8
- package/store/query.d.ts +0 -68
- package/store/repo-migration.d.ts +0 -51
- package/store/serve-lock.d.ts +0 -42
- package/store/structure.d.ts +0 -66
|
@@ -0,0 +1,253 @@
|
|
|
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, allowedExternalRoots)` (policy-gated
|
|
243
|
+
* via `project_policy.citation_requires_sha`, and only when the project has a
|
|
244
|
+
* known `path` — a path-less project records `sha:"unverified"` verbatim; a
|
|
245
|
+
* path-present project still rejects a target whose canonical path lies
|
|
246
|
+
* outside the project root AND every `citationAllowedExternalRoots` entry —
|
|
247
|
+
* the carve-out, BUG c6d35272 — and the error names those roots),
|
|
248
|
+
* `InvalidArgumentError('duplicateAction', ...)`
|
|
249
|
+
* (an unrecognized value — §6.4), `WriteContentionError`/
|
|
250
|
+
* `WriteIOError` (§4c — an exhausted driver-level retry on the underlying
|
|
251
|
+
* `immediate` transaction).
|
|
252
|
+
*/
|
|
253
|
+
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,81 @@
|
|
|
1
|
+
import { BacklogConfig } from '../env.js';
|
|
2
|
+
import { Scope } from '@adhd/environment-base-spec';
|
|
3
|
+
import { Environment } from '@adhd/environment';
|
|
4
|
+
|
|
5
|
+
/** The reloadable slice — exactly `BacklogConfig['embedding']`, by value. */
|
|
6
|
+
export interface EmbeddingConfig {
|
|
7
|
+
readonly enabled: boolean;
|
|
8
|
+
readonly provider: string;
|
|
9
|
+
readonly model: string;
|
|
10
|
+
}
|
|
11
|
+
/** A change detector over the config LAYERS feeding `embedding.*`. */
|
|
12
|
+
export interface EmbeddingFingerprint {
|
|
13
|
+
/**
|
|
14
|
+
* The resolution cascade's own content hash
|
|
15
|
+
* (`Environment.version.configHash`) as of the last observation. Covers
|
|
16
|
+
* EVERY field, so it is compared only to decide whether the file content
|
|
17
|
+
* actually changed — never to decide what to adopt.
|
|
18
|
+
*/
|
|
19
|
+
readonly configHash: string;
|
|
20
|
+
/** Cheap pre-gate: path -> `${mtimeMs}:${size}` (or `absent`) per layer file. */
|
|
21
|
+
readonly files: ReadonlyArray<{
|
|
22
|
+
path: string;
|
|
23
|
+
stamp: string;
|
|
24
|
+
}>;
|
|
25
|
+
}
|
|
26
|
+
export interface EmbeddingLiveConfig {
|
|
27
|
+
/** Effective NOW — the last ADOPTED on-disk value, not the startup snapshot. */
|
|
28
|
+
current(): EmbeddingConfig;
|
|
29
|
+
/** Last value observed on disk (may equal `current()` after a refresh). */
|
|
30
|
+
configured(): EmbeddingConfig;
|
|
31
|
+
/** True when on-disk says enabled but the effective value is still disabled. */
|
|
32
|
+
divergent(): boolean;
|
|
33
|
+
/** Cheap stat pre-gate, then (on change) rebuild + adopt. */
|
|
34
|
+
refresh(): {
|
|
35
|
+
changed: boolean;
|
|
36
|
+
from: EmbeddingConfig;
|
|
37
|
+
to: EmbeddingConfig;
|
|
38
|
+
};
|
|
39
|
+
fingerprint(): EmbeddingFingerprint;
|
|
40
|
+
}
|
|
41
|
+
/** Options mirroring the subset of `BuildBacklogEnvOptions` that selects the config layers. */
|
|
42
|
+
export interface BacklogConfigLayerOptions {
|
|
43
|
+
scope?: Scope;
|
|
44
|
+
adhdRoot?: string;
|
|
45
|
+
cwd?: string;
|
|
46
|
+
namespace?: string;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The resolved config LAYER FILE paths the `backlog` environment cascade reads
|
|
50
|
+
* — system/global `config.yaml` plus, when a project root resolves,
|
|
51
|
+
* `config.yaml` + `config.local.yaml` there.
|
|
52
|
+
*
|
|
53
|
+
* Enumerated from `@adhd/environment-builder`'s own `resolveEnvironmentContext`
|
|
54
|
+
* (the SAME root resolution `Environment`'s constructor runs) and the
|
|
55
|
+
* base-spec's `CONFIG_FILENAME`/`LOCAL_CONFIG_FILENAME` constants — never a
|
|
56
|
+
* hand-built `~/.adhd/...` path that could drift from the builder's scheme.
|
|
57
|
+
* Duplicates are collapsed: an explicit `adhdRoot` overrides BOTH the `global`
|
|
58
|
+
* and `system` root bases in `resolveRoots`, so those two layers resolve to the
|
|
59
|
+
* same file under test isolation and must be fingerprinted once.
|
|
60
|
+
*/
|
|
61
|
+
export declare function backlogConfigLayerFiles(opts?: BacklogConfigLayerOptions): string[];
|
|
62
|
+
/**
|
|
63
|
+
* Builds the live holder for one long-lived process.
|
|
64
|
+
*
|
|
65
|
+
* @param opts.baseline the `ctx.env` `startBacklogServer` already built — its
|
|
66
|
+
* `config.embedding` is the effective value until a refresh adopts a change,
|
|
67
|
+
* and its `version.configHash` is the "startup hash" the divergence WARN
|
|
68
|
+
* reports.
|
|
69
|
+
* @param opts.rebuild `() => buildBacklogEnv(<the SAME opts>)` — a fresh
|
|
70
|
+
* `Environment`, never a mutation of `baseline`.
|
|
71
|
+
* @param opts.layerFiles the resolved config layer paths (see
|
|
72
|
+
* {@link backlogConfigLayerFiles}); called on every observation, cheap.
|
|
73
|
+
* @param opts.log where an adoption (`'info'`) or a re-read failure (`'warn'`)
|
|
74
|
+
* is reported. The write layer's only sink is `console.error`.
|
|
75
|
+
*/
|
|
76
|
+
export declare function createEmbeddingLiveConfig(opts: {
|
|
77
|
+
baseline: Environment<BacklogConfig>;
|
|
78
|
+
rebuild: () => Environment<BacklogConfig>;
|
|
79
|
+
layerFiles: () => readonly string[];
|
|
80
|
+
log: (level: 'info' | 'warn', message: string) => void;
|
|
81
|
+
}): EmbeddingLiveConfig;
|
|
@@ -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 {};
|