@adhd/backlog 1.0.4 → 1.0.5
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 +16 -0
- package/api.ir.json +1 -1
- package/envelope.d.ts +1 -1
- package/index.js +51 -51
- package/index.mjs +4070 -3937
- package/package.json +1 -1
- package/query/query.d.ts +12 -9
- package/query/resolve.d.ts +27 -0
- package/store/catalog-invariant-guard.d.ts +10 -4
- package/write/catalog.d.ts +9 -2
- package/write/errors.d.ts +37 -1
- package/write/tx.d.ts +23 -1
- package/write/uid-prefix.d.ts +78 -0
package/package.json
CHANGED
package/query/query.d.ts
CHANGED
|
@@ -48,15 +48,18 @@ export interface IQueryStoreHandle {
|
|
|
48
48
|
*/
|
|
49
49
|
readonly assertVocabulary?: () => Promise<void>;
|
|
50
50
|
/**
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
51
|
+
* Status/priority catalog invariant check (`store/catalog-invariant-guard.ts`),
|
|
52
|
+
* exposed OPT-IN — deliberately NOT consulted by `queryIssuesWithMeta`, and
|
|
53
|
+
* therefore NEVER on the ordinary read path. A drifted catalog (an unflagged
|
|
54
|
+
* reserved terminal status, or two same-kind rows sharing a case fold) is a
|
|
55
|
+
* bounded data problem: the read path serves the store regardless, and the
|
|
56
|
+
* drift is surfaced as a NAMED, non-zero check by the `store-check` CLI verb.
|
|
57
|
+
* `api.ts`'s `queryHandle` still wires it for every real host, so a caller
|
|
58
|
+
* that WANTS to assert explicitly can call `handle.assertCatalogInvariants?.()`
|
|
59
|
+
* — but no read ever does so implicitly. (Earlier this was awaited inside
|
|
60
|
+
* `queryIssuesWithMeta`; that abort turned one drifted row into a total read
|
|
61
|
+
* outage and was removed. The write path's per-verb abort was removed the
|
|
62
|
+
* same way, so neither ordinary path is gated.)
|
|
60
63
|
*/
|
|
61
64
|
readonly assertCatalogInvariants?: () => Promise<void>;
|
|
62
65
|
}
|
package/query/resolve.d.ts
CHANGED
|
@@ -9,12 +9,39 @@ export interface IResolvedRef {
|
|
|
9
9
|
name: string;
|
|
10
10
|
record: NodeRecord;
|
|
11
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* Resolve `ref` to exactly one LIVE node, by exact uid or by a UNIQUE uid
|
|
14
|
+
* prefix. Exact match is the fast path and always wins.
|
|
15
|
+
*
|
|
16
|
+
* Errors: `AmbiguousReferenceError` when a prefix matches ≥2 live nodes (the
|
|
17
|
+
* candidates are carried on the error and named in its message);
|
|
18
|
+
* `IssueNotFoundError` (kind `issue`) / `CatalogNotFoundError` (any other
|
|
19
|
+
* kind) when an exact uid or prefix matches nothing; `InvalidArgumentError`
|
|
20
|
+
* when `ref` is a uid attempt shorter than the minimum prefix length.
|
|
21
|
+
*/
|
|
22
|
+
export declare function resolveUidPrefix(graph: GraphBackend, ref: string, opts?: {
|
|
23
|
+
expectedKind?: string;
|
|
24
|
+
}): Promise<NodeRecord>;
|
|
25
|
+
/**
|
|
26
|
+
* Like {@link resolveUidPrefix}, but `null` instead of a not-found error when
|
|
27
|
+
* nothing matches — for callers that treat a uid miss as "fall through to a
|
|
28
|
+
* name lookup." An AMBIGUOUS prefix still throws (never silently picks one),
|
|
29
|
+
* and a too-short uid attempt returns `null` so a genuine short business name
|
|
30
|
+
* is not swallowed.
|
|
31
|
+
*/
|
|
32
|
+
export declare function tryResolveUidPrefix(graph: GraphBackend, ref: string, opts?: {
|
|
33
|
+
expectedKind?: string;
|
|
34
|
+
}): Promise<NodeRecord | null>;
|
|
12
35
|
/**
|
|
13
36
|
* Resolve `uid` → the live `issue` node, or throw {@link IssueNotFoundError}
|
|
14
37
|
* (SPEC.md §6.1: "a `uid` with no matching live node throws
|
|
15
38
|
* `IssueNotFoundError(uid)`"). This is the READ-PATH counterpart of
|
|
16
39
|
* `write/tx.ts`'s `getNodeByUidTx` — safe to call standalone because it is
|
|
17
40
|
* not composing a check-then-act write around the result.
|
|
41
|
+
*
|
|
42
|
+
* Accepts an exact uid or a UNIQUE uid prefix (see {@link resolveUidPrefix});
|
|
43
|
+
* a superseded node — whether reached by exact uid or prefix — throws the same
|
|
44
|
+
* {@link StaleSupersedeError} pointing at the chain head.
|
|
18
45
|
*/
|
|
19
46
|
export declare function resolveIssueByUid(graph: GraphBackend, uid: string): Promise<NodeRecord>;
|
|
20
47
|
/**
|
|
@@ -53,10 +53,16 @@ export declare function inspectCatalogInvariants(adapter: StoreAdapter): Promise
|
|
|
53
53
|
* Throws {@link CatalogInvariantError}, naming every offending row and the
|
|
54
54
|
* repair that resolves it, otherwise.
|
|
55
55
|
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
56
|
+
* WHERE IT RUNS: the `store-check` CLI verb — a NAMED, non-zero check an
|
|
57
|
+
* operator runs deliberately — plus any caller that asserts deliberately via
|
|
58
|
+
* `IQueryStoreHandle.assertCatalogInvariants`. It is deliberately NOT run on
|
|
59
|
+
* the ordinary read path (`queryIssuesWithMeta`), on the write path (`api.ts`'s
|
|
60
|
+
* `writeHandle`), or at store open (`openGraphBacklogStore`): a violation must
|
|
61
|
+
* be detected loudly and REPORTED, never allowed to turn every read — or every
|
|
62
|
+
* unrelated write — into an outage.
|
|
63
|
+
*
|
|
64
|
+
* BOUNDED BY CONSTRUCTION: it must not scan the issue graph per call. It reads
|
|
65
|
+
* only the (small) status/priority catalog — see
|
|
60
66
|
* {@link inspectCatalogInvariants}.
|
|
61
67
|
*/
|
|
62
68
|
export declare function assertCatalogInvariants(adapter: StoreAdapter): Promise<void>;
|
package/write/catalog.d.ts
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import { IComponentSummary, ILocationSummary, ILocationType, IProjectSummary } from '../query/types.js';
|
|
2
2
|
import { IEdgeKindRule, IWriteStoreHandle } from './tx.js';
|
|
3
|
+
import { isUidShaped } from './uid-prefix.js';
|
|
3
4
|
import { AdapterTransaction } from '@adhd/sox-store-adapter';
|
|
4
5
|
|
|
5
|
-
/**
|
|
6
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Disambiguation by SHAPE, not a second field (§6.1): a 36-character
|
|
8
|
+
* version-4-UUID-formatted string is a `uid`; anything else is a `name`. The
|
|
9
|
+
* single definition lives in `write/uid-prefix.ts` (which also owns prefix
|
|
10
|
+
* classification); re-exported here so every existing `isUidShaped` importer
|
|
11
|
+
* keeps its path unchanged.
|
|
12
|
+
*/
|
|
13
|
+
export { isUidShaped };
|
|
7
14
|
export interface IResolvedCatalogRow {
|
|
8
15
|
rowid: number;
|
|
9
16
|
uid: string;
|
package/write/errors.d.ts
CHANGED
|
@@ -162,6 +162,35 @@ export declare class CatalogNotFoundError extends BacklogWriteError {
|
|
|
162
162
|
readonly retryable = false;
|
|
163
163
|
constructor(catalogKind: string, ref: string);
|
|
164
164
|
}
|
|
165
|
+
/**
|
|
166
|
+
* A uid reference matched MORE THAN ONE live node by prefix. Carries the
|
|
167
|
+
* candidate set (`uid` + `kind` + `name`) as structured data so a caller can
|
|
168
|
+
* disambiguate and re-run, and renders every candidate into the message.
|
|
169
|
+
*
|
|
170
|
+
* The resolver NEVER auto-selects among the candidates — silently returning
|
|
171
|
+
* the wrong item is strictly worse than refusing. Distinct from
|
|
172
|
+
* `IssueNotFoundError` (an exact uid that is genuinely absent) so a caller can
|
|
173
|
+
* branch structurally: an ambiguous reference is actionable (re-run with a
|
|
174
|
+
* longer prefix or the full uid), a missing one is not.
|
|
175
|
+
*
|
|
176
|
+
* `E_VALIDATION`, never retryable — retrying the identical short reference
|
|
177
|
+
* resolves to the identical candidate set.
|
|
178
|
+
*/
|
|
179
|
+
export declare class AmbiguousReferenceError extends BacklogWriteError {
|
|
180
|
+
readonly ref: string;
|
|
181
|
+
readonly candidates: ReadonlyArray<{
|
|
182
|
+
uid: string;
|
|
183
|
+
kind: string;
|
|
184
|
+
name: string;
|
|
185
|
+
}>;
|
|
186
|
+
readonly code: "E_VALIDATION";
|
|
187
|
+
readonly retryable = false;
|
|
188
|
+
constructor(ref: string, candidates: ReadonlyArray<{
|
|
189
|
+
uid: string;
|
|
190
|
+
kind: string;
|
|
191
|
+
name: string;
|
|
192
|
+
}>);
|
|
193
|
+
}
|
|
165
194
|
/**
|
|
166
195
|
* A flat-catalog write named a token that differs from an EXISTING LIVE row of
|
|
167
196
|
* the SAME kind only by letter case (`IN_PROGRESS` vs `in_progress`, `high` vs
|
|
@@ -208,9 +237,16 @@ export declare class InvalidArgumentError extends BacklogWriteError {
|
|
|
208
237
|
/** No live `issue` node carries the given `uid` (SPEC.md §6.1). */
|
|
209
238
|
export declare class IssueNotFoundError extends BacklogWriteError {
|
|
210
239
|
readonly uid: string;
|
|
240
|
+
readonly asPrefix: boolean;
|
|
211
241
|
readonly code: "E_VALIDATION";
|
|
212
242
|
readonly retryable = false;
|
|
213
|
-
|
|
243
|
+
/**
|
|
244
|
+
* @param uid The uid (or uid prefix) the caller passed in.
|
|
245
|
+
* @param asPrefix Set by the prefix resolver when `uid` was a short prefix
|
|
246
|
+
* rather than a full uid, so the message says "no item matches" instead of
|
|
247
|
+
* implying the caller passed a complete uid that is simply absent.
|
|
248
|
+
*/
|
|
249
|
+
constructor(uid: string, asPrefix?: boolean);
|
|
214
250
|
}
|
|
215
251
|
/**
|
|
216
252
|
* `claim` (§6.3.5): the lease is held by someone else, not yet stale, and the
|
package/write/tx.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { INodeReadExecutor } from './uid-prefix.js';
|
|
1
2
|
import { BacklogWriteError } from './errors.js';
|
|
2
3
|
import { TypePolicy } from '@adhd/sox-graph-store';
|
|
3
4
|
import { AdapterTransaction, StoreAdapter } from '@adhd/sox-store-adapter';
|
|
@@ -98,7 +99,28 @@ export declare function canonicalJSONStringify(value: Record<string, unknown>):
|
|
|
98
99
|
* issued against `tx` instead of the bare adapter — never a `getNodeByUid`
|
|
99
100
|
* call itself, which always runs against `this.adapter` (§4c).
|
|
100
101
|
*/
|
|
101
|
-
export declare function getNodeByUidTx(tx:
|
|
102
|
+
export declare function getNodeByUidTx(tx: INodeReadExecutor, uid: string): Promise<ITxNodeRow | null>;
|
|
103
|
+
/**
|
|
104
|
+
* uid reference → LIVE `issue`/catalog node row, inside a tx, accepting a
|
|
105
|
+
* UNIQUE uid PREFIX as well as an exact uid. This is the single write-side
|
|
106
|
+
* funnel every issue verb ({@link resolveLiveIssueTx}) and catalog
|
|
107
|
+
* uid-resolution (`catalog.ts`'s `resolveByUidTx`, hence `rm-location`) goes
|
|
108
|
+
* through, so the prefix contract is defined once rather than per verb.
|
|
109
|
+
*
|
|
110
|
+
* Exact match is the fast path and keeps winning: a full uid is looked up
|
|
111
|
+
* directly, and only when that misses is a prefix scan attempted. A prefix
|
|
112
|
+
* below `MIN_UID_PREFIX_LENGTH` throws an "too short" argument error; a prefix
|
|
113
|
+
* that matches zero rows throws the kind-appropriate not-found; a prefix that
|
|
114
|
+
* matches two or more throws {@link AmbiguousReferenceError} naming every
|
|
115
|
+
* candidate — never an arbitrary pick.
|
|
116
|
+
*
|
|
117
|
+
* @throws IssueNotFoundError (kind `issue`) / CatalogNotFoundError (any other
|
|
118
|
+
* kind) when nothing live matches.
|
|
119
|
+
* @throws AmbiguousReferenceError when a prefix matches more than one live row.
|
|
120
|
+
*/
|
|
121
|
+
export declare function resolveUidPrefixTx(exec: INodeReadExecutor, ref: string, opts?: {
|
|
122
|
+
expectedKind?: string;
|
|
123
|
+
}): Promise<ITxNodeRow>;
|
|
102
124
|
/**
|
|
103
125
|
* Resolves `uid` to the CURRENT, live `issue` row, or throws.
|
|
104
126
|
*
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { InvalidArgumentError } from './errors.js';
|
|
2
|
+
|
|
3
|
+
/** Whether `ref` is an exact, full uid (resolve-or-throw), never a business `name` and never a prefix. */
|
|
4
|
+
export declare function isUidShaped(ref: string): boolean;
|
|
5
|
+
/**
|
|
6
|
+
* The shortest uid prefix this resolver will accept. Eight hex characters —
|
|
7
|
+
* the first UUID block, the human-copyable unit, and 32 bits of address space;
|
|
8
|
+
* see this file's header for the full justification.
|
|
9
|
+
*/
|
|
10
|
+
export declare const MIN_UID_PREFIX_LENGTH = 8;
|
|
11
|
+
/**
|
|
12
|
+
* Whether `ref` is a proper prefix (length `>=` {@link MIN_UID_PREFIX_LENGTH},
|
|
13
|
+
* `< 36`) of some canonical uid string — hex characters with hyphens only at
|
|
14
|
+
* the canonical boundary positions.
|
|
15
|
+
*/
|
|
16
|
+
export declare function isUidPrefixShaped(ref: string): boolean;
|
|
17
|
+
/** How a caller-supplied reference reads: an exact uid, a uid prefix, a too-short uid attempt, or not uid-shaped at all. */
|
|
18
|
+
export type UidRefKind = 'exact' | 'prefix' | 'too-short' | 'not-uid';
|
|
19
|
+
/**
|
|
20
|
+
* Classify `ref` for uid resolution. `too-short` is a uid ATTEMPT (hex/hyphen
|
|
21
|
+
* only) below {@link MIN_UID_PREFIX_LENGTH} — a strict uid context rejects it
|
|
22
|
+
* as too short, while a uid-or-name context falls back to a name lookup, so a
|
|
23
|
+
* genuine 6-character value that is itself a valid name is never swallowed.
|
|
24
|
+
*/
|
|
25
|
+
export declare function classifyUidRef(ref: string): UidRefKind;
|
|
26
|
+
/** One live node matching a uid PREFIX, projected to the fields the ambiguity decision and its error need. */
|
|
27
|
+
export interface IUidCandidate {
|
|
28
|
+
uid: string;
|
|
29
|
+
kind: string;
|
|
30
|
+
name: string | null;
|
|
31
|
+
isSuperseded: boolean;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The minimal executor a prefix candidate read needs. Structural, so it is
|
|
35
|
+
* satisfied by `AdapterTransaction`, graph-store's `GraphTransaction`, and a
|
|
36
|
+
* bare `StoreAdapter` alike — nothing here depends on which one it is.
|
|
37
|
+
*/
|
|
38
|
+
export interface IUidPrefixQueryExecutor {
|
|
39
|
+
executeAll<T = Record<string, unknown>>(sql: string, args?: unknown[]): Promise<{
|
|
40
|
+
rows: T[];
|
|
41
|
+
}>;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The executor a uid resolution needs: the prefix candidate read plus the
|
|
45
|
+
* exact single-row read. Structural, so `AdapterTransaction`,
|
|
46
|
+
* graph-store's `GraphTransaction`, and a bare `StoreAdapter` all satisfy it.
|
|
47
|
+
*/
|
|
48
|
+
export interface INodeReadExecutor extends IUidPrefixQueryExecutor {
|
|
49
|
+
executeGet<T = Record<string, unknown>>(sql: string, args?: unknown[]): Promise<T | null>;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Every LIVE node whose uid starts with `prefix`.
|
|
53
|
+
*
|
|
54
|
+
* The range predicate `uid >= ? AND uid < ?` is an indexed prefix scan (the
|
|
55
|
+
* store keeps a unique index on `uid`) and, unlike a `LIKE` pattern, is
|
|
56
|
+
* case-exact against the lowercase uids the store mints — so the input is
|
|
57
|
+
* lower-cased once here and the caller never has to reason about collation.
|
|
58
|
+
* Soft-deleted rows are excluded in SQL (`t_invalid IS NULL`); a superseded
|
|
59
|
+
* row is returned (its `is_superseded` flag carried) so a caller that resolves
|
|
60
|
+
* to it can raise the same stale-reference error the exact path does.
|
|
61
|
+
*/
|
|
62
|
+
export declare function queryLiveUidPrefixCandidates(exec: IUidPrefixQueryExecutor, prefix: string): Promise<IUidCandidate[]>;
|
|
63
|
+
/**
|
|
64
|
+
* The ambiguity decision, shared by both paths. Returns the sole candidate, or
|
|
65
|
+
* `null` when there are none (the caller owns the not-found error, which
|
|
66
|
+
* differs by kind). Two or more candidates throw {@link AmbiguousReferenceError}
|
|
67
|
+
* — the resolver never auto-selects.
|
|
68
|
+
*/
|
|
69
|
+
export declare function selectUniqueUidCandidate(ref: string, candidates: readonly IUidCandidate[]): IUidCandidate | null;
|
|
70
|
+
/**
|
|
71
|
+
* The not-found error for a uid reference, keyed by the kind the caller asked
|
|
72
|
+
* for. `asPrefix` selects the prefix-specific wording ("no item matches")
|
|
73
|
+
* versus the exact-uid wording, so a caller can tell "this short reference
|
|
74
|
+
* matched nothing" from "this full uid is not here."
|
|
75
|
+
*/
|
|
76
|
+
export declare function missingUidError(expectedKind: string | undefined, ref: string, asPrefix: boolean): Error;
|
|
77
|
+
/** The too-short refusal — a uid attempt below {@link MIN_UID_PREFIX_LENGTH}. */
|
|
78
|
+
export declare function tooShortUidError(ref: string): InvalidArgumentError;
|