@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,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>;
@@ -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>;