Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

237 linesCodeBlameRaw
1import assert from "node:assert/strict";
2import { test } from "node:test";
3
4import {
5 aclChain,
6 aclRootOf,
7 canShare,
8 effectiveRole,
9 explicitAccess,
10 folioPathOf,
11 folioReadableByWorkspace,
12 folioScope,
13 generalRoleCap,
14 inheritsSpace,
15 isPrivateFolio,
16 materialize,
17 roleOf,
18 type FolioAclNode,
19 type FolioGrant,
20 type Person,
21 type SpaceRules,
22} from "./access.ts";
23
24// People: Ana (team web), Bo (no team), Cy (team design), Wes (workspace owner).
25const ana: Person = { user_id: "ana", owner: false, teams: new Set(["web"]) };
26const bo: Person = { user_id: "bo", owner: false, teams: new Set() };
27const cy: Person = { user_id: "cy", owner: false, teams: new Set(["design"]) };
28const wes: Person = { user_id: "wes", owner: true, teams: new Set() };
29
30// Spaces.
31const open: SpaceRules = { kind: "workspace", team: null, default_role: "edit", members: [] };
32const openView: SpaceRules = { kind: "workspace", team: null, default_role: "view", members: [] };
33const team: SpaceRules = { kind: "team", team: "web", default_role: "comment", members: [] };
34const membersOnly: SpaceRules = { kind: "private", team: null, default_role: null, members: [{ principal: "user:cy", role: "manage" }] };
35const SPACES: Record<string, SpaceRules> = { open, openView, team, membersOnly };
36
37function node(id: string, over: Partial<FolioAclNode> = {}): FolioAclNode {
38 return {
39 id,
40 owner: "user:ana",
41 parent_id: null,
42 space_id: null,
43 inherit: true,
44 general_access: "none",
45 general_role: null,
46 created_at: "2026-10-01T00:00:00Z",
47 ...over,
48 };
49}
50
51/** The role a person has on `id`, given every node and grant. */
52function roleIn(nodes: FolioAclNode[], grants: Record<string, FolioGrant[]>, id: string, person: Person, visited = false) {
53 const byId = new Map(nodes.map((n) => [n.id, n]));
54 const chain = aclChain(id, byId);
55 const root = chain[chain.length - 1]!;
56 const space = root.space_id ? SPACES[root.space_id]! : null;
57 return effectiveRole(chain, new Map(Object.entries(grants)), space ? roleOf(space, person) : null, person, { visited });
58}
59
60test("private: only the owner, and not the workspace owner", () => {
61 const nodes = [node("f")];
62 assert.equal(roleIn(nodes, {}, "f", ana), "manage");
63 assert.equal(roleIn(nodes, {}, "f", bo), null);
64 assert.equal(roleIn(nodes, {}, "f", wes), null);
65 const byId = new Map(nodes.map((n) => [n.id, n]));
66 assert.equal(isPrivateFolio(aclChain("f", byId), new Map()), true);
67});
68
69test("an open space gives every member its base role; owners manage", () => {
70 const nodes = [node("f", { space_id: "open", owner: "user:cy" })];
71 assert.equal(roleIn(nodes, {}, "f", ana), "edit");
72 assert.equal(roleIn(nodes, {}, "f", bo), "edit");
73 assert.equal(roleIn(nodes, {}, "f", cy), "manage");
74 assert.equal(roleIn(nodes, {}, "f", wes), "manage");
75 const byId = new Map(nodes.map((n) => [n.id, n]));
76 assert.equal(isPrivateFolio(aclChain("f", byId), new Map()), false);
77});
78
79test("a team space: its team gets the base role; others nothing; workspace owners manage", () => {
80 const nodes = [node("f", { space_id: "team", owner: "user:cy" })];
81 assert.equal(roleIn(nodes, {}, "f", ana), "comment");
82 assert.equal(roleIn(nodes, {}, "f", bo), null);
83 assert.equal(roleIn(nodes, {}, "f", wes), "manage");
84});
85
86test("a members-only space: only its members, never the workspace owner", () => {
87 const nodes = [node("f", { space_id: "membersOnly", owner: "user:cy" })];
88 assert.equal(roleIn(nodes, {}, "f", cy), "manage");
89 assert.equal(roleIn(nodes, {}, "f", ana), null);
90 assert.equal(roleIn(nodes, {}, "f", wes), null);
91});
92
93test("grants to a person, an agent and a team", () => {
94 const nodes = [node("f")];
95 const grants = {
96 f: [
97 { principal: "user:bo", role: "comment" as const },
98 { principal: "team:design", role: "edit" as const },
99 { principal: "agent:ag1", role: "edit" as const },
100 ],
101 };
102 assert.equal(roleIn(nodes, grants, "f", bo), "comment");
103 assert.equal(roleIn(nodes, grants, "f", cy), "edit");
104 // An agent grant gives no person anything.
105 assert.equal(roleIn(nodes, grants, "f", wes), null);
106 const byId = new Map(nodes.map((n) => [n.id, n]));
107 assert.equal(isPrivateFolio(aclChain("f", byId), new Map(Object.entries(grants))), false);
108});
109
110test("a grant raises a space role but never lowers it", () => {
111 const nodes = [node("f", { space_id: "openView", owner: "user:cy" })];
112 assert.equal(roleIn(nodes, { f: [{ principal: "user:bo", role: "edit" }] }, "f", bo), "edit");
113 assert.equal(roleIn(nodes, { f: [{ principal: "user:ana", role: "view" }] }, "f", ana), "view");
114 const open2 = [node("f", { space_id: "open", owner: "user:cy" })];
115 assert.equal(roleIn(open2, { f: [{ principal: "user:ana", role: "view" }] }, "f", ana), "edit");
116});
117
118test("general access: workspace gives every member its role, never manage", () => {
119 const nodes = [node("f", { general_access: "workspace", general_role: "comment" })];
120 assert.equal(roleIn(nodes, {}, "f", bo), "comment");
121 assert.equal(roleIn(nodes, {}, "f", wes), "comment");
122 const capped = [node("f", { general_access: "workspace", general_role: "manage" })];
123 assert.equal(roleIn(capped, {}, "f", bo), "edit");
124 assert.equal(generalRoleCap(null), "view");
125});
126
127test("general access: link gives its role only to people who opened it", () => {
128 const nodes = [node("f", { general_access: "link", general_role: "view" })];
129 assert.equal(roleIn(nodes, {}, "f", bo), null);
130 assert.equal(roleIn(nodes, {}, "f", bo, true), "view");
131});
132
133test("nested docs inherit their parent's grants, space and general access", () => {
134 const nodes = [
135 node("top", { space_id: "team", owner: "user:cy" }),
136 node("mid", { space_id: "team", owner: "user:cy", parent_id: "top" }),
137 node("leaf", { space_id: "team", owner: "user:cy", parent_id: "mid" }),
138 ];
139 const grants = { top: [{ principal: "user:bo", role: "view" as const }] };
140 assert.equal(roleIn(nodes, grants, "leaf", ana), "comment");
141 assert.equal(roleIn(nodes, grants, "leaf", bo), "view");
142 assert.equal(roleIn(nodes, grants, "leaf", wes), "manage");
143 const byId = new Map(nodes.map((n) => [n.id, n]));
144 assert.deepEqual(
145 aclChain("leaf", byId).map((n) => n.id),
146 ["leaf", "mid", "top"],
147 );
148 assert.equal(inheritsSpace(aclChain("leaf", byId)), true);
149});
150
151test("a parent's owner keeps full access to what others add under it", () => {
152 const nodes = [node("top"), node("child", { parent_id: "top", owner: "user:bo" })];
153 assert.equal(roleIn(nodes, {}, "child", ana), "manage");
154 assert.equal(roleIn(nodes, {}, "child", bo), "manage");
155 assert.equal(roleIn(nodes, {}, "child", cy), null);
156});
157
158test("restricting stops the space, the parent's grants and general access", () => {
159 const nodes = [
160 node("top", { space_id: "open", owner: "user:cy", general_access: "workspace", general_role: "view" }),
161 node("secret", { space_id: "open", owner: "user:cy", parent_id: "top", inherit: false }),
162 node("under", { space_id: "open", owner: "user:cy", parent_id: "secret" }),
163 ];
164 const grants = { top: [{ principal: "user:bo", role: "edit" as const }], secret: [{ principal: "user:ana", role: "comment" as const }] };
165 assert.equal(roleIn(nodes, grants, "secret", bo), null);
166 assert.equal(roleIn(nodes, grants, "secret", ana), "comment");
167 assert.equal(roleIn(nodes, grants, "under", ana), "comment");
168 assert.equal(roleIn(nodes, grants, "under", wes), null);
169 assert.equal(roleIn(nodes, grants, "under", cy), "manage");
170 // A restricted top-level folio in a space leaves the space's people out too.
171 const top = [node("t", { space_id: "open", owner: "user:cy", inherit: false })];
172 assert.equal(roleIn(top, {}, "t", ana), null);
173 assert.equal(roleIn(top, {}, "t", wes), null);
174});
175
176test("the access root and path of a new folio", () => {
177 assert.equal(aclRootOf({ id: "a", inherit: true, parent_id: null }, null), "a");
178 assert.equal(aclRootOf({ id: "b", inherit: true, parent_id: "a" }, "a"), "a");
179 assert.equal(aclRootOf({ id: "c", inherit: false, parent_id: "b" }, "a"), "c");
180 assert.equal(folioPathOf("a", null), "/a/");
181 assert.equal(folioPathOf("b", "/a/"), "/a/b/");
182});
183
184test("materialize writes the owner, ancestor owners and grants up to the access root, highest role each", () => {
185 const nodes = [
186 node("top", { owner: "user:ana" }),
187 node("child", { parent_id: "top", owner: "user:bo" }),
188 node("restricted", { parent_id: "child", owner: "user:bo", inherit: false }),
189 ];
190 const byId = new Map(nodes.map((n) => [n.id, n]));
191 const grants = new Map<string, FolioGrant[]>([
192 ["top", [{ principal: "user:cy", role: "view", granted_at: "2026-10-02T00:00:00Z" }]],
193 ["child", [{ principal: "user:cy", role: "edit", granted_at: "2026-10-03T00:00:00Z" }]],
194 ]);
195 const rows = materialize(["top", "child", "restricted"], byId, grants);
196 const of = (id: string) => Object.fromEntries(rows.filter((r) => r.folio_id === id).map((r) => [r.principal, `${r.role}@${r.via}`]));
197 assert.deepEqual(of("top"), { "user:ana": "manage@owner", "user:cy": "view@top" });
198 assert.deepEqual(of("child"), { "user:bo": "manage@owner", "user:cy": "edit@child", "user:ana": "manage@top" });
199 assert.deepEqual(of("restricted"), { "user:bo": "manage@owner" });
200 assert.equal(explicitAccess(aclChain("child", byId), grants).get("user:cy")?.since, "2026-10-03T00:00:00Z");
201});
202
203test("the index scope is the space's only when access is exactly the space's", () => {
204 const byId = (nodes: FolioAclNode[]) => new Map(nodes.map((n) => [n.id, n]));
205 const plain = byId([node("a", { space_id: "open" })]);
206 assert.equal(folioScope(aclChain("a", plain), new Map()), "space:open");
207 assert.equal(folioScope(aclChain("a", plain), new Map([["a", [{ principal: "user:bo", role: "view" }]]])), "folio:a");
208 const general = byId([node("a", { space_id: "open", general_access: "workspace", general_role: "view" })]);
209 assert.equal(folioScope(aclChain("a", general), new Map()), "folio:a");
210 const nested = byId([node("a", { space_id: "open" }), node("b", { space_id: "open", parent_id: "a", owner: "user:bo" })]);
211 assert.equal(folioScope(aclChain("b", nested), new Map()), "space:open");
212 const restricted = byId([node("a", { space_id: "open" }), node("b", { space_id: "open", parent_id: "a", inherit: false })]);
213 assert.equal(folioScope(aclChain("b", restricted), new Map()), "folio:b");
214 const priv = byId([node("p")]);
215 assert.equal(folioScope(aclChain("p", priv), new Map()), "folio:p");
216});
217
218test("readable by the whole workspace: open spaces and workspace general access only", () => {
219 const byId = (nodes: FolioAclNode[]) => new Map(nodes.map((n) => [n.id, n]));
220 assert.equal(folioReadableByWorkspace(aclChain("a", byId([node("a", { space_id: "open" })])), open), true);
221 assert.equal(folioReadableByWorkspace(aclChain("a", byId([node("a", { space_id: "team" })])), team), false);
222 assert.equal(folioReadableByWorkspace(aclChain("a", byId([node("a", { space_id: "membersOnly" })])), membersOnly), false);
223 assert.equal(folioReadableByWorkspace(aclChain("a", byId([node("a")])), null), false);
224 assert.equal(folioReadableByWorkspace(aclChain("a", byId([node("a", { general_access: "workspace", general_role: "view" })])), null), true);
225 // A link is never the workspace's, even for people who opened it.
226 assert.equal(folioReadableByWorkspace(aclChain("a", byId([node("a", { general_access: "link", general_role: "view" })])), null), false);
227 // Restricted inside an open space: not the workspace's.
228 const restricted = byId([node("a", { space_id: "open" }), node("b", { space_id: "open", parent_id: "a", inherit: false })]);
229 assert.equal(folioReadableByWorkspace(aclChain("b", restricted), open), false);
230});
231
232test("who may share", () => {
233 assert.equal(canShare("manage"), true);
234 assert.equal(canShare("edit"), false);
235 assert.equal(canShare("edit", true), true);
236 assert.equal(canShare(null, true), false);
237});