@anchrd/intel-contract 0.12.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/contract/contract.d.ts +66 -1193
- package/dist/contract/contract.js +16 -705
- package/package.json +1 -1
|
@@ -28,9 +28,6 @@ export const SessionUser = z.strictObject({
|
|
|
28
28
|
// (those stay behind each door, where `/session` deliberately does not carry them). `agentRuntime`
|
|
29
29
|
// says whether an agent Worker is bound at all (#190): without it the UI offers no "New agent" and
|
|
30
30
|
// an agent node explains itself instead of rendering views that could only end in a 503.
|
|
31
|
-
export const IntelCapabilities = z.strictObject({
|
|
32
|
-
agentRuntime: z.boolean(),
|
|
33
|
-
});
|
|
34
31
|
// The fourth kind is `table` (#40), the fifth is `agent` (#139) and the sixth is `board` (#285).
|
|
35
32
|
// Each is a kind of node, not a kind of thing: it hangs in the same folder tree, inherits the same
|
|
36
33
|
// folder grants, carries the same immutable versions and the same R2 body as a document
|
|
@@ -41,7 +38,7 @@ export const IntelCapabilities = z.strictObject({
|
|
|
41
38
|
// or a policy in order to file it. The same holds for `board`: it is a file somebody may make, not
|
|
42
39
|
// a place the tree grows a special corner for — which is exactly why a board is one node carrying
|
|
43
40
|
// its tasks and not a folder that only tasks may live in (#285).
|
|
44
|
-
export const NodeKind = z.enum(["folder", "document", "attachment", "table"
|
|
41
|
+
export const NodeKind = z.enum(["folder", "document", "attachment", "table"]);
|
|
45
42
|
// ⚠️ There is no `ContextPolicy`, and it is not coming back in this shape (#76). It said whether a
|
|
46
43
|
// document should be pinned into a context, be found by relevance, or be named explicitly — an
|
|
47
44
|
// instruction to a retrieval Intel does not perform. Intel hands out references and the agent
|
|
@@ -268,469 +265,6 @@ export const RedefineTableInput = z.strictObject({
|
|
|
268
265
|
}, { error: "A current column can fill only one new column" }),
|
|
269
266
|
idempotencyKey: z.string().min(8).max(200),
|
|
270
267
|
});
|
|
271
|
-
// ── The board (#285) ─────────────────────────────────────────────────────────────────────────────
|
|
272
|
-
//
|
|
273
|
-
// A board's body is a whole project board — the status list and every task — stored as one
|
|
274
|
-
// validated JSON version in R2, exactly the way an agent's definition is. It is a kind of node and
|
|
275
|
-
// not a kind of thing (see `NodeKind`): it hangs in a folder, inherits that folder's grants, has
|
|
276
|
-
// versions and is exported like everything else. Its own media type exists so a reader can tell a
|
|
277
|
-
// board from prose without parsing it.
|
|
278
|
-
export const BoardMediaType = "application/vnd.anchrd.board+json";
|
|
279
|
-
// ⚠️ `archived` belongs to EVERY status list and cannot be configured away (#285). It is the shelf
|
|
280
|
-
// tasks are swept onto, so that being finished with a task never has to mean deleting it — and
|
|
281
|
-
// getting one back is an explicit move to another status, never an undelete.
|
|
282
|
-
export const ArchivedBoardStatusId = "archived";
|
|
283
|
-
// A status id is referenced by every task that sits in that column, so it is machine-shaped and
|
|
284
|
-
// bounded rather than free text. Renaming a column changes its LABEL; the id stays, and no task has
|
|
285
|
-
// to be rewritten to follow it.
|
|
286
|
-
export const BoardStatusId = z.string().regex(/^[a-z0-9][a-z0-9_]{0,39}$/);
|
|
287
|
-
export const BoardStatus = z.strictObject({
|
|
288
|
-
id: BoardStatusId,
|
|
289
|
-
label: z.string().trim().min(1).max(60),
|
|
290
|
-
// ⚠️ Server-assigned from the position in `ConfigureBoardInput.statuses`, never sent. Two columns
|
|
291
|
-
// both claiming position 3 is a board no surface could draw, and it is a state nobody has to be
|
|
292
|
-
// able to reason about if it cannot be expressed.
|
|
293
|
-
order: z.number().int().nonnegative(),
|
|
294
|
-
/**
|
|
295
|
-
* Whether standing in this column means the work is finished (anchrd/intel#311).
|
|
296
|
-
*
|
|
297
|
-
* It answers the one question #285 left open — when a `dependsOn` is satisfied — and it is the
|
|
298
|
-
* ONLY answer to it. There is deliberately no second reading anywhere: a surface that decided
|
|
299
|
-
* "done" for itself would decide it differently the first time somebody reconfigured a board.
|
|
300
|
-
*
|
|
301
|
-
* ⚠️ It is a property of the status, not a position in the list, and that distinction is the
|
|
302
|
-
* whole ticket. #286 first read "the last column before `archived`" as done, reasoning by symmetry
|
|
303
|
-
* with the server's rule that a new task lands in the first column that is not `archived`. But a
|
|
304
|
-
* status list is configurable on purpose, so `… done → blocked → archived` makes "blocked" mean
|
|
305
|
-
* finished — silently, with a wrong blocked marker as the only symptom.
|
|
306
|
-
*
|
|
307
|
-
* ⚠️ Several columns may carry it. "Done" and a cancelled-like column are both ends of the work,
|
|
308
|
-
* and nothing waiting on a cancelled task is still blocked by it.
|
|
309
|
-
*/
|
|
310
|
-
terminal: z.boolean(),
|
|
311
|
-
});
|
|
312
|
-
// What a board starts out with. Five columns, of which the last one is the fixed `archived` shelf.
|
|
313
|
-
// `done` and the shelf are where work ends; the three before them are not (anchrd/intel#311).
|
|
314
|
-
export const BoardDefaultStatuses = [
|
|
315
|
-
{ id: "backlog", label: "Backlog", order: 0, terminal: false },
|
|
316
|
-
{ id: "in_progress", label: "In progress", order: 1, terminal: false },
|
|
317
|
-
{ id: "review", label: "Review", order: 2, terminal: false },
|
|
318
|
-
{ id: "done", label: "Done", order: 3, terminal: true },
|
|
319
|
-
{ id: ArchivedBoardStatusId, label: "Archived", order: 4, terminal: true },
|
|
320
|
-
];
|
|
321
|
-
/**
|
|
322
|
-
* Who a task is on: a person Gate knows, or an agent node in this installation (#285).
|
|
323
|
-
*
|
|
324
|
-
* ⚠️ A `user` id is deliberately NOT validated against Intel's own id shape. Identity is Gate's
|
|
325
|
-
* (see the product boundary), so a rule here would be Intel inventing one about somebody else's
|
|
326
|
-
* identifier — the same reason `GateApplicationId` is a plain bounded string.
|
|
327
|
-
*/
|
|
328
|
-
export const BoardAssignee = z.discriminatedUnion("type", [
|
|
329
|
-
z.strictObject({ type: z.literal("user"), id: z.string().min(1).max(255) }),
|
|
330
|
-
z.strictObject({ type: z.literal("agent"), nodeId: IntelId }),
|
|
331
|
-
]);
|
|
332
|
-
/**
|
|
333
|
-
* A task's place among the others, as a fractional index (#285).
|
|
334
|
-
*
|
|
335
|
-
* ⚠️ Server-assigned, and a caller can never send one. A move names its NEIGHBOURS and the server
|
|
336
|
-
* mints a key between theirs, so moving one task writes one task and renumbers nothing — the whole
|
|
337
|
-
* reason a board is not addressed by position the way a table's rows are (`TableRowPosition`).
|
|
338
|
-
* A hand-written key could collide, and two tasks sharing a key have no defined order at all.
|
|
339
|
-
*
|
|
340
|
-
* ⚠️ This CHARACTER CLASS is the truth about a stored key, not `fractional-indexing`, and the two
|
|
341
|
-
* are deliberately not the same set (anchrd/intel#359). A board that arrived through a bundle may
|
|
342
|
-
* spell `"0"`, `"a00"`, `"zzz"` or `"A"` — all of them legal here, none of them readable by that
|
|
343
|
-
* library. Narrowing the regex to what it reads would be a rule on the STORED document, so a board
|
|
344
|
-
* carrying one would stop parsing in all four places a board body is read (the board read, the
|
|
345
|
-
* indexer, the link reader, the bundle import) — unreadable, unsearchable and unmovable at once,
|
|
346
|
-
* over a value its owner never wrote. That is the trap #311, #318 and #321 each walked into from a
|
|
347
|
-
* different side.
|
|
348
|
-
*
|
|
349
|
-
* ⚠️ What every consumer may therefore rely on is exactly what stands here: keys are non-empty
|
|
350
|
-
* strings over `[0-9A-Za-z]` and are ORDERED BY STRING COMPARISON. Nothing may assume more — and
|
|
351
|
-
* `packages/api`'s `orderBetween` is the one place that asks `fractional-indexing` for a key and
|
|
352
|
-
* carries on without it when it refuses a bound.
|
|
353
|
-
*/
|
|
354
|
-
// ⚠️ Named because a consumer has to be able to stay inside it. The repair of a repeated task id
|
|
355
|
-
// (anchrd/intel#341) mints a key beside an existing one, and a key one character too long would be
|
|
356
|
-
// written and then refused by the very next read — the whole board lost over a repair.
|
|
357
|
-
export const MaxBoardTaskOrderLength = 64;
|
|
358
|
-
export const BoardTaskOrder = z
|
|
359
|
-
.string()
|
|
360
|
-
.regex(new RegExp(`^[0-9A-Za-z]{1,${MaxBoardTaskOrderLength}}$`));
|
|
361
|
-
export const BoardTaskId = IntelId;
|
|
362
|
-
export const BoardTaskLabel = z.string().trim().min(1).max(60);
|
|
363
|
-
// A day, not an instant. A task is due on a date; giving it a time zone would make the same task
|
|
364
|
-
// due on two different days depending on who is looking at it.
|
|
365
|
-
export const BoardTaskDate = z.iso.date();
|
|
366
|
-
// Markdown, and capped: a task's description is a card, and what needs more than this is a document
|
|
367
|
-
// the task can point at through `references`.
|
|
368
|
-
export const BoardTaskDescription = z.string().max(20_000);
|
|
369
|
-
/**
|
|
370
|
-
* The tasks one task waits for (#285), each of them at most once (anchrd/intel#318).
|
|
371
|
-
*
|
|
372
|
-
* ⚠️ Board-internal only, enforced on the write path: a dependency on a task in another board would
|
|
373
|
-
* hang this node on a file that can change without anyone here noticing. Across boards the link is
|
|
374
|
-
* `references`, which points at the board NODE and lands in the link graph.
|
|
375
|
-
*
|
|
376
|
-
* ⚠️ Refused rather than folded together, the same shape as the status ids in `ConfigureBoardInput`.
|
|
377
|
-
* A repeat carries no information — but that is a fact about the value, not about the answer: a
|
|
378
|
-
* caller handed back a shorter list than it sent is told nothing, and composes the same one again.
|
|
379
|
-
* The refusal names the mistake once, and the caller is holding the list it has to fix. (The one
|
|
380
|
-
* place a repeat is folded instead is `upgradeStoredBoard`, where there is no caller to tell.)
|
|
381
|
-
*
|
|
382
|
-
* ⚠️ It sits on the STORED task as well as on the two inputs, so a consumer may rely on it rather
|
|
383
|
-
* than defend against it — `createBoardGraph` mints one edge key per pair, and a second one threw
|
|
384
|
-
* the whole graph view off the screen for everybody looking at that board.
|
|
385
|
-
*/
|
|
386
|
-
export const BoardTaskDependsOn = z
|
|
387
|
-
.array(BoardTaskId)
|
|
388
|
-
.max(64)
|
|
389
|
-
.refine((ids) => new Set(ids).size === ids.length, {
|
|
390
|
-
error: "A task can be named only once in dependsOn",
|
|
391
|
-
});
|
|
392
|
-
/**
|
|
393
|
-
* A task's labels, each of them at most once (anchrd/intel#318).
|
|
394
|
-
*
|
|
395
|
-
* ⚠️ The same rule the detail panel has always applied to what a person types — it refuses to add a
|
|
396
|
-
* label the task already carries — stated where every surface meets it, because the MCP write path
|
|
397
|
-
* did not. A repeated label draws the same chip twice on the card, with two remove buttons of which
|
|
398
|
-
* either takes both away, and weights that word higher in the search text (`indexing.ts`).
|
|
399
|
-
*/
|
|
400
|
-
export const BoardTaskLabels = z
|
|
401
|
-
.array(BoardTaskLabel)
|
|
402
|
-
.max(32)
|
|
403
|
-
.refine((labels) => new Set(labels).size === labels.length, {
|
|
404
|
-
error: "A label can be named only once",
|
|
405
|
-
});
|
|
406
|
-
/**
|
|
407
|
-
* The Intel nodes a task points at, each of them at most once (anchrd/intel#318).
|
|
408
|
-
*
|
|
409
|
-
* They land in the link graph as `text` links, the same way a document's inline links do, so what a
|
|
410
|
-
* board points at is visible from the other side too.
|
|
411
|
-
*
|
|
412
|
-
* ⚠️ Distinct for the same reason as `labels`: the picker in the detail panel already refuses one
|
|
413
|
-
* the task holds, and the link graph counts a repeat once anyway (`ON CONFLICT DO NOTHING`), so a
|
|
414
|
-
* duplicate is a second row in the panel and nothing else — which is exactly the kind of value that
|
|
415
|
-
* has no reading and should not be storable.
|
|
416
|
-
*/
|
|
417
|
-
export const BoardTaskReferences = z
|
|
418
|
-
.array(IntelId)
|
|
419
|
-
.max(64)
|
|
420
|
-
.refine((ids) => new Set(ids).size === ids.length, {
|
|
421
|
-
error: "A node can be referenced only once",
|
|
422
|
-
});
|
|
423
|
-
export const BoardTask = z.strictObject({
|
|
424
|
-
id: BoardTaskId,
|
|
425
|
-
title: z.string().trim().min(1).max(240),
|
|
426
|
-
status: BoardStatusId,
|
|
427
|
-
assignee: BoardAssignee.nullable(),
|
|
428
|
-
labels: BoardTaskLabels,
|
|
429
|
-
startDate: BoardTaskDate.nullable(),
|
|
430
|
-
dueDate: BoardTaskDate.nullable(),
|
|
431
|
-
// ⚠️ The whole hierarchy in one field, deliberately: epic, task and subtask are a DEPTH and not a
|
|
432
|
-
// type (#285). A `kind` beside it would allow a subtask under nothing and an epic under an epic,
|
|
433
|
-
// and every surface would then need its own opinion about which combinations mean anything.
|
|
434
|
-
parentId: BoardTaskId.nullable(),
|
|
435
|
-
dependsOn: BoardTaskDependsOn,
|
|
436
|
-
order: BoardTaskOrder,
|
|
437
|
-
description: BoardTaskDescription,
|
|
438
|
-
references: BoardTaskReferences,
|
|
439
|
-
});
|
|
440
|
-
// The whole board, as it is stored and as it is read. There is no second representation to keep in
|
|
441
|
-
// step with it — this document is the file.
|
|
442
|
-
export const BoardDocument = z.strictObject({
|
|
443
|
-
statuses: z.array(BoardStatus).min(1).max(32),
|
|
444
|
-
tasks: z.array(BoardTask).max(5_000),
|
|
445
|
-
});
|
|
446
|
-
// How deep `parentId` may nest. Five is epic → task → subtask with room left over; without a bound
|
|
447
|
-
// a chain of a thousand tasks would be a valid board that no view can draw and no walk can afford.
|
|
448
|
-
export const BoardMaxTaskDepth = 5;
|
|
449
|
-
export const GetBoardInput = z.strictObject({ nodeId: IntelId });
|
|
450
|
-
// A board as it is read. `versionId` is `null` while nothing has been written yet — the same answer
|
|
451
|
-
// a table gives before its header exists — and the document is then the defaults.
|
|
452
|
-
export const NodeBoard = z.strictObject({
|
|
453
|
-
node: Node,
|
|
454
|
-
board: BoardDocument,
|
|
455
|
-
versionId: IntelId.nullable(),
|
|
456
|
-
});
|
|
457
|
-
export const BoardStatusInput = z.strictObject({
|
|
458
|
-
id: BoardStatusId,
|
|
459
|
-
label: z.string().trim().min(1).max(60),
|
|
460
|
-
/**
|
|
461
|
-
* Whether this column means finished (anchrd/intel#311).
|
|
462
|
-
*
|
|
463
|
-
* ⚠️ Optional, and the absence is not the same as `false`. A caller who says nothing gets the
|
|
464
|
-
* server's answer — `false` for an ordinary column, `true` for the shelf, which cannot be
|
|
465
|
-
* anything else. Making it a required boolean would force every caller that only wanted to rename
|
|
466
|
-
* a column to restate the whole board's notion of done, and getting one entry wrong there is a
|
|
467
|
-
* silent change to what counts as blocked.
|
|
468
|
-
*/
|
|
469
|
-
terminal: z.boolean().optional(),
|
|
470
|
-
});
|
|
471
|
-
// The status list, written whole and in the order it should be drawn — never a patch. Adding,
|
|
472
|
-
// renaming and reordering are all this one call, and `archived` has to be in what it is given.
|
|
473
|
-
export const ConfigureBoardInput = z.strictObject({
|
|
474
|
-
nodeId: IntelId,
|
|
475
|
-
statuses: z
|
|
476
|
-
.array(BoardStatusInput)
|
|
477
|
-
.min(1)
|
|
478
|
-
.max(32)
|
|
479
|
-
.refine((statuses) => new Set(statuses.map((status) => status.id)).size === statuses.length, {
|
|
480
|
-
error: "Status ids must be distinct",
|
|
481
|
-
})
|
|
482
|
-
.refine((statuses) => statuses.some((status) => status.id === ArchivedBoardStatusId), {
|
|
483
|
-
error: `The "${ArchivedBoardStatusId}" status cannot be removed`,
|
|
484
|
-
})
|
|
485
|
-
// ⚠️ Refused rather than corrected, the same way removing the shelf is refused. A task swept
|
|
486
|
-
// onto `archived` is finished with, and a board that could declare the shelf non-terminal would
|
|
487
|
-
// hold every archived task open as a blocker forever. Only an EXPLICIT `false` is refused —
|
|
488
|
-
// saying nothing is fine and means the server's `true` (anchrd/intel#311).
|
|
489
|
-
.refine((statuses) => statuses.find((status) => status.id === ArchivedBoardStatusId)?.terminal !== false, { error: `The "${ArchivedBoardStatusId}" status is always terminal` }),
|
|
490
|
-
idempotencyKey: z.string().min(8).max(200),
|
|
491
|
-
});
|
|
492
|
-
/**
|
|
493
|
-
* A new task (#285).
|
|
494
|
-
*
|
|
495
|
-
* ⚠️ No `baseVersionId`, on this and on every other task operation, and that absence is the
|
|
496
|
-
* feature. A board is addressed by stable task id and never by position, so two agents touching two
|
|
497
|
-
* different tasks cannot collide — demanding a base version would invent the `version_conflict`
|
|
498
|
-
* that #285 exists to remove, and force every caller to read the whole board first.
|
|
499
|
-
*/
|
|
500
|
-
export const AddBoardTaskInput = z.strictObject({
|
|
501
|
-
nodeId: IntelId,
|
|
502
|
-
title: z.string().trim().min(1).max(240),
|
|
503
|
-
// Omitted means the first status that is not `archived`: a new task belongs on the board, not on
|
|
504
|
-
// the shelf.
|
|
505
|
-
status: BoardStatusId.optional(),
|
|
506
|
-
assignee: BoardAssignee.nullable().default(null),
|
|
507
|
-
labels: BoardTaskLabels.default([]),
|
|
508
|
-
startDate: BoardTaskDate.nullable().default(null),
|
|
509
|
-
dueDate: BoardTaskDate.nullable().default(null),
|
|
510
|
-
parentId: BoardTaskId.nullable().default(null),
|
|
511
|
-
dependsOn: BoardTaskDependsOn.default([]),
|
|
512
|
-
description: BoardTaskDescription.default(""),
|
|
513
|
-
references: BoardTaskReferences.default([]),
|
|
514
|
-
// Where among its neighbours it goes. Both absent puts it last in its column.
|
|
515
|
-
afterTaskId: BoardTaskId.nullable().default(null),
|
|
516
|
-
beforeTaskId: BoardTaskId.nullable().default(null),
|
|
517
|
-
idempotencyKey: z.string().min(8).max(200),
|
|
518
|
-
});
|
|
519
|
-
/**
|
|
520
|
-
* What a task says about itself.
|
|
521
|
-
*
|
|
522
|
-
* ⚠️ Deliberately no `status`, no `parentId` and no `order`: where a task SITS is a move, and a
|
|
523
|
-
* move is the operation that has to mint an order key and re-check the two cycle rules. Folding
|
|
524
|
-
* both into one call would mean every field edit pays for those checks and every move could quietly
|
|
525
|
-
* rewrite a description.
|
|
526
|
-
*/
|
|
527
|
-
export const UpdateBoardTaskInput = z
|
|
528
|
-
.strictObject({
|
|
529
|
-
nodeId: IntelId,
|
|
530
|
-
taskId: BoardTaskId,
|
|
531
|
-
title: z.string().trim().min(1).max(240).optional(),
|
|
532
|
-
assignee: BoardAssignee.nullable().optional(),
|
|
533
|
-
labels: BoardTaskLabels.optional(),
|
|
534
|
-
startDate: BoardTaskDate.nullable().optional(),
|
|
535
|
-
dueDate: BoardTaskDate.nullable().optional(),
|
|
536
|
-
dependsOn: BoardTaskDependsOn.optional(),
|
|
537
|
-
description: BoardTaskDescription.optional(),
|
|
538
|
-
references: BoardTaskReferences.optional(),
|
|
539
|
-
idempotencyKey: z.string().min(8).max(200),
|
|
540
|
-
})
|
|
541
|
-
.refine((input) => input.title !== undefined ||
|
|
542
|
-
input.assignee !== undefined ||
|
|
543
|
-
input.labels !== undefined ||
|
|
544
|
-
input.startDate !== undefined ||
|
|
545
|
-
input.dueDate !== undefined ||
|
|
546
|
-
input.dependsOn !== undefined ||
|
|
547
|
-
input.description !== undefined ||
|
|
548
|
-
input.references !== undefined, { error: "At least one change is required" });
|
|
549
|
-
// Where a task sits: its column, its parent, its place among its neighbours. Archiving is this call
|
|
550
|
-
// with `status: "archived"` — there is no separate verb, because it is not a separate act.
|
|
551
|
-
export const MoveBoardTaskInput = z
|
|
552
|
-
.strictObject({
|
|
553
|
-
nodeId: IntelId,
|
|
554
|
-
taskId: BoardTaskId,
|
|
555
|
-
status: BoardStatusId.optional(),
|
|
556
|
-
parentId: BoardTaskId.nullable().optional(),
|
|
557
|
-
afterTaskId: BoardTaskId.nullable().default(null),
|
|
558
|
-
beforeTaskId: BoardTaskId.nullable().default(null),
|
|
559
|
-
idempotencyKey: z.string().min(8).max(200),
|
|
560
|
-
})
|
|
561
|
-
.refine((input) => input.status !== undefined ||
|
|
562
|
-
input.parentId !== undefined ||
|
|
563
|
-
input.afterTaskId !== null ||
|
|
564
|
-
input.beforeTaskId !== null, { error: "A move needs a status, a parent or a neighbour" });
|
|
565
|
-
// ⚠️ Deleting cascades to every descendant, and the count comes back so a surface can warn BEFORE
|
|
566
|
-
// asking. See `DeleteBoardTaskResult`.
|
|
567
|
-
export const DeleteBoardTaskInput = z.strictObject({
|
|
568
|
-
nodeId: IntelId,
|
|
569
|
-
taskId: BoardTaskId,
|
|
570
|
-
idempotencyKey: z.string().min(8).max(200),
|
|
571
|
-
});
|
|
572
|
-
/**
|
|
573
|
-
* The way out of a board whose `tasks[]` names one id twice (anchrd/intel#341).
|
|
574
|
-
*
|
|
575
|
-
* ⚠️ It names no task, and that is not an oversight. Such a board holds a pair the caller cannot
|
|
576
|
-
* tell apart — every other operation here addresses a task BY id (#285), so the one thing nobody
|
|
577
|
-
* can say is "the second of the two". The board is what is named, and the server does the one thing
|
|
578
|
-
* that removes the ambiguity: the first entry under an id keeps it, every later one gets a freshly
|
|
579
|
-
* minted id and keeps everything else it carries.
|
|
580
|
-
*
|
|
581
|
-
* ⚠️ It exists because the pair is deliberately NOT folded away on read (`upgradeStoredBoard`,
|
|
582
|
-
* `repeatedBoardId`): two tasks under one id are two whole tasks, and a fold on the read is written
|
|
583
|
-
* back by the next save of any kind. So somebody has to ask for the repair, and this is the asking.
|
|
584
|
-
*/
|
|
585
|
-
export const RepairBoardTaskIdsInput = z.strictObject({
|
|
586
|
-
nodeId: IntelId,
|
|
587
|
-
idempotencyKey: z.string().min(8).max(200),
|
|
588
|
-
});
|
|
589
|
-
// The one task that was written, not the whole board: a board can hold thousands of tasks, and
|
|
590
|
-
// answering a one-card edit with all of them would make every write pay for the read.
|
|
591
|
-
export const BoardTaskResult = z.strictObject({
|
|
592
|
-
node: Node,
|
|
593
|
-
version: NodeVersion,
|
|
594
|
-
task: BoardTask,
|
|
595
|
-
});
|
|
596
|
-
// `deleted` counts the task AND every descendant that went with it, so a caller can say what
|
|
597
|
-
// happened rather than "done".
|
|
598
|
-
export const DeleteBoardTaskResult = z.strictObject({
|
|
599
|
-
node: Node,
|
|
600
|
-
version: NodeVersion,
|
|
601
|
-
deleted: z.number().int().positive(),
|
|
602
|
-
});
|
|
603
|
-
export const ConfigureBoardResult = z.strictObject({
|
|
604
|
-
node: Node,
|
|
605
|
-
version: NodeVersion,
|
|
606
|
-
statuses: z.array(BoardStatus),
|
|
607
|
-
});
|
|
608
|
-
/**
|
|
609
|
-
* What the repair did, task by task (anchrd/intel#341).
|
|
610
|
-
*
|
|
611
|
-
* ⚠️ `previousId` is the id the entry shared, and it still names a task on this board — the FIRST
|
|
612
|
-
* entry under it, the one that kept it. That is what makes the repair readable: nothing that
|
|
613
|
-
* pointed at that id moved, so a caller can see which of the two the board's `parentId` and
|
|
614
|
-
* `dependsOn` edges have been meaning all along, and move them with `board_task_move` and
|
|
615
|
-
* `board_task_update` if they meant the other one.
|
|
616
|
-
*
|
|
617
|
-
* ⚠️ At least one entry, because a board with nothing to repair is refused rather than answered
|
|
618
|
-
* with an empty list and a new version that changed nothing.
|
|
619
|
-
*/
|
|
620
|
-
export const RepairBoardTaskIdsResult = z.strictObject({
|
|
621
|
-
node: Node,
|
|
622
|
-
version: NodeVersion,
|
|
623
|
-
renumbered: z
|
|
624
|
-
.array(z.strictObject({ previousId: BoardTaskId, task: BoardTask }))
|
|
625
|
-
.min(1)
|
|
626
|
-
.max(5_000),
|
|
627
|
-
});
|
|
628
|
-
// ── The agent definition (#139, ADR-0005 §4) ─────────────────────────────────────────────────────
|
|
629
|
-
//
|
|
630
|
-
// An agent's body is a definition, stored as an immutable version in R2 exactly like a document's.
|
|
631
|
-
// Its own media type exists so a reader can tell a definition from prose without parsing it.
|
|
632
|
-
export const AgentMediaType = "application/vnd.anchrd.agent+json";
|
|
633
|
-
// ⚠️ The role lives on the AGENT, never on the node it names, and that is the whole difference to
|
|
634
|
-
// the removed `context_policy` (ADR-0005 §2, #76). The same folder can be the system message for
|
|
635
|
-
// one agent and nothing but search space for another; a node has no opinion about how it is used.
|
|
636
|
-
// Any future field on a node saying how it should be loaded is `context_policy` under a new name.
|
|
637
|
-
//
|
|
638
|
-
// system-message prepended verbatim by the runtime
|
|
639
|
-
// semantic-context search space; the agent searches it when it decides to
|
|
640
|
-
// memory write target — ordinary Knowledge, versioned and readable like everything else
|
|
641
|
-
export const AgentReferenceRole = z.enum(["system-message", "semantic-context", "memory"]);
|
|
642
|
-
export const AgentReference = z.strictObject({ nodeId: IntelId, role: AgentReferenceRole });
|
|
643
|
-
/**
|
|
644
|
-
* Which kinds of node each role can actually be given (#255).
|
|
645
|
-
*
|
|
646
|
-
* ⚠️ Not every role takes every kind, and the reasons are about what the runtime DOES with a
|
|
647
|
-
* reference rather than about tidiness:
|
|
648
|
-
*
|
|
649
|
-
* `memory` is a folder because the agent WRITES there — `agent_remember` creates a note inside
|
|
650
|
-
* it. A single document as memory would mean the agent overwrites the document it was given.
|
|
651
|
-
*
|
|
652
|
-
* `semantic-context` is a folder because it is a search SPACE, searched per folder by
|
|
653
|
-
* `loop/scoped-search`. A single document is not a narrower search space; reading it whole is a
|
|
654
|
-
* different behaviour, and one that gets named before it is introduced, not slipped in.
|
|
655
|
-
*
|
|
656
|
-
* `system-message` reads single nodes already and takes a document or a table as well as a
|
|
657
|
-
* folder. A document is the natural case — a skill somebody wrote as ordinary text — and a table
|
|
658
|
-
* is the same read: the runtime asks intel for the node and prepends its content, which for a
|
|
659
|
-
* table is its CSV.
|
|
660
|
-
*
|
|
661
|
-
* ⚠️ `folder` stays on `system-message` although a folder carries no content of its own. Every
|
|
662
|
-
* definition written before #255 could only name folders, and taking the combination away here
|
|
663
|
-
* would refuse the next save of an agent that has been working for months — "existing definitions
|
|
664
|
-
* stay valid" is not only about reading them.
|
|
665
|
-
*
|
|
666
|
-
* ⚠️ This is the ONE place the rule lives. The screen offers what it says and the write path
|
|
667
|
-
* refuses what it forbids; a surface that made its own list would eventually disagree with the
|
|
668
|
-
* other, and the one that matters is whichever runs last.
|
|
669
|
-
*/
|
|
670
|
-
export const AgentReferenceKinds = {
|
|
671
|
-
"system-message": ["folder", "document", "table"],
|
|
672
|
-
"semantic-context": ["folder"],
|
|
673
|
-
memory: ["folder"],
|
|
674
|
-
};
|
|
675
|
-
export function agentReferenceAccepts(role, kind) {
|
|
676
|
-
return AgentReferenceKinds[role].includes(kind);
|
|
677
|
-
}
|
|
678
|
-
/** The roles a node of this kind may be given — the same rule, read from the other side. */
|
|
679
|
-
export function agentReferenceRolesFor(kind) {
|
|
680
|
-
return AgentReferenceRole.options.filter((role) => agentReferenceAccepts(role, kind));
|
|
681
|
-
}
|
|
682
|
-
// A `document` target means the content of that document is the instruction — a "skill" somebody
|
|
683
|
-
// wrote as ordinary text; a `flow` target means a run is started through Intel MCP and worked step
|
|
684
|
-
// by step. Both are references, so nothing in here goes stale (ADR-0005 §4).
|
|
685
|
-
//
|
|
686
|
-
// ⚠️ Intel stores a schedule as a declared fact and never fires it. The alarm lives in the runtime
|
|
687
|
-
// (ADR-0005 §3); Intel gains no scheduler, which is D24 confirmed rather than bent.
|
|
688
|
-
export const AgentScheduleTarget = z.strictObject({
|
|
689
|
-
kind: z.enum(["document", "flow"]),
|
|
690
|
-
id: IntelId,
|
|
691
|
-
});
|
|
692
|
-
/**
|
|
693
|
-
* ⚠️ `timezone` is what the cron expression is READ IN, and it belongs to the schedule rather than
|
|
694
|
-
* to whoever is looking at it (#228). "Every morning at eight" means eight o'clock where the person
|
|
695
|
-
* who wrote it sits — in Berlin that is 06:00 UTC in summer and 07:00 in winter, and a field that
|
|
696
|
-
* does not carry the zone cannot express that difference. A UTC cron is an hour wrong twice a year
|
|
697
|
-
* and nobody sees why.
|
|
698
|
-
*
|
|
699
|
-
* The UI suggests the reader's own zone when a schedule is created, but it is not a per-user
|
|
700
|
-
* setting: an agent's schedule would otherwise move whenever its owner travelled, and it would mean
|
|
701
|
-
* different times to two people reading the same definition. What is stored is the answer.
|
|
702
|
-
*
|
|
703
|
-
* ⚠️ The default is `"UTC"`, and it is load-bearing rather than tidy: every definition written
|
|
704
|
-
* before this field parses to it and therefore keeps firing exactly when it did. A default of
|
|
705
|
-
* "whatever the writer's browser says" would silently move every existing schedule at the next save.
|
|
706
|
-
*
|
|
707
|
-
* The name is validated against this runtime's own tz database rather than a pattern. A regular
|
|
708
|
-
* expression would accept `Mars/Olympus`, and the failure would surface inside a Durable Object
|
|
709
|
-
* alarm — the place where nobody is watching.
|
|
710
|
-
*/
|
|
711
|
-
const IanaTimezone = z
|
|
712
|
-
.string()
|
|
713
|
-
.trim()
|
|
714
|
-
.min(1)
|
|
715
|
-
.max(64)
|
|
716
|
-
.refine((zone) => {
|
|
717
|
-
try {
|
|
718
|
-
new Intl.DateTimeFormat("en-US", { timeZone: zone });
|
|
719
|
-
return true;
|
|
720
|
-
}
|
|
721
|
-
catch {
|
|
722
|
-
return false;
|
|
723
|
-
}
|
|
724
|
-
}, { message: "must be an IANA timezone name this runtime knows, for example Europe/Berlin" });
|
|
725
|
-
export const AgentSchedule = z.strictObject({
|
|
726
|
-
cron: z.string().trim().min(1).max(120),
|
|
727
|
-
timezone: IanaTimezone.default("UTC"),
|
|
728
|
-
target: AgentScheduleTarget,
|
|
729
|
-
});
|
|
730
|
-
export const AgentModel = z.strictObject({
|
|
731
|
-
provider: z.enum(["workers-ai", "anthropic"]),
|
|
732
|
-
model: z.string().trim().min(1).max(120),
|
|
733
|
-
});
|
|
734
268
|
/**
|
|
735
269
|
* One MCP server as the portal names it. The handle is what the portal puts in front of every tool
|
|
736
270
|
* that server offers (`notion_notion-search` belongs to `notion`), and it is the only identifier
|
|
@@ -747,233 +281,6 @@ export const ToolServerHandle = z
|
|
|
747
281
|
.min(1)
|
|
748
282
|
.max(120)
|
|
749
283
|
.regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/, "A server handle is the portal's own identifier");
|
|
750
|
-
const ToolServerHandles = z.array(ToolServerHandle).max(32).default([]);
|
|
751
|
-
/**
|
|
752
|
-
* What a caller may ASK for: whole MCP servers, and nothing about who delegates them (D30).
|
|
753
|
-
*
|
|
754
|
-
* ⚠️ The absence of `delegatedBy` is the point, and it is why the write shape differs from the read
|
|
755
|
-
* shape at all. Intel writes that field from the session it authorized; a caller who could name
|
|
756
|
-
* somebody else would be handing an agent a portal connection they do not have, and the agent would
|
|
757
|
-
* act on it unattended. Leaving the field out of the input makes that structural instead of a
|
|
758
|
-
* runtime overwrite: a body carrying it is a parse error at the boundary, on every surface, and no
|
|
759
|
-
* screen ever has to invent a value it has no business knowing.
|
|
760
|
-
*/
|
|
761
|
-
export const AgentToolSelection = z.strictObject({ servers: ToolServerHandles });
|
|
762
|
-
/**
|
|
763
|
-
* What is STORED and read back: the selection plus whose portal connection it came from (D30).
|
|
764
|
-
*
|
|
765
|
-
* ⚠️ This is a selection, not a permission. Nothing here grants anything: whether a server is
|
|
766
|
-
* reachable is still decided by one live `tools/list` with `delegatedBy`'s own portal token, so a
|
|
767
|
-
* delegator who loses the server or the connection takes it away from the agent at the next run
|
|
768
|
-
* with no edit to this document.
|
|
769
|
-
*
|
|
770
|
-
* ⚠️ Read shape only. It appears in `AgentDefinition` and never in an input — see
|
|
771
|
-
* `AgentToolSelection` for why the two are deliberately different documents rather than one schema
|
|
772
|
-
* with an optional field.
|
|
773
|
-
*/
|
|
774
|
-
export const AgentToolDelegation = z.strictObject({
|
|
775
|
-
delegatedBy: IntelId,
|
|
776
|
-
servers: ToolServerHandles,
|
|
777
|
-
});
|
|
778
|
-
/**
|
|
779
|
-
* ⚠️ No accounts, no secrets and no channels — and the reason is mechanical rather than tidy
|
|
780
|
-
* (ADR-0005 §4): this body is read, shared, exported and put into model context, so a secret in it
|
|
781
|
-
* is a secret in a citation. Identity is Gate's, accounts are the portal's, channels are runtime
|
|
782
|
-
* configuration.
|
|
783
|
-
*
|
|
784
|
-
* ⚠️ `tools` is the one correction to that list (D30), and it is narrower than it looks. What is
|
|
785
|
-
* stored is a **selection of whole servers plus who delegated them**, never a mirrored permission
|
|
786
|
-
* and never a catalog: the catalog stays a live `tools/list` made with the delegator's token at the
|
|
787
|
-
* moment the agent runs. ADR-0005 §4's "no tools in the definition" forbade the mirror, and the
|
|
788
|
-
* mirror is still forbidden — a tool name, a schema or an account in here would be the thing that
|
|
789
|
-
* line was written against.
|
|
790
|
-
*
|
|
791
|
-
* ⚠️ Strict on purpose, and deliberately stricter than the runtime's own reader
|
|
792
|
-
* (`packages/agent/src/definition/definition.ts`, which is `z.object`). Intel is the writer: an
|
|
793
|
-
* unknown field here is a caller's mistake and is refused at the boundary. The runtime is the
|
|
794
|
-
* reader and released separately, so it must keep starting agents when Intel adds a field
|
|
795
|
-
* tomorrow. The asymmetry is the point, not an oversight.
|
|
796
|
-
*/
|
|
797
|
-
const AgentBody = {
|
|
798
|
-
references: z.array(AgentReference).max(200).default([]),
|
|
799
|
-
schedules: z.array(AgentSchedule).max(50).default([]),
|
|
800
|
-
model: AgentModel,
|
|
801
|
-
};
|
|
802
|
-
export const AgentDefinition = z.strictObject({
|
|
803
|
-
...AgentBody,
|
|
804
|
-
// `null` is "this agent has no tools", and it is also what every definition written before D30
|
|
805
|
-
// parses to. An empty `servers` list means the same thing and is kept as its own state so
|
|
806
|
-
// removing the last server does not have to erase who was delegating.
|
|
807
|
-
tools: AgentToolDelegation.nullable().default(null),
|
|
808
|
-
});
|
|
809
|
-
/**
|
|
810
|
-
* The same document as `AgentDefinition`, minus the one field a caller may not write.
|
|
811
|
-
*
|
|
812
|
-
* ⚠️ Two schemas rather than one, and the split is load-bearing (#208, D30). Everything an agent IS
|
|
813
|
-
* comes from whoever edits it; **whose portal connection it acts on** does not, because that is an
|
|
814
|
-
* authority the editor would be granting to themselves. So the write shape simply has no place to
|
|
815
|
-
* put it: `{ tools: { servers: [...] } }` is what a screen or an MCP client sends, Intel adds
|
|
816
|
-
* `delegatedBy` from the session, and a body that tries to name one is refused by the strict object
|
|
817
|
-
* before any of it is read. The reading shape keeps the field because a reader must be able to see
|
|
818
|
-
* whose connection an agent runs on.
|
|
819
|
-
*/
|
|
820
|
-
export const AgentDefinitionInput = z.strictObject({
|
|
821
|
-
...AgentBody,
|
|
822
|
-
tools: AgentToolSelection.nullable().default(null),
|
|
823
|
-
});
|
|
824
|
-
export const SaveAgentDefinitionInput = z.strictObject({
|
|
825
|
-
nodeId: IntelId,
|
|
826
|
-
baseVersionId: IntelId.nullable(),
|
|
827
|
-
definition: AgentDefinitionInput,
|
|
828
|
-
idempotencyKey: z.string().min(8).max(200),
|
|
829
|
-
});
|
|
830
|
-
export const GetAgentInput = z.strictObject({ nodeId: IntelId });
|
|
831
|
-
// Switching an agent off and on again, and starting one run by hand. All three name only the agent
|
|
832
|
-
// and — for a run — which of the targets it already schedules.
|
|
833
|
-
//
|
|
834
|
-
// ⚠️ Intel holds none of this. Whether an agent is paused is state of its Durable Object, not a
|
|
835
|
-
// field of the definition: a definition is versioned, shared and read into model context (ADR-0005
|
|
836
|
-
// §4), so every pause would otherwise be a new version and would tell the agent it is switched off.
|
|
837
|
-
// These inputs are what Intel accepts and passes on, nothing that Intel stores.
|
|
838
|
-
export const PauseAgentInput = z.strictObject({ nodeId: IntelId });
|
|
839
|
-
export const RunAgentNowInput = z.strictObject({
|
|
840
|
-
nodeId: IntelId,
|
|
841
|
-
target: AgentScheduleTarget,
|
|
842
|
-
});
|
|
843
|
-
/**
|
|
844
|
-
* What one agent has actually cost, read out of Cloudflare's AI Gateway log (#251).
|
|
845
|
-
*
|
|
846
|
-
* ⚠️ Intel computes none of this from tokens and a price table. The gateway publishes the billed
|
|
847
|
-
* figure per call, and that figure is the debit from the Cloudflare balance 1:1 — Cloudflare takes
|
|
848
|
-
* its 5 % when the balance is loaded and passes inference through unchanged (measured 2026-08-07).
|
|
849
|
-
* A second, self-maintained answer beside it would be wrong on the day the two disagreed, and the
|
|
850
|
-
* wrong one would be the one on screen.
|
|
851
|
-
*
|
|
852
|
-
* ⚠️ `status` travels with the numbers and may never be dropped. `runs: []` means "cost nothing"
|
|
853
|
-
* only when `status` is `read`; under `not_configured` or `unreadable` it means "not known", and a
|
|
854
|
-
* screen that renders the two alike reports an outage as a saving.
|
|
855
|
-
*/
|
|
856
|
-
export const AgentCostStatus = z.enum(["read", "not_configured", "unreadable"]);
|
|
857
|
-
export const AgentRunCost = z.strictObject({
|
|
858
|
-
runId: z.string(),
|
|
859
|
-
cost: z.number(),
|
|
860
|
-
calls: z.number(),
|
|
861
|
-
});
|
|
862
|
-
export const AgentCostWindow = z.strictObject({
|
|
863
|
-
days: z.number(),
|
|
864
|
-
cost: z.number(),
|
|
865
|
-
calls: z.number(),
|
|
866
|
-
// Which models produced this figure. It is here so the model select can say the number is about
|
|
867
|
-
// the PAST (#257) — a reader who switched model would otherwise take it for a forecast.
|
|
868
|
-
models: z.array(z.string()),
|
|
869
|
-
});
|
|
870
|
-
export const AgentCosts = z.strictObject({
|
|
871
|
-
status: AgentCostStatus,
|
|
872
|
-
currency: z.literal("USD"),
|
|
873
|
-
runs: z.array(AgentRunCost),
|
|
874
|
-
windows: z.array(AgentCostWindow),
|
|
875
|
-
// The read hit its page limit, so every total above is a floor rather than a total.
|
|
876
|
-
partial: z.boolean(),
|
|
877
|
-
});
|
|
878
|
-
/**
|
|
879
|
-
* What the models on offer cost and how much they hold (#257).
|
|
880
|
-
*
|
|
881
|
-
* ⚠️ `source` is per ENTRY and not per response, and that is not over-engineering. Cloudflare
|
|
882
|
-
* publishes figures for the models it serves itself and none at all for the Anthropic models it
|
|
883
|
-
* resells through Unified Billing — so a perfectly healthy read still leaves half the list on a
|
|
884
|
-
* written-out table, and one flag for the whole answer would call either the read stale or the
|
|
885
|
-
* table live.
|
|
886
|
-
*/
|
|
887
|
-
export const ModelPrice = z.strictObject({
|
|
888
|
-
inputPerMillion: z.number(),
|
|
889
|
-
outputPerMillion: z.number(),
|
|
890
|
-
});
|
|
891
|
-
export const ModelCatalogEntry = z.strictObject({
|
|
892
|
-
provider: z.enum(["workers-ai", "anthropic"]),
|
|
893
|
-
model: z.string(),
|
|
894
|
-
name: z.string(),
|
|
895
|
-
contextTokens: z.number().nullable(),
|
|
896
|
-
// `null` where this installation has no figure. Never zero and never a guess — an invented number
|
|
897
|
-
// is a false statement about money.
|
|
898
|
-
price: ModelPrice.nullable(),
|
|
899
|
-
source: z.enum(["cloudflare", "builtin"]),
|
|
900
|
-
});
|
|
901
|
-
export const ModelCatalog = z.strictObject({
|
|
902
|
-
entries: z.array(ModelCatalogEntry),
|
|
903
|
-
liveStatus: z.enum(["read", "not_configured", "unreadable"]),
|
|
904
|
-
});
|
|
905
|
-
// ⚠️ Three states, not two, and the same three the flow list makes: omitted is the whole tree,
|
|
906
|
-
// `null` is the root level, an ID is that folder. "Which agents may I use" is a question about the
|
|
907
|
-
// tree rather than about one folder, so the useful answer has to be reachable without knowing where
|
|
908
|
-
// somebody filed them.
|
|
909
|
-
export const ListAgentsInput = z.strictObject({
|
|
910
|
-
parentId: IntelId.nullable().optional(),
|
|
911
|
-
includeArchived: z.boolean().default(false),
|
|
912
|
-
});
|
|
913
|
-
export const CreateAgentInput = z.strictObject({
|
|
914
|
-
parentId: IntelId.nullable().default(null),
|
|
915
|
-
title: z.string().trim().min(1).max(240),
|
|
916
|
-
description: z.string().trim().max(2_000).nullable().default(null),
|
|
917
|
-
definition: AgentDefinitionInput,
|
|
918
|
-
idempotencyKey: z.string().min(8).max(200),
|
|
919
|
-
});
|
|
920
|
-
// The ID of the Gate Application an agent runs as. Deliberately NOT an `IntelId`: it is Better
|
|
921
|
-
// Auth's user ID, minted in Gate and only ever handed back to Gate, so validating it against
|
|
922
|
-
// Intel's own ID shape would be Intel inventing a rule about somebody else's identifier.
|
|
923
|
-
export const GateApplicationId = z.string().min(1).max(255);
|
|
924
|
-
// The definition is `null` exactly while the node exists and no version has been written yet — the
|
|
925
|
-
// same window in which a document's content is `null`.
|
|
926
|
-
//
|
|
927
|
-
// ⚠️ `applicationId` names the machine principal, it does not authenticate it (#182, D27). That is
|
|
928
|
-
// why the ID may be stored, listed and drawn while the key may not: one is a name, the other is the
|
|
929
|
-
// credential, and Gate hands the credential out exactly once and keeps only its hash. `null` means
|
|
930
|
-
// this agent has no Application — an agent node written before #182, restored from a bundle, or
|
|
931
|
-
// imported from another installation. Such an agent is not switched with its node, and giving it a
|
|
932
|
-
// principal is an operator's act in Gate.
|
|
933
|
-
export const NodeAgent = z.strictObject({
|
|
934
|
-
node: Node,
|
|
935
|
-
version: NodeVersion.nullable(),
|
|
936
|
-
definition: AgentDefinition.nullable(),
|
|
937
|
-
applicationId: GateApplicationId.nullable(),
|
|
938
|
-
});
|
|
939
|
-
/**
|
|
940
|
-
* ⚠️ There is NO key field in this file, and adding one back would be the regression (D29, #207).
|
|
941
|
-
*
|
|
942
|
-
* Until #207 the create answer carried the Application key in plain text, once, and a person had to
|
|
943
|
-
* carry it into a Worker secret by hand — which is why an agent created through the screen could
|
|
944
|
-
* never run (#200). The key now goes from Gate straight into the agent runtime over Intel's service
|
|
945
|
-
* binding and is encrypted into that agent's Durable Object; it reaches no browser, no MCP tool
|
|
946
|
-
* result and no response body at all. `NodeAgent` is a `z.strictObject`, so a field named `key`
|
|
947
|
-
* added anywhere in this file is a parse error at the boundary rather than a leak somebody has to
|
|
948
|
-
* spot in review.
|
|
949
|
-
*
|
|
950
|
-
* What `POST /nodes/agents` and `agent_create` answer is therefore exactly what every read answers:
|
|
951
|
-
* the node, its first definition, and the `applicationId` that NAMES the principal without
|
|
952
|
-
* authenticating it.
|
|
953
|
-
*/
|
|
954
|
-
export const CreatedAgent = NodeAgent;
|
|
955
|
-
// Which agent's key is being replaced. `nodeId` and not the Application ID: this addresses an agent
|
|
956
|
-
// in Intel's tree, and the Application behind it is Intel's to look up — a caller naming the
|
|
957
|
-
// principal directly would be rotating a key for an agent nobody checked they may edit.
|
|
958
|
-
export const RotateAgentKeyInput = z.strictObject({ nodeId: IntelId });
|
|
959
|
-
/**
|
|
960
|
-
* What replacing an agent's key answers.
|
|
961
|
-
*
|
|
962
|
-
* ⚠️ No key, and that is the whole shape of D29: Intel asks Gate for a new one, hands it to the
|
|
963
|
-
* runtime over the service binding, and forgets it inside the same call. What the caller gets is
|
|
964
|
-
* the fact that it happened, so a screen can say so — `applicationId` names the principal whose key
|
|
965
|
-
* was replaced, which is a name and not a credential.
|
|
966
|
-
*/
|
|
967
|
-
export const AgentKeyRotated = z.strictObject({
|
|
968
|
-
nodeId: IntelId,
|
|
969
|
-
applicationId: GateApplicationId,
|
|
970
|
-
rotatedAt: IsoDateTime,
|
|
971
|
-
});
|
|
972
|
-
export const AgentList = z.strictObject({ items: z.array(Node) });
|
|
973
|
-
// ⚠️ Kept for what is already stored, not for what is written. Relations were picked in a dialog
|
|
974
|
-
// until #41; a link is now made where it is meant — in the text — and every link written from now
|
|
975
|
-
// on is a `references`. Rewriting the old rows would destroy a distinction somebody chose on
|
|
976
|
-
// purpose, and dropping the column would destroy it with them, so both stay readable.
|
|
977
284
|
export const NodeLinkRelation = z.enum(["references", "related", "depends_on", "implements"]);
|
|
978
285
|
// Where the link came from. `text` links are derived from a document's content and are rewritten
|
|
979
286
|
// whenever it is saved; `manual` links were made in the dialog #41 removed and are now history.
|
|
@@ -1588,15 +895,7 @@ export const FlowPublishPreview = z.strictObject({
|
|
|
1588
895
|
// What accesses what, for one level of the shared tree (#19). A folder answers it for its contents,
|
|
1589
896
|
// a single flow for itself. Documents and flows are two kinds of thing that share one tree
|
|
1590
897
|
// (ADR-0004 §1), so the graph carries both and says which of them it is.
|
|
1591
|
-
export const RelationNodeKind = z.enum([
|
|
1592
|
-
"folder",
|
|
1593
|
-
"document",
|
|
1594
|
-
"attachment",
|
|
1595
|
-
"table",
|
|
1596
|
-
"agent",
|
|
1597
|
-
"board",
|
|
1598
|
-
"flow",
|
|
1599
|
-
]);
|
|
898
|
+
export const RelationNodeKind = z.enum(["folder", "document", "attachment", "table", "flow"]);
|
|
1600
899
|
export const RelationNode = z.strictObject({
|
|
1601
900
|
id: IntelId,
|
|
1602
901
|
kind: RelationNodeKind,
|
|
@@ -1779,8 +1078,20 @@ export const FlowRunHistory = z.strictObject({
|
|
|
1779
1078
|
// The one name the importer looks for at the zip root. A different spelling would make a bundle a
|
|
1780
1079
|
// naked folder, so the constant lives in the contract rather than in each surface.
|
|
1781
1080
|
export const BundleManifestFilename = "manifest.json";
|
|
1782
|
-
|
|
1783
|
-
|
|
1081
|
+
/**
|
|
1082
|
+
* What a bundle entry can be. `flow` joins the node kinds because a flow shares the folder tree
|
|
1083
|
+
* without being a node (ADR-0004), and the bundle mirrors the tree, not the tables.
|
|
1084
|
+
*
|
|
1085
|
+
* ⚠️ `agent` and `board` are STILL HERE, and that is the one place in this file where a value
|
|
1086
|
+
* survives its feature (#390). A bundle is somebody else's file: an export written before Agents
|
|
1087
|
+
* and Board were parked (#385) is a correct export, and it has to PARSE so the import can refuse it
|
|
1088
|
+
* by name — with the entry, the kind and the branch the code is on. Take them out and the same
|
|
1089
|
+
* bundle fails as `unexpected enum value`, which sends its holder looking for a broken file that is
|
|
1090
|
+
* not broken.
|
|
1091
|
+
*
|
|
1092
|
+
* They belong to the wire format of a file that already exists, not to the product. Nothing may
|
|
1093
|
+
* create either kind; `NodeKind` is the enum that says so.
|
|
1094
|
+
*/
|
|
1784
1095
|
export const BundleEntryKind = z.enum([
|
|
1785
1096
|
"folder",
|
|
1786
1097
|
"document",
|