@adhd/backlog 1.0.1 → 1.0.3
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 +19 -0
- package/api.ir.json +1 -1
- package/cli-input-envelope.d.ts +40 -0
- package/index.d.ts +4 -0
- package/index.js +43 -42
- package/index.mjs +3487 -2968
- package/package.json +13 -13
- package/query/query.d.ts +12 -0
- package/store/catalog-invariant-guard.d.ts +62 -0
- package/write/catalog-merge.d.ts +123 -0
- package/write/catalog-repair.d.ts +74 -0
- package/write/catalog.d.ts +57 -0
- package/write/create-issue.d.ts +1 -1
- package/write/transition.d.ts +4 -3
package/package.json
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adhd/backlog",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"bin": {
|
|
5
5
|
"adhd-backlog": "index.js"
|
|
6
6
|
},
|
|
7
7
|
"dependencies": {
|
|
8
|
-
"@adhd/apigen-base-logical": "^0.1.
|
|
9
|
-
"@adhd/apigen-core-client": "^0.3.
|
|
10
|
-
"@adhd/apigen-engine-naming": "^0.2.
|
|
11
|
-
"@adhd/apigen-plugin-api-fastify": "^0.2.
|
|
12
|
-
"@adhd/apigen-plugin-batch": "^0.2.
|
|
13
|
-
"@adhd/apigen-plugin-cli-output": "^0.2.
|
|
14
|
-
"@adhd/apigen-plugin-ir-cache": "^0.1.
|
|
15
|
-
"@adhd/apigen-plugin-mcp": "^0.3.
|
|
16
|
-
"@adhd/apigen-plugin-openapi": "^0.2.
|
|
17
|
-
"@adhd/environment": "^0.1.
|
|
18
|
-
"@adhd/environment-base-spec": "^0.1.
|
|
19
|
-
"@adhd/environment-builder": "^0.1.
|
|
8
|
+
"@adhd/apigen-base-logical": "^0.1.4",
|
|
9
|
+
"@adhd/apigen-core-client": "^0.3.3",
|
|
10
|
+
"@adhd/apigen-engine-naming": "^0.2.5",
|
|
11
|
+
"@adhd/apigen-plugin-api-fastify": "^0.2.6",
|
|
12
|
+
"@adhd/apigen-plugin-batch": "^0.2.5",
|
|
13
|
+
"@adhd/apigen-plugin-cli-output": "^0.2.6",
|
|
14
|
+
"@adhd/apigen-plugin-ir-cache": "^0.1.3",
|
|
15
|
+
"@adhd/apigen-plugin-mcp": "^0.3.1",
|
|
16
|
+
"@adhd/apigen-plugin-openapi": "^0.2.5",
|
|
17
|
+
"@adhd/environment": "^0.1.8",
|
|
18
|
+
"@adhd/environment-base-spec": "^0.1.3",
|
|
19
|
+
"@adhd/environment-builder": "^0.1.7",
|
|
20
20
|
"@adhd/sox-graph-store": "^0.11.1",
|
|
21
21
|
"@adhd/sox-hybrid-search": "^0.5.0",
|
|
22
22
|
"@adhd/sox-semantic": "^0.1.8",
|
package/query/query.d.ts
CHANGED
|
@@ -47,6 +47,18 @@ export interface IQueryStoreHandle {
|
|
|
47
47
|
* open) so a long-lived process notices a store rewritten under it.
|
|
48
48
|
*/
|
|
49
49
|
readonly assertVocabulary?: () => Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* Fail-loud status/priority catalog invariant guard
|
|
52
|
+
* (`store/catalog-invariant-guard.ts`). When present, `queryIssuesWithMeta`
|
|
53
|
+
* awaits it alongside {@link IQueryStoreHandle.assertVocabulary} before
|
|
54
|
+
* dispatching any view: a catalog carrying an unflagged reserved terminal
|
|
55
|
+
* status, or two same-kind rows sharing a case fold, must fail loudly rather
|
|
56
|
+
* than serve a mis-classified read. OPTIONAL so a hand-built handle (tests,
|
|
57
|
+
* the ETL) is unaffected — `api.ts`'s `queryHandle` wires it for every real
|
|
58
|
+
* host, and it is deliberately re-run per query (not latched at open), like
|
|
59
|
+
* the vocabulary guard.
|
|
60
|
+
*/
|
|
61
|
+
readonly assertCatalogInvariants?: () => Promise<void>;
|
|
50
62
|
}
|
|
51
63
|
/**
|
|
52
64
|
* Whether a `query` verb's own input touches the semantic channel at all —
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { StoreAdapter } from '@adhd/sox-store-adapter';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* One way the catalog can violate an invariant. Carried as structured data on
|
|
5
|
+
* {@link CatalogInvariantError} so a caller (a diagnostic, a test) can act on
|
|
6
|
+
* the offending rows without parsing the rendered message.
|
|
7
|
+
*/
|
|
8
|
+
export type CatalogInvariantViolation = {
|
|
9
|
+
kind: 'unflagged-terminal';
|
|
10
|
+
/** The row's own name, verbatim. */
|
|
11
|
+
name: string;
|
|
12
|
+
/** The row's uid — the handle a reader uses to inspect or repair it. */
|
|
13
|
+
uid: string;
|
|
14
|
+
} | {
|
|
15
|
+
kind: 'case-fragment-duplicate';
|
|
16
|
+
/** The case-folded name the rows collide on. */
|
|
17
|
+
fold: string;
|
|
18
|
+
/** Every colliding spelling, in rowid order (≥ 2). */
|
|
19
|
+
names: readonly string[];
|
|
20
|
+
/** The colliding rows' uids, aligned index-for-index with {@link names}. */
|
|
21
|
+
uids: readonly string[];
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Render a violation list into the self-explaining message the guard throws.
|
|
25
|
+
* Exported so a diagnostic can reuse the exact wording the error carries.
|
|
26
|
+
*/
|
|
27
|
+
export declare function renderCatalogInvariantViolations(violations: ReadonlyArray<CatalogInvariantViolation>): string;
|
|
28
|
+
/**
|
|
29
|
+
* The status/priority catalog violates an invariant this build's read layer
|
|
30
|
+
* depends on. Carries the offending rows as structured fields so a caller can
|
|
31
|
+
* render them; the message itself names every row AND the repair.
|
|
32
|
+
*/
|
|
33
|
+
export declare class CatalogInvariantError extends Error {
|
|
34
|
+
readonly violations: ReadonlyArray<CatalogInvariantViolation>;
|
|
35
|
+
constructor(violations: ReadonlyArray<CatalogInvariantViolation>);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Read every live status/priority row and return the invariant violations.
|
|
39
|
+
* A pure read — it never writes, so it is safe against any store, including
|
|
40
|
+
* the live one.
|
|
41
|
+
*
|
|
42
|
+
* BOUNDED by the catalog: exactly one statement against
|
|
43
|
+
* `node WHERE kind IN ('status','priority') AND t_invalid IS NULL`, folding
|
|
44
|
+
* each name in JS. The row set is the catalog (a handful of rows), never the
|
|
45
|
+
* issue graph.
|
|
46
|
+
*/
|
|
47
|
+
export declare function inspectCatalogInvariants(adapter: StoreAdapter): Promise<CatalogInvariantViolation[]>;
|
|
48
|
+
/**
|
|
49
|
+
* Assert the store's status/priority catalog satisfies both invariants.
|
|
50
|
+
*
|
|
51
|
+
* Passes (no throw) on an empty store, and on a catalog whose reserved
|
|
52
|
+
* terminal statuses are flagged and whose same-kind names are fold-unique.
|
|
53
|
+
* Throws {@link CatalogInvariantError}, naming every offending row and the
|
|
54
|
+
* repair that resolves it, otherwise.
|
|
55
|
+
*
|
|
56
|
+
* BOUNDED BY CONSTRUCTION: this runs on every write verb (`api.ts`'s
|
|
57
|
+
* `writeHandle`), every query (`api.ts`'s `queryHandle` → `query.ts`), and
|
|
58
|
+
* store open (`openGraphBacklogStore`), so it must not scan the store per
|
|
59
|
+
* call. It reads only the (small) status/priority catalog — see
|
|
60
|
+
* {@link inspectCatalogInvariants}.
|
|
61
|
+
*/
|
|
62
|
+
export declare function assertCatalogInvariants(adapter: StoreAdapter): Promise<void>;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { IWriteStoreHandle } from './tx.js';
|
|
2
|
+
import { NodeRecord } from '@adhd/sox-graph-store';
|
|
3
|
+
|
|
4
|
+
/** The two catalog kinds this repair collapses (D3's whole scope). */
|
|
5
|
+
export type CatalogKind = 'status' | 'priority';
|
|
6
|
+
/**
|
|
7
|
+
* A status fold group that CANNOT be safely collapsed: two or more LIVE rows
|
|
8
|
+
* share a fold, yet none carries the write path's lowercase spelling. Merging
|
|
9
|
+
* would have to pick a non-lowercase canonical (or rename one to a spelling no
|
|
10
|
+
* write emits), and the next ordinary `create` would exact-match miss and
|
|
11
|
+
* re-mint the fragment. Surfaced, never merged, never renamed.
|
|
12
|
+
*/
|
|
13
|
+
export interface IUnmergeableGroup {
|
|
14
|
+
kind: CatalogKind;
|
|
15
|
+
names: string[];
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* A planned merge: the fold groups to collapse, by uid. `groups` is EMPTY when
|
|
19
|
+
* the store has no live case-variant group — the idempotency signal.
|
|
20
|
+
*
|
|
21
|
+
* `unmergeable` carries the status groups deliberately LEFT ALONE (no
|
|
22
|
+
* write-spelling member). It is a diagnostic, never an action: the plan is
|
|
23
|
+
* incomplete-by-refusal for those groups, so a caller that treats a zero-group
|
|
24
|
+
* plan as "the store is clean" must also check this list.
|
|
25
|
+
*/
|
|
26
|
+
export interface IMergePlan {
|
|
27
|
+
groups: Array<{
|
|
28
|
+
canonicalUid: string;
|
|
29
|
+
fragmentUids: string[];
|
|
30
|
+
}>;
|
|
31
|
+
unmergeable: IUnmergeableGroup[];
|
|
32
|
+
at: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* One merged-away fragment, with everything needed to restore it. The
|
|
36
|
+
* `fragmentMeta`/`canonicalMetaBefore` fields carry the full prior `meta`
|
|
37
|
+
* objects (not just the changed keys), so reverse is byte-exact on the keys
|
|
38
|
+
* this repair touches and never guesses.
|
|
39
|
+
*
|
|
40
|
+
* {@link canonicalMetaBefore} is the one field beyond the required shape —
|
|
41
|
+
* without it a reverse cannot distinguish "canonical had no rank, adopted the
|
|
42
|
+
* fragment's" from "canonical's own rank was this", and cannot remove exactly
|
|
43
|
+
* the aliases this repair appended. It is OPTIONAL so a hand-authored minimal
|
|
44
|
+
* journal still type-checks; a journal written by this module always includes
|
|
45
|
+
* it.
|
|
46
|
+
*/
|
|
47
|
+
export interface IMergeJournalEntry {
|
|
48
|
+
fragmentUid: string;
|
|
49
|
+
canonicalUid: string;
|
|
50
|
+
fragmentMeta: unknown;
|
|
51
|
+
repointedEdges: Array<{
|
|
52
|
+
srcRowid: number;
|
|
53
|
+
rel: string;
|
|
54
|
+
fragmentDstRowid: number;
|
|
55
|
+
}>;
|
|
56
|
+
/** The fragment's numeric `rank` at apply time, when it had one — the value the survivor did NOT adopt unless it had none of its own. */
|
|
57
|
+
retainedRank?: number;
|
|
58
|
+
/** Present only when a PRIORITY group had no upper-spelled member: the canonical row was renamed to uppercase, and this is how to undo it. A status canonical is never renamed. */
|
|
59
|
+
renamed?: {
|
|
60
|
+
uid: string;
|
|
61
|
+
from: string;
|
|
62
|
+
to: string;
|
|
63
|
+
};
|
|
64
|
+
/** The canonical row's full prior `meta`, for exact reversal of the appended aliases / adopted rank. */
|
|
65
|
+
canonicalMetaBefore?: unknown;
|
|
66
|
+
}
|
|
67
|
+
export interface IMergeJournal {
|
|
68
|
+
entries: IMergeJournalEntry[];
|
|
69
|
+
at: string;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Build the collapse plan from the LIVE catalog rows. Pure over node records —
|
|
73
|
+
* it never reads edges, so a fragment with more incoming edges than the
|
|
74
|
+
* canonical loses anyway (owner directive: spelling decides, not edge count).
|
|
75
|
+
*
|
|
76
|
+
* Grouping is by {@link catalogNameFold} alone. Within a fold group the
|
|
77
|
+
* canonical is the member already carrying the write path's spelling for that
|
|
78
|
+
* kind — lowercase for `status`, uppercase for `priority`.
|
|
79
|
+
*
|
|
80
|
+
* A `priority` group with no upper-spelled member falls back to its
|
|
81
|
+
* lowest-rowid member (deterministically renamed to uppercase at apply, as
|
|
82
|
+
* before). A `status` group with no lower-spelled member is NOT planned at all:
|
|
83
|
+
* any canonical would carry a spelling the write path never emits, so merging
|
|
84
|
+
* would regenerate the fragment on the next ordinary `create`. It is surfaced
|
|
85
|
+
* in `unmergeable` instead.
|
|
86
|
+
*/
|
|
87
|
+
export declare function planCaseFragmentMerge(liveStatuses: readonly NodeRecord[], livePriorities: readonly NodeRecord[]): IMergePlan;
|
|
88
|
+
/**
|
|
89
|
+
* Apply the plan in ONE `executeWriteTransaction` (`BEGIN IMMEDIATE` + bounded
|
|
90
|
+
* busy-only retry, §4c) — every group in the plan commits together or not at
|
|
91
|
+
* all, so a refusal in a later group can never leave an earlier group half
|
|
92
|
+
* merged.
|
|
93
|
+
*
|
|
94
|
+
* For each group: re-point every incoming catalog edge onto the canonical,
|
|
95
|
+
* append the merged-away (and renamed-from) spellings to
|
|
96
|
+
* `canonical.meta.aliases`, adopt the canonical's rank (a fragment's only if
|
|
97
|
+
* the canonical lacks one), and invalidate each fragment with `t_invalid`.
|
|
98
|
+
*
|
|
99
|
+
* REFUSES (throws, rolling the whole apply back) any group whose fragment does
|
|
100
|
+
* not fold to the canonical's live name, or whose kind differs — the
|
|
101
|
+
* `closed`/`DONE` guard: two distinct tokens are never case-variants and can
|
|
102
|
+
* never be collapsed here. It also REFUSES a `status` group whose canonical is
|
|
103
|
+
* not already lowercase-spelled — merging INTO a spelling the write path never
|
|
104
|
+
* emits would let the next ordinary `create` re-mint the fragment.
|
|
105
|
+
*
|
|
106
|
+
* Only a `priority` group is ever renamed (to uppercase, when it had no
|
|
107
|
+
* uppercase member). A `status` `name` is NEVER rewritten.
|
|
108
|
+
*
|
|
109
|
+
* A planned fragment that is no longer LIVE (a concurrent writer invalidated
|
|
110
|
+
* it between plan and apply) is tolerated — it is skipped, exactly as D1's
|
|
111
|
+
* apply re-guards each row.
|
|
112
|
+
*/
|
|
113
|
+
export declare function applyCaseFragmentMerge(handle: IWriteStoreHandle, plan: IMergePlan): Promise<IMergeJournal>;
|
|
114
|
+
/**
|
|
115
|
+
* Reverse an applied journal in ONE `executeWriteTransaction`: re-liven each
|
|
116
|
+
* fragment, restore its exact prior `meta`, re-point its original edges back
|
|
117
|
+
* off the canonical, restore the canonical's prior `meta` (dropping the
|
|
118
|
+
* appended aliases / adopted rank), and undo any rename.
|
|
119
|
+
*
|
|
120
|
+
* A fragment or canonical uid that no longer exists is skipped — the same
|
|
121
|
+
* tolerant posture D1's reverse takes for a concurrently-deleted row.
|
|
122
|
+
*/
|
|
123
|
+
export declare function reverseCaseFragmentMerge(handle: IWriteStoreHandle, journal: IMergeJournal): Promise<void>;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { RESERVED_TERMINAL_STATUS_NAMES } from './catalog.js';
|
|
2
|
+
import { IWriteStoreHandle } from './tx.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The reserved terminal vocabulary — SINGLE SOURCE, owned by
|
|
6
|
+
* `write/catalog.ts` (the mint path that seeds from it) and re-exported here
|
|
7
|
+
* so this module's consumers keep one import site. A drift test
|
|
8
|
+
* (`catalog-mint-terminal.spec.ts`) asserts this is the SAME object the mint
|
|
9
|
+
* path uses, i.e. that exactly ONE definition exists in the codebase.
|
|
10
|
+
*/
|
|
11
|
+
export { RESERVED_TERMINAL_STATUS_NAMES };
|
|
12
|
+
/**
|
|
13
|
+
* Unicode fold used by BOTH duplicate detection and JS-side grouping — never
|
|
14
|
+
* `SQL lower()`/`NOCASE` (ASCII-only, so it folds `A`–`Z` and nothing else).
|
|
15
|
+
* `NFKC` first so compatibility forms collapse, then `toLowerCase()` (the JS
|
|
16
|
+
* Unicode-aware case fold, mirroring PostgreSQL `citext`'s documented Unicode
|
|
17
|
+
* behavior) so `RESOLVED`, `resolved` and `Resolved` group together.
|
|
18
|
+
*/
|
|
19
|
+
export declare function catalogNameFold(name: string): string;
|
|
20
|
+
/**
|
|
21
|
+
* The set of LIVE `status` rowids the backfill should set `terminal:true` on,
|
|
22
|
+
* plus the plan timestamp. Rows already `terminal:true` are EXCLUDED here (the
|
|
23
|
+
* idempotency guarantee: a fully-repaired store plans nothing).
|
|
24
|
+
*/
|
|
25
|
+
export interface ITerminalBackfillPlan {
|
|
26
|
+
setTerminalRowids: number[];
|
|
27
|
+
at: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The apply journal — enough to restore EXACTLY the prior state. `hadTerminal`
|
|
31
|
+
* is the row's `meta.terminal === true` at apply time; because a row already
|
|
32
|
+
* `true` is never changed (and never journaled), every real entry's
|
|
33
|
+
* `hadTerminal` is `false`.
|
|
34
|
+
*/
|
|
35
|
+
export interface ITerminalBackfillJournal {
|
|
36
|
+
entries: Array<{
|
|
37
|
+
rowid: number;
|
|
38
|
+
uid: string;
|
|
39
|
+
hadTerminal: boolean;
|
|
40
|
+
}>;
|
|
41
|
+
at: string;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Build the backfill plan: every LIVE `status` row whose FOLDED name is a
|
|
45
|
+
* reserved terminal name and whose `terminal` flag is not already `true`.
|
|
46
|
+
*
|
|
47
|
+
* Reads are plain SELECTs against the bare adapter — this is a repair planner,
|
|
48
|
+
* not a write verb, and it holds no lock across the (separately transactional)
|
|
49
|
+
* apply. A row created or renamed between plan and apply is tolerantly skipped
|
|
50
|
+
* by {@link applyTerminalBackfill}, which re-guards each row inside its own
|
|
51
|
+
* transaction.
|
|
52
|
+
*/
|
|
53
|
+
export declare function planTerminalBackfill(handle: IWriteStoreHandle, at?: string): Promise<ITerminalBackfillPlan>;
|
|
54
|
+
/**
|
|
55
|
+
* Apply the plan in ONE `executeWriteTransaction` (`BEGIN IMMEDIATE` +
|
|
56
|
+
* busy-only bounded retry, §4c): read each target status row, guarded-parse
|
|
57
|
+
* its `meta`, set `terminal:true`, guarded
|
|
58
|
+
* `UPDATE node SET meta = ? WHERE rowid = ? AND t_invalid IS NULL`. Only the
|
|
59
|
+
* `terminal` flag is touched — every other `meta` key is carried through
|
|
60
|
+
* verbatim.
|
|
61
|
+
*
|
|
62
|
+
* A rowid that is no longer a LIVE `status` row between plan and apply is
|
|
63
|
+
* skipped (nothing to repair, nothing journaled); a planned rowid that IS a
|
|
64
|
+
* live non-`status` row is a plan/DB mismatch and throws loudly (ADR-0002 D5
|
|
65
|
+
* step 4's error-loudly posture) rather than silently rewriting an unrelated
|
|
66
|
+
* node.
|
|
67
|
+
*/
|
|
68
|
+
export declare function applyTerminalBackfill(handle: IWriteStoreHandle, plan: ITerminalBackfillPlan): Promise<ITerminalBackfillJournal>;
|
|
69
|
+
/**
|
|
70
|
+
* Reverse an applied journal in ONE `executeWriteTransaction`: restore
|
|
71
|
+
* `meta.terminal` to its recorded `hadTerminal` for each entry — and NOTHING
|
|
72
|
+
* else. A rowid that is no longer a LIVE `status` row is skipped.
|
|
73
|
+
*/
|
|
74
|
+
export declare function reverseTerminalBackfill(handle: IWriteStoreHandle, journal: ITerminalBackfillJournal): Promise<void>;
|
package/write/catalog.d.ts
CHANGED
|
@@ -56,12 +56,69 @@ export interface IMintOrResolveInput {
|
|
|
56
56
|
*/
|
|
57
57
|
at?: string;
|
|
58
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* The reserved TERMINAL status vocabulary — the ONE in-code definition
|
|
61
|
+
* (SPEC.md §2: `status.terminal` drives closedness). Modelled directly on
|
|
62
|
+
* {@link EDGE_KIND_TABLE} further down this file: a frozen in-code table the
|
|
63
|
+
* mint layer seeds from, exactly as {@link resolveEdgeKindTx} seeds
|
|
64
|
+
* `edge_kind` from §3's fixed edge table.
|
|
65
|
+
*
|
|
66
|
+
* The flag, never the name, is the read-time source of truth —
|
|
67
|
+
* `query/card.ts`'s `isStatusTerminal` stays name-blind (ADR-0002 D1: an
|
|
68
|
+
* unfixed-source gap is repaired at the SOURCE, never papered over with a
|
|
69
|
+
* read-time name fallback, which would be a second, drifting source of
|
|
70
|
+
* truth). This table is that source fix: a name listed here that has no live
|
|
71
|
+
* `status` row is SEEDED `terminal:true` by {@link mintOrResolveStatusTx}, so
|
|
72
|
+
* minting `closed` (or any other reserved name) can no longer produce a row
|
|
73
|
+
* that reads as non-terminal — the drift `catalog-repair.ts` exists to clean
|
|
74
|
+
* up is not regenerated on the next mint.
|
|
75
|
+
*
|
|
76
|
+
* Membership is EXACT and case-sensitive — the same predicate the `(kind,name)`
|
|
77
|
+
* lookup uses (`WHERE name = ?`). A case-variant (`Closed`) is deliberately
|
|
78
|
+
* NOT a member: it is an exact miss, self-heals into its own row (never a
|
|
79
|
+
* throw), and is left to the separately-owned case-fragment repair, which
|
|
80
|
+
* folds names. Spelling the canonical rows here is what pins them; there is
|
|
81
|
+
* exactly ONE such table in the codebase (`catalog-repair.ts` re-exports
|
|
82
|
+
* this set rather than declaring its own).
|
|
83
|
+
*/
|
|
84
|
+
export declare const RESERVED_TERMINAL_STATUS_NAMES: ReadonlySet<string>;
|
|
85
|
+
/** Whether `name` is EXACTLY one of {@link RESERVED_TERMINAL_STATUS_NAMES} — case-sensitive, the same predicate the `(kind,name)` resolve uses. */
|
|
86
|
+
export declare function isReservedTerminalStatusName(name: string): boolean;
|
|
59
87
|
/**
|
|
60
88
|
* The flat-catalog find-then-create (§1, §4c, §6.1): `kind`/`status`/
|
|
61
89
|
* `priority`/`agent` are mintable on an unresolved NAME; a uid-shaped `ref`
|
|
62
90
|
* that does not resolve instead throws — minting NEVER applies to a uid.
|
|
63
91
|
*/
|
|
64
92
|
export declare function mintOrResolveCatalogTx(tx: AdapterTransaction, input: IMintOrResolveInput): Promise<IResolvedCatalogRow>;
|
|
93
|
+
/**
|
|
94
|
+
* Resolve-or-seed a `status` catalog row (§2, §6.1, §6.3.2, §6.3.4) — the
|
|
95
|
+
* ONE sanctioned status mint path, and the write-side counterpart of the
|
|
96
|
+
* read-side `query/card.ts`'s `isStatusTerminal`.
|
|
97
|
+
*
|
|
98
|
+
* Unlike the generic {@link mintOrResolveCatalogTx}, it SEEDS `terminal` from
|
|
99
|
+
* the frozen {@link RESERVED_TERMINAL_STATUS_NAMES} table on a miss — never
|
|
100
|
+
* from a call-site literal (`terminal:false` hardcoded at the call site was
|
|
101
|
+
* the drift's root cause) and never from a read-time name heuristic
|
|
102
|
+
* (`isStatusTerminal` stays name-blind, ADR-0002 D1). A name in the table
|
|
103
|
+
* seeds `terminal:true`; every other name seeds the pre-existing
|
|
104
|
+
* `terminal:false` default (§6.3.2: a novel or mistyped name must never
|
|
105
|
+
* silently close or exclude an item).
|
|
106
|
+
*
|
|
107
|
+
* Self-healing and idempotent, exactly like {@link resolveEdgeKindTx}: the
|
|
108
|
+
* exact `(kind,name)` lookup inside {@link mintOrResolveCatalogTx} finds the
|
|
109
|
+
* seeded row on every subsequent call, so reseeding never duplicates — the
|
|
110
|
+
* store converges to one live `status` row per name. A case-variant name
|
|
111
|
+
* (`Closed` vs a seeded `closed`) is an exact miss, so it self-heals into its
|
|
112
|
+
* own row rather than throwing — the pre-existing resolution property, not a
|
|
113
|
+
* terminality decision (the case-fragment repair folds those separately).
|
|
114
|
+
*
|
|
115
|
+
* A uid-shaped `ref` that does not resolve still throws
|
|
116
|
+
* `CatalogNotFoundError` — minting never applies to a uid (§6.1).
|
|
117
|
+
*/
|
|
118
|
+
export declare function mintOrResolveStatusTx(tx: AdapterTransaction, input: {
|
|
119
|
+
ref: string;
|
|
120
|
+
at?: string;
|
|
121
|
+
}): Promise<IResolvedCatalogRow>;
|
|
65
122
|
/** `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
123
|
export declare function nextPriorityRankTx(tx: AdapterTransaction): Promise<number>;
|
|
67
124
|
/**
|
package/write/create-issue.d.ts
CHANGED
|
@@ -25,7 +25,7 @@ export interface ICreateIssueInput {
|
|
|
25
25
|
component?: string;
|
|
26
26
|
/** catalog name or uid; default is the project's configured `policy.defaultKind`, falling back to the global `"issue"` row. An unresolved NAME mints; a uid-shaped ref that does not resolve throws (§6.1). */
|
|
27
27
|
kind?: string;
|
|
28
|
-
/** catalog name or uid; default is `policy.defaultStatus`, falling back to the global `"open"` row
|
|
28
|
+
/** catalog name or uid; default is `policy.defaultStatus`, falling back to the global `"open"` row. An unresolved NAME is minted via `mintOrResolveStatusTx`: a name in the frozen reserved terminal table (`RESERVED_TERMINAL_STATUS_NAMES`, `catalog.ts`) seeds `terminal:true`, every other name seeds `terminal:false` (a novel/typo name must never silently close an item, §6.3.2); a uid-shaped ref that does not resolve throws (§6.1). */
|
|
29
29
|
status?: string;
|
|
30
30
|
/** catalog name or uid; genuinely OPTIONAL — §6.3.2 states minting behavior for a GIVEN unresolved name but, unlike `kind`/`status`, states no fallback for the omitted case; omitted therefore writes no `has_priority` edge at all (a deliberate reading of §6.3.2's more precise per-field text over §4's summary prose — see this project's own README/CHANGELOG note on this slice for the citation). An unresolved NAME mints with `rank` = one past the current max (lowest urgency); a uid-shaped ref that does not resolve throws. */
|
|
31
31
|
priority?: string;
|
package/write/transition.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export interface ITransitionInput {
|
|
|
6
6
|
uid: string;
|
|
7
7
|
/** The identity of the acting agent or person (§6.3's opening rule). REQUIRED. */
|
|
8
8
|
by: string;
|
|
9
|
-
/** catalog name or uid. An unresolved NAME
|
|
9
|
+
/** catalog name or uid. An unresolved NAME is minted via `mintOrResolveStatusTx` (exactly like `create`'s own `status` field, §6.3.2/§6.1): a name in the frozen reserved terminal table (`RESERVED_TERMINAL_STATUS_NAMES`, `catalog.ts`) seeds `terminal:true`, every other name seeds `terminal:false`; a uid-shaped ref that does not resolve throws `CatalogNotFoundError('status', ref)`. */
|
|
10
10
|
toStatus: string;
|
|
11
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
12
|
note?: string;
|
|
@@ -42,8 +42,9 @@ export interface ITransitionOutcome {
|
|
|
42
42
|
* `update` — this file's own doc comment on `update.ts` explains why that
|
|
43
43
|
* reuses this error class rather than a new one), `CatalogNotFoundError('status',
|
|
44
44
|
* toStatus)` (uid-shaped `toStatus` only — an unresolved NAME instead
|
|
45
|
-
* auto-mints
|
|
46
|
-
*
|
|
45
|
+
* auto-mints via `mintOrResolveStatusTx`, seeding `terminal` from the frozen
|
|
46
|
+
* reserved table exactly as `create`'s own `status` field does (§6.1), a
|
|
47
|
+
* project-declared `requiredFields` entry (§2 — here, always just
|
|
47
48
|
* `status`, see this file's own doc comment) left blank,
|
|
48
49
|
* `NoteRequiredError` (policy-gated), `CitationRequiredError`
|
|
49
50
|
* (policy-gated, terminal-only), `CitationUnverifiableError(target,
|