flagon-io/g1t

public

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

g1t/services/context/src/catalog.test.ts

214 lines12,603 bytesCodeBlame
1import { test } from "node:test";
2import assert from "node:assert/strict";
3
4import { assemble, authorsOf } from "./assemble.ts";
5import { extract } from "./extract.ts";
6import { composeRunContext, HEADER } from "./runcontext.ts";
7import { evaluate } from "./scorecards.ts";
8import { granted } from "../../../packages/contracts/src/access.ts";
9import { indexFilter, memoryReadable, merge, readable, allowedKinds, countVisible, projectReadable, runMemoryReadable } from "./visibility.ts";
10
11const project = {
12 id: "prj_1",
13 workspace: "acme",
14 slug: "web",
15 name: "web",
16 description: "The storefront.",
17 private: true,
18 repoId: "rep_1",
19 repo: { namespace: "acme", name: "web" },
20 rootDir: "",
21 defaultBranch: "main",
22};
23const ctx = { project: "web", siblings: ["package-lock.json"] };
24
25test("a project's entities and relations come from its files and surroundings", () => {
26 const files = [
27 { path: "package.json", facts: extract("package.json", JSON.stringify({ name: "@acme/web", scripts: { test: "vitest" }, dependencies: { "@acme/ui": "*" } }), ctx) },
28 { path: "README.md", facts: extract("README.md", "# Web\n\nThe storefront.", ctx) },
29 { path: "wrangler.jsonc", facts: extract("wrangler.jsonc", '{"name":"web","routes":["shop.acme.com/*"]}', ctx) },
30 { path: ".g1t/workflows/ci.yml", facts: extract(".g1t/workflows/ci.yml", "run: npm test", ctx) },
31 ];
32 const built = assemble(project, files, {
33 owners: ["ana"],
34 dependsOn: [{ slug: "api", as: "API_URL" }],
35 deploy: { enabled: true, production: { url: "https://web--acme.g1t.page", commit: "abcdef1234", deployedAt: "2026-10-01T00:00:00Z" }, previews: 2, latest: null },
36 integrations: [{ id: "int_1", provider: "sentry", name: "Sentry", kind: "alerts", repo: "acme/web" }],
37 });
38 const keys = built.entities.map((entity) => `${entity.kind}:${entity.key}`);
39 for (const key of ["project:web", "owner:ana", "language:javascript", "package:npm:@acme/web", "doc:web:README.md", "api:web:worker:web", "app:web", "environment:web/production", "environment:web/preview"]) {
40 assert.ok(keys.includes(key), `${key} in ${keys.join(", ")}`);
41 }
42 const relations = built.relations.map((r) => `${r.from.kind}:${r.from.key} ${r.kind} ${r.to.kind}:${r.to.key}`);
43 for (const relation of [
44 "project:web depends_on project:api",
45 "project:web owned_by owner:ana",
46 "project:web documented_by doc:web:README.md",
47 "project:web exposes package:npm:@acme/web",
48 "package:npm:@acme/web depends_on package:npm:@acme/ui",
49 "app:web deploys_to environment:web/production",
50 "app:web exposes api:web:worker:web",
51 "project:web uses language:javascript",
52 "project:web uses integration:int_1",
53 ]) {
54 assert.ok(relations.includes(relation), relation);
55 }
56 assert.equal(built.tests, true);
57 const entry = built.entities.find((entity) => entity.kind === "project")!;
58 assert.match(entry.summary!, /The storefront\. Written in JavaScript\. .*Uses api\. Owned by ana\./);
59 assert.equal(entry.data.productionUrl, "https://web--acme.g1t.page");
60});
61
62test("the same inputs build the same catalog", () => {
63 const files = [{ path: "go.mod", facts: extract("go.mod", "module x/y\n", ctx) }];
64 const around = { owners: [], dependsOn: [], deploy: null, integrations: [] };
65 assert.deepEqual(assemble(project, files, around), assemble(project, files, around));
66 // Without deployments there is no app or environment.
67 assert.ok(!assemble(project, files, around).entities.some((entity) => entity.kind === "app" || entity.kind === "environment"));
68});
69
70test("owners are the members who wrote a fifth or more of the recent commits", () => {
71 const commit = (name: string, email = `${name}@example.com`) => ({ author: { name, email } });
72 const commits = [...Array(6)].map(() => commit("Ana")).concat([commit("someone", "bo@acme.com"), commit("bot"), commit("bot"), commit("cy")]);
73 assert.deepEqual(authorsOf(commits, ["ana", "bo", "cy"]), ["ana"]);
74 assert.deepEqual(authorsOf([commit("x", "bo@acme.com")], ["bo"]), ["bo"]);
75 assert.deepEqual(authorsOf([], ["ana"]), []);
76});
77
78test("scorecards pass, fail with a fix, or do not apply", () => {
79 const rules = evaluate({
80 name: "web",
81 owners: [],
82 docs: ["README.md"],
83 tests: false,
84 testCommand: "npm test",
85 deploy: { enabled: true, production: null, latest: { kind: "production", status: "failed", error: "build failed" } },
86 secretFindings: null,
87 });
88 const by = Object.fromEntries(rules.map((rule) => [rule.rule, rule]));
89 assert.equal(by.has_owner.status, "fail");
90 assert.deepEqual(by.has_owner.fix?.checks, ["grep -q '^owners:' .g1t/project.yml"]);
91 assert.equal(by.has_readme.status, "pass");
92 assert.equal(by.has_readme.fix, null);
93 assert.equal(by.has_agents_md.fix?.title, "Add an AGENTS.md to web");
94 assert.match(by.tests_in_ci.fix!.body, /`npm test`/);
95 assert.equal(by.production_green.status, "fail");
96 assert.match(by.production_green.detail, /build failed/);
97 assert.equal(by.no_secret_findings.status, "na");
98 const quiet = evaluate({ name: "lib", owners: ["ana"], docs: ["readme.md", "CLAUDE.md"], tests: true, testCommand: null, deploy: null, secretFindings: 0 });
99 assert.deepEqual(quiet.map((rule) => rule.status), ["pass", "pass", "pass", "pass", "na", "pass"]);
100});
101
102test("the run context marks its sources and keeps to its budget", () => {
103 const input = {
104 projects: [
105 {
106 slug: "web",
107 name: "web",
108 repo: "acme/web",
109 rootDir: "",
110 languages: ["TypeScript"],
111 packages: ["@acme/web"],
112 testCommands: ["npm test"],
113 owners: ["ana"],
114 dependsOn: [{ slug: "api", as: "API_URL", url: "https://api--acme.g1t.page" }],
115 usedBy: [],
116 environments: [{ name: "Production", url: "https://web--acme.g1t.page", status: "ready" }],
117 docs: ["README.md"],
118 },
119 ],
120 memories: [{ id: "mem_1", kind: "gotcha", text: "Tests need TZ=UTC.", source: "AGENTS.md" }],
121 decisions: [{ id: "mem_2", kind: "decision", text: "Decided in #12: keep v1 webhooks.", source: "#12" }],
122 budget: 4000,
123 };
124 const { text, sources } = composeRunContext(input);
125 assert.ok(text!.startsWith(HEADER));
126 assert.match(text!, /Project web \(acme\/web\) \[source: catalog\]:/);
127 assert.match(text!, /Uses: api at https:\/\/api--acme\.g1t\.page \(its address is in API_URL\)/);
128 assert.match(text!, /\[gotcha\] Tests need TZ=UTC\. \[source: AGENTS\.md\]/);
129 assert.match(text!, /Recent decisions:\n- Decided in #12: keep v1 webhooks\. \[source: #12\]/);
130 assert.deepEqual(sources.sort(), ["catalog:web", "memory:mem_1", "memory:mem_2"]);
131 const tight = composeRunContext({ ...input, budget: HEADER.length + 120 });
132 assert.ok(tight.text!.length <= HEADER.length + 120);
133 assert.deepEqual(composeRunContext({ projects: [], memories: [], decisions: [], budget: 4000 }), { text: null, sources: [] });
134});
135
136test("search reads one workspace, and only what a reader may see", () => {
137 const member = { workspace: "acme", member: true, full: true, visible: new Set<string>() };
138 const outsider = { workspace: "acme", member: false, full: false, visible: new Set(["site"]) };
139 assert.deepEqual(indexFilter(member, {}), { workspace: "acme" });
140 assert.deepEqual(indexFilter(outsider, { project: "site", kinds: ["memory", "doc"] }), { workspace: "acme", private: false, project: "site", kind: { $in: ["doc"] } });
141 assert.ok(!(allowedKinds(outsider) ?? []).includes("memory"));
142 const row = { workspace: "acme", kind: "doc", project: "site", private: false };
143 assert.ok(readable(row, outsider));
144 assert.ok(!readable({ ...row, workspace: "other" }, member), "never another workspace");
145 assert.ok(!readable({ ...row, project: "billing", private: true }, outsider), "a private project not listed for them");
146 assert.ok(!readable({ ...row, kind: "memory" }, outsider), "memory is for members");
147 assert.ok(!readable({ ...row, project: "made-private-since" }, outsider));
148 assert.ok(readable({ ...row, kind: "memory", private: true }, member));
149});
150
151test("a member with no base permission sees only the private projects granted to them", () => {
152 const user = { id: "u", username: "u", kind: "user" as const, workspaces: [{ slug: "acme", role: "member" as const, base_permission: "none" as const }], grants: [{ repo_id: "repo_api", workspace: "acme", role: "read" as const }] };
153 assert.equal(granted(user, { id: "", namespace: "acme", isPrivate: true }), null, "does not read every repository");
154 // What the projects service lists for them: public ones, and the one granted.
155 const reader = { workspace: "acme", member: true, full: false, visible: new Set(["site", "api"]), privateVisible: true, repos: new Set(["acme/site", "acme/api"]) };
156 const row = { workspace: "acme", kind: "issue", project: "api", private: true };
157 assert.ok(readable(row, reader), "the granted private project");
158 assert.ok(!readable({ ...row, project: "billing" }, reader), "another private project");
159 assert.ok(!readable({ ...row, project: "" }, reader), "a private row with no project");
160 assert.ok(readable({ ...row, project: "site", private: false }, reader));
161 assert.ok(readable({ ...row, kind: "memory", project: "" }, reader), "workspace memory is for every member");
162 assert.ok(!readable({ ...row, kind: "memory", project: "billing" }, reader));
163 assert.ok(memoryReadable(null, reader));
164 assert.ok(memoryReadable({ namespace: "Acme", name: "API" }, reader));
165 assert.ok(!memoryReadable({ namespace: "acme", name: "billing" }, reader));
166 assert.ok(!memoryReadable(null, { ...reader, member: false }), "memory is for members");
167 assert.deepEqual(indexFilter(reader, {}), { workspace: "acme" }, "private rows are checked one by one");
168 assert.equal(indexFilter({ ...reader, member: false, privateVisible: false }, {}).private, false);
169});
170
171test("the hub's counts are over what the viewer may read", () => {
172 const rows = [
173 { kind: "project", project: "site", private: 0, n: 1 },
174 { kind: "project", project: "api", private: 1, n: 1 },
175 { kind: "project", project: "billing", private: 1, n: 1 },
176 { kind: "doc", project: "billing", private: 1, n: 7 },
177 { kind: "language", project: null, private: 0, n: 3 },
178 ];
179 const full = { workspace: "acme", member: true, full: true, visible: new Set<string>() };
180 assert.deepEqual(countVisible(rows, full), { project: 3, doc: 7, language: 3 });
181 const none = { workspace: "acme", member: true, full: false, visible: new Set(["site", "api"]), privateVisible: true };
182 assert.deepEqual(countVisible(rows, none), { project: 2, language: 3 }, "billing is not theirs");
183});
184
185test("an agent run is told only what the person it acts for may read", () => {
186 const web = { namespace: "acme", name: "web" };
187 const workspaceMemory = { scope: "workspace", repo: null };
188 const webMemory = { scope: "project", repo: web };
189 const billingMemory = { scope: "project", repo: { namespace: "acme", name: "billing" } };
190 // The workspace's own step: everything.
191 assert.ok(runMemoryReadable(workspaceMemory, null, "acme/web"));
192 assert.ok(projectReadable("billing", null));
193 // A member who reads everything.
194 const member = { workspace: "acme", member: true, full: true, visible: new Set<string>() };
195 assert.ok(runMemoryReadable(workspaceMemory, member, "acme/web"));
196 assert.ok(runMemoryReadable(billingMemory, member, "acme/web"));
197 // An outside collaborator with Write on acme/web.
198 const outside = { workspace: "acme", member: false, full: false, visible: new Set(["web", "site"]), repos: new Set(["acme/web", "acme/site"]) };
199 assert.ok(!runMemoryReadable(workspaceMemory, outside, "acme/web"), "never the workspace's memory");
200 assert.ok(runMemoryReadable(webMemory, outside, "Acme/Web"), "the project's memory");
201 assert.ok(!runMemoryReadable(billingMemory, outside, "acme/web"));
202 assert.ok(!runMemoryReadable({ scope: "project", repo: { namespace: "acme", name: "site" } }, outside, "acme/web"), "only the run's own project");
203 assert.ok(projectReadable("site", outside));
204 assert.ok(!projectReadable("billing", outside), "a dependency they cannot read is not named");
205});
206
207test("semantic hits come first, without repeats", () => {
208 const hit = (id: string, score: number) => ({ kind: "doc" as const, id, title: id, snippet: "", project: null, url: null, score, source: "doc", by: null, updatedAt: null });
209 assert.deepEqual(
210 merge([hit("a", 0.5), hit("b", 0.9)], [hit("a", 0.2), hit("c", 0.2)], 10).map((h) => h.id),
211 ["b", "a", "c"],
212 );
213 assert.equal(merge([hit("a", 1)], [hit("b", 1)], 1).length, 1);
214});