@catalyst-cloud/sdk 0.13.1 → 0.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -11
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/live-sync-client.d.ts +4 -0
- package/dist/live-sync-client.d.ts.map +1 -1
- package/dist/live-sync-client.js +36 -1
- package/dist/live-sync-client.js.map +1 -1
- package/dist/ndjson.d.ts.map +1 -1
- package/dist/ndjson.js +12 -9
- package/dist/ndjson.js.map +1 -1
- package/dist/node.js +8507 -30
- package/dist/replica/catalyst-replica.d.ts +13 -7
- package/dist/replica/catalyst-replica.d.ts.map +1 -1
- package/dist/replica/catalyst-replica.js +99 -41
- package/dist/replica/catalyst-replica.js.map +1 -1
- package/dist/tenant-client.d.ts +1750 -795
- package/dist/tenant-client.d.ts.map +1 -1
- package/dist/tenant-client.js +168 -7
- package/dist/tenant-client.js.map +1 -1
- package/dist/tenant-contract.d.ts +8 -2
- package/dist/tenant-contract.d.ts.map +1 -1
- package/dist/tenant-contract.js +6 -1
- package/dist/tenant-contract.js.map +1 -1
- package/package.json +48 -10
package/dist/tenant-client.d.ts
CHANGED
|
@@ -1,36 +1,808 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
// Generated by dts-bundle-generator v9.5.1
|
|
2
|
+
|
|
3
|
+
export type MemberLinearIdentity = {
|
|
4
|
+
resolution: "unattempted" | "no_email" | "no_match";
|
|
5
|
+
} | {
|
|
6
|
+
resolution: "resolved";
|
|
7
|
+
linearUserId: string;
|
|
8
|
+
resolvedAt: number;
|
|
9
|
+
} | {
|
|
10
|
+
resolution: "manual";
|
|
11
|
+
linearUserId: string;
|
|
12
|
+
resolvedAt: number;
|
|
13
|
+
resolvedBy: string | null;
|
|
14
|
+
};
|
|
15
|
+
export interface LinearIdentityOption {
|
|
16
|
+
id: string;
|
|
17
|
+
name: string | null;
|
|
18
|
+
displayName: string | null;
|
|
19
|
+
avatarUrl: string | null;
|
|
20
|
+
}
|
|
21
|
+
export interface LinearIdentityView {
|
|
22
|
+
identity: MemberLinearIdentity;
|
|
23
|
+
/** Absent means no choice was offered, including a temporarily unreadable roster. */
|
|
24
|
+
options?: LinearIdentityOption[];
|
|
25
|
+
}
|
|
26
|
+
export type LinearIdentityResult = ({
|
|
27
|
+
outcome: "ok";
|
|
28
|
+
} & LinearIdentityView) | {
|
|
29
|
+
outcome: "conflict";
|
|
30
|
+
status: 409;
|
|
31
|
+
reason: "already_resolved" | "already_claimed" | "identity_changed";
|
|
32
|
+
} | TenantClientFailure;
|
|
33
|
+
/** A bindable / storable SQLite scalar (matches the Workers `SqlStorageValue`). */
|
|
34
|
+
export type SqlValue = string | number | null | ArrayBuffer;
|
|
35
|
+
/**
|
|
36
|
+
* CTC-1912 — the "Blocked on an ask" chip's payload: the OPEN ask-labelled issue whose `blocks` edge
|
|
37
|
+
* points at this row, oldest wins when several cover it. `null` when nothing open and ask-labelled
|
|
38
|
+
* blocks this row.
|
|
39
|
+
*/
|
|
40
|
+
export interface BlockedOnAskView {
|
|
41
|
+
ask_identifier: SqlValue;
|
|
42
|
+
ask_assignee_id: SqlValue;
|
|
43
|
+
ask_assignee_name: SqlValue;
|
|
44
|
+
ask_created_at: SqlValue;
|
|
45
|
+
}
|
|
46
|
+
/** A denormalized label chip on an issue row. */
|
|
47
|
+
export interface IssueLabelView {
|
|
48
|
+
id: string;
|
|
49
|
+
name: SqlValue;
|
|
50
|
+
/** The real Linear label color (CTC-37); null until captured (UI falls back to a hash tint). */
|
|
51
|
+
color: SqlValue;
|
|
52
|
+
}
|
|
53
|
+
/** A denormalized relation edge on an issue row (joined to the issue by identifier, not id). */
|
|
54
|
+
export interface IssueRelationView {
|
|
55
|
+
/** The relation's own stable Linear id (relations.id PK, CTC-147) — distinct from the JOIN key
|
|
56
|
+
* (issue_identifier). Lets a consumer key/dedupe edges without inferring an id from the endpoints. */
|
|
57
|
+
id: SqlValue;
|
|
58
|
+
type: SqlValue;
|
|
59
|
+
issue_identifier: SqlValue;
|
|
60
|
+
related_identifier: SqlValue;
|
|
61
|
+
/**
|
|
62
|
+
* CTC-885: the workflow-state NAME of the issue on the FAR end of this edge, resolved at READ
|
|
63
|
+
* time; NULL when that issue is not mirrored (or is soft-deleted).
|
|
64
|
+
*
|
|
65
|
+
* ⛔ NULL MEANS "WE COULD NOT RESOLVE IT", NOT "IT IS OPEN" — 22 of 929 live `blocks` edges point
|
|
66
|
+
* at an identifier with no `issues` row (cross-team targets the workspace-wide `issueRelations`
|
|
67
|
+
* query surfaces but the sweep does not mirror). Reading NULL as open is the bug this field
|
|
68
|
+
* exists to make impossible; every consumer must branch on it.
|
|
69
|
+
*
|
|
70
|
+
* ⛔ DERIVED, NEVER STORED. A `relations.related_state` column would be written under the
|
|
71
|
+
* `updated_at`-guarded LWW upsert keyed on the RELATION's updatedAt (linear-reconcile.ts:1030-1056)
|
|
72
|
+
* — and a far-end state change does not touch the edge, so the guard would refuse the write and
|
|
73
|
+
* freeze the value forever. The exact bug CTC-885 reports would survive its own fix, invisibly.
|
|
74
|
+
*/
|
|
75
|
+
related_state: SqlValue;
|
|
76
|
+
}
|
|
77
|
+
/** One issue row for the list view — snake_case mirror columns + denormalized labels/relations. */
|
|
78
|
+
export interface IssueView {
|
|
79
|
+
id: string;
|
|
80
|
+
identifier: SqlValue;
|
|
81
|
+
title: SqlValue;
|
|
82
|
+
state: SqlValue;
|
|
83
|
+
/** The workflow state's stable Linear id (CTC-148) — behind the `state` name. */
|
|
84
|
+
state_id: SqlValue;
|
|
85
|
+
/** Effective Linear workflow type, derived for partial webhook snapshots (CTC-1587). */
|
|
86
|
+
state_type: SqlValue;
|
|
87
|
+
assignee: SqlValue;
|
|
88
|
+
/** Assignee identity (CTC-USERS) — the users.id FK + the resolved name/avatar, so the card renders
|
|
89
|
+
* an avatar instead of a gray "Unknown" chip. `assignee_name` falls back to the legacy `assignee`
|
|
90
|
+
* name string when the user isn't mirrored; `assignee_avatar_url` is null when unresolved. */
|
|
91
|
+
assignee_id: SqlValue;
|
|
92
|
+
assignee_name: SqlValue;
|
|
93
|
+
assignee_avatar_url: SqlValue;
|
|
94
|
+
/**
|
|
95
|
+
* The delegate AGENT's `users.id` FK, or null when nobody is acting on this issue (CTC-552).
|
|
96
|
+
*
|
|
97
|
+
* ⚠️ PRESENCE, NOT DISPLAY — which is why this is the ONLY delegate field on the list row, while the
|
|
98
|
+
* DETAIL view additionally resolves `delegate_name`/`delegate_avatar_url`. The consumer that needs a
|
|
99
|
+
* delegate on a LIST is the per-member "Waiting on me" filter, and it asks only *is a delegate set at
|
|
100
|
+
* all*. Resolving a name for every row of every page would buy a users lookup for nobody.
|
|
101
|
+
*
|
|
102
|
+
* ⛔ NULL HERE IS **NOT**, ON ITS OWN, A "SOMEONE IS WAITING ON YOU" SIGNAL — and reading it that way
|
|
103
|
+
* is the specific mistake this field exists to make avoidable. An undelegated, human-assigned issue
|
|
104
|
+
* is ALSO every fleet ticket's PRE-delegation state (`apps/mirror/src/write-proxy/delegate.ts`,
|
|
105
|
+
* CTC-593, which is the only writer of Linear's `delegate` field). A filter built on
|
|
106
|
+
* `delegate_id == null` alone therefore shows a member the entire fleet backlog nobody ever asked
|
|
107
|
+
* them about. The clause that actually discriminates a genuine human ask is an OUTGOING `blocks`
|
|
108
|
+
* relation — already carried on this same row in `relations[]`. Both clauses, or neither: this field
|
|
109
|
+
* is half of a pair, and the other half is two lines down.
|
|
110
|
+
*/
|
|
111
|
+
delegate_id: SqlValue;
|
|
112
|
+
priority: SqlValue;
|
|
113
|
+
estimate: SqlValue;
|
|
114
|
+
project_id: SqlValue;
|
|
115
|
+
cycle_id: SqlValue;
|
|
116
|
+
team_id: SqlValue;
|
|
117
|
+
/** Denormalized team key + name (CTC-148) — label a row's team without a teams lookup; team_id kept. */
|
|
118
|
+
team_key: SqlValue;
|
|
119
|
+
team_name: SqlValue;
|
|
120
|
+
/** The manual drag-rank (CTC-TICKET-FIELDS) — Linear sortOrder; carried on the list row so a future
|
|
121
|
+
* sort-by-rank can order without a detail round-trip. Null until captured. */
|
|
122
|
+
sort_order: SqlValue;
|
|
123
|
+
updated_at: SqlValue;
|
|
124
|
+
labels: IssueLabelView[];
|
|
125
|
+
relations: IssueRelationView[];
|
|
126
|
+
/** The raiser (CTC-867) — who raised the ask, sourced from the issue's Linear creator. Stored
|
|
127
|
+
* inline on the issue row (comments.author_* precedent), so no `users` JOIN is needed. Null when
|
|
128
|
+
* the issue has no recorded creator (never a fabricated "Unknown"). */
|
|
129
|
+
raiser: {
|
|
130
|
+
name: SqlValue;
|
|
131
|
+
avatarUrl: SqlValue;
|
|
132
|
+
} | null;
|
|
133
|
+
/** CTC-1736 — the asks that already NAME this issue, Done ones included. See {@link CoveringAskView}. */
|
|
134
|
+
covering_asks: CoveringAskView[];
|
|
135
|
+
/** CTC-1912 — the OPEN ask-labelled issue whose `blocks` edge points at this row (oldest wins),
|
|
136
|
+
* derived at read time (see {@link resolveBlockedOnAsk}). Null when nothing holds this ticket. */
|
|
137
|
+
blocked_on_ask: BlockedOnAskView | null;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* CTC-1736 — an ask that already names this issue, whether or not it is still open.
|
|
141
|
+
*
|
|
142
|
+
* ⛔ WHY THIS EXISTS AS A COLUMN AT ALL. Both arms of `waitingOnMeClause` gate on `notDone`, so a
|
|
143
|
+
* covering ask that has been ANSWERED is excluded from the page for exactly the reason any other
|
|
144
|
+
* Done ticket is — and the ask→subject `blocks` edge lives on the ASK's row, not on the subject's.
|
|
145
|
+
* The instant the ask is answered, the page carries no evidence it ever existed, and the surface
|
|
146
|
+
* reported "no ask has been raised for it yet" about a ticket whose ask had been raised AND
|
|
147
|
+
* answered. That sentence is what CTC-1736 is.
|
|
148
|
+
*
|
|
149
|
+
* ⛔ AN ANNOTATION, NOT A PREDICATE. Nothing here narrows any row set. CTC-607's rule (an inference
|
|
150
|
+
* may add to a declaration, never veto it) is preserved because there is no veto anywhere in this
|
|
151
|
+
* field's construction — it is SELECT-list only.
|
|
152
|
+
*
|
|
153
|
+
* ⛔ NOT VIEWER-SCOPED, unlike `notAlreadyAsked`'s `ai.assignee_id = ?`. Suppression must be
|
|
154
|
+
* viewer-scoped; a CAPTION must not be. An ask raised about this ticket and assigned to somebody
|
|
155
|
+
* else was still raised about this ticket. `assignee_id` rides along as data for any consumer that
|
|
156
|
+
* does need to scope.
|
|
157
|
+
*
|
|
158
|
+
* ⚠️ REACHED ONLY THROUGH A RELATION EDGE. The join is `relations` (`type IN ('blocks',
|
|
159
|
+
* 'related')`, ask → this issue), so an ask that names this ticket in its TITLE but carries no
|
|
160
|
+
* relation to it at all is invisible here and the row still renders the "no ask has been raised"
|
|
161
|
+
* caption. That is AC1's own scope for CTC-1736 ("an ask exists whose `blocks` edges include ticket
|
|
162
|
+
* T"), not an oversight — but `waiting-on-me.ts`'s CTC-607 note records a MEASURED `catalyst-ask`
|
|
163
|
+
* with zero blocking relations, so the gap is real and stated here rather than rediscovered.
|
|
164
|
+
*
|
|
165
|
+
* ⭐ CTC-2890 validate remediation (attempt 1, code-review CR-3) — `related` JOINED `blocks` HERE.
|
|
166
|
+
* CTC-2890 stops an auto-raised ask from carrying a `blocks` edge to the ticket it is about; it
|
|
167
|
+
* attaches an informing `related` edge instead. Every covering-ask and `blockedOnAsk` reader in this
|
|
168
|
+
* module reached an ask through `type = 'blocks'` alone, so after that change a held ticket had an
|
|
169
|
+
* open ask naming it and the UI said no ask covered it — the same unreadability CTC-2890 was raised
|
|
170
|
+
* out of, one surface over. The widening cannot pull in a non-ask: the other end of the join is
|
|
171
|
+
* constrained to the ask label family by `openAskBlockerConditions` (or `ASK_LABEL` on the page-scoped
|
|
172
|
+
* read), so the only new rows are asks a human or a raiser deliberately linked to this ticket.
|
|
173
|
+
*
|
|
174
|
+
* ⚠️ CAPPED AT {@link MAX_COVERING_ASKS} PER ROW, ordered subject-first then open-first then
|
|
175
|
+
* newest. ⛔ Therefore NEVER derive a COUNT from this array — it is a sample, not a tally
|
|
176
|
+
* (ADR-0049's rule, one surface over). Naming an entry is the only sanctioned use.
|
|
177
|
+
*/
|
|
178
|
+
export interface CoveringAskView {
|
|
179
|
+
identifier: SqlValue;
|
|
180
|
+
title: SqlValue;
|
|
181
|
+
state: SqlValue;
|
|
182
|
+
/** Whose ask it is. Data, not a filter — see the interface doc. */
|
|
183
|
+
assignee_id: SqlValue;
|
|
184
|
+
/** The ask's title names THIS row as its subject (`Unblock <identifier> …`) rather than merely
|
|
185
|
+
* carrying a `blocks` edge to it. The chain carve-out CTC-1337 records depends on the
|
|
186
|
+
* difference: an ask about A also blocks B, and B is a genuine unasked-about blocker. */
|
|
187
|
+
subject: boolean;
|
|
188
|
+
/** The ask's own state normalizes into {@link ANSWERED_STATE_KEYS} — i.e. a member actually
|
|
189
|
+
* answered it. ⛔ NOT `DONE_STATE_KEYS` (validate-round Finding 1, 2026-09-06): that list means
|
|
190
|
+
* "finished" and folds `canceled`/`cancelled`/`duplicate` in with `done`, so an ask closed WITHOUT
|
|
191
|
+
* a decision would read as answered. See `closedUnanswered` for that case. */
|
|
192
|
+
resolved: boolean;
|
|
193
|
+
/** The ask was closed WITHOUT a decision — canceled or marked a duplicate — as distinct from
|
|
194
|
+
* genuinely {@link resolved}. Mutually exclusive with `resolved`; both false while the ask is
|
|
195
|
+
* still open. */
|
|
196
|
+
closedUnanswered: boolean;
|
|
197
|
+
}
|
|
198
|
+
/** A denormalized comment on an issue detail view. */
|
|
199
|
+
export interface IssueCommentView {
|
|
200
|
+
id: SqlValue;
|
|
201
|
+
body: SqlValue;
|
|
202
|
+
/** Comment author + threading (CTC-42): name/avatar for the header, is_bot for the agent marker,
|
|
203
|
+
* parent_id for reply nesting. All nullable (a pre-backfill / bot comment may lack them). */
|
|
204
|
+
author_id: SqlValue;
|
|
205
|
+
author_name: SqlValue;
|
|
206
|
+
author_avatar_url: SqlValue;
|
|
207
|
+
is_bot: SqlValue;
|
|
208
|
+
parent_id: SqlValue;
|
|
209
|
+
/** CTC-744 — Comment.createdAt, so a thread's REPLIES sort in created order rather than
|
|
210
|
+
* last-edited order. Nullable: the column predates the backfill on some rows. The list's own
|
|
211
|
+
* `ORDER BY updated_at` is unchanged — this only orders replies WITHIN a thread. */
|
|
212
|
+
created_at: SqlValue;
|
|
213
|
+
updated_at: SqlValue;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* CTC-524 — an agent session on the issue detail, with its activity stream.
|
|
217
|
+
*
|
|
218
|
+
* ⛔ RAW STATE, NOT A VERDICT. `status` is Linear's own (`active` / `awaitingInput` / `complete` /
|
|
219
|
+
* `error`) and `updated_at` is when it last moved. CTC-503's abandoned-vs-awaiting distinction is
|
|
220
|
+
* DERIVED from the two by the reader — deliberately not decided here, because a threshold baked into
|
|
221
|
+
* the read-model is inherited by every consumer that had no say in it and cannot see it.
|
|
222
|
+
*/
|
|
223
|
+
export interface IssueAgentSessionView {
|
|
224
|
+
id: SqlValue;
|
|
225
|
+
status: SqlValue;
|
|
226
|
+
app_user_id: SqlValue;
|
|
227
|
+
/** ⚠️ Frequently NULL — a session the app created for itself has no human creator (ARCHITECT-7).
|
|
228
|
+
* That null is evidence, not a gap to paper over. */
|
|
229
|
+
creator_id: SqlValue;
|
|
230
|
+
created_at: SqlValue;
|
|
231
|
+
updated_at: SqlValue;
|
|
232
|
+
activities: IssueAgentActivityView[];
|
|
233
|
+
}
|
|
234
|
+
/** One activity in a session's stream. `type` carries the notify semantics (CTC-501) — an
|
|
235
|
+
* `elicitation` reached a human's Inbox, a `thought` did not. */
|
|
236
|
+
export interface IssueAgentActivityView {
|
|
237
|
+
id: SqlValue;
|
|
238
|
+
type: SqlValue;
|
|
239
|
+
body: SqlValue;
|
|
240
|
+
/** CTC-1089 — an `action` activity's fields (Linear's ActionContent carries NO `body`), promoted
|
|
241
|
+
* out of `raw`. Null for every other activity type. */
|
|
242
|
+
action: SqlValue;
|
|
243
|
+
parameter: SqlValue;
|
|
244
|
+
result: SqlValue;
|
|
245
|
+
signal: SqlValue;
|
|
246
|
+
created_at: SqlValue;
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* A resolved label reference on an activity event (CTC-17). `id` is ALWAYS present (the event really
|
|
250
|
+
* touched that label); `name`/`color` resolve from `labels` REGARDLESS of removed_at — a label deleted
|
|
251
|
+
* AFTER the event still names its past row in the historical timeline — and stay null when the id isn't
|
|
252
|
+
* mirrored (never fabricated). This differs from the LIVE label chips on the issue row, which DO filter
|
|
253
|
+
* removed_at: those show current state, this shows history.
|
|
254
|
+
*/
|
|
255
|
+
export interface IssueActivityLabelRef {
|
|
256
|
+
id: SqlValue;
|
|
257
|
+
name: SqlValue;
|
|
258
|
+
color: SqlValue;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* One materialized IssueHistory event on an issue's timeline (CTC-17) — the TRANSITIONS the live
|
|
262
|
+
* `issues` row structurally can't express (it stores only the current value + a single actor that each
|
|
263
|
+
* write clobbers). Sourced from the immutable `issue_history` table (CTC-14), ordered by `created_at`
|
|
264
|
+
* (the timeline sort key, ms epoch) so the UI can interleave these rows against comment timestamps.
|
|
265
|
+
*
|
|
266
|
+
* The id-bearing transition fields are resolved to display values AT READ TIME — the read-model owns
|
|
267
|
+
* Linear-id → display resolution so the UI stays dumb and never needs a Linear-domain lookup table:
|
|
268
|
+
* • `actor_id`, `from/to_assignee_id` → `users` (name; actor also carries avatar)
|
|
269
|
+
* • `from/to_project_id` → project name · `from/to_cycle_id` → cycle number
|
|
270
|
+
* • `from/to_parent_id` → issue identifier · label-id arrays → {id,name,color}
|
|
271
|
+
* Every resolution is GUARDED (a null id resolves to null; an unmirrored id stays null — never
|
|
272
|
+
* fabricated) and HISTORICAL (no removed_at filter — a since-deleted label/project/parent still names
|
|
273
|
+
* its past event). `from/to_team_id` stay RAW ids (the mirror has no teams table to resolve against).
|
|
274
|
+
*
|
|
275
|
+
* Raw scalars the UI formats itself: `from/to_state` are workflow state NAMES already; `from/to_priority`
|
|
276
|
+
* are the Linear 0-4 priority enum; `from/to_estimate` are points; `from/to_title` are strings;
|
|
277
|
+
* `from/to_due_date` are TimelessDate "YYYY-MM-DD" strings AS-IS (NEVER a timestamp — the trap). The
|
|
278
|
+
* booleans are NULL-PRESERVING: `null` = the event did not touch the property, `0` = an explicit false
|
|
279
|
+
* transition (unarchive/untrash), `1` = true. Collapsing null→false would fabricate "no change" as false.
|
|
280
|
+
*/
|
|
281
|
+
export interface IssueActivityView {
|
|
282
|
+
id: SqlValue;
|
|
283
|
+
/** Event time (ms epoch) — the timeline sort key the UI interleaves against comment `updated_at`. */
|
|
284
|
+
created_at: SqlValue;
|
|
285
|
+
actor_id: SqlValue;
|
|
286
|
+
actor_name: SqlValue;
|
|
287
|
+
actor_avatar_url: SqlValue;
|
|
288
|
+
/** Workflow state transition — NAMES already (issues store state as a name; there is no states table). */
|
|
289
|
+
from_state: SqlValue;
|
|
290
|
+
to_state: SqlValue;
|
|
291
|
+
/** Assignee transition — the raw ids plus the resolved display names (null when unmirrored/unset). */
|
|
292
|
+
from_assignee_id: SqlValue;
|
|
293
|
+
from_assignee_name: SqlValue;
|
|
294
|
+
to_assignee_id: SqlValue;
|
|
295
|
+
to_assignee_name: SqlValue;
|
|
296
|
+
from_priority: SqlValue;
|
|
297
|
+
to_priority: SqlValue;
|
|
298
|
+
from_estimate: SqlValue;
|
|
299
|
+
to_estimate: SqlValue;
|
|
300
|
+
from_title: SqlValue;
|
|
301
|
+
to_title: SqlValue;
|
|
302
|
+
/** Cycle transition — the raw ids plus the resolved cycle numbers. */
|
|
303
|
+
from_cycle_id: SqlValue;
|
|
304
|
+
from_cycle_number: SqlValue;
|
|
305
|
+
to_cycle_id: SqlValue;
|
|
306
|
+
to_cycle_number: SqlValue;
|
|
307
|
+
/** Project transition — the raw ids plus the resolved project names. */
|
|
308
|
+
from_project_id: SqlValue;
|
|
309
|
+
from_project_name: SqlValue;
|
|
310
|
+
to_project_id: SqlValue;
|
|
311
|
+
to_project_name: SqlValue;
|
|
312
|
+
/** Parent-issue transition — the raw ids plus the resolved issue identifiers. */
|
|
313
|
+
from_parent_id: SqlValue;
|
|
314
|
+
from_parent_identifier: SqlValue;
|
|
315
|
+
to_parent_id: SqlValue;
|
|
316
|
+
to_parent_identifier: SqlValue;
|
|
317
|
+
/** Team transition — RAW ids (no teams table to resolve a name from). */
|
|
318
|
+
from_team_id: SqlValue;
|
|
319
|
+
to_team_id: SqlValue;
|
|
320
|
+
/** TimelessDate "YYYY-MM-DD" strings AS-IS (NEVER a timestamp). */
|
|
321
|
+
from_due_date: SqlValue;
|
|
322
|
+
to_due_date: SqlValue;
|
|
323
|
+
/** Labels added/removed in this event — resolved to {id,name,color} (historical; see IssueActivityLabelRef). */
|
|
324
|
+
added_labels: IssueActivityLabelRef[];
|
|
325
|
+
removed_labels: IssueActivityLabelRef[];
|
|
326
|
+
/** NULL-PRESERVING booleans — null = untouched, 0 = explicit false, 1 = true (see the interface doc). */
|
|
327
|
+
updated_description: SqlValue;
|
|
328
|
+
archived: SqlValue;
|
|
329
|
+
auto_archived: SqlValue;
|
|
330
|
+
auto_closed: SqlValue;
|
|
331
|
+
trashed: SqlValue;
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* The single-issue detail view — the OPEN UI detail-pane surface. Reuses the EXACT join shapes
|
|
335
|
+
* buildIssuesView uses (labels via issue_labels JOIN labels scoped to this issue_id; relations by
|
|
336
|
+
* IDENTIFIER), plus the issue's live comments. `description` (CTC-35), the delegate/bot actor
|
|
337
|
+
* (CTC-38), and the parent-issue pointer (CTC-39) ride this DETAIL fetch only — they are deliberately
|
|
338
|
+
* NOT in buildIssuesView / the SSE list row (keeps the list payload + apply path small).
|
|
339
|
+
*
|
|
340
|
+
* The raw `project_id`/`cycle_id` foreign keys are resolved to human-readable display values (CTC-36)
|
|
341
|
+
* so the rail renders names + lifecycle context, not UUIDs: `project_name`/`project_state` and
|
|
342
|
+
* `cycle_number`/`cycle_starts_at`/`cycle_ends_at`. Resolved by separate guarded lookups (NOT a JOIN —
|
|
343
|
+
* keeps the issue SELECT byte-identical and avoids ambiguous `id` columns); all stay null when the FK
|
|
344
|
+
* is null OR the referenced project/cycle isn't mirrored yet (never fabricated). Cycles carry only a
|
|
345
|
+
* `number` + start/end ms epochs in the mirror (schema.sql:51) — the client formats "Cycle N" + dates.
|
|
346
|
+
*/
|
|
347
|
+
export interface IssueDetailView {
|
|
348
|
+
id: string;
|
|
349
|
+
identifier: SqlValue;
|
|
350
|
+
title: SqlValue;
|
|
351
|
+
/** The issue's markdown description (CTC-35) — detail-only; null when absent. */
|
|
352
|
+
description: SqlValue;
|
|
353
|
+
state: SqlValue;
|
|
354
|
+
/** The workflow state's stable Linear id (CTC-148) — behind the `state` name. */
|
|
355
|
+
state_id: SqlValue;
|
|
356
|
+
assignee: SqlValue;
|
|
357
|
+
/** Assignee identity (CTC-USERS): the FK + resolved name/avatar (null when the user isn't mirrored —
|
|
358
|
+
* never fabricated). `assignee_name` falls back to the legacy `assignee` name string. */
|
|
359
|
+
assignee_id: SqlValue;
|
|
360
|
+
assignee_name: SqlValue;
|
|
361
|
+
assignee_avatar_url: SqlValue;
|
|
362
|
+
priority: SqlValue;
|
|
363
|
+
estimate: SqlValue;
|
|
364
|
+
project_id: SqlValue;
|
|
365
|
+
project_name: SqlValue;
|
|
366
|
+
project_state: SqlValue;
|
|
367
|
+
/** Project enrichment (CTC-HIERARCHY) — resolved alongside project_name; null when unmirrored. */
|
|
368
|
+
project_description: SqlValue;
|
|
369
|
+
project_progress: SqlValue;
|
|
370
|
+
project_health: SqlValue;
|
|
371
|
+
cycle_id: SqlValue;
|
|
372
|
+
cycle_number: SqlValue;
|
|
373
|
+
/** The cycle's display name (cycles.name, CTC-147) — null when the cycle is unnamed or unmirrored.
|
|
374
|
+
* Resolved alongside cycle_number; lets the UI label a cycle without a separate lookup. */
|
|
375
|
+
cycle_name: SqlValue;
|
|
376
|
+
cycle_starts_at: SqlValue;
|
|
377
|
+
cycle_ends_at: SqlValue;
|
|
378
|
+
team_id: SqlValue;
|
|
379
|
+
/** Denormalized team key + name (CTC-148) — label the issue's team without a teams lookup; team_id kept. */
|
|
380
|
+
team_key: SqlValue;
|
|
381
|
+
team_name: SqlValue;
|
|
382
|
+
/** Delegate (agent) + bot/last-actor (CTC-38) — null for human-assigned tickets. */
|
|
383
|
+
delegate_id: SqlValue;
|
|
384
|
+
delegate_name: SqlValue;
|
|
385
|
+
/** The delegate AGENT's avatar (CTC-USERS) — resolved delegate_id → users; null when unmirrored. */
|
|
386
|
+
delegate_avatar_url: SqlValue;
|
|
387
|
+
bot_actor_name: SqlValue;
|
|
388
|
+
bot_actor_type: SqlValue;
|
|
389
|
+
bot_actor_sub_type: SqlValue;
|
|
390
|
+
/** Parent-issue pointer (CTC-39) — null for top-level issues. */
|
|
391
|
+
parent_id: SqlValue;
|
|
392
|
+
parent_identifier: SqlValue;
|
|
393
|
+
/** Ticket field enrichment (CTC-TICKET-FIELDS) — the Linear deep-link, the lifecycle timestamps
|
|
394
|
+
* (ms epochs), the due date (TimelessDate string, NOT a timestamp), the human priority label, and
|
|
395
|
+
* the manual drag-rank. All nullable (a pre-backfill row, or a state the issue hasn't reached). */
|
|
396
|
+
url: SqlValue;
|
|
397
|
+
started_at: SqlValue;
|
|
398
|
+
completed_at: SqlValue;
|
|
399
|
+
canceled_at: SqlValue;
|
|
400
|
+
created_at: SqlValue;
|
|
401
|
+
due_date: SqlValue;
|
|
402
|
+
priority_label: SqlValue;
|
|
403
|
+
sort_order: SqlValue;
|
|
404
|
+
updated_at: SqlValue;
|
|
405
|
+
comments: IssueCommentView[];
|
|
406
|
+
/** CTC-524 — agent sessions on this issue, newest first, each with its activities. */
|
|
407
|
+
agent_sessions: IssueAgentSessionView[];
|
|
408
|
+
/** The interleavable activity timeline (CTC-17) — issue_history transitions ordered by created_at,
|
|
409
|
+
* with ids resolved to display values. The UI merges this with `comments` by timestamp. Empty []
|
|
410
|
+
* when the issue has no captured history (a fresh issue, or one created before history backfill). */
|
|
411
|
+
activity: IssueActivityView[];
|
|
412
|
+
labels: IssueLabelView[];
|
|
413
|
+
relations: IssueRelationView[];
|
|
414
|
+
/** The PRs that reference this ticket (CTC-269) — the ticket→PR side of the cross-link. Reverse
|
|
415
|
+
* lookup on `pull_requests.linear_issue_identifier`; empty [] when no mirrored PR names this key. */
|
|
416
|
+
linked_pulls: IssueLinkedPullView[];
|
|
417
|
+
}
|
|
418
|
+
/** A PR that references an issue (CTC-269) — the ticket→PR reverse-lookup row. Carries just enough to
|
|
419
|
+
* render the Relations card row (a state dot + title) and deep-link straight to the PR on GitHub
|
|
420
|
+
* (`repo_id` + `number`, CTC-1537) without a second fetch. `node_id` is retained only as a React key —
|
|
421
|
+
* it plays no role in the link, which is always derivable since `repo_id`/`number` are non-nullable.
|
|
422
|
+
* The client derives the review-state display from state/draft/merged (pull-presenter). */
|
|
423
|
+
export interface IssueLinkedPullView {
|
|
424
|
+
repo_id: SqlValue;
|
|
425
|
+
number: SqlValue;
|
|
426
|
+
node_id: SqlValue;
|
|
427
|
+
title: SqlValue;
|
|
428
|
+
state: SqlValue;
|
|
429
|
+
draft: SqlValue;
|
|
430
|
+
merged: SqlValue;
|
|
431
|
+
}
|
|
432
|
+
/** A denormalized CI check pill on a PR row. */
|
|
433
|
+
export interface PullCheckView {
|
|
434
|
+
name: SqlValue;
|
|
435
|
+
status: SqlValue;
|
|
436
|
+
conclusion: SqlValue;
|
|
437
|
+
}
|
|
438
|
+
/** One PR row for the list view — snake_case mirror columns + denormalized checks. */
|
|
439
|
+
export interface PullView {
|
|
440
|
+
repo_id: SqlValue;
|
|
441
|
+
number: SqlValue;
|
|
442
|
+
node_id: SqlValue;
|
|
443
|
+
/** PR data tier 1 (CTC-PR-TIER1): title (the #1 complaint), body, author, dates, comment count,
|
|
444
|
+
* milestone — all rode the fetched PR list/object (zero extra GitHub API calls). All nullable
|
|
445
|
+
* (a pre-backfill row, or a PR with no milestone, leaves them null). */
|
|
446
|
+
title: SqlValue;
|
|
447
|
+
body: SqlValue;
|
|
448
|
+
author_login: SqlValue;
|
|
449
|
+
author_avatar_url: SqlValue;
|
|
450
|
+
state: SqlValue;
|
|
451
|
+
draft: SqlValue;
|
|
452
|
+
merged: SqlValue;
|
|
453
|
+
merged_at: SqlValue;
|
|
454
|
+
head_sha: SqlValue;
|
|
455
|
+
base_ref: SqlValue;
|
|
456
|
+
mergeable: SqlValue;
|
|
457
|
+
mergeable_state: SqlValue;
|
|
458
|
+
auto_merge: SqlValue;
|
|
459
|
+
created_at: SqlValue;
|
|
460
|
+
closed_at: SqlValue;
|
|
461
|
+
comment_count: SqlValue;
|
|
462
|
+
milestone_title: SqlValue;
|
|
463
|
+
updated_at: SqlValue;
|
|
464
|
+
/** CTC-269: the PR's source branch (persisted) + the Linear ticket key parsed from the PR text.
|
|
465
|
+
* Both nullable (a pre-backfill row, or a PR whose text names no ticket). */
|
|
466
|
+
head_ref: SqlValue;
|
|
467
|
+
linear_issue_identifier: SqlValue;
|
|
468
|
+
checks: PullCheckView[];
|
|
469
|
+
/** CTC-312: 1 when the PR has any review with state COMMENTED or CHANGES_REQUESTED
|
|
470
|
+
* (case-insensitive). The board's Remediate column reads this; the list view ignores it. */
|
|
471
|
+
has_review_signal: number;
|
|
472
|
+
/** CTC-312: 1 when the PR's head sha carries a legacy commit STATUS in a failing state
|
|
473
|
+
* (failure/error). The board's Remediate signal reads this so a repo that reports CI via the
|
|
474
|
+
* Status API (not the modern check_runs) still surfaces as failing; the list view ignores it. */
|
|
475
|
+
has_failing_status: number;
|
|
476
|
+
/** CTC-1912 — inherited from the linked ticket (`linear_issue_identifier`): the OPEN ask-labelled
|
|
477
|
+
* issue blocking that ticket, oldest wins. Null when the PR names no ticket, or that ticket is
|
|
478
|
+
* unheld. Derived at read time, same helper as `IssueView.blocked_on_ask`. */
|
|
479
|
+
blocked_on_ask: BlockedOnAskView | null;
|
|
480
|
+
}
|
|
481
|
+
/** A review on a PR (CTC-125) — the resolved reviewer + verdict state. `reviews` DOES carry a `body`
|
|
482
|
+
* column (CTC-575), but this view does not select it — only the state (APPROVED / CHANGES_REQUESTED /
|
|
483
|
+
* COMMENTED) shows; surfacing the review text is a UX-consumer concern tracked separately. */
|
|
484
|
+
export interface PullReviewView {
|
|
485
|
+
review_id: SqlValue;
|
|
486
|
+
/** The reviewer's stable Linear/GitHub user id (reviews.user_id) — the collision-free key for
|
|
487
|
+
* collapsing a reviewer's history to one effective verdict (CTC-357 review). `reviewer_name` is a
|
|
488
|
+
* nullable, non-unique display name and must NOT be used for grouping. */
|
|
489
|
+
reviewer_id: SqlValue;
|
|
490
|
+
reviewer_name: SqlValue;
|
|
491
|
+
reviewer_avatar_url: SqlValue;
|
|
492
|
+
state: SqlValue;
|
|
493
|
+
submitted_at: SqlValue;
|
|
494
|
+
}
|
|
495
|
+
/** A legacy commit-status context on a PR's head sha (CTC-125) — the GitHub Status API (distinct from
|
|
496
|
+
* the modern check_runs). Often empty by design when CI reports via check_runs instead. */
|
|
497
|
+
export interface PullStatusView {
|
|
498
|
+
context: SqlValue;
|
|
499
|
+
state: SqlValue;
|
|
500
|
+
target_url: SqlValue;
|
|
501
|
+
updated_at: SqlValue;
|
|
502
|
+
}
|
|
503
|
+
/** One PR's full detail (CTC-125) — the list `PullView` columns PLUS the already-ingested reviews +
|
|
504
|
+
* commit_statuses (never surfaced before). Keyed by `node_id`. `checks[]` joins by head_sha (modern
|
|
505
|
+
* Actions report here — lead with these); `commit_statuses[]` is the legacy Status API. */
|
|
506
|
+
export interface PullDetailView {
|
|
507
|
+
repo_id: SqlValue;
|
|
508
|
+
number: SqlValue;
|
|
509
|
+
node_id: SqlValue;
|
|
510
|
+
title: SqlValue;
|
|
511
|
+
body: SqlValue;
|
|
512
|
+
author_login: SqlValue;
|
|
513
|
+
author_avatar_url: SqlValue;
|
|
514
|
+
state: SqlValue;
|
|
515
|
+
draft: SqlValue;
|
|
516
|
+
merged: SqlValue;
|
|
517
|
+
merged_at: SqlValue;
|
|
518
|
+
head_sha: SqlValue;
|
|
519
|
+
base_ref: SqlValue;
|
|
520
|
+
mergeable: SqlValue;
|
|
521
|
+
mergeable_state: SqlValue;
|
|
522
|
+
auto_merge: SqlValue;
|
|
523
|
+
created_at: SqlValue;
|
|
524
|
+
closed_at: SqlValue;
|
|
525
|
+
comment_count: SqlValue;
|
|
526
|
+
milestone_title: SqlValue;
|
|
527
|
+
updated_at: SqlValue;
|
|
528
|
+
/** CTC-269 ticket↔PR link. `head_ref` is the persisted source branch; `linear_issue_identifier`
|
|
529
|
+
* the ticket key parsed from the PR text; `linked_issue_state` that issue's live workflow state
|
|
530
|
+
* resolved from the mirror (null when the PR names no ticket OR the named issue isn't mirrored —
|
|
531
|
+
* never fabricated). The client renders the ticket chip + its state and deep-links to /tickets. */
|
|
532
|
+
head_ref: SqlValue;
|
|
533
|
+
linear_issue_identifier: SqlValue;
|
|
534
|
+
linked_issue_state: SqlValue;
|
|
535
|
+
checks: PullCheckView[];
|
|
536
|
+
reviews: PullReviewView[];
|
|
537
|
+
commit_statuses: PullStatusView[];
|
|
538
|
+
/** CTC-1912 — same as `PullView.blocked_on_ask`. Not existence-gated on the linked issue's row
|
|
539
|
+
* (unlike `linear_issue_identifier`/`linked_issue_state`): the edge itself is the evidence. */
|
|
540
|
+
blocked_on_ask: BlockedOnAskView | null;
|
|
541
|
+
}
|
|
542
|
+
/** A denormalized parent-initiative reference on a project row (via project_initiatives).
|
|
543
|
+
* `id` is always present (the edge exists); `name` resolves from the initiatives table and is
|
|
544
|
+
* null when that initiative isn't mirrored — never fabricated. */
|
|
545
|
+
export interface ProjectInitiativeRef {
|
|
546
|
+
id: string;
|
|
547
|
+
name: SqlValue;
|
|
548
|
+
}
|
|
549
|
+
/** One project row for the list view — snake_case mirror columns + resolved lead + parent initiatives.
|
|
550
|
+
* `target_date`/`start_date` are Linear TimelessDate "YYYY-MM-DD" strings AS-IS (NEVER a timestamp). */
|
|
551
|
+
export interface ProjectView {
|
|
552
|
+
id: string;
|
|
553
|
+
name: SqlValue;
|
|
554
|
+
state: SqlValue;
|
|
555
|
+
description: SqlValue;
|
|
556
|
+
/** The project lead identity (CTC-HIERARCHY): the users.id FK + resolved name/avatar (null when the
|
|
557
|
+
* user isn't mirrored — never fabricated). */
|
|
558
|
+
lead_id: SqlValue;
|
|
559
|
+
lead_name: SqlValue;
|
|
560
|
+
lead_avatar_url: SqlValue;
|
|
561
|
+
target_date: SqlValue;
|
|
562
|
+
start_date: SqlValue;
|
|
563
|
+
progress: SqlValue;
|
|
564
|
+
health: SqlValue;
|
|
565
|
+
icon: SqlValue;
|
|
566
|
+
color: SqlValue;
|
|
567
|
+
updated_at: SqlValue;
|
|
568
|
+
/** The initiatives this project belongs to (many:many via project_initiatives). [] when none. */
|
|
569
|
+
initiatives: ProjectInitiativeRef[];
|
|
570
|
+
}
|
|
571
|
+
/**
|
|
572
|
+
* How the consumer proves it may open `/connect`.
|
|
573
|
+
*
|
|
574
|
+
* • `token` — a service bearer token. The WHATWG WebSocket constructor
|
|
575
|
+
* cannot set an Authorization header, so the token rides the URL as `?token=`. The service
|
|
576
|
+
* constant-time compares it and STRIPS it before forwarding internally. Use this ONLY on a
|
|
577
|
+
* trusted backend — never the browser.
|
|
578
|
+
* • `cookie` — append NOTHING to the URL. The browser's same-origin session cookie rides the
|
|
579
|
+
* WebSocket upgrade automatically. This makes it impossible to leak a token from the browser path.
|
|
580
|
+
* • `bearer` — a person's OAuth access token, resolved by `getToken()` and awaited FRESH on every
|
|
581
|
+
* (re)connect and every /snapshot fetch (CTC-2111). The token is short-lived (a WorkOS user access
|
|
582
|
+
* token, ~15 min) and rotates underneath a running client, so unlike `token` it is NEVER captured
|
|
583
|
+
* once. Its resolved value rides `?token=` exactly as the `token` strategy's value does. Pair it
|
|
584
|
+
* with `onAuthError` + the `"auth-required"` status: a mirror close code 4401 means re-authenticate
|
|
585
|
+
* (revocation / inactivity), and the client STOPS reconnecting rather than hammering a dead token.
|
|
586
|
+
*/
|
|
587
|
+
export type AuthStrategy = {
|
|
588
|
+
kind: "token";
|
|
589
|
+
token: string;
|
|
590
|
+
} | {
|
|
591
|
+
kind: "cookie";
|
|
592
|
+
} | {
|
|
593
|
+
kind: "bearer";
|
|
594
|
+
getToken: () => Promise<string>;
|
|
595
|
+
};
|
|
596
|
+
/** The eleven stage slots `teams[].stages` is keyed by. Served as `slots` on the wire, in this order. */
|
|
597
|
+
export type WorkflowSlot = "dispatch" | "intake" | "research" | "plan" | "implement" | "remediate" | "verify" | "review" | "pr" | "done" | "canceled";
|
|
598
|
+
/** ⛔ `unscoped`, NOT `workspace` — the mirror's `labels.team_id` is advisory (NULL means either
|
|
599
|
+
* "genuinely workspace-scoped" or "not re-stamped yet"), so the document reports what was read. */
|
|
600
|
+
export type LabelScopeKind = "unscoped" | "team" | "absent";
|
|
601
|
+
export interface ContractLabel {
|
|
602
|
+
name: string;
|
|
603
|
+
unscopedId: string | null;
|
|
604
|
+
teamScopedId: string | null;
|
|
605
|
+
scope: LabelScopeKind;
|
|
606
|
+
/** The id `scope` resolves to (the unscoped copy wins); `null` when `scope` is `absent`. */
|
|
607
|
+
preferredId: string | null;
|
|
608
|
+
}
|
|
609
|
+
export interface ContractStage {
|
|
610
|
+
/** Linear's state id — THE join key. Address stages by slot, and states by this. */
|
|
611
|
+
stateId: string;
|
|
612
|
+
/** The state's CURRENT name, read live — never the mapping's write-time snapshot. */
|
|
613
|
+
name: string | null;
|
|
614
|
+
type: string | null;
|
|
615
|
+
/** False when the mapped id is not in the team's live stage set (archived, or re-minted). */
|
|
616
|
+
stateStillExists: boolean;
|
|
617
|
+
/** How the mapping was made: created | matched | chosen. */
|
|
618
|
+
source: string;
|
|
619
|
+
}
|
|
620
|
+
export interface ContractReadinessCheck {
|
|
621
|
+
id: string;
|
|
622
|
+
state: "pass" | "fail" | "unknown";
|
|
623
|
+
reason?: string;
|
|
624
|
+
count?: number;
|
|
625
|
+
}
|
|
626
|
+
export interface ContractTeam {
|
|
627
|
+
id: string;
|
|
628
|
+
key: string | null;
|
|
629
|
+
name: string | null;
|
|
630
|
+
workflowMode: string;
|
|
631
|
+
gitAutomation: string;
|
|
632
|
+
/** Keyed by slot. An UNMAPPED slot is ABSENT — never a placeholder. */
|
|
633
|
+
stages: Partial<Record<WorkflowSlot, ContractStage>>;
|
|
634
|
+
labels: {
|
|
635
|
+
ask: ContractLabel[];
|
|
636
|
+
hold: ContractLabel[];
|
|
637
|
+
release: ContractLabel[];
|
|
638
|
+
};
|
|
639
|
+
readiness: {
|
|
640
|
+
/** `unchecked` means no verdict has ever been computed — a real answer, not `blocked`. */
|
|
641
|
+
status: "ready" | "degraded" | "blocked" | "unchecked";
|
|
642
|
+
checks: ContractReadinessCheck[];
|
|
643
|
+
checkedAt: number | null;
|
|
644
|
+
expiresAt: number | null;
|
|
645
|
+
workflowRev: number | null;
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
export type ContractRoute = {
|
|
649
|
+
method: "GET" | "POST";
|
|
650
|
+
path: string;
|
|
651
|
+
/** Whether a call spends a unit of the per-host daily write budget. */
|
|
652
|
+
takesWriteBudgetUnit: boolean;
|
|
653
|
+
/** The contract version this route first appeared in. */
|
|
654
|
+
since: string;
|
|
655
|
+
} | {
|
|
656
|
+
method: "PUT";
|
|
657
|
+
path: "/api/v1/agent/tenant/review-agents/write";
|
|
658
|
+
takesWriteBudgetUnit: false;
|
|
659
|
+
since: "2.8.0";
|
|
660
|
+
idempotencyKeyField: null;
|
|
661
|
+
};
|
|
662
|
+
export interface ContractMergeRepository {
|
|
663
|
+
repoId: string;
|
|
664
|
+
owner: string;
|
|
665
|
+
name: string;
|
|
666
|
+
/** The EFFECTIVE policy after repo-wins-over-tenant precedence. */
|
|
667
|
+
policy: string;
|
|
668
|
+
policySource: "repo" | "tenant" | "default";
|
|
669
|
+
}
|
|
670
|
+
export interface ContractMergeCleanPassShape {
|
|
671
|
+
kind: string;
|
|
672
|
+
description: string;
|
|
673
|
+
/** FALSE means the cloud does NOT accept this shape today. */
|
|
674
|
+
honoured: boolean;
|
|
675
|
+
ticket: string | null;
|
|
676
|
+
}
|
|
677
|
+
export interface ContractAdvanceRule {
|
|
678
|
+
phase: string;
|
|
679
|
+
result: "ok" | "failed";
|
|
680
|
+
fromSlots: readonly WorkflowSlot[] | null;
|
|
681
|
+
toSlot: WorkflowSlot | null;
|
|
682
|
+
because: string;
|
|
683
|
+
}
|
|
684
|
+
export interface TenantContract {
|
|
685
|
+
/** Semver over the DOCUMENT'S SHAPE (MAJOR: meaning changed/removed; MINOR: additive). */
|
|
686
|
+
contractVersion: string;
|
|
687
|
+
protocolVersion: number;
|
|
688
|
+
/** The cloud states the freshness bounds; the CLIENT measures its own age against them. */
|
|
689
|
+
cache: {
|
|
690
|
+
maxAgeSeconds: number;
|
|
691
|
+
staleRefusalSeconds: number;
|
|
692
|
+
};
|
|
693
|
+
account: {
|
|
694
|
+
id: string;
|
|
695
|
+
slug: string;
|
|
696
|
+
name: string;
|
|
697
|
+
linearWorkspaceId: string | null;
|
|
698
|
+
linearWorkspaceSlug: string | null;
|
|
699
|
+
};
|
|
700
|
+
apiPrefix: string;
|
|
701
|
+
slots: readonly WorkflowSlot[];
|
|
702
|
+
routes: readonly ContractRoute[];
|
|
703
|
+
teams: ContractTeam[];
|
|
704
|
+
vocabulary: {
|
|
705
|
+
askLabelPrefix: string;
|
|
706
|
+
askPhaseLabelPrefix: string;
|
|
707
|
+
askMarkerLabel: string;
|
|
708
|
+
releaseLabel: string;
|
|
709
|
+
bookkeeping: {
|
|
710
|
+
marker: string;
|
|
711
|
+
prefixOnly: boolean;
|
|
712
|
+
asciiCaseInsensitive: boolean;
|
|
713
|
+
leadingWhitespaceTrimmed: boolean;
|
|
714
|
+
};
|
|
715
|
+
};
|
|
716
|
+
askTemplate: {
|
|
717
|
+
headings: {
|
|
718
|
+
ask: string;
|
|
719
|
+
options: string;
|
|
720
|
+
defaultIfSilent: string;
|
|
721
|
+
howToAnswer: string;
|
|
722
|
+
};
|
|
723
|
+
howToAnswer: {
|
|
724
|
+
withOptions: string;
|
|
725
|
+
withoutOptions: string;
|
|
726
|
+
};
|
|
727
|
+
optionBulletFormat: string;
|
|
728
|
+
maxLetteredOptions: number;
|
|
729
|
+
maxAskGenerations: number;
|
|
730
|
+
example: string;
|
|
731
|
+
};
|
|
732
|
+
ladder: {
|
|
733
|
+
phases: readonly string[];
|
|
734
|
+
keying: string;
|
|
735
|
+
keyingScope: string;
|
|
736
|
+
/** `phases` is the full vocabulary; this says whether `intake` actually runs. */
|
|
737
|
+
intakeEnabled: boolean;
|
|
738
|
+
advance: readonly ContractAdvanceRule[];
|
|
739
|
+
};
|
|
740
|
+
thresholds: {
|
|
741
|
+
scope: string;
|
|
742
|
+
parkAfterConsecutiveFailures: number;
|
|
743
|
+
remediateRoundCap: number;
|
|
744
|
+
remediateEscalatedRoundBudget: number;
|
|
745
|
+
remediateRewindBudget: number;
|
|
746
|
+
retryBackoffMs: readonly number[];
|
|
747
|
+
hostDailyWriteBudget: number;
|
|
748
|
+
};
|
|
749
|
+
merge: {
|
|
750
|
+
policies: readonly string[];
|
|
751
|
+
defaultPolicy: string;
|
|
752
|
+
repositories: readonly ContractMergeRepository[];
|
|
753
|
+
reviewerLogin: string;
|
|
754
|
+
cleanPassShapes: readonly ContractMergeCleanPassShape[];
|
|
755
|
+
cloudRemediateRequiredChecks: readonly string[];
|
|
756
|
+
prLabels: {
|
|
757
|
+
queueReady: string;
|
|
758
|
+
handStepsHold: string;
|
|
759
|
+
hold: string;
|
|
760
|
+
preview: string;
|
|
761
|
+
};
|
|
762
|
+
};
|
|
763
|
+
readinessChecks: readonly {
|
|
764
|
+
id: string;
|
|
765
|
+
severity: string;
|
|
766
|
+
needsAnswer: boolean;
|
|
767
|
+
}[];
|
|
768
|
+
humans: readonly {
|
|
769
|
+
linearUserId: string;
|
|
770
|
+
role: "owner" | "admin";
|
|
771
|
+
}[];
|
|
772
|
+
asks: {
|
|
773
|
+
/** `null` means NOT CONFIGURED, and a consumer must refuse rather than guess. */
|
|
774
|
+
approvalsTeamId: string | null;
|
|
775
|
+
};
|
|
776
|
+
}
|
|
5
777
|
/** Default per-request deadline — the bundle's `REQUEST_TIMEOUT_MS`. */
|
|
6
778
|
export declare const DEFAULT_TIMEOUT_MS = 15000;
|
|
7
779
|
/** One cached contract: the document, the validator it came with, and when it was fetched. */
|
|
8
780
|
export interface ContractCacheEntry {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
781
|
+
etag: string | null;
|
|
782
|
+
/** Epoch ms of the fetch (or the last successful 304 revalidation). */
|
|
783
|
+
fetchedAt: number;
|
|
784
|
+
contractVersion: string;
|
|
785
|
+
doc: TenantContract;
|
|
14
786
|
}
|
|
15
787
|
/** Where the contract cache lives. In-memory by default; the bundle supplies a disk-backed one. */
|
|
16
788
|
export interface ContractCacheStore {
|
|
17
|
-
|
|
18
|
-
|
|
789
|
+
get(): ContractCacheEntry | null | Promise<ContractCacheEntry | null>;
|
|
790
|
+
set(entry: ContractCacheEntry): void | Promise<void>;
|
|
19
791
|
}
|
|
20
792
|
/** The default store: one entry, held for the life of the client. */
|
|
21
793
|
export declare function memoryContractCache(): ContractCacheStore;
|
|
22
794
|
export interface TenantClientBaseOptions {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
795
|
+
/** The service origin (e.g. "https://staging.catalystcloud.dev"). A trailing slash is trimmed;
|
|
796
|
+
* every route path is absolute under it. */
|
|
797
|
+
baseUrl: string;
|
|
798
|
+
/** Injectable for tests; defaults to the platform global. */
|
|
799
|
+
fetch?: typeof fetch;
|
|
800
|
+
/** Per-request deadline in ms. Default {@link DEFAULT_TIMEOUT_MS}. */
|
|
801
|
+
timeoutMs?: number;
|
|
802
|
+
/** Default {@link memoryContractCache}. */
|
|
803
|
+
contractCache?: ContractCacheStore;
|
|
804
|
+
/** Epoch ms; injectable so a test can cross the cache bounds without sleeping. */
|
|
805
|
+
now?: () => number;
|
|
34
806
|
}
|
|
35
807
|
/**
|
|
36
808
|
* Exactly one credential. `key` is the CTC-2004 shape, kept working with identical wire behaviour;
|
|
@@ -40,14 +812,14 @@ export interface TenantClientBaseOptions {
|
|
|
40
812
|
* normalized to `{kind:"token", token:key}` at construction, so `send()` never sees two shapes.
|
|
41
813
|
*/
|
|
42
814
|
export type TenantClientOptions = TenantClientBaseOptions & ({
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
815
|
+
/** A tenant or personal key. Most agent proxy writes require an organization key;
|
|
816
|
+
* `teamWorkflow` and personal connection methods require the caller's own personal key or
|
|
817
|
+
* device login. */
|
|
818
|
+
key: string;
|
|
819
|
+
auth?: never;
|
|
48
820
|
} | {
|
|
49
|
-
|
|
50
|
-
|
|
821
|
+
auth: AuthStrategy;
|
|
822
|
+
key?: never;
|
|
51
823
|
});
|
|
52
824
|
/**
|
|
53
825
|
* The arms ANY call can end in. Every arm the server produced carries its `status`; `network` is the
|
|
@@ -56,295 +828,295 @@ export type TenantClientOptions = TenantClientBaseOptions & ({
|
|
|
56
828
|
export type TenantClientFailure =
|
|
57
829
|
/** 401 — the credential was not accepted. `ref` joins to the cloud's log line. */
|
|
58
830
|
{
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
831
|
+
outcome: "unauthorized";
|
|
832
|
+
status: 401;
|
|
833
|
+
reason: string;
|
|
834
|
+
ref: string | null;
|
|
63
835
|
}
|
|
64
836
|
/** 403 — wrong principal class, wrong tenant, or a missing scope (`required` names it). */
|
|
65
837
|
| {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
838
|
+
outcome: "forbidden";
|
|
839
|
+
status: 403;
|
|
840
|
+
reason: string;
|
|
841
|
+
required: string | null;
|
|
842
|
+
account: string | null;
|
|
71
843
|
}
|
|
72
844
|
/** 429 — the per-host daily write budget or the reserved Linear read floor. `retryAfterSeconds` is
|
|
73
845
|
* the server's own `Retry-After` when it sent one (the read floor does; the write budget does not). */
|
|
74
846
|
| {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
847
|
+
outcome: "rate-limited";
|
|
848
|
+
status: 429;
|
|
849
|
+
reason: string;
|
|
850
|
+
retryAfterSeconds: number | null;
|
|
79
851
|
}
|
|
80
852
|
/** A 400-class NAMED refusal — malformed input, a bad cursor, a tenant with no Linear workspace. */
|
|
81
853
|
| {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
854
|
+
outcome: "rejected";
|
|
855
|
+
status: number;
|
|
856
|
+
reason: string;
|
|
857
|
+
attempts?: number;
|
|
86
858
|
}
|
|
87
859
|
/** The server named a failure of its own (a 502 partial document, a 503 Linear client). */
|
|
88
860
|
| {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
861
|
+
outcome: "failed";
|
|
862
|
+
status: number;
|
|
863
|
+
reason: string;
|
|
92
864
|
}
|
|
93
865
|
/** fetch threw, or the deadline fired. */
|
|
94
866
|
| {
|
|
95
|
-
|
|
96
|
-
|
|
867
|
+
outcome: "network";
|
|
868
|
+
reason: string;
|
|
97
869
|
}
|
|
98
870
|
/** The body was not JSON, or not the shape this route is documented to answer. */
|
|
99
871
|
| {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
872
|
+
outcome: "shape";
|
|
873
|
+
status: number;
|
|
874
|
+
reason: string;
|
|
103
875
|
}
|
|
104
876
|
/** Any other non-2xx. */
|
|
105
877
|
| {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
878
|
+
outcome: "http";
|
|
879
|
+
status: number;
|
|
880
|
+
reason: string;
|
|
109
881
|
};
|
|
110
882
|
export type TeamWorkflowFailure = TenantClientFailure & {
|
|
111
|
-
|
|
883
|
+
error?: string;
|
|
112
884
|
};
|
|
113
885
|
export type TeamWorkflowResult<T> = ({
|
|
114
|
-
|
|
115
|
-
|
|
886
|
+
outcome: "ok";
|
|
887
|
+
status: number;
|
|
116
888
|
} & T) | TeamWorkflowFailure;
|
|
117
889
|
export interface TeamWorkflowReadiness {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
890
|
+
teamId: string;
|
|
891
|
+
teamKey: string;
|
|
892
|
+
teamName: string;
|
|
893
|
+
status: "ready" | "degraded" | "blocked" | "unchecked";
|
|
894
|
+
checkedAt?: number;
|
|
895
|
+
checks: readonly TeamReadinessCheck[];
|
|
896
|
+
workflowRev: number;
|
|
897
|
+
[field: string]: unknown;
|
|
126
898
|
}
|
|
127
899
|
export interface TeamReadinessCheck {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
900
|
+
id: string;
|
|
901
|
+
state: "pass" | "fail" | "unknown";
|
|
902
|
+
reason?: string;
|
|
903
|
+
count?: number;
|
|
132
904
|
}
|
|
133
905
|
export type TeamWorkflowSlot = "dispatch" | "intake" | "research" | "plan" | "implement" | "remediate" | "verify" | "review" | "pr" | "done" | "canceled";
|
|
134
906
|
export interface TeamWorkflowStage {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
907
|
+
id: string;
|
|
908
|
+
name: string;
|
|
909
|
+
type: string;
|
|
910
|
+
position: number;
|
|
139
911
|
}
|
|
140
912
|
export interface TeamWorkflowRow {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
913
|
+
slot: TeamWorkflowSlot;
|
|
914
|
+
linearStateId: string | null;
|
|
915
|
+
linearStateName?: string;
|
|
916
|
+
linearStateType?: string;
|
|
917
|
+
source?: "created" | "matched" | "chosen";
|
|
918
|
+
stateStillExists?: boolean;
|
|
147
919
|
}
|
|
148
920
|
export interface TeamWorkflowSummary extends TeamWorkflowReadiness {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
921
|
+
mode: "mapped-existing" | "adopted-recommended" | "mixed" | null;
|
|
922
|
+
gitAutomation: "off" | "managed";
|
|
923
|
+
mappedSlots: number;
|
|
924
|
+
mappedLoadBearingSlots: number;
|
|
925
|
+
mirrored: boolean | null;
|
|
154
926
|
}
|
|
155
927
|
export interface TeamList {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
928
|
+
teams: TeamWorkflowSummary[];
|
|
929
|
+
canManage: boolean;
|
|
930
|
+
liveTeamRead: {
|
|
931
|
+
attempted: boolean;
|
|
932
|
+
error: string | null;
|
|
933
|
+
reason?: "no-teams-known" | "unnamed-team";
|
|
934
|
+
};
|
|
935
|
+
everChecked: boolean;
|
|
936
|
+
mirrorRead: boolean;
|
|
165
937
|
}
|
|
166
938
|
export interface TeamWorkflowView {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
939
|
+
config: {
|
|
940
|
+
teamId: string;
|
|
941
|
+
mode: "mapped-existing" | "adopted-recommended" | "mixed";
|
|
942
|
+
gitAutomation: "off" | "managed";
|
|
943
|
+
workflowRev: number;
|
|
944
|
+
};
|
|
945
|
+
rows: readonly TeamWorkflowRow[];
|
|
946
|
+
stages: readonly TeamWorkflowStage[];
|
|
947
|
+
stageSource: "linear" | "mirror" | "none";
|
|
948
|
+
readiness: TeamWorkflowReadiness;
|
|
949
|
+
mappingHash: string;
|
|
950
|
+
checklist: string[] | null;
|
|
179
951
|
}
|
|
180
952
|
/** Save can commit the mapping even when its follow-up readiness recompute is unavailable. */
|
|
181
953
|
export type TeamWorkflowSaveResult = Omit<TeamWorkflowView, "mappingHash" | "checklist" | "readiness"> & {
|
|
182
|
-
|
|
954
|
+
readiness: TeamWorkflowReadiness | null;
|
|
183
955
|
};
|
|
184
956
|
export interface TeamMappingRowInput {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
957
|
+
slot: TeamWorkflowSlot;
|
|
958
|
+
linearStateId: string | null;
|
|
959
|
+
source?: "created" | "matched" | "chosen";
|
|
188
960
|
}
|
|
189
961
|
export interface TeamMappingSaveInput {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
962
|
+
team: string;
|
|
963
|
+
expectedMappingHash: string;
|
|
964
|
+
mode?: "mapped-existing" | "adopted-recommended" | "mixed";
|
|
965
|
+
gitAutomation?: "off" | "managed";
|
|
966
|
+
rows: TeamMappingRowInput[];
|
|
195
967
|
}
|
|
196
968
|
export interface TeamMigrationChoice {
|
|
197
|
-
|
|
198
|
-
|
|
969
|
+
sourceStateId: string;
|
|
970
|
+
destinationStateId: string;
|
|
199
971
|
}
|
|
200
972
|
export interface TeamMigrationSource {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
973
|
+
stateId: string;
|
|
974
|
+
name: string;
|
|
975
|
+
type: string;
|
|
976
|
+
ticketCount: number;
|
|
977
|
+
destinationStateId: string | null;
|
|
978
|
+
destinationSlot: TeamWorkflowSlot | null;
|
|
979
|
+
because: "unique-type" | "terminal-family" | "chosen" | null;
|
|
980
|
+
outcome: "ready" | "needs-a-choice" | "empty" | "moved" | "partially-moved" | "protected" | "retired-in-catalyst";
|
|
981
|
+
reason?: string;
|
|
982
|
+
retiredAt?: number;
|
|
211
983
|
}
|
|
212
984
|
export interface TeamMigrationPlan {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
985
|
+
teamId: string;
|
|
986
|
+
sources: readonly TeamMigrationSource[];
|
|
987
|
+
migrationHash: string;
|
|
988
|
+
overLimit: boolean;
|
|
989
|
+
issueCount: number;
|
|
990
|
+
retireLogReadable: boolean;
|
|
991
|
+
/** A false value means the server will refuse stage moves and retirement. Older servers omit it. */
|
|
992
|
+
actionsAvailable?: boolean;
|
|
221
993
|
}
|
|
222
994
|
export interface TeamAdoptResult {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
995
|
+
teamId: string;
|
|
996
|
+
teamKey: string;
|
|
997
|
+
mode: "adopted-recommended" | "mapped-existing" | "mixed" | null;
|
|
998
|
+
stages: readonly {
|
|
999
|
+
name: string;
|
|
1000
|
+
type: string;
|
|
1001
|
+
outcome: string;
|
|
1002
|
+
stateId?: string;
|
|
1003
|
+
reason?: string;
|
|
1004
|
+
}[];
|
|
1005
|
+
planHash: string;
|
|
1006
|
+
unfilledLoadBearing: readonly unknown[];
|
|
1007
|
+
provenanceGaps: readonly unknown[];
|
|
1008
|
+
labels: readonly {
|
|
1009
|
+
name: string;
|
|
1010
|
+
outcome: string;
|
|
1011
|
+
reason?: string;
|
|
1012
|
+
}[];
|
|
1013
|
+
labelProvenanceGaps: readonly unknown[];
|
|
1014
|
+
labelsNotCreated: readonly unknown[];
|
|
1015
|
+
checklist?: string[];
|
|
1016
|
+
readiness?: TeamWorkflowReadiness | null;
|
|
245
1017
|
}
|
|
246
1018
|
export interface TeamUndoPreview {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
1019
|
+
teamId: string;
|
|
1020
|
+
mode: "preview";
|
|
1021
|
+
candidates: readonly {
|
|
1022
|
+
stateId: string;
|
|
1023
|
+
name: string | null;
|
|
1024
|
+
}[];
|
|
1025
|
+
undoHash: string;
|
|
254
1026
|
}
|
|
255
1027
|
export interface TeamUndoResult {
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
1028
|
+
archived: readonly {
|
|
1029
|
+
stateId: string;
|
|
1030
|
+
name: string | null;
|
|
1031
|
+
}[];
|
|
1032
|
+
kept: readonly {
|
|
1033
|
+
stateId: string;
|
|
1034
|
+
name: string | null;
|
|
1035
|
+
reason: string;
|
|
1036
|
+
}[];
|
|
1037
|
+
failed: readonly {
|
|
1038
|
+
stateId: string;
|
|
1039
|
+
name: string | null;
|
|
1040
|
+
reason: string;
|
|
1041
|
+
}[];
|
|
1042
|
+
readiness: TeamWorkflowReadiness | null;
|
|
271
1043
|
}
|
|
272
1044
|
export interface TeamMigrationChunk {
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
1045
|
+
teamId: string;
|
|
1046
|
+
sources: readonly TeamMigrationSource[];
|
|
1047
|
+
remaining: number;
|
|
1048
|
+
migrationHash: string;
|
|
1049
|
+
moved: number;
|
|
1050
|
+
readiness: TeamWorkflowReadiness | null;
|
|
279
1051
|
}
|
|
280
1052
|
export interface TeamMigrationRetire {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
1053
|
+
retired: readonly {
|
|
1054
|
+
stateId: string;
|
|
1055
|
+
name: string;
|
|
1056
|
+
}[];
|
|
1057
|
+
kept: readonly {
|
|
1058
|
+
stateId: string;
|
|
1059
|
+
name: string;
|
|
1060
|
+
reason: string;
|
|
1061
|
+
}[];
|
|
1062
|
+
failed: readonly {
|
|
1063
|
+
stateId: string;
|
|
1064
|
+
name: string;
|
|
1065
|
+
reason: string;
|
|
1066
|
+
}[];
|
|
1067
|
+
logGaps: readonly {
|
|
1068
|
+
stateId: string;
|
|
1069
|
+
reason: string;
|
|
1070
|
+
}[];
|
|
1071
|
+
readiness: TeamWorkflowReadiness | null;
|
|
300
1072
|
}
|
|
301
1073
|
/** CTC-3780 — a project's WIP limit, from `GET|POST /api/v1/agent/team-wip-limit`. */
|
|
302
1074
|
export interface ProjectWipLimit {
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
1075
|
+
team: {
|
|
1076
|
+
id: string;
|
|
1077
|
+
key: string;
|
|
1078
|
+
};
|
|
1079
|
+
/** The limit that applies: the project's own, else the tenant flag's, else the default (12). */
|
|
1080
|
+
limit: number;
|
|
1081
|
+
source: "project" | "flag" | "default";
|
|
1082
|
+
/** The project's own stored value; null when it takes the flag or the default. */
|
|
1083
|
+
stored: number | null;
|
|
1084
|
+
/** The project's work in progress, counted as the WIP gate counts it. */
|
|
1085
|
+
inProgress: number;
|
|
1086
|
+
/** Epoch ms at which `inProgress` was counted. */
|
|
1087
|
+
countedAt: number;
|
|
316
1088
|
}
|
|
317
1089
|
export type ContractResult = {
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
1090
|
+
outcome: "ok";
|
|
1091
|
+
doc: TenantContract;
|
|
1092
|
+
/** `cache` — served inside `maxAgeSeconds` (or on a network failure inside
|
|
1093
|
+
* `staleRefusalSeconds`); `revalidated` — a 304; `network` — a fresh 200. */
|
|
1094
|
+
source: "cache" | "network" | "revalidated";
|
|
1095
|
+
ageSeconds: number;
|
|
1096
|
+
etag: string | null;
|
|
1097
|
+
contractVersion: string;
|
|
326
1098
|
}
|
|
327
1099
|
/** Past `staleRefusalSeconds` and the refresh failed: refused by name, with the failure that
|
|
328
1100
|
* prevented revalidation. No document is served — there is no literal to fall back to. */
|
|
329
1101
|
| {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
1102
|
+
outcome: "stale";
|
|
1103
|
+
ageSeconds: number;
|
|
1104
|
+
staleRefusalSeconds: number;
|
|
1105
|
+
cause: TenantClientFailure;
|
|
334
1106
|
}
|
|
335
1107
|
/** `offline: true` with nothing cached. */
|
|
336
1108
|
| {
|
|
337
|
-
|
|
338
|
-
|
|
1109
|
+
outcome: "missing";
|
|
1110
|
+
reason: string;
|
|
339
1111
|
} | TenantClientFailure;
|
|
340
1112
|
export type ContractFailure = Exclude<ContractResult, {
|
|
341
|
-
|
|
1113
|
+
outcome: "ok";
|
|
342
1114
|
}>;
|
|
343
1115
|
export interface ContractOptions {
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
1116
|
+
/** Revalidate even inside `maxAgeSeconds`. */
|
|
1117
|
+
refresh?: boolean;
|
|
1118
|
+
/** Never touch the network: the cache if any, else `missing`. */
|
|
1119
|
+
offline?: boolean;
|
|
348
1120
|
}
|
|
349
1121
|
declare const PAGE_CURSOR: unique symbol;
|
|
350
1122
|
/**
|
|
@@ -353,87 +1125,115 @@ declare const PAGE_CURSOR: unique symbol;
|
|
|
353
1125
|
* is not a page position.
|
|
354
1126
|
*/
|
|
355
1127
|
export type PageCursor = string & {
|
|
356
|
-
|
|
1128
|
+
readonly [PAGE_CURSOR]: true;
|
|
357
1129
|
};
|
|
358
1130
|
/** Admit a persisted token as a {@link PageCursor}. */
|
|
359
1131
|
export declare function pageCursor(token: string): PageCursor;
|
|
360
1132
|
/** `GET /api/v1/issues` — the facets `MirrorDO.handleIssues` reads, by their WIRE names' meaning. */
|
|
361
1133
|
export interface IssueListParams {
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
1134
|
+
after?: PageCursor;
|
|
1135
|
+
limit?: number;
|
|
1136
|
+
teamId?: string;
|
|
1137
|
+
teamKey?: string;
|
|
1138
|
+
/** An unrecognised value is no narrowing server-side, never a 400 — so the type is closed here. */
|
|
1139
|
+
state?: "active" | "backlog" | "done";
|
|
1140
|
+
/** CTC-4556 — one Linear state by name (`state_name`), any case. A name no issue carries is an
|
|
1141
|
+
* empty page, unlike an unknown `state` bucket. Needs a mirror that lists it in `scope`. */
|
|
1142
|
+
stateName?: string;
|
|
1143
|
+
/** Linear's 1..4. */
|
|
1144
|
+
priority?: 1 | 2 | 3 | 4;
|
|
1145
|
+
assigneeId?: string;
|
|
1146
|
+
assignee?: string;
|
|
1147
|
+
cycle?: string;
|
|
1148
|
+
blocked?: "ask";
|
|
1149
|
+
/** `"priority"` orders the FULL scoped set by urgency; absent keeps `updated_at DESC`. */
|
|
1150
|
+
sort?: "priority";
|
|
1151
|
+
waitingOn?: string;
|
|
1152
|
+
waitingMode?: "asks" | "all";
|
|
378
1153
|
}
|
|
379
1154
|
export interface KeysetPageMeta {
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
1155
|
+
/** The next page's `after`, or `null` on the last page (the header is absent there). */
|
|
1156
|
+
nextCursor: PageCursor | null;
|
|
1157
|
+
/** The scope's FULL row count (`X-Mirror-Total`), `null` when the server did not send it. */
|
|
1158
|
+
total: number | null;
|
|
1159
|
+
/** The feed head seq this page is consistent with (`X-Mirror-Cursor`). */
|
|
1160
|
+
head: number | null;
|
|
386
1161
|
}
|
|
387
1162
|
export type IssueListResult = ({
|
|
388
|
-
|
|
389
|
-
|
|
1163
|
+
outcome: "ok";
|
|
1164
|
+
rows: IssueView[];
|
|
1165
|
+
/** CTC-4556 — the params that narrowed this page (`X-Mirror-Scope`), or `null` when the mirror
|
|
1166
|
+
* did not say. A filter you sent that is missing here was NOT applied: the rows are wider. */
|
|
1167
|
+
scope: string[] | null;
|
|
390
1168
|
} & KeysetPageMeta) | TenantClientFailure;
|
|
1169
|
+
/** CTC-1889 — the ticket's repair loop: remediate rounds, validate attempts, an active hold and the
|
|
1170
|
+
* parks that apply. The server's detail handler adds it after the view builder, and only when there
|
|
1171
|
+
* is something to show, so it is absent rather than null on a quiet ticket. Mirrors the cloud's
|
|
1172
|
+
* `TicketRepairLoopWire` (apps/mirror/src/do/ticket-repair-loop.ts). */
|
|
1173
|
+
export interface TicketRepairLoop {
|
|
1174
|
+
remediateRounds: number;
|
|
1175
|
+
validateAttempts: number;
|
|
1176
|
+
hold: {
|
|
1177
|
+
reason: string;
|
|
1178
|
+
headSha: string | null;
|
|
1179
|
+
heldAtMs: number;
|
|
1180
|
+
} | null;
|
|
1181
|
+
parks: ReadonlyArray<{
|
|
1182
|
+
phase: string;
|
|
1183
|
+
sentinel: string;
|
|
1184
|
+
label: string;
|
|
1185
|
+
}>;
|
|
1186
|
+
}
|
|
1187
|
+
/** `GET /api/v1/issues/:identifier`: the read-model's detail view plus the server-added fields. */
|
|
1188
|
+
export type IssueDetail = IssueDetailView & {
|
|
1189
|
+
repairLoop?: TicketRepairLoop;
|
|
1190
|
+
};
|
|
391
1191
|
export type IssueGetResult = {
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
1192
|
+
outcome: "ok";
|
|
1193
|
+
issue: IssueDetail;
|
|
1194
|
+
head: number | null;
|
|
395
1195
|
} | {
|
|
396
|
-
|
|
397
|
-
|
|
1196
|
+
outcome: "not-found";
|
|
1197
|
+
status: 404;
|
|
398
1198
|
} | TenantClientFailure;
|
|
399
1199
|
export interface PullListParams {
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
1200
|
+
after?: PageCursor;
|
|
1201
|
+
limit?: number;
|
|
1202
|
+
/** `owner/name`. */
|
|
1203
|
+
repo?: string;
|
|
1204
|
+
state?: "open" | "merged";
|
|
1205
|
+
blocked?: "ask";
|
|
406
1206
|
}
|
|
407
1207
|
export type PullListResult = ({
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
1208
|
+
outcome: "ok";
|
|
1209
|
+
rows: PullView[];
|
|
1210
|
+
blockedOnAskTotal: number | null;
|
|
411
1211
|
} & KeysetPageMeta) | TenantClientFailure;
|
|
412
1212
|
export type PullGetResult = {
|
|
413
|
-
|
|
414
|
-
|
|
1213
|
+
outcome: "ok";
|
|
1214
|
+
pull: PullDetailView;
|
|
415
1215
|
} | {
|
|
416
|
-
|
|
417
|
-
|
|
1216
|
+
outcome: "not-found";
|
|
1217
|
+
status: 404;
|
|
418
1218
|
} | TenantClientFailure;
|
|
419
1219
|
/** `GET /api/v1/projects` is offset-paged (the legacy `?cursor=` carries the offset). */
|
|
420
1220
|
export interface ProjectListParams {
|
|
421
|
-
|
|
422
|
-
|
|
1221
|
+
limit?: number;
|
|
1222
|
+
offset?: number;
|
|
423
1223
|
}
|
|
424
1224
|
export type ProjectListResult = {
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
1225
|
+
outcome: "ok";
|
|
1226
|
+
rows: ProjectView[];
|
|
1227
|
+
head: number | null;
|
|
428
1228
|
} | TenantClientFailure;
|
|
429
1229
|
/** `GET /api/v1/me` — how a key learns its own tenant. */
|
|
430
1230
|
export type MeResult = {
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
1231
|
+
outcome: "ok";
|
|
1232
|
+
account: string;
|
|
1233
|
+
slug: string;
|
|
1234
|
+
name: string;
|
|
1235
|
+
permissions: string[] | null;
|
|
1236
|
+
principal: "service" | "session";
|
|
437
1237
|
} | TenantClientFailure;
|
|
438
1238
|
/** `GET /api/v1/issues/:id/execution` — the ticket's own execution/telemetry report. ⛔ The fields
|
|
439
1239
|
* below are DOCUMENTED, not compiled: this repo does not depend on `catalyst-cloud`. The index
|
|
@@ -441,203 +1241,327 @@ export type MeResult = {
|
|
|
441
1241
|
* be refused as a shape error. The tenant decides which principals may read this route (Decision 5);
|
|
442
1242
|
* the client reports whatever it answers. */
|
|
443
1243
|
export interface TicketExecutionReport {
|
|
444
|
-
|
|
445
|
-
|
|
1244
|
+
/** The ticket the report is about. The server always sends it, under any selection. */
|
|
1245
|
+
readonly ticket: string;
|
|
1246
|
+
/** ⚠️ Never sent by the server; kept so code that read it still compiles. Read `ticket`. */
|
|
1247
|
+
readonly identifier?: string;
|
|
1248
|
+
readonly [key: string]: unknown;
|
|
446
1249
|
}
|
|
447
1250
|
export type TicketExecutionResult = {
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
1251
|
+
outcome: "ok";
|
|
1252
|
+
status: 200;
|
|
1253
|
+
report: TicketExecutionReport;
|
|
451
1254
|
} | {
|
|
452
|
-
|
|
453
|
-
|
|
1255
|
+
outcome: "not-found";
|
|
1256
|
+
status: 404;
|
|
1257
|
+
} | TenantClientFailure;
|
|
1258
|
+
/** The projections `GET /api/v1/issues/:identifier` names. */
|
|
1259
|
+
export type IssueGetProjection = "status" | "brief";
|
|
1260
|
+
/** The projections `GET /api/v1/issues/:identifier/execution` names. */
|
|
1261
|
+
export type IssueExecutionProjection = "status" | "failures";
|
|
1262
|
+
export interface ReadSelectOptions<P extends string> {
|
|
1263
|
+
/** Top-level keys only, sent joined by commas. An empty list is the server's `invalid_field`. */
|
|
1264
|
+
fields?: readonly string[];
|
|
1265
|
+
projection?: P;
|
|
1266
|
+
}
|
|
1267
|
+
export type IssueGetOptions = ReadSelectOptions<IssueGetProjection>;
|
|
1268
|
+
export type IssueExecutionOptions = ReadSelectOptions<IssueExecutionProjection>;
|
|
1269
|
+
/** The server refused a selection. `name` is the field or projection it did not know (`"fields"`
|
|
1270
|
+
* for an empty list); `allowed` is its catalog, so a caller can retry with a name that exists. */
|
|
1271
|
+
export interface ReadSelectRefusal {
|
|
1272
|
+
outcome: "select-refused";
|
|
1273
|
+
status: 400;
|
|
1274
|
+
error: "unknown_field" | "unknown_projection" | "invalid_field";
|
|
1275
|
+
name: string | null;
|
|
1276
|
+
allowed: readonly string[];
|
|
1277
|
+
reason: string;
|
|
1278
|
+
}
|
|
1279
|
+
/** A selected issue: any key may be absent, and the server always keeps `identifier`. */
|
|
1280
|
+
export type ProjectedIssueDetail = {
|
|
1281
|
+
[K in keyof IssueDetail as K extends "identifier" ? never : K]?: IssueDetail[K];
|
|
1282
|
+
} & {
|
|
1283
|
+
identifier: IssueDetail["identifier"];
|
|
1284
|
+
} extends infer T ? {
|
|
1285
|
+
[K in keyof T]: T[K];
|
|
1286
|
+
} : never;
|
|
1287
|
+
export type IssueGetProjectedResult = {
|
|
1288
|
+
outcome: "ok";
|
|
1289
|
+
issue: ProjectedIssueDetail;
|
|
1290
|
+
head: number | null;
|
|
1291
|
+
} | {
|
|
1292
|
+
outcome: "not-found";
|
|
1293
|
+
status: 404;
|
|
1294
|
+
} | ReadSelectRefusal | TenantClientFailure;
|
|
1295
|
+
/** The report type is already open, so a selected report needs no narrower type. */
|
|
1296
|
+
export type TicketExecutionProjectedResult = TicketExecutionResult | ReadSelectRefusal;
|
|
1297
|
+
export interface EventQueryParams {
|
|
1298
|
+
ticket?: string;
|
|
1299
|
+
/** One durable event type, or several (each sent as its own `type=`). */
|
|
1300
|
+
type?: string | readonly string[];
|
|
1301
|
+
/** The server's default is 50, its maximum 200. */
|
|
1302
|
+
limit?: number;
|
|
1303
|
+
/** The server's default is `desc`, newest first. */
|
|
1304
|
+
order?: "asc" | "desc";
|
|
1305
|
+
afterSeq?: number;
|
|
1306
|
+
beforeSeq?: number;
|
|
1307
|
+
}
|
|
1308
|
+
/** Where the next page starts: the param to set and its value. `null` on the last page. */
|
|
1309
|
+
export interface EventQueryCursor {
|
|
1310
|
+
param: "afterSeq" | "beforeSeq";
|
|
1311
|
+
value: number;
|
|
1312
|
+
}
|
|
1313
|
+
/** The sequence range the cloud's index holds. An event outside it is not missing, only unindexed. */
|
|
1314
|
+
export interface EventIndexCoverage {
|
|
1315
|
+
indexedFromSeq: number | null;
|
|
1316
|
+
indexedToSeq: number | null;
|
|
1317
|
+
}
|
|
1318
|
+
/** One backbone event, verbatim: the same object a `/events/backbone` line carries. ⛔ DOCUMENTED,
|
|
1319
|
+
* not compiled beyond `sequence` and `type`, so a field the cloud adds reaches the caller. */
|
|
1320
|
+
export interface QueriedBackboneEvent {
|
|
1321
|
+
readonly sequence: number;
|
|
1322
|
+
readonly type: string;
|
|
1323
|
+
readonly bodyUnavailable?: undefined;
|
|
1324
|
+
readonly [key: string]: unknown;
|
|
1325
|
+
}
|
|
1326
|
+
/** An event whose body the cloud never archived (its archive write was given up on). The page
|
|
1327
|
+
* serves this stub in the event's place, never a gap, so a cursor never skips it unseen. */
|
|
1328
|
+
export interface QueriedEventStub {
|
|
1329
|
+
readonly tenantId: string;
|
|
1330
|
+
readonly sequence: number;
|
|
1331
|
+
readonly eventId: string;
|
|
1332
|
+
readonly type: string;
|
|
1333
|
+
readonly ticket?: string;
|
|
1334
|
+
readonly bodyUnavailable: true;
|
|
1335
|
+
/** `archive_write_failed` today. */
|
|
1336
|
+
readonly reason: string;
|
|
1337
|
+
}
|
|
1338
|
+
/** Branch on `bodyUnavailable === true`. */
|
|
1339
|
+
export type QueriedEvent = QueriedBackboneEvent | QueriedEventStub;
|
|
1340
|
+
export type EventQueryResult = {
|
|
1341
|
+
outcome: "ok";
|
|
1342
|
+
status: 200;
|
|
1343
|
+
events: QueriedEvent[];
|
|
1344
|
+
next: EventQueryCursor | null;
|
|
1345
|
+
coverage: EventIndexCoverage;
|
|
1346
|
+
}
|
|
1347
|
+
/** The server has no event query route (404). Fall back; this is not "no events". */
|
|
1348
|
+
| {
|
|
1349
|
+
outcome: "unsupported";
|
|
1350
|
+
status: 404;
|
|
1351
|
+
reason: string;
|
|
1352
|
+
}
|
|
1353
|
+
/** One event is larger than a whole page (413). `next` resumes past it; `pages()` does so itself. */
|
|
1354
|
+
| {
|
|
1355
|
+
outcome: "too-large";
|
|
1356
|
+
status: 413;
|
|
1357
|
+
sequence: number;
|
|
1358
|
+
maxBytes: number;
|
|
1359
|
+
eventBytes: number;
|
|
1360
|
+
next: EventQueryCursor;
|
|
1361
|
+
}
|
|
1362
|
+
/** The index or archive could not be read (503). Retry the same request: `resume` is the cursor
|
|
1363
|
+
* this request sent (null on a first page), and `sequence` names the event whose read failed. */
|
|
1364
|
+
| {
|
|
1365
|
+
outcome: "unavailable";
|
|
1366
|
+
status: 503;
|
|
1367
|
+
error: "archive_read_failed" | "index_unavailable" | "archive_unavailable";
|
|
1368
|
+
retryable: true;
|
|
1369
|
+
sequence: number | null;
|
|
1370
|
+
resume: EventQueryCursor | null;
|
|
1371
|
+
}
|
|
1372
|
+
/** A `type` outside the cloud's durable registry (400). `types` lists every one it refused. */
|
|
1373
|
+
| {
|
|
1374
|
+
outcome: "event-type-refused";
|
|
1375
|
+
status: 400;
|
|
1376
|
+
types: string[];
|
|
1377
|
+
reason: string;
|
|
454
1378
|
} | TenantClientFailure;
|
|
455
1379
|
/** `GET /api/v1/work-eligibility?team=` — the dispatcher's own "why is nothing moving" answer.
|
|
456
1380
|
* ⛔ DOCUMENTED, not compiled — see the section note above. */
|
|
457
1381
|
export interface WorkEligibilityReport {
|
|
458
|
-
|
|
459
|
-
|
|
1382
|
+
readonly team?: string;
|
|
1383
|
+
readonly [key: string]: unknown;
|
|
460
1384
|
}
|
|
461
1385
|
export type WorkEligibilityResult = {
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
1386
|
+
outcome: "ok";
|
|
1387
|
+
status: 200;
|
|
1388
|
+
report: WorkEligibilityReport;
|
|
465
1389
|
} | TenantClientFailure;
|
|
466
1390
|
/** `GET /api/v1/dispatch-queue/current?team=` — an ENVELOPE (`entries[]`), not a bare array. */
|
|
467
1391
|
export interface DispatchQueueEnvelope {
|
|
468
|
-
|
|
469
|
-
|
|
1392
|
+
readonly entries: readonly Record<string, unknown>[];
|
|
1393
|
+
readonly [key: string]: unknown;
|
|
470
1394
|
}
|
|
471
1395
|
export type DispatchQueueResult = {
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
1396
|
+
outcome: "ok";
|
|
1397
|
+
status: 200;
|
|
1398
|
+
queue: DispatchQueueEnvelope;
|
|
475
1399
|
} | TenantClientFailure;
|
|
476
1400
|
/** `GET /api/v1/fleet-activity/current` — a bare array of activity rows. */
|
|
477
1401
|
export type FleetActivityResult = {
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
1402
|
+
outcome: "ok";
|
|
1403
|
+
status: 200;
|
|
1404
|
+
rows: readonly Record<string, unknown>[];
|
|
481
1405
|
} | TenantClientFailure;
|
|
482
1406
|
/** `GET /api/v1/agent-roster/current` — a bare array of roster rows. */
|
|
483
1407
|
export type AgentRosterResult = {
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
1408
|
+
outcome: "ok";
|
|
1409
|
+
status: 200;
|
|
1410
|
+
rows: readonly Record<string, unknown>[];
|
|
487
1411
|
} | TenantClientFailure;
|
|
488
1412
|
/** `GET /api/v1/lease/attributions?ticket=&phase=` — both params are required (the route 400s when
|
|
489
1413
|
* either is absent); the body shape is tolerant (object or array). */
|
|
490
1414
|
export type LeaseAttributionsResult = {
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
1415
|
+
outcome: "ok";
|
|
1416
|
+
status: 200;
|
|
1417
|
+
body: unknown;
|
|
494
1418
|
} | TenantClientFailure;
|
|
495
1419
|
/** `GET /api/v1/coding-accounts` — an OBJECT carrying an `accounts` array. */
|
|
496
1420
|
export interface CodingAccountsReport {
|
|
497
|
-
|
|
498
|
-
|
|
1421
|
+
readonly accounts: readonly Record<string, unknown>[];
|
|
1422
|
+
readonly [key: string]: unknown;
|
|
499
1423
|
}
|
|
500
1424
|
export type CodingAccountsResult = {
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
1425
|
+
outcome: "ok";
|
|
1426
|
+
status: 200;
|
|
1427
|
+
report: CodingAccountsReport;
|
|
504
1428
|
} | TenantClientFailure;
|
|
505
1429
|
/** `GET /api/v1/cycles` — the SDK's own structural copy; see the section note above. */
|
|
506
1430
|
export interface CycleRow {
|
|
507
|
-
|
|
508
|
-
|
|
1431
|
+
readonly id?: string;
|
|
1432
|
+
readonly [key: string]: unknown;
|
|
509
1433
|
}
|
|
510
1434
|
export type CyclesListResult = {
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
1435
|
+
outcome: "ok";
|
|
1436
|
+
status: 200;
|
|
1437
|
+
rows: readonly CycleRow[];
|
|
514
1438
|
} | TenantClientFailure;
|
|
515
1439
|
/** `GET /api/v1/search?q=&limit=` — four result buckets, the SDK's own structural copy. */
|
|
516
1440
|
export interface SearchParams {
|
|
517
|
-
|
|
518
|
-
|
|
1441
|
+
q: string;
|
|
1442
|
+
limit?: number;
|
|
519
1443
|
}
|
|
520
1444
|
export interface SearchResults {
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
1445
|
+
readonly issues?: readonly Record<string, unknown>[];
|
|
1446
|
+
readonly pulls?: readonly Record<string, unknown>[];
|
|
1447
|
+
readonly projects?: readonly Record<string, unknown>[];
|
|
1448
|
+
readonly initiatives?: readonly Record<string, unknown>[];
|
|
1449
|
+
readonly [key: string]: unknown;
|
|
526
1450
|
}
|
|
527
1451
|
export type SearchResult = {
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
1452
|
+
outcome: "ok";
|
|
1453
|
+
status: 200;
|
|
1454
|
+
results: SearchResults;
|
|
531
1455
|
} | TenantClientFailure;
|
|
532
1456
|
/** `GET /api/v1/workflow-stages` — the key-authenticated twin; its shape is never quoted anywhere in
|
|
533
1457
|
* the pool (Decision 7). Tolerant: accepts `{stages, source?}` OR a bare array, normalizing both. */
|
|
534
1458
|
export type WorkflowStagesResult = {
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
1459
|
+
outcome: "ok";
|
|
1460
|
+
status: 200;
|
|
1461
|
+
stages: readonly Record<string, unknown>[];
|
|
1462
|
+
source: string | null;
|
|
539
1463
|
} | TenantClientFailure;
|
|
540
1464
|
/** `GET /api/v1/changes?since=` — the NDJSON change feed. */
|
|
541
1465
|
export type ChangesStreamResult = {
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
1466
|
+
outcome: "ok";
|
|
1467
|
+
status: 200;
|
|
1468
|
+
head: number | null;
|
|
1469
|
+
rows: AsyncGenerator<Record<string, unknown>>;
|
|
1470
|
+
/**
|
|
1471
|
+
* Release the feed WITHOUT draining it — cancels the response body and stops the idle deadline.
|
|
1472
|
+
* Idempotent, and safe to call after the rows have been read.
|
|
1473
|
+
*
|
|
1474
|
+
* ⛔ A caller that reads `head` and then DISCARDS `rows` must call this (CTC-2132 validate
|
|
1475
|
+
* attempt 14, code-review Finding 3): nothing else runs the generator's cleanup, so the body
|
|
1476
|
+
* would stay uncancelled and its connection referenced. Draining the rows to EOF, or `break`ing
|
|
1477
|
+
* out of the `for await`, already releases both — `close()` is for the caller that never starts.
|
|
1478
|
+
*/
|
|
1479
|
+
close: () => Promise<void>;
|
|
556
1480
|
}
|
|
557
1481
|
/** The tenant can no longer replay from `since` — reseed from `snapshot.head()` (or a full
|
|
558
1482
|
* snapshot) rather than replaying the gap. `head` is the tenant's current head. */
|
|
559
1483
|
| {
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
1484
|
+
outcome: "resync";
|
|
1485
|
+
status: 409;
|
|
1486
|
+
head: number | null;
|
|
1487
|
+
reason: string;
|
|
564
1488
|
} | TenantClientFailure;
|
|
565
1489
|
export type ChangesListResult = {
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
1490
|
+
outcome: "ok";
|
|
1491
|
+
status: 200;
|
|
1492
|
+
head: number | null;
|
|
1493
|
+
rows: Record<string, unknown>[];
|
|
570
1494
|
} | Extract<ChangesStreamResult, {
|
|
571
|
-
|
|
1495
|
+
outcome: "resync";
|
|
572
1496
|
}> | TenantClientFailure;
|
|
573
1497
|
/** `GET /api/v1/snapshot?head=1` — the cheap head probe (Decision 7): the shape was inferred from
|
|
574
1498
|
* prose, not a quoted response body, so the parse is deliberately tolerant of three answer shapes. */
|
|
575
1499
|
export type SnapshotHeadResult = {
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
1500
|
+
outcome: "ok";
|
|
1501
|
+
status: 200;
|
|
1502
|
+
head: number;
|
|
1503
|
+
source: "header" | "body";
|
|
580
1504
|
} | {
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
1505
|
+
outcome: "shape";
|
|
1506
|
+
status: number;
|
|
1507
|
+
reason: string;
|
|
584
1508
|
} | TenantClientFailure;
|
|
585
1509
|
export interface RawRequest {
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
1510
|
+
method?: "GET" | "POST";
|
|
1511
|
+
/** ⛔ An ABSOLUTE PATH under the client's own origin ("/api/v1/…"), never a URL. A value that is
|
|
1512
|
+
* not one is refused BEFORE the request is built: `new URL(origin + path)` does not throw on
|
|
1513
|
+
* "@host/x" — it reads the origin as USERINFO and `host` becomes the attacker's, which would send
|
|
1514
|
+
* this client's bearer credential to that host (reproduced: `new URL("https://cloud.example" +
|
|
1515
|
+
* "@evil.example/steal")` → host `evil.example`, no throw). CTC-2132. */
|
|
1516
|
+
path: string;
|
|
1517
|
+
query?: Record<string, string | number | undefined>;
|
|
1518
|
+
headers?: Record<string, string>;
|
|
1519
|
+
body?: unknown;
|
|
596
1520
|
}
|
|
597
1521
|
export type RawRequestResult = {
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
1522
|
+
outcome: "ok";
|
|
1523
|
+
status: number;
|
|
1524
|
+
json: unknown;
|
|
1525
|
+
headers: Headers;
|
|
602
1526
|
} | TenantClientFailure;
|
|
603
1527
|
/** Personal provider consent is initiated by a user credential, never an account key. */
|
|
604
1528
|
export type PersonalConnectionProvider = "linear" | "github";
|
|
605
1529
|
export type PersonalConnectionStartResult = {
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
1530
|
+
outcome: "ok";
|
|
1531
|
+
status: 200;
|
|
1532
|
+
authorizationUrl: string;
|
|
1533
|
+
expiresAt: number;
|
|
610
1534
|
} | {
|
|
611
|
-
|
|
612
|
-
|
|
1535
|
+
outcome: "workspace-required";
|
|
1536
|
+
status: 409;
|
|
613
1537
|
} | TenantClientFailure;
|
|
614
1538
|
export type PersonalConnectionStatusResult = {
|
|
615
|
-
|
|
616
|
-
|
|
1539
|
+
outcome: "absent";
|
|
1540
|
+
status: 200;
|
|
617
1541
|
} | {
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
1542
|
+
outcome: "lapsed";
|
|
1543
|
+
status: 200;
|
|
1544
|
+
lapsedAt: number | null;
|
|
621
1545
|
} | {
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
1546
|
+
outcome: "connected";
|
|
1547
|
+
status: 200;
|
|
1548
|
+
provider: "linear";
|
|
1549
|
+
linearUserId: string;
|
|
1550
|
+
grantedScope: string | null;
|
|
1551
|
+
updatedAt: number;
|
|
1552
|
+
expiresAt: number | null;
|
|
629
1553
|
} | {
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
1554
|
+
outcome: "connected";
|
|
1555
|
+
status: 200;
|
|
1556
|
+
provider: "github";
|
|
1557
|
+
githubUserId: string;
|
|
1558
|
+
githubLogin: string;
|
|
1559
|
+
updatedAt: number;
|
|
1560
|
+
expiresAt: number | null;
|
|
637
1561
|
} | {
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
1562
|
+
outcome: "unavailable";
|
|
1563
|
+
status: 503;
|
|
1564
|
+
provider: PersonalConnectionProvider;
|
|
641
1565
|
} | TenantClientFailure;
|
|
642
1566
|
/** The routes this client wraps, by the last segment of their contract path. `delegate` (operator
|
|
643
1567
|
* credential only) and `linear/read` (an origin-fresh read the replica rule discourages) are
|
|
@@ -645,311 +1569,328 @@ export type PersonalConnectionStatusResult = {
|
|
|
645
1569
|
export type AgentRouteName = "issue-state" | "issue-label" | "issue-comment" | "issue-create" | "reaction" | "attachment" | "attachments" | "session" | "ask" | "ask-accept" | "project-repositories" | "project-repositories/remove" | "portal-servers" | "portal-servers/register" | "portal-servers/remove" | "messages";
|
|
646
1570
|
/** The runtime twin of {@link AgentRouteName}: the list a test can walk against the contract.
|
|
647
1571
|
* Kept in lockstep with the union by a compile-time equality in test/tenant-client-agent.test.ts. */
|
|
648
|
-
export declare const AGENT_ROUTE_NAMES: readonly [
|
|
1572
|
+
export declare const AGENT_ROUTE_NAMES: readonly [
|
|
1573
|
+
"issue-state",
|
|
1574
|
+
"issue-label",
|
|
1575
|
+
"issue-comment",
|
|
1576
|
+
"issue-create",
|
|
1577
|
+
"reaction",
|
|
1578
|
+
"attachment",
|
|
1579
|
+
"attachments",
|
|
1580
|
+
"session",
|
|
1581
|
+
"ask",
|
|
1582
|
+
"ask-accept",
|
|
1583
|
+
"project-repositories",
|
|
1584
|
+
"project-repositories/remove",
|
|
1585
|
+
"portal-servers",
|
|
1586
|
+
"portal-servers/register",
|
|
1587
|
+
"portal-servers/remove",
|
|
1588
|
+
"messages"
|
|
1589
|
+
];
|
|
649
1590
|
/** The arms every agent call can end in BEFORE the route answers: the contract could not be served,
|
|
650
1591
|
* or the tenant's contract does not list the route. */
|
|
651
1592
|
export type AgentCallFailure = ContractFailure | {
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
1593
|
+
outcome: "route-unknown";
|
|
1594
|
+
route: AgentRouteName;
|
|
1595
|
+
routes: string[];
|
|
655
1596
|
} | TenantClientFailure;
|
|
656
1597
|
/** The proxy's shared write outcome (`ProxiedWriteResult`), on the wire with the HTTP status. The
|
|
657
1598
|
* `rejected` arm is the shared {@link TenantClientFailure} one. */
|
|
658
1599
|
export type ProxiedWriteOutcome = {
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
1600
|
+
outcome: "succeeded";
|
|
1601
|
+
status: number;
|
|
1602
|
+
attempts: number;
|
|
662
1603
|
} | {
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
1604
|
+
outcome: "exhausted";
|
|
1605
|
+
status: number;
|
|
1606
|
+
attempts: number;
|
|
1607
|
+
lastError: string;
|
|
667
1608
|
};
|
|
668
1609
|
export interface IssueStateInput {
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
1610
|
+
issueId: string;
|
|
1611
|
+
/** The Linear state id — resolve it by SLOT through {@link stageIdForSlot}, never by name. */
|
|
1612
|
+
stateId: string;
|
|
672
1613
|
}
|
|
673
1614
|
export type IssueStateResult = ProxiedWriteOutcome | AgentCallFailure;
|
|
674
1615
|
export interface IssueLabelInput {
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
1616
|
+
issueId: string;
|
|
1617
|
+
labelIds: string[];
|
|
1618
|
+
mode: "add" | "remove";
|
|
678
1619
|
}
|
|
679
1620
|
export interface IssueLabelItemResult {
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
1621
|
+
labelId: string;
|
|
1622
|
+
/** `already-absent` — a remove that found nothing to remove; counts as satisfied, not as a landing. */
|
|
1623
|
+
outcome: "succeeded" | "rejected" | "exhausted" | "already-absent";
|
|
1624
|
+
attempts: number;
|
|
1625
|
+
reason?: string;
|
|
1626
|
+
lastError?: string;
|
|
686
1627
|
}
|
|
687
1628
|
export type IssueLabelResult = {
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
1629
|
+
outcome: "succeeded" | "failed";
|
|
1630
|
+
status: number;
|
|
1631
|
+
results: IssueLabelItemResult[];
|
|
691
1632
|
} | AgentCallFailure;
|
|
692
1633
|
export interface IssueCommentInput {
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
1634
|
+
issueId: string;
|
|
1635
|
+
body: string;
|
|
1636
|
+
parentId?: string;
|
|
1637
|
+
/** The agent display name (`botActor.userDisplayName`). Absent means the plain app actor. */
|
|
1638
|
+
createAsUser?: string;
|
|
698
1639
|
}
|
|
699
1640
|
export type IssueCommentResult = ProxiedWriteOutcome | AgentCallFailure;
|
|
700
1641
|
export interface IssueCreateInput {
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
1642
|
+
teamId: string;
|
|
1643
|
+
title: string;
|
|
1644
|
+
description?: string;
|
|
1645
|
+
labelIds?: string[];
|
|
1646
|
+
assigneeId?: string;
|
|
1647
|
+
parentId?: string;
|
|
1648
|
+
/** Linear's 0–4 scale. */
|
|
1649
|
+
priority?: 0 | 1 | 2 | 3 | 4;
|
|
1650
|
+
stateId?: string;
|
|
1651
|
+
projectId?: string;
|
|
1652
|
+
createAsUser?: string;
|
|
712
1653
|
}
|
|
713
1654
|
export type IssueCreateResult = {
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
1655
|
+
outcome: "succeeded";
|
|
1656
|
+
status: number;
|
|
1657
|
+
attempts: number;
|
|
1658
|
+
identifier: string;
|
|
1659
|
+
id: string;
|
|
1660
|
+
url: string | null;
|
|
720
1661
|
} | Extract<ProxiedWriteOutcome, {
|
|
721
|
-
|
|
1662
|
+
outcome: "exhausted";
|
|
722
1663
|
}> | AgentCallFailure;
|
|
723
1664
|
/** Exactly one of `issueId`/`commentId` — enforced by the SERVER (a 400 `rejected`); the client
|
|
724
1665
|
* sends what it is given rather than carrying a second copy of the rule. */
|
|
725
1666
|
export interface ReactionInput {
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
1667
|
+
issueId?: string;
|
|
1668
|
+
commentId?: string;
|
|
1669
|
+
/** Defaults server-side to the fleet's claim emoji. */
|
|
1670
|
+
emoji?: string;
|
|
1671
|
+
mode?: "add" | "remove";
|
|
1672
|
+
createAsUser?: string;
|
|
732
1673
|
}
|
|
733
1674
|
export type ReactionResult = {
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
1675
|
+
outcome: "succeeded";
|
|
1676
|
+
status: number;
|
|
1677
|
+
attempts: number;
|
|
1678
|
+
reactionId: string | null;
|
|
1679
|
+
alreadyPresent?: true;
|
|
1680
|
+
userId?: string | null;
|
|
740
1681
|
} | {
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
1682
|
+
outcome: "succeeded";
|
|
1683
|
+
status: number;
|
|
1684
|
+
attempts: number;
|
|
1685
|
+
alreadyAbsent: true;
|
|
1686
|
+
removed: 0;
|
|
746
1687
|
} | {
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
1688
|
+
outcome: "succeeded";
|
|
1689
|
+
status: number;
|
|
1690
|
+
attempts: number;
|
|
1691
|
+
removed: number;
|
|
751
1692
|
} | Extract<ProxiedWriteOutcome, {
|
|
752
|
-
|
|
1693
|
+
outcome: "exhausted";
|
|
753
1694
|
}> | AgentCallFailure;
|
|
754
1695
|
export interface ProxiedAttachment {
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
1696
|
+
id: string;
|
|
1697
|
+
url: string;
|
|
1698
|
+
title: string | null;
|
|
1699
|
+
metadata: Record<string, unknown> | null;
|
|
759
1700
|
}
|
|
760
1701
|
export interface AttachmentInput {
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
1702
|
+
issueId: string;
|
|
1703
|
+
/** The upsert key — Linear dedups attachments by url. */
|
|
1704
|
+
url: string;
|
|
1705
|
+
title: string;
|
|
1706
|
+
metadata: Record<string, unknown>;
|
|
766
1707
|
}
|
|
767
1708
|
export type AttachmentResult = (ProxiedWriteOutcome & {
|
|
768
|
-
|
|
1709
|
+
attachment?: ProxiedAttachment;
|
|
769
1710
|
}) | AgentCallFailure;
|
|
770
1711
|
export type AttachmentsResult = {
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
1712
|
+
outcome: "succeeded";
|
|
1713
|
+
status: number;
|
|
1714
|
+
attachments: ProxiedAttachment[];
|
|
774
1715
|
}
|
|
775
1716
|
/** The proxy answers an absent issue as a 404 `rejected` — "no attachments" and "no such issue"
|
|
776
1717
|
* are different answers, and this arm keeps them apart. */
|
|
777
1718
|
| {
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
1719
|
+
outcome: "not-found";
|
|
1720
|
+
status: 404;
|
|
1721
|
+
reason: string;
|
|
781
1722
|
} | {
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
1723
|
+
outcome: "unauthorized-upstream";
|
|
1724
|
+
status: number;
|
|
1725
|
+
reason: string;
|
|
785
1726
|
} | AgentCallFailure;
|
|
786
1727
|
export interface SessionPlanEntry {
|
|
787
|
-
|
|
788
|
-
|
|
1728
|
+
content: string;
|
|
1729
|
+
status: string;
|
|
789
1730
|
}
|
|
790
1731
|
export type SessionActivity = {
|
|
791
|
-
|
|
792
|
-
|
|
1732
|
+
type: "thought" | "response";
|
|
1733
|
+
body: string;
|
|
793
1734
|
} | {
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
1735
|
+
type: "error";
|
|
1736
|
+
body: string;
|
|
1737
|
+
reasonCode?: string;
|
|
797
1738
|
} | {
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
1739
|
+
type: "action";
|
|
1740
|
+
action: string;
|
|
1741
|
+
parameter: string;
|
|
1742
|
+
result?: string;
|
|
802
1743
|
};
|
|
803
1744
|
export interface SessionInput {
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
1745
|
+
issueId: string;
|
|
1746
|
+
plan?: SessionPlanEntry[];
|
|
1747
|
+
activity?: SessionActivity;
|
|
1748
|
+
externalUrls?: {
|
|
1749
|
+
label: string;
|
|
1750
|
+
url: string;
|
|
1751
|
+
}[];
|
|
811
1752
|
}
|
|
812
1753
|
/** The proxy's shared write body as it appears INSIDE the session composite (no status of its own). */
|
|
813
1754
|
export type ProxiedWriteBody = {
|
|
814
|
-
|
|
815
|
-
|
|
1755
|
+
outcome: "succeeded";
|
|
1756
|
+
attempts: number;
|
|
816
1757
|
} | {
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
1758
|
+
outcome: "exhausted";
|
|
1759
|
+
attempts: number;
|
|
1760
|
+
lastError: string;
|
|
820
1761
|
} | {
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
1762
|
+
outcome: "rejected";
|
|
1763
|
+
attempts: number;
|
|
1764
|
+
reason: string;
|
|
824
1765
|
};
|
|
825
1766
|
export type EnsureSessionOutcome = {
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
1767
|
+
outcome: "reused";
|
|
1768
|
+
sessionId: string;
|
|
1769
|
+
attempts: number;
|
|
829
1770
|
} | {
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
1771
|
+
outcome: "created";
|
|
1772
|
+
sessionId: string;
|
|
1773
|
+
attempts: number;
|
|
833
1774
|
} | {
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
1775
|
+
outcome: "rejected";
|
|
1776
|
+
attempts: number;
|
|
1777
|
+
reason: string;
|
|
837
1778
|
} | {
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
1779
|
+
outcome: "exhausted";
|
|
1780
|
+
attempts: number;
|
|
1781
|
+
lastError: string;
|
|
841
1782
|
};
|
|
842
1783
|
/** ⚠️ The status is the WORST of the sub-outcomes (rejected 400 > exhausted 502 > 200); read
|
|
843
1784
|
* `session`/`plan`/`externalUrls`/`activity` individually — a 502 here can carry a reused session. */
|
|
844
1785
|
export type SessionResult = {
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
1786
|
+
outcome: "ok";
|
|
1787
|
+
status: number;
|
|
1788
|
+
session: EnsureSessionOutcome;
|
|
1789
|
+
plan?: ProxiedWriteBody;
|
|
1790
|
+
externalUrls?: ProxiedWriteBody;
|
|
1791
|
+
activity?: ProxiedWriteBody;
|
|
851
1792
|
} | AgentCallFailure;
|
|
852
1793
|
export interface AskInput {
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
1794
|
+
teamId: string;
|
|
1795
|
+
title: string;
|
|
1796
|
+
context: string;
|
|
1797
|
+
defaultIfSilent: string;
|
|
1798
|
+
/** The idempotency key — a re-raise under the same key answers the existing ask. */
|
|
1799
|
+
askKey: string;
|
|
1800
|
+
options?: string[];
|
|
1801
|
+
blocks?: string[];
|
|
1802
|
+
nothingToBlock?: true;
|
|
1803
|
+
target?: {
|
|
1804
|
+
membershipId: string;
|
|
1805
|
+
} | {
|
|
1806
|
+
email: string;
|
|
1807
|
+
};
|
|
867
1808
|
}
|
|
868
1809
|
export type AskResult = {
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
1810
|
+
outcome: "created";
|
|
1811
|
+
status: number;
|
|
1812
|
+
askId: string;
|
|
1813
|
+
identifier: string;
|
|
1814
|
+
existing?: true;
|
|
874
1815
|
} | {
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
1816
|
+
outcome: "created-but-relations-partial";
|
|
1817
|
+
status: number;
|
|
1818
|
+
askId: string;
|
|
1819
|
+
identifier: string;
|
|
1820
|
+
failedBlocks: string[];
|
|
1821
|
+
existing?: true;
|
|
881
1822
|
} | {
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
1823
|
+
outcome: "exhausted";
|
|
1824
|
+
status: number;
|
|
1825
|
+
lastError: string;
|
|
885
1826
|
} | AgentCallFailure;
|
|
886
1827
|
export interface AskAcceptInput {
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
1828
|
+
askIssueId: string;
|
|
1829
|
+
answerCommentId: string;
|
|
1830
|
+
/** A single line of at most 64 characters. */
|
|
1831
|
+
acceptedByRole?: string;
|
|
891
1832
|
}
|
|
892
1833
|
export type AskAcceptRefusal = "not-an-ask" | "issue-not-mirrored" | "already-resolved" | "answer-comment-not-mirrored" | "answer-comment-not-on-ask" | "answer-comment-not-wake-target" | "answer-comment-is-agent" | "not-assignee" | "already-in-flight" | "readiness-needs-tap" | "ask-has-no-team";
|
|
893
1834
|
export type AskAcceptResult = {
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
1835
|
+
outcome: "recorded";
|
|
1836
|
+
status: number;
|
|
1837
|
+
askIdentifier: string;
|
|
1838
|
+
decisionSummary: string;
|
|
1839
|
+
failedBlockedComments: string[];
|
|
1840
|
+
resume: unknown[];
|
|
1841
|
+
unblock?: unknown;
|
|
901
1842
|
} | {
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
1843
|
+
outcome: "refused";
|
|
1844
|
+
status: number;
|
|
1845
|
+
reason: AskAcceptRefusal;
|
|
905
1846
|
} | {
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
1847
|
+
outcome: "record-failed";
|
|
1848
|
+
status: number;
|
|
1849
|
+
reason: string;
|
|
1850
|
+
resume?: unknown[];
|
|
910
1851
|
} | AgentCallFailure;
|
|
911
1852
|
/** Both project-repository routes take the same address: a repository plus ONE of project /
|
|
912
1853
|
* teamKey / teamId. This route is NOT accounted against the write budget
|
|
913
1854
|
* (`takesWriteBudgetUnit: false`); it is gated on an admin/owner personal key or an
|
|
914
1855
|
* organization key carrying `mirror:write`. */
|
|
915
1856
|
export interface ProjectRepositoryInput {
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
1857
|
+
/** `owner/name`, sent VERBATIM. ⚠️ The cloud validates the trimmed halves but stores what it
|
|
1858
|
+
* was sent (CTC-2503 re-validation) — trim before calling if the value came from a human. */
|
|
1859
|
+
repository: string;
|
|
1860
|
+
project?: string;
|
|
1861
|
+
teamKey?: string;
|
|
1862
|
+
teamId?: string;
|
|
922
1863
|
}
|
|
923
1864
|
export interface RegisteredProjectRepository {
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
1865
|
+
repoId: string;
|
|
1866
|
+
owner: string;
|
|
1867
|
+
name: string;
|
|
1868
|
+
/** Present when the `repos` row carries one (e.g. `"paused"`). */
|
|
1869
|
+
status?: string;
|
|
929
1870
|
}
|
|
930
1871
|
export type ProjectRepositoryRegisterResult = {
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
1872
|
+
outcome: "registered";
|
|
1873
|
+
status: number;
|
|
1874
|
+
registered: RegisteredProjectRepository;
|
|
1875
|
+
created: boolean;
|
|
1876
|
+
linked: boolean;
|
|
936
1877
|
}
|
|
937
1878
|
/** The project is absent, archived, or another tenant's — one answer for all three. */
|
|
938
1879
|
| {
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
1880
|
+
outcome: "not-found";
|
|
1881
|
+
status: 404;
|
|
1882
|
+
reason: string;
|
|
942
1883
|
} | AgentCallFailure;
|
|
943
1884
|
export type ProjectRepositoryRemoveResult =
|
|
944
1885
|
/** `removed: false` is idempotent success, not a failure — there was nothing to unlink. */
|
|
945
1886
|
{
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
1887
|
+
outcome: "removed";
|
|
1888
|
+
status: number;
|
|
1889
|
+
removed: boolean;
|
|
949
1890
|
} | {
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
1891
|
+
outcome: "not-found";
|
|
1892
|
+
status: 404;
|
|
1893
|
+
reason: string;
|
|
953
1894
|
} | AgentCallFailure;
|
|
954
1895
|
/** CTC-3254 — `POST …/agent/messages` (CTC-3121): a message to the agent running a ticket's current
|
|
955
1896
|
* phase, which it reads as one more user turn when its current turn ends. The SENDER comes from the
|
|
@@ -958,77 +1899,77 @@ export type ProjectRepositoryRemoveResult =
|
|
|
958
1899
|
* message). To address a roster `agentId`, resolve it to the ticket its worker runs (the roster
|
|
959
1900
|
* entry's `scope`) before calling; the cloud does not resolve roster ids. */
|
|
960
1901
|
export interface AgentMessageInput {
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
1902
|
+
/** The ticket identifier, e.g. `CTC-42`. */
|
|
1903
|
+
ticket: string;
|
|
1904
|
+
/** At most 4096 bytes of UTF-8. */
|
|
1905
|
+
body: string;
|
|
1906
|
+
/** Defaults to `note` on the server. */
|
|
1907
|
+
kind?: "note" | "question" | "answer";
|
|
1908
|
+
/** The id of the earlier message this one answers. */
|
|
1909
|
+
re?: string;
|
|
969
1910
|
}
|
|
970
1911
|
export type AgentMessageResult =
|
|
971
1912
|
/** The running session reads it at its next turn boundary. */
|
|
972
1913
|
{
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
1914
|
+
outcome: "accepted";
|
|
1915
|
+
status: number;
|
|
1916
|
+
messageId: string;
|
|
1917
|
+
ticket: string;
|
|
1918
|
+
delivery: "next-turn";
|
|
978
1919
|
}
|
|
979
1920
|
/** It waits for the ticket's next phase, and why: nothing is running, the run has no channel for it
|
|
980
1921
|
* mid-phase, or the run is not reading messages. */
|
|
981
1922
|
| {
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
1923
|
+
outcome: "accepted";
|
|
1924
|
+
status: number;
|
|
1925
|
+
messageId: string;
|
|
1926
|
+
ticket: string;
|
|
1927
|
+
delivery: "next-phase";
|
|
1928
|
+
reason: "no-live-phase" | "run-cannot-receive" | "run-not-reading";
|
|
988
1929
|
}
|
|
989
1930
|
/** The ticket already holds the per-ticket cap of pending messages (20). */
|
|
990
1931
|
| {
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
1932
|
+
outcome: "refused";
|
|
1933
|
+
status: 409;
|
|
1934
|
+
reason: "too_many_pending";
|
|
994
1935
|
} | AgentCallFailure;
|
|
995
1936
|
/** Upstream auth references only account vault names, never credential values. */
|
|
996
1937
|
export type PortalServerAuth = {
|
|
997
|
-
|
|
1938
|
+
kind: "none";
|
|
998
1939
|
} | {
|
|
999
|
-
|
|
1000
|
-
|
|
1940
|
+
kind: "bearer";
|
|
1941
|
+
secretName: string;
|
|
1001
1942
|
} | {
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1943
|
+
kind: "headers";
|
|
1944
|
+
headers: {
|
|
1945
|
+
name: string;
|
|
1946
|
+
secretName: string;
|
|
1947
|
+
}[];
|
|
1007
1948
|
};
|
|
1008
1949
|
export interface PortalServerRegisterInput {
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1950
|
+
name: string;
|
|
1951
|
+
url: string;
|
|
1952
|
+
auth: PortalServerAuth;
|
|
1012
1953
|
}
|
|
1013
1954
|
export interface PortalServer extends PortalServerRegisterInput {
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1955
|
+
id: string;
|
|
1956
|
+
/** Custom servers stay pending until a server-side account admin approves them. */
|
|
1957
|
+
status: "ready" | "pending" | "pending_egress_guard";
|
|
1017
1958
|
}
|
|
1018
1959
|
export type PortalServerRegisterResult = {
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1960
|
+
outcome: "registered";
|
|
1961
|
+
status: number;
|
|
1962
|
+
server: PortalServer;
|
|
1022
1963
|
} | AgentCallFailure;
|
|
1023
1964
|
export type PortalServersResult = {
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1965
|
+
outcome: "ok";
|
|
1966
|
+
status: number;
|
|
1967
|
+
servers: PortalServer[];
|
|
1027
1968
|
} | AgentCallFailure;
|
|
1028
1969
|
export type PortalServerRemoveResult = {
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1970
|
+
outcome: "removed";
|
|
1971
|
+
status: number;
|
|
1972
|
+
removed: boolean;
|
|
1032
1973
|
} | AgentCallFailure;
|
|
1033
1974
|
/** The next-page keyset token on `/issues` and `/pulls`; ABSENT on the last page. */
|
|
1034
1975
|
export declare const NEXT_CURSOR_HEADER = "X-Mirror-Next-Cursor";
|
|
@@ -1036,6 +1977,8 @@ export declare const NEXT_CURSOR_HEADER = "X-Mirror-Next-Cursor";
|
|
|
1036
1977
|
export declare const HEAD_SEQ_HEADER = "X-Mirror-Cursor";
|
|
1037
1978
|
/** The scope's full row count on `/issues` and `/pulls` — the page can never report what lies past it. */
|
|
1038
1979
|
export declare const TOTAL_HEADER = "X-Mirror-Total";
|
|
1980
|
+
/** `/issues` only (CTC-4556): the params that narrowed the page, comma-separated. */
|
|
1981
|
+
export declare const SCOPE_HEADER = "X-Mirror-Scope";
|
|
1039
1982
|
/** `/pulls` only: the "N of M blocked on an ask" numerator. */
|
|
1040
1983
|
export declare const BLOCKED_ON_ASK_TOTAL_HEADER = "X-Mirror-Blocked-On-Ask-Total";
|
|
1041
1984
|
/** The contract route's own version header, equal to the body's `contractVersion`. */
|
|
@@ -1050,135 +1993,147 @@ export declare function normalizeBaseUrl(baseUrl: string): string;
|
|
|
1050
1993
|
export declare function createTenantClient(opts: TenantClientOptions): TenantClient;
|
|
1051
1994
|
/** The client. See {@link createTenantClient}. */
|
|
1052
1995
|
export interface TenantClient {
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1996
|
+
/** Personal bearer team setup. The key's tenant and admin role are enforced by the cloud. */
|
|
1997
|
+
teamWorkflow: {
|
|
1998
|
+
teams(): Promise<TeamWorkflowResult<TeamList>>;
|
|
1999
|
+
get(team: string): Promise<TeamWorkflowResult<TeamWorkflowView>>;
|
|
2000
|
+
check(team: string): Promise<TeamWorkflowResult<{
|
|
2001
|
+
readiness: TeamWorkflowReadiness;
|
|
2002
|
+
ask: Record<string, unknown>;
|
|
2003
|
+
}>>;
|
|
2004
|
+
save(input: TeamMappingSaveInput): Promise<TeamWorkflowResult<TeamWorkflowSaveResult>>;
|
|
2005
|
+
adoptPreview(team: string): Promise<TeamWorkflowResult<TeamAdoptResult>>;
|
|
2006
|
+
adoptApply(team: string, planHash: string): Promise<TeamWorkflowResult<TeamAdoptResult>>;
|
|
2007
|
+
undoPreview(team: string): Promise<TeamWorkflowResult<TeamUndoPreview>>;
|
|
2008
|
+
undoApply(team: string, undoHash: string): Promise<TeamWorkflowResult<TeamUndoResult>>;
|
|
2009
|
+
migratePreview(team: string, choices?: TeamMigrationChoice[]): Promise<TeamWorkflowResult<{
|
|
2010
|
+
preview: TeamMigrationPlan;
|
|
2011
|
+
}>>;
|
|
2012
|
+
migrateChunk(team: string, migrationHash: string, choices?: TeamMigrationChoice[]): Promise<TeamWorkflowResult<TeamMigrationChunk>>;
|
|
2013
|
+
migrateRetire(team: string, migrationHash: string, choices?: TeamMigrationChoice[]): Promise<TeamWorkflowResult<TeamMigrationRetire>>;
|
|
2014
|
+
};
|
|
2015
|
+
/** A project's WIP limit (CTC-3780). Any active member reads; personal key or CLI login. */
|
|
2016
|
+
getProjectWipLimit(team: string): Promise<TeamWorkflowResult<ProjectWipLimit>>;
|
|
2017
|
+
/** Sets it, clamped by the cloud to 0..9999; `null` returns the project to the default. Admin or
|
|
2018
|
+
* owner only: anyone else gets `outcome: "forbidden"`. Answers the value after the write. */
|
|
2019
|
+
setProjectWipLimit(team: string, limit: number | null): Promise<TeamWorkflowResult<ProjectWipLimit>>;
|
|
2020
|
+
/** Self-service unmatched identity recovery. Personal credentials only. */
|
|
2021
|
+
linearIdentity: {
|
|
2022
|
+
get(): Promise<LinearIdentityResult>;
|
|
2023
|
+
/** Records only the authenticated member's choice. Automatically resolved identities refuse. */
|
|
2024
|
+
set(linearUserId: string): Promise<LinearIdentityResult>;
|
|
2025
|
+
};
|
|
2026
|
+
/** The tenant's fact document, cached per its own `cache` policy. */
|
|
2027
|
+
contract(opts?: ContractOptions): Promise<ContractResult>;
|
|
2028
|
+
/** `GET /api/v1/me` — the account this key belongs to. */
|
|
2029
|
+
me(): Promise<MeResult>;
|
|
2030
|
+
/** The generic authed-request escape hatch (CTC-2132) — for a route nobody enumerated. `path` MUST
|
|
2031
|
+
* be an absolute path under this client's own origin; see {@link RawRequest}. */
|
|
2032
|
+
request(req: RawRequest): Promise<RawRequestResult>;
|
|
2033
|
+
/** User-scoped GitHub and Linear OAuth grants. Use a personal key or device-login credential. */
|
|
2034
|
+
personalConnections: {
|
|
2035
|
+
/** Returns a short lived URL to open in the user's browser. Does not perform consent. */
|
|
2036
|
+
start(provider: PersonalConnectionProvider): Promise<PersonalConnectionStartResult>;
|
|
2037
|
+
/** A provider outage is `unavailable`, never `absent`. */
|
|
2038
|
+
status(provider: PersonalConnectionProvider): Promise<PersonalConnectionStatusResult>;
|
|
2039
|
+
};
|
|
2040
|
+
issues: {
|
|
2041
|
+
/** `GET /api/v1/issues` — keyset-paged; follow `nextCursor` until it is `null`. */
|
|
2042
|
+
list(params?: IssueListParams): Promise<IssueListResult>;
|
|
2043
|
+
/** `GET /api/v1/issues/:identifier`. */
|
|
2044
|
+
get(identifier: string): Promise<IssueGetResult>;
|
|
2045
|
+
/** The same read, asking for only some top-level keys (CTC-4549). The issue is partial; a name
|
|
2046
|
+
* the server does not know is `select-refused` with its catalog. */
|
|
2047
|
+
get(identifier: string, select: IssueGetOptions): Promise<IssueGetProjectedResult>;
|
|
2048
|
+
/** `GET /api/v1/issues/:id/execution` — the ticket's own execution/telemetry report. */
|
|
2049
|
+
execution(identifier: string): Promise<TicketExecutionResult>;
|
|
2050
|
+
/** The same report, asking for only some top-level keys (CTC-4549). */
|
|
2051
|
+
execution(identifier: string, select: IssueExecutionOptions): Promise<TicketExecutionProjectedResult>;
|
|
2052
|
+
};
|
|
2053
|
+
pulls: {
|
|
2054
|
+
list(params?: PullListParams): Promise<PullListResult>;
|
|
2055
|
+
/** `GET /api/v1/pulls/:id` — `id` is the PR's GitHub node id. */
|
|
2056
|
+
get(nodeId: string): Promise<PullGetResult>;
|
|
2057
|
+
};
|
|
2058
|
+
projects: {
|
|
2059
|
+
list(params?: ProjectListParams): Promise<ProjectListResult>;
|
|
2060
|
+
};
|
|
2061
|
+
cycles: {
|
|
2062
|
+
/** `GET /api/v1/cycles` — the SDK's own structural row type; see {@link CycleRow}. */
|
|
2063
|
+
list(): Promise<CyclesListResult>;
|
|
2064
|
+
};
|
|
2065
|
+
/** `GET /api/v1/search?q=&limit=`. */
|
|
2066
|
+
search(params: SearchParams): Promise<SearchResult>;
|
|
2067
|
+
/** `GET /api/v1/workflow-stages` — tolerant of a `{stages,source?}` envelope or a bare array. */
|
|
2068
|
+
workflowStages(): Promise<WorkflowStagesResult>;
|
|
2069
|
+
changes: {
|
|
2070
|
+
/** `GET /api/v1/changes?since=` — NDJSON. ⛔ The returned generator MAY THROW mid-iteration; use
|
|
2071
|
+
* `list` for the nothing-throws, buffered alternative. */
|
|
2072
|
+
stream(params: {
|
|
2073
|
+
since: number | "head";
|
|
2074
|
+
signal?: AbortSignal;
|
|
2075
|
+
}): Promise<ChangesStreamResult>;
|
|
2076
|
+
/** The same feed, buffered — never throws; a mid-stream fault comes back as the `network` arm. */
|
|
2077
|
+
list(params: {
|
|
2078
|
+
since: number | "head";
|
|
2079
|
+
signal?: AbortSignal;
|
|
2080
|
+
}): Promise<ChangesListResult>;
|
|
2081
|
+
};
|
|
2082
|
+
/** A tenant's event history from the cloud (CTC-4549). An older server answers `unsupported`. */
|
|
2083
|
+
events: {
|
|
2084
|
+
/** `GET /api/v1/events/query` — one page, filtered by ticket and type. */
|
|
2085
|
+
query(params?: EventQueryParams): Promise<EventQueryResult>;
|
|
2086
|
+
/** Every page, following `next`. Never throws; a failed page is yielded and ends the walk. */
|
|
2087
|
+
pages(params?: EventQueryParams): AsyncGenerator<EventQueryResult, void, undefined>;
|
|
2088
|
+
};
|
|
2089
|
+
snapshot: {
|
|
2090
|
+
/** `GET /api/v1/snapshot?head=1` — the cheap head probe; never falls back to a full snapshot. */
|
|
2091
|
+
head(): Promise<SnapshotHeadResult>;
|
|
2092
|
+
};
|
|
2093
|
+
/** The diagnosis / telemetry reads (CTC-2132, M2 addendum) — a customer delegate agent's own
|
|
2094
|
+
* "why is nothing moving" / "what is my fleet doing" answers. */
|
|
2095
|
+
diagnostics: {
|
|
2096
|
+
workEligibility(params?: {
|
|
2097
|
+
team?: string;
|
|
2098
|
+
}): Promise<WorkEligibilityResult>;
|
|
2099
|
+
dispatchQueue(params?: {
|
|
2100
|
+
team?: string;
|
|
2101
|
+
}): Promise<DispatchQueueResult>;
|
|
2102
|
+
fleetActivity(params?: {
|
|
2103
|
+
account?: string;
|
|
2104
|
+
}): Promise<FleetActivityResult>;
|
|
2105
|
+
agentRoster(): Promise<AgentRosterResult>;
|
|
2106
|
+
leaseAttributions(params: {
|
|
2107
|
+
ticket: string;
|
|
2108
|
+
phase: string;
|
|
2109
|
+
}): Promise<LeaseAttributionsResult>;
|
|
2110
|
+
codingAccounts(): Promise<CodingAccountsResult>;
|
|
2111
|
+
};
|
|
2112
|
+
/** The `/api/v1/agent/*` proxy — every tenant write as the app actor, path from `routes[]`. */
|
|
2113
|
+
agent: {
|
|
2114
|
+
issueState(input: IssueStateInput): Promise<IssueStateResult>;
|
|
2115
|
+
issueLabel(input: IssueLabelInput): Promise<IssueLabelResult>;
|
|
2116
|
+
issueComment(input: IssueCommentInput): Promise<IssueCommentResult>;
|
|
2117
|
+
issueCreate(input: IssueCreateInput): Promise<IssueCreateResult>;
|
|
2118
|
+
reaction(input: ReactionInput): Promise<ReactionResult>;
|
|
2119
|
+
attachment(input: AttachmentInput): Promise<AttachmentResult>;
|
|
2120
|
+
/** `GET …/attachments?issueId=` — the soft-CAS read-back; no write-budget unit. */
|
|
2121
|
+
attachments(input: {
|
|
2122
|
+
issueId: string;
|
|
2123
|
+
}): Promise<AttachmentsResult>;
|
|
2124
|
+
session(input: SessionInput): Promise<SessionResult>;
|
|
2125
|
+
ask(input: AskInput): Promise<AskResult>;
|
|
2126
|
+
askAccept(input: AskAcceptInput): Promise<AskAcceptResult>;
|
|
2127
|
+
portalServerRegister(input: PortalServerRegisterInput): Promise<PortalServerRegisterResult>;
|
|
2128
|
+
portalServers(): Promise<PortalServersResult>;
|
|
2129
|
+
portalServerRemove(input: {
|
|
2130
|
+
name: string;
|
|
2131
|
+
}): Promise<PortalServerRemoveResult>;
|
|
2132
|
+
projectRepositoryRegister(input: ProjectRepositoryInput): Promise<ProjectRepositoryRegisterResult>;
|
|
2133
|
+
projectRepositoryRemove(input: ProjectRepositoryInput): Promise<ProjectRepositoryRemoveResult>;
|
|
2134
|
+
/** `POST …/agent/messages` — send a ticket's running agent a message it reads at its next turn. */
|
|
2135
|
+
message(input: AgentMessageInput): Promise<AgentMessageResult>;
|
|
2136
|
+
};
|
|
2137
|
+
}
|
|
2138
|
+
|
|
1183
2139
|
export {};
|
|
1184
|
-
//# sourceMappingURL=tenant-client.d.ts.map
|