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.
| 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. | 1 | import assert from "node:assert/strict"; |
| 2 | import { test } from "node:test"; | |
| 3 | ||
| 4 | import { aclChain, type FolioAclNode, type FolioGrant, type Person, type SpaceRules } from "../access.ts"; | |
| 5 | import { agentMayFind, agentReach, audienceRule, type AudienceRule } from "./agents.ts"; | |
| 6 | ||
| 7 | // The leak rules (docs.g1t.sh/guides/agent-access/). | |
| 8 | ||
| 9 | const ana: Person = { user_id: "ana", owner: false, teams: new Set(["web"]) }; | |
| 10 | const bo: Person = { user_id: "bo", owner: false, teams: new Set() }; | |
| 11 | const cy: Person = { user_id: "cy", owner: false, teams: new Set(["web"]) }; | |
| 12 | ||
| 13 | const open: SpaceRules = { kind: "workspace", team: null, default_role: "edit", members: [] }; | |
| 14 | const team: SpaceRules = { kind: "team", team: "web", default_role: "edit", members: [] }; | |
| 15 | ||
| 16 | function node(id: string, over: Partial<FolioAclNode> = {}): FolioAclNode { | |
| 17 | return { id, owner: "user:ana", parent_id: null, space_id: null, inherit: true, general_access: "none", general_role: null, ...over }; | |
| 18 | } | |
| 19 | ||
| 20 | function reach(n: FolioAclNode, grants: Record<string, FolioGrant[]>, space: SpaceRules | null, rule: AudienceRule, people: Person[] = [], visits: string[] = [], asker = ana) { | |
| 21 | return agentReach({ | |
| 22 | chain: aclChain(n.id, new Map([[n.id, n]])), | |
| 23 | grants: new Map(Object.entries(grants)), | |
| 24 | space, | |
| 25 | asker, | |
| 26 | askerVisited: visits.includes(asker.user_id), | |
| 27 | rule, | |
| 28 | people, | |
| 29 | visited: (p) => visits.includes(p.user_id), | |
| 30 | agent_mode: "edit", | |
| 31 | }); | |
| 32 | } | |
| 33 | ||
| 34 | test("audience rules: none or only the asker is the asker; more than 20 people is the workspace", () => { | |
| 35 | assert.deepEqual(audienceRule(null, "ana"), { kind: "asker" }); | |
| 36 | assert.deepEqual(audienceRule({ kind: "people", user_ids: ["ana"] }, "ana"), { kind: "asker" }); | |
| 37 | assert.deepEqual(audienceRule({ kind: "people", user_ids: ["ana", "bo", "bo"] }, "ana"), { kind: "people", user_ids: ["bo"] }); | |
| 38 | assert.deepEqual(audienceRule({ kind: "workspace" }, "ana"), { kind: "workspace" }); | |
| 39 | const crowd = Array.from({ length: 21 }, (_, i) => `u${i}`); | |
| 40 | assert.deepEqual(audienceRule({ kind: "people", user_ids: crowd }, "ana"), { kind: "workspace" }); | |
| 41 | const twenty = Array.from({ length: 19 }, (_, i) => `u${i}`); | |
| 42 | assert.equal(audienceRule({ kind: "people", user_ids: twenty }, "ana").kind, "people"); | |
| 43 | }); | |
| 44 | ||
| 45 | test("rule 1: a public channel finds only open-space and workspace-wide folios", () => { | |
| 46 | const ws: AudienceRule = { kind: "workspace" }; | |
| 47 | assert.equal(agentMayFind(reach(node("a", { space_id: "open" }), {}, open, ws)), true); | |
| 48 | assert.equal(agentMayFind(reach(node("a", { general_access: "workspace", general_role: "view" }), {}, null, ws)), true); | |
| 49 | // Private, shared, team, link: never in a public channel. | |
| 50 | assert.equal(agentMayFind(reach(node("a"), {}, null, ws)), false); | |
| 51 | assert.equal(agentMayFind(reach(node("a"), { a: [{ principal: "user:bo", role: "view" }] }, null, ws)), false); | |
| 52 | assert.equal(agentMayFind(reach(node("a", { space_id: "team" }), {}, team, ws)), false); | |
| 53 | assert.equal(agentMayFind(reach(node("a", { general_access: "link", general_role: "view" }), {}, null, ws, [], ["ana"])), false); | |
| 54 | }); | |
| 55 | ||
| 56 | test("rule 1: a conversation finds only what everyone in it can read", () => { | |
| 57 | const withBo: AudienceRule = { kind: "people", user_ids: ["bo"] }; | |
| 58 | const shared = node("a"); | |
| 59 | assert.equal(agentMayFind(reach(shared, { a: [{ principal: "user:bo", role: "view" }] }, null, withBo, [bo])), true); | |
| 60 | assert.equal(agentMayFind(reach(shared, {}, null, withBo, [bo])), false); | |
| 61 | // A team space: Cy is in the team, Bo isn't. | |
| 62 | const t = node("t", { space_id: "team" }); | |
| 63 | assert.equal(agentMayFind(reach(t, {}, team, { kind: "people", user_ids: ["cy"] }, [cy])), true); | |
| 64 | assert.equal(agentMayFind(reach(t, {}, team, withBo, [bo])), false); | |
| 65 | // A link folio: only when everyone opened it. | |
| 66 | const link = node("l", { general_access: "link", general_role: "view" }); | |
| 67 | assert.equal(agentMayFind(reach(link, {}, null, withBo, [bo], ["ana"])), false); | |
| 68 | assert.equal(agentMayFind(reach(link, {}, null, withBo, [bo], ["ana", "bo"])), true); | |
| 69 | }); | |
| 70 | ||
| 71 | test("rule 1: the asker's own Private is theirs alone", () => { | |
| 72 | assert.equal(agentMayFind(reach(node("p"), {}, null, { kind: "asker" })), true); | |
| 73 | assert.equal(agentMayFind(reach(node("p"), {}, null, { kind: "people", user_ids: ["bo"] }, [bo])), false); | |
| 74 | // Someone else's Private: not even for its asker. | |
| 75 | assert.equal(agentMayFind(reach(node("p", { owner: "user:bo" }), {}, null, { kind: "asker" })), false); | |
| 76 | }); | |
| 77 | ||
| 78 | test("rule 2: reading what the audience can't all read says so", () => { | |
| 79 | const r = reach(node("p"), {}, null, { kind: "workspace" }); | |
| 80 | assert.equal(r.asker_role, "manage"); | |
| 81 | assert.equal(r.audience_can_read, false); | |
| 82 | const fine = reach(node("o", { space_id: "open" }), {}, open, { kind: "workspace" }); | |
| 83 | assert.equal(fine.audience_can_read, true); | |
| 84 | }); | |
| 85 | ||
| 86 | test("an agent's grant never widens what it can do for its asker", () => { | |
| 87 | const n = node("f", { owner: "user:cy" }); | |
| 88 | const grants = { f: [{ principal: "agent:ag1", role: "manage" as const }, { principal: "user:bo", role: "comment" as const }] }; | |
| 89 | const r = reach(n, grants, null, { kind: "asker" }, [], [], bo); | |
| 90 | assert.equal(r.asker_role, "comment"); | |
| Four small things seen today. An agent can attach a file it made to any doc the person who asked can edit, whatever the space's agent mode: attaching changes nothing in the doc, so the suggest mode that stopped every PDF and spreadsheet no longer does; the ability is its own, attach, beside read, suggest and edit. Home no longer counts robots among the people: events that name an agent by its own id, or g1t's upkeep as the workspace or as g1t, read as the agent and as g1t, in the digest and on the page, so the faces say who instead of someone. Tooltips are the inverted bubble, the page's foreground behind the page's background, with no border. The Spend page's slice tabs keep their ring inside the row instead of losing its top to the scroll edge, and the Apps launcher keeps its search box and footer in place while it reads which apps there are, with skeleton tiles between. | 91 | assert.deepEqual(r.can, { read: true, suggest: true, edit: false, attach: false }, "a commenter can't have a file attached for them either"); |
| 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. | 92 | const none = reach(n, grants, null, { kind: "asker" }, [], [], ana); |
| 93 | assert.equal(none.asker_role, null); | |
| Four small things seen today. An agent can attach a file it made to any doc the person who asked can edit, whatever the space's agent mode: attaching changes nothing in the doc, so the suggest mode that stopped every PDF and spreadsheet no longer does; the ability is its own, attach, beside read, suggest and edit. Home no longer counts robots among the people: events that name an agent by its own id, or g1t's upkeep as the workspace or as g1t, read as the agent and as g1t, in the digest and on the page, so the faces say who instead of someone. Tooltips are the inverted bubble, the page's foreground behind the page's background, with no border. The Spend page's slice tabs keep their ring inside the row instead of losing its top to the scroll edge, and the Apps launcher keeps its search box and footer in place while it reads which apps there are, with skeleton tiles between. | 94 | assert.deepEqual(none.can, { read: false, suggest: false, edit: false, attach: false }); |
| 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. | 95 | assert.equal(agentMayFind(none), false); |
| 96 | }); |