@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.
- package/CHANGELOG.md +93 -44
- package/README.md +332 -81
- package/api.d.ts +146 -0
- package/cli.d.ts +45 -18
- package/env.d.ts +23 -3
- package/envelope.d.ts +163 -0
- package/index.d.ts +11 -10
- package/index.js +531 -173
- package/index.mjs +29814 -15647
- package/install-skill.d.ts +23 -0
- package/package.json +50 -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 +619 -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/version-info.d.ts +15 -0
- package/write/audit.d.ts +36 -0
- package/write/bootstrap.d.ts +123 -0
- package/write/catalog.d.ts +351 -0
- package/write/claim-lease.d.ts +21 -0
- package/write/claim.d.ts +80 -0
- package/write/create-issue.d.ts +250 -0
- package/write/delete.d.ts +39 -0
- package/write/embed-drain.d.ts +68 -0
- package/write/embedding-observer.d.ts +80 -0
- package/write/errors.d.ts +303 -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 +60 -0
- package/write/tx.d.ts +344 -0
- package/write/update.d.ts +81 -0
- package/client.d.ts +0 -169
- 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,303 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* errors.ts — the write layer error taxonomy (SPEC.md §4c).
|
|
3
|
+
*
|
|
4
|
+
* Every write-layer write ultimately fails in exactly one of two ways, never a raw
|
|
5
|
+
* driver exception and never a bare string:
|
|
6
|
+
*
|
|
7
|
+
* 1. A **named, typed, transport-facing class** thrown before any driver call
|
|
8
|
+
* ever runs (an app-level validation failure — a missing `by`, an
|
|
9
|
+
* unresolved catalog reference, a CAS conflict this spec itself detects),
|
|
10
|
+
* or thrown by {@link executeWriteTransaction} (tx.ts) after it has
|
|
11
|
+
* decided a driver-level failure is terminal.
|
|
12
|
+
* 2. The internal {@link IWriteError} envelope — `tx.ts`'s OWN pattern-matched
|
|
13
|
+
* shape for a caught driver-level failure, used only to decide
|
|
14
|
+
* retry-vs-rethrow. A caller of the write layer (createIssue, and every
|
|
15
|
+
* other §6.3 verb) never sees this shape directly; they only ever catch
|
|
16
|
+
* one of the named classes below.
|
|
17
|
+
*
|
|
18
|
+
* All named classes here are `E_VALIDATION`- or `E_CONSTRAINT`-class
|
|
19
|
+
* members of the same union (SPEC.md §4c, "Two failure-signaling shapes,
|
|
20
|
+
* reconciled into one contract") — `IssueNotFoundError`, `InvalidArgumentError`,
|
|
21
|
+
* `CatalogNotFoundError`, `ClaimHeldError`, `SingleValuedRelationConflictError`,
|
|
22
|
+
* `NoteRequiredError`, `CitationRequiredError`, `CitationUnverifiableError` are
|
|
23
|
+
* `E_VALIDATION` (never retryable — thrown before any driver call runs);
|
|
24
|
+
* `StaleSupersedeError` is the one deliberate `E_CONSTRAINT` this spec's own
|
|
25
|
+
* `supersede` CAS raises (§4c, "updateIssue's body path"). `WriteContentionError`
|
|
26
|
+
* / `WriteIOError` are the two driver-level classes tx.ts's retry loop
|
|
27
|
+
* produces on exhaustion (`E_CONTENTION` / `E_IO`).
|
|
28
|
+
*/
|
|
29
|
+
/** The closed code union every {@link IWriteError} carries (SPEC.md §4c). */
|
|
30
|
+
export type WriteErrorCode = 'E_CONTENTION' | 'E_CONSTRAINT' | 'E_VALIDATION' | 'E_IO';
|
|
31
|
+
/**
|
|
32
|
+
* The write layer's internal envelope for a caught driver-level failure
|
|
33
|
+
* (SPEC.md §4c). This is NEVER the shape a caller of `createIssue` (or any
|
|
34
|
+
* other write-layer verb) receives directly — it is what `tx.ts`'s retry loop pattern-
|
|
35
|
+
* matches on internally before re-throwing one of the named classes below.
|
|
36
|
+
* `retryable` is the ONLY field a caller should ever branch on, and only on
|
|
37
|
+
* the named classes that carry it forward — never a message-text match.
|
|
38
|
+
*/
|
|
39
|
+
export interface IWriteError {
|
|
40
|
+
code: WriteErrorCode;
|
|
41
|
+
retryable: boolean;
|
|
42
|
+
retry_after_ms?: number;
|
|
43
|
+
message: string;
|
|
44
|
+
/** The original driver-native error — never swallowed. */
|
|
45
|
+
cause: unknown;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Common base for every transport-facing error the write layer throws.
|
|
49
|
+
* `tx.ts`'s retry loop uses `instanceof BacklogWriteError` to recognize an
|
|
50
|
+
* already-decided, terminal failure (thrown from inside a verb's own
|
|
51
|
+
* transaction callback) and rethrow it untouched — never reclassify or retry
|
|
52
|
+
* a failure this layer has already named.
|
|
53
|
+
*/
|
|
54
|
+
export declare abstract class BacklogWriteError extends Error implements IWriteError {
|
|
55
|
+
abstract readonly code: WriteErrorCode;
|
|
56
|
+
abstract readonly retryable: boolean;
|
|
57
|
+
readonly retry_after_ms?: number;
|
|
58
|
+
readonly cause: unknown;
|
|
59
|
+
protected constructor(message: string, cause?: unknown, retryAfterMs?: number);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* `E_CONTENTION`, exhausted. Thrown by {@link executeWriteTransaction}
|
|
63
|
+
* (tx.ts) after 3 total attempts (1 initial + 2 retries, 250ms/500ms linear
|
|
64
|
+
* backoff — SPEC.md §4c "Retry semantics") all failed on lock/commit
|
|
65
|
+
* contention (`isBusyError`/`isConcurrentConflict`). `retryable` stays `true`
|
|
66
|
+
* even on exhaustion (ADR-0012 §4's own contract) — the caller, not the write
|
|
67
|
+
* layer, owns any retry beyond this bound.
|
|
68
|
+
*/
|
|
69
|
+
export declare class WriteContentionError extends BacklogWriteError {
|
|
70
|
+
readonly code: "E_CONTENTION";
|
|
71
|
+
readonly retryable = true;
|
|
72
|
+
constructor(retryAfterMs: number, cause: unknown);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* `E_IO`, the first (and, by design, only) occurrence — SPEC.md §4c never
|
|
76
|
+
* auto-retries `E_IO` on any write-layer verb, because `writeAudit` (§4a)
|
|
77
|
+
* rides inside literally every write transaction as an unguarded, keyless
|
|
78
|
+
* INSERT. `retryable` stays `true` (ADR-0012 §4) — the caller, who alone has
|
|
79
|
+
* the business context to check whether the write actually landed (e.g. a
|
|
80
|
+
* `query` before resubmitting), owns the decision to retry.
|
|
81
|
+
*
|
|
82
|
+
* Constructed by {@link executeWriteTransaction} (tx.ts) ONLY for
|
|
83
|
+
* {@link classifyDriverError}'s recognized-but-unclassified-database-error
|
|
84
|
+
* branch (`isDatabaseError(err)` true). A genuinely unrecognized,
|
|
85
|
+
* non-database-shaped failure (`isDatabaseError(err)` false — a deterministic
|
|
86
|
+
* bug in this module's own code) is never wrapped in this class — it would
|
|
87
|
+
* assert `retryable: true` on a failure that can never succeed on retry — and
|
|
88
|
+
* is instead rethrown untouched by the caller.
|
|
89
|
+
*/
|
|
90
|
+
export declare class WriteIOError extends BacklogWriteError {
|
|
91
|
+
readonly code: "E_IO";
|
|
92
|
+
readonly retryable = true;
|
|
93
|
+
constructor(cause: unknown);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* The one deliberate `E_CONSTRAINT` this spec's own CAS raises (SPEC.md
|
|
97
|
+
* §4c, "updateIssue's body path: supersede needs a CAS the library doesn't
|
|
98
|
+
* give it") — the `UPDATE node SET is_superseded = 1 WHERE rowid = ? AND
|
|
99
|
+
* is_superseded = 0` guard affected zero rows, meaning a concurrent writer
|
|
100
|
+
* already superseded the same target first. Never retryable — retrying a
|
|
101
|
+
* terminal CAS loss cannot change its outcome.
|
|
102
|
+
*/
|
|
103
|
+
export declare class StaleSupersedeError extends BacklogWriteError {
|
|
104
|
+
readonly uid: string;
|
|
105
|
+
readonly successorUid?: string | undefined;
|
|
106
|
+
readonly code: "E_CONSTRAINT";
|
|
107
|
+
readonly retryable = false;
|
|
108
|
+
/**
|
|
109
|
+
* @param uid The stale uid the caller passed in.
|
|
110
|
+
* @param successorUid The uid this issue lives under NOW, when it is known —
|
|
111
|
+
* the head of the `SUPERSEDES` chain starting at `uid`. Supplied by the
|
|
112
|
+
* READ path (`resolveIssueByUid`), which can walk the chain against
|
|
113
|
+
* committed state. The WRITE-path CAS deliberately omits it: it loses the
|
|
114
|
+
* race *inside* its own transaction, so any successor it read would be a
|
|
115
|
+
* uid another in-flight writer may still supersede before this caller
|
|
116
|
+
* acts on it. Absent means "not known here", never "none exists".
|
|
117
|
+
*/
|
|
118
|
+
constructor(uid: string, successorUid?: string | undefined);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* A `project`/`component`/`kind`/`status`/`priority`/`agent`/`edge_kind`
|
|
122
|
+
* reference did not resolve. Thrown for a UUID-shaped `ref` that has no live
|
|
123
|
+
* row (uids are never auto-vivified, SPEC.md §6.1), and for `project`/
|
|
124
|
+
* `component` NAMEs, which `createIssue` never mints (§1, §6.1 — component
|
|
125
|
+
* and project are resolved-only by every issue verb).
|
|
126
|
+
*/
|
|
127
|
+
export declare class CatalogNotFoundError extends BacklogWriteError {
|
|
128
|
+
readonly catalogKind: string;
|
|
129
|
+
readonly ref: string;
|
|
130
|
+
readonly code: "E_VALIDATION";
|
|
131
|
+
readonly retryable = false;
|
|
132
|
+
constructor(catalogKind: string, ref: string);
|
|
133
|
+
}
|
|
134
|
+
/** A caller-supplied argument is missing, blank, or fails a project-declared invariant. */
|
|
135
|
+
export declare class InvalidArgumentError extends BacklogWriteError {
|
|
136
|
+
readonly field: string;
|
|
137
|
+
readonly code: "E_VALIDATION";
|
|
138
|
+
readonly retryable = false;
|
|
139
|
+
constructor(field: string, detail?: string);
|
|
140
|
+
}
|
|
141
|
+
/** No live `issue` node carries the given `uid` (SPEC.md §6.1). */
|
|
142
|
+
export declare class IssueNotFoundError extends BacklogWriteError {
|
|
143
|
+
readonly uid: string;
|
|
144
|
+
readonly code: "E_VALIDATION";
|
|
145
|
+
readonly retryable = false;
|
|
146
|
+
constructor(uid: string);
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* `claim` (§6.3.5): the lease is held by someone else, not yet stale, and the
|
|
150
|
+
* caller did not pass `force:true`.
|
|
151
|
+
*/
|
|
152
|
+
export declare class ClaimHeldError extends BacklogWriteError {
|
|
153
|
+
readonly heldBy: string;
|
|
154
|
+
readonly heldSince: string;
|
|
155
|
+
readonly code: "E_VALIDATION";
|
|
156
|
+
readonly retryable = false;
|
|
157
|
+
constructor(heldBy: string, heldSince: string);
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* `claim` (§6.3.5): the target issue's current status is `terminal` — there
|
|
161
|
+
* is nothing left to lease on an already-closed issue. Re-open via
|
|
162
|
+
* `transition` to a non-terminal status instead (SPEC.md's own `closedAt`
|
|
163
|
+
* clearing rule, §6.3.4) — claiming a terminal issue is not a documented or
|
|
164
|
+
* intended step in that flow.
|
|
165
|
+
*/
|
|
166
|
+
export declare class IssueTerminalError extends BacklogWriteError {
|
|
167
|
+
readonly uid: string;
|
|
168
|
+
readonly status: string;
|
|
169
|
+
readonly code: "E_VALIDATION";
|
|
170
|
+
readonly retryable = false;
|
|
171
|
+
constructor(uid: string, status: string);
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* `relate` (§6.3.6): a single-valued rel already has a value on the CAPPED
|
|
175
|
+
* side, and it is not the one being added. This is the SAME
|
|
176
|
+
* `edge_kind.multiplicity` gate every edge write runs (§2, `checkMultiplicityTx`
|
|
177
|
+
* in tx.ts) — `relate`'s `n:1` rels (`supersedes`, `duplicate_of`, `part_of`)
|
|
178
|
+
* are its most visible caller, but the gate is generic over BOTH capped
|
|
179
|
+
* multiplicities `edge_kind` declares (§2):
|
|
180
|
+
*
|
|
181
|
+
* - `n:1` — the SOURCE's out-degree is capped at one (`side: 'source'`): the
|
|
182
|
+
* source already points at a different target.
|
|
183
|
+
* - `1:n` — the TARGET's in-degree is capped at one (`side: 'target'`): the
|
|
184
|
+
* target is already pointed at by a different source.
|
|
185
|
+
*
|
|
186
|
+
* Deliberately a single named-options constructor, not positional
|
|
187
|
+
* `(a, b, c)` args — `checkMultiplicityTx`'s two branches resolve the capped
|
|
188
|
+
* uid and the conflicting uid via DIFFERENT SQL joins (one via `e.dst`, one
|
|
189
|
+
* via `e.src`), and two same-shaped `string` positional args are trivially
|
|
190
|
+
* swappable by a future edit without a type error to catch it (exactly what
|
|
191
|
+
* happened to the 1:n branch before this fix — the message string still read
|
|
192
|
+
* as plausible English with the values swapped, which is how it went
|
|
193
|
+
* unnoticed). Named fields make the mistake a call site can no longer make
|
|
194
|
+
* silently.
|
|
195
|
+
*/
|
|
196
|
+
export declare class SingleValuedRelationConflictError extends BacklogWriteError {
|
|
197
|
+
readonly code: "E_VALIDATION";
|
|
198
|
+
readonly retryable = false;
|
|
199
|
+
/** Which side of `rel` this multiplicity rule caps at one value. */
|
|
200
|
+
readonly side: 'source' | 'target';
|
|
201
|
+
/** The uid of the node whose `side` is capped (already at its one allowed value). */
|
|
202
|
+
readonly cappedUid: string;
|
|
203
|
+
readonly rel: string;
|
|
204
|
+
/** The uid of the pre-existing OTHER endpoint already occupying the capped side's one slot. */
|
|
205
|
+
readonly conflictingUid: string;
|
|
206
|
+
constructor(input: {
|
|
207
|
+
side: 'source' | 'target';
|
|
208
|
+
cappedUid: string;
|
|
209
|
+
rel: string;
|
|
210
|
+
conflictingUid: string;
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* A citation's `sha` resolved to the `"unverified"` sentinel (§8.5's
|
|
215
|
+
* two-branch rule) and `project_policy.citation_requires_sha` (default
|
|
216
|
+
* `true`) rejects that. The gate applies only where verification is POSSIBLE:
|
|
217
|
+
* the owning project has a non-empty filesystem `path` and the cited file is
|
|
218
|
+
* missing (or escapes the project root). A PATH-LESS project cannot hash its
|
|
219
|
+
* citations at all, so the gate is waived and `sha:"unverified"` is persisted
|
|
220
|
+
* verbatim — `CitationUnverifiableError` is never thrown for it.
|
|
221
|
+
*/
|
|
222
|
+
export declare class CitationUnverifiableError extends BacklogWriteError {
|
|
223
|
+
readonly target: string;
|
|
224
|
+
readonly code: "E_VALIDATION";
|
|
225
|
+
readonly retryable = false;
|
|
226
|
+
constructor(target: string);
|
|
227
|
+
}
|
|
228
|
+
/** `transition` (§6.3.4): `project_policy.transition_requires_note` (default `true`) and no `note` was given. */
|
|
229
|
+
export declare class NoteRequiredError extends BacklogWriteError {
|
|
230
|
+
readonly uid: string;
|
|
231
|
+
readonly code: "E_VALIDATION";
|
|
232
|
+
readonly retryable = false;
|
|
233
|
+
constructor(uid: string);
|
|
234
|
+
}
|
|
235
|
+
/** `transition` (§6.3.4): `project_policy.citation_required` (default `false`) is set, the target status is terminal, and no citation was given. */
|
|
236
|
+
export declare class CitationRequiredError extends BacklogWriteError {
|
|
237
|
+
readonly uid: string;
|
|
238
|
+
readonly code: "E_VALIDATION";
|
|
239
|
+
readonly retryable = false;
|
|
240
|
+
constructor(uid: string);
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* The read layer's own validation class (SPEC.md §6.5) — an unknown `fields`
|
|
244
|
+
* entry (`assertKnownFields`) or an out-of-range/non-integral `limit`
|
|
245
|
+
* (`assertQueryLimit`, "a cap that isn't an error is indistinguishable from a
|
|
246
|
+
* complete result"). Declared here, not in `src/query/`, per this file's own
|
|
247
|
+
* "do not invent parallel ones" rule: it is the SAME `E_VALIDATION`-class
|
|
248
|
+
* member of the write layer's error union (§4c), just raised by the read
|
|
249
|
+
* surface — a `query`/`get` caller catches it identically to any other named
|
|
250
|
+
* class in this module. `field` names the offending input key
|
|
251
|
+
* (`'fields'`/`'limit'`); `detail` carries the specific reason (which unknown
|
|
252
|
+
* name, or which bound was violated).
|
|
253
|
+
*/
|
|
254
|
+
export declare class BacklogValidationError extends BacklogWriteError {
|
|
255
|
+
readonly field: string;
|
|
256
|
+
readonly code: "E_VALIDATION";
|
|
257
|
+
readonly retryable = false;
|
|
258
|
+
constructor(field: string, detail?: string);
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Classify a caught, RAW driver-level error (never one of our own
|
|
262
|
+
* {@link BacklogWriteError} subclasses — those are already decided and must
|
|
263
|
+
* never reach this function) into the internal {@link IWriteError} envelope,
|
|
264
|
+
* using `@adhd/sox-store-adapter`'s portable, driver-shaped detection
|
|
265
|
+
* predicates (SPEC.md §4c) — never a raw `err.code`/message match of our
|
|
266
|
+
* own, since Turso's `err.code` carries no discriminating information
|
|
267
|
+
* (ADR-0012 §3).
|
|
268
|
+
*
|
|
269
|
+
* `isDatabaseError` (`@adhd/sox-store-adapter`'s "is this err shaped like a
|
|
270
|
+
* recognized database error AT ALL" predicate) is what actually distinguishes
|
|
271
|
+
* the two `E_IO` sub-cases the SPEC.md §4c table collapses into one row
|
|
272
|
+
* ("Unclassified driver/connection failure | `E_IO` | `isDatabaseError`
|
|
273
|
+
* catch-all | `true`"):
|
|
274
|
+
*
|
|
275
|
+
* - A RECOGNIZED-but-otherwise-unclassified database error (`isDatabaseError`
|
|
276
|
+
* true — e.g. a driver/connection fault that isn't busy/contention/unique/FK)
|
|
277
|
+
* is the case that table row actually describes: `E_IO`, `retryable: true`,
|
|
278
|
+
* surfaced once by {@link executeWriteTransaction} (tx.ts) as
|
|
279
|
+
* {@link WriteIOError} and never auto-retried (§4c "Retry semantics" — the
|
|
280
|
+
* audit-atomicity hazard applies uniformly).
|
|
281
|
+
* - A GENUINELY UNRECOGNIZED failure — `isDatabaseError` false, meaning it
|
|
282
|
+
* never even reached the driver (a `TypeError`/`SyntaxError`/other
|
|
283
|
+
* deterministic bug in this module's own code, e.g. a `JSON.parse` thrown
|
|
284
|
+
* from inside a verb's transaction callback) — is NOT a database fault at
|
|
285
|
+
* all and must never be reported `retryable: true`: a caller honouring
|
|
286
|
+
* `retryable` would retry a failure that can never succeed. This still
|
|
287
|
+
* returns `code: 'E_IO'` (the closed `WriteErrorCode` union, SPEC.md §4c,
|
|
288
|
+
* has no fifth bucket for "not a driver error at all" and this failure did
|
|
289
|
+
* reach the transaction boundary same as the recognized case) but with
|
|
290
|
+
* `retryable: false` — the discriminator {@link executeWriteTransaction}
|
|
291
|
+
* uses to skip {@link WriteIOError} (which hardcodes `retryable: true`,
|
|
292
|
+
* ADR-0012 §4) for this branch and instead rethrow the original error
|
|
293
|
+
* UNTOUCHED, exactly like the unclassified-`E_CONSTRAINT` catch-all below
|
|
294
|
+
* it: a raw `TypeError`/`SyntaxError` is *more* diagnostic than a generic
|
|
295
|
+
* `WriteIOError`, not less.
|
|
296
|
+
*/
|
|
297
|
+
export declare function classifyDriverError(err: unknown): IWriteError;
|
|
298
|
+
/**
|
|
299
|
+
* Rejects the specific bare-role literals SPEC.md calls out by name, on top
|
|
300
|
+
* of whatever blank/missing check the call site already runs. Call this
|
|
301
|
+
* immediately after `assertNonBlank('by', input.by)` at every mutating verb.
|
|
302
|
+
*/
|
|
303
|
+
export declare function assertNotBareRoleLiteral(field: string, value: string): void;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ITxNodeRow } from './tx.js';
|
|
2
|
+
import { AdapterTransaction } from '@adhd/sox-store-adapter';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* `caller` is embedded in the invariant-violation error messages so a thrown
|
|
6
|
+
* `Error` still reads as coming from the verb that actually called this,
|
|
7
|
+
* matching the per-file error-message convention both callers already used
|
|
8
|
+
* before this was extracted.
|
|
9
|
+
*/
|
|
10
|
+
export declare function resolveIssueStatusTx(tx: AdapterTransaction, issueRowid: number, caller: string): Promise<ITxNodeRow>;
|
package/write/move.d.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { IWriteStoreHandle } from './tx.js';
|
|
2
|
+
|
|
3
|
+
export interface IMoveIssueInput {
|
|
4
|
+
/** The `issue` uid to move (§6.3, an "Issue verb"). */
|
|
5
|
+
uid: string;
|
|
6
|
+
/**
|
|
7
|
+
* uid or name of the destination project, resolved per §6.1 —
|
|
8
|
+
* RESOLVE-ONLY, exactly like `createIssue`'s own `project` field: never
|
|
9
|
+
* minted by this verb. Omitted (undefined) is a distinct third case, never
|
|
10
|
+
* an error: it resolves to the issue's OWN CURRENT project (a
|
|
11
|
+
* component-only move — see this file's own doc comment).
|
|
12
|
+
*/
|
|
13
|
+
toProject?: string;
|
|
14
|
+
/**
|
|
15
|
+
* uid or name of the destination component, scoped within the RESOLVED
|
|
16
|
+
* destination project (whichever project that is), resolved per §6.1 —
|
|
17
|
+
* RESOLVE-ONLY, never minted (see §6.1's project-vs-component asymmetry:
|
|
18
|
+
* use `upsertComponent` first if the component does not yet exist).
|
|
19
|
+
* Omitted (undefined) resolves instead to the destination project's
|
|
20
|
+
* reserved default component `(root)`, already guaranteed live by
|
|
21
|
+
* `upsertProject` (§3/§8 AC-23) — never minted here either.
|
|
22
|
+
*/
|
|
23
|
+
toComponent?: string;
|
|
24
|
+
/** The acting agent or person performing the move (§6.3's opening rule). REQUIRED. */
|
|
25
|
+
by: string;
|
|
26
|
+
}
|
|
27
|
+
export interface IMoveIssueOutcome {
|
|
28
|
+
uid: string;
|
|
29
|
+
/** `true` when the resolved destination component is the SAME live component the issue already occupied — an idempotent call, stated rather than disguised (see this file's own doc comment). No edge was invalidated or written, and no audit row was produced. */
|
|
30
|
+
noop: boolean;
|
|
31
|
+
fromProject: string;
|
|
32
|
+
toProject: string;
|
|
33
|
+
fromComponent: string;
|
|
34
|
+
toComponent: string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Reparent an issue onto a (possibly different) project's (possibly
|
|
38
|
+
* different) component (§6.3.6, §8 AC-17). One `immediate` transaction:
|
|
39
|
+
* resolve `uid` → live `issue` node (tx-scoped, §4c) → walk its CURRENT
|
|
40
|
+
* `owns_component`/`owns_project` edges → resolve the DESTINATION project
|
|
41
|
+
* (given, or the current one) → resolve the DESTINATION component within
|
|
42
|
+
* that project (given, or that project's reserved `(root)`, §8 AC-23) → if
|
|
43
|
+
* the destination component is the SAME live component the issue already
|
|
44
|
+
* occupies, return a no-op outcome (nothing invalidated, nothing written, no
|
|
45
|
+
* audit — see this file's own doc comment); otherwise hand-composed
|
|
46
|
+
* `invalidateEdgeTx` (the OLD `owns_component` edge) THEN hand-composed
|
|
47
|
+
* `writeEdgeTx` (the NEW one) THEN `writeAudit` — all against the SAME `tx`,
|
|
48
|
+
* so the invalidate and the write either both land or neither does (never a
|
|
49
|
+
* write-then-write that can half-apply). The invalidate runs strictly BEFORE
|
|
50
|
+
* the write so `writeEdgeTx`'s own `1:n`-multiplicity check (the target
|
|
51
|
+
* issue's in-degree capped at one, tx.ts's `checkMultiplicityTx`) sees the
|
|
52
|
+
* old edge already retired and never raises a false
|
|
53
|
+
* `SingleValuedRelationConflictError` against the issue's own prior
|
|
54
|
+
* placement.
|
|
55
|
+
*
|
|
56
|
+
* Errors: `InvalidArgumentError` (`uid`/`by` missing/blank),
|
|
57
|
+
* `IssueNotFoundError` (no live `issue` node carries `uid`),
|
|
58
|
+
* `CatalogNotFoundError('project', toProject)` (a given `toProject` did not
|
|
59
|
+
* resolve — resolve-only, never minted, exactly like `createIssue`'s own
|
|
60
|
+
* `project` field), `CatalogNotFoundError('component', toComponent)` (a given
|
|
61
|
+
* `toComponent` did not resolve WITHIN the resolved destination project —
|
|
62
|
+
* resolve-only, never minted; a uid belonging to a DIFFERENT project is
|
|
63
|
+
* treated identically to an unresolved ref, per `resolveComponentTx`'s own
|
|
64
|
+
* contract), `SingleValuedRelationConflictError` (defensive — the generic
|
|
65
|
+
* `1:n` multiplicity guard `writeEdgeTx` runs for every edge write; expected
|
|
66
|
+
* unreachable here given the invalidate-before-write ordering above),
|
|
67
|
+
* `WriteContentionError`/`WriteIOError` (§4c — an exhausted driver-level
|
|
68
|
+
* retry on the underlying `immediate` transaction).
|
|
69
|
+
*/
|
|
70
|
+
export declare function move(handle: IWriteStoreHandle, input: IMoveIssueInput): Promise<IMoveIssueOutcome>;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { IWriteStoreHandle } from './tx.js';
|
|
2
|
+
|
|
3
|
+
/** The closed `rel` union `relate` accepts (§3/§6.3.6) — issue → issue in every case. */
|
|
4
|
+
export type RelateRel = 'relates_to' | 'supersedes' | 'blocks' | 'duplicate_of' | 'part_of';
|
|
5
|
+
export interface IRelateInput {
|
|
6
|
+
/** The relation's SOURCE `issue` uid (§1: uid is the only identifier — never a name, never repo-scoped). */
|
|
7
|
+
sourceUid: string;
|
|
8
|
+
/** The relation's TARGET `issue` uid — may belong to ANY project, including a different one than `sourceUid` (see this file's own doc comment on the resolved cross-project gap). */
|
|
9
|
+
targetUid: string;
|
|
10
|
+
rel: RelateRel;
|
|
11
|
+
action: 'add' | 'remove';
|
|
12
|
+
/** The acting agent or person performing the change (§6.3's opening rule). REQUIRED. */
|
|
13
|
+
by: string;
|
|
14
|
+
}
|
|
15
|
+
export interface IRelateOutcome {
|
|
16
|
+
sourceUid: string;
|
|
17
|
+
targetUid: string;
|
|
18
|
+
rel: string;
|
|
19
|
+
action: 'add' | 'remove';
|
|
20
|
+
/** `true` when `add` found an already-live matching edge, or `remove` found none — an idempotent call, stated rather than disguised (§6.3.6). No edge was invalidated or written, and no audit row was produced. */
|
|
21
|
+
noop: boolean;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Add or remove a typed `issue ↔ issue` relation (§6.3.6). One `immediate`
|
|
25
|
+
* transaction: resolve `sourceUid`/`targetUid` → live `issue` nodes (tx-scoped,
|
|
26
|
+
* §4c) → resolve `rel`'s `edge_kind` rule (`resolveEdgeKindTx`, catalog.ts) →
|
|
27
|
+
* check whether a LIVE `(sourceUid, targetUid, rel)` edge already exists →
|
|
28
|
+
* branch:
|
|
29
|
+
*
|
|
30
|
+
* - `add`, edge already live → `noop:true`, nothing written.
|
|
31
|
+
* - `add`, edge not live (absent, or previously `remove`d) → `writeEdgeTx`
|
|
32
|
+
* (tx.ts) — inserts fresh or re-livens; throws
|
|
33
|
+
* `SingleValuedRelationConflictError` if `rel` is single-valued (`n:1`:
|
|
34
|
+
* `supersedes`/`duplicate_of`/`part_of`) and `sourceUid` already has a
|
|
35
|
+
* LIVE `rel` edge to a DIFFERENT target (`checkMultiplicityTx`, tx.ts —
|
|
36
|
+
* this file adds no separate check) — then `writeAudit`, `noop:false`.
|
|
37
|
+
* - `remove`, edge not live → `noop:true`, nothing written.
|
|
38
|
+
* - `remove`, edge live → `invalidateEdgeTx` (tx.ts) then `writeAudit`,
|
|
39
|
+
* `noop:false`.
|
|
40
|
+
*
|
|
41
|
+
* Errors: `InvalidArgumentError` (`sourceUid`/`targetUid`/`by` missing or
|
|
42
|
+
* blank; `rel` outside the closed 5-value set — the TS type restricts this
|
|
43
|
+
* at compile time but a transport boundary, e.g. CLI/MCP JSON input, can
|
|
44
|
+
* still send an arbitrary string; `action` outside `'add'|'remove'`, same
|
|
45
|
+
* reason; `sourceUid === targetUid` — a self-relation is always a client
|
|
46
|
+
* error, never silently written, §6.3.6), `IssueNotFoundError` (either
|
|
47
|
+
* endpoint is not a live `issue` node), `SingleValuedRelationConflictError`
|
|
48
|
+
* (per the `add` branch above), `WriteContentionError`/`WriteIOError` (§4c —
|
|
49
|
+
* an exhausted driver-level retry on the underlying `immediate`
|
|
50
|
+
* transaction).
|
|
51
|
+
*/
|
|
52
|
+
export declare function relate(handle: IWriteStoreHandle, input: IRelateInput): Promise<IRelateOutcome>;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { IWriteStoreHandle } from './tx.js';
|
|
2
|
+
import { ICitationInput } from './create-issue.js';
|
|
3
|
+
|
|
4
|
+
export interface ITransitionInput {
|
|
5
|
+
/** The `issue` uid to transition (§6.3, an "Issue verb"). */
|
|
6
|
+
uid: string;
|
|
7
|
+
/** The identity of the acting agent or person (§6.3's opening rule). REQUIRED. */
|
|
8
|
+
by: string;
|
|
9
|
+
/** catalog name or uid. An unresolved NAME mints a new status row with `terminal:false` (exactly like `create`'s own `status` field, §6.3.2/§6.1); a uid-shaped ref that does not resolve throws `CatalogNotFoundError('status', ref)`. */
|
|
10
|
+
toStatus: string;
|
|
11
|
+
/** REQUIRED unless `project_policy.transition_requires_note` is `false` (default `true`) — optional in the type; enforced at runtime (`NoteRequiredError`), never at the TS level. */
|
|
12
|
+
note?: string;
|
|
13
|
+
/** REQUIRED (≥1) when `project_policy.citation_required` is `true` AND `toStatus` resolves to a terminal status. Written as `citation` nodes + `has_citation` edges exactly like `create`'s own citations. */
|
|
14
|
+
citations?: ICitationInput[];
|
|
15
|
+
/**
|
|
16
|
+
* The item-level disclosure-contract git context — the SAME plain metadata
|
|
17
|
+
* scalar `create`'s own `gitContext` field writes (repo `AGENTS.md`'s "Cite
|
|
18
|
+
* what you read": the FIRST element of a `Citations:` block is `<active git
|
|
19
|
+
* context>`). When supplied, it UPDATES the item's stored value on the
|
|
20
|
+
* metadata touch this verb already performs; when omitted, the item's
|
|
21
|
+
* existing value (if any) is left untouched. ITEM-level, deliberately not a
|
|
22
|
+
* per-citation `ref` — it rides the issue, never a `citation` node.
|
|
23
|
+
*/
|
|
24
|
+
gitContext?: string;
|
|
25
|
+
}
|
|
26
|
+
export interface ITransitionOutcome {
|
|
27
|
+
uid: string;
|
|
28
|
+
fromStatus: string;
|
|
29
|
+
toStatus: string;
|
|
30
|
+
/** Present iff `toStatus.terminal` — see this file's own doc comment on `closedAt`. */
|
|
31
|
+
closedAt?: string;
|
|
32
|
+
/** The minted `transition` node's own uid, for audit-trail addressing. */
|
|
33
|
+
transitionUid: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Transition an issue's status (§4a, §6.3.4). One `immediate` transaction.
|
|
37
|
+
*
|
|
38
|
+
* Errors: `InvalidArgumentError` (`uid`/`by`/`toStatus` missing/blank, blank
|
|
39
|
+
* `citations[i].file`, or a resolved `toStatus` outside this project's
|
|
40
|
+
* `allowedStatuses` set), `IssueNotFoundError` (no live `issue` node carries
|
|
41
|
+
* `uid`), `StaleSupersedeError(uid)` (`uid` was already superseded by a prior
|
|
42
|
+
* `update` — this file's own doc comment on `update.ts` explains why that
|
|
43
|
+
* reuses this error class rather than a new one), `CatalogNotFoundError('status',
|
|
44
|
+
* toStatus)` (uid-shaped `toStatus` only — an unresolved NAME instead
|
|
45
|
+
* auto-mints with `terminal:false`, exactly as `create`'s own `status` field,
|
|
46
|
+
* §6.1), a project-declared `requiredFields` entry (§2 — here, always just
|
|
47
|
+
* `status`, see this file's own doc comment) left blank,
|
|
48
|
+
* `NoteRequiredError` (policy-gated), `CitationRequiredError`
|
|
49
|
+
* (policy-gated, terminal-only), `CitationUnverifiableError(target)`
|
|
50
|
+
* (policy-gated via `project_policy.citation_requires_sha` — a given
|
|
51
|
+
* citation's `sha` resolved to the `"unverified"` sentinel and the project
|
|
52
|
+
* requires a real hash; the gate applies only when the project has a known
|
|
53
|
+
* `path`, so a path-less project records `sha:"unverified"` verbatim),
|
|
54
|
+
* `ClaimHeldError(heldBy, heldSince)` (§6.3.5 — a
|
|
55
|
+
* live, non-stale claim held by someone other than `input.by` blocks the
|
|
56
|
+
* status change; see claim-lease.ts), `WriteContentionError`/`WriteIOError`
|
|
57
|
+
* (§4c — an exhausted driver-level retry on the underlying `immediate`
|
|
58
|
+
* transaction).
|
|
59
|
+
*/
|
|
60
|
+
export declare function transition(handle: IWriteStoreHandle, input: ITransitionInput): Promise<ITransitionOutcome>;
|