@catalyst-cloud/sdk 0.13.0 → 0.14.0
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.map +1 -1
- package/dist/node.js +8463 -30
- package/dist/tenant-client.d.ts +1581 -777
- package/dist/tenant-client.d.ts.map +1 -1
- package/dist/tenant-client.js +13 -0
- package/dist/tenant-client.js.map +1 -1
- package/package.json +48 -10
package/dist/tenant-client.d.ts
CHANGED
|
@@ -1,36 +1,802 @@
|
|
|
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 interface 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
|
+
export interface ContractMergeRepository {
|
|
657
|
+
repoId: string;
|
|
658
|
+
owner: string;
|
|
659
|
+
name: string;
|
|
660
|
+
/** The EFFECTIVE policy after repo-wins-over-tenant precedence. */
|
|
661
|
+
policy: string;
|
|
662
|
+
policySource: "repo" | "tenant" | "default";
|
|
663
|
+
}
|
|
664
|
+
export interface ContractMergeCleanPassShape {
|
|
665
|
+
kind: string;
|
|
666
|
+
description: string;
|
|
667
|
+
/** FALSE means the cloud does NOT accept this shape today. */
|
|
668
|
+
honoured: boolean;
|
|
669
|
+
ticket: string | null;
|
|
670
|
+
}
|
|
671
|
+
export interface ContractAdvanceRule {
|
|
672
|
+
phase: string;
|
|
673
|
+
result: "ok" | "failed";
|
|
674
|
+
fromSlots: readonly WorkflowSlot[] | null;
|
|
675
|
+
toSlot: WorkflowSlot | null;
|
|
676
|
+
because: string;
|
|
677
|
+
}
|
|
678
|
+
export interface TenantContract {
|
|
679
|
+
/** Semver over the DOCUMENT'S SHAPE (MAJOR: meaning changed/removed; MINOR: additive). */
|
|
680
|
+
contractVersion: string;
|
|
681
|
+
protocolVersion: number;
|
|
682
|
+
/** The cloud states the freshness bounds; the CLIENT measures its own age against them. */
|
|
683
|
+
cache: {
|
|
684
|
+
maxAgeSeconds: number;
|
|
685
|
+
staleRefusalSeconds: number;
|
|
686
|
+
};
|
|
687
|
+
account: {
|
|
688
|
+
id: string;
|
|
689
|
+
slug: string;
|
|
690
|
+
name: string;
|
|
691
|
+
linearWorkspaceId: string | null;
|
|
692
|
+
linearWorkspaceSlug: string | null;
|
|
693
|
+
};
|
|
694
|
+
apiPrefix: string;
|
|
695
|
+
slots: readonly WorkflowSlot[];
|
|
696
|
+
routes: readonly ContractRoute[];
|
|
697
|
+
teams: ContractTeam[];
|
|
698
|
+
vocabulary: {
|
|
699
|
+
askLabelPrefix: string;
|
|
700
|
+
askPhaseLabelPrefix: string;
|
|
701
|
+
askMarkerLabel: string;
|
|
702
|
+
releaseLabel: string;
|
|
703
|
+
bookkeeping: {
|
|
704
|
+
marker: string;
|
|
705
|
+
prefixOnly: boolean;
|
|
706
|
+
asciiCaseInsensitive: boolean;
|
|
707
|
+
leadingWhitespaceTrimmed: boolean;
|
|
708
|
+
};
|
|
709
|
+
};
|
|
710
|
+
askTemplate: {
|
|
711
|
+
headings: {
|
|
712
|
+
ask: string;
|
|
713
|
+
options: string;
|
|
714
|
+
defaultIfSilent: string;
|
|
715
|
+
howToAnswer: string;
|
|
716
|
+
};
|
|
717
|
+
howToAnswer: {
|
|
718
|
+
withOptions: string;
|
|
719
|
+
withoutOptions: string;
|
|
720
|
+
};
|
|
721
|
+
optionBulletFormat: string;
|
|
722
|
+
maxLetteredOptions: number;
|
|
723
|
+
maxAskGenerations: number;
|
|
724
|
+
example: string;
|
|
725
|
+
};
|
|
726
|
+
ladder: {
|
|
727
|
+
phases: readonly string[];
|
|
728
|
+
keying: string;
|
|
729
|
+
keyingScope: string;
|
|
730
|
+
/** `phases` is the full vocabulary; this says whether `intake` actually runs. */
|
|
731
|
+
intakeEnabled: boolean;
|
|
732
|
+
advance: readonly ContractAdvanceRule[];
|
|
733
|
+
};
|
|
734
|
+
thresholds: {
|
|
735
|
+
scope: string;
|
|
736
|
+
parkAfterConsecutiveFailures: number;
|
|
737
|
+
remediateRoundCap: number;
|
|
738
|
+
remediateEscalatedRoundBudget: number;
|
|
739
|
+
remediateRewindBudget: number;
|
|
740
|
+
retryBackoffMs: readonly number[];
|
|
741
|
+
hostDailyWriteBudget: number;
|
|
742
|
+
};
|
|
743
|
+
merge: {
|
|
744
|
+
policies: readonly string[];
|
|
745
|
+
defaultPolicy: string;
|
|
746
|
+
repositories: readonly ContractMergeRepository[];
|
|
747
|
+
reviewerLogin: string;
|
|
748
|
+
cleanPassShapes: readonly ContractMergeCleanPassShape[];
|
|
749
|
+
cloudRemediateRequiredChecks: readonly string[];
|
|
750
|
+
prLabels: {
|
|
751
|
+
queueReady: string;
|
|
752
|
+
handStepsHold: string;
|
|
753
|
+
hold: string;
|
|
754
|
+
preview: string;
|
|
755
|
+
};
|
|
756
|
+
};
|
|
757
|
+
readinessChecks: readonly {
|
|
758
|
+
id: string;
|
|
759
|
+
severity: string;
|
|
760
|
+
needsAnswer: boolean;
|
|
761
|
+
}[];
|
|
762
|
+
humans: readonly {
|
|
763
|
+
linearUserId: string;
|
|
764
|
+
role: "owner" | "admin";
|
|
765
|
+
}[];
|
|
766
|
+
asks: {
|
|
767
|
+
/** `null` means NOT CONFIGURED, and a consumer must refuse rather than guess. */
|
|
768
|
+
approvalsTeamId: string | null;
|
|
769
|
+
};
|
|
770
|
+
}
|
|
5
771
|
/** Default per-request deadline — the bundle's `REQUEST_TIMEOUT_MS`. */
|
|
6
772
|
export declare const DEFAULT_TIMEOUT_MS = 15000;
|
|
7
773
|
/** One cached contract: the document, the validator it came with, and when it was fetched. */
|
|
8
774
|
export interface ContractCacheEntry {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
775
|
+
etag: string | null;
|
|
776
|
+
/** Epoch ms of the fetch (or the last successful 304 revalidation). */
|
|
777
|
+
fetchedAt: number;
|
|
778
|
+
contractVersion: string;
|
|
779
|
+
doc: TenantContract;
|
|
14
780
|
}
|
|
15
781
|
/** Where the contract cache lives. In-memory by default; the bundle supplies a disk-backed one. */
|
|
16
782
|
export interface ContractCacheStore {
|
|
17
|
-
|
|
18
|
-
|
|
783
|
+
get(): ContractCacheEntry | null | Promise<ContractCacheEntry | null>;
|
|
784
|
+
set(entry: ContractCacheEntry): void | Promise<void>;
|
|
19
785
|
}
|
|
20
786
|
/** The default store: one entry, held for the life of the client. */
|
|
21
787
|
export declare function memoryContractCache(): ContractCacheStore;
|
|
22
788
|
export interface TenantClientBaseOptions {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
789
|
+
/** The service origin (e.g. "https://staging.catalystcloud.dev"). A trailing slash is trimmed;
|
|
790
|
+
* every route path is absolute under it. */
|
|
791
|
+
baseUrl: string;
|
|
792
|
+
/** Injectable for tests; defaults to the platform global. */
|
|
793
|
+
fetch?: typeof fetch;
|
|
794
|
+
/** Per-request deadline in ms. Default {@link DEFAULT_TIMEOUT_MS}. */
|
|
795
|
+
timeoutMs?: number;
|
|
796
|
+
/** Default {@link memoryContractCache}. */
|
|
797
|
+
contractCache?: ContractCacheStore;
|
|
798
|
+
/** Epoch ms; injectable so a test can cross the cache bounds without sleeping. */
|
|
799
|
+
now?: () => number;
|
|
34
800
|
}
|
|
35
801
|
/**
|
|
36
802
|
* Exactly one credential. `key` is the CTC-2004 shape, kept working with identical wire behaviour;
|
|
@@ -40,14 +806,14 @@ export interface TenantClientBaseOptions {
|
|
|
40
806
|
* normalized to `{kind:"token", token:key}` at construction, so `send()` never sees two shapes.
|
|
41
807
|
*/
|
|
42
808
|
export type TenantClientOptions = TenantClientBaseOptions & ({
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
809
|
+
/** A tenant or personal key. Most agent proxy writes require an organization key;
|
|
810
|
+
* `teamWorkflow` and personal connection methods require the caller's own personal key or
|
|
811
|
+
* device login. */
|
|
812
|
+
key: string;
|
|
813
|
+
auth?: never;
|
|
48
814
|
} | {
|
|
49
|
-
|
|
50
|
-
|
|
815
|
+
auth: AuthStrategy;
|
|
816
|
+
key?: never;
|
|
51
817
|
});
|
|
52
818
|
/**
|
|
53
819
|
* The arms ANY call can end in. Every arm the server produced carries its `status`; `network` is the
|
|
@@ -56,279 +822,295 @@ export type TenantClientOptions = TenantClientBaseOptions & ({
|
|
|
56
822
|
export type TenantClientFailure =
|
|
57
823
|
/** 401 — the credential was not accepted. `ref` joins to the cloud's log line. */
|
|
58
824
|
{
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
825
|
+
outcome: "unauthorized";
|
|
826
|
+
status: 401;
|
|
827
|
+
reason: string;
|
|
828
|
+
ref: string | null;
|
|
63
829
|
}
|
|
64
830
|
/** 403 — wrong principal class, wrong tenant, or a missing scope (`required` names it). */
|
|
65
831
|
| {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
832
|
+
outcome: "forbidden";
|
|
833
|
+
status: 403;
|
|
834
|
+
reason: string;
|
|
835
|
+
required: string | null;
|
|
836
|
+
account: string | null;
|
|
71
837
|
}
|
|
72
838
|
/** 429 — the per-host daily write budget or the reserved Linear read floor. `retryAfterSeconds` is
|
|
73
839
|
* the server's own `Retry-After` when it sent one (the read floor does; the write budget does not). */
|
|
74
840
|
| {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
841
|
+
outcome: "rate-limited";
|
|
842
|
+
status: 429;
|
|
843
|
+
reason: string;
|
|
844
|
+
retryAfterSeconds: number | null;
|
|
79
845
|
}
|
|
80
846
|
/** A 400-class NAMED refusal — malformed input, a bad cursor, a tenant with no Linear workspace. */
|
|
81
847
|
| {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
848
|
+
outcome: "rejected";
|
|
849
|
+
status: number;
|
|
850
|
+
reason: string;
|
|
851
|
+
attempts?: number;
|
|
86
852
|
}
|
|
87
853
|
/** The server named a failure of its own (a 502 partial document, a 503 Linear client). */
|
|
88
854
|
| {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
855
|
+
outcome: "failed";
|
|
856
|
+
status: number;
|
|
857
|
+
reason: string;
|
|
92
858
|
}
|
|
93
859
|
/** fetch threw, or the deadline fired. */
|
|
94
860
|
| {
|
|
95
|
-
|
|
96
|
-
|
|
861
|
+
outcome: "network";
|
|
862
|
+
reason: string;
|
|
97
863
|
}
|
|
98
864
|
/** The body was not JSON, or not the shape this route is documented to answer. */
|
|
99
865
|
| {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
866
|
+
outcome: "shape";
|
|
867
|
+
status: number;
|
|
868
|
+
reason: string;
|
|
103
869
|
}
|
|
104
870
|
/** Any other non-2xx. */
|
|
105
871
|
| {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
872
|
+
outcome: "http";
|
|
873
|
+
status: number;
|
|
874
|
+
reason: string;
|
|
109
875
|
};
|
|
110
876
|
export type TeamWorkflowFailure = TenantClientFailure & {
|
|
111
|
-
|
|
877
|
+
error?: string;
|
|
112
878
|
};
|
|
113
879
|
export type TeamWorkflowResult<T> = ({
|
|
114
|
-
|
|
115
|
-
|
|
880
|
+
outcome: "ok";
|
|
881
|
+
status: number;
|
|
116
882
|
} & T) | TeamWorkflowFailure;
|
|
117
883
|
export interface TeamWorkflowReadiness {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
884
|
+
teamId: string;
|
|
885
|
+
teamKey: string;
|
|
886
|
+
teamName: string;
|
|
887
|
+
status: "ready" | "degraded" | "blocked" | "unchecked";
|
|
888
|
+
checkedAt?: number;
|
|
889
|
+
checks: readonly TeamReadinessCheck[];
|
|
890
|
+
workflowRev: number;
|
|
891
|
+
[field: string]: unknown;
|
|
126
892
|
}
|
|
127
893
|
export interface TeamReadinessCheck {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
894
|
+
id: string;
|
|
895
|
+
state: "pass" | "fail" | "unknown";
|
|
896
|
+
reason?: string;
|
|
897
|
+
count?: number;
|
|
132
898
|
}
|
|
133
899
|
export type TeamWorkflowSlot = "dispatch" | "intake" | "research" | "plan" | "implement" | "remediate" | "verify" | "review" | "pr" | "done" | "canceled";
|
|
134
900
|
export interface TeamWorkflowStage {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
901
|
+
id: string;
|
|
902
|
+
name: string;
|
|
903
|
+
type: string;
|
|
904
|
+
position: number;
|
|
139
905
|
}
|
|
140
906
|
export interface TeamWorkflowRow {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
907
|
+
slot: TeamWorkflowSlot;
|
|
908
|
+
linearStateId: string | null;
|
|
909
|
+
linearStateName?: string;
|
|
910
|
+
linearStateType?: string;
|
|
911
|
+
source?: "created" | "matched" | "chosen";
|
|
912
|
+
stateStillExists?: boolean;
|
|
147
913
|
}
|
|
148
914
|
export interface TeamWorkflowSummary extends TeamWorkflowReadiness {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
915
|
+
mode: "mapped-existing" | "adopted-recommended" | "mixed" | null;
|
|
916
|
+
gitAutomation: "off" | "managed";
|
|
917
|
+
mappedSlots: number;
|
|
918
|
+
mappedLoadBearingSlots: number;
|
|
919
|
+
mirrored: boolean | null;
|
|
154
920
|
}
|
|
155
921
|
export interface TeamList {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
922
|
+
teams: TeamWorkflowSummary[];
|
|
923
|
+
canManage: boolean;
|
|
924
|
+
liveTeamRead: {
|
|
925
|
+
attempted: boolean;
|
|
926
|
+
error: string | null;
|
|
927
|
+
reason?: "no-teams-known" | "unnamed-team";
|
|
928
|
+
};
|
|
929
|
+
everChecked: boolean;
|
|
930
|
+
mirrorRead: boolean;
|
|
165
931
|
}
|
|
166
932
|
export interface TeamWorkflowView {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
933
|
+
config: {
|
|
934
|
+
teamId: string;
|
|
935
|
+
mode: "mapped-existing" | "adopted-recommended" | "mixed";
|
|
936
|
+
gitAutomation: "off" | "managed";
|
|
937
|
+
workflowRev: number;
|
|
938
|
+
};
|
|
939
|
+
rows: readonly TeamWorkflowRow[];
|
|
940
|
+
stages: readonly TeamWorkflowStage[];
|
|
941
|
+
stageSource: "linear" | "mirror" | "none";
|
|
942
|
+
readiness: TeamWorkflowReadiness;
|
|
943
|
+
mappingHash: string;
|
|
944
|
+
checklist: string[] | null;
|
|
179
945
|
}
|
|
180
946
|
/** Save can commit the mapping even when its follow-up readiness recompute is unavailable. */
|
|
181
947
|
export type TeamWorkflowSaveResult = Omit<TeamWorkflowView, "mappingHash" | "checklist" | "readiness"> & {
|
|
182
|
-
|
|
948
|
+
readiness: TeamWorkflowReadiness | null;
|
|
183
949
|
};
|
|
184
950
|
export interface TeamMappingRowInput {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
951
|
+
slot: TeamWorkflowSlot;
|
|
952
|
+
linearStateId: string | null;
|
|
953
|
+
source?: "created" | "matched" | "chosen";
|
|
188
954
|
}
|
|
189
955
|
export interface TeamMappingSaveInput {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
956
|
+
team: string;
|
|
957
|
+
expectedMappingHash: string;
|
|
958
|
+
mode?: "mapped-existing" | "adopted-recommended" | "mixed";
|
|
959
|
+
gitAutomation?: "off" | "managed";
|
|
960
|
+
rows: TeamMappingRowInput[];
|
|
195
961
|
}
|
|
196
962
|
export interface TeamMigrationChoice {
|
|
197
|
-
|
|
198
|
-
|
|
963
|
+
sourceStateId: string;
|
|
964
|
+
destinationStateId: string;
|
|
199
965
|
}
|
|
200
966
|
export interface TeamMigrationSource {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
967
|
+
stateId: string;
|
|
968
|
+
name: string;
|
|
969
|
+
type: string;
|
|
970
|
+
ticketCount: number;
|
|
971
|
+
destinationStateId: string | null;
|
|
972
|
+
destinationSlot: TeamWorkflowSlot | null;
|
|
973
|
+
because: "unique-type" | "terminal-family" | "chosen" | null;
|
|
974
|
+
outcome: "ready" | "needs-a-choice" | "empty" | "moved" | "partially-moved" | "protected" | "retired-in-catalyst";
|
|
975
|
+
reason?: string;
|
|
976
|
+
retiredAt?: number;
|
|
211
977
|
}
|
|
212
978
|
export interface TeamMigrationPlan {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
979
|
+
teamId: string;
|
|
980
|
+
sources: readonly TeamMigrationSource[];
|
|
981
|
+
migrationHash: string;
|
|
982
|
+
overLimit: boolean;
|
|
983
|
+
issueCount: number;
|
|
984
|
+
retireLogReadable: boolean;
|
|
985
|
+
/** A false value means the server will refuse stage moves and retirement. Older servers omit it. */
|
|
986
|
+
actionsAvailable?: boolean;
|
|
221
987
|
}
|
|
222
988
|
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
|
-
|
|
989
|
+
teamId: string;
|
|
990
|
+
teamKey: string;
|
|
991
|
+
mode: "adopted-recommended" | "mapped-existing" | "mixed" | null;
|
|
992
|
+
stages: readonly {
|
|
993
|
+
name: string;
|
|
994
|
+
type: string;
|
|
995
|
+
outcome: string;
|
|
996
|
+
stateId?: string;
|
|
997
|
+
reason?: string;
|
|
998
|
+
}[];
|
|
999
|
+
planHash: string;
|
|
1000
|
+
unfilledLoadBearing: readonly unknown[];
|
|
1001
|
+
provenanceGaps: readonly unknown[];
|
|
1002
|
+
labels: readonly {
|
|
1003
|
+
name: string;
|
|
1004
|
+
outcome: string;
|
|
1005
|
+
reason?: string;
|
|
1006
|
+
}[];
|
|
1007
|
+
labelProvenanceGaps: readonly unknown[];
|
|
1008
|
+
labelsNotCreated: readonly unknown[];
|
|
1009
|
+
checklist?: string[];
|
|
1010
|
+
readiness?: TeamWorkflowReadiness | null;
|
|
245
1011
|
}
|
|
246
1012
|
export interface TeamUndoPreview {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
1013
|
+
teamId: string;
|
|
1014
|
+
mode: "preview";
|
|
1015
|
+
candidates: readonly {
|
|
1016
|
+
stateId: string;
|
|
1017
|
+
name: string | null;
|
|
1018
|
+
}[];
|
|
1019
|
+
undoHash: string;
|
|
254
1020
|
}
|
|
255
1021
|
export interface TeamUndoResult {
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
1022
|
+
archived: readonly {
|
|
1023
|
+
stateId: string;
|
|
1024
|
+
name: string | null;
|
|
1025
|
+
}[];
|
|
1026
|
+
kept: readonly {
|
|
1027
|
+
stateId: string;
|
|
1028
|
+
name: string | null;
|
|
1029
|
+
reason: string;
|
|
1030
|
+
}[];
|
|
1031
|
+
failed: readonly {
|
|
1032
|
+
stateId: string;
|
|
1033
|
+
name: string | null;
|
|
1034
|
+
reason: string;
|
|
1035
|
+
}[];
|
|
1036
|
+
readiness: TeamWorkflowReadiness | null;
|
|
271
1037
|
}
|
|
272
1038
|
export interface TeamMigrationChunk {
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
1039
|
+
teamId: string;
|
|
1040
|
+
sources: readonly TeamMigrationSource[];
|
|
1041
|
+
remaining: number;
|
|
1042
|
+
migrationHash: string;
|
|
1043
|
+
moved: number;
|
|
1044
|
+
readiness: TeamWorkflowReadiness | null;
|
|
279
1045
|
}
|
|
280
1046
|
export interface TeamMigrationRetire {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
1047
|
+
retired: readonly {
|
|
1048
|
+
stateId: string;
|
|
1049
|
+
name: string;
|
|
1050
|
+
}[];
|
|
1051
|
+
kept: readonly {
|
|
1052
|
+
stateId: string;
|
|
1053
|
+
name: string;
|
|
1054
|
+
reason: string;
|
|
1055
|
+
}[];
|
|
1056
|
+
failed: readonly {
|
|
1057
|
+
stateId: string;
|
|
1058
|
+
name: string;
|
|
1059
|
+
reason: string;
|
|
1060
|
+
}[];
|
|
1061
|
+
logGaps: readonly {
|
|
1062
|
+
stateId: string;
|
|
1063
|
+
reason: string;
|
|
1064
|
+
}[];
|
|
1065
|
+
readiness: TeamWorkflowReadiness | null;
|
|
1066
|
+
}
|
|
1067
|
+
/** CTC-3780 — a project's WIP limit, from `GET|POST /api/v1/agent/team-wip-limit`. */
|
|
1068
|
+
export interface ProjectWipLimit {
|
|
1069
|
+
team: {
|
|
1070
|
+
id: string;
|
|
1071
|
+
key: string;
|
|
1072
|
+
};
|
|
1073
|
+
/** The limit that applies: the project's own, else the tenant flag's, else the default (12). */
|
|
1074
|
+
limit: number;
|
|
1075
|
+
source: "project" | "flag" | "default";
|
|
1076
|
+
/** The project's own stored value; null when it takes the flag or the default. */
|
|
1077
|
+
stored: number | null;
|
|
1078
|
+
/** The project's work in progress, counted as the WIP gate counts it. */
|
|
1079
|
+
inProgress: number;
|
|
1080
|
+
/** Epoch ms at which `inProgress` was counted. */
|
|
1081
|
+
countedAt: number;
|
|
300
1082
|
}
|
|
301
1083
|
export type ContractResult = {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
1084
|
+
outcome: "ok";
|
|
1085
|
+
doc: TenantContract;
|
|
1086
|
+
/** `cache` — served inside `maxAgeSeconds` (or on a network failure inside
|
|
1087
|
+
* `staleRefusalSeconds`); `revalidated` — a 304; `network` — a fresh 200. */
|
|
1088
|
+
source: "cache" | "network" | "revalidated";
|
|
1089
|
+
ageSeconds: number;
|
|
1090
|
+
etag: string | null;
|
|
1091
|
+
contractVersion: string;
|
|
310
1092
|
}
|
|
311
1093
|
/** Past `staleRefusalSeconds` and the refresh failed: refused by name, with the failure that
|
|
312
1094
|
* prevented revalidation. No document is served — there is no literal to fall back to. */
|
|
313
1095
|
| {
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
1096
|
+
outcome: "stale";
|
|
1097
|
+
ageSeconds: number;
|
|
1098
|
+
staleRefusalSeconds: number;
|
|
1099
|
+
cause: TenantClientFailure;
|
|
318
1100
|
}
|
|
319
1101
|
/** `offline: true` with nothing cached. */
|
|
320
1102
|
| {
|
|
321
|
-
|
|
322
|
-
|
|
1103
|
+
outcome: "missing";
|
|
1104
|
+
reason: string;
|
|
323
1105
|
} | TenantClientFailure;
|
|
324
1106
|
export type ContractFailure = Exclude<ContractResult, {
|
|
325
|
-
|
|
1107
|
+
outcome: "ok";
|
|
326
1108
|
}>;
|
|
327
1109
|
export interface ContractOptions {
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
1110
|
+
/** Revalidate even inside `maxAgeSeconds`. */
|
|
1111
|
+
refresh?: boolean;
|
|
1112
|
+
/** Never touch the network: the cache if any, else `missing`. */
|
|
1113
|
+
offline?: boolean;
|
|
332
1114
|
}
|
|
333
1115
|
declare const PAGE_CURSOR: unique symbol;
|
|
334
1116
|
/**
|
|
@@ -337,87 +1119,87 @@ declare const PAGE_CURSOR: unique symbol;
|
|
|
337
1119
|
* is not a page position.
|
|
338
1120
|
*/
|
|
339
1121
|
export type PageCursor = string & {
|
|
340
|
-
|
|
1122
|
+
readonly [PAGE_CURSOR]: true;
|
|
341
1123
|
};
|
|
342
1124
|
/** Admit a persisted token as a {@link PageCursor}. */
|
|
343
1125
|
export declare function pageCursor(token: string): PageCursor;
|
|
344
1126
|
/** `GET /api/v1/issues` — the facets `MirrorDO.handleIssues` reads, by their WIRE names' meaning. */
|
|
345
1127
|
export interface IssueListParams {
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
1128
|
+
after?: PageCursor;
|
|
1129
|
+
limit?: number;
|
|
1130
|
+
teamId?: string;
|
|
1131
|
+
teamKey?: string;
|
|
1132
|
+
/** An unrecognised value is no narrowing server-side, never a 400 — so the type is closed here. */
|
|
1133
|
+
state?: "active" | "backlog" | "done";
|
|
1134
|
+
/** Linear's 1..4. */
|
|
1135
|
+
priority?: 1 | 2 | 3 | 4;
|
|
1136
|
+
assigneeId?: string;
|
|
1137
|
+
assignee?: string;
|
|
1138
|
+
cycle?: string;
|
|
1139
|
+
blocked?: "ask";
|
|
1140
|
+
/** `"priority"` orders the FULL scoped set by urgency; absent keeps `updated_at DESC`. */
|
|
1141
|
+
sort?: "priority";
|
|
1142
|
+
waitingOn?: string;
|
|
1143
|
+
waitingMode?: "asks" | "all";
|
|
362
1144
|
}
|
|
363
1145
|
export interface KeysetPageMeta {
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
1146
|
+
/** The next page's `after`, or `null` on the last page (the header is absent there). */
|
|
1147
|
+
nextCursor: PageCursor | null;
|
|
1148
|
+
/** The scope's FULL row count (`X-Mirror-Total`), `null` when the server did not send it. */
|
|
1149
|
+
total: number | null;
|
|
1150
|
+
/** The feed head seq this page is consistent with (`X-Mirror-Cursor`). */
|
|
1151
|
+
head: number | null;
|
|
370
1152
|
}
|
|
371
1153
|
export type IssueListResult = ({
|
|
372
|
-
|
|
373
|
-
|
|
1154
|
+
outcome: "ok";
|
|
1155
|
+
rows: IssueView[];
|
|
374
1156
|
} & KeysetPageMeta) | TenantClientFailure;
|
|
375
1157
|
export type IssueGetResult = {
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
1158
|
+
outcome: "ok";
|
|
1159
|
+
issue: IssueDetailView;
|
|
1160
|
+
head: number | null;
|
|
379
1161
|
} | {
|
|
380
|
-
|
|
381
|
-
|
|
1162
|
+
outcome: "not-found";
|
|
1163
|
+
status: 404;
|
|
382
1164
|
} | TenantClientFailure;
|
|
383
1165
|
export interface PullListParams {
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
1166
|
+
after?: PageCursor;
|
|
1167
|
+
limit?: number;
|
|
1168
|
+
/** `owner/name`. */
|
|
1169
|
+
repo?: string;
|
|
1170
|
+
state?: "open" | "merged";
|
|
1171
|
+
blocked?: "ask";
|
|
390
1172
|
}
|
|
391
1173
|
export type PullListResult = ({
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
1174
|
+
outcome: "ok";
|
|
1175
|
+
rows: PullView[];
|
|
1176
|
+
blockedOnAskTotal: number | null;
|
|
395
1177
|
} & KeysetPageMeta) | TenantClientFailure;
|
|
396
1178
|
export type PullGetResult = {
|
|
397
|
-
|
|
398
|
-
|
|
1179
|
+
outcome: "ok";
|
|
1180
|
+
pull: PullDetailView;
|
|
399
1181
|
} | {
|
|
400
|
-
|
|
401
|
-
|
|
1182
|
+
outcome: "not-found";
|
|
1183
|
+
status: 404;
|
|
402
1184
|
} | TenantClientFailure;
|
|
403
1185
|
/** `GET /api/v1/projects` is offset-paged (the legacy `?cursor=` carries the offset). */
|
|
404
1186
|
export interface ProjectListParams {
|
|
405
|
-
|
|
406
|
-
|
|
1187
|
+
limit?: number;
|
|
1188
|
+
offset?: number;
|
|
407
1189
|
}
|
|
408
1190
|
export type ProjectListResult = {
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
1191
|
+
outcome: "ok";
|
|
1192
|
+
rows: ProjectView[];
|
|
1193
|
+
head: number | null;
|
|
412
1194
|
} | TenantClientFailure;
|
|
413
1195
|
/** `GET /api/v1/me` — how a key learns its own tenant. */
|
|
414
1196
|
export type MeResult = {
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
1197
|
+
outcome: "ok";
|
|
1198
|
+
account: string;
|
|
1199
|
+
slug: string;
|
|
1200
|
+
name: string;
|
|
1201
|
+
permissions: string[] | null;
|
|
1202
|
+
principal: "service" | "session";
|
|
421
1203
|
} | TenantClientFailure;
|
|
422
1204
|
/** `GET /api/v1/issues/:id/execution` — the ticket's own execution/telemetry report. ⛔ The fields
|
|
423
1205
|
* below are DOCUMENTED, not compiled: this repo does not depend on `catalyst-cloud`. The index
|
|
@@ -425,203 +1207,203 @@ export type MeResult = {
|
|
|
425
1207
|
* be refused as a shape error. The tenant decides which principals may read this route (Decision 5);
|
|
426
1208
|
* the client reports whatever it answers. */
|
|
427
1209
|
export interface TicketExecutionReport {
|
|
428
|
-
|
|
429
|
-
|
|
1210
|
+
readonly identifier?: string;
|
|
1211
|
+
readonly [key: string]: unknown;
|
|
430
1212
|
}
|
|
431
1213
|
export type TicketExecutionResult = {
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
1214
|
+
outcome: "ok";
|
|
1215
|
+
status: 200;
|
|
1216
|
+
report: TicketExecutionReport;
|
|
435
1217
|
} | {
|
|
436
|
-
|
|
437
|
-
|
|
1218
|
+
outcome: "not-found";
|
|
1219
|
+
status: 404;
|
|
438
1220
|
} | TenantClientFailure;
|
|
439
1221
|
/** `GET /api/v1/work-eligibility?team=` — the dispatcher's own "why is nothing moving" answer.
|
|
440
1222
|
* ⛔ DOCUMENTED, not compiled — see the section note above. */
|
|
441
1223
|
export interface WorkEligibilityReport {
|
|
442
|
-
|
|
443
|
-
|
|
1224
|
+
readonly team?: string;
|
|
1225
|
+
readonly [key: string]: unknown;
|
|
444
1226
|
}
|
|
445
1227
|
export type WorkEligibilityResult = {
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
1228
|
+
outcome: "ok";
|
|
1229
|
+
status: 200;
|
|
1230
|
+
report: WorkEligibilityReport;
|
|
449
1231
|
} | TenantClientFailure;
|
|
450
1232
|
/** `GET /api/v1/dispatch-queue/current?team=` — an ENVELOPE (`entries[]`), not a bare array. */
|
|
451
1233
|
export interface DispatchQueueEnvelope {
|
|
452
|
-
|
|
453
|
-
|
|
1234
|
+
readonly entries: readonly Record<string, unknown>[];
|
|
1235
|
+
readonly [key: string]: unknown;
|
|
454
1236
|
}
|
|
455
1237
|
export type DispatchQueueResult = {
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
1238
|
+
outcome: "ok";
|
|
1239
|
+
status: 200;
|
|
1240
|
+
queue: DispatchQueueEnvelope;
|
|
459
1241
|
} | TenantClientFailure;
|
|
460
1242
|
/** `GET /api/v1/fleet-activity/current` — a bare array of activity rows. */
|
|
461
1243
|
export type FleetActivityResult = {
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
1244
|
+
outcome: "ok";
|
|
1245
|
+
status: 200;
|
|
1246
|
+
rows: readonly Record<string, unknown>[];
|
|
465
1247
|
} | TenantClientFailure;
|
|
466
1248
|
/** `GET /api/v1/agent-roster/current` — a bare array of roster rows. */
|
|
467
1249
|
export type AgentRosterResult = {
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
1250
|
+
outcome: "ok";
|
|
1251
|
+
status: 200;
|
|
1252
|
+
rows: readonly Record<string, unknown>[];
|
|
471
1253
|
} | TenantClientFailure;
|
|
472
1254
|
/** `GET /api/v1/lease/attributions?ticket=&phase=` — both params are required (the route 400s when
|
|
473
1255
|
* either is absent); the body shape is tolerant (object or array). */
|
|
474
1256
|
export type LeaseAttributionsResult = {
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
1257
|
+
outcome: "ok";
|
|
1258
|
+
status: 200;
|
|
1259
|
+
body: unknown;
|
|
478
1260
|
} | TenantClientFailure;
|
|
479
1261
|
/** `GET /api/v1/coding-accounts` — an OBJECT carrying an `accounts` array. */
|
|
480
1262
|
export interface CodingAccountsReport {
|
|
481
|
-
|
|
482
|
-
|
|
1263
|
+
readonly accounts: readonly Record<string, unknown>[];
|
|
1264
|
+
readonly [key: string]: unknown;
|
|
483
1265
|
}
|
|
484
1266
|
export type CodingAccountsResult = {
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
1267
|
+
outcome: "ok";
|
|
1268
|
+
status: 200;
|
|
1269
|
+
report: CodingAccountsReport;
|
|
488
1270
|
} | TenantClientFailure;
|
|
489
1271
|
/** `GET /api/v1/cycles` — the SDK's own structural copy; see the section note above. */
|
|
490
1272
|
export interface CycleRow {
|
|
491
|
-
|
|
492
|
-
|
|
1273
|
+
readonly id?: string;
|
|
1274
|
+
readonly [key: string]: unknown;
|
|
493
1275
|
}
|
|
494
1276
|
export type CyclesListResult = {
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
1277
|
+
outcome: "ok";
|
|
1278
|
+
status: 200;
|
|
1279
|
+
rows: readonly CycleRow[];
|
|
498
1280
|
} | TenantClientFailure;
|
|
499
1281
|
/** `GET /api/v1/search?q=&limit=` — four result buckets, the SDK's own structural copy. */
|
|
500
1282
|
export interface SearchParams {
|
|
501
|
-
|
|
502
|
-
|
|
1283
|
+
q: string;
|
|
1284
|
+
limit?: number;
|
|
503
1285
|
}
|
|
504
1286
|
export interface SearchResults {
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
1287
|
+
readonly issues?: readonly Record<string, unknown>[];
|
|
1288
|
+
readonly pulls?: readonly Record<string, unknown>[];
|
|
1289
|
+
readonly projects?: readonly Record<string, unknown>[];
|
|
1290
|
+
readonly initiatives?: readonly Record<string, unknown>[];
|
|
1291
|
+
readonly [key: string]: unknown;
|
|
510
1292
|
}
|
|
511
1293
|
export type SearchResult = {
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
1294
|
+
outcome: "ok";
|
|
1295
|
+
status: 200;
|
|
1296
|
+
results: SearchResults;
|
|
515
1297
|
} | TenantClientFailure;
|
|
516
1298
|
/** `GET /api/v1/workflow-stages` — the key-authenticated twin; its shape is never quoted anywhere in
|
|
517
1299
|
* the pool (Decision 7). Tolerant: accepts `{stages, source?}` OR a bare array, normalizing both. */
|
|
518
1300
|
export type WorkflowStagesResult = {
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
1301
|
+
outcome: "ok";
|
|
1302
|
+
status: 200;
|
|
1303
|
+
stages: readonly Record<string, unknown>[];
|
|
1304
|
+
source: string | null;
|
|
523
1305
|
} | TenantClientFailure;
|
|
524
1306
|
/** `GET /api/v1/changes?since=` — the NDJSON change feed. */
|
|
525
1307
|
export type ChangesStreamResult = {
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
1308
|
+
outcome: "ok";
|
|
1309
|
+
status: 200;
|
|
1310
|
+
head: number | null;
|
|
1311
|
+
rows: AsyncGenerator<Record<string, unknown>>;
|
|
1312
|
+
/**
|
|
1313
|
+
* Release the feed WITHOUT draining it — cancels the response body and stops the idle deadline.
|
|
1314
|
+
* Idempotent, and safe to call after the rows have been read.
|
|
1315
|
+
*
|
|
1316
|
+
* ⛔ A caller that reads `head` and then DISCARDS `rows` must call this (CTC-2132 validate
|
|
1317
|
+
* attempt 14, code-review Finding 3): nothing else runs the generator's cleanup, so the body
|
|
1318
|
+
* would stay uncancelled and its connection referenced. Draining the rows to EOF, or `break`ing
|
|
1319
|
+
* out of the `for await`, already releases both — `close()` is for the caller that never starts.
|
|
1320
|
+
*/
|
|
1321
|
+
close: () => Promise<void>;
|
|
540
1322
|
}
|
|
541
1323
|
/** The tenant can no longer replay from `since` — reseed from `snapshot.head()` (or a full
|
|
542
1324
|
* snapshot) rather than replaying the gap. `head` is the tenant's current head. */
|
|
543
1325
|
| {
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
1326
|
+
outcome: "resync";
|
|
1327
|
+
status: 409;
|
|
1328
|
+
head: number | null;
|
|
1329
|
+
reason: string;
|
|
548
1330
|
} | TenantClientFailure;
|
|
549
1331
|
export type ChangesListResult = {
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
1332
|
+
outcome: "ok";
|
|
1333
|
+
status: 200;
|
|
1334
|
+
head: number | null;
|
|
1335
|
+
rows: Record<string, unknown>[];
|
|
554
1336
|
} | Extract<ChangesStreamResult, {
|
|
555
|
-
|
|
1337
|
+
outcome: "resync";
|
|
556
1338
|
}> | TenantClientFailure;
|
|
557
1339
|
/** `GET /api/v1/snapshot?head=1` — the cheap head probe (Decision 7): the shape was inferred from
|
|
558
1340
|
* prose, not a quoted response body, so the parse is deliberately tolerant of three answer shapes. */
|
|
559
1341
|
export type SnapshotHeadResult = {
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
1342
|
+
outcome: "ok";
|
|
1343
|
+
status: 200;
|
|
1344
|
+
head: number;
|
|
1345
|
+
source: "header" | "body";
|
|
564
1346
|
} | {
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
1347
|
+
outcome: "shape";
|
|
1348
|
+
status: number;
|
|
1349
|
+
reason: string;
|
|
568
1350
|
} | TenantClientFailure;
|
|
569
1351
|
export interface RawRequest {
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
1352
|
+
method?: "GET" | "POST";
|
|
1353
|
+
/** ⛔ An ABSOLUTE PATH under the client's own origin ("/api/v1/…"), never a URL. A value that is
|
|
1354
|
+
* not one is refused BEFORE the request is built: `new URL(origin + path)` does not throw on
|
|
1355
|
+
* "@host/x" — it reads the origin as USERINFO and `host` becomes the attacker's, which would send
|
|
1356
|
+
* this client's bearer credential to that host (reproduced: `new URL("https://cloud.example" +
|
|
1357
|
+
* "@evil.example/steal")` → host `evil.example`, no throw). CTC-2132. */
|
|
1358
|
+
path: string;
|
|
1359
|
+
query?: Record<string, string | number | undefined>;
|
|
1360
|
+
headers?: Record<string, string>;
|
|
1361
|
+
body?: unknown;
|
|
580
1362
|
}
|
|
581
1363
|
export type RawRequestResult = {
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
1364
|
+
outcome: "ok";
|
|
1365
|
+
status: number;
|
|
1366
|
+
json: unknown;
|
|
1367
|
+
headers: Headers;
|
|
586
1368
|
} | TenantClientFailure;
|
|
587
1369
|
/** Personal provider consent is initiated by a user credential, never an account key. */
|
|
588
1370
|
export type PersonalConnectionProvider = "linear" | "github";
|
|
589
1371
|
export type PersonalConnectionStartResult = {
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
1372
|
+
outcome: "ok";
|
|
1373
|
+
status: 200;
|
|
1374
|
+
authorizationUrl: string;
|
|
1375
|
+
expiresAt: number;
|
|
594
1376
|
} | {
|
|
595
|
-
|
|
596
|
-
|
|
1377
|
+
outcome: "workspace-required";
|
|
1378
|
+
status: 409;
|
|
597
1379
|
} | TenantClientFailure;
|
|
598
1380
|
export type PersonalConnectionStatusResult = {
|
|
599
|
-
|
|
600
|
-
|
|
1381
|
+
outcome: "absent";
|
|
1382
|
+
status: 200;
|
|
601
1383
|
} | {
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
1384
|
+
outcome: "lapsed";
|
|
1385
|
+
status: 200;
|
|
1386
|
+
lapsedAt: number | null;
|
|
605
1387
|
} | {
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
1388
|
+
outcome: "connected";
|
|
1389
|
+
status: 200;
|
|
1390
|
+
provider: "linear";
|
|
1391
|
+
linearUserId: string;
|
|
1392
|
+
grantedScope: string | null;
|
|
1393
|
+
updatedAt: number;
|
|
1394
|
+
expiresAt: number | null;
|
|
613
1395
|
} | {
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
1396
|
+
outcome: "connected";
|
|
1397
|
+
status: 200;
|
|
1398
|
+
provider: "github";
|
|
1399
|
+
githubUserId: string;
|
|
1400
|
+
githubLogin: string;
|
|
1401
|
+
updatedAt: number;
|
|
1402
|
+
expiresAt: number | null;
|
|
621
1403
|
} | {
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
1404
|
+
outcome: "unavailable";
|
|
1405
|
+
status: 503;
|
|
1406
|
+
provider: PersonalConnectionProvider;
|
|
625
1407
|
} | TenantClientFailure;
|
|
626
1408
|
/** The routes this client wraps, by the last segment of their contract path. `delegate` (operator
|
|
627
1409
|
* credential only) and `linear/read` (an origin-fresh read the replica rule discourages) are
|
|
@@ -629,311 +1411,328 @@ export type PersonalConnectionStatusResult = {
|
|
|
629
1411
|
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";
|
|
630
1412
|
/** The runtime twin of {@link AgentRouteName}: the list a test can walk against the contract.
|
|
631
1413
|
* Kept in lockstep with the union by a compile-time equality in test/tenant-client-agent.test.ts. */
|
|
632
|
-
export declare const AGENT_ROUTE_NAMES: readonly [
|
|
1414
|
+
export declare const AGENT_ROUTE_NAMES: readonly [
|
|
1415
|
+
"issue-state",
|
|
1416
|
+
"issue-label",
|
|
1417
|
+
"issue-comment",
|
|
1418
|
+
"issue-create",
|
|
1419
|
+
"reaction",
|
|
1420
|
+
"attachment",
|
|
1421
|
+
"attachments",
|
|
1422
|
+
"session",
|
|
1423
|
+
"ask",
|
|
1424
|
+
"ask-accept",
|
|
1425
|
+
"project-repositories",
|
|
1426
|
+
"project-repositories/remove",
|
|
1427
|
+
"portal-servers",
|
|
1428
|
+
"portal-servers/register",
|
|
1429
|
+
"portal-servers/remove",
|
|
1430
|
+
"messages"
|
|
1431
|
+
];
|
|
633
1432
|
/** The arms every agent call can end in BEFORE the route answers: the contract could not be served,
|
|
634
1433
|
* or the tenant's contract does not list the route. */
|
|
635
1434
|
export type AgentCallFailure = ContractFailure | {
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
1435
|
+
outcome: "route-unknown";
|
|
1436
|
+
route: AgentRouteName;
|
|
1437
|
+
routes: string[];
|
|
639
1438
|
} | TenantClientFailure;
|
|
640
1439
|
/** The proxy's shared write outcome (`ProxiedWriteResult`), on the wire with the HTTP status. The
|
|
641
1440
|
* `rejected` arm is the shared {@link TenantClientFailure} one. */
|
|
642
1441
|
export type ProxiedWriteOutcome = {
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
1442
|
+
outcome: "succeeded";
|
|
1443
|
+
status: number;
|
|
1444
|
+
attempts: number;
|
|
646
1445
|
} | {
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
1446
|
+
outcome: "exhausted";
|
|
1447
|
+
status: number;
|
|
1448
|
+
attempts: number;
|
|
1449
|
+
lastError: string;
|
|
651
1450
|
};
|
|
652
1451
|
export interface IssueStateInput {
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
1452
|
+
issueId: string;
|
|
1453
|
+
/** The Linear state id — resolve it by SLOT through {@link stageIdForSlot}, never by name. */
|
|
1454
|
+
stateId: string;
|
|
656
1455
|
}
|
|
657
1456
|
export type IssueStateResult = ProxiedWriteOutcome | AgentCallFailure;
|
|
658
1457
|
export interface IssueLabelInput {
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
1458
|
+
issueId: string;
|
|
1459
|
+
labelIds: string[];
|
|
1460
|
+
mode: "add" | "remove";
|
|
662
1461
|
}
|
|
663
1462
|
export interface IssueLabelItemResult {
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
1463
|
+
labelId: string;
|
|
1464
|
+
/** `already-absent` — a remove that found nothing to remove; counts as satisfied, not as a landing. */
|
|
1465
|
+
outcome: "succeeded" | "rejected" | "exhausted" | "already-absent";
|
|
1466
|
+
attempts: number;
|
|
1467
|
+
reason?: string;
|
|
1468
|
+
lastError?: string;
|
|
670
1469
|
}
|
|
671
1470
|
export type IssueLabelResult = {
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
1471
|
+
outcome: "succeeded" | "failed";
|
|
1472
|
+
status: number;
|
|
1473
|
+
results: IssueLabelItemResult[];
|
|
675
1474
|
} | AgentCallFailure;
|
|
676
1475
|
export interface IssueCommentInput {
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
1476
|
+
issueId: string;
|
|
1477
|
+
body: string;
|
|
1478
|
+
parentId?: string;
|
|
1479
|
+
/** The agent display name (`botActor.userDisplayName`). Absent means the plain app actor. */
|
|
1480
|
+
createAsUser?: string;
|
|
682
1481
|
}
|
|
683
1482
|
export type IssueCommentResult = ProxiedWriteOutcome | AgentCallFailure;
|
|
684
1483
|
export interface IssueCreateInput {
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
1484
|
+
teamId: string;
|
|
1485
|
+
title: string;
|
|
1486
|
+
description?: string;
|
|
1487
|
+
labelIds?: string[];
|
|
1488
|
+
assigneeId?: string;
|
|
1489
|
+
parentId?: string;
|
|
1490
|
+
/** Linear's 0–4 scale. */
|
|
1491
|
+
priority?: 0 | 1 | 2 | 3 | 4;
|
|
1492
|
+
stateId?: string;
|
|
1493
|
+
projectId?: string;
|
|
1494
|
+
createAsUser?: string;
|
|
696
1495
|
}
|
|
697
1496
|
export type IssueCreateResult = {
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
1497
|
+
outcome: "succeeded";
|
|
1498
|
+
status: number;
|
|
1499
|
+
attempts: number;
|
|
1500
|
+
identifier: string;
|
|
1501
|
+
id: string;
|
|
1502
|
+
url: string | null;
|
|
704
1503
|
} | Extract<ProxiedWriteOutcome, {
|
|
705
|
-
|
|
1504
|
+
outcome: "exhausted";
|
|
706
1505
|
}> | AgentCallFailure;
|
|
707
1506
|
/** Exactly one of `issueId`/`commentId` — enforced by the SERVER (a 400 `rejected`); the client
|
|
708
1507
|
* sends what it is given rather than carrying a second copy of the rule. */
|
|
709
1508
|
export interface ReactionInput {
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
1509
|
+
issueId?: string;
|
|
1510
|
+
commentId?: string;
|
|
1511
|
+
/** Defaults server-side to the fleet's claim emoji. */
|
|
1512
|
+
emoji?: string;
|
|
1513
|
+
mode?: "add" | "remove";
|
|
1514
|
+
createAsUser?: string;
|
|
716
1515
|
}
|
|
717
1516
|
export type ReactionResult = {
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
1517
|
+
outcome: "succeeded";
|
|
1518
|
+
status: number;
|
|
1519
|
+
attempts: number;
|
|
1520
|
+
reactionId: string | null;
|
|
1521
|
+
alreadyPresent?: true;
|
|
1522
|
+
userId?: string | null;
|
|
724
1523
|
} | {
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
1524
|
+
outcome: "succeeded";
|
|
1525
|
+
status: number;
|
|
1526
|
+
attempts: number;
|
|
1527
|
+
alreadyAbsent: true;
|
|
1528
|
+
removed: 0;
|
|
730
1529
|
} | {
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
1530
|
+
outcome: "succeeded";
|
|
1531
|
+
status: number;
|
|
1532
|
+
attempts: number;
|
|
1533
|
+
removed: number;
|
|
735
1534
|
} | Extract<ProxiedWriteOutcome, {
|
|
736
|
-
|
|
1535
|
+
outcome: "exhausted";
|
|
737
1536
|
}> | AgentCallFailure;
|
|
738
1537
|
export interface ProxiedAttachment {
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
1538
|
+
id: string;
|
|
1539
|
+
url: string;
|
|
1540
|
+
title: string | null;
|
|
1541
|
+
metadata: Record<string, unknown> | null;
|
|
743
1542
|
}
|
|
744
1543
|
export interface AttachmentInput {
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
1544
|
+
issueId: string;
|
|
1545
|
+
/** The upsert key — Linear dedups attachments by url. */
|
|
1546
|
+
url: string;
|
|
1547
|
+
title: string;
|
|
1548
|
+
metadata: Record<string, unknown>;
|
|
750
1549
|
}
|
|
751
1550
|
export type AttachmentResult = (ProxiedWriteOutcome & {
|
|
752
|
-
|
|
1551
|
+
attachment?: ProxiedAttachment;
|
|
753
1552
|
}) | AgentCallFailure;
|
|
754
1553
|
export type AttachmentsResult = {
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
1554
|
+
outcome: "succeeded";
|
|
1555
|
+
status: number;
|
|
1556
|
+
attachments: ProxiedAttachment[];
|
|
758
1557
|
}
|
|
759
1558
|
/** The proxy answers an absent issue as a 404 `rejected` — "no attachments" and "no such issue"
|
|
760
1559
|
* are different answers, and this arm keeps them apart. */
|
|
761
1560
|
| {
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
1561
|
+
outcome: "not-found";
|
|
1562
|
+
status: 404;
|
|
1563
|
+
reason: string;
|
|
765
1564
|
} | {
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
1565
|
+
outcome: "unauthorized-upstream";
|
|
1566
|
+
status: number;
|
|
1567
|
+
reason: string;
|
|
769
1568
|
} | AgentCallFailure;
|
|
770
1569
|
export interface SessionPlanEntry {
|
|
771
|
-
|
|
772
|
-
|
|
1570
|
+
content: string;
|
|
1571
|
+
status: string;
|
|
773
1572
|
}
|
|
774
1573
|
export type SessionActivity = {
|
|
775
|
-
|
|
776
|
-
|
|
1574
|
+
type: "thought" | "response";
|
|
1575
|
+
body: string;
|
|
777
1576
|
} | {
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
1577
|
+
type: "error";
|
|
1578
|
+
body: string;
|
|
1579
|
+
reasonCode?: string;
|
|
781
1580
|
} | {
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
1581
|
+
type: "action";
|
|
1582
|
+
action: string;
|
|
1583
|
+
parameter: string;
|
|
1584
|
+
result?: string;
|
|
786
1585
|
};
|
|
787
1586
|
export interface SessionInput {
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
1587
|
+
issueId: string;
|
|
1588
|
+
plan?: SessionPlanEntry[];
|
|
1589
|
+
activity?: SessionActivity;
|
|
1590
|
+
externalUrls?: {
|
|
1591
|
+
label: string;
|
|
1592
|
+
url: string;
|
|
1593
|
+
}[];
|
|
795
1594
|
}
|
|
796
1595
|
/** The proxy's shared write body as it appears INSIDE the session composite (no status of its own). */
|
|
797
1596
|
export type ProxiedWriteBody = {
|
|
798
|
-
|
|
799
|
-
|
|
1597
|
+
outcome: "succeeded";
|
|
1598
|
+
attempts: number;
|
|
800
1599
|
} | {
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
1600
|
+
outcome: "exhausted";
|
|
1601
|
+
attempts: number;
|
|
1602
|
+
lastError: string;
|
|
804
1603
|
} | {
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
1604
|
+
outcome: "rejected";
|
|
1605
|
+
attempts: number;
|
|
1606
|
+
reason: string;
|
|
808
1607
|
};
|
|
809
1608
|
export type EnsureSessionOutcome = {
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
1609
|
+
outcome: "reused";
|
|
1610
|
+
sessionId: string;
|
|
1611
|
+
attempts: number;
|
|
813
1612
|
} | {
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
1613
|
+
outcome: "created";
|
|
1614
|
+
sessionId: string;
|
|
1615
|
+
attempts: number;
|
|
817
1616
|
} | {
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
1617
|
+
outcome: "rejected";
|
|
1618
|
+
attempts: number;
|
|
1619
|
+
reason: string;
|
|
821
1620
|
} | {
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
1621
|
+
outcome: "exhausted";
|
|
1622
|
+
attempts: number;
|
|
1623
|
+
lastError: string;
|
|
825
1624
|
};
|
|
826
1625
|
/** ⚠️ The status is the WORST of the sub-outcomes (rejected 400 > exhausted 502 > 200); read
|
|
827
1626
|
* `session`/`plan`/`externalUrls`/`activity` individually — a 502 here can carry a reused session. */
|
|
828
1627
|
export type SessionResult = {
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
1628
|
+
outcome: "ok";
|
|
1629
|
+
status: number;
|
|
1630
|
+
session: EnsureSessionOutcome;
|
|
1631
|
+
plan?: ProxiedWriteBody;
|
|
1632
|
+
externalUrls?: ProxiedWriteBody;
|
|
1633
|
+
activity?: ProxiedWriteBody;
|
|
835
1634
|
} | AgentCallFailure;
|
|
836
1635
|
export interface AskInput {
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
1636
|
+
teamId: string;
|
|
1637
|
+
title: string;
|
|
1638
|
+
context: string;
|
|
1639
|
+
defaultIfSilent: string;
|
|
1640
|
+
/** The idempotency key — a re-raise under the same key answers the existing ask. */
|
|
1641
|
+
askKey: string;
|
|
1642
|
+
options?: string[];
|
|
1643
|
+
blocks?: string[];
|
|
1644
|
+
nothingToBlock?: true;
|
|
1645
|
+
target?: {
|
|
1646
|
+
membershipId: string;
|
|
1647
|
+
} | {
|
|
1648
|
+
email: string;
|
|
1649
|
+
};
|
|
851
1650
|
}
|
|
852
1651
|
export type AskResult = {
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
1652
|
+
outcome: "created";
|
|
1653
|
+
status: number;
|
|
1654
|
+
askId: string;
|
|
1655
|
+
identifier: string;
|
|
1656
|
+
existing?: true;
|
|
858
1657
|
} | {
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
1658
|
+
outcome: "created-but-relations-partial";
|
|
1659
|
+
status: number;
|
|
1660
|
+
askId: string;
|
|
1661
|
+
identifier: string;
|
|
1662
|
+
failedBlocks: string[];
|
|
1663
|
+
existing?: true;
|
|
865
1664
|
} | {
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
1665
|
+
outcome: "exhausted";
|
|
1666
|
+
status: number;
|
|
1667
|
+
lastError: string;
|
|
869
1668
|
} | AgentCallFailure;
|
|
870
1669
|
export interface AskAcceptInput {
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
1670
|
+
askIssueId: string;
|
|
1671
|
+
answerCommentId: string;
|
|
1672
|
+
/** A single line of at most 64 characters. */
|
|
1673
|
+
acceptedByRole?: string;
|
|
875
1674
|
}
|
|
876
1675
|
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";
|
|
877
1676
|
export type AskAcceptResult = {
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
1677
|
+
outcome: "recorded";
|
|
1678
|
+
status: number;
|
|
1679
|
+
askIdentifier: string;
|
|
1680
|
+
decisionSummary: string;
|
|
1681
|
+
failedBlockedComments: string[];
|
|
1682
|
+
resume: unknown[];
|
|
1683
|
+
unblock?: unknown;
|
|
885
1684
|
} | {
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
1685
|
+
outcome: "refused";
|
|
1686
|
+
status: number;
|
|
1687
|
+
reason: AskAcceptRefusal;
|
|
889
1688
|
} | {
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
1689
|
+
outcome: "record-failed";
|
|
1690
|
+
status: number;
|
|
1691
|
+
reason: string;
|
|
1692
|
+
resume?: unknown[];
|
|
894
1693
|
} | AgentCallFailure;
|
|
895
1694
|
/** Both project-repository routes take the same address: a repository plus ONE of project /
|
|
896
1695
|
* teamKey / teamId. This route is NOT accounted against the write budget
|
|
897
1696
|
* (`takesWriteBudgetUnit: false`); it is gated on an admin/owner personal key or an
|
|
898
1697
|
* organization key carrying `mirror:write`. */
|
|
899
1698
|
export interface ProjectRepositoryInput {
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
1699
|
+
/** `owner/name`, sent VERBATIM. ⚠️ The cloud validates the trimmed halves but stores what it
|
|
1700
|
+
* was sent (CTC-2503 re-validation) — trim before calling if the value came from a human. */
|
|
1701
|
+
repository: string;
|
|
1702
|
+
project?: string;
|
|
1703
|
+
teamKey?: string;
|
|
1704
|
+
teamId?: string;
|
|
906
1705
|
}
|
|
907
1706
|
export interface RegisteredProjectRepository {
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
1707
|
+
repoId: string;
|
|
1708
|
+
owner: string;
|
|
1709
|
+
name: string;
|
|
1710
|
+
/** Present when the `repos` row carries one (e.g. `"paused"`). */
|
|
1711
|
+
status?: string;
|
|
913
1712
|
}
|
|
914
1713
|
export type ProjectRepositoryRegisterResult = {
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
1714
|
+
outcome: "registered";
|
|
1715
|
+
status: number;
|
|
1716
|
+
registered: RegisteredProjectRepository;
|
|
1717
|
+
created: boolean;
|
|
1718
|
+
linked: boolean;
|
|
920
1719
|
}
|
|
921
1720
|
/** The project is absent, archived, or another tenant's — one answer for all three. */
|
|
922
1721
|
| {
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
1722
|
+
outcome: "not-found";
|
|
1723
|
+
status: 404;
|
|
1724
|
+
reason: string;
|
|
926
1725
|
} | AgentCallFailure;
|
|
927
1726
|
export type ProjectRepositoryRemoveResult =
|
|
928
1727
|
/** `removed: false` is idempotent success, not a failure — there was nothing to unlink. */
|
|
929
1728
|
{
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
1729
|
+
outcome: "removed";
|
|
1730
|
+
status: number;
|
|
1731
|
+
removed: boolean;
|
|
933
1732
|
} | {
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
1733
|
+
outcome: "not-found";
|
|
1734
|
+
status: 404;
|
|
1735
|
+
reason: string;
|
|
937
1736
|
} | AgentCallFailure;
|
|
938
1737
|
/** CTC-3254 — `POST …/agent/messages` (CTC-3121): a message to the agent running a ticket's current
|
|
939
1738
|
* phase, which it reads as one more user turn when its current turn ends. The SENDER comes from the
|
|
@@ -942,77 +1741,77 @@ export type ProjectRepositoryRemoveResult =
|
|
|
942
1741
|
* message). To address a roster `agentId`, resolve it to the ticket its worker runs (the roster
|
|
943
1742
|
* entry's `scope`) before calling; the cloud does not resolve roster ids. */
|
|
944
1743
|
export interface AgentMessageInput {
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
1744
|
+
/** The ticket identifier, e.g. `CTC-42`. */
|
|
1745
|
+
ticket: string;
|
|
1746
|
+
/** At most 4096 bytes of UTF-8. */
|
|
1747
|
+
body: string;
|
|
1748
|
+
/** Defaults to `note` on the server. */
|
|
1749
|
+
kind?: "note" | "question" | "answer";
|
|
1750
|
+
/** The id of the earlier message this one answers. */
|
|
1751
|
+
re?: string;
|
|
953
1752
|
}
|
|
954
1753
|
export type AgentMessageResult =
|
|
955
1754
|
/** The running session reads it at its next turn boundary. */
|
|
956
1755
|
{
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
1756
|
+
outcome: "accepted";
|
|
1757
|
+
status: number;
|
|
1758
|
+
messageId: string;
|
|
1759
|
+
ticket: string;
|
|
1760
|
+
delivery: "next-turn";
|
|
962
1761
|
}
|
|
963
1762
|
/** It waits for the ticket's next phase, and why: nothing is running, the run has no channel for it
|
|
964
1763
|
* mid-phase, or the run is not reading messages. */
|
|
965
1764
|
| {
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
1765
|
+
outcome: "accepted";
|
|
1766
|
+
status: number;
|
|
1767
|
+
messageId: string;
|
|
1768
|
+
ticket: string;
|
|
1769
|
+
delivery: "next-phase";
|
|
1770
|
+
reason: "no-live-phase" | "run-cannot-receive" | "run-not-reading";
|
|
972
1771
|
}
|
|
973
1772
|
/** The ticket already holds the per-ticket cap of pending messages (20). */
|
|
974
1773
|
| {
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
1774
|
+
outcome: "refused";
|
|
1775
|
+
status: 409;
|
|
1776
|
+
reason: "too_many_pending";
|
|
978
1777
|
} | AgentCallFailure;
|
|
979
1778
|
/** Upstream auth references only account vault names, never credential values. */
|
|
980
1779
|
export type PortalServerAuth = {
|
|
981
|
-
|
|
1780
|
+
kind: "none";
|
|
982
1781
|
} | {
|
|
983
|
-
|
|
984
|
-
|
|
1782
|
+
kind: "bearer";
|
|
1783
|
+
secretName: string;
|
|
985
1784
|
} | {
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
1785
|
+
kind: "headers";
|
|
1786
|
+
headers: {
|
|
1787
|
+
name: string;
|
|
1788
|
+
secretName: string;
|
|
1789
|
+
}[];
|
|
991
1790
|
};
|
|
992
1791
|
export interface PortalServerRegisterInput {
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
1792
|
+
name: string;
|
|
1793
|
+
url: string;
|
|
1794
|
+
auth: PortalServerAuth;
|
|
996
1795
|
}
|
|
997
1796
|
export interface PortalServer extends PortalServerRegisterInput {
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1797
|
+
id: string;
|
|
1798
|
+
/** Custom servers stay pending until a server-side account admin approves them. */
|
|
1799
|
+
status: "ready" | "pending" | "pending_egress_guard";
|
|
1001
1800
|
}
|
|
1002
1801
|
export type PortalServerRegisterResult = {
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1802
|
+
outcome: "registered";
|
|
1803
|
+
status: number;
|
|
1804
|
+
server: PortalServer;
|
|
1006
1805
|
} | AgentCallFailure;
|
|
1007
1806
|
export type PortalServersResult = {
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1807
|
+
outcome: "ok";
|
|
1808
|
+
status: number;
|
|
1809
|
+
servers: PortalServer[];
|
|
1011
1810
|
} | AgentCallFailure;
|
|
1012
1811
|
export type PortalServerRemoveResult = {
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1812
|
+
outcome: "removed";
|
|
1813
|
+
status: number;
|
|
1814
|
+
removed: boolean;
|
|
1016
1815
|
} | AgentCallFailure;
|
|
1017
1816
|
/** The next-page keyset token on `/issues` and `/pulls`; ABSENT on the last page. */
|
|
1018
1817
|
export declare const NEXT_CURSOR_HEADER = "X-Mirror-Next-Cursor";
|
|
@@ -1034,130 +1833,135 @@ export declare function normalizeBaseUrl(baseUrl: string): string;
|
|
|
1034
1833
|
export declare function createTenantClient(opts: TenantClientOptions): TenantClient;
|
|
1035
1834
|
/** The client. See {@link createTenantClient}. */
|
|
1036
1835
|
export interface TenantClient {
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
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
|
-
|
|
1836
|
+
/** Personal bearer team setup. The key's tenant and admin role are enforced by the cloud. */
|
|
1837
|
+
teamWorkflow: {
|
|
1838
|
+
teams(): Promise<TeamWorkflowResult<TeamList>>;
|
|
1839
|
+
get(team: string): Promise<TeamWorkflowResult<TeamWorkflowView>>;
|
|
1840
|
+
check(team: string): Promise<TeamWorkflowResult<{
|
|
1841
|
+
readiness: TeamWorkflowReadiness;
|
|
1842
|
+
ask: Record<string, unknown>;
|
|
1843
|
+
}>>;
|
|
1844
|
+
save(input: TeamMappingSaveInput): Promise<TeamWorkflowResult<TeamWorkflowSaveResult>>;
|
|
1845
|
+
adoptPreview(team: string): Promise<TeamWorkflowResult<TeamAdoptResult>>;
|
|
1846
|
+
adoptApply(team: string, planHash: string): Promise<TeamWorkflowResult<TeamAdoptResult>>;
|
|
1847
|
+
undoPreview(team: string): Promise<TeamWorkflowResult<TeamUndoPreview>>;
|
|
1848
|
+
undoApply(team: string, undoHash: string): Promise<TeamWorkflowResult<TeamUndoResult>>;
|
|
1849
|
+
migratePreview(team: string, choices?: TeamMigrationChoice[]): Promise<TeamWorkflowResult<{
|
|
1850
|
+
preview: TeamMigrationPlan;
|
|
1851
|
+
}>>;
|
|
1852
|
+
migrateChunk(team: string, migrationHash: string, choices?: TeamMigrationChoice[]): Promise<TeamWorkflowResult<TeamMigrationChunk>>;
|
|
1853
|
+
migrateRetire(team: string, migrationHash: string, choices?: TeamMigrationChoice[]): Promise<TeamWorkflowResult<TeamMigrationRetire>>;
|
|
1854
|
+
};
|
|
1855
|
+
/** A project's WIP limit (CTC-3780). Any active member reads; personal key or CLI login. */
|
|
1856
|
+
getProjectWipLimit(team: string): Promise<TeamWorkflowResult<ProjectWipLimit>>;
|
|
1857
|
+
/** Sets it, clamped by the cloud to 0..9999; `null` returns the project to the default. Admin or
|
|
1858
|
+
* owner only: anyone else gets `outcome: "forbidden"`. Answers the value after the write. */
|
|
1859
|
+
setProjectWipLimit(team: string, limit: number | null): Promise<TeamWorkflowResult<ProjectWipLimit>>;
|
|
1860
|
+
/** Self-service unmatched identity recovery. Personal credentials only. */
|
|
1861
|
+
linearIdentity: {
|
|
1862
|
+
get(): Promise<LinearIdentityResult>;
|
|
1863
|
+
/** Records only the authenticated member's choice. Automatically resolved identities refuse. */
|
|
1864
|
+
set(linearUserId: string): Promise<LinearIdentityResult>;
|
|
1865
|
+
};
|
|
1866
|
+
/** The tenant's fact document, cached per its own `cache` policy. */
|
|
1867
|
+
contract(opts?: ContractOptions): Promise<ContractResult>;
|
|
1868
|
+
/** `GET /api/v1/me` — the account this key belongs to. */
|
|
1869
|
+
me(): Promise<MeResult>;
|
|
1870
|
+
/** The generic authed-request escape hatch (CTC-2132) — for a route nobody enumerated. `path` MUST
|
|
1871
|
+
* be an absolute path under this client's own origin; see {@link RawRequest}. */
|
|
1872
|
+
request(req: RawRequest): Promise<RawRequestResult>;
|
|
1873
|
+
/** User-scoped GitHub and Linear OAuth grants. Use a personal key or device-login credential. */
|
|
1874
|
+
personalConnections: {
|
|
1875
|
+
/** Returns a short lived URL to open in the user's browser. Does not perform consent. */
|
|
1876
|
+
start(provider: PersonalConnectionProvider): Promise<PersonalConnectionStartResult>;
|
|
1877
|
+
/** A provider outage is `unavailable`, never `absent`. */
|
|
1878
|
+
status(provider: PersonalConnectionProvider): Promise<PersonalConnectionStatusResult>;
|
|
1879
|
+
};
|
|
1880
|
+
issues: {
|
|
1881
|
+
/** `GET /api/v1/issues` — keyset-paged; follow `nextCursor` until it is `null`. */
|
|
1882
|
+
list(params?: IssueListParams): Promise<IssueListResult>;
|
|
1883
|
+
/** `GET /api/v1/issues/:identifier`. */
|
|
1884
|
+
get(identifier: string): Promise<IssueGetResult>;
|
|
1885
|
+
/** `GET /api/v1/issues/:id/execution` — the ticket's own execution/telemetry report. */
|
|
1886
|
+
execution(identifier: string): Promise<TicketExecutionResult>;
|
|
1887
|
+
};
|
|
1888
|
+
pulls: {
|
|
1889
|
+
list(params?: PullListParams): Promise<PullListResult>;
|
|
1890
|
+
/** `GET /api/v1/pulls/:id` — `id` is the PR's GitHub node id. */
|
|
1891
|
+
get(nodeId: string): Promise<PullGetResult>;
|
|
1892
|
+
};
|
|
1893
|
+
projects: {
|
|
1894
|
+
list(params?: ProjectListParams): Promise<ProjectListResult>;
|
|
1895
|
+
};
|
|
1896
|
+
cycles: {
|
|
1897
|
+
/** `GET /api/v1/cycles` — the SDK's own structural row type; see {@link CycleRow}. */
|
|
1898
|
+
list(): Promise<CyclesListResult>;
|
|
1899
|
+
};
|
|
1900
|
+
/** `GET /api/v1/search?q=&limit=`. */
|
|
1901
|
+
search(params: SearchParams): Promise<SearchResult>;
|
|
1902
|
+
/** `GET /api/v1/workflow-stages` — tolerant of a `{stages,source?}` envelope or a bare array. */
|
|
1903
|
+
workflowStages(): Promise<WorkflowStagesResult>;
|
|
1904
|
+
changes: {
|
|
1905
|
+
/** `GET /api/v1/changes?since=` — NDJSON. ⛔ The returned generator MAY THROW mid-iteration; use
|
|
1906
|
+
* `list` for the nothing-throws, buffered alternative. */
|
|
1907
|
+
stream(params: {
|
|
1908
|
+
since: number | "head";
|
|
1909
|
+
signal?: AbortSignal;
|
|
1910
|
+
}): Promise<ChangesStreamResult>;
|
|
1911
|
+
/** The same feed, buffered — never throws; a mid-stream fault comes back as the `network` arm. */
|
|
1912
|
+
list(params: {
|
|
1913
|
+
since: number | "head";
|
|
1914
|
+
signal?: AbortSignal;
|
|
1915
|
+
}): Promise<ChangesListResult>;
|
|
1916
|
+
};
|
|
1917
|
+
snapshot: {
|
|
1918
|
+
/** `GET /api/v1/snapshot?head=1` — the cheap head probe; never falls back to a full snapshot. */
|
|
1919
|
+
head(): Promise<SnapshotHeadResult>;
|
|
1920
|
+
};
|
|
1921
|
+
/** The diagnosis / telemetry reads (CTC-2132, M2 addendum) — a customer delegate agent's own
|
|
1922
|
+
* "why is nothing moving" / "what is my fleet doing" answers. */
|
|
1923
|
+
diagnostics: {
|
|
1924
|
+
workEligibility(params?: {
|
|
1925
|
+
team?: string;
|
|
1926
|
+
}): Promise<WorkEligibilityResult>;
|
|
1927
|
+
dispatchQueue(params?: {
|
|
1928
|
+
team?: string;
|
|
1929
|
+
}): Promise<DispatchQueueResult>;
|
|
1930
|
+
fleetActivity(params?: {
|
|
1931
|
+
account?: string;
|
|
1932
|
+
}): Promise<FleetActivityResult>;
|
|
1933
|
+
agentRoster(): Promise<AgentRosterResult>;
|
|
1934
|
+
leaseAttributions(params: {
|
|
1935
|
+
ticket: string;
|
|
1936
|
+
phase: string;
|
|
1937
|
+
}): Promise<LeaseAttributionsResult>;
|
|
1938
|
+
codingAccounts(): Promise<CodingAccountsResult>;
|
|
1939
|
+
};
|
|
1940
|
+
/** The `/api/v1/agent/*` proxy — every tenant write as the app actor, path from `routes[]`. */
|
|
1941
|
+
agent: {
|
|
1942
|
+
issueState(input: IssueStateInput): Promise<IssueStateResult>;
|
|
1943
|
+
issueLabel(input: IssueLabelInput): Promise<IssueLabelResult>;
|
|
1944
|
+
issueComment(input: IssueCommentInput): Promise<IssueCommentResult>;
|
|
1945
|
+
issueCreate(input: IssueCreateInput): Promise<IssueCreateResult>;
|
|
1946
|
+
reaction(input: ReactionInput): Promise<ReactionResult>;
|
|
1947
|
+
attachment(input: AttachmentInput): Promise<AttachmentResult>;
|
|
1948
|
+
/** `GET …/attachments?issueId=` — the soft-CAS read-back; no write-budget unit. */
|
|
1949
|
+
attachments(input: {
|
|
1950
|
+
issueId: string;
|
|
1951
|
+
}): Promise<AttachmentsResult>;
|
|
1952
|
+
session(input: SessionInput): Promise<SessionResult>;
|
|
1953
|
+
ask(input: AskInput): Promise<AskResult>;
|
|
1954
|
+
askAccept(input: AskAcceptInput): Promise<AskAcceptResult>;
|
|
1955
|
+
portalServerRegister(input: PortalServerRegisterInput): Promise<PortalServerRegisterResult>;
|
|
1956
|
+
portalServers(): Promise<PortalServersResult>;
|
|
1957
|
+
portalServerRemove(input: {
|
|
1958
|
+
name: string;
|
|
1959
|
+
}): Promise<PortalServerRemoveResult>;
|
|
1960
|
+
projectRepositoryRegister(input: ProjectRepositoryInput): Promise<ProjectRepositoryRegisterResult>;
|
|
1961
|
+
projectRepositoryRemove(input: ProjectRepositoryInput): Promise<ProjectRepositoryRemoveResult>;
|
|
1962
|
+
/** `POST …/agent/messages` — send a ticket's running agent a message it reads at its next turn. */
|
|
1963
|
+
message(input: AgentMessageInput): Promise<AgentMessageResult>;
|
|
1964
|
+
};
|
|
1965
|
+
}
|
|
1966
|
+
|
|
1162
1967
|
export {};
|
|
1163
|
-
//# sourceMappingURL=tenant-client.d.ts.map
|