Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.
| Artifacts contracts: folios, their four kinds, dashboard datasets, folio events and the artifacts scopes are typed and validated the same in TypeScript and Rust, with nothing using them yet | 1 | import assert from "node:assert/strict"; |
| 2 | import { readFileSync } from "node:fs"; | |
| 3 | import { test } from "node:test"; | |
| 4 | ||
| 5 | import type { ServiceBinding } from "./clients.ts"; | |
| 6 | import { datasetQueryError } from "./datasets.ts"; | |
| 7 | import { dashboardGridError, dashboardOpError } from "./folios-dashboard.ts"; | |
| 8 | import { DESIGN_MAX_NODES, countDesignSpecs, designOpError } from "./folios-design.ts"; | |
| 9 | import { SLIDE_LAYOUTS, SLIDE_LAYOUT_REGIONS, slideFragment, slidesOpError } from "./folios-slides.ts"; | |
| 10 | import { | |
| 11 | FOLIO_KINDS, | |
| 12 | FOLIO_RPC_METHODS, | |
| 13 | FOLIO_ROUTE_SEGMENTS, | |
| 14 | folioAccessChangeError, | |
| 15 | folioAgentEditError, | |
| 16 | folioCanHaveChildren, | |
| 17 | folioIdFrom, | |
| 18 | folioListQueryError, | |
| 19 | folioPath, | |
| 20 | folioSlug, | |
| 21 | foliosClient, | |
| 22 | linkedFolioIds, | |
| 23 | newFolioError, | |
| 24 | type FolioAccessChange, | |
| 25 | type NewFolio, | |
| 26 | } from "./folios.ts"; | |
| 27 | import type { User } from "./identity.ts"; | |
| 28 | ||
| 29 | const fixtures = JSON.parse(readFileSync(new URL("./folios.fixtures.json", import.meta.url), "utf8")) as { | |
| 30 | new_folio: { input: unknown; error: string | null }[]; | |
| 31 | access_change: { change: unknown; error: string | null }[]; | |
| 32 | agent_edit: { edit: unknown; error: string | null }[]; | |
| 33 | folio_ids: { segment: string; id: string | null }[]; | |
| 34 | }; | |
| 35 | ||
| 36 | function expect(label: string, actual: string | null, error: string | null) { | |
| 37 | if (error === null) assert.equal(actual, null, label); | |
| 38 | else if (error === "*") assert.notEqual(actual, null, `${label} should be refused`); | |
| 39 | else assert.equal(actual, error, label); | |
| 40 | } | |
| 41 | ||
| 42 | test("agrees with the Rust validators on every shared case", () => { | |
| 43 | for (const { input, error } of fixtures.new_folio) expect(JSON.stringify(input), newFolioError(input as NewFolio), error); | |
| 44 | for (const { change, error } of fixtures.access_change) expect(JSON.stringify(change), folioAccessChangeError(change as FolioAccessChange), error); | |
| 45 | for (const { edit, error } of fixtures.agent_edit) expect(JSON.stringify(edit), folioAgentEditError(edit), error); | |
| 46 | for (const { segment, id } of fixtures.folio_ids) assert.equal(folioIdFrom(segment), id, segment); | |
| 47 | }); | |
| 48 | ||
| 49 | test("addresses are flat, end in the id and survive renames", () => { | |
| 50 | const id = "fol_01jb2k7x9hfq0b3zj0f5s2m8ra"; | |
| 51 | assert.equal(folioPath("acme", "Q4 roadmap: pricing", id), `/acme/-/artifacts/q4-roadmap-pricing-${id}`); | |
| 52 | assert.equal(folioSlug("", id), id); | |
| 53 | assert.equal(folioSlug("Café ✨ notes", id), `cafe-notes-${id}`); | |
| 54 | assert.equal(folioIdFrom(folioSlug("Anything at all", id)), id); | |
| 55 | assert.deepEqual(linkedFolioIds(`see /acme/-/artifacts/x-${id} and ${id}`), [id]); | |
| 56 | for (const segment of FOLIO_ROUTE_SEGMENTS) assert.equal(folioIdFrom(segment), null, segment); | |
| 57 | }); | |
| 58 | ||
| 59 | test("only a doc holds other folios", () => { | |
| 60 | assert.deepEqual(FOLIO_KINDS.filter(folioCanHaveChildren), ["doc"]); | |
| 61 | }); | |
| 62 | ||
| 63 | test("list queries are checked", () => { | |
| 64 | assert.equal(folioListQueryError({ tab: "shared", kinds: ["slides"], limit: 50 }), null); | |
| 65 | assert.equal(folioListQueryError({ tab: "everything" as "all" }), "tab is all, yours or shared."); | |
| 66 | assert.equal(folioListQueryError({ tab: "all", limit: 500 }), "limit is between 1 and 100."); | |
| 67 | assert.equal(folioListQueryError({ tab: "all", kinds: ["sheet" as "doc"] }), "There is no kind of artifact called sheet."); | |
| 68 | }); | |
| 69 | ||
| The artifacts service is services/artifacts, the Worker g1t-artifacts, bound as ARTIFACTS by the API, the site and the agents; its live rooms move to it with a Durable Object transfer from g1t-docs-service, and its database, bucket, indexes and queue keep their names. The git store's binding and settings are GITSTORE, its ops scripts gitstore-*, and workflow run artifacts keep their compatible API under run_artifacts modules. The deploy tool puts a Worker that has never deployed before the Workers in its stage that bind to it, and the deploy guide gives the cutover runbook. | 70 | test("the client calls exactly the artifacts service's folio methods", async () => { |
| Artifacts contracts: folios, their four kinds, dashboard datasets, folio events and the artifacts scopes are typed and validated the same in TypeScript and Rust, with nothing using them yet | 71 | const called: string[] = []; |
| 72 | const bodies: Record<string, unknown>[] = []; | |
| 73 | const binding: ServiceBinding = { | |
| 74 | fetch: async (input: RequestInfo | URL, init?: RequestInit) => { | |
| 75 | called.push(String(input).replace("https://service/rpc/", "")); | |
| 76 | bodies.push(JSON.parse(String(init?.body))); | |
| 77 | return new Response(JSON.stringify({ ok: true, value: null }), { headers: { "content-type": "application/json" } }); | |
| 78 | }, | |
| 79 | } as ServiceBinding; | |
| 80 | const client = foliosClient(binding); | |
| 81 | const viewer = { id: "usr_1" } as User; | |
| 82 | const ws = "acme"; | |
| 83 | const f = "fol_1"; | |
| 84 | await Promise.all([ | |
| 85 | client.list(ws, viewer, { tab: "all" }), | |
| 86 | client.sidebar(ws, viewer), | |
| 87 | client.folio(ws, viewer, f), | |
| The folio client's test calls folio_page too, so it checks every method the docs service answers. | 88 | client.page(ws, viewer, f), |
| Artifacts contracts: folios, their four kinds, dashboard datasets, folio events and the artifacts scopes are typed and validated the same in TypeScript and Rust, with nothing using them yet | 89 | client.create(ws, viewer, { kind: "doc" }), |
| 90 | client.update(ws, viewer, f, { title: "x" }), | |
| 91 | client.move(ws, viewer, f, { space_id: null, parent_id: null }), | |
| 92 | client.duplicate(ws, viewer, f), | |
| 93 | client.trash(ws, viewer, f), | |
| 94 | client.restore(ws, viewer, f), | |
| 95 | client.delete(ws, viewer, f), | |
| 96 | client.trashed(ws, viewer), | |
| 97 | client.favorite(ws, viewer, f, true), | |
| 98 | client.content(ws, viewer, f), | |
| 99 | client.edit(ws, viewer, f, { kind: "doc", target: { kind: "append" }, markdown: "x" }), | |
| 100 | client.access(ws, viewer, f), | |
| 101 | client.changeAccess(ws, viewer, f, { op: "grant", principal: "user:usr_2", role: "view" }), | |
| 102 | client.changeAccess(ws, viewer, f, { op: "general", access: "workspace", role: "view" }), | |
| 103 | client.requestAccess(ws, viewer, f), | |
| 104 | client.joinSpace(ws, viewer, "spc_1"), | |
| 105 | client.leaveSpace(ws, viewer, "spc_1"), | |
| 106 | client.search(ws, viewer, { q: "roadmap" }), | |
| 107 | client.versions(ws, viewer, f), | |
| 108 | client.version(ws, viewer, f, "ver_1"), | |
| 109 | client.restoreVersion(ws, viewer, f, "ver_1"), | |
| 110 | client.templates(ws, viewer), | |
| 111 | client.saveTemplate(ws, viewer, { folio_id: f, name: "T" }), | |
| 112 | client.deleteTemplate(ws, viewer, "tpl_1"), | |
| 113 | client.export(ws, viewer, f), | |
| 114 | client.suggestions(ws, viewer, f), | |
| 115 | client.decideSuggestion(ws, viewer, "sug_1", "accept"), | |
| 116 | client.proposals(ws, viewer, f), | |
| 117 | client.decideProposal(ws, viewer, "prp_1", "reject"), | |
| 118 | client.thread(ws, viewer, f, { op: "resolve", thread_id: "thr_1" }), | |
| 119 | client.threads(ws, viewer, f), | |
| 120 | client.queryTile(ws, viewer, f, "t1"), | |
| 121 | client.queryDataset(ws, viewer, { dataset: "issues", measure: { op: "count" } }), | |
| 122 | client.queryDatasetForAgent(ws, "agt_1", viewer, { folio_id: f, tile_id: "t1" }), | |
| 123 | client.foliosForAgent(ws, "agt_1", viewer, { tab: "all" }), | |
| 124 | client.readForAgent(ws, "agt_1", viewer, f), | |
| 125 | client.createAsAgent(ws, "agt_1", viewer, { kind: "doc", title: "Notes", where: "private" }), | |
| 126 | client.editAsAgent(ws, "agt_1", viewer, f, { kind: "slides", ops: [{ op: "delete_slides", slide_ids: ["s1"] }] }), | |
| 127 | client.shareAsAgent(ws, "agt_1", viewer, f, { user_ids: ["usr_2"], role: "view" }, { kind: "people", user_ids: ["usr_1", "usr_2"] }), | |
| Every agent starts with g1t's foundational skills, playbooks for documents, research, data, code, communication, and files and media, each using only tools the agent already has and saying plainly what isn't possible yet: agents make PDFs, Word documents, spreadsheets, CSV and Markdown files with make_file, kept with a doc in Artifacts and served from g1tusercontent.com; an agent's Skills tab shows what each skill can do and with which tools, and owners turn a skill off per agent; the agent skills guide says how. | 128 | client.attachAsAgent(ws, "agt_1", viewer, f, { name: "q3.pdf", content_type: "application/pdf", data: "JVBERi0=" }), |
| Artifacts contracts: folios, their four kinds, dashboard datasets, folio events and the artifacts scopes are typed and validated the same in TypeScript and Rust, with nothing using them yet | 129 | client.recallForAgent(ws, "agt_1", viewer, { query: "pricing" }), |
| 130 | client.staleForAgent(ws, "agt_1", viewer), | |
| 131 | client.markCurrent(ws, viewer, f), | |
| 132 | client.reindex(ws, viewer), | |
| 133 | ]); | |
| 134 | assert.deepEqual([...new Set(called)].sort(), [...FOLIO_RPC_METHODS].sort()); | |
| 135 | // Every body names the workspace and the viewer, and keys are snake_case. | |
| 136 | for (const body of bodies) { | |
| 137 | assert.equal(body.workspace, ws); | |
| 138 | assert.ok(body.viewer); | |
| 139 | for (const key of Object.keys(body)) assert.match(key, /^[a-z_]+$/, key); | |
| 140 | } | |
| 141 | }); | |
| 142 | ||
| 143 | test("slides ops are checked by shape", () => { | |
| 144 | assert.equal(slidesOpError({ op: "insert_slides", after_slide_id: null, markdown: "---" }), null); | |
| 145 | assert.equal(slidesOpError({ op: "move_slide", slide_id: "s1", after_slide_id: "s2" }), null); | |
| 146 | assert.equal(slidesOpError({ op: "set_theme", theme: "night", accent: "#8b7cf6" }), null); | |
| 147 | assert.equal(slidesOpError({ op: "set_theme", theme: "night", accent: "lavender" }), "accent is a colour like #8b7cf6, or null."); | |
| 148 | assert.equal(slidesOpError({ op: "replace_slide", markdown: "# x" }), "replace_slide needs slide_id."); | |
| 149 | assert.equal(slidesOpError({ op: "delete_slides", slide_ids: [] }), "delete_slides needs slide_ids."); | |
| 150 | assert.equal(slidesOpError({ op: "shuffle" }), "There is no slides op called shuffle."); | |
| 151 | for (const layout of SLIDE_LAYOUTS) assert.ok(SLIDE_LAYOUT_REGIONS[layout].includes("notes"), layout); | |
| 152 | assert.equal(slideFragment("s1", "left"), "slide:s1:left"); | |
| 153 | }); | |
| 154 | ||
| 155 | test("design ops are checked by shape and size", () => { | |
| 156 | const frame = { type: "frame", name: "Home", layout: "column", gap: 16, children: [{ type: "text", text: "Hello" }, { type: "html", html: "<b>hi</b>" }] }; | |
| 157 | assert.equal(designOpError({ op: "upsert_nodes", nodes: [frame] }), null); | |
| 158 | assert.deepEqual(countDesignSpecs([frame as never]), { nodes: 3, depth: 2 }); | |
| 159 | assert.equal(designOpError({ op: "upsert_nodes", nodes: [{ type: "rect", children: [{ type: "text" }] }] }), "Only frames and components have children."); | |
| 160 | assert.equal(designOpError({ op: "upsert_nodes", nodes: [{ type: "text", html: "<b>" }] }), "Only an html node has html."); | |
| 161 | assert.equal(designOpError({ op: "upsert_nodes", nodes: [{ type: "blob" }] }), "There is no node type called blob."); | |
| 162 | assert.equal(designOpError({ op: "set_html", node_id: "n1", html: "x".repeat(200 * 1024 + 1) }), "An html node holds at most 200 KB."); | |
| 163 | assert.equal(designOpError({ op: "set_html", node_id: "n1", html: "é".repeat(100 * 1024) }), null); | |
| 164 | assert.equal(designOpError({ op: "set_html", node_id: "n1", html: "é".repeat(100 * 1024 + 1) }), "An html node holds at most 200 KB."); | |
| 165 | const many = Array.from({ length: DESIGN_MAX_NODES + 1 }, () => ({ type: "rect" })); | |
| 166 | assert.equal(designOpError({ op: "upsert_nodes", nodes: many }), `A design holds at most ${DESIGN_MAX_NODES} nodes.`); | |
| 167 | assert.equal(designOpError({ op: "move", ids: ["a"], dx: 1, dy: Number.NaN }), "move needs dx and dy."); | |
| 168 | assert.equal(designOpError({ op: "instance" }), "There is no design op called instance."); | |
| 169 | }); | |
| 170 | ||
| 171 | test("dashboard ops check tiles, their queries and the grid", () => { | |
| 172 | const tile = { | |
| 173 | id: "t1", | |
| 174 | type: "line", | |
| 175 | title: "Merged per week", | |
| 176 | description: "", | |
| 177 | query: { dataset: "pull_requests", measure: { op: "count" }, interval: "week", time: "merged_at" }, | |
| 178 | viz: {}, | |
| 179 | grid: { x: 0, y: 0, w: 6, h: 4 }, | |
| 180 | }; | |
| 181 | assert.equal(dashboardOpError({ op: "upsert_tile", tile }, datasetQueryError), null); | |
| 182 | assert.equal(dashboardOpError({ op: "upsert_tile", tile: { ...tile, query: { ...tile.query, group_by: "secret" } } }, datasetQueryError), "pull_requests can't be grouped by secret."); | |
| 183 | assert.equal(dashboardOpError({ op: "upsert_tile", tile: { ...tile, type: "markdown", query: null, markdown: "Notes" } }, datasetQueryError), null); | |
| 184 | assert.equal(dashboardOpError({ op: "upsert_tile", tile: { ...tile, type: "markdown" } }, datasetQueryError), "A text tile has no query."); | |
| 185 | assert.equal(dashboardOpError({ op: "upsert_tile", tile: { ...tile, grid: { x: 8, y: 0, w: 6, h: 4 } } }, datasetQueryError), "A tile fits in 12 columns."); | |
| 186 | assert.equal(dashboardOpError({ op: "set_filters", filters: { range: "30d", repos: ["acme/web"] } }, datasetQueryError), null); | |
| 187 | assert.equal(dashboardOpError({ op: "set_filters", filters: { range: { from: "2026-02-30", to: "2026-03-01" } } }, datasetQueryError), "A range's from and to are dates or RFC 3339 UTC times."); | |
| 188 | assert.equal(dashboardOpError({ op: "set_layout", tiles: [{ id: "t1", grid: { x: 0, y: 0, w: 0, h: 1 } }] }, datasetQueryError), "grid starts at 0, 0 and is at least 1 by 1."); | |
| 189 | assert.equal(dashboardGridError({ x: 0, y: 0, w: 12, h: 1 }), null); | |
| 190 | }); |