@adhd/backlog 1.0.4 → 1.0.6
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 +69 -0
- package/README.md +194 -39
- package/api.d.ts +87 -0
- package/api.ir.json +1 -1
- package/citation.d.ts +176 -0
- package/envelope.d.ts +36 -2
- package/index.d.ts +23 -3
- package/index.js +105 -57
- package/index.mjs +11450 -6758
- package/ir-artifact.d.ts +7 -3
- package/lifecycle.d.ts +49 -0
- package/package.json +5 -5
- package/query/canonical.d.ts +16 -0
- package/query/card.d.ts +116 -4
- package/query/get.d.ts +8 -1
- package/query/index.d.ts +1 -0
- package/query/query.d.ts +32 -22
- package/query/redirect.d.ts +30 -0
- package/query/resolve.d.ts +75 -0
- package/query/similar-clusters.d.ts +8 -0
- package/query/similarity-signals.d.ts +47 -0
- package/query/spec-staleness.d.ts +21 -0
- package/query/types.d.ts +249 -34
- package/query/verdict-core.d.ts +21 -0
- package/query/verdict.d.ts +22 -0
- package/query/views/catalog.d.ts +47 -0
- package/query/views/registry.d.ts +27 -7
- package/query/views/report.d.ts +49 -0
- package/query/views/semantic.d.ts +28 -5
- package/query/views/stats.d.ts +38 -2
- package/readiness.d.ts +29 -0
- package/retry-policy.d.ts +44 -0
- package/serve.d.ts +1 -1
- package/server.d.ts +71 -3
- package/service-config.d.ts +109 -0
- package/service-errors.d.ts +51 -0
- package/skill/SKILL.md +688 -81
- package/store/catalog-invariant-guard.d.ts +13 -7
- package/vocabulary.d.ts +48 -0
- package/write/anchor-check.d.ts +139 -0
- package/write/attestation.d.ts +64 -0
- package/write/catalog-merge.d.ts +15 -8
- package/write/catalog.d.ts +72 -2
- package/write/citation-path.d.ts +31 -0
- package/write/citation.d.ts +157 -0
- package/write/create-issue.d.ts +143 -29
- package/write/errors.d.ts +196 -1
- package/write/gate.d.ts +67 -0
- package/write/merge-project.d.ts +54 -0
- package/write/obligation.d.ts +133 -0
- package/write/relate.d.ts +1 -1
- package/write/revision.d.ts +27 -0
- package/write/similarity-scan.d.ts +84 -0
- package/write/spec-revision.d.ts +131 -0
- package/write/spec-revision.reconcile.d.ts +48 -0
- package/write/transition.d.ts +13 -2
- package/write/tx.d.ts +44 -2
- package/write/uid-prefix.d.ts +78 -0
- package/write/update.d.ts +23 -1
package/write/create-issue.d.ts
CHANGED
|
@@ -1,21 +1,16 @@
|
|
|
1
|
+
import { ISimilarCandidate, SimilarityScanDegradedReason } from './similarity-scan.js';
|
|
1
2
|
import { IWriteStoreHandle } from './tx.js';
|
|
3
|
+
import { ICitation } from '../citation.js';
|
|
2
4
|
import { StoreSearchBackend } from '@adhd/sox-hybrid-search';
|
|
3
5
|
import { GraphBackend } from '@adhd/sox-graph-store';
|
|
4
6
|
|
|
5
7
|
/**
|
|
6
|
-
* A filing-time citation
|
|
7
|
-
*
|
|
8
|
-
* `ICitationInput`
|
|
9
|
-
*
|
|
8
|
+
* A filing-time citation. `ICitation` — the ONE contract, defined in
|
|
9
|
+
* `../citation.ts` — is imported here and re-exported below so an existing
|
|
10
|
+
* importer of `ICitationInput` from this module keeps compiling; the shape and
|
|
11
|
+
* the persisted codec live in exactly one place (`../citation.ts`), never here.
|
|
10
12
|
*/
|
|
11
|
-
export
|
|
12
|
-
file: string;
|
|
13
|
-
lines?: string;
|
|
14
|
-
context?: string;
|
|
15
|
-
symbol?: string;
|
|
16
|
-
/** Best-effort enrichment payload — not re-specified here; carried through verbatim into the citation node's metadata. */
|
|
17
|
-
blastRadius?: unknown;
|
|
18
|
-
}
|
|
13
|
+
export type { ICitation, ICitationInput } from '../citation.js';
|
|
19
14
|
export interface ICreateIssueInput {
|
|
20
15
|
title: string;
|
|
21
16
|
body: string;
|
|
@@ -29,7 +24,7 @@ export interface ICreateIssueInput {
|
|
|
29
24
|
status?: string;
|
|
30
25
|
/** 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
26
|
priority?: string;
|
|
32
|
-
citations?:
|
|
27
|
+
citations?: ICitation[];
|
|
33
28
|
/** catalog agent name/uid; defaults to `by`. An unresolved NAME mints; a uid-shaped ref that does not resolve throws (§6.1). */
|
|
34
29
|
author?: string;
|
|
35
30
|
/** Plain metadata scalar (§6.2) — no edge. */
|
|
@@ -48,6 +43,27 @@ export interface ICreateIssueInput {
|
|
|
48
43
|
* every read/render path is byte-for-byte unchanged.
|
|
49
44
|
*/
|
|
50
45
|
gitContext?: string;
|
|
46
|
+
/**
|
|
47
|
+
* A **dedupe-scoping hint only**: the uid of the item this one is being
|
|
48
|
+
* filed under. It names a parent so the pre-write similarity scan (§6.4
|
|
49
|
+
* point 1) EXCLUDES that item AND its `part_of` ancestor chain from the
|
|
50
|
+
* duplicate candidate set, so a child that deliberately restates its
|
|
51
|
+
* parent's intent is not suppressed as a duplicate OF that parent (defect
|
|
52
|
+
* c5460239: a child of C7 reproduced `created:false`/
|
|
53
|
+
* `duplicate-suppressed` with its own parent as the top candidate at
|
|
54
|
+
* 0.9811). A child restating its GRANDPARENT is likewise not suppressed,
|
|
55
|
+
* because the ancestry walk spans the whole `part_of` chain.
|
|
56
|
+
*
|
|
57
|
+
* It writes NO `part_of` edge — `relate` is the sole `part_of` writer
|
|
58
|
+
* (§6.3.6). Because it only scopes the read-side scan, it is invisible on
|
|
59
|
+
* the emitted card and nothing new is persisted; it has no effect once a
|
|
60
|
+
* create is not a duplicate. An unresolvable uid throws
|
|
61
|
+
* `IssueNotFoundError` (fail loud, ADR-0002 D5), never silently ignored.
|
|
62
|
+
*
|
|
63
|
+
* A uid ONLY, never a name (matching `relate`'s `sourceUid`/`targetUid` uid
|
|
64
|
+
* convention, §1/§6.1).
|
|
65
|
+
*/
|
|
66
|
+
dedupeExcludeUid?: string;
|
|
51
67
|
/** The acting identity — agent or person (§6.3's opening rule) — REQUIRED on every mutating verb. A missing/blank value throws `InvalidArgumentError('by', ...)` before any write runs. */
|
|
52
68
|
by: string;
|
|
53
69
|
/**
|
|
@@ -58,7 +74,16 @@ export interface ICreateIssueInput {
|
|
|
58
74
|
* value (§6.4 point 3, first sentence).
|
|
59
75
|
*
|
|
60
76
|
* - `'abort'` — nothing is written; `{created:false,
|
|
61
|
-
* reason:'duplicate-suppressed', duplicateCandidates}`.
|
|
77
|
+
* reason:'duplicate-suppressed', duplicateCandidates}`. If the scan was
|
|
78
|
+
* degraded in the ONE way that can NEVER resolve on its own —
|
|
79
|
+
* `no-embed-query`, the backend wired without `embedQuery` so the
|
|
80
|
+
* calibrated channel can never run — it also fails closed with
|
|
81
|
+
* `{created:false, reason:'duplicate-scan-degraded',
|
|
82
|
+
* duplicateScanDegraded:true, duplicateScanDegradedReason:'no-embed-query'}`
|
|
83
|
+
* (BUG 4e8fce2a). A `no-vector-scores` degrade (indistinguishable at read
|
|
84
|
+
* time from benign on-write embedding lag) or a `no-search-backend`
|
|
85
|
+
* degrade (dedupe never mounted) instead PROCEEDS and carries the
|
|
86
|
+
* `duplicateScanDegraded` signal.
|
|
62
87
|
* - `'force'` — the write proceeds to a genuinely new, distinct `uid`
|
|
63
88
|
* despite the match; `duplicateCandidates` is still reported.
|
|
64
89
|
* - `'comment'` — no new issue node is written; a `note` node is attached
|
|
@@ -94,6 +119,54 @@ export interface IDuplicateCandidate {
|
|
|
94
119
|
/** Cosine similarity in `[0,1]`, straight off the vector channel — directly comparable to `project_policy.dedupe_threshold`. */
|
|
95
120
|
score: number;
|
|
96
121
|
}
|
|
122
|
+
/**
|
|
123
|
+
* Why {@link scanForDuplicates} could not produce a calibrated comparison —
|
|
124
|
+
* i.e. why the gate did NOT actually scan for duplicates despite being asked
|
|
125
|
+
* to. See {@link IDuplicateScanOutcome.degraded}.
|
|
126
|
+
*
|
|
127
|
+
* C9: this is now an ALIAS of the shared scan's own reason union
|
|
128
|
+
* (`write/similarity-scan.ts`), so the create gate and the `view:'similar'`
|
|
129
|
+
* cluster block can never drift on what "degraded" means. The name and every
|
|
130
|
+
* member are unchanged, so existing importers/tests are unaffected.
|
|
131
|
+
*/
|
|
132
|
+
export type DuplicateScanDegradedReason = SimilarityScanDegradedReason;
|
|
133
|
+
/**
|
|
134
|
+
* The result of {@link scanForDuplicates}. Its `candidates` arm is the
|
|
135
|
+
* pre-existing return value; `degraded` is the missing signal this type now
|
|
136
|
+
* carries (BUG 4e8fce2a).
|
|
137
|
+
*
|
|
138
|
+
* **`degraded` is the distinction the gate previously could not make.**
|
|
139
|
+
* Before this type existed, `scanForDuplicates` returned only
|
|
140
|
+
* `IDuplicateCandidate[]`, so an empty array meant EITHER "the scan ran and
|
|
141
|
+
* genuinely found nothing" OR "the scan could not run at all" — two states
|
|
142
|
+
* with opposite safety meanings that the caller had no way to tell apart. The
|
|
143
|
+
* abort branch (`createIssue`) then treated both as "no duplicates" and wrote
|
|
144
|
+
* the item, so `duplicateAction:'abort'` silently FAILED OPEN whenever the
|
|
145
|
+
* semantic substrate was degraded (no search backend, no `embedQuery`, or an
|
|
146
|
+
* empty/mismatched vector space). See {@link scanForDuplicates}'s own doc
|
|
147
|
+
* comment for exactly which conditions set this flag.
|
|
148
|
+
*/
|
|
149
|
+
export interface IDuplicateScanOutcome {
|
|
150
|
+
candidates: IDuplicateCandidate[];
|
|
151
|
+
/**
|
|
152
|
+
* C9 — advisory CROSS-project candidates surfaced alongside the
|
|
153
|
+
* same-project `candidates`. Never suppress a create, never fire
|
|
154
|
+
* `comment`, and the scan writes no edge for them (AC3). Empty when the
|
|
155
|
+
* scope is `same-project` (the default).
|
|
156
|
+
*/
|
|
157
|
+
similarCandidates?: ISimilarCandidate[];
|
|
158
|
+
/**
|
|
159
|
+
* `true` iff the scan could NOT perform a calibrated duplicate comparison
|
|
160
|
+
* for the in-scope issues — no vector channel ran, so `candidates` is empty
|
|
161
|
+
* because the gate is blind, NOT because the store is clean. Always `false`
|
|
162
|
+
* when the project genuinely has zero issues to compare against, or when
|
|
163
|
+
* `dedupeScanEnabled` is off: those are complete (if empty) scans, not
|
|
164
|
+
* degraded ones.
|
|
165
|
+
*/
|
|
166
|
+
degraded: boolean;
|
|
167
|
+
/** Set iff `degraded` — the concrete reason, never a generic "unavailable". */
|
|
168
|
+
degradedReason?: DuplicateScanDegradedReason;
|
|
169
|
+
}
|
|
97
170
|
/**
|
|
98
171
|
* The search substrate `createIssue`'s duplicate gate needs (§6.4 point 1),
|
|
99
172
|
* threaded alongside {@link IWriteStoreHandle} rather than folded into it:
|
|
@@ -109,19 +182,22 @@ export interface IDuplicateCandidate {
|
|
|
109
182
|
*
|
|
110
183
|
* `search.embedQuery` is declared OPTIONAL here (unlike
|
|
111
184
|
* `IQueryStoreHandle.search.embedQuery`, which is mandatory) specifically to
|
|
112
|
-
* express §6.4 point 4's degraded case: a `StoreSearchBackend`
|
|
113
|
-
* (FTS/text
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
* `
|
|
117
|
-
*
|
|
185
|
+
* express §6.4 point 4's `no-embed-query` degraded case: a `StoreSearchBackend`
|
|
186
|
+
* can be wired (FTS/text runs off the graph store directly) while no embedding
|
|
187
|
+
* model/vector space is configured, so the CALIBRATED (vector) channel can
|
|
188
|
+
* never run. `scanForDuplicates` reports that as `degradedReason:
|
|
189
|
+
* 'no-embed-query'`, and `createIssue`'s `abort` FAILS CLOSED on it — a
|
|
190
|
+
* persistent configuration absence the operator must fix, never a transient.
|
|
191
|
+
* (It deliberately does NOT fall back to a text-only scan: BM25 is
|
|
192
|
+
* uncalibrated and would reintroduce the false-positive class — see
|
|
193
|
+
* {@link scanForDuplicates}.) `search` itself stays OPTIONAL (no backend
|
|
118
194
|
* mounted at all) for the same "never silently go dark" posture §6.4 point 4
|
|
119
|
-
* states, but applied one layer further out: `scanForDuplicates`
|
|
120
|
-
* wholly-absent backend as
|
|
121
|
-
*
|
|
122
|
-
* fail because the product-feature-only dedupe UX (§6.4's own
|
|
123
|
-
* missed warning, not a correctness defect") happens to be
|
|
124
|
-
* environment.
|
|
195
|
+
* states, but applied one layer further out: `scanForDuplicates` reports a
|
|
196
|
+
* wholly-absent backend as `no-search-backend` (zero candidates, `create`
|
|
197
|
+
* PROCEEDS and carries the signal) rather than throwing — filing an issue must
|
|
198
|
+
* never hard-fail because the product-feature-only dedupe UX (§6.4's own
|
|
199
|
+
* framing: "a missed warning, not a correctness defect") happens to be
|
|
200
|
+
* unwired in a given environment.
|
|
125
201
|
*/
|
|
126
202
|
export interface IDuplicateScanHandle {
|
|
127
203
|
readonly graph?: GraphBackend;
|
|
@@ -202,7 +278,19 @@ export interface ICreateIssueCard {
|
|
|
202
278
|
* `duplicateAction` — this is NOT an empty array in that case (§6.4 point
|
|
203
279
|
* 3, first sentence).
|
|
204
280
|
* - `reason` — present iff `!created` and the gate suppressed the write
|
|
205
|
-
* (`duplicateAction:'abort'`, the default)
|
|
281
|
+
* (`duplicateAction:'abort'`, the default) OR refused it because the scan
|
|
282
|
+
* was degraded in the never-resolvable `no-embed-query` way
|
|
283
|
+
* (`'duplicate-scan-degraded'`, BUG 4e8fce2a — see
|
|
284
|
+
* {@link IDuplicateScanOutcome}).
|
|
285
|
+
* - `duplicateScanDegraded`/`duplicateScanDegradedReason` — present iff the
|
|
286
|
+
* pre-write scan could not run a calibrated comparison
|
|
287
|
+
* ({@link scanForDuplicates}'s `degraded`), on EVERY outcome: on the
|
|
288
|
+
* `'abort'` refusal for the never-resolvable `no-embed-query` degrade
|
|
289
|
+
* (`created:false`, `reason:'duplicate-scan-degraded'`), and on
|
|
290
|
+
* `'force'`/`'comment'`/the `no-vector-scores` and `no-search-backend`
|
|
291
|
+
* `'abort'` paths where the write proceeded anyway.
|
|
292
|
+
* This is the field that makes a degraded scan non-silent: absent on a
|
|
293
|
+
* healthy scan (the common case), so byte-for-byte unchanged there.
|
|
206
294
|
* - `commentedOn` — present iff `duplicateAction:'comment'` fired.
|
|
207
295
|
* - `supersededUid` — always absent from `createIssue` alone; only the
|
|
208
296
|
* `supersedes` composition (§6.3.2, not yet built here) would set it.
|
|
@@ -212,12 +300,32 @@ export interface ICreateIssueResult {
|
|
|
212
300
|
uid?: string;
|
|
213
301
|
item?: ICreateIssueCard;
|
|
214
302
|
duplicateCandidates?: IDuplicateCandidate[];
|
|
215
|
-
|
|
303
|
+
/**
|
|
304
|
+
* C9 — advisory cross-project similarity candidates. Present iff the
|
|
305
|
+
* configured `similarityScope` is wider than `same-project` and the scan
|
|
306
|
+
* surfaced at least one. Purely informational: it never changes whether the
|
|
307
|
+
* write proceeded, and the scan writes no `similar_to` edge (AC2/AC3).
|
|
308
|
+
*/
|
|
309
|
+
similarCandidates?: ISimilarCandidate[];
|
|
310
|
+
reason?: 'duplicate-suppressed' | 'duplicate-scan-degraded';
|
|
311
|
+
/** Present iff the pre-write scan was degraded — see this interface's own doc comment. Paired with {@link duplicateScanDegradedReason}. */
|
|
312
|
+
duplicateScanDegraded?: boolean;
|
|
313
|
+
/** Why the scan was degraded. Present iff {@link duplicateScanDegraded}. */
|
|
314
|
+
duplicateScanDegradedReason?: DuplicateScanDegradedReason;
|
|
216
315
|
supersededUid?: string;
|
|
217
316
|
commentedOn?: {
|
|
218
317
|
uid: string;
|
|
219
318
|
noteId: string;
|
|
220
319
|
};
|
|
320
|
+
/**
|
|
321
|
+
* C1 AC8 — how the issue's component was resolved. `'explicit'` when the
|
|
322
|
+
* caller supplied `component` and it resolved; `'default-root'` when the
|
|
323
|
+
* caller supplied none and the project's reserved `(root)` default was used.
|
|
324
|
+
* Makes "silently defaulted" distinguishable from "resolved": a supplied
|
|
325
|
+
* component that does NOT resolve still throws `CatalogNotFoundError` and
|
|
326
|
+
* never reaches this field.
|
|
327
|
+
*/
|
|
328
|
+
placementResolved?: 'explicit' | 'default-root';
|
|
221
329
|
}
|
|
222
330
|
/**
|
|
223
331
|
* Maximum length of the item-level `gitContext` disclosure scalar, enforced
|
|
@@ -246,7 +354,13 @@ export declare function assertGitContextWithinCap(value: string | undefined): vo
|
|
|
246
354
|
* outside the project root AND every `citationAllowedExternalRoots` entry —
|
|
247
355
|
* the carve-out, BUG c6d35272 — and the error names those roots),
|
|
248
356
|
* `InvalidArgumentError('duplicateAction', ...)`
|
|
249
|
-
* (an unrecognized value — §6.4), `
|
|
357
|
+
* (an unrecognized value — §6.4), the `dedupeExcludeUid` resolution errors
|
|
358
|
+
* (`IssueNotFoundError` / `AmbiguousReferenceError` / `InvalidArgumentError` /
|
|
359
|
+
* `StaleSupersedeError` — via `resolveIssueByUid`, see
|
|
360
|
+
* {@link resolveDedupeExcludeIds}) when a `dedupeExcludeUid` was supplied but
|
|
361
|
+
* does not resolve to a live, non-superseded `issue` — fail loud, ADR-0002 D5;
|
|
362
|
+
* c5460239),
|
|
363
|
+
* `WriteContentionError`/
|
|
250
364
|
* `WriteIOError` (§4c — an exhausted driver-level retry on the underlying
|
|
251
365
|
* `immediate` transaction).
|
|
252
366
|
*/
|
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
|
|
@@ -355,6 +391,81 @@ export declare class BacklogValidationError extends BacklogWriteError {
|
|
|
355
391
|
readonly retryable = false;
|
|
356
392
|
constructor(field: string, detail?: string);
|
|
357
393
|
}
|
|
394
|
+
/**
|
|
395
|
+
* A supplied anchor locator is outside the closed grammar (`path:<file>[:<line>]`
|
|
396
|
+
* | `url:<url>` | `query:<cql>` | `registry:<ref>`), or is a bare `path:line`
|
|
397
|
+
* with no `digest`. A caller-input mistake, so `E_VALIDATION`, never retryable —
|
|
398
|
+
* nothing is written and retrying the identical locator fails identically.
|
|
399
|
+
*/
|
|
400
|
+
export declare class AnchorLocatorInvalidError extends BacklogWriteError {
|
|
401
|
+
readonly locator: string;
|
|
402
|
+
readonly code: "E_VALIDATION";
|
|
403
|
+
readonly retryable = false;
|
|
404
|
+
constructor(locator: string, detail?: string);
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* `recheck` named an `attestation` uid that is not a live `attestation` node.
|
|
408
|
+
* Distinct from {@link IssueNotFoundError} (an `issue` uid) and
|
|
409
|
+
* {@link CatalogNotFoundError} so a caller can branch on the missing record
|
|
410
|
+
* kind; `E_VALIDATION`, never retryable.
|
|
411
|
+
*/
|
|
412
|
+
export declare class AttestationNotFoundError extends BacklogWriteError {
|
|
413
|
+
readonly uid: string;
|
|
414
|
+
readonly code: "E_VALIDATION";
|
|
415
|
+
readonly retryable = false;
|
|
416
|
+
constructor(uid: string);
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* `obligate` (C4, DESIGN §2 Primitive 3) was handed a `requirement` outside the
|
|
420
|
+
* CLOSED predicate core — an unknown `op`, a missing/ill-typed leaf
|
|
421
|
+
* (`evidence.kind`, `relation.type`/`direction`, a non-array `all_of`/`any_of`
|
|
422
|
+
* `of`, a `not` whose `of` is not a predicate), an unexpected extra key, or an
|
|
423
|
+
* `evidence.min < 1`. Raised by `assertValidPredicate` BEFORE any transaction
|
|
424
|
+
* opens, so an invalid predicate never holds a write lock.
|
|
425
|
+
*
|
|
426
|
+
* `E_VALIDATION`, never retryable — nothing is written, and retrying the
|
|
427
|
+
* identical predicate fails identically. Distinct from `InvalidArgumentError`
|
|
428
|
+
* so a caller can tell "the argument envelope was malformed" from "the
|
|
429
|
+
* predicate grammar was violated".
|
|
430
|
+
*/
|
|
431
|
+
export declare class InvalidPredicateError extends BacklogWriteError {
|
|
432
|
+
readonly detail: string;
|
|
433
|
+
readonly code: "E_VALIDATION";
|
|
434
|
+
readonly retryable = false;
|
|
435
|
+
constructor(detail: string);
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* `unobligate` named a uid that is not a live `obligation` node. Distinct from
|
|
439
|
+
* {@link IssueNotFoundError} (an `issue` uid) and {@link CatalogNotFoundError}
|
|
440
|
+
* so a caller can branch on the missing record kind; `E_VALIDATION`, never
|
|
441
|
+
* retryable.
|
|
442
|
+
*/
|
|
443
|
+
export declare class ObligationNotFoundError extends BacklogWriteError {
|
|
444
|
+
readonly uid: string;
|
|
445
|
+
readonly code: "E_VALIDATION";
|
|
446
|
+
readonly retryable = false;
|
|
447
|
+
constructor(uid: string);
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* `appendSpecRevision`'s `base_revision` no longer matches the ticket's current
|
|
451
|
+
* spec pointer — a concurrent spec edit won the CAS. `E_VALIDATION`,
|
|
452
|
+
* `retryable: false`, and mapped EXPLICITLY to the envelope code
|
|
453
|
+
* `precondition_failed` in `api.ts`'s `ERROR_CLASS_TO_ENVELOPE_CODE` (C10 AC6):
|
|
454
|
+
* the `E_VALIDATION` fallthrough would otherwise return `validation`.
|
|
455
|
+
*
|
|
456
|
+
* A stale `base_revision` is NOT resource contention — no other actor holds the
|
|
457
|
+
* node; the *caller's own* read is out of date and must be re-read before
|
|
458
|
+
* retrying — so it belongs with the existing refusing-write preconditions
|
|
459
|
+
* (`IssueTerminalError`, `CitationUnverifiableError`), not with `conflict`
|
|
460
|
+
* (reserved for a resource another actor holds). No write runs.
|
|
461
|
+
*/
|
|
462
|
+
export declare class SpecRevisionConflictError extends BacklogWriteError {
|
|
463
|
+
readonly expected: string;
|
|
464
|
+
readonly actual: string;
|
|
465
|
+
readonly code: "E_VALIDATION";
|
|
466
|
+
readonly retryable = false;
|
|
467
|
+
constructor(expected: string, actual: string);
|
|
468
|
+
}
|
|
358
469
|
/**
|
|
359
470
|
* Classify a caught, RAW driver-level error (never one of our own
|
|
360
471
|
* {@link BacklogWriteError} subclasses — those are already decided and must
|
|
@@ -399,3 +510,87 @@ export declare function classifyDriverError(err: unknown): IWriteError;
|
|
|
399
510
|
* immediately after `assertNonBlank('by', input.by)` at every mutating verb.
|
|
400
511
|
*/
|
|
401
512
|
export declare function assertNotBareRoleLiteral(field: string, value: string): void;
|
|
513
|
+
/**
|
|
514
|
+
* (C5) A terminal transition was refused because a `block`-severity obligation
|
|
515
|
+
* is unsatisfied (DESIGN §2 Primitive 3). `E_VALIDATION` → the envelope maps
|
|
516
|
+
* it to `precondition_failed`, with the structured `IGateRefusal` in
|
|
517
|
+
* `error.details.refusal` (adhd ADR-0004 — object-shaped, never a `{result}`
|
|
518
|
+
* envelope). Thrown from INSIDE `transition`'s open `BEGIN IMMEDIATE`
|
|
519
|
+
* transaction and BEFORE any write, so the throw rolls the transaction back and
|
|
520
|
+
* a re-read shows the status unchanged.
|
|
521
|
+
*
|
|
522
|
+
* `E_VALIDATION`, never retryable — retrying the identical payload against
|
|
523
|
+
* unchanged obligations fails identically (the caller must satisfy or override
|
|
524
|
+
* the obligation first).
|
|
525
|
+
*
|
|
526
|
+
* Deliberately declared ATTACHED to `errors.ts`'s end (after every existing
|
|
527
|
+
* export) and importing `IGateRefusal` via an inline `import(...)` type, so
|
|
528
|
+
* this additive change shifts NO existing line and leaves `CONTRACT.md`'s
|
|
529
|
+
* `(errors.ts:NNN)` anchors (enforced by `contract-anchors.spec.ts`) true.
|
|
530
|
+
*/
|
|
531
|
+
export declare class ObligationUnsatisfiedError extends BacklogWriteError {
|
|
532
|
+
readonly refusal: import('./gate.js').IGateRefusal;
|
|
533
|
+
readonly code: "E_VALIDATION";
|
|
534
|
+
readonly retryable = false;
|
|
535
|
+
constructor(refusal: import('./gate.js').IGateRefusal);
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* (C5) A claimed override of a refusing obligation is not permitted: the caller
|
|
539
|
+
* is not in the obligation's `override.actors`, or the override carries a blank
|
|
540
|
+
* reason. An override ALWAYS requires a recorded reason (DESIGN §2 Primitive 3
|
|
541
|
+
* — "not a configurable boolean"), so a blank one is refused, never treated as
|
|
542
|
+
* a silent pass.
|
|
543
|
+
*
|
|
544
|
+
* `E_VALIDATION` → `precondition_failed`, never retryable.
|
|
545
|
+
*/
|
|
546
|
+
export declare class OverrideNotPermittedError extends BacklogWriteError {
|
|
547
|
+
readonly obligationUid: string;
|
|
548
|
+
readonly actor: string;
|
|
549
|
+
readonly code: "E_VALIDATION";
|
|
550
|
+
readonly retryable = false;
|
|
551
|
+
constructor(obligationUid: string, actor: string);
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* (C6) A verb's entry precondition was refused by a live block-severity verdict
|
|
555
|
+
* condition — today only `claim` (DESIGN §2 Primitive 3/4: taking blocked work
|
|
556
|
+
* is the observed failure). `E_VALIDATION` → the envelope maps it to
|
|
557
|
+
* `precondition_failed`, with the structured `ICondition` in
|
|
558
|
+
* `error.details.refusal` (adhd ADR-0004 — object-shaped, never a `{result}`
|
|
559
|
+
* envelope). Thrown from INSIDE `claim`'s open `BEGIN IMMEDIATE` transaction
|
|
560
|
+
* and BEFORE any write, so the throw rolls the transaction back and the item
|
|
561
|
+
* is left unclaimed.
|
|
562
|
+
*
|
|
563
|
+
* `E_VALIDATION`, never retryable — retrying the identical payload against an
|
|
564
|
+
* unchanged blocker fails identically (the caller must clear the blocker, or
|
|
565
|
+
* `force` and record it first).
|
|
566
|
+
*
|
|
567
|
+
* Declared ATTACHED to `errors.ts`'s end (after every existing export) and
|
|
568
|
+
* importing `ICondition` via an inline `import(...)` type, so this additive
|
|
569
|
+
* change shifts NO existing line and leaves `CONTRACT.md`'s `(errors.ts:NNN)`
|
|
570
|
+
* anchors (enforced by `contract-anchors.spec.ts`) true.
|
|
571
|
+
*/
|
|
572
|
+
export declare class PreconditionRefusedError extends BacklogWriteError {
|
|
573
|
+
readonly refusal: import('../query/types.js').ICondition;
|
|
574
|
+
readonly code: "E_VALIDATION";
|
|
575
|
+
readonly retryable = false;
|
|
576
|
+
constructor(refusal: import('../query/types.js').ICondition);
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* `removeCitation` named a uid that is not a LIVE `citation` node — absent,
|
|
580
|
+
* already removed, or a uid naming a node of a DIFFERENT kind (the "foreign
|
|
581
|
+
* citation" refusal). Distinct from `IssueNotFoundError` (an `issue` uid) and
|
|
582
|
+
* `CatalogNotFoundError` (a registry/`catalog` ref) so a caller can branch on
|
|
583
|
+
* the missing record kind; `E_VALIDATION`, never retryable.
|
|
584
|
+
*
|
|
585
|
+
* Declared ATTACHED to `errors.ts`'s end (after every existing export, like
|
|
586
|
+
* `ObligationUnsatisfiedError`/`OverrideNotPermittedError`/
|
|
587
|
+
* `PreconditionRefusedError` before it), so this additive change shifts NO
|
|
588
|
+
* existing line and leaves `CONTRACT.md`'s `(errors.ts:NNN)` anchors
|
|
589
|
+
* (enforced by `contract-anchors.spec.ts`) true.
|
|
590
|
+
*/
|
|
591
|
+
export declare class CitationNotFoundError extends BacklogWriteError {
|
|
592
|
+
readonly uid: string;
|
|
593
|
+
readonly code: "E_VALIDATION";
|
|
594
|
+
readonly retryable = false;
|
|
595
|
+
constructor(uid: string);
|
|
596
|
+
}
|
package/write/gate.d.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { ITxNodeRow } from './tx.js';
|
|
2
|
+
import { IVerdict } from '../query/types.js';
|
|
3
|
+
import { AdapterTransaction } from '@adhd/sox-store-adapter';
|
|
4
|
+
|
|
5
|
+
/** The closed refusal-reason vocabulary (DESIGN §2 Primitive 4's governed core, scoped to the write gate). */
|
|
6
|
+
export type IGateReasonCode = 'EvidenceUnverified' | 'EvidenceStale' | 'BlockedBy' | 'MissingObligation' | 'ClaimStale' | 'ReferenceUnresolved' | 'Unknown';
|
|
7
|
+
/**
|
|
8
|
+
* The object-shaped refusal payload (adhd ADR-0004 — carried in
|
|
9
|
+
* `error.details.refusal`, never as a `{result}` envelope).
|
|
10
|
+
*/
|
|
11
|
+
export interface IGateRefusal {
|
|
12
|
+
code: IGateReasonCode;
|
|
13
|
+
message: string;
|
|
14
|
+
/** The obligation that refused, when the refusal is obligation-scoped. */
|
|
15
|
+
obligationUid?: string;
|
|
16
|
+
/** The thing to fix — blocker uid / attestation uid. */
|
|
17
|
+
subject?: string;
|
|
18
|
+
/** For EvidenceUnverified: the `claim.kind` the obligation required. */
|
|
19
|
+
required_kind?: string;
|
|
20
|
+
/** The mechanical check that produced the refusal (e.g. 'default_branch_ancestor'). */
|
|
21
|
+
performed_check?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface IGateEvaluation {
|
|
24
|
+
satisfied: boolean;
|
|
25
|
+
refusals: IGateRefusal[];
|
|
26
|
+
/** Pairs recorded as `satisfies` edges on success. */
|
|
27
|
+
satisfiedBy: Array<{
|
|
28
|
+
obligationUid: string;
|
|
29
|
+
attestationUid: string;
|
|
30
|
+
}>;
|
|
31
|
+
/** Obligations whose on_fail is 'warn' and predicate false — recorded, never refusing. */
|
|
32
|
+
warnings: IGateRefusal[];
|
|
33
|
+
}
|
|
34
|
+
/** The gated transition's inputs, threaded from `transition.ts`'s open `tx`. */
|
|
35
|
+
export interface IGateInput {
|
|
36
|
+
issueRowid: number;
|
|
37
|
+
fromStatus: string;
|
|
38
|
+
toStatus: string;
|
|
39
|
+
effectiveActor: string;
|
|
40
|
+
at: string;
|
|
41
|
+
/** A caller-supplied override attempt; honoured only for a listed actor + a non-blank reason. */
|
|
42
|
+
override?: {
|
|
43
|
+
reason: string;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Evaluate the transition-scoped obligations of `issueRowid` for `from → to`
|
|
48
|
+
* INSIDE the caller's open `tx`. Never writes. `effectiveActor` enables the
|
|
49
|
+
* listed-actor override.
|
|
50
|
+
*
|
|
51
|
+
* Returns a typed {@link IGateEvaluation}; the caller decides whether to throw
|
|
52
|
+
* ({@link ObligationUnsatisfiedError}) and writes the `satisfies` edges on
|
|
53
|
+
* success. Throws {@link OverrideNotPermittedError} when an override is
|
|
54
|
+
* claimed by an actor not listed on the refusing obligation, or carries a
|
|
55
|
+
* blank reason.
|
|
56
|
+
*/
|
|
57
|
+
export declare function evaluateTransitionGateTx(tx: AdapterTransaction, input: IGateInput): Promise<IGateEvaluation>;
|
|
58
|
+
/**
|
|
59
|
+
* Derive the verdict INSIDE the caller's open tx, through rungs 1–2 only
|
|
60
|
+
* (claim is a precondition, never a full anchor re-resolve). Never writes;
|
|
61
|
+
* reuses C4's {@link evaluatePredicate} with the tx-scoped {@link
|
|
62
|
+
* makeTxResolver} and C6's own `orderConditions`/`computeActionable`.
|
|
63
|
+
*/
|
|
64
|
+
export declare function evaluateVerdictTx(tx: AdapterTransaction, issueRow: ITxNodeRow, input: {
|
|
65
|
+
at: string;
|
|
66
|
+
claimStaleAfterMin: number;
|
|
67
|
+
}): Promise<IVerdict>;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { IWriteStoreHandle } from './tx.js';
|
|
2
|
+
|
|
3
|
+
export interface IMergeProjectInput {
|
|
4
|
+
/** The duplicate / retiring row — an exact uid. */
|
|
5
|
+
fromUid: string;
|
|
6
|
+
/** The canonical survivor — a uid or a name (resolved live). */
|
|
7
|
+
toUid: string;
|
|
8
|
+
/** identity; asserted non-blank + not a bare role literal */
|
|
9
|
+
by: string;
|
|
10
|
+
}
|
|
11
|
+
export interface IMergeProjectOutcome {
|
|
12
|
+
survivorUid: string;
|
|
13
|
+
retiredUid: string;
|
|
14
|
+
/** How many issues' `owns_component` chains were re-pointed onto the survivor. */
|
|
15
|
+
movedIssues: number;
|
|
16
|
+
/** Always true — the retired uid is never reusable. */
|
|
17
|
+
retired: true;
|
|
18
|
+
}
|
|
19
|
+
export interface IRmProjectInput {
|
|
20
|
+
uid: string;
|
|
21
|
+
reason: string;
|
|
22
|
+
by: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Merge duplicate project `fromUid` into canonical `toUid` in ONE
|
|
26
|
+
* `BEGIN IMMEDIATE` transaction: re-point every component owned by `fromUid`
|
|
27
|
+
* (its `owns_project` edges AND each component's `meta.projectUid`) onto
|
|
28
|
+
* `toUid`, set `fromUid.meta.redirectTo = toUid` + `fromUid.meta.retiredAt`,
|
|
29
|
+
* stamp `t_invalid`, and write an audit row.
|
|
30
|
+
*
|
|
31
|
+
* Idempotent: a second call with the same `(from,to)` is a no-op success
|
|
32
|
+
* (`movedIssues: 0`) — the retired `from` row is still readable by uid, its
|
|
33
|
+
* `meta.redirectTo` already names the survivor.
|
|
34
|
+
*
|
|
35
|
+
* @throws CatalogNotFoundError `fromUid` is not a `project` row, or `toUid`
|
|
36
|
+
* does not resolve to a LIVE project.
|
|
37
|
+
* @throws InvalidArgumentError `toUid` equals `fromUid`, or `fromUid` is
|
|
38
|
+
* already retired pointing at a DIFFERENT survivor (never silently
|
|
39
|
+
* re-chain).
|
|
40
|
+
*/
|
|
41
|
+
export declare function mergeProject(handle: IWriteStoreHandle, input: IMergeProjectInput): Promise<IMergeProjectOutcome>;
|
|
42
|
+
/**
|
|
43
|
+
* Soft-retire a project WITHOUT a survivor: set `meta.retiredAt`, stamp
|
|
44
|
+
* `t_invalid`, and audit. No `redirectTo` is written, so its old name resolves
|
|
45
|
+
* to nothing (the row is retired, not redirected).
|
|
46
|
+
*
|
|
47
|
+
* Idempotent: a second call against an already-retired row is a success no-op.
|
|
48
|
+
*
|
|
49
|
+
* @throws CatalogNotFoundError `uid` is not a `project` row.
|
|
50
|
+
*/
|
|
51
|
+
export declare function rmProject(handle: IWriteStoreHandle, input: IRmProjectInput): Promise<{
|
|
52
|
+
uid: string;
|
|
53
|
+
retired: true;
|
|
54
|
+
}>;
|