@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.
@@ -1,36 +1,802 @@
1
- import { type LinearIdentityResult } from "./linear-identity.js";
2
- import type { IssueDetailView, IssueView, ProjectView, PullDetailView, PullView } from "@catalyst-cloud/read-model";
3
- import { type AuthStrategy } from "./live-sync-client.js";
4
- import { type TenantContract } from "./tenant-contract.js";
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
- etag: string | null;
10
- /** Epoch ms of the fetch (or the last successful 304 revalidation). */
11
- fetchedAt: number;
12
- contractVersion: string;
13
- doc: TenantContract;
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
- get(): ContractCacheEntry | null | Promise<ContractCacheEntry | null>;
18
- set(entry: ContractCacheEntry): void | Promise<void>;
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
- /** The service origin (e.g. "https://staging.catalystcloud.dev"). A trailing slash is trimmed;
24
- * every route path is absolute under it. */
25
- baseUrl: string;
26
- /** Injectable for tests; defaults to the platform global. */
27
- fetch?: typeof fetch;
28
- /** Per-request deadline in ms. Default {@link DEFAULT_TIMEOUT_MS}. */
29
- timeoutMs?: number;
30
- /** Default {@link memoryContractCache}. */
31
- contractCache?: ContractCacheStore;
32
- /** Epoch ms; injectable so a test can cross the cache bounds without sleeping. */
33
- now?: () => number;
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
- /** A tenant or personal key. Most agent proxy writes require an organization key;
44
- * `teamWorkflow` and personal connection methods require the caller's own personal key or
45
- * device login. */
46
- key: string;
47
- auth?: never;
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
- auth: AuthStrategy;
50
- key?: never;
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
- outcome: "unauthorized";
60
- status: 401;
61
- reason: string;
62
- ref: string | null;
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
- outcome: "forbidden";
67
- status: 403;
68
- reason: string;
69
- required: string | null;
70
- account: string | null;
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
- outcome: "rate-limited";
76
- status: 429;
77
- reason: string;
78
- retryAfterSeconds: number | null;
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
- outcome: "rejected";
83
- status: number;
84
- reason: string;
85
- attempts?: number;
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
- outcome: "failed";
90
- status: number;
91
- reason: string;
855
+ outcome: "failed";
856
+ status: number;
857
+ reason: string;
92
858
  }
93
859
  /** fetch threw, or the deadline fired. */
94
860
  | {
95
- outcome: "network";
96
- reason: string;
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
- outcome: "shape";
101
- status: number;
102
- reason: string;
866
+ outcome: "shape";
867
+ status: number;
868
+ reason: string;
103
869
  }
104
870
  /** Any other non-2xx. */
105
871
  | {
106
- outcome: "http";
107
- status: number;
108
- reason: string;
872
+ outcome: "http";
873
+ status: number;
874
+ reason: string;
109
875
  };
110
876
  export type TeamWorkflowFailure = TenantClientFailure & {
111
- error?: string;
877
+ error?: string;
112
878
  };
113
879
  export type TeamWorkflowResult<T> = ({
114
- outcome: "ok";
115
- status: number;
880
+ outcome: "ok";
881
+ status: number;
116
882
  } & T) | TeamWorkflowFailure;
117
883
  export interface TeamWorkflowReadiness {
118
- teamId: string;
119
- teamKey: string;
120
- teamName: string;
121
- status: "ready" | "degraded" | "blocked" | "unchecked";
122
- checkedAt?: number;
123
- checks: readonly TeamReadinessCheck[];
124
- workflowRev: number;
125
- [field: string]: unknown;
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
- id: string;
129
- state: "pass" | "fail" | "unknown";
130
- reason?: string;
131
- count?: number;
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
- id: string;
136
- name: string;
137
- type: string;
138
- position: number;
901
+ id: string;
902
+ name: string;
903
+ type: string;
904
+ position: number;
139
905
  }
140
906
  export interface TeamWorkflowRow {
141
- slot: TeamWorkflowSlot;
142
- linearStateId: string | null;
143
- linearStateName?: string;
144
- linearStateType?: string;
145
- source?: "created" | "matched" | "chosen";
146
- stateStillExists?: boolean;
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
- mode: "mapped-existing" | "adopted-recommended" | "mixed" | null;
150
- gitAutomation: "off" | "managed";
151
- mappedSlots: number;
152
- mappedLoadBearingSlots: number;
153
- mirrored: boolean | null;
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
- teams: TeamWorkflowSummary[];
157
- canManage: boolean;
158
- liveTeamRead: {
159
- attempted: boolean;
160
- error: string | null;
161
- reason?: "no-teams-known" | "unnamed-team";
162
- };
163
- everChecked: boolean;
164
- mirrorRead: boolean;
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
- config: {
168
- teamId: string;
169
- mode: "mapped-existing" | "adopted-recommended" | "mixed";
170
- gitAutomation: "off" | "managed";
171
- workflowRev: number;
172
- };
173
- rows: readonly TeamWorkflowRow[];
174
- stages: readonly TeamWorkflowStage[];
175
- stageSource: "linear" | "mirror" | "none";
176
- readiness: TeamWorkflowReadiness;
177
- mappingHash: string;
178
- checklist: string[] | null;
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
- readiness: TeamWorkflowReadiness | null;
948
+ readiness: TeamWorkflowReadiness | null;
183
949
  };
184
950
  export interface TeamMappingRowInput {
185
- slot: TeamWorkflowSlot;
186
- linearStateId: string | null;
187
- source?: "created" | "matched" | "chosen";
951
+ slot: TeamWorkflowSlot;
952
+ linearStateId: string | null;
953
+ source?: "created" | "matched" | "chosen";
188
954
  }
189
955
  export interface TeamMappingSaveInput {
190
- team: string;
191
- expectedMappingHash: string;
192
- mode?: "mapped-existing" | "adopted-recommended" | "mixed";
193
- gitAutomation?: "off" | "managed";
194
- rows: TeamMappingRowInput[];
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
- sourceStateId: string;
198
- destinationStateId: string;
963
+ sourceStateId: string;
964
+ destinationStateId: string;
199
965
  }
200
966
  export interface TeamMigrationSource {
201
- stateId: string;
202
- name: string;
203
- type: string;
204
- ticketCount: number;
205
- destinationStateId: string | null;
206
- destinationSlot: TeamWorkflowSlot | null;
207
- because: "unique-type" | "terminal-family" | "chosen" | null;
208
- outcome: "ready" | "needs-a-choice" | "empty" | "moved" | "partially-moved" | "protected" | "retired-in-catalyst";
209
- reason?: string;
210
- retiredAt?: number;
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
- teamId: string;
214
- sources: readonly TeamMigrationSource[];
215
- migrationHash: string;
216
- overLimit: boolean;
217
- issueCount: number;
218
- retireLogReadable: boolean;
219
- /** A false value means the server will refuse stage moves and retirement. Older servers omit it. */
220
- actionsAvailable?: boolean;
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
- teamId: string;
224
- teamKey: string;
225
- mode: "adopted-recommended" | "mapped-existing" | "mixed" | null;
226
- stages: readonly {
227
- name: string;
228
- type: string;
229
- outcome: string;
230
- stateId?: string;
231
- reason?: string;
232
- }[];
233
- planHash: string;
234
- unfilledLoadBearing: readonly unknown[];
235
- provenanceGaps: readonly unknown[];
236
- labels: readonly {
237
- name: string;
238
- outcome: string;
239
- reason?: string;
240
- }[];
241
- labelProvenanceGaps: readonly unknown[];
242
- labelsNotCreated: readonly unknown[];
243
- checklist?: string[];
244
- readiness?: TeamWorkflowReadiness | null;
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
- teamId: string;
248
- mode: "preview";
249
- candidates: readonly {
250
- stateId: string;
251
- name: string | null;
252
- }[];
253
- undoHash: string;
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
- archived: readonly {
257
- stateId: string;
258
- name: string | null;
259
- }[];
260
- kept: readonly {
261
- stateId: string;
262
- name: string | null;
263
- reason: string;
264
- }[];
265
- failed: readonly {
266
- stateId: string;
267
- name: string | null;
268
- reason: string;
269
- }[];
270
- readiness: TeamWorkflowReadiness | null;
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
- teamId: string;
274
- sources: readonly TeamMigrationSource[];
275
- remaining: number;
276
- migrationHash: string;
277
- moved: number;
278
- readiness: TeamWorkflowReadiness | null;
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
- retired: readonly {
282
- stateId: string;
283
- name: string;
284
- }[];
285
- kept: readonly {
286
- stateId: string;
287
- name: string;
288
- reason: string;
289
- }[];
290
- failed: readonly {
291
- stateId: string;
292
- name: string;
293
- reason: string;
294
- }[];
295
- logGaps: readonly {
296
- stateId: string;
297
- reason: string;
298
- }[];
299
- readiness: TeamWorkflowReadiness | null;
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
- outcome: "ok";
303
- doc: TenantContract;
304
- /** `cache` — served inside `maxAgeSeconds` (or on a network failure inside
305
- * `staleRefusalSeconds`); `revalidated` — a 304; `network` — a fresh 200. */
306
- source: "cache" | "network" | "revalidated";
307
- ageSeconds: number;
308
- etag: string | null;
309
- contractVersion: string;
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
- outcome: "stale";
315
- ageSeconds: number;
316
- staleRefusalSeconds: number;
317
- cause: TenantClientFailure;
1096
+ outcome: "stale";
1097
+ ageSeconds: number;
1098
+ staleRefusalSeconds: number;
1099
+ cause: TenantClientFailure;
318
1100
  }
319
1101
  /** `offline: true` with nothing cached. */
320
1102
  | {
321
- outcome: "missing";
322
- reason: string;
1103
+ outcome: "missing";
1104
+ reason: string;
323
1105
  } | TenantClientFailure;
324
1106
  export type ContractFailure = Exclude<ContractResult, {
325
- outcome: "ok";
1107
+ outcome: "ok";
326
1108
  }>;
327
1109
  export interface ContractOptions {
328
- /** Revalidate even inside `maxAgeSeconds`. */
329
- refresh?: boolean;
330
- /** Never touch the network: the cache if any, else `missing`. */
331
- offline?: boolean;
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
- readonly [PAGE_CURSOR]: true;
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
- after?: PageCursor;
347
- limit?: number;
348
- teamId?: string;
349
- teamKey?: string;
350
- /** An unrecognised value is no narrowing server-side, never a 400 — so the type is closed here. */
351
- state?: "active" | "backlog" | "done";
352
- /** Linear's 1..4. */
353
- priority?: 1 | 2 | 3 | 4;
354
- assigneeId?: string;
355
- assignee?: string;
356
- cycle?: string;
357
- blocked?: "ask";
358
- /** `"priority"` orders the FULL scoped set by urgency; absent keeps `updated_at DESC`. */
359
- sort?: "priority";
360
- waitingOn?: string;
361
- waitingMode?: "asks" | "all";
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
- /** The next page's `after`, or `null` on the last page (the header is absent there). */
365
- nextCursor: PageCursor | null;
366
- /** The scope's FULL row count (`X-Mirror-Total`), `null` when the server did not send it. */
367
- total: number | null;
368
- /** The feed head seq this page is consistent with (`X-Mirror-Cursor`). */
369
- head: number | null;
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
- outcome: "ok";
373
- rows: IssueView[];
1154
+ outcome: "ok";
1155
+ rows: IssueView[];
374
1156
  } & KeysetPageMeta) | TenantClientFailure;
375
1157
  export type IssueGetResult = {
376
- outcome: "ok";
377
- issue: IssueDetailView;
378
- head: number | null;
1158
+ outcome: "ok";
1159
+ issue: IssueDetailView;
1160
+ head: number | null;
379
1161
  } | {
380
- outcome: "not-found";
381
- status: 404;
1162
+ outcome: "not-found";
1163
+ status: 404;
382
1164
  } | TenantClientFailure;
383
1165
  export interface PullListParams {
384
- after?: PageCursor;
385
- limit?: number;
386
- /** `owner/name`. */
387
- repo?: string;
388
- state?: "open" | "merged";
389
- blocked?: "ask";
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
- outcome: "ok";
393
- rows: PullView[];
394
- blockedOnAskTotal: number | null;
1174
+ outcome: "ok";
1175
+ rows: PullView[];
1176
+ blockedOnAskTotal: number | null;
395
1177
  } & KeysetPageMeta) | TenantClientFailure;
396
1178
  export type PullGetResult = {
397
- outcome: "ok";
398
- pull: PullDetailView;
1179
+ outcome: "ok";
1180
+ pull: PullDetailView;
399
1181
  } | {
400
- outcome: "not-found";
401
- status: 404;
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
- limit?: number;
406
- offset?: number;
1187
+ limit?: number;
1188
+ offset?: number;
407
1189
  }
408
1190
  export type ProjectListResult = {
409
- outcome: "ok";
410
- rows: ProjectView[];
411
- head: number | null;
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
- outcome: "ok";
416
- account: string;
417
- slug: string;
418
- name: string;
419
- permissions: string[] | null;
420
- principal: "service" | "session";
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
- readonly identifier?: string;
429
- readonly [key: string]: unknown;
1210
+ readonly identifier?: string;
1211
+ readonly [key: string]: unknown;
430
1212
  }
431
1213
  export type TicketExecutionResult = {
432
- outcome: "ok";
433
- status: 200;
434
- report: TicketExecutionReport;
1214
+ outcome: "ok";
1215
+ status: 200;
1216
+ report: TicketExecutionReport;
435
1217
  } | {
436
- outcome: "not-found";
437
- status: 404;
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
- readonly team?: string;
443
- readonly [key: string]: unknown;
1224
+ readonly team?: string;
1225
+ readonly [key: string]: unknown;
444
1226
  }
445
1227
  export type WorkEligibilityResult = {
446
- outcome: "ok";
447
- status: 200;
448
- report: WorkEligibilityReport;
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
- readonly entries: readonly Record<string, unknown>[];
453
- readonly [key: string]: unknown;
1234
+ readonly entries: readonly Record<string, unknown>[];
1235
+ readonly [key: string]: unknown;
454
1236
  }
455
1237
  export type DispatchQueueResult = {
456
- outcome: "ok";
457
- status: 200;
458
- queue: DispatchQueueEnvelope;
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
- outcome: "ok";
463
- status: 200;
464
- rows: readonly Record<string, unknown>[];
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
- outcome: "ok";
469
- status: 200;
470
- rows: readonly Record<string, unknown>[];
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
- outcome: "ok";
476
- status: 200;
477
- body: unknown;
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
- readonly accounts: readonly Record<string, unknown>[];
482
- readonly [key: string]: unknown;
1263
+ readonly accounts: readonly Record<string, unknown>[];
1264
+ readonly [key: string]: unknown;
483
1265
  }
484
1266
  export type CodingAccountsResult = {
485
- outcome: "ok";
486
- status: 200;
487
- report: CodingAccountsReport;
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
- readonly id?: string;
492
- readonly [key: string]: unknown;
1273
+ readonly id?: string;
1274
+ readonly [key: string]: unknown;
493
1275
  }
494
1276
  export type CyclesListResult = {
495
- outcome: "ok";
496
- status: 200;
497
- rows: readonly CycleRow[];
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
- q: string;
502
- limit?: number;
1283
+ q: string;
1284
+ limit?: number;
503
1285
  }
504
1286
  export interface SearchResults {
505
- readonly issues?: readonly Record<string, unknown>[];
506
- readonly pulls?: readonly Record<string, unknown>[];
507
- readonly projects?: readonly Record<string, unknown>[];
508
- readonly initiatives?: readonly Record<string, unknown>[];
509
- readonly [key: string]: unknown;
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
- outcome: "ok";
513
- status: 200;
514
- results: SearchResults;
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
- outcome: "ok";
520
- status: 200;
521
- stages: readonly Record<string, unknown>[];
522
- source: string | null;
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
- outcome: "ok";
527
- status: 200;
528
- head: number | null;
529
- rows: AsyncGenerator<Record<string, unknown>>;
530
- /**
531
- * Release the feed WITHOUT draining it — cancels the response body and stops the idle deadline.
532
- * Idempotent, and safe to call after the rows have been read.
533
- *
534
- * ⛔ A caller that reads `head` and then DISCARDS `rows` must call this (CTC-2132 validate
535
- * attempt 14, code-review Finding 3): nothing else runs the generator's cleanup, so the body
536
- * would stay uncancelled and its connection referenced. Draining the rows to EOF, or `break`ing
537
- * out of the `for await`, already releases both — `close()` is for the caller that never starts.
538
- */
539
- close: () => Promise<void>;
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
- outcome: "resync";
545
- status: 409;
546
- head: number | null;
547
- reason: string;
1326
+ outcome: "resync";
1327
+ status: 409;
1328
+ head: number | null;
1329
+ reason: string;
548
1330
  } | TenantClientFailure;
549
1331
  export type ChangesListResult = {
550
- outcome: "ok";
551
- status: 200;
552
- head: number | null;
553
- rows: Record<string, unknown>[];
1332
+ outcome: "ok";
1333
+ status: 200;
1334
+ head: number | null;
1335
+ rows: Record<string, unknown>[];
554
1336
  } | Extract<ChangesStreamResult, {
555
- outcome: "resync";
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
- outcome: "ok";
561
- status: 200;
562
- head: number;
563
- source: "header" | "body";
1342
+ outcome: "ok";
1343
+ status: 200;
1344
+ head: number;
1345
+ source: "header" | "body";
564
1346
  } | {
565
- outcome: "shape";
566
- status: number;
567
- reason: string;
1347
+ outcome: "shape";
1348
+ status: number;
1349
+ reason: string;
568
1350
  } | TenantClientFailure;
569
1351
  export interface RawRequest {
570
- method?: "GET" | "POST";
571
- /** ⛔ An ABSOLUTE PATH under the client's own origin ("/api/v1/…"), never a URL. A value that is
572
- * not one is refused BEFORE the request is built: `new URL(origin + path)` does not throw on
573
- * "@host/x" — it reads the origin as USERINFO and `host` becomes the attacker's, which would send
574
- * this client's bearer credential to that host (reproduced: `new URL("https://cloud.example" +
575
- * "@evil.example/steal")` → host `evil.example`, no throw). CTC-2132. */
576
- path: string;
577
- query?: Record<string, string | number | undefined>;
578
- headers?: Record<string, string>;
579
- body?: unknown;
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
- outcome: "ok";
583
- status: number;
584
- json: unknown;
585
- headers: Headers;
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
- outcome: "ok";
591
- status: 200;
592
- authorizationUrl: string;
593
- expiresAt: number;
1372
+ outcome: "ok";
1373
+ status: 200;
1374
+ authorizationUrl: string;
1375
+ expiresAt: number;
594
1376
  } | {
595
- outcome: "workspace-required";
596
- status: 409;
1377
+ outcome: "workspace-required";
1378
+ status: 409;
597
1379
  } | TenantClientFailure;
598
1380
  export type PersonalConnectionStatusResult = {
599
- outcome: "absent";
600
- status: 200;
1381
+ outcome: "absent";
1382
+ status: 200;
601
1383
  } | {
602
- outcome: "lapsed";
603
- status: 200;
604
- lapsedAt: number | null;
1384
+ outcome: "lapsed";
1385
+ status: 200;
1386
+ lapsedAt: number | null;
605
1387
  } | {
606
- outcome: "connected";
607
- status: 200;
608
- provider: "linear";
609
- linearUserId: string;
610
- grantedScope: string | null;
611
- updatedAt: number;
612
- expiresAt: number | null;
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
- outcome: "connected";
615
- status: 200;
616
- provider: "github";
617
- githubUserId: string;
618
- githubLogin: string;
619
- updatedAt: number;
620
- expiresAt: number | null;
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
- outcome: "unavailable";
623
- status: 503;
624
- provider: PersonalConnectionProvider;
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 ["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"];
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
- outcome: "route-unknown";
637
- route: AgentRouteName;
638
- routes: string[];
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
- outcome: "succeeded";
644
- status: number;
645
- attempts: number;
1442
+ outcome: "succeeded";
1443
+ status: number;
1444
+ attempts: number;
646
1445
  } | {
647
- outcome: "exhausted";
648
- status: number;
649
- attempts: number;
650
- lastError: string;
1446
+ outcome: "exhausted";
1447
+ status: number;
1448
+ attempts: number;
1449
+ lastError: string;
651
1450
  };
652
1451
  export interface IssueStateInput {
653
- issueId: string;
654
- /** The Linear state id — resolve it by SLOT through {@link stageIdForSlot}, never by name. */
655
- stateId: string;
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
- issueId: string;
660
- labelIds: string[];
661
- mode: "add" | "remove";
1458
+ issueId: string;
1459
+ labelIds: string[];
1460
+ mode: "add" | "remove";
662
1461
  }
663
1462
  export interface IssueLabelItemResult {
664
- labelId: string;
665
- /** `already-absent` — a remove that found nothing to remove; counts as satisfied, not as a landing. */
666
- outcome: "succeeded" | "rejected" | "exhausted" | "already-absent";
667
- attempts: number;
668
- reason?: string;
669
- lastError?: string;
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
- outcome: "succeeded" | "failed";
673
- status: number;
674
- results: IssueLabelItemResult[];
1471
+ outcome: "succeeded" | "failed";
1472
+ status: number;
1473
+ results: IssueLabelItemResult[];
675
1474
  } | AgentCallFailure;
676
1475
  export interface IssueCommentInput {
677
- issueId: string;
678
- body: string;
679
- parentId?: string;
680
- /** The agent display name (`botActor.userDisplayName`). Absent means the plain app actor. */
681
- createAsUser?: string;
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
- teamId: string;
686
- title: string;
687
- description?: string;
688
- labelIds?: string[];
689
- assigneeId?: string;
690
- parentId?: string;
691
- /** Linear's 0–4 scale. */
692
- priority?: 0 | 1 | 2 | 3 | 4;
693
- stateId?: string;
694
- projectId?: string;
695
- createAsUser?: string;
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
- outcome: "succeeded";
699
- status: number;
700
- attempts: number;
701
- identifier: string;
702
- id: string;
703
- url: string | null;
1497
+ outcome: "succeeded";
1498
+ status: number;
1499
+ attempts: number;
1500
+ identifier: string;
1501
+ id: string;
1502
+ url: string | null;
704
1503
  } | Extract<ProxiedWriteOutcome, {
705
- outcome: "exhausted";
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
- issueId?: string;
711
- commentId?: string;
712
- /** Defaults server-side to the fleet's claim emoji. */
713
- emoji?: string;
714
- mode?: "add" | "remove";
715
- createAsUser?: string;
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
- outcome: "succeeded";
719
- status: number;
720
- attempts: number;
721
- reactionId: string | null;
722
- alreadyPresent?: true;
723
- userId?: string | null;
1517
+ outcome: "succeeded";
1518
+ status: number;
1519
+ attempts: number;
1520
+ reactionId: string | null;
1521
+ alreadyPresent?: true;
1522
+ userId?: string | null;
724
1523
  } | {
725
- outcome: "succeeded";
726
- status: number;
727
- attempts: number;
728
- alreadyAbsent: true;
729
- removed: 0;
1524
+ outcome: "succeeded";
1525
+ status: number;
1526
+ attempts: number;
1527
+ alreadyAbsent: true;
1528
+ removed: 0;
730
1529
  } | {
731
- outcome: "succeeded";
732
- status: number;
733
- attempts: number;
734
- removed: number;
1530
+ outcome: "succeeded";
1531
+ status: number;
1532
+ attempts: number;
1533
+ removed: number;
735
1534
  } | Extract<ProxiedWriteOutcome, {
736
- outcome: "exhausted";
1535
+ outcome: "exhausted";
737
1536
  }> | AgentCallFailure;
738
1537
  export interface ProxiedAttachment {
739
- id: string;
740
- url: string;
741
- title: string | null;
742
- metadata: Record<string, unknown> | null;
1538
+ id: string;
1539
+ url: string;
1540
+ title: string | null;
1541
+ metadata: Record<string, unknown> | null;
743
1542
  }
744
1543
  export interface AttachmentInput {
745
- issueId: string;
746
- /** The upsert key — Linear dedups attachments by url. */
747
- url: string;
748
- title: string;
749
- metadata: Record<string, unknown>;
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
- attachment?: ProxiedAttachment;
1551
+ attachment?: ProxiedAttachment;
753
1552
  }) | AgentCallFailure;
754
1553
  export type AttachmentsResult = {
755
- outcome: "succeeded";
756
- status: number;
757
- attachments: ProxiedAttachment[];
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
- outcome: "not-found";
763
- status: 404;
764
- reason: string;
1561
+ outcome: "not-found";
1562
+ status: 404;
1563
+ reason: string;
765
1564
  } | {
766
- outcome: "unauthorized-upstream";
767
- status: number;
768
- reason: string;
1565
+ outcome: "unauthorized-upstream";
1566
+ status: number;
1567
+ reason: string;
769
1568
  } | AgentCallFailure;
770
1569
  export interface SessionPlanEntry {
771
- content: string;
772
- status: string;
1570
+ content: string;
1571
+ status: string;
773
1572
  }
774
1573
  export type SessionActivity = {
775
- type: "thought" | "response";
776
- body: string;
1574
+ type: "thought" | "response";
1575
+ body: string;
777
1576
  } | {
778
- type: "error";
779
- body: string;
780
- reasonCode?: string;
1577
+ type: "error";
1578
+ body: string;
1579
+ reasonCode?: string;
781
1580
  } | {
782
- type: "action";
783
- action: string;
784
- parameter: string;
785
- result?: string;
1581
+ type: "action";
1582
+ action: string;
1583
+ parameter: string;
1584
+ result?: string;
786
1585
  };
787
1586
  export interface SessionInput {
788
- issueId: string;
789
- plan?: SessionPlanEntry[];
790
- activity?: SessionActivity;
791
- externalUrls?: {
792
- label: string;
793
- url: string;
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
- outcome: "succeeded";
799
- attempts: number;
1597
+ outcome: "succeeded";
1598
+ attempts: number;
800
1599
  } | {
801
- outcome: "exhausted";
802
- attempts: number;
803
- lastError: string;
1600
+ outcome: "exhausted";
1601
+ attempts: number;
1602
+ lastError: string;
804
1603
  } | {
805
- outcome: "rejected";
806
- attempts: number;
807
- reason: string;
1604
+ outcome: "rejected";
1605
+ attempts: number;
1606
+ reason: string;
808
1607
  };
809
1608
  export type EnsureSessionOutcome = {
810
- outcome: "reused";
811
- sessionId: string;
812
- attempts: number;
1609
+ outcome: "reused";
1610
+ sessionId: string;
1611
+ attempts: number;
813
1612
  } | {
814
- outcome: "created";
815
- sessionId: string;
816
- attempts: number;
1613
+ outcome: "created";
1614
+ sessionId: string;
1615
+ attempts: number;
817
1616
  } | {
818
- outcome: "rejected";
819
- attempts: number;
820
- reason: string;
1617
+ outcome: "rejected";
1618
+ attempts: number;
1619
+ reason: string;
821
1620
  } | {
822
- outcome: "exhausted";
823
- attempts: number;
824
- lastError: string;
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
- outcome: "ok";
830
- status: number;
831
- session: EnsureSessionOutcome;
832
- plan?: ProxiedWriteBody;
833
- externalUrls?: ProxiedWriteBody;
834
- activity?: ProxiedWriteBody;
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
- teamId: string;
838
- title: string;
839
- context: string;
840
- defaultIfSilent: string;
841
- /** The idempotency key — a re-raise under the same key answers the existing ask. */
842
- askKey: string;
843
- options?: string[];
844
- blocks?: string[];
845
- nothingToBlock?: true;
846
- target?: {
847
- membershipId: string;
848
- } | {
849
- email: string;
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
- outcome: "created";
854
- status: number;
855
- askId: string;
856
- identifier: string;
857
- existing?: true;
1652
+ outcome: "created";
1653
+ status: number;
1654
+ askId: string;
1655
+ identifier: string;
1656
+ existing?: true;
858
1657
  } | {
859
- outcome: "created-but-relations-partial";
860
- status: number;
861
- askId: string;
862
- identifier: string;
863
- failedBlocks: string[];
864
- existing?: true;
1658
+ outcome: "created-but-relations-partial";
1659
+ status: number;
1660
+ askId: string;
1661
+ identifier: string;
1662
+ failedBlocks: string[];
1663
+ existing?: true;
865
1664
  } | {
866
- outcome: "exhausted";
867
- status: number;
868
- lastError: string;
1665
+ outcome: "exhausted";
1666
+ status: number;
1667
+ lastError: string;
869
1668
  } | AgentCallFailure;
870
1669
  export interface AskAcceptInput {
871
- askIssueId: string;
872
- answerCommentId: string;
873
- /** A single line of at most 64 characters. */
874
- acceptedByRole?: string;
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
- outcome: "recorded";
879
- status: number;
880
- askIdentifier: string;
881
- decisionSummary: string;
882
- failedBlockedComments: string[];
883
- resume: unknown[];
884
- unblock?: unknown;
1677
+ outcome: "recorded";
1678
+ status: number;
1679
+ askIdentifier: string;
1680
+ decisionSummary: string;
1681
+ failedBlockedComments: string[];
1682
+ resume: unknown[];
1683
+ unblock?: unknown;
885
1684
  } | {
886
- outcome: "refused";
887
- status: number;
888
- reason: AskAcceptRefusal;
1685
+ outcome: "refused";
1686
+ status: number;
1687
+ reason: AskAcceptRefusal;
889
1688
  } | {
890
- outcome: "record-failed";
891
- status: number;
892
- reason: string;
893
- resume?: unknown[];
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
- /** `owner/name`, sent VERBATIM. ⚠️ The cloud validates the trimmed halves but stores what it
901
- * was sent (CTC-2503 re-validation) — trim before calling if the value came from a human. */
902
- repository: string;
903
- project?: string;
904
- teamKey?: string;
905
- teamId?: string;
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
- repoId: string;
909
- owner: string;
910
- name: string;
911
- /** Present when the `repos` row carries one (e.g. `"paused"`). */
912
- status?: string;
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
- outcome: "registered";
916
- status: number;
917
- registered: RegisteredProjectRepository;
918
- created: boolean;
919
- linked: boolean;
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
- outcome: "not-found";
924
- status: 404;
925
- reason: string;
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
- outcome: "removed";
931
- status: number;
932
- removed: boolean;
1729
+ outcome: "removed";
1730
+ status: number;
1731
+ removed: boolean;
933
1732
  } | {
934
- outcome: "not-found";
935
- status: 404;
936
- reason: string;
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
- /** The ticket identifier, e.g. `CTC-42`. */
946
- ticket: string;
947
- /** At most 4096 bytes of UTF-8. */
948
- body: string;
949
- /** Defaults to `note` on the server. */
950
- kind?: "note" | "question" | "answer";
951
- /** The id of the earlier message this one answers. */
952
- re?: string;
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
- outcome: "accepted";
958
- status: number;
959
- messageId: string;
960
- ticket: string;
961
- delivery: "next-turn";
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
- outcome: "accepted";
967
- status: number;
968
- messageId: string;
969
- ticket: string;
970
- delivery: "next-phase";
971
- reason: "no-live-phase" | "run-cannot-receive" | "run-not-reading";
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
- outcome: "refused";
976
- status: 409;
977
- reason: "too_many_pending";
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
- kind: "none";
1780
+ kind: "none";
982
1781
  } | {
983
- kind: "bearer";
984
- secretName: string;
1782
+ kind: "bearer";
1783
+ secretName: string;
985
1784
  } | {
986
- kind: "headers";
987
- headers: {
988
- name: string;
989
- secretName: string;
990
- }[];
1785
+ kind: "headers";
1786
+ headers: {
1787
+ name: string;
1788
+ secretName: string;
1789
+ }[];
991
1790
  };
992
1791
  export interface PortalServerRegisterInput {
993
- name: string;
994
- url: string;
995
- auth: PortalServerAuth;
1792
+ name: string;
1793
+ url: string;
1794
+ auth: PortalServerAuth;
996
1795
  }
997
1796
  export interface PortalServer extends PortalServerRegisterInput {
998
- id: string;
999
- /** Custom servers stay pending until a server-side account admin approves them. */
1000
- status: "ready" | "pending" | "pending_egress_guard";
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
- outcome: "registered";
1004
- status: number;
1005
- server: PortalServer;
1802
+ outcome: "registered";
1803
+ status: number;
1804
+ server: PortalServer;
1006
1805
  } | AgentCallFailure;
1007
1806
  export type PortalServersResult = {
1008
- outcome: "ok";
1009
- status: number;
1010
- servers: PortalServer[];
1807
+ outcome: "ok";
1808
+ status: number;
1809
+ servers: PortalServer[];
1011
1810
  } | AgentCallFailure;
1012
1811
  export type PortalServerRemoveResult = {
1013
- outcome: "removed";
1014
- status: number;
1015
- removed: boolean;
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
- /** Personal bearer team setup. The key's tenant and admin role are enforced by the cloud. */
1038
- teamWorkflow: {
1039
- teams(): Promise<TeamWorkflowResult<TeamList>>;
1040
- get(team: string): Promise<TeamWorkflowResult<TeamWorkflowView>>;
1041
- check(team: string): Promise<TeamWorkflowResult<{
1042
- readiness: TeamWorkflowReadiness;
1043
- ask: Record<string, unknown>;
1044
- }>>;
1045
- save(input: TeamMappingSaveInput): Promise<TeamWorkflowResult<TeamWorkflowSaveResult>>;
1046
- adoptPreview(team: string): Promise<TeamWorkflowResult<TeamAdoptResult>>;
1047
- adoptApply(team: string, planHash: string): Promise<TeamWorkflowResult<TeamAdoptResult>>;
1048
- undoPreview(team: string): Promise<TeamWorkflowResult<TeamUndoPreview>>;
1049
- undoApply(team: string, undoHash: string): Promise<TeamWorkflowResult<TeamUndoResult>>;
1050
- migratePreview(team: string, choices?: TeamMigrationChoice[]): Promise<TeamWorkflowResult<{
1051
- preview: TeamMigrationPlan;
1052
- }>>;
1053
- migrateChunk(team: string, migrationHash: string, choices?: TeamMigrationChoice[]): Promise<TeamWorkflowResult<TeamMigrationChunk>>;
1054
- migrateRetire(team: string, migrationHash: string, choices?: TeamMigrationChoice[]): Promise<TeamWorkflowResult<TeamMigrationRetire>>;
1055
- };
1056
- /** Self-service unmatched identity recovery. Personal credentials only. */
1057
- linearIdentity: {
1058
- get(): Promise<LinearIdentityResult>;
1059
- /** Records only the authenticated member's choice. Automatically resolved identities refuse. */
1060
- set(linearUserId: string): Promise<LinearIdentityResult>;
1061
- };
1062
- /** The tenant's fact document, cached per its own `cache` policy. */
1063
- contract(opts?: ContractOptions): Promise<ContractResult>;
1064
- /** `GET /api/v1/me` — the account this key belongs to. */
1065
- me(): Promise<MeResult>;
1066
- /** The generic authed-request escape hatch (CTC-2132) — for a route nobody enumerated. `path` MUST
1067
- * be an absolute path under this client's own origin; see {@link RawRequest}. */
1068
- request(req: RawRequest): Promise<RawRequestResult>;
1069
- /** User-scoped GitHub and Linear OAuth grants. Use a personal key or device-login credential. */
1070
- personalConnections: {
1071
- /** Returns a short lived URL to open in the user's browser. Does not perform consent. */
1072
- start(provider: PersonalConnectionProvider): Promise<PersonalConnectionStartResult>;
1073
- /** A provider outage is `unavailable`, never `absent`. */
1074
- status(provider: PersonalConnectionProvider): Promise<PersonalConnectionStatusResult>;
1075
- };
1076
- issues: {
1077
- /** `GET /api/v1/issues` — keyset-paged; follow `nextCursor` until it is `null`. */
1078
- list(params?: IssueListParams): Promise<IssueListResult>;
1079
- /** `GET /api/v1/issues/:identifier`. */
1080
- get(identifier: string): Promise<IssueGetResult>;
1081
- /** `GET /api/v1/issues/:id/execution` — the ticket's own execution/telemetry report. */
1082
- execution(identifier: string): Promise<TicketExecutionResult>;
1083
- };
1084
- pulls: {
1085
- list(params?: PullListParams): Promise<PullListResult>;
1086
- /** `GET /api/v1/pulls/:id` — `id` is the PR's GitHub node id. */
1087
- get(nodeId: string): Promise<PullGetResult>;
1088
- };
1089
- projects: {
1090
- list(params?: ProjectListParams): Promise<ProjectListResult>;
1091
- };
1092
- cycles: {
1093
- /** `GET /api/v1/cycles` — the SDK's own structural row type; see {@link CycleRow}. */
1094
- list(): Promise<CyclesListResult>;
1095
- };
1096
- /** `GET /api/v1/search?q=&limit=`. */
1097
- search(params: SearchParams): Promise<SearchResult>;
1098
- /** `GET /api/v1/workflow-stages` — tolerant of a `{stages,source?}` envelope or a bare array. */
1099
- workflowStages(): Promise<WorkflowStagesResult>;
1100
- changes: {
1101
- /** `GET /api/v1/changes?since=` — NDJSON. ⛔ The returned generator MAY THROW mid-iteration; use
1102
- * `list` for the nothing-throws, buffered alternative. */
1103
- stream(params: {
1104
- since: number | "head";
1105
- signal?: AbortSignal;
1106
- }): Promise<ChangesStreamResult>;
1107
- /** The same feed, buffered — never throws; a mid-stream fault comes back as the `network` arm. */
1108
- list(params: {
1109
- since: number | "head";
1110
- signal?: AbortSignal;
1111
- }): Promise<ChangesListResult>;
1112
- };
1113
- snapshot: {
1114
- /** `GET /api/v1/snapshot?head=1` — the cheap head probe; never falls back to a full snapshot. */
1115
- head(): Promise<SnapshotHeadResult>;
1116
- };
1117
- /** The diagnosis / telemetry reads (CTC-2132, M2 addendum) — a customer delegate agent's own
1118
- * "why is nothing moving" / "what is my fleet doing" answers. */
1119
- diagnostics: {
1120
- workEligibility(params?: {
1121
- team?: string;
1122
- }): Promise<WorkEligibilityResult>;
1123
- dispatchQueue(params?: {
1124
- team?: string;
1125
- }): Promise<DispatchQueueResult>;
1126
- fleetActivity(params?: {
1127
- account?: string;
1128
- }): Promise<FleetActivityResult>;
1129
- agentRoster(): Promise<AgentRosterResult>;
1130
- leaseAttributions(params: {
1131
- ticket: string;
1132
- phase: string;
1133
- }): Promise<LeaseAttributionsResult>;
1134
- codingAccounts(): Promise<CodingAccountsResult>;
1135
- };
1136
- /** The `/api/v1/agent/*` proxy — every tenant write as the app actor, path from `routes[]`. */
1137
- agent: {
1138
- issueState(input: IssueStateInput): Promise<IssueStateResult>;
1139
- issueLabel(input: IssueLabelInput): Promise<IssueLabelResult>;
1140
- issueComment(input: IssueCommentInput): Promise<IssueCommentResult>;
1141
- issueCreate(input: IssueCreateInput): Promise<IssueCreateResult>;
1142
- reaction(input: ReactionInput): Promise<ReactionResult>;
1143
- attachment(input: AttachmentInput): Promise<AttachmentResult>;
1144
- /** `GET …/attachments?issueId=` — the soft-CAS read-back; no write-budget unit. */
1145
- attachments(input: {
1146
- issueId: string;
1147
- }): Promise<AttachmentsResult>;
1148
- session(input: SessionInput): Promise<SessionResult>;
1149
- ask(input: AskInput): Promise<AskResult>;
1150
- askAccept(input: AskAcceptInput): Promise<AskAcceptResult>;
1151
- portalServerRegister(input: PortalServerRegisterInput): Promise<PortalServerRegisterResult>;
1152
- portalServers(): Promise<PortalServersResult>;
1153
- portalServerRemove(input: {
1154
- name: string;
1155
- }): Promise<PortalServerRemoveResult>;
1156
- projectRepositoryRegister(input: ProjectRepositoryInput): Promise<ProjectRepositoryRegisterResult>;
1157
- projectRepositoryRemove(input: ProjectRepositoryInput): Promise<ProjectRepositoryRemoveResult>;
1158
- /** `POST …/agent/messages` — send a ticket's running agent a message it reads at its next turn. */
1159
- message(input: AgentMessageInput): Promise<AgentMessageResult>;
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