Skip to content
83 linesCodeBlameRaw

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 docs service answers every artifacts call: docs can be made, listed, shared, moved, trashed, restored, searched, versioned and edited live in their own rooms, agents read, write and recall them only where their person and everyone in the conversation can, and folio events go out on the bus, while Docs' pages keep working as before.1/**
2 * What an agent may reach in folios for the person it acts for (its
3 * asker), and who else will see its answer (the audience): the leak rules
The docs folder is gone, and what it held lives where people read it: how a self-hosted g1t runs and how to deploy g1t to Cloudflare are pages on docs.g1t.sh under Run g1t yourself, and speed, rate limits and operating g1t.sh are sections of CONTRIBUTING.md; code that cited a file in docs/ now points to the page or section that covers it, or says what it means itself, and applied migrations and the runner images are left as they were.4 * (docs.g1t.sh/guides/agent-access/), apart from where rows come from.
The docs service answers every artifacts call: docs can be made, listed, shared, moved, trashed, restored, searched, versioned and edited live in their own rooms, agents read, write and recall them only where their person and everyone in the conversation can, and folio events go out on the bus, while Docs' pages keep working as before.5 * Pure.
6 *
7 * - The agent never has more than its asker (`agentFolioRole`).
8 * - Finding things (lists, search, recall, stale) is narrowed to folios
9 * every person in the audience can read, so nothing turns up in a
10 * conversation that someone in it can't open.
11 * - A public channel's audience is "the workspace": only folios readable
12 * through an open space or general access `workspace`. Never a link
13 * folio, a share, or Private. More than 20 people reads the same way.
14 * - Reading one folio the asker named (a link they gave) works whenever
15 * the asker can read it; the result says `audience_can_read: false`
16 * when someone else in the conversation can't, so the agent says it
17 * sent the link to the asker instead of quoting it.
18 */
19import type { DocAgentAbilities, DocAgentMode, DocAudience, DocRole } from "@g1t/contracts";
20
21import { agentAbilities, agentFolioRole, effectiveRole, folioReadableByWorkspace, roleOf, type FolioAclNode, type FolioGrants, type Person, type SpaceRules } from "../access.ts";
22
23/** More people than this in a conversation read as the whole workspace. */
24export const AUDIENCE_AS_WORKSPACE = 20;
25
26/** Who an answer reaches, as access checks it. */
27export type AudienceRule =
28 /** The asker alone (no audience, or only them). */
29 | { kind: "asker" }
30 /** These other people besides the asker, by user id. */
31 | { kind: "people"; user_ids: string[] }
32 /** Everyone in the workspace. */
33 | { kind: "workspace" };
34
35export function audienceRule(audience: DocAudience | null | undefined, askerId: string): AudienceRule {
36 if (!audience) return { kind: "asker" };
37 if (audience.kind === "workspace") return { kind: "workspace" };
38 if (audience.kind !== "people" || !Array.isArray(audience.user_ids)) return { kind: "asker" };
39 const others = [...new Set(audience.user_ids.map(String))].filter((id) => id && id !== askerId);
40 if (!others.length) return { kind: "asker" };
41 if (others.length + 1 > AUDIENCE_AS_WORKSPACE) return { kind: "workspace" };
42 return { kind: "people", user_ids: others };
43}
44
45/** One folio as an agent sees it. */
46export type AgentReach = {
47 /** The asker's role, or null when they can't read it. */
48 asker_role: DocRole | null;
49 /** Whether everyone in the audience can read it too. */
50 audience_can_read: boolean;
51 /** What the agent may do: the asker's role, nothing more, by the folio's agent mode. */
52 can: DocAgentAbilities;
53};
54
55export type ReachInput = {
56 chain: readonly FolioAclNode[];
57 grants: FolioGrants;
58 /** The folio's space's rules, when its chain inherits one (else null). */
59 space: SpaceRules | null;
60 asker: Person;
61 askerVisited: boolean;
62 rule: AudienceRule;
63 /** The audience's people (for a `people` rule). */
64 people: readonly Person[];
65 /** Whether a person opened the folio's link. */
66 visited: (person: Person) => boolean;
67 agent_mode: DocAgentMode;
68};
69
70export function agentReach(input: ReachInput): AgentReach {
71 const spaceRole = (p: Person) => (input.space ? roleOf(input.space, p) : null);
72 const asker = effectiveRole(input.chain, input.grants, spaceRole(input.asker), input.asker, { visited: input.askerVisited });
73 let audience = true;
74 if (input.rule.kind === "workspace") audience = folioReadableByWorkspace(input.chain, input.space);
75 else if (input.rule.kind === "people") audience = input.people.every((p) => !!effectiveRole(input.chain, input.grants, spaceRole(p), p, { visited: input.visited(p) }));
76 const role = agentFolioRole(asker, true);
77 return { asker_role: role, audience_can_read: !!asker && audience, can: agentAbilities(role, input.agent_mode) };
78}
79
80/** Whether an agent may find a folio (lists, search, recall): its asker and every person in the audience can read it. */
81export function agentMayFind(reach: AgentReach): boolean {
82 return !!reach.asker_role && reach.audience_can_read;
83}