@cat-factory/contracts 0.274.0 → 0.276.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.
Files changed (57) hide show
  1. package/dist/accountSettings.d.ts +156 -22
  2. package/dist/accountSettings.d.ts.map +1 -1
  3. package/dist/accountSettings.js +48 -5
  4. package/dist/accountSettings.js.map +1 -1
  5. package/dist/errors.d.ts +16 -1
  6. package/dist/errors.d.ts.map +1 -1
  7. package/dist/errors.js +16 -0
  8. package/dist/errors.js.map +1 -1
  9. package/dist/index.d.ts +2 -1
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +2 -1
  12. package/dist/index.js.map +1 -1
  13. package/dist/notification-routing.d.ts +100 -0
  14. package/dist/notification-routing.d.ts.map +1 -0
  15. package/dist/notification-routing.js +155 -0
  16. package/dist/notification-routing.js.map +1 -0
  17. package/dist/public-api.d.ts +137 -0
  18. package/dist/public-api.d.ts.map +1 -1
  19. package/dist/public-api.js +74 -0
  20. package/dist/public-api.js.map +1 -1
  21. package/dist/public-board.d.ts +276 -0
  22. package/dist/public-board.d.ts.map +1 -0
  23. package/dist/public-board.js +215 -0
  24. package/dist/public-board.js.map +1 -0
  25. package/dist/public-evidence.d.ts +30 -3
  26. package/dist/public-evidence.d.ts.map +1 -1
  27. package/dist/public-evidence.js +27 -3
  28. package/dist/public-evidence.js.map +1 -1
  29. package/dist/requests.d.ts +19 -2
  30. package/dist/requests.d.ts.map +1 -1
  31. package/dist/requests.js +18 -1
  32. package/dist/requests.js.map +1 -1
  33. package/dist/routes/accounts.d.ts +81 -15
  34. package/dist/routes/accounts.d.ts.map +1 -1
  35. package/dist/routes/board.d.ts +6 -2
  36. package/dist/routes/board.d.ts.map +1 -1
  37. package/dist/routes/index.d.ts +1 -0
  38. package/dist/routes/index.d.ts.map +1 -1
  39. package/dist/routes/index.js +1 -0
  40. package/dist/routes/index.js.map +1 -1
  41. package/dist/routes/notifications.d.ts +76 -0
  42. package/dist/routes/notifications.d.ts.map +1 -1
  43. package/dist/routes/notifications.js +15 -0
  44. package/dist/routes/notifications.js.map +1 -1
  45. package/dist/routes/public-api.d.ts +18 -0
  46. package/dist/routes/public-api.d.ts.map +1 -1
  47. package/dist/routes/public-board.d.ts +369 -0
  48. package/dist/routes/public-board.d.ts.map +1 -0
  49. package/dist/routes/public-board.js +112 -0
  50. package/dist/routes/public-board.js.map +1 -0
  51. package/dist/routes/public-evidence.d.ts +1 -0
  52. package/dist/routes/public-evidence.d.ts.map +1 -1
  53. package/package.json +1 -1
  54. package/dist/gate-parking.d.ts +0 -30
  55. package/dist/gate-parking.d.ts.map +0 -1
  56. package/dist/gate-parking.js +0 -58
  57. package/dist/gate-parking.js.map +0 -1
@@ -0,0 +1,276 @@
1
+ import * as v from 'valibot';
2
+ /**
3
+ * The repository a new service frame is backed by.
4
+ *
5
+ * `repoId` is the provider's own id for the repo, as `GET /api/v1/repos` serves it: the neutral
6
+ * name for what the internal projection calls `githubId`. It is deliberately not an `owner/name`
7
+ * pair: a repo can be renamed or transferred without changing its id, and a caller that held a name
8
+ * would silently create a service against a different repository after such a move.
9
+ */
10
+ export declare const publicServiceRepoSchema: v.ObjectSchema<{
11
+ /** The repo's provider id, from `GET /api/v1/repos`. */
12
+ readonly repoId: v.NumberSchema<undefined>;
13
+ /**
14
+ * For a MONOREPO, the subdirectory (relative to the repo root) this service lives in, e.g.
15
+ * `packages/api`. Required when the repo is a monorepo and refused for a whole-repo service,
16
+ * because it is what scopes each agent's working directory: a monorepo backs one service per
17
+ * subdirectory, and two services claiming the same one would fight over the same subtree.
18
+ */
19
+ readonly directory: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.TrimAction, v.MaxLengthAction<string, 400, undefined>]>, undefined>;
20
+ /**
21
+ * Whether the repository hosts SEVERAL services. Sent with the create rather than as a separate
22
+ * up-front write; when supplied it is persisted on the repo, so the flag a later create reads is
23
+ * the one this call set. Omitted ⇒ whatever the repo already says.
24
+ */
25
+ readonly monorepo: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
26
+ }, undefined>;
27
+ export type PublicServiceRepo = v.InferOutput<typeof publicServiceRepoSchema>;
28
+ /**
29
+ * Create a board service (a service frame), optionally backed by a repository.
30
+ *
31
+ * Without `repo` the frame is a structural placeholder: it can hold tasks, and a run started on one
32
+ * of them is REFUSED, because execution resolves a task's repository by walking up to the enclosing
33
+ * service frame and there is nothing there. That is a legitimate intermediate state (a caller
34
+ * mapping out a board before its repositories exist) and it is why the field is optional rather
35
+ * than why it should usually be omitted.
36
+ *
37
+ * There is deliberately no `position`: see the note at the top of this file.
38
+ */
39
+ export declare const createPublicServiceSchema: v.ObjectSchema<{
40
+ /**
41
+ * The service's name. Optional, because a repo-backed service is named after the repository (or,
42
+ * for a monorepo service, after its subdirectory) exactly as the app's import does, and a caller
43
+ * that has no better name should get that one rather than being made to invent it.
44
+ */
45
+ readonly title: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.TrimAction, v.MinLengthAction<string, 1, undefined>, v.MaxLengthAction<string, 200, undefined>]>, undefined>;
46
+ /** What the service is, for the agents that read it as context. Defaults to a generated line. */
47
+ readonly description: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.TrimAction, v.MaxLengthAction<string, 2000, undefined>]>, undefined>;
48
+ /**
49
+ * The service's architectural role. Omitted ⇒ `service`.
50
+ *
51
+ * Narrower than the board's own block types on purpose: this is the set a REPOSITORY can back,
52
+ * which is the only kind of frame this endpoint creates. A `database` or `queue` frame documents
53
+ * infrastructure for the agents to read and runs nothing, so nothing here would create one.
54
+ */
55
+ readonly type: v.OptionalSchema<v.PicklistSchema<["service", "frontend", "library", "document"], undefined>, undefined>;
56
+ /** The repository backing the service. Omitted ⇒ an unlinked frame (see above). */
57
+ readonly repo: v.OptionalSchema<v.ObjectSchema<{
58
+ /** The repo's provider id, from `GET /api/v1/repos`. */
59
+ readonly repoId: v.NumberSchema<undefined>;
60
+ /**
61
+ * For a MONOREPO, the subdirectory (relative to the repo root) this service lives in, e.g.
62
+ * `packages/api`. Required when the repo is a monorepo and refused for a whole-repo service,
63
+ * because it is what scopes each agent's working directory: a monorepo backs one service per
64
+ * subdirectory, and two services claiming the same one would fight over the same subtree.
65
+ */
66
+ readonly directory: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.TrimAction, v.MaxLengthAction<string, 400, undefined>]>, undefined>;
67
+ /**
68
+ * Whether the repository hosts SEVERAL services. Sent with the create rather than as a separate
69
+ * up-front write; when supplied it is persisted on the repo, so the flag a later create reads is
70
+ * the one this call set. Omitted ⇒ whatever the repo already says.
71
+ */
72
+ readonly monorepo: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
73
+ }, undefined>, undefined>;
74
+ }, undefined>;
75
+ export type CreatePublicServiceInput = v.InferOutput<typeof createPublicServiceSchema>;
76
+ /**
77
+ * A repository this workspace can back a service with, as `GET /api/v1/repos` lists it.
78
+ *
79
+ * The discovery half of {@link publicServiceRepoSchema}, and it is why service creation is usable
80
+ * headlessly at all: the create takes a `repoId`, and until this existed the only way to learn one
81
+ * was to open the app. Deliberately a small projection (enough to recognise a repository and pass
82
+ * it back), not a mirror of the internal projection row, which carries installation ids and
83
+ * sync bookkeeping that are this platform's business rather than a caller's.
84
+ */
85
+ export declare const publicRepoSchema: v.ObjectSchema<{
86
+ /** The provider's id for the repo: the value to pass as `repo.repoId`. */
87
+ readonly repoId: v.NumberSchema<undefined>;
88
+ /** Which provider it lives on (`github` / `gitlab`). */
89
+ readonly provider: v.StringSchema<undefined>;
90
+ readonly owner: v.StringSchema<undefined>;
91
+ readonly name: v.StringSchema<undefined>;
92
+ /**
93
+ * The branch a run's work is based on and merged into, or EMPTY when the projection has not
94
+ * recorded one yet (a repository connected moments ago, before its first sync). Empty rather
95
+ * than null because there is nothing here that could invent a default, and a caller reading it
96
+ * to name a base needs to see that it has to ask the provider rather than assume `main`.
97
+ */
98
+ readonly defaultBranch: v.StringSchema<undefined>;
99
+ /** Whether the repository is private on its provider. */
100
+ readonly private: v.BooleanSchema<undefined>;
101
+ /** Whether the repo is flagged as hosting several services (see `repo.monorepo`). */
102
+ readonly monorepo: v.BooleanSchema<undefined>;
103
+ /**
104
+ * The service this repository already backs ON THIS BOARD, or null.
105
+ *
106
+ * Present because a whole-repo repository backs at most ONE service, so a caller choosing one to
107
+ * create against needs to know which choices are already spent, and, more usefully, because a
108
+ * caller re-running its provisioning finds the service it created last time here rather than
109
+ * discovering it through a `409`. A monorepo answers null even when its subdirectories back
110
+ * services, since it can back more.
111
+ *
112
+ * Null and {@link linkedElsewhere} together are the honest answer when the service is homed on
113
+ * another board of the account: read the flag before treating null as "available".
114
+ */
115
+ readonly serviceId: v.NullableSchema<v.StringSchema<undefined>, undefined>;
116
+ /**
117
+ * True when this repository already backs a whole-repo service homed on ANOTHER board of the
118
+ * account, so `POST /api/v1/services` will refuse it (`reason: repo_service_homed_elsewhere`).
119
+ *
120
+ * A service is account-owned and a board can MOUNT one homed elsewhere, but every read on this
121
+ * API is scoped to the calling key's own workspace, so a frame homed on another board has no id
122
+ * this surface could hand back: it would not appear in `GET /api/v1/services` and
123
+ * `POST /api/v1/services/{serviceId}/tasks` would 404 on it. Hence a flag rather than a second
124
+ * id field — this states that the choice is spent without naming an address that does not work
125
+ * here. Use the board that homes the service, or a key scoped to it.
126
+ */
127
+ readonly linkedElsewhere: v.BooleanSchema<undefined>;
128
+ }, undefined>;
129
+ export type PublicRepo = v.InferOutput<typeof publicRepoSchema>;
130
+ export declare const publicRepoListSchema: v.ObjectSchema<{
131
+ readonly repos: v.ArraySchema<v.ObjectSchema<{
132
+ /** The provider's id for the repo: the value to pass as `repo.repoId`. */
133
+ readonly repoId: v.NumberSchema<undefined>;
134
+ /** Which provider it lives on (`github` / `gitlab`). */
135
+ readonly provider: v.StringSchema<undefined>;
136
+ readonly owner: v.StringSchema<undefined>;
137
+ readonly name: v.StringSchema<undefined>;
138
+ /**
139
+ * The branch a run's work is based on and merged into, or EMPTY when the projection has not
140
+ * recorded one yet (a repository connected moments ago, before its first sync). Empty rather
141
+ * than null because there is nothing here that could invent a default, and a caller reading it
142
+ * to name a base needs to see that it has to ask the provider rather than assume `main`.
143
+ */
144
+ readonly defaultBranch: v.StringSchema<undefined>;
145
+ /** Whether the repository is private on its provider. */
146
+ readonly private: v.BooleanSchema<undefined>;
147
+ /** Whether the repo is flagged as hosting several services (see `repo.monorepo`). */
148
+ readonly monorepo: v.BooleanSchema<undefined>;
149
+ /**
150
+ * The service this repository already backs ON THIS BOARD, or null.
151
+ *
152
+ * Present because a whole-repo repository backs at most ONE service, so a caller choosing one to
153
+ * create against needs to know which choices are already spent, and, more usefully, because a
154
+ * caller re-running its provisioning finds the service it created last time here rather than
155
+ * discovering it through a `409`. A monorepo answers null even when its subdirectories back
156
+ * services, since it can back more.
157
+ *
158
+ * Null and {@link linkedElsewhere} together are the honest answer when the service is homed on
159
+ * another board of the account: read the flag before treating null as "available".
160
+ */
161
+ readonly serviceId: v.NullableSchema<v.StringSchema<undefined>, undefined>;
162
+ /**
163
+ * True when this repository already backs a whole-repo service homed on ANOTHER board of the
164
+ * account, so `POST /api/v1/services` will refuse it (`reason: repo_service_homed_elsewhere`).
165
+ *
166
+ * A service is account-owned and a board can MOUNT one homed elsewhere, but every read on this
167
+ * API is scoped to the calling key's own workspace, so a frame homed on another board has no id
168
+ * this surface could hand back: it would not appear in `GET /api/v1/services` and
169
+ * `POST /api/v1/services/{serviceId}/tasks` would 404 on it. Hence a flag rather than a second
170
+ * id field — this states that the choice is spent without naming an address that does not work
171
+ * here. Use the board that homes the service, or a key scoped to it.
172
+ */
173
+ readonly linkedElsewhere: v.BooleanSchema<undefined>;
174
+ }, undefined>, undefined>;
175
+ }, undefined>;
176
+ export type PublicRepoList = v.InferOutput<typeof publicRepoListSchema>;
177
+ /**
178
+ * Declare that a task must wait for another one.
179
+ *
180
+ * The gap it closes: an integration filing five related tasks and starting them got five runs
181
+ * racing against one repository, each opening a pull request against a base the others were
182
+ * moving. The platform has had the mechanism to serialise them (the engine's start gate refuses a
183
+ * task whose blockers are not `done`, and `autoStartDependents` starts a task when its blocker
184
+ * merges) and no way for an external caller to be told about it.
185
+ *
186
+ * The edge is stored on the DEPENDENT: `POST /api/v1/tasks/{taskId}/dependencies` says "this task
187
+ * waits for `dependsOnTaskId`". Both ends must be tasks (only a task ever reaches `done`, so an
188
+ * edge onto a service or a module would wedge the run's start gate forever), and an edge that would
189
+ * close a cycle is refused, so the gate and the auto-start can never deadlock.
190
+ */
191
+ export declare const publicTaskDependencySchema: v.ObjectSchema<{
192
+ /** The task that must finish first. Must be a task in the same workspace, and not this one. */
193
+ readonly dependsOnTaskId: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.TrimAction, v.MinLengthAction<string, 1, undefined>, v.MaxLengthAction<string, 120, undefined>]>;
194
+ }, undefined>;
195
+ export type PublicTaskDependencyInput = v.InferOutput<typeof publicTaskDependencySchema>;
196
+ /**
197
+ * A document attached to a task as agent context, as the task's document list serves it.
198
+ *
199
+ * Identified by `(source, externalId)` rather than by an id of its own, because that pair IS a
200
+ * projected document's identity: a re-import of the same page lands on the same row, which is what
201
+ * makes attaching one idempotent. `source` is `upload` for a body a caller carried rather than
202
+ * named, and such a document has no page behind it, which is why `url` is empty there rather than
203
+ * absent.
204
+ */
205
+ export declare const publicAttachedDocumentSchema: v.ObjectSchema<{
206
+ /** Which source the document came from, or `upload` for one a caller carried inline. */
207
+ readonly source: v.PicklistSchema<["confluence", "notion", "github", "figma", "zeplin", "linear", "upload"], undefined>;
208
+ /** The source's stable id for the page: the value to pass back to detach it. */
209
+ readonly externalId: v.StringSchema<undefined>;
210
+ readonly title: v.StringSchema<undefined>;
211
+ /** Canonical URL on the source; EMPTY for an `upload`, which has no page to link back to. */
212
+ readonly url: v.StringSchema<undefined>;
213
+ /** A short plain-text excerpt of the body (the full text reaches the run, not this list). */
214
+ readonly excerpt: v.StringSchema<undefined>;
215
+ }, undefined>;
216
+ export type PublicAttachedDocument = v.InferOutput<typeof publicAttachedDocumentSchema>;
217
+ export declare const publicAttachedDocumentListSchema: v.ObjectSchema<{
218
+ readonly documents: v.ArraySchema<v.ObjectSchema<{
219
+ /** Which source the document came from, or `upload` for one a caller carried inline. */
220
+ readonly source: v.PicklistSchema<["confluence", "notion", "github", "figma", "zeplin", "linear", "upload"], undefined>;
221
+ /** The source's stable id for the page: the value to pass back to detach it. */
222
+ readonly externalId: v.StringSchema<undefined>;
223
+ readonly title: v.StringSchema<undefined>;
224
+ /** Canonical URL on the source; EMPTY for an `upload`, which has no page to link back to. */
225
+ readonly url: v.StringSchema<undefined>;
226
+ /** A short plain-text excerpt of the body (the full text reaches the run, not this list). */
227
+ readonly excerpt: v.StringSchema<undefined>;
228
+ }, undefined>, undefined>;
229
+ }, undefined>;
230
+ export type PublicAttachedDocumentList = v.InferOutput<typeof publicAttachedDocumentListSchema>;
231
+ /**
232
+ * Attach a requirements document to a task that already exists.
233
+ *
234
+ * The same two forms creation takes ({@link publicTaskDocumentSchema}): NAME a page in a connected
235
+ * document source, or CARRY the text. What it adds is the moment: a task's spec routinely arrives
236
+ * after the task does (a ticket filed first and specified later, a PRD that lands mid-review), and
237
+ * until this existed the only way to attach one was to delete the task and file it again, losing
238
+ * the id every stored reference points at, its ticket claim (which then refuses every future filing
239
+ * of that ticket) and the documents it already carried.
240
+ *
241
+ * A document a DIFFERENT live task already holds is refused rather than moved: a document row
242
+ * carries exactly one attachment, so moving it would strip the other task of a document it was
243
+ * created with, with nothing in its next run reporting the absence.
244
+ */
245
+ export declare const attachPublicTaskDocumentSchema: v.ObjectSchema<{
246
+ readonly document: v.VariantSchema<"kind", [v.ObjectSchema<{
247
+ readonly kind: v.LiteralSchema<"source", undefined>;
248
+ readonly source: v.PicklistSchema<["confluence", "notion", "github", "figma", "zeplin", "linear"], undefined>;
249
+ readonly ref: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.TrimAction, v.MinLengthAction<string, 1, undefined>, v.MaxLengthAction<string, 500, undefined>]>;
250
+ }, undefined>, v.ObjectSchema<{
251
+ readonly kind: v.LiteralSchema<"upload", undefined>;
252
+ readonly title: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.TrimAction, v.MinLengthAction<string, 1, undefined>, v.MaxLengthAction<string, 200, undefined>]>;
253
+ readonly content: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.TrimAction, v.MinLengthAction<string, 1, undefined>, v.MaxLengthAction<string, 100000, undefined>]>;
254
+ }, undefined>], undefined>;
255
+ }, undefined>;
256
+ export type AttachPublicTaskDocumentInput = v.InferOutput<typeof attachPublicTaskDocumentSchema>;
257
+ /**
258
+ * Detach a document from a task, naming it by the `(source, externalId)` pair the task's document
259
+ * list serves.
260
+ *
261
+ * A POST with a body rather than a `DELETE .../documents/{id}`, because a document's identity is
262
+ * two values and one of them is a free-form external id: a Confluence page id is fine in a path
263
+ * segment and a GitHub docs path (`docs/architecture/adr-0001.md`) is not, so half the sources
264
+ * would need escaping rules a caller has to get right to address its own document.
265
+ *
266
+ * The document itself SURVIVES: it stays in the workspace exactly as the app's own detach leaves
267
+ * it, so re-attaching it costs no re-import. Idempotent: detaching a document this task does not
268
+ * hold is a no-op rather than an error, because a caller retrying after a timeout should converge
269
+ * rather than have to distinguish "it was never attached" from "I already detached it".
270
+ */
271
+ export declare const detachPublicTaskDocumentSchema: v.ObjectSchema<{
272
+ readonly source: v.PicklistSchema<["confluence", "notion", "github", "figma", "zeplin", "linear", "upload"], undefined>;
273
+ readonly externalId: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.TrimAction, v.MinLengthAction<string, 1, undefined>, v.MaxLengthAction<string, 500, undefined>]>;
274
+ }, undefined>;
275
+ export type DetachPublicTaskDocumentInput = v.InferOutput<typeof detachPublicTaskDocumentSchema>;
276
+ //# sourceMappingURL=public-board.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"public-board.d.ts","sourceRoot":"","sources":["../src/public-board.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,SAAS,CAAA;AA8B5B;;;;;;;GAOG;AACH,eAAO,MAAM,uBAAuB;IAClC,wDAAwD;;IAExD;;;;;OAKG;;IAEH;;;;OAIG;;aAEH,CAAA;AACF,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,uBAAuB,CAAC,CAAA;AAE7E;;;;;;;;;;GAUG;AACH,eAAO,MAAM,yBAAyB;IACpC;;;;OAIG;;IAEH,iGAAiG;;IAEjG;;;;;;OAMG;;IAEH,mFAAmF;;QA9CnF,wDAAwD;;QAExD;;;;;WAKG;;QAEH;;;;WAIG;;;aAmCH,CAAA;AACF,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,yBAAyB,CAAC,CAAA;AAEtF;;;;;;;;GAQG;AACH,eAAO,MAAM,gBAAgB;IAC3B,0EAA0E;;IAE1E,wDAAwD;;;;IAIxD;;;;;OAKG;;IAEH,yDAAyD;;IAEzD,qFAAqF;;IAErF;;;;;;;;;;;OAWG;;IAEH;;;;;;;;;;OAUG;;aAEH,CAAA;AACF,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,gBAAgB,CAAC,CAAA;AAE/D,eAAO,MAAM,oBAAoB;;QA7C/B,0EAA0E;;QAE1E,wDAAwD;;;;QAIxD;;;;;WAKG;;QAEH,yDAAyD;;QAEzD,qFAAqF;;QAErF;;;;;;;;;;;WAWG;;QAEH;;;;;;;;;;WAUG;;;aAK6E,CAAA;AAClF,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,oBAAoB,CAAC,CAAA;AAEvE;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,0BAA0B;IACrC,+FAA+F;;aAE/F,CAAA;AACF,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,0BAA0B,CAAC,CAAA;AAExF;;;;;;;;GAQG;AACH,eAAO,MAAM,4BAA4B;IACvC,wFAAwF;;IAExF,gFAAgF;;;IAGhF,6FAA6F;;IAE7F,6FAA6F;;aAE7F,CAAA;AACF,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,4BAA4B,CAAC,CAAA;AAEvF,eAAO,MAAM,gCAAgC;;QAZ3C,wFAAwF;;QAExF,gFAAgF;;;QAGhF,6FAA6F;;QAE7F,6FAA6F;;;aAO7F,CAAA;AACF,MAAM,MAAM,0BAA0B,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,gCAAgC,CAAC,CAAA;AAE/F;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,8BAA8B;;;;;;;;;;aAEzC,CAAA;AACF,MAAM,MAAM,6BAA6B,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,8BAA8B,CAAC,CAAA;AAEhG;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,8BAA8B;;;aAGzC,CAAA;AACF,MAAM,MAAM,6BAA6B,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,8BAA8B,CAAC,CAAA"}
@@ -0,0 +1,215 @@
1
+ import * as v from 'valibot';
2
+ import { documentOriginSchema } from './documents.js';
3
+ import { frameRepoTypeSchema } from './primitives.js';
4
+ import { publicTaskDocumentSchema } from './public-api.js';
5
+ // ---------------------------------------------------------------------------
6
+ // Public-API wire contracts for PROVISIONING the board a headless deployment drives, and for
7
+ // the two task relationships that outlive a single create call.
8
+ //
9
+ // `/api/v1` could list services and create a task under one, and nothing could create a service,
10
+ // link a repository to one, express an ordering between two tasks, or change a task's attached
11
+ // documents after it was filed. That is the same class of gap the outbound webhook and headless key
12
+ // provisioning each closed for themselves: a deployment whose operator is headless could drive
13
+ // every part of this API except the one act it had to open a browser for.
14
+ //
15
+ // Three rules shape everything here, and each is a boundary rather than a preference:
16
+ //
17
+ // 1. **No board COORDINATES.** Positions, sizes and reparenting are ergonomics for a human looking
18
+ // at a canvas. Publishing a coordinate system into a surface that is frozen forever buys an
19
+ // integration nothing and costs the board its freedom to change how it lays itself out, so a
20
+ // service created here is laid out by the platform exactly as the app's own import button
21
+ // lays one out.
22
+ // 2. **The REPO LINK is the load-bearing half.** A service frame with no linked repository cannot
23
+ // run anything (`resolveRepoTarget` throws by design, deliberately with no first-repo
24
+ // fallback), so an endpoint that created frames and could not link them would ship output
25
+ // that is unusable by the very surface that made it.
26
+ // 3. **One rule at every door.** Every write here delegates to the SAME service method the SPA's
27
+ // own controller calls, so an invariant cannot differ by surface.
28
+ // ---------------------------------------------------------------------------
29
+ /**
30
+ * The repository a new service frame is backed by.
31
+ *
32
+ * `repoId` is the provider's own id for the repo, as `GET /api/v1/repos` serves it: the neutral
33
+ * name for what the internal projection calls `githubId`. It is deliberately not an `owner/name`
34
+ * pair: a repo can be renamed or transferred without changing its id, and a caller that held a name
35
+ * would silently create a service against a different repository after such a move.
36
+ */
37
+ export const publicServiceRepoSchema = v.object({
38
+ /** The repo's provider id, from `GET /api/v1/repos`. */
39
+ repoId: v.number(),
40
+ /**
41
+ * For a MONOREPO, the subdirectory (relative to the repo root) this service lives in, e.g.
42
+ * `packages/api`. Required when the repo is a monorepo and refused for a whole-repo service,
43
+ * because it is what scopes each agent's working directory: a monorepo backs one service per
44
+ * subdirectory, and two services claiming the same one would fight over the same subtree.
45
+ */
46
+ directory: v.optional(v.pipe(v.string(), v.trim(), v.maxLength(400))),
47
+ /**
48
+ * Whether the repository hosts SEVERAL services. Sent with the create rather than as a separate
49
+ * up-front write; when supplied it is persisted on the repo, so the flag a later create reads is
50
+ * the one this call set. Omitted ⇒ whatever the repo already says.
51
+ */
52
+ monorepo: v.optional(v.boolean()),
53
+ });
54
+ /**
55
+ * Create a board service (a service frame), optionally backed by a repository.
56
+ *
57
+ * Without `repo` the frame is a structural placeholder: it can hold tasks, and a run started on one
58
+ * of them is REFUSED, because execution resolves a task's repository by walking up to the enclosing
59
+ * service frame and there is nothing there. That is a legitimate intermediate state (a caller
60
+ * mapping out a board before its repositories exist) and it is why the field is optional rather
61
+ * than why it should usually be omitted.
62
+ *
63
+ * There is deliberately no `position`: see the note at the top of this file.
64
+ */
65
+ export const createPublicServiceSchema = v.object({
66
+ /**
67
+ * The service's name. Optional, because a repo-backed service is named after the repository (or,
68
+ * for a monorepo service, after its subdirectory) exactly as the app's import does, and a caller
69
+ * that has no better name should get that one rather than being made to invent it.
70
+ */
71
+ title: v.optional(v.pipe(v.string(), v.trim(), v.minLength(1), v.maxLength(200))),
72
+ /** What the service is, for the agents that read it as context. Defaults to a generated line. */
73
+ description: v.optional(v.pipe(v.string(), v.trim(), v.maxLength(2000))),
74
+ /**
75
+ * The service's architectural role. Omitted ⇒ `service`.
76
+ *
77
+ * Narrower than the board's own block types on purpose: this is the set a REPOSITORY can back,
78
+ * which is the only kind of frame this endpoint creates. A `database` or `queue` frame documents
79
+ * infrastructure for the agents to read and runs nothing, so nothing here would create one.
80
+ */
81
+ type: v.optional(frameRepoTypeSchema),
82
+ /** The repository backing the service. Omitted ⇒ an unlinked frame (see above). */
83
+ repo: v.optional(publicServiceRepoSchema),
84
+ });
85
+ /**
86
+ * A repository this workspace can back a service with, as `GET /api/v1/repos` lists it.
87
+ *
88
+ * The discovery half of {@link publicServiceRepoSchema}, and it is why service creation is usable
89
+ * headlessly at all: the create takes a `repoId`, and until this existed the only way to learn one
90
+ * was to open the app. Deliberately a small projection (enough to recognise a repository and pass
91
+ * it back), not a mirror of the internal projection row, which carries installation ids and
92
+ * sync bookkeeping that are this platform's business rather than a caller's.
93
+ */
94
+ export const publicRepoSchema = v.object({
95
+ /** The provider's id for the repo: the value to pass as `repo.repoId`. */
96
+ repoId: v.number(),
97
+ /** Which provider it lives on (`github` / `gitlab`). */
98
+ provider: v.string(),
99
+ owner: v.string(),
100
+ name: v.string(),
101
+ /**
102
+ * The branch a run's work is based on and merged into, or EMPTY when the projection has not
103
+ * recorded one yet (a repository connected moments ago, before its first sync). Empty rather
104
+ * than null because there is nothing here that could invent a default, and a caller reading it
105
+ * to name a base needs to see that it has to ask the provider rather than assume `main`.
106
+ */
107
+ defaultBranch: v.string(),
108
+ /** Whether the repository is private on its provider. */
109
+ private: v.boolean(),
110
+ /** Whether the repo is flagged as hosting several services (see `repo.monorepo`). */
111
+ monorepo: v.boolean(),
112
+ /**
113
+ * The service this repository already backs ON THIS BOARD, or null.
114
+ *
115
+ * Present because a whole-repo repository backs at most ONE service, so a caller choosing one to
116
+ * create against needs to know which choices are already spent, and, more usefully, because a
117
+ * caller re-running its provisioning finds the service it created last time here rather than
118
+ * discovering it through a `409`. A monorepo answers null even when its subdirectories back
119
+ * services, since it can back more.
120
+ *
121
+ * Null and {@link linkedElsewhere} together are the honest answer when the service is homed on
122
+ * another board of the account: read the flag before treating null as "available".
123
+ */
124
+ serviceId: v.nullable(v.string()),
125
+ /**
126
+ * True when this repository already backs a whole-repo service homed on ANOTHER board of the
127
+ * account, so `POST /api/v1/services` will refuse it (`reason: repo_service_homed_elsewhere`).
128
+ *
129
+ * A service is account-owned and a board can MOUNT one homed elsewhere, but every read on this
130
+ * API is scoped to the calling key's own workspace, so a frame homed on another board has no id
131
+ * this surface could hand back: it would not appear in `GET /api/v1/services` and
132
+ * `POST /api/v1/services/{serviceId}/tasks` would 404 on it. Hence a flag rather than a second
133
+ * id field — this states that the choice is spent without naming an address that does not work
134
+ * here. Use the board that homes the service, or a key scoped to it.
135
+ */
136
+ linkedElsewhere: v.boolean(),
137
+ });
138
+ export const publicRepoListSchema = v.object({ repos: v.array(publicRepoSchema) });
139
+ /**
140
+ * Declare that a task must wait for another one.
141
+ *
142
+ * The gap it closes: an integration filing five related tasks and starting them got five runs
143
+ * racing against one repository, each opening a pull request against a base the others were
144
+ * moving. The platform has had the mechanism to serialise them (the engine's start gate refuses a
145
+ * task whose blockers are not `done`, and `autoStartDependents` starts a task when its blocker
146
+ * merges) and no way for an external caller to be told about it.
147
+ *
148
+ * The edge is stored on the DEPENDENT: `POST /api/v1/tasks/{taskId}/dependencies` says "this task
149
+ * waits for `dependsOnTaskId`". Both ends must be tasks (only a task ever reaches `done`, so an
150
+ * edge onto a service or a module would wedge the run's start gate forever), and an edge that would
151
+ * close a cycle is refused, so the gate and the auto-start can never deadlock.
152
+ */
153
+ export const publicTaskDependencySchema = v.object({
154
+ /** The task that must finish first. Must be a task in the same workspace, and not this one. */
155
+ dependsOnTaskId: v.pipe(v.string(), v.trim(), v.minLength(1), v.maxLength(120)),
156
+ });
157
+ /**
158
+ * A document attached to a task as agent context, as the task's document list serves it.
159
+ *
160
+ * Identified by `(source, externalId)` rather than by an id of its own, because that pair IS a
161
+ * projected document's identity: a re-import of the same page lands on the same row, which is what
162
+ * makes attaching one idempotent. `source` is `upload` for a body a caller carried rather than
163
+ * named, and such a document has no page behind it, which is why `url` is empty there rather than
164
+ * absent.
165
+ */
166
+ export const publicAttachedDocumentSchema = v.object({
167
+ /** Which source the document came from, or `upload` for one a caller carried inline. */
168
+ source: documentOriginSchema,
169
+ /** The source's stable id for the page: the value to pass back to detach it. */
170
+ externalId: v.string(),
171
+ title: v.string(),
172
+ /** Canonical URL on the source; EMPTY for an `upload`, which has no page to link back to. */
173
+ url: v.string(),
174
+ /** A short plain-text excerpt of the body (the full text reaches the run, not this list). */
175
+ excerpt: v.string(),
176
+ });
177
+ export const publicAttachedDocumentListSchema = v.object({
178
+ documents: v.array(publicAttachedDocumentSchema),
179
+ });
180
+ /**
181
+ * Attach a requirements document to a task that already exists.
182
+ *
183
+ * The same two forms creation takes ({@link publicTaskDocumentSchema}): NAME a page in a connected
184
+ * document source, or CARRY the text. What it adds is the moment: a task's spec routinely arrives
185
+ * after the task does (a ticket filed first and specified later, a PRD that lands mid-review), and
186
+ * until this existed the only way to attach one was to delete the task and file it again, losing
187
+ * the id every stored reference points at, its ticket claim (which then refuses every future filing
188
+ * of that ticket) and the documents it already carried.
189
+ *
190
+ * A document a DIFFERENT live task already holds is refused rather than moved: a document row
191
+ * carries exactly one attachment, so moving it would strip the other task of a document it was
192
+ * created with, with nothing in its next run reporting the absence.
193
+ */
194
+ export const attachPublicTaskDocumentSchema = v.object({
195
+ document: publicTaskDocumentSchema,
196
+ });
197
+ /**
198
+ * Detach a document from a task, naming it by the `(source, externalId)` pair the task's document
199
+ * list serves.
200
+ *
201
+ * A POST with a body rather than a `DELETE .../documents/{id}`, because a document's identity is
202
+ * two values and one of them is a free-form external id: a Confluence page id is fine in a path
203
+ * segment and a GitHub docs path (`docs/architecture/adr-0001.md`) is not, so half the sources
204
+ * would need escaping rules a caller has to get right to address its own document.
205
+ *
206
+ * The document itself SURVIVES: it stays in the workspace exactly as the app's own detach leaves
207
+ * it, so re-attaching it costs no re-import. Idempotent: detaching a document this task does not
208
+ * hold is a no-op rather than an error, because a caller retrying after a timeout should converge
209
+ * rather than have to distinguish "it was never attached" from "I already detached it".
210
+ */
211
+ export const detachPublicTaskDocumentSchema = v.object({
212
+ source: documentOriginSchema,
213
+ externalId: v.pipe(v.string(), v.trim(), v.minLength(1), v.maxLength(500)),
214
+ });
215
+ //# sourceMappingURL=public-board.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"public-board.js","sourceRoot":"","sources":["../src/public-board.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,SAAS,CAAA;AAC5B,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAA;AACrD,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAA;AACrD,OAAO,EAAE,wBAAwB,EAAE,MAAM,iBAAiB,CAAA;AAE1D,8EAA8E;AAC9E,6FAA6F;AAC7F,gEAAgE;AAChE,EAAE;AACF,iGAAiG;AACjG,+FAA+F;AAC/F,oGAAoG;AACpG,+FAA+F;AAC/F,0EAA0E;AAC1E,EAAE;AACF,sFAAsF;AACtF,EAAE;AACF,oGAAoG;AACpG,gGAAgG;AAChG,iGAAiG;AACjG,8FAA8F;AAC9F,oBAAoB;AACpB,mGAAmG;AACnG,0FAA0F;AAC1F,8FAA8F;AAC9F,yDAAyD;AACzD,kGAAkG;AAClG,sEAAsE;AACtE,8EAA8E;AAE9E;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC9C,wDAAwD;IACxD,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE;IAClB;;;;;OAKG;IACH,SAAS,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC;IACrE;;;;OAIG;IACH,QAAQ,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;CAClC,CAAC,CAAA;AAGF;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,CAAC,MAAM,CAAC;IAChD;;;;OAIG;IACH,KAAK,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC;IACjF,iGAAiG;IACjG,WAAW,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;IACxE;;;;;;OAMG;IACH,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,mBAAmB,CAAC;IACrC,mFAAmF;IACnF,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,uBAAuB,CAAC;CAC1C,CAAC,CAAA;AAGF;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC;IACvC,0EAA0E;IAC1E,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE;IAClB,wDAAwD;IACxD,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;IACpB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;IACjB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;IAChB;;;;;OAKG;IACH,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE;IACzB,yDAAyD;IACzD,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE;IACpB,qFAAqF;IACrF,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE;IACrB;;;;;;;;;;;OAWG;IACH,SAAS,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;IACjC;;;;;;;;;;OAUG;IACH,eAAe,EAAE,CAAC,CAAC,OAAO,EAAE;CAC7B,CAAC,CAAA;AAGF,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,gBAAgB,CAAC,EAAE,CAAC,CAAA;AAGlF;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,CAAC,CAAC,MAAM,CAAC;IACjD,+FAA+F;IAC/F,eAAe,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;CAChF,CAAC,CAAA;AAGF;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,4BAA4B,GAAG,CAAC,CAAC,MAAM,CAAC;IACnD,wFAAwF;IACxF,MAAM,EAAE,oBAAoB;IAC5B,gFAAgF;IAChF,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE;IACtB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;IACjB,6FAA6F;IAC7F,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE;IACf,6FAA6F;IAC7F,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;CACpB,CAAC,CAAA;AAGF,MAAM,CAAC,MAAM,gCAAgC,GAAG,CAAC,CAAC,MAAM,CAAC;IACvD,SAAS,EAAE,CAAC,CAAC,KAAK,CAAC,4BAA4B,CAAC;CACjD,CAAC,CAAA;AAGF;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,8BAA8B,GAAG,CAAC,CAAC,MAAM,CAAC;IACrD,QAAQ,EAAE,wBAAwB;CACnC,CAAC,CAAA;AAGF;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,8BAA8B,GAAG,CAAC,CAAC,MAAM,CAAC;IACrD,MAAM,EAAE,oBAAoB;IAC5B,UAAU,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;CAC3E,CAAC,CAAA"}
@@ -6,11 +6,30 @@ import * as v from 'valibot';
6
6
  */
7
7
  export declare const publicArtifactKindSchema: v.PicklistSchema<["screenshot", "reference"], undefined>;
8
8
  export type PublicArtifactKind = v.InferOutput<typeof publicArtifactKindSchema>;
9
+ /**
10
+ * What an artifact is ANCHORED on, which is what says whether the run produced it:
11
+ *
12
+ * - `run`: captured by THIS run. It carries the run's id, and a re-run captures its own.
13
+ * - `task`: attached to the run's task and outliving any single run of it. A reference design a
14
+ * person uploaded before the first run is the case that matters: it is what the run's
15
+ * screenshots are judged against, and it is deliberately not run-anchored, because uploading one
16
+ * per attempt is exactly what a reference exists to avoid.
17
+ *
18
+ * Stated per row rather than folded away, because the two sets answer different questions and a
19
+ * silent union would make "the run captured 3 screenshots" unreadable off a list of 5. It is also
20
+ * why the list is not simply the run's own rows: a caller enumerating a run's artifacts to compare
21
+ * a screenshot against its reference saw only one half, so the reference rendered as ABSENT on the
22
+ * one surface whose job is to say what a run proved, while being individually fetchable all along.
23
+ */
24
+ export declare const publicArtifactScopeSchema: v.PicklistSchema<["run", "task"], undefined>;
25
+ export type PublicArtifactScope = v.InferOutput<typeof publicArtifactScopeSchema>;
9
26
  /** One binary artifact a run produced (metadata; the bytes are a separate fetch). */
10
27
  export declare const publicRunArtifactSchema: v.ObjectSchema<{
11
28
  /** The id to pass to `GET /api/v1/artifacts/{artifactId}/blob`. */
12
29
  readonly artifactId: v.StringSchema<undefined>;
13
30
  readonly kind: v.PicklistSchema<["screenshot", "reference"], undefined>;
31
+ /** Which anchor this row came from ({@link publicArtifactScopeSchema}). */
32
+ readonly scope: v.PicklistSchema<["run", "task"], undefined>;
14
33
  /** Logical view name, which is what pairs a screenshot with its reference. Null when unnamed. */
15
34
  readonly view: v.NullableSchema<v.StringSchema<undefined>, undefined>;
16
35
  /** The MIME type the blob endpoint will answer with (always a raster image today). */
@@ -26,17 +45,25 @@ export declare const publicRunArtifactSchema: v.ObjectSchema<{
26
45
  }, undefined>;
27
46
  export type PublicRunArtifact = v.InferOutput<typeof publicRunArtifactSchema>;
28
47
  /**
29
- * A run's artifacts, whole rather than paginated.
48
+ * A run's artifacts (the ones it CAPTURED plus the ones attached to its task, each saying which
49
+ * it is: {@link publicArtifactScopeSchema}), whole rather than paginated.
30
50
  *
31
51
  * Deliberately unpaged where every sibling list is keyset-paginated: the capture path enforces
32
- * a per-run ceiling, so the row count is bounded by construction and the response size is
33
- * computable before the request. A cursor here would be a page-2 that structurally cannot exist.
52
+ * a per-run ceiling and a task's uploads are a human-sized set, so the row count is bounded by
53
+ * construction and the response size is computable before the request. A cursor here would be a
54
+ * page-2 that structurally cannot exist.
55
+ *
56
+ * An artifact that is BOTH (a screenshot a run captured against its own task) appears ONCE, as
57
+ * `run`: the run is the more specific anchor, and a row appearing twice under two scopes would
58
+ * make every count off this list wrong in the direction that reads as extra evidence.
34
59
  */
35
60
  export declare const publicRunArtifactListSchema: v.ObjectSchema<{
36
61
  readonly artifacts: v.ArraySchema<v.ObjectSchema<{
37
62
  /** The id to pass to `GET /api/v1/artifacts/{artifactId}/blob`. */
38
63
  readonly artifactId: v.StringSchema<undefined>;
39
64
  readonly kind: v.PicklistSchema<["screenshot", "reference"], undefined>;
65
+ /** Which anchor this row came from ({@link publicArtifactScopeSchema}). */
66
+ readonly scope: v.PicklistSchema<["run", "task"], undefined>;
40
67
  /** Logical view name, which is what pairs a screenshot with its reference. Null when unnamed. */
41
68
  readonly view: v.NullableSchema<v.StringSchema<undefined>, undefined>;
42
69
  /** The MIME type the blob endpoint will answer with (always a raster image today). */
@@ -1 +1 @@
1
- {"version":3,"file":"public-evidence.d.ts","sourceRoot":"","sources":["../src/public-evidence.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,SAAS,CAAA;AA0B5B;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,0DAA0C,CAAA;AAC/E,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,wBAAwB,CAAC,CAAA;AAE/E,qFAAqF;AACrF,eAAO,MAAM,uBAAuB;IAClC,mEAAmE;;;IAGnE,iGAAiG;;IAEjG,sFAAsF;;IAEtF;;;OAGG;;IAEH,0FAA0F;;;aAG1F,CAAA;AACF,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,uBAAuB,CAAC,CAAA;AAE7E;;;;;;GAMG;AACH,eAAO,MAAM,2BAA2B;;QAzBtC,mEAAmE;;;QAGnE,iGAAiG;;QAEjG,sFAAsF;;QAEtF;;;WAGG;;QAEH,0FAA0F;;;;aAe1F,CAAA;AACF,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,2BAA2B,CAAC,CAAA"}
1
+ {"version":3,"file":"public-evidence.d.ts","sourceRoot":"","sources":["../src/public-evidence.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,SAAS,CAAA;AA0B5B;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,0DAA0C,CAAA;AAC/E,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,wBAAwB,CAAC,CAAA;AAE/E;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,yBAAyB,8CAA8B,CAAA;AACpE,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,yBAAyB,CAAC,CAAA;AAEjF,qFAAqF;AACrF,eAAO,MAAM,uBAAuB;IAClC,mEAAmE;;;IAGnE,2EAA2E;;IAE3E,iGAAiG;;IAEjG,sFAAsF;;IAEtF;;;OAGG;;IAEH,0FAA0F;;;aAG1F,CAAA;AACF,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,uBAAuB,CAAC,CAAA;AAE7E;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,2BAA2B;;QAjCtC,mEAAmE;;;QAGnE,2EAA2E;;QAE3E,iGAAiG;;QAEjG,sFAAsF;;QAEtF;;;WAGG;;QAEH,0FAA0F;;;;aAqB1F,CAAA;AACF,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,2BAA2B,CAAC,CAAA"}
@@ -28,11 +28,29 @@ import * as v from 'valibot';
28
28
  * human uploaded for the run to be judged against.
29
29
  */
30
30
  export const publicArtifactKindSchema = v.picklist(['screenshot', 'reference']);
31
+ /**
32
+ * What an artifact is ANCHORED on, which is what says whether the run produced it:
33
+ *
34
+ * - `run`: captured by THIS run. It carries the run's id, and a re-run captures its own.
35
+ * - `task`: attached to the run's task and outliving any single run of it. A reference design a
36
+ * person uploaded before the first run is the case that matters: it is what the run's
37
+ * screenshots are judged against, and it is deliberately not run-anchored, because uploading one
38
+ * per attempt is exactly what a reference exists to avoid.
39
+ *
40
+ * Stated per row rather than folded away, because the two sets answer different questions and a
41
+ * silent union would make "the run captured 3 screenshots" unreadable off a list of 5. It is also
42
+ * why the list is not simply the run's own rows: a caller enumerating a run's artifacts to compare
43
+ * a screenshot against its reference saw only one half, so the reference rendered as ABSENT on the
44
+ * one surface whose job is to say what a run proved, while being individually fetchable all along.
45
+ */
46
+ export const publicArtifactScopeSchema = v.picklist(['run', 'task']);
31
47
  /** One binary artifact a run produced (metadata; the bytes are a separate fetch). */
32
48
  export const publicRunArtifactSchema = v.object({
33
49
  /** The id to pass to `GET /api/v1/artifacts/{artifactId}/blob`. */
34
50
  artifactId: v.string(),
35
51
  kind: publicArtifactKindSchema,
52
+ /** Which anchor this row came from ({@link publicArtifactScopeSchema}). */
53
+ scope: publicArtifactScopeSchema,
36
54
  /** Logical view name, which is what pairs a screenshot with its reference. Null when unnamed. */
37
55
  view: v.nullable(v.string()),
38
56
  /** The MIME type the blob endpoint will answer with (always a raster image today). */
@@ -47,11 +65,17 @@ export const publicRunArtifactSchema = v.object({
47
65
  createdAt: v.number(),
48
66
  });
49
67
  /**
50
- * A run's artifacts, whole rather than paginated.
68
+ * A run's artifacts (the ones it CAPTURED plus the ones attached to its task, each saying which
69
+ * it is: {@link publicArtifactScopeSchema}), whole rather than paginated.
51
70
  *
52
71
  * Deliberately unpaged where every sibling list is keyset-paginated: the capture path enforces
53
- * a per-run ceiling, so the row count is bounded by construction and the response size is
54
- * computable before the request. A cursor here would be a page-2 that structurally cannot exist.
72
+ * a per-run ceiling and a task's uploads are a human-sized set, so the row count is bounded by
73
+ * construction and the response size is computable before the request. A cursor here would be a
74
+ * page-2 that structurally cannot exist.
75
+ *
76
+ * An artifact that is BOTH (a screenshot a run captured against its own task) appears ONCE, as
77
+ * `run`: the run is the more specific anchor, and a row appearing twice under two scopes would
78
+ * make every count off this list wrong in the direction that reads as extra evidence.
55
79
  */
56
80
  export const publicRunArtifactListSchema = v.object({
57
81
  artifacts: v.array(publicRunArtifactSchema),
@@ -1 +1 @@
1
- {"version":3,"file":"public-evidence.js","sourceRoot":"","sources":["../src/public-evidence.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,SAAS,CAAA;AAE5B,8EAA8E;AAC9E,wFAAwF;AACxF,kDAAkD;AAClD,EAAE;AACF,2FAA2F;AAC3F,wFAAwF;AACxF,uFAAuF;AACvF,wFAAwF;AACxF,0FAA0F;AAC1F,oBAAoB;AACpB,EAAE;AACF,6CAA6C;AAC7C,EAAE;AACF,6FAA6F;AAC7F,6FAA6F;AAC7F,uFAAuF;AACvF,0FAA0F;AAC1F,yEAAyE;AACzE,yFAAyF;AACzF,8FAA8F;AAC9F,4FAA4F;AAC5F,mEAAmE;AACnE,8EAA8E;AAE9E;;;;GAIG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC,YAAY,EAAE,WAAW,CAAC,CAAC,CAAA;AAG/E,qFAAqF;AACrF,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC9C,mEAAmE;IACnE,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE;IACtB,IAAI,EAAE,wBAAwB;IAC9B,iGAAiG;IACjG,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;IAC5B,sFAAsF;IACtF,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE;IACvB;;;OAGG;IACH,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;IACpB,0FAA0F;IAC1F,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;IAChB,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;CACtB,CAAC,CAAA;AAGF;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG,CAAC,CAAC,MAAM,CAAC;IAClD,SAAS,EAAE,CAAC,CAAC,KAAK,CAAC,uBAAuB,CAAC;CAC5C,CAAC,CAAA"}
1
+ {"version":3,"file":"public-evidence.js","sourceRoot":"","sources":["../src/public-evidence.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,SAAS,CAAA;AAE5B,8EAA8E;AAC9E,wFAAwF;AACxF,kDAAkD;AAClD,EAAE;AACF,2FAA2F;AAC3F,wFAAwF;AACxF,uFAAuF;AACvF,wFAAwF;AACxF,0FAA0F;AAC1F,oBAAoB;AACpB,EAAE;AACF,6CAA6C;AAC7C,EAAE;AACF,6FAA6F;AAC7F,6FAA6F;AAC7F,uFAAuF;AACvF,0FAA0F;AAC1F,yEAAyE;AACzE,yFAAyF;AACzF,8FAA8F;AAC9F,4FAA4F;AAC5F,mEAAmE;AACnE,8EAA8E;AAE9E;;;;GAIG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC,YAAY,EAAE,WAAW,CAAC,CAAC,CAAA;AAG/E;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAA;AAGpE,qFAAqF;AACrF,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC9C,mEAAmE;IACnE,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE;IACtB,IAAI,EAAE,wBAAwB;IAC9B,2EAA2E;IAC3E,KAAK,EAAE,yBAAyB;IAChC,iGAAiG;IACjG,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;IAC5B,sFAAsF;IACtF,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE;IACvB;;;OAGG;IACH,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;IACpB,0FAA0F;IAC1F,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;IAChB,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;CACtB,CAAC,CAAA;AAGF;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG,CAAC,CAAC,MAAM,CAAC;IAClD,SAAS,EAAE,CAAC,CAAC,KAAK,CAAC,uBAAuB,CAAC;CAC5C,CAAC,CAAA"}