@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,369 @@
|
|
|
1
|
+
import { IComponentSummary, ILocationSummary, ILocationType, IProjectSummary } from '../query/types.js';
|
|
2
|
+
import { IEdgeKindRule, IWriteStoreHandle } from './tx.js';
|
|
3
|
+
import { AdapterTransaction } from '@adhd/sox-store-adapter';
|
|
4
|
+
|
|
5
|
+
/** Whether `ref` should be treated as a `uid` (exact resolve-or-throw) rather than a business `name` (find, and for the flat catalogs, find-then-mint). */
|
|
6
|
+
export declare function isUidShaped(ref: string): boolean;
|
|
7
|
+
export interface IResolvedCatalogRow {
|
|
8
|
+
rowid: number;
|
|
9
|
+
uid: string;
|
|
10
|
+
name: string;
|
|
11
|
+
}
|
|
12
|
+
export interface IResolvedProjectRow extends IResolvedCatalogRow {
|
|
13
|
+
metadata: Record<string, unknown> | undefined;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* `project` (§1, §6.1): resolved-ONLY, `uid` or `name`, NEVER minted by an
|
|
17
|
+
* issue verb — minting a project is the explicit `upsertProject` registry
|
|
18
|
+
* verb's job alone (§3a), out of scope for this slice.
|
|
19
|
+
*/
|
|
20
|
+
export declare function resolveProjectTx(tx: AdapterTransaction, ref: string): Promise<IResolvedProjectRow>;
|
|
21
|
+
/**
|
|
22
|
+
* `component`, scoped within `project` (§6.1, §6.3.2): resolved-ONLY, `uid`
|
|
23
|
+
* or `name` — an unresolved name throws `CatalogNotFoundError('component', name)`
|
|
24
|
+
* rather than silently forking a new component under the given project.
|
|
25
|
+
*/
|
|
26
|
+
export declare function resolveComponentTx(tx: AdapterTransaction, input: {
|
|
27
|
+
projectUid: string;
|
|
28
|
+
ref: string;
|
|
29
|
+
}): Promise<IResolvedCatalogRow>;
|
|
30
|
+
/**
|
|
31
|
+
* `project`'s reserved default component, `(root)` (§3, §6.1, §6.3.2,
|
|
32
|
+
* §8 AC-23): what `createIssue` resolves to when `component` is omitted.
|
|
33
|
+
* A row `upsertProject` already guarantees is live for every project — NEVER
|
|
34
|
+
* minted here, in either case (§1). Resolved via `owns_project` (the SAME
|
|
35
|
+
* traversal §3a's registry uses), not a bare name lookup, so a `(root)` row
|
|
36
|
+
* belonging to a DIFFERENT project can never be mismatched onto this one.
|
|
37
|
+
*/
|
|
38
|
+
export declare function resolveDefaultComponentTx(tx: AdapterTransaction, input: {
|
|
39
|
+
projectRowid: number;
|
|
40
|
+
}): Promise<IResolvedCatalogRow>;
|
|
41
|
+
export type FlatCatalogKind = 'kind' | 'status' | 'priority' | 'agent';
|
|
42
|
+
export interface IMintOrResolveInput {
|
|
43
|
+
catalogKind: FlatCatalogKind;
|
|
44
|
+
ref: string;
|
|
45
|
+
/** Metadata to seed onto a MINTED row only — never read or applied when `ref` resolves to an existing row. Evaluated lazily (only on an actual mint) since e.g. `priority`'s rank needs a fresh in-tx MAX query (§6.3.2). */
|
|
46
|
+
mintMetadata?: (tx: AdapterTransaction) => Promise<Record<string, unknown>>;
|
|
47
|
+
/**
|
|
48
|
+
* The caller's single logical-write timestamp, forwarded to `writeNodeTx` so a
|
|
49
|
+
* minted catalog row carries the SAME `t_created`/`t_valid` as the issue that
|
|
50
|
+
* caused the mint. Omitted, `writeNodeTx` stamps its own `nowISO()`, which is
|
|
51
|
+
* what produced per-row drift within one `createIssue`.
|
|
52
|
+
*
|
|
53
|
+
* Deliberately NOT threaded into `resolveEdgeKindTx`'s `edge_kind` rows: those
|
|
54
|
+
* are schema-infrastructure rows describing the relation table itself, not part
|
|
55
|
+
* of any one issue's logical write, so they keep their own clock.
|
|
56
|
+
*/
|
|
57
|
+
at?: string;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The flat-catalog find-then-create (§1, §4c, §6.1): `kind`/`status`/
|
|
61
|
+
* `priority`/`agent` are mintable on an unresolved NAME; a uid-shaped `ref`
|
|
62
|
+
* that does not resolve instead throws — minting NEVER applies to a uid.
|
|
63
|
+
*/
|
|
64
|
+
export declare function mintOrResolveCatalogTx(tx: AdapterTransaction, input: IMintOrResolveInput): Promise<IResolvedCatalogRow>;
|
|
65
|
+
/** `priority`'s mint rule (§6.3.2): "rank set to one past the current max rank (i.e. lowest urgency) — a novel priority can never silently outrank an existing one." */
|
|
66
|
+
export declare function nextPriorityRankTx(tx: AdapterTransaction): Promise<number>;
|
|
67
|
+
/**
|
|
68
|
+
* §3's fixed edge table — the ONE declared shape for every `rel` (§2's
|
|
69
|
+
* closing statement: "the SAME check for every row, never two different
|
|
70
|
+
* checks"). `audits`' `source_kind: '*'` is the one declared sentinel (§2).
|
|
71
|
+
*/
|
|
72
|
+
export declare const EDGE_KIND_TABLE: readonly IEdgeKindRule[];
|
|
73
|
+
/**
|
|
74
|
+
* Resolve the `edge_kind` catalog row for `rel`, inside `tx` — the SAME
|
|
75
|
+
* `tx.executeGet` lookup §1 describes for the VALIDATION half of every edge
|
|
76
|
+
* write. `edge_kind` is NEVER caller-mintable (§1, §6.1): every row is
|
|
77
|
+
* "seeded once from §3's fixed edge table," normally by a dedicated
|
|
78
|
+
* bootstrap/seed step (§8.6 step 2 is the ETL's own version of this same
|
|
79
|
+
* seeding).
|
|
80
|
+
*
|
|
81
|
+
* **Documented deviation for this slice.** This task ships the write-layer
|
|
82
|
+
* core only (`errors.ts`/`tx.ts`/`catalog.ts`/`audit.ts`/`create-issue.ts`) —
|
|
83
|
+
* no dedicated seed/bootstrap script exists yet to have run before
|
|
84
|
+
* `createIssue` is first called. Rather than make `createIssue` depend on an
|
|
85
|
+
* out-of-scope script, this function self-heals: on a miss, it seeds the
|
|
86
|
+
* row from {@link EDGE_KIND_TABLE} — the IDENTICAL fixed §3 shape a real seed
|
|
87
|
+
* script would write, never a caller-supplied one — inside the SAME `tx` the
|
|
88
|
+
* calling verb already opened. This satisfies "seeded once" (idempotent:
|
|
89
|
+
* the `tx.executeGet` above finds it on every subsequent call) without
|
|
90
|
+
* requiring a separate bootstrap step to exist first. A future seed script
|
|
91
|
+
* making this self-heal path dead code is expected and fine — the row shape
|
|
92
|
+
* it would find already-present is identical either way.
|
|
93
|
+
*
|
|
94
|
+
* **A malformed row (should never happen — only this module ever writes
|
|
95
|
+
* one) is guarded, never crashed on, and never left to accumulate.** `meta`
|
|
96
|
+
* is read via {@link parseMetaObject} — a non-JSON `meta` value degrades to
|
|
97
|
+
* `{}` instead of throwing a raw `SyntaxError` out of the transaction
|
|
98
|
+
* callback (Finding 1: unguarded, that would misclassify as a retryable
|
|
99
|
+
* `E_IO` at `executeWriteTransaction`'s boundary and retry forever against a
|
|
100
|
+
* deterministic parse failure). Either that degraded `{}`, or a
|
|
101
|
+
* successfully-parsed object missing one of the three expected keys, takes
|
|
102
|
+
* the SAME "malformed" branch below: the row is invalidated and a correct
|
|
103
|
+
* replacement is minted from {@link EDGE_KIND_TABLE} in the SAME `tx`, so the
|
|
104
|
+
* next call's SELECT (now a deterministic `ORDER BY rowid` — the prior
|
|
105
|
+
* ordering-free `LIMIT 1` made which duplicate got read unspecified, Finding
|
|
106
|
+
* 2) can never re-find the malformed row and re-mint yet another duplicate.
|
|
107
|
+
* The store converges to exactly one live `edge_kind` row per `rel` after
|
|
108
|
+
* this call, rather than growing an unbounded set of malformed duplicates.
|
|
109
|
+
*/
|
|
110
|
+
export declare function resolveEdgeKindTx(tx: AdapterTransaction, rel: string): Promise<IEdgeKindRule>;
|
|
111
|
+
/**
|
|
112
|
+
* Per-project policy (§2): DATA, realized as `project.meta.metadata.policy`
|
|
113
|
+
* — §2's table has no dedicated node/edge of its own (it is 1:1 with
|
|
114
|
+
* `project`, never many-to-many), so it lives inside the SAME metadata blob
|
|
115
|
+
* `project`'s other declared fields (`repoUrl`, `monorepo`, …, §3a) already
|
|
116
|
+
* occupy. Every field defaults exactly as §2 states when the project carries
|
|
117
|
+
* no `policy` object at all (a project seeded before policy existed, or
|
|
118
|
+
* minimally).
|
|
119
|
+
*/
|
|
120
|
+
export interface IProjectPolicy {
|
|
121
|
+
readonly transitionRequiresNote: boolean;
|
|
122
|
+
readonly citationRequired: boolean;
|
|
123
|
+
readonly citationRequiresSha: boolean;
|
|
124
|
+
/**
|
|
125
|
+
* External filesystem roots (absolute paths) this project may cite from, in
|
|
126
|
+
* ADDITION to its own `metadata.path` root (BUG c6d35272). A citation target
|
|
127
|
+
* is accepted iff its CANONICAL (symlink-resolved) path lies within the
|
|
128
|
+
* project root OR one of these roots (`citation-path.ts`'s
|
|
129
|
+
* `resolveCitationTarget`); a `..` traversal or a symlink that escapes stays
|
|
130
|
+
* rejected, and an arbitrary absolute path outside every root is never
|
|
131
|
+
* readable. Only the resulting `sha` is persisted — never file content.
|
|
132
|
+
*
|
|
133
|
+
* This layer defaults to the EMPTY array, and {@link resolveProjectPolicy}'s
|
|
134
|
+
* injected runtime default (`defaultCitationAllowedExternalRoots()`) is ALSO
|
|
135
|
+
* empty — there is NO machine-global default root (BUG 62059b57 follow-up:
|
|
136
|
+
* the store's `~/.adhd/backlog` data home is not citable). A project opts
|
|
137
|
+
* into the external carve-out by naming roots here; an empty array (the
|
|
138
|
+
* default, or an explicit `[]`) leaves it disabled. This is TYPED,
|
|
139
|
+
* per-project config — deliberately never an environment toggle.
|
|
140
|
+
*/
|
|
141
|
+
readonly citationAllowedExternalRoots: readonly string[];
|
|
142
|
+
readonly defaultStatus?: string;
|
|
143
|
+
readonly defaultKind?: string;
|
|
144
|
+
readonly dedupeScanEnabled: boolean;
|
|
145
|
+
readonly dedupeThreshold: number;
|
|
146
|
+
readonly claimStaleAfterMin: number;
|
|
147
|
+
/** `project_status` — empty means "no restriction" (§2). */
|
|
148
|
+
readonly allowedStatuses: readonly string[];
|
|
149
|
+
/** `project_kind` — empty means "no restriction" (§2). */
|
|
150
|
+
readonly allowedKinds: readonly string[];
|
|
151
|
+
/** `project_field_requirement` — field names required on every mutating write for this project (§2). */
|
|
152
|
+
readonly requiredFields: readonly string[];
|
|
153
|
+
}
|
|
154
|
+
export declare function resolveProjectPolicy(project: IResolvedProjectRow): IProjectPolicy;
|
|
155
|
+
/**
|
|
156
|
+
* Whether a resolved project has a known filesystem `path` (§8.4/§8.5) — the
|
|
157
|
+
* precondition for citation content-addressing to be POSSIBLE at all. When it
|
|
158
|
+
* does not, `computeCitationSha` can only ever return the `"unverified"`
|
|
159
|
+
* sentinel (`create-issue.ts`/`transition.ts` both short-circuit on a
|
|
160
|
+
* non-string/empty `metadata.path`), so `project_policy.citationRequiresSha`
|
|
161
|
+
* has nothing to gate: the gate applies only where verification is possible.
|
|
162
|
+
* A path-less project therefore records `sha:"unverified"` verbatim, matching
|
|
163
|
+
* the ETL's own precedent (`tools/etl/citation.ts`). Kept beside
|
|
164
|
+
* {@link resolveProjectPolicy} — it reads the same resolved project row. The
|
|
165
|
+
* PREDICATE itself is shared by BOTH write paths so the `create`/`transition`
|
|
166
|
+
* gates never drift on WHICH projects are verifiable; the surrounding
|
|
167
|
+
* `computeCitationSha` hashing body is NOT shared — it stays a per-file copy
|
|
168
|
+
* in each verb (this package's established per-file-duplication convention —
|
|
169
|
+
* see `transition.ts`'s own doc comment on its copy).
|
|
170
|
+
*
|
|
171
|
+
* A TYPE PREDICATE, not a bare `boolean`: both `computeCitationSha` call sites
|
|
172
|
+
* (and the waiver log beside each gate) need the narrowed `metadata.path` as a
|
|
173
|
+
* `string` immediately after the guard, and a predicate is the one form that
|
|
174
|
+
* gives them that without a non-null assertion or a re-read of the same field.
|
|
175
|
+
*/
|
|
176
|
+
export declare function projectHasKnownPath(project: IResolvedProjectRow): project is IResolvedProjectRow & {
|
|
177
|
+
metadata: Record<string, unknown> & {
|
|
178
|
+
path: string;
|
|
179
|
+
};
|
|
180
|
+
};
|
|
181
|
+
export interface IUpsertProjectInput {
|
|
182
|
+
/** The business key `project` is upserted by (§3a, §4). */
|
|
183
|
+
name: string;
|
|
184
|
+
path?: string;
|
|
185
|
+
repoUrl?: string;
|
|
186
|
+
monorepo?: boolean;
|
|
187
|
+
description?: string;
|
|
188
|
+
/** The acting agent's or person's identity (§6.3's opening rule). REQUIRED. */
|
|
189
|
+
by: string;
|
|
190
|
+
}
|
|
191
|
+
export interface IUpsertProjectOutcome {
|
|
192
|
+
uid: string;
|
|
193
|
+
/** `false` when an existing LIVE `project` row with this `name` was found and updated in place; `true` only on a genuine first mint. */
|
|
194
|
+
created: boolean;
|
|
195
|
+
project: IProjectSummary;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Create-or-update a `project` by `name` (§3a, §4). One `immediate`
|
|
199
|
+
* transaction: find the LIVE `project` row by `name` — on a HIT, merge the
|
|
200
|
+
* supplied fields into the EXISTING `meta` blob (never a wholesale replace;
|
|
201
|
+
* an existing `meta.policy` — §2's per-project policy, read back by
|
|
202
|
+
* {@link resolveProjectPolicy} — survives every subsequent `upsertProject`
|
|
203
|
+
* untouched) and audit `'updated'`; on a MISS, mint the `project` row AND,
|
|
204
|
+
* "on first creation ONLY," the reserved default `component` row named
|
|
205
|
+
* `(root)` plus its `owns_project` edge, in the SAME transaction (§4) — audit
|
|
206
|
+
* `'created'`. A repeat `upsertProject` against an existing project finds
|
|
207
|
+
* `(root)` already live (via the existing-project branch, which never touches
|
|
208
|
+
* it) and writes nothing further for it: idempotent, never a second row.
|
|
209
|
+
*
|
|
210
|
+
* Race-safety (§4c): the find-then-create SELECT and the INSERT/UPDATE both
|
|
211
|
+
* run inside the SAME `BEGIN IMMEDIATE` transaction two concurrent processes
|
|
212
|
+
* cannot both hold at once — one commits, the other's driver-level conflict
|
|
213
|
+
* surfaces as `E_CONTENTION` and is retried by {@link executeWriteTransaction}
|
|
214
|
+
* (250ms/500ms backoff, 3 attempts, §4c), never a racing check-then-insert.
|
|
215
|
+
*
|
|
216
|
+
* Errors: `InvalidArgumentError` (`name`/`by` missing or blank),
|
|
217
|
+
* `WriteContentionError`/`WriteIOError` (§4c, an exhausted or unclassified
|
|
218
|
+
* driver-level failure on the underlying `immediate` transaction).
|
|
219
|
+
*/
|
|
220
|
+
export declare function upsertProject(handle: IWriteStoreHandle, input: IUpsertProjectInput): Promise<IUpsertProjectOutcome>;
|
|
221
|
+
/**
|
|
222
|
+
* Transaction-PARTICIPANT core of {@link upsertProject} — the exact body
|
|
223
|
+
* that used to run inside its `executeWriteTransaction` callback, extracted
|
|
224
|
+
* so a caller that already owns an open `immediate` transaction (the ETL's
|
|
225
|
+
* `tools/etl/import-item.ts` bundles an entire source item's writes into ONE
|
|
226
|
+
* transaction, SPEC.md §7a) can participate in it instead of nesting a
|
|
227
|
+
* second `executeWriteTransaction` call — nesting would either deadlock or
|
|
228
|
+
* silently open two separate transactions where one atomic one was intended.
|
|
229
|
+
* `upsertProject` itself is now a thin wrapper: open the transaction, hand it
|
|
230
|
+
* to this function. Same validation contract as `upsertProject` — callers of
|
|
231
|
+
* THIS function are responsible for having already run `assertNonBlank`/
|
|
232
|
+
* `assertNotBareRoleLiteral` on `input.name`/`input.by` (this function does
|
|
233
|
+
* not re-validate, matching every other `*Tx` helper in this module).
|
|
234
|
+
*/
|
|
235
|
+
export declare function upsertProjectTx(tx: AdapterTransaction, handle: Pick<IWriteStoreHandle, 'typePolicy'>, input: IUpsertProjectInput): Promise<IUpsertProjectOutcome>;
|
|
236
|
+
export interface IUpsertComponentInput {
|
|
237
|
+
/** `project` reference — `uid` or `name` (§6.1's disambiguation-by-shape rule), resolved-ONLY, never minted. */
|
|
238
|
+
project: string;
|
|
239
|
+
/** The business key `component` is upserted by, scoped to `project` (§3a, §4: "upsert by `(project, name)`"). */
|
|
240
|
+
name: string;
|
|
241
|
+
path?: string;
|
|
242
|
+
description?: string;
|
|
243
|
+
/** The acting agent's or person's identity (§6.3's opening rule). REQUIRED. */
|
|
244
|
+
by: string;
|
|
245
|
+
}
|
|
246
|
+
export interface IUpsertComponentOutcome {
|
|
247
|
+
uid: string;
|
|
248
|
+
/** `false` when an existing LIVE `component` row with this `(project, name)` was found and updated in place. */
|
|
249
|
+
created: boolean;
|
|
250
|
+
component: IComponentSummary;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Create-or-update a `component` by `(project, name)` (§3a, §4). `project`
|
|
254
|
+
* is resolved-ONLY ({@link resolveProjectTx}) — an unresolved `project`
|
|
255
|
+
* throws `CatalogNotFoundError` before any component-side work runs. One
|
|
256
|
+
* `immediate` transaction thereafter: find the LIVE `component` row scoped to
|
|
257
|
+
* the resolved project's `uid` — on a HIT, merge the supplied fields into the
|
|
258
|
+
* EXISTING `meta` (never a wholesale replace, same rule as
|
|
259
|
+
* {@link upsertProject}) and audit `'updated'`; on a MISS, mint the row.
|
|
260
|
+
* Either way, the `owns_project` edge (project → component, §3a's registry
|
|
261
|
+
* edge set) is written with the SAME `ON CONFLICT` upsert `writeEdgeTx`
|
|
262
|
+
* itself already performs — a no-op re-livening when the edge already exists,
|
|
263
|
+
* so a component whose edge somehow drifted from its `meta.projectUid` (it
|
|
264
|
+
* never should, both are written in the SAME transaction on creation) is
|
|
265
|
+
* self-healed rather than left stale.
|
|
266
|
+
*
|
|
267
|
+
* Errors: `InvalidArgumentError` (`project`/`name`/`by` missing or blank),
|
|
268
|
+
* `CatalogNotFoundError` (`project` does not resolve),
|
|
269
|
+
* `WriteContentionError`/`WriteIOError` (§4c).
|
|
270
|
+
*/
|
|
271
|
+
export declare function upsertComponent(handle: IWriteStoreHandle, input: IUpsertComponentInput): Promise<IUpsertComponentOutcome>;
|
|
272
|
+
/**
|
|
273
|
+
* Transaction-PARTICIPANT core of {@link upsertComponent} — same extraction
|
|
274
|
+
* rationale as {@link upsertProjectTx}: lets a caller that already owns an
|
|
275
|
+
* open `immediate` transaction (the ETL) participate instead of nesting a
|
|
276
|
+
* second `executeWriteTransaction`. `upsertComponent` itself is now a thin
|
|
277
|
+
* wrapper: validate, open the transaction, hand it to this function. Callers
|
|
278
|
+
* of THIS function are responsible for having already run the same
|
|
279
|
+
* `assertNonBlank`/`assertNotBareRoleLiteral` validation `upsertComponent`
|
|
280
|
+
* runs before opening its transaction.
|
|
281
|
+
*/
|
|
282
|
+
export declare function upsertComponentTx(tx: AdapterTransaction, handle: Pick<IWriteStoreHandle, 'typePolicy'>, input: IUpsertComponentInput): Promise<IUpsertComponentOutcome>;
|
|
283
|
+
export interface IUpsertLocationInput {
|
|
284
|
+
/**
|
|
285
|
+
* `component` reference — `uid` or `name` (§6.1's disambiguation-by-shape
|
|
286
|
+
* rule). A bare NAME is ambiguous on its own (component names are unique
|
|
287
|
+
* only WITHIN a project, §6.3.2), so a name reference REQUIRES `project`
|
|
288
|
+
* to scope it; a `uid` reference never needs `project` (a documented
|
|
289
|
+
* judgment call — SPEC.md §3a states the call convention
|
|
290
|
+
* `upsertLocation({component, locType, value})` without a `project` field,
|
|
291
|
+
* but never states how a bare component name resolves; this reading stays
|
|
292
|
+
* consistent with every OTHER verb's own `project`-scoped component
|
|
293
|
+
* resolution — §6.1, §6.3.2 — rather than inventing a global name-uniqueness
|
|
294
|
+
* scan `resolveComponentTx` itself does not perform).
|
|
295
|
+
*/
|
|
296
|
+
component: string;
|
|
297
|
+
project?: string;
|
|
298
|
+
locType: ILocationType;
|
|
299
|
+
value: string;
|
|
300
|
+
/** The acting agent's or person's identity (§6.3's opening rule). REQUIRED. */
|
|
301
|
+
by: string;
|
|
302
|
+
}
|
|
303
|
+
export interface IUpsertLocationOutcome {
|
|
304
|
+
uid: string;
|
|
305
|
+
/** `false` when a LIVE `location` row with this exact `(component, locType, value)` already existed — a genuine no-op: identity IS the row's entire content, so nothing is written or audited (§4a's "never disguised as a real write" rule, `move.ts`'s stated-no-op pattern). */
|
|
306
|
+
created: boolean;
|
|
307
|
+
location: ILocationSummary;
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Create-or-find a `location` by `(component, locType, value)` (§3a, §4).
|
|
311
|
+
* `component` is resolved-ONLY — by `uid` directly, or by `name` scoped to
|
|
312
|
+
* `project` (see {@link IUpsertLocationInput.component}'s doc comment for the
|
|
313
|
+
* resolved ambiguity). One `immediate` transaction: find a LIVE `location`
|
|
314
|
+
* row with the exact triple — a HIT is a genuine no-op (the triple IS the
|
|
315
|
+
* row's whole identity; there is no fourth field left to merge, unlike
|
|
316
|
+
* `project`/`component`), so NOTHING is written or audited and `created:
|
|
317
|
+
* false` is returned; a MISS mints the row plus its `has_location` edge
|
|
318
|
+
* (component → location, §3a's registry edge set) and audits `'created'`.
|
|
319
|
+
*
|
|
320
|
+
* An invalidated (`rmLocation`-ed) row is never resurrected or matched here —
|
|
321
|
+
* the SELECT is `t_invalid IS NULL`-scoped like every other resolution in
|
|
322
|
+
* this module, so re-`upsertLocation`-ing the identical triple after an
|
|
323
|
+
* `rmLocation` mints a genuinely NEW row with a NEW `uid`, leaving the
|
|
324
|
+
* invalidated row's own audit trail untouched (the same bi-temporal
|
|
325
|
+
* non-resurrection rule `delete.ts` documents for `issue`).
|
|
326
|
+
*
|
|
327
|
+
* Errors: `InvalidArgumentError` (`component`/`value`/`by` missing or blank,
|
|
328
|
+
* `locType` not one of `path`/`url`/`tool`, or a bare `component` NAME given
|
|
329
|
+
* without `project`), `CatalogNotFoundError` (`project`/`component` does not
|
|
330
|
+
* resolve), `WriteContentionError`/`WriteIOError` (§4c).
|
|
331
|
+
*/
|
|
332
|
+
export declare function upsertLocation(handle: IWriteStoreHandle, input: IUpsertLocationInput): Promise<IUpsertLocationOutcome>;
|
|
333
|
+
export interface IRmLocationInput {
|
|
334
|
+
uid: string;
|
|
335
|
+
/** The acting agent's or person's identity (§6.3's opening rule). REQUIRED. */
|
|
336
|
+
by: string;
|
|
337
|
+
/** Optional explanation recorded on the invalidation's audit row and merged into the location's own `meta` (mirrors `delete.ts`'s `reason`, which SPEC.md §6.3.7 requires for `issue` — kept optional here since §3a/§4 state no such requirement for `location`). */
|
|
338
|
+
reason?: string;
|
|
339
|
+
}
|
|
340
|
+
export interface IRmLocationOutcome {
|
|
341
|
+
uid: string;
|
|
342
|
+
invalidated: true;
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Soft-invalidate a LIVE `location` by `uid` (§3a, §4: "invalidate"; §4c's
|
|
346
|
+
* table: "invalidate-by-uid after confirming the row is live, for uniformity
|
|
347
|
+
* with every other verb above"). One `immediate` transaction: resolve `uid`
|
|
348
|
+
* → LIVE `location` node (`resolveByUidTx`, never a bare `getNodeByUid`, §4c)
|
|
349
|
+
* → hand-composed guarded `UPDATE node SET t_invalid = ?, meta = ? WHERE
|
|
350
|
+
* rowid = ?` (merging `invalidatedReason`/`invalidatedAt` into the EXISTING
|
|
351
|
+
* `meta`, never a wholesale replace — the SAME shape `delete.ts` uses for
|
|
352
|
+
* `issue`) → invalidate the owning `has_location` edge
|
|
353
|
+
* ({@link invalidateEdgeTx}, resolved via the location's own
|
|
354
|
+
* `meta.componentUid`) → `writeAudit` (action `'deleted'`, matching
|
|
355
|
+
* `delete.ts`'s own vocabulary), all against the SAME `tx`.
|
|
356
|
+
*
|
|
357
|
+
* No `issue` node ever references a `location` directly — per §3a's fixed
|
|
358
|
+
* edge table ({@link EDGE_KIND_TABLE}), `has_location` (component → location)
|
|
359
|
+
* is the ONLY rel touching `location` at all — so the sole referencing edge
|
|
360
|
+
* this invalidates is the owning component's own `has_location` edge; no
|
|
361
|
+
* issue-side fallout exists to reconcile.
|
|
362
|
+
*
|
|
363
|
+
* Errors: `InvalidArgumentError` (`uid`/`by` missing or blank),
|
|
364
|
+
* `CatalogNotFoundError` (no LIVE `location` node carries `uid` — including
|
|
365
|
+
* an ALREADY-`rmLocation`-ed `uid`, the same non-resurrection rule
|
|
366
|
+
* {@link IUpsertLocationOutcome}'s doc comment describes),
|
|
367
|
+
* `WriteContentionError`/`WriteIOError` (§4c).
|
|
368
|
+
*/
|
|
369
|
+
export declare function rmLocation(handle: IWriteStoreHandle, input: IRmLocationInput): Promise<IRmLocationOutcome>;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* citation-path.ts — the citation-path containment contract (BUG c6d35272).
|
|
3
|
+
*
|
|
4
|
+
* A citation's `sha` hashes EXTERNAL file content (§6.3.2/§8.5). By default a
|
|
5
|
+
* citation target must resolve INSIDE the owning project's own root
|
|
6
|
+
* (`project.metadata.path`): that keeps the read surface confined to the tree
|
|
7
|
+
* the project already owns, so a citation can never be used as a
|
|
8
|
+
* file-exists/readable oracle for an arbitrary absolute path elsewhere on the
|
|
9
|
+
* host. But some genuine evidence lives OUTSIDE that root — a machine-level
|
|
10
|
+
* tool (`~/.local/bin/gx`), a globally-installed package's `dist/**`, a
|
|
11
|
+
* `/tmp` scratch artifact, or any root the project explicitly allowlists via
|
|
12
|
+
* `citationAllowedExternalRoots`. Before this module the ONLY way to cite such
|
|
13
|
+
* evidence on a path-PRESENT project was to omit the citation entirely —
|
|
14
|
+
* which `create`
|
|
15
|
+
* still reported as SUCCESS, silently downgrading a labelled-unverified
|
|
16
|
+
* citation to an unlabelled absence (exactly what "No citation, no claim"
|
|
17
|
+
* forbids).
|
|
18
|
+
*
|
|
19
|
+
* The carve-out is a TYPED, per-project ALLOWLIST — `project_policy.
|
|
20
|
+
* citationAllowedExternalRoots` (see `catalog.ts`'s `IProjectPolicy`) — never
|
|
21
|
+
* an env toggle and never a blanket "any absolute path" escape. A citation is
|
|
22
|
+
* accepted iff its CANONICAL (symlink-resolved) target lies within the project
|
|
23
|
+
* root OR within one of the project's allowed external roots. Every allowed
|
|
24
|
+
* root is itself canonicalized before comparison, so:
|
|
25
|
+
*
|
|
26
|
+
* - a relative `../…` traversal cannot widen the surface (it resolves to an
|
|
27
|
+
* absolute candidate that is then containment-checked);
|
|
28
|
+
* - a symlink INSIDE the root that points OUTSIDE it is rejected (the
|
|
29
|
+
* canonical target is outside every root) — this is the same defect the
|
|
30
|
+
* sibling item c6d90ddf names, closed here for free by canonicalizing;
|
|
31
|
+
* - a root that is itself reached through a symlink still matches, because
|
|
32
|
+
* both the candidate and the root are canonicalized through the SAME
|
|
33
|
+
* `realpath` pass.
|
|
34
|
+
*
|
|
35
|
+
* Nothing here reads file CONTENT — only paths are resolved and compared.
|
|
36
|
+
* Only the resulting sha is ever persisted (§8.5); the citation node stores
|
|
37
|
+
* `target`/`sha`, never the bytes.
|
|
38
|
+
*/
|
|
39
|
+
/**
|
|
40
|
+
* The default external roots a project may cite from, used when the project's
|
|
41
|
+
* own policy supplies no `citationAllowedExternalRoots`. This is the EMPTY
|
|
42
|
+
* array: the runtime grants NO machine-global default root.
|
|
43
|
+
*
|
|
44
|
+
* Why: the runtime's own data home, `~/.adhd/backlog`, is the STORE's home,
|
|
45
|
+
* not an evidence tree. It holds only the machine-global backlog database
|
|
46
|
+
* (`production/data/backlog-v2.db`) and its snapshots (`production/backup-*`,
|
|
47
|
+
* top-level `backup-*`/`backups/`), plus the `test/` store. Granting it as a
|
|
48
|
+
* default root let a citation's `sha` read/hash surface resolve straight INTO
|
|
49
|
+
* the shared backlog graph — the very graph the citation containment exists to
|
|
50
|
+
* keep out of citation reads. The earlier narrowing from `~/.adhd` down to
|
|
51
|
+
* `~/.adhd/backlog` (BUG 62059b57) stopped one directory too high (BUG
|
|
52
|
+
* 62059b57 follow-up).
|
|
53
|
+
*
|
|
54
|
+
* This does NOT gut the carve-out MECHANISM: a project that genuinely needs a
|
|
55
|
+
* specific external root (a machine tool, a globally-installed package's
|
|
56
|
+
* `dist/**`) names it explicitly in `project_policy.citationAllowedExternalRoots`,
|
|
57
|
+
* and that typed, per-project allowlist is unchanged. Only the unearned
|
|
58
|
+
* machine-global default is gone.
|
|
59
|
+
*
|
|
60
|
+
* Deliberately a FUNCTION, called LAZILY by {@link resolveProjectPolicy} on
|
|
61
|
+
* every policy resolve, never a module-level constant: the call surface stays
|
|
62
|
+
* stable if a legitimate machine-global root is ever re-introduced, and every
|
|
63
|
+
* caller keeps the lazy, per-resolve contract.
|
|
64
|
+
*/
|
|
65
|
+
export declare function defaultCitationAllowedExternalRoots(): string[];
|
|
66
|
+
/**
|
|
67
|
+
* Render `root` for a human-facing error message: `~`-anchored when it lies
|
|
68
|
+
* under the current home directory, the bare absolute path otherwise. Keeps
|
|
69
|
+
* the `CitationUnverifiableError` message stable across machines (a literal
|
|
70
|
+
* `/Users/<name>/.adhd` would leak the operator's identity and differ per
|
|
71
|
+
* host, making the message hard to assert on).
|
|
72
|
+
*/
|
|
73
|
+
export declare function displayExternalRoot(root: string): string;
|
|
74
|
+
/**
|
|
75
|
+
* Lexical containment: does `candidate` sit at or under `root`?
|
|
76
|
+
*
|
|
77
|
+
* Uses `path.relative` — NEVER a bare `startsWith(root)`, which a sibling
|
|
78
|
+
* directory sharing a name prefix would defeat (`/repo` vs `/repo-evil`:
|
|
79
|
+
* `'/repo-evil/x'.startsWith('/repo')` is `true`). Both arguments are expected
|
|
80
|
+
* to be ABSOLUTE and (ideally) canonical already; this function is purely
|
|
81
|
+
* lexical and performs no I/O.
|
|
82
|
+
*/
|
|
83
|
+
export declare function isPathWithin(root: string, candidate: string): boolean;
|
|
84
|
+
/**
|
|
85
|
+
* The §4c "the file genuinely is not there" error taxonomy, in ONE place.
|
|
86
|
+
*
|
|
87
|
+
* `ENOENT` (a path segment does not exist) and `ENOTDIR` (a path segment that
|
|
88
|
+
* should be a directory is in fact a file, so the target cannot exist) both
|
|
89
|
+
* mean exactly "not there" — the ONLY case any caller may degrade to the
|
|
90
|
+
* `'unverified'` sentinel. Every other errno (`EACCES`, `EPERM`, `EMFILE`,
|
|
91
|
+
* `EISDIR`, `ELOOP`, …) is a REAL I/O failure and must never be masked as an
|
|
92
|
+
* absent file. Shared by {@link canonicalizePath} and both write verbs'
|
|
93
|
+
* `computeCitationSha` (BUG c6d35272 follow-up) so that taxonomy is
|
|
94
|
+
* single-source and cannot drift between its three call sites.
|
|
95
|
+
*
|
|
96
|
+
* NOT used by `tools/etl/citation.ts`: that frozen tool deliberately ALSO
|
|
97
|
+
* exempts `EISDIR` (its own documented, corpus-driven divergence), so folding
|
|
98
|
+
* it into this narrower ENOENT/ENOTDIR predicate would regress it.
|
|
99
|
+
*/
|
|
100
|
+
export declare function isMissingPathError(err: unknown): boolean;
|
|
101
|
+
/**
|
|
102
|
+
* Resolve `p` to its real, symlink-free absolute path.
|
|
103
|
+
*
|
|
104
|
+
* `p` need not exist. On `ENOENT`/`ENOTDIR` the nearest EXISTING ancestor is
|
|
105
|
+
* canonicalized and the non-existent tail is re-joined onto it, so a citation
|
|
106
|
+
* naming a file that does not exist still yields a stable absolute candidate
|
|
107
|
+
* for the containment check — the "does it actually exist / is it readable?"
|
|
108
|
+
* verdict is deliberately left to the caller's `readFile` (which the write
|
|
109
|
+
* layer's error taxonomy degrades to the `'unverified'` sentinel on
|
|
110
|
+
* ENOENT/ENOTDIR, `WriteIOError` otherwise). Any OTHER error (EACCES, ELOOP,
|
|
111
|
+
* …) propagates untouched — a real I/O fault must never be masked as a
|
|
112
|
+
* missing file.
|
|
113
|
+
*/
|
|
114
|
+
export declare function canonicalizePath(p: string): Promise<string>;
|
|
115
|
+
/** The outcome of {@link resolveCitationTarget}. */
|
|
116
|
+
export interface IResolvedCitationTarget {
|
|
117
|
+
/** `true` iff the canonical candidate lies within the project root or one of `allowedRoots`. */
|
|
118
|
+
accepted: boolean;
|
|
119
|
+
/** The CANONICAL (symlink-resolved) absolute path — the exact path the caller should read. Returned even when `accepted` is `false`, for a caller that wants to report it. */
|
|
120
|
+
candidate: string;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Decide whether `file` (absolute, or relative to `projectRoot`) is a
|
|
124
|
+
* citable target: canonical containment against `projectRoot` ∪
|
|
125
|
+
* `allowedRoots`. See this module's header for the security model.
|
|
126
|
+
*
|
|
127
|
+
* `projectRoot` and every entry of `allowedRoots` are canonicalized through
|
|
128
|
+
* the SAME pass as the candidate, so a root reached via a symlink still
|
|
129
|
+
* matches its own contents (and a root that does not exist is canonicalized
|
|
130
|
+
* to its nearest existing ancestor + the literal tail, exactly like the
|
|
131
|
+
* candidate).
|
|
132
|
+
*/
|
|
133
|
+
export declare function resolveCitationTarget(projectRoot: string, file: string, allowedRoots: readonly string[]): Promise<IResolvedCitationTarget>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* claim-lease.ts — the ephemeral claim lease's read-side logic, shared by
|
|
3
|
+
* every write verb that needs to know whether a live claim currently blocks
|
|
4
|
+
* it. `claim.ts` (§6.3.5) is the only verb that MUTATES `claimedBy`/
|
|
5
|
+
* `claimedAt`; this module only ever reads and interprets that pair, so
|
|
6
|
+
* `transition.ts` (§6.3.5's own "a live claim by someone else blocks a
|
|
7
|
+
* status change" rule) can enforce the SAME staleness semantics without
|
|
8
|
+
* duplicating the age-math or silently drifting from it over time.
|
|
9
|
+
*/
|
|
10
|
+
/** Pulls the raw `claimedBy`/`claimedAt` pair off an issue's metadata, typed and undefined-safe. */
|
|
11
|
+
export declare function extractClaimMeta(metadata: Record<string, unknown> | undefined): {
|
|
12
|
+
claimedBy: string | undefined;
|
|
13
|
+
claimedAt: string | undefined;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* A missing `claimedAt` (should not happen alongside a real `claimedBy`, but
|
|
17
|
+
* never trusted) is treated as infinitely old — the same fail-open choice
|
|
18
|
+
* `claim.ts`'s own reclaim branch already makes.
|
|
19
|
+
*/
|
|
20
|
+
export declare function claimAgeMinutes(claimedAt: string | undefined, now: string): number;
|
|
21
|
+
export declare function isClaimStale(claimedAt: string | undefined, now: string, staleAfterMin: number): boolean;
|
package/write/claim.d.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { IWriteStoreHandle } from './tx.js';
|
|
2
|
+
|
|
3
|
+
export interface IClaimInput {
|
|
4
|
+
/** The `issue` uid to claim/release/renew (§6.3, an "Issue verb"). */
|
|
5
|
+
uid: string;
|
|
6
|
+
/** The claimant — the acting agent or person. REQUIRED on every action (§6.3's opening rule). */
|
|
7
|
+
by: string;
|
|
8
|
+
action: 'claim' | 'release' | 'renew';
|
|
9
|
+
/** Human-confirmed override of a NON-stale claim (§6.2). Only consulted on `action:'claim'` when the current claimant differs and the lease is not yet stale. */
|
|
10
|
+
force?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface IClaimOutcome {
|
|
13
|
+
uid: string;
|
|
14
|
+
status: 'claimed' | 'held' | 'reclaimed-stale' | 'renewed' | 'released' | 'release-noop';
|
|
15
|
+
claimedBy?: string;
|
|
16
|
+
claimedAt?: string;
|
|
17
|
+
heldBy?: string;
|
|
18
|
+
heldSince?: string;
|
|
19
|
+
previousClaimant?: string;
|
|
20
|
+
wasClaimedBy?: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* `claim` / `release` / `renew` an issue's lease (§6.3.5). One `immediate`
|
|
24
|
+
* transaction; the CAS decision and the metadata touch both run against the
|
|
25
|
+
* SAME `tx` the transaction opened, so two concurrent callers against the
|
|
26
|
+
* SAME `uid` serialize through the transaction's RESERVED lock — the loser's
|
|
27
|
+
* own re-fetch (inside its own attempt, after the winner commits) sees the
|
|
28
|
+
* winner's already-written `claimedBy`/`claimedAt`, never a stale
|
|
29
|
+
* pre-transaction read.
|
|
30
|
+
*
|
|
31
|
+
* Rule table (SPEC.md §6.3.5, restated here for the metadata-only shape the
|
|
32
|
+
* lease now lives in):
|
|
33
|
+
*
|
|
34
|
+
* | action | current `claimedBy` | outcome |
|
|
35
|
+
* |-----------|-------------------------------------------------------|---------|
|
|
36
|
+
* | `claim` | unset | write `{claimedBy, claimedAt}`; `status:'claimed'` |
|
|
37
|
+
* | `claim` | `== by` | no-op write of `claimedAt`; `status:'held'` |
|
|
38
|
+
* | `claim` | `!= by`, age < `claim_stale_after_min`, `!force` | throws `ClaimHeldError` |
|
|
39
|
+
* | `claim` | `!= by`, age ≥ threshold, OR `force:true` | write `{claimedBy, claimedAt}`; `status:'reclaimed-stale'`, `previousClaimant` |
|
|
40
|
+
* | `release` | `== by` | clear both fields; `status:'released'` |
|
|
41
|
+
* | `release` | `!= by` or unset | no write; `status:'release-noop'`, `wasClaimedBy` |
|
|
42
|
+
* | `renew` | `== by` | bump `claimedAt`; `status:'renewed'` |
|
|
43
|
+
* | `renew` | `!= by` or unset | throws `ClaimHeldError` |
|
|
44
|
+
*
|
|
45
|
+
* **Audit.** Every branch that performs a real write emits exactly one
|
|
46
|
+
* `audit` node via {@link writeAudit}, inside the SAME transaction (§4a "no
|
|
47
|
+
* bare mutation"). `writeAudit`'s own doc comment enumerates the audit
|
|
48
|
+
* `action` vocabulary as `'claimed'`/`'released'`/`'renewed'`/
|
|
49
|
+
* `'reclaimed-stale'` — four values, not five — so the `claim`-when-`== by`
|
|
50
|
+
* ("held", an idempotent re-affirmation of an already-owned claim) branch
|
|
51
|
+
* reuses `action:'claimed'`; its `IClaimOutcome.status` is still the distinct
|
|
52
|
+
* `'held'` value the rule table names, carried in the OUTCOME, not the audit
|
|
53
|
+
* action string. The two branches the rule table marks "no write"
|
|
54
|
+
* (`release-noop`) perform no touch and emit no audit — nothing changed, so
|
|
55
|
+
* §4a's "every STATE CHANGE writes an audit node" does not apply. This
|
|
56
|
+
* reading resolves a genuine tension in SPEC.md §6.3.5 (see this package's
|
|
57
|
+
* write-verb report for the full citation) between "every branch writes an
|
|
58
|
+
* audit node" and the rule table's own "no write" / four-action vocabulary;
|
|
59
|
+
* it is the only reading consistent with `writeAudit`'s own doc comment.
|
|
60
|
+
*
|
|
61
|
+
* **`renew` against an unclaimed issue.** The rule table assigns this to the
|
|
62
|
+
* SAME `ClaimHeldError` outcome as "held by someone else" — but there is no
|
|
63
|
+
* real holder to report. SPEC.md does not resolve this case explicitly; this
|
|
64
|
+
* implementation reports `ClaimHeldError('', '')` (empty-string sentinels,
|
|
65
|
+
* never a fabricated agent name or timestamp) so the thrown error's shape is
|
|
66
|
+
* still exactly `ClaimHeldError` per the rule table, without inventing data
|
|
67
|
+
* the graph does not have.
|
|
68
|
+
*
|
|
69
|
+
* Errors: `InvalidArgumentError` (`uid`/`by` missing/blank, or `action`
|
|
70
|
+
* outside the closed `'claim'|'release'|'renew'` set — the TS type restricts
|
|
71
|
+
* this at compile time but a transport boundary, e.g. CLI/MCP JSON input, can
|
|
72
|
+
* still send an arbitrary string), `IssueNotFoundError` (no live `issue` node
|
|
73
|
+
* carries `uid`), `ClaimHeldError` (per the rule table above),
|
|
74
|
+
* `IssueTerminalError` (`action:'claim'` only — the issue's current status
|
|
75
|
+
* is `terminal`; `release`/`renew` are exempt, since cleanup on an issue
|
|
76
|
+
* that got closed out from under the claimant must still succeed),
|
|
77
|
+
* `WriteContentionError`/`WriteIOError` (§4c — an exhausted driver-level
|
|
78
|
+
* retry on the underlying `immediate` transaction).
|
|
79
|
+
*/
|
|
80
|
+
export declare function claim(handle: IWriteStoreHandle, input: IClaimInput): Promise<IClaimOutcome>;
|