@adhd/backlog 1.0.5 → 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 +53 -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 +35 -1
- package/index.d.ts +23 -3
- package/index.js +106 -58
- package/index.mjs +11627 -7068
- 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 +20 -13
- package/query/redirect.d.ts +30 -0
- package/query/resolve.d.ts +48 -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 +4 -4
- 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 +63 -0
- 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 +159 -0
- 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 +21 -1
- package/write/update.d.ts +23 -1
package/write/relate.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { IWriteStoreHandle } from './tx.js';
|
|
2
2
|
|
|
3
3
|
/** The closed `rel` union `relate` accepts (§3/§6.3.6) — issue → issue in every case. */
|
|
4
|
-
export type RelateRel = 'relates_to' | 'supersedes' | 'blocks' | 'duplicate_of' | 'part_of';
|
|
4
|
+
export type RelateRel = 'relates_to' | 'supersedes' | 'blocks' | 'duplicate_of' | 'part_of' | 'similar_to';
|
|
5
5
|
export interface IRelateInput {
|
|
6
6
|
/** The relation's SOURCE `issue` uid (§1: uid is the only identifier — never a name, never repo-scoped). */
|
|
7
7
|
sourceUid: string;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* revision.ts — the issue content-revision counter (DESIGN §2 Primitive 1/4).
|
|
3
|
+
*
|
|
4
|
+
* One revision counter, one name: `subject.revision` (Primitive 2) and
|
|
5
|
+
* `source_revision` (Primitive 4) are the SAME quantity — a monotonic counter
|
|
6
|
+
* on the issue's own `meta.revision`, bumped on every mutating write. A check
|
|
7
|
+
* or a verdict is stale relative to this one counter, so it must be read
|
|
8
|
+
* through one function rather than each consumer re-deriving it.
|
|
9
|
+
*
|
|
10
|
+
* This module is pure (no store, no I/O): it reads only the `meta` blob a
|
|
11
|
+
* caller already has in hand. `C6`'s verdict layer owns the bump sites
|
|
12
|
+
* (`create-issue` seeds `revision: 0`; `update`/`transition`/`claim`/`relate`/
|
|
13
|
+
* `move` set `nextRevision(priorMeta)`); `attest`/`recheck` deliberately do
|
|
14
|
+
* NOT bump, because they never touch the subject.
|
|
15
|
+
*
|
|
16
|
+
* A never-mutated issue (no `meta.revision` at all, e.g. every issue created
|
|
17
|
+
* before the counter existed) reads as `0`, so the pre-counter corpus is
|
|
18
|
+
* consistent without a backfill.
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* The monotonic revision of an issue's content — `0` for a never-mutated
|
|
22
|
+
* issue. A non-finite or non-numeric stored value (a malformed `meta` blob)
|
|
23
|
+
* degrades to `0` rather than propagating `NaN` into a comparison.
|
|
24
|
+
*/
|
|
25
|
+
export declare function readRevision(meta: Record<string, unknown> | undefined): number;
|
|
26
|
+
/** `readRevision(meta) + 1` — the revision a mutating write stamps onto its node. */
|
|
27
|
+
export declare function nextRevision(meta: Record<string, unknown> | undefined): number;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { StoreSearchBackend } from '@adhd/sox-hybrid-search';
|
|
2
|
+
import { GraphBackend } from '@adhd/sox-graph-store';
|
|
3
|
+
|
|
4
|
+
/** The search/graph substrate the scan needs — structurally compatible with the create gate's `IDuplicateScanHandle`. */
|
|
5
|
+
export interface ISimilarityScanHandle {
|
|
6
|
+
readonly graph?: GraphBackend;
|
|
7
|
+
readonly search?: {
|
|
8
|
+
readonly backend: StoreSearchBackend;
|
|
9
|
+
embedQuery?(text: string): Promise<Float32Array>;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
/** Why the scan could not produce a calibrated comparison — never a generic "unavailable". */
|
|
13
|
+
export type SimilarityScanDegradedReason = 'no-search-backend' | 'no-embed-query' | 'no-vector-scores';
|
|
14
|
+
/** One advisory similarity candidate, with the provenance a reviewer needs to judge it across projects. */
|
|
15
|
+
export interface ISimilarCandidate {
|
|
16
|
+
uid: string;
|
|
17
|
+
title: string;
|
|
18
|
+
/** Cosine similarity in `[0,1]`, straight off the vector channel. */
|
|
19
|
+
score: number;
|
|
20
|
+
scope: 'same-project' | 'cross-project';
|
|
21
|
+
provenance?: {
|
|
22
|
+
projectUid: string;
|
|
23
|
+
projectName: string;
|
|
24
|
+
componentUid?: string;
|
|
25
|
+
repoUrl?: string;
|
|
26
|
+
};
|
|
27
|
+
/** Which independent signals fired, e.g. `['cosine','title-tokens']`. */
|
|
28
|
+
signals?: string[];
|
|
29
|
+
}
|
|
30
|
+
export interface ISimilarityScanInput {
|
|
31
|
+
title: string;
|
|
32
|
+
body: string;
|
|
33
|
+
scope: 'same-project' | 'multi-project' | 'store-wide';
|
|
34
|
+
/** Required for `same-project`/`multi-project`: the filing project. */
|
|
35
|
+
projectUid?: string;
|
|
36
|
+
sameProjectThreshold: number;
|
|
37
|
+
crossProjectThreshold: number;
|
|
38
|
+
margin: number;
|
|
39
|
+
tokenOverlapMin: number;
|
|
40
|
+
limit?: number;
|
|
41
|
+
/**
|
|
42
|
+
* The filing item's own citation tokens / component path — the "A" side of
|
|
43
|
+
* the structural signal. Optional: absent means the structural signal can
|
|
44
|
+
* only ever fire on the candidate's side, which is the correct default for a
|
|
45
|
+
* caller with no new-item context (the view cluster scan).
|
|
46
|
+
*/
|
|
47
|
+
citationTokens?: readonly string[];
|
|
48
|
+
componentPath?: string;
|
|
49
|
+
/** Ids the scan must EXCLUDE (the declared `dedupeExcludeUid` parent + its `part_of` ancestor chain). */
|
|
50
|
+
excludeIds?: ReadonlySet<number>;
|
|
51
|
+
}
|
|
52
|
+
/** The richer outcome {@link scanSimilarCandidatesWithMeta} returns; {@link scanSimilarCandidates} is its candidates-only projection. */
|
|
53
|
+
export interface ISimilarityScanOutcome {
|
|
54
|
+
candidates: ISimilarCandidate[];
|
|
55
|
+
degraded: boolean;
|
|
56
|
+
degradedReason?: SimilarityScanDegradedReason;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Citation-derived structural tokens + owning component path for one issue.
|
|
60
|
+
*
|
|
61
|
+
* Exported so BOTH scan callers can supply the A-side of the AC7 structural
|
|
62
|
+
* signal from a real stored item: the `view:'similar'` cluster block derives
|
|
63
|
+
* its seed's context with this directly (the seed is already written), while
|
|
64
|
+
* the create-time gate derives the not-yet-written filing item's context from
|
|
65
|
+
* its input in `write/create-issue.ts`.
|
|
66
|
+
*/
|
|
67
|
+
export declare function structuralContextFor(graph: GraphBackend, issueId: number): Promise<{
|
|
68
|
+
citationTokens: string[];
|
|
69
|
+
componentPath?: string;
|
|
70
|
+
}>;
|
|
71
|
+
/**
|
|
72
|
+
* The scan, with its degraded-mode metadata. Returns `{candidates:[], degraded:false}`
|
|
73
|
+
* when the scope is a COMPLETE scan of nothing (the scope resolves to an empty
|
|
74
|
+
* candidate set, or every candidate was excluded); returns a degraded outcome
|
|
75
|
+
* when there ARE rows to compare against but the calibrated (vector) channel
|
|
76
|
+
* could not answer — the distinction the create gate's `abort` fails closed on.
|
|
77
|
+
*/
|
|
78
|
+
export declare function scanSimilarCandidatesWithMeta(handle: ISimilarityScanHandle, input: ISimilarityScanInput): Promise<ISimilarityScanOutcome>;
|
|
79
|
+
/**
|
|
80
|
+
* The candidates-only projection of {@link scanSimilarCandidatesWithMeta}
|
|
81
|
+
* (the C9 spec's stated signature): best-first, advisory, never writes, and
|
|
82
|
+
* `[]` when the embedding substrate is absent.
|
|
83
|
+
*/
|
|
84
|
+
export declare function scanSimilarCandidates(handle: ISimilarityScanHandle, input: ISimilarityScanInput): Promise<ISimilarCandidate[]>;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { IWriteStoreHandle } from './tx.js';
|
|
2
|
+
import { GraphBackend, NodeRecord } from '@adhd/sox-graph-store';
|
|
3
|
+
|
|
4
|
+
/** The long-form file export an anchor points at: `locator + digest` (Primitive 2) — referenced, never embedded. */
|
|
5
|
+
export interface ISpecAnchor {
|
|
6
|
+
locator: string;
|
|
7
|
+
digest: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Immutable revision object metadata (node `kind: 'SPEC'`). The node's
|
|
11
|
+
* `content` column holds the FRAGMENT appended at this revision (the delta; the
|
|
12
|
+
* base revision's fragment IS the base document). No revision stores a full
|
|
13
|
+
* snapshot — the document is the fold over the chain's `content` values and
|
|
14
|
+
* `revision_token` is the hash of that fold.
|
|
15
|
+
*/
|
|
16
|
+
export interface ISpecRevisionMeta {
|
|
17
|
+
/** The ticket's logical id (the head resolved at append time) — see the module header. */
|
|
18
|
+
spec_of: string;
|
|
19
|
+
/** 1,2,3… monotonic within the spec. */
|
|
20
|
+
revision_seq: number;
|
|
21
|
+
/** `'sha256:<hex>'` of the FOLDED document at this revision. */
|
|
22
|
+
revision_token: string;
|
|
23
|
+
/** Predecessor revision uid (`null` for the base revision). */
|
|
24
|
+
prev_revision: string | null;
|
|
25
|
+
/** The long-form file export (Primitive 2). */
|
|
26
|
+
anchor: ISpecAnchor;
|
|
27
|
+
appended_by: string;
|
|
28
|
+
appended_at: string;
|
|
29
|
+
}
|
|
30
|
+
export interface ISpecAppendInput {
|
|
31
|
+
/** The ticket's logical id — any uid on its `SUPERSEDES` chain is resolved forward. */
|
|
32
|
+
uid: string;
|
|
33
|
+
/** The delta appended at this revision (becomes the node's `content`). */
|
|
34
|
+
fragment: string;
|
|
35
|
+
/** The file export for this revision. */
|
|
36
|
+
anchor?: ISpecAnchor;
|
|
37
|
+
/** REQUIRED CAS token — the revision uid the caller read (`''` when the ticket had no spec yet). */
|
|
38
|
+
base_revision: string;
|
|
39
|
+
by: string;
|
|
40
|
+
}
|
|
41
|
+
export interface ISpecAppendOutcome {
|
|
42
|
+
/** UNCHANGED — cross-checked by AC1. */
|
|
43
|
+
uid: string;
|
|
44
|
+
/** The NEW revision uid. */
|
|
45
|
+
spec_revision: string;
|
|
46
|
+
/** `'sha256:<hex>'`. */
|
|
47
|
+
spec_revision_token: string;
|
|
48
|
+
revision_seq: number;
|
|
49
|
+
}
|
|
50
|
+
/** Current revision + its token for a work item. */
|
|
51
|
+
export interface ISpecPointer {
|
|
52
|
+
revision_uid: string;
|
|
53
|
+
revision_token: string;
|
|
54
|
+
revision_seq: number;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The store surface {@link appendSpecRevision} needs. Beyond the ordinary write
|
|
58
|
+
* handle it requires the store's `GraphBackend`, because revision DISCOVERY must
|
|
59
|
+
* run the SAME live-kind traversal the read path (`deriveSpecHead`) and the
|
|
60
|
+
* reconciliation (`spec-revision.reconcile.ts`) use — and that traversal
|
|
61
|
+
* (`query/resolve.ts`'s `resolveEdgeScopedCandidates`) is a `GraphBackend` read
|
|
62
|
+
* that must run BEFORE the `BEGIN IMMEDIATE` transaction opens (a `GraphBackend`
|
|
63
|
+
* call never runs inside it — see `resolveLogicalHeadTx`). `GraphBacklogStore`
|
|
64
|
+
* and the test `TestIssueStore` both satisfy this shape.
|
|
65
|
+
*/
|
|
66
|
+
export type ISpecAppendStore = IWriteStoreHandle & {
|
|
67
|
+
readonly graph: GraphBackend;
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* Rowids of live items whose DECLARED catalog kind is `SPEC` — an
|
|
71
|
+
* `issue`-shaped node carrying a live `has_kind` edge to the `kind:'SPEC'`
|
|
72
|
+
* catalog row. This is the SHIPPED production shape: the reconciled corpus is
|
|
73
|
+
* `issue` nodes whose DECLARED kind is `SPEC`, and reconciliation stamps them
|
|
74
|
+
* `spec_of`/`revision_seq` WITHOUT changing the raw `node.kind` column. Any
|
|
75
|
+
* discovery keyed on the raw column alone is therefore blind to them.
|
|
76
|
+
*
|
|
77
|
+
* Uses the SAME live-kind traversal the read path's `filter.kind`
|
|
78
|
+
* (`query/views/semantic.ts`) and `spec-revision.reconcile.ts` use —
|
|
79
|
+
* `query/resolve.ts`'s {@link resolveEdgeScopedCandidates} on
|
|
80
|
+
* `has_kind`→`SPEC` — so read, write, and reconcile can never disagree on what
|
|
81
|
+
* "a live `kind:'SPEC'` item" is. Returns `undefined` when `SPEC` resolves to
|
|
82
|
+
* no live catalog row (nothing is declared SPEC).
|
|
83
|
+
*/
|
|
84
|
+
export declare function declaredSpecRowids(graph: GraphBackend): Promise<ReadonlySet<number> | undefined>;
|
|
85
|
+
/**
|
|
86
|
+
* Every live SPEC document — the UNION of two representations the store has
|
|
87
|
+
* held, both of which are specs:
|
|
88
|
+
*
|
|
89
|
+
* (a) a node whose RAW `kind` column is `SPEC` — the immutable revision object
|
|
90
|
+
* {@link appendSpecRevision} mints (and the shape the unit fixtures
|
|
91
|
+
* build); and
|
|
92
|
+
* (b) an `issue` node whose DECLARED catalog kind is `SPEC` — the reconciled
|
|
93
|
+
* production corpus (see {@link declaredSpecRowids}).
|
|
94
|
+
*
|
|
95
|
+
* This is the ONE discovery the read path ({@link deriveSpecHead}), the write
|
|
96
|
+
* path (`findChainHeadTx` via {@link declaredSpecRowids}), and the
|
|
97
|
+
* reconciliation all share, so they cannot drift on which items are specs. The
|
|
98
|
+
* union is load-bearing: a raw-kind-only reader missed shape (b) entirely.
|
|
99
|
+
*/
|
|
100
|
+
export declare function discoverLiveSpecNodes(graph: GraphBackend): Promise<NodeRecord[]>;
|
|
101
|
+
/**
|
|
102
|
+
* Append a revision. One `BEGIN IMMEDIATE` transaction (ADR-0001 + ADR-0012 —
|
|
103
|
+
* no temp-file/rename/flock; atomicity is the store's job):
|
|
104
|
+
*
|
|
105
|
+
* 1. resolve `uid` → logical head (input may be any `SUPERSEDES`-chain uid — AC9);
|
|
106
|
+
* 2. read the current revision; if it is not `base_revision` →
|
|
107
|
+
* {@link SpecRevisionConflictError} (stale base — no write; AC6);
|
|
108
|
+
* 3. write a NEW `kind:'SPEC'` node holding the fragment (AC1/AC8);
|
|
109
|
+
* 4. advance the ticket's `meta.spec_revision` IN PLACE via the CAS helper,
|
|
110
|
+
* bumping `meta.revision` — the uid is preserved (AC1);
|
|
111
|
+
* 5. `writeAudit(action:'spec-appended')`.
|
|
112
|
+
*
|
|
113
|
+
* NEVER update or delete a revision node (AC1/AC8).
|
|
114
|
+
*/
|
|
115
|
+
export declare function appendSpecRevision(handle: ISpecAppendStore, input: ISpecAppendInput): Promise<ISpecAppendOutcome>;
|
|
116
|
+
/**
|
|
117
|
+
* The derived chain head for a ticket — a live `SPEC` node whose
|
|
118
|
+
* `meta.spec_of` is in the ticket's `SUPERSEDES`-chain uid set with the max
|
|
119
|
+
* `meta.revision_seq` (read-path analogue of {@link findChainHeadTx}, backed by
|
|
120
|
+
* the same reconciliation-created functional index). `undefined` when the ticket has
|
|
121
|
+
* no live revision.
|
|
122
|
+
*/
|
|
123
|
+
export declare function deriveSpecHead(graph: GraphBackend, uid: string): Promise<ISpecPointer | undefined>;
|
|
124
|
+
/** The pointer ON THE RECORD only (`meta.spec_revision`) — never the derived head. Used by the staleness cross-check. */
|
|
125
|
+
export declare function readPointerRecord(graph: GraphBackend, uid: string): Promise<ISpecPointer | undefined>;
|
|
126
|
+
/**
|
|
127
|
+
* Current revision + its token for a work item (`undefined` if it has no spec).
|
|
128
|
+
* Prefers the on-record pointer; falls back to the derived chain head so a
|
|
129
|
+
* ticket whose pointer was not (yet) written — pre-reconciliation data — still reads.
|
|
130
|
+
*/
|
|
131
|
+
export declare function readSpecPointer(graph: GraphBackend, uid: string): Promise<ISpecPointer | undefined>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { IWriteStoreHandle } from './tx.js';
|
|
2
|
+
import { GraphBackend } from '@adhd/sox-graph-store';
|
|
3
|
+
|
|
4
|
+
/** The store surface the reconciliation needs — satisfied by `GraphBacklogStore` and the test store alike. */
|
|
5
|
+
export type ISpecReconcileStore = IWriteStoreHandle & {
|
|
6
|
+
readonly graph: GraphBackend;
|
|
7
|
+
};
|
|
8
|
+
export interface ISpecReconcileStamp {
|
|
9
|
+
revisionUid: string;
|
|
10
|
+
/** The logical id stamped onto `meta.spec_of` (the ticket's head). */
|
|
11
|
+
specOf: string;
|
|
12
|
+
revisionSeq: number;
|
|
13
|
+
prevRevision: string | null;
|
|
14
|
+
revisionToken: string;
|
|
15
|
+
/** `false` when the node already carries exactly this stamp (skip the write). */
|
|
16
|
+
needsStamp: boolean;
|
|
17
|
+
}
|
|
18
|
+
export interface ISpecReconcileHead {
|
|
19
|
+
ticketUid: string;
|
|
20
|
+
revisionUid: string;
|
|
21
|
+
revisionToken: string;
|
|
22
|
+
revisionSeq: number;
|
|
23
|
+
/** `false` when the ticket's pointer already names this head with this token. */
|
|
24
|
+
needsPointer: boolean;
|
|
25
|
+
}
|
|
26
|
+
export interface ISpecReconcileReport {
|
|
27
|
+
/** How many live `SPEC` nodes were discovered. */
|
|
28
|
+
scanned: number;
|
|
29
|
+
stamps: ISpecReconcileStamp[];
|
|
30
|
+
heads: ISpecReconcileHead[];
|
|
31
|
+
/** `SPEC` nodes already stamped (idempotent no-op). */
|
|
32
|
+
alreadyStamped: number;
|
|
33
|
+
/** `SPEC` nodes with no live `part_of` parent — left untouched, reported. */
|
|
34
|
+
orphaned: string[];
|
|
35
|
+
/** Whether the lookup index exists after this call. */
|
|
36
|
+
indexCreated: boolean;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Compute the reconciliation plan WITHOUT writing (the dry-run). Discovery is
|
|
40
|
+
* `query`-driven; nothing is mutated.
|
|
41
|
+
*/
|
|
42
|
+
export declare function planSpecRevisionReconcile(store: ISpecReconcileStore): Promise<ISpecReconcileReport>;
|
|
43
|
+
/**
|
|
44
|
+
* Apply the reconciliation. Idempotent: an already-stamped `SPEC` and an
|
|
45
|
+
* already-pointed ticket are both skipped, so a second run is a no-op. One
|
|
46
|
+
* `BEGIN IMMEDIATE` transaction (ADR-0001) — never a hard delete.
|
|
47
|
+
*/
|
|
48
|
+
export declare function applySpecRevisionReconcile(store: ISpecReconcileStore): Promise<ISpecReconcileReport>;
|
package/write/transition.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { IWriteStoreHandle } from './tx.js';
|
|
2
|
-
import {
|
|
2
|
+
import { ICitation } from '../citation.js';
|
|
3
3
|
|
|
4
4
|
export interface ITransitionInput {
|
|
5
5
|
/** The `issue` uid to transition (§6.3, an "Issue verb"). */
|
|
@@ -11,7 +11,7 @@ export interface ITransitionInput {
|
|
|
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;
|
|
13
13
|
/** REQUIRED (≥1) when `project_policy.citation_required` is `true` AND `toStatus` resolves to a terminal status. Written as `citation` nodes + `has_citation` edges exactly like `create`'s own citations. */
|
|
14
|
-
citations?:
|
|
14
|
+
citations?: ICitation[];
|
|
15
15
|
/**
|
|
16
16
|
* The item-level disclosure-contract git context — the SAME plain metadata
|
|
17
17
|
* scalar `create`'s own `gitContext` field writes (repo `AGENTS.md`'s "Cite
|
|
@@ -22,6 +22,17 @@ export interface ITransitionInput {
|
|
|
22
22
|
* per-citation `ref` — it rides the issue, never a `citation` node.
|
|
23
23
|
*/
|
|
24
24
|
gitContext?: string;
|
|
25
|
+
/**
|
|
26
|
+
* A recorded override of a refusing obligation (C5, DESIGN §2 Primitive 3).
|
|
27
|
+
* Honoured ONLY when the caller is in the refusing obligation's
|
|
28
|
+
* `override.actors`; a reason is ALWAYS required (never a configurable
|
|
29
|
+
* boolean) and is recorded on the audit row. A claimed override by a
|
|
30
|
+
* non-listed actor, or one with a blank reason, throws
|
|
31
|
+
* `OverrideNotPermittedError`.
|
|
32
|
+
*/
|
|
33
|
+
override?: {
|
|
34
|
+
reason: string;
|
|
35
|
+
};
|
|
25
36
|
}
|
|
26
37
|
export interface ITransitionOutcome {
|
|
27
38
|
uid: string;
|
package/write/tx.d.ts
CHANGED
|
@@ -162,7 +162,7 @@ export declare function resolveLiveIssueTx(tx: AdapterTransaction, uid: string):
|
|
|
162
162
|
/** rowid → node, inside a tx (used to resolve an edge endpoint's kind without a redundant round trip when the caller doesn't already know it). */
|
|
163
163
|
export declare function getNodeByRowidTx(tx: AdapterTransaction, rowid: number): Promise<ITxNodeRow | null>;
|
|
164
164
|
export interface IWriteNodeTxInput {
|
|
165
|
-
/** The entity-type discriminator — `project`/`component`/`location`/`issue`/`kind`/`edge_kind`/`status`/`priority`/`agent`/`note`/`citation`/`transition`/`audit` (§3). NEVER validated against a closed vocabulary here — the schema is open by design (§0 anti-antipattern 3); the write layer is the only composer of these literals. */
|
|
165
|
+
/** The entity-type discriminator — `project`/`component`/`location`/`issue`/`kind`/`edge_kind`/`status`/`priority`/`agent`/`note`/`citation`/`transition`/`audit`/`attestation`/`obligation`/`SPEC` (§3; the SPEC revision kind is added by C10, DESIGN §12). NEVER validated against a closed vocabulary here — the schema is open by design (§0 anti-antipattern 3); the write layer is the only composer of these literals. */
|
|
166
166
|
kind: string;
|
|
167
167
|
/** The business name (`issue.title`, a catalog row's name, …). Omit for a node with no name (none currently exist in §3's table, but the column is nullable). */
|
|
168
168
|
name?: string;
|
|
@@ -328,6 +328,26 @@ export interface IInvalidateEdgeTxInput {
|
|
|
328
328
|
* instead of the bare adapter.
|
|
329
329
|
*/
|
|
330
330
|
export declare function invalidateEdgeTx(tx: AdapterTransaction, input: IInvalidateEdgeTxInput): Promise<void>;
|
|
331
|
+
/**
|
|
332
|
+
* Update a LIVE node's `meta` IN PLACE inside `tx`, CAS-guarded on the node's
|
|
333
|
+
* own `meta.revision` counter (SR-2 monotonic revision + SR-6 per-node CAS,
|
|
334
|
+
* per DESIGN §2 Primitive 1's "node's monotonic `revision`, bumped on every
|
|
335
|
+
* mutating write"). Preserves `uid` — this is the ONLY write path that mutates
|
|
336
|
+
* a node without superseding it, and it exists solely for the C10 spec pointer
|
|
337
|
+
* (`meta.spec_revision`) and the SR-2 counter it bumps. No other verb may adopt
|
|
338
|
+
* it without a new decision.
|
|
339
|
+
*
|
|
340
|
+
* `patch`'s keys are shallow-merged over the node's existing meta; the merged
|
|
341
|
+
* blob's `revision` is set to `expectedRevision + 1`. Returns the new revision
|
|
342
|
+
* number on success, or `null` when the node is missing OR the stored revision
|
|
343
|
+
* no longer equals `expectedRevision` (a concurrent writer won the CAS — the
|
|
344
|
+
* caller must surface a stale-base error, and because this runs inside the
|
|
345
|
+
* caller's own `BEGIN IMMEDIATE` transaction, a thrown error rolls the whole
|
|
346
|
+
* transaction back). A node with no `meta.revision` at all reads as revision
|
|
347
|
+
* `0` (matching `revision.ts`'s `readRevision`), so a pre-counter row is
|
|
348
|
+
* CAS-addressable without a backfill.
|
|
349
|
+
*/
|
|
350
|
+
export declare function updateNodeMetaTx(tx: AdapterTransaction, uid: string, patch: Record<string, unknown>, expectedRevision: number): Promise<number | null>;
|
|
331
351
|
/**
|
|
332
352
|
* The one transaction wrapper every write verb calls — `store.adapter.transaction(fn,
|
|
333
353
|
* {mode: resolveTransactionMode()})`, which is `'immediate'` (§4c: `BEGIN
|
package/write/update.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { IWriteStoreHandle } from './tx.js';
|
|
2
|
+
import { ICitation } from '../citation.js';
|
|
2
3
|
|
|
3
4
|
export interface IUpdateIssueInput {
|
|
4
5
|
/** The `issue` uid to update (§6.3, an "Issue verb"). */
|
|
@@ -17,6 +18,27 @@ export interface IUpdateIssueInput {
|
|
|
17
18
|
assignee?: string;
|
|
18
19
|
/** catalog agent name/uid; → `touch` + `authored_by` edge rewrite. An unresolved NAME mints; a uid-shaped ref that does not resolve throws `CatalogNotFoundError('agent', ref)`. */
|
|
19
20
|
author?: string;
|
|
21
|
+
/**
|
|
22
|
+
* The DESIRED set of citations this issue should carry after this call — a
|
|
23
|
+
* DIFF-EMITTER, never a body rewrite (the architecture verdict): the live
|
|
24
|
+
* citation set is read inside the SAME `immediate` transaction, citations
|
|
25
|
+
* present live but absent from `citations` are removed (bi-temporally), and
|
|
26
|
+
* citations in `citations` but not live are added. Identity for the diff is
|
|
27
|
+
* `(file, lines, revision)` (§ `citationKey`), never `sha`/`context`.
|
|
28
|
+
*
|
|
29
|
+
* Routing a citation change through `body`/`supersede` is FORBIDDEN: that
|
|
30
|
+
* would mint a new issue uid and violate §1's "uid is the only identity".
|
|
31
|
+
* When `body` IS also given, the supersede mints the new node first, its
|
|
32
|
+
* live citations are carried forward, and THIS diff is applied against the
|
|
33
|
+
* carried-forward set on the new node — still inside the one transaction.
|
|
34
|
+
*
|
|
35
|
+
* Each citation's `sha` is computed (and policy-gated, exactly as `create`/
|
|
36
|
+
* `transition` do) before the transaction opens, so the write lock is never
|
|
37
|
+
* held across a filesystem/git read. `citations: []` clears every live
|
|
38
|
+
* citation. Omitted entirely ⇒ citations are untouched (and carried forward
|
|
39
|
+
* verbatim on a body edit).
|
|
40
|
+
*/
|
|
41
|
+
citations?: ICitation[];
|
|
20
42
|
/**
|
|
21
43
|
* §4b/§6.2 — waits for the fire-and-forget on-write embedding round-trip
|
|
22
44
|
* before `update` returns, when `true` and `handle.embedding` is
|
|
@@ -30,7 +52,7 @@ export interface IUpdateIssueInput {
|
|
|
30
52
|
*/
|
|
31
53
|
awaitEmbed?: boolean;
|
|
32
54
|
}
|
|
33
|
-
export type IUpdateIssueChangedField = 'title' | 'body' | 'kind' | 'priority' | 'assignee' | 'author';
|
|
55
|
+
export type IUpdateIssueChangedField = 'title' | 'body' | 'kind' | 'priority' | 'assignee' | 'author' | 'citations';
|
|
34
56
|
export interface IUpdateIssueOutcome {
|
|
35
57
|
/**
|
|
36
58
|
* The resulting CURRENT issue's uid — the SAME `input.uid` when `body` was
|