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.

g1t.sh
305 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 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/**
2 * Who and where, for the artifacts service's folio code: the workspace by
Chat, artifacts and notifications no longer serve g1t's own agents. Direct messages and conversation invites take people only, and a space or folio is shared with people and teams. Chat stops waking agents, drops the RPCs agents posted, read, searched and handed off through, and stops answering buttons on agent cards; old cards keep their links. An agent's old messages still show, under its saved name or as "A deleted agent". Artifacts drops the folio and docs RPCs agents read and edited pages with, and an old agent member of a space can still be removed. Notifications drop "an agent is waiting on you". A deployment no longer trusts a pull request with secrets just because g1t's agent opened it, and the sign-up card no longer promises a team of agents.3 * slug, its people and teams, how member keys show, and the
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.4 * spaces with a person's role in each. Cached per request (one instance
5 * per request). Docs' page code (src/index.ts, `Docs`) keeps its own copy
6 * of these until it is removed.
7 */
8import {
9 fail,
10 identityClient,
11 newId,
12 ok,
13 parsePrincipalKey,
14 principalKey,
15 type DocAgentMode,
16 type DocRole,
17 type DocSpace,
18 type DocSpaceKind,
19 type Member,
20 type MemberProfile,
21 type Principal,
22 type Result,
23 type ServiceBinding,
24 type User,
25 type Viewer,
26 type Workspace,
27} from "@g1t/contracts";
28
29import { roleOf, type Person, type SpaceRules } from "./access.ts";
30import { freeSlug } from "./slugs.ts";
31
Chat, artifacts and notifications no longer serve g1t's own agents. Direct messages and conversation invites take people only, and a space or folio is shared with people and teams. Chat stops waking agents, drops the RPCs agents posted, read, searched and handed off through, and stops answering buttons on agent cards; old cards keep their links. An agent's old messages still show, under its saved name or as "A deleted agent". Artifacts drops the folio and docs RPCs agents read and edited pages with, and an old agent member of a space can still be removed. Notifications drop "an agent is waiting on you". A deployment no longer trusts a pull request with secrets just because g1t's agent opened it, and the sign-up card no longer promises a team of agents.32export type WhoEnv = { DB: D1Database; IDENTITY: ServiceBinding };
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.33
34export type SpaceRow = {
35 id: string;
36 workspace_id: string;
37 slug: string;
38 name: string;
39 description: string | null;
40 icon: string | null;
41 kind: DocSpaceKind;
42 team: string | null;
43 default_role: DocRole | null;
44 agent_mode: DocAgentMode;
45 /** 1: people with edit access may share what is in it (migration 0005). */
46 editors_can_share: number;
47 is_default: number;
48 created_by: string;
49 created_at: string;
50 archived_at: string | null;
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r951 /**
52 * Set when the space was deleted (migration 0007). The row stays, so the
53 * trash's rows that point at it keep their space's name and rules (whoever
54 * could edit them still restores them) and General, once deleted, is not
55 * made again; listings leave deleted spaces out.
56 */
57 deleted_at: string | null;
58 deleted_by: string | null;
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.59};
60
61/** A space, with who is in it and the viewer's role (null: they can't read it). */
62export type Space = { row: SpaceRow; members: { principal: string; role: DocRole }[]; projects: string[]; role: DocRole | null };
63
64export const now = () => new Date().toISOString();
65
A doc opens at once: the page carries the document's saved state, so the editor mounts without waiting for its live room, which connects in the background and sends only what changed, with a quiet dot that turns green when it's synced; edits made before then are kept and sent once. The editor's code is preloaded from the page and fetched early when you hover a doc, the app's shared code ships in a few chunks instead of a hundred small ones, socket tickets come with the page instead of a round trip, and the artifacts service answers a page, a sidebar and a room in parallel lookups instead of a dozen in a row, reporting its database round trips in Server-Timing. The artifacts guide says how a doc opens, and CONTRIBUTING's Speed section has the numbers.66/**
67 * Kept across requests in this isolate: the workspace behind a slug for
68 * half a minute (identity answers it in ~80 ms, and every folio call
69 * starts with it), and which workspaces already have their General
70 * space (made once, never unmade). A rename reaches the old slug within
71 * that half minute, which is what the site's own redirect allows.
72 */
73const WORKSPACE_TTL_MS = 30_000;
74const workspaces = new Map<string, { at: number; value: Promise<Workspace | null> }>();
75const defaultsMade = new Set<string>();
76
77/** For tests: forgets everything kept across requests. */
78export function forgetKept(): void {
79 workspaces.clear();
80 defaultsMade.clear();
81}
82
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.83export function rulesOf(space: Pick<Space, "row" | "members">): SpaceRules {
84 return { kind: space.row.kind, team: space.row.team, default_role: space.row.default_role, members: space.members };
85}
86
87export function isMember(viewer: Viewer, workspace: string): boolean {
88 return !!viewer?.workspaces?.some((m) => m.slug === String(workspace ?? "").toLowerCase());
89}
90
91export function userKey(viewer: Pick<User, "id">): string {
92 return principalKey({ kind: "user", id: viewer.id });
93}
94
95export class Who {
96 private readonly workspaces = new Map<string, Promise<Workspace | null>>();
97 private readonly people = new Map<string, Promise<Map<string, Member>>>();
98 private readonly teams = new Map<string, Promise<Map<string, Set<string>>>>();
99 private readonly spaces = new Map<string, Promise<Omit<Space, "role">[]>>();
100 readonly usernames = new Map<string, string>();
101
102 constructor(private readonly env: WhoEnv) {}
103
104 workspace(slug: string): Promise<Workspace | null> {
105 const key = String(slug ?? "").toLowerCase();
106 let found = this.workspaces.get(key);
107 if (!found) {
A doc opens at once: the page carries the document's saved state, so the editor mounts without waiting for its live room, which connects in the background and sends only what changed, with a quiet dot that turns green when it's synced; edits made before then are kept and sent once. The editor's code is preloaded from the page and fetched early when you hover a doc, the app's shared code ships in a few chunks instead of a hundred small ones, socket tickets come with the page instead of a round trip, and the artifacts service answers a page, a sidebar and a room in parallel lookups instead of a dozen in a row, reporting its database round trips in Server-Timing. The artifacts guide says how a doc opens, and CONTRIBUTING's Speed section has the numbers.108 const kept = workspaces.get(key);
109 if (kept && Date.now() - kept.at < WORKSPACE_TTL_MS) found = kept.value;
110 else {
111 found = identityClient(this.env.IDENTITY).getWorkspace(key).catch(() => null);
112 workspaces.set(key, { at: Date.now(), value: found });
113 // Only an answer is kept: a miss or a failure is asked again next time.
114 void found.then((w) => {
115 if (!w) workspaces.delete(key);
116 });
117 }
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.118 this.workspaces.set(key, found);
119 }
120 return found;
121 }
122
123 /** The workspace acting for itself: how this service asks identity about its members. */
124 actor(workspace: Workspace): User {
125 return { id: workspace.id, username: workspace.slug, kind: "workspace", verified: true, workspaces: [{ slug: workspace.slug, role: "member" }] };
126 }
127
128 /** The workspace's people by username (lowercased). */
129 members(workspace: Workspace): Promise<Map<string, Member>> {
130 let found = this.people.get(workspace.id);
131 if (!found) {
132 found = identityClient(this.env.IDENTITY)
133 .listMembers(workspace.slug, this.actor(workspace))
134 .then((r) => new Map(r.ok ? r.value.map((m) => [m.username.toLowerCase(), m]) : []))
135 .catch(() => new Map<string, Member>());
136 this.people.set(workspace.id, found);
137 }
138 return found;
139 }
140
141 /** Each member's teams (slugs, lowercased), by username. */
142 teamsOf(workspace: Workspace): Promise<Map<string, Set<string>>> {
143 let found = this.teams.get(workspace.id);
144 if (!found) {
145 found = identityClient(this.env.IDENTITY)
146 .teamMemberships(this.actor(workspace), workspace.slug)
147 .then((r) => new Map(r.ok ? r.value.map((m) => [m.username.toLowerCase(), new Set(m.teams.map((t) => t.slug.toLowerCase()))]) : []))
148 .catch(() => new Map<string, Set<string>>());
149 this.teams.set(workspace.id, found);
150 }
151 return found;
152 }
153
154 async nameUsers(ids: string[]): Promise<void> {
155 const unnamed = [...new Set(ids)].filter((id) => !this.usernames.has(id));
156 if (!unnamed.length) return;
157 const named = await identityClient(this.env.IDENTITY)
158 .usernames(unnamed)
159 .catch(() => ({}) as Record<string, string>);
160 for (const [id, username] of Object.entries(named)) this.usernames.set(id, username);
161 }
162
Chat, artifacts and notifications no longer serve g1t's own agents. Direct messages and conversation invites take people only, and a space or folio is shared with people and teams. Chat stops waking agents, drops the RPCs agents posted, read, searched and handed off through, and stops answering buttons on agent cards; old cards keep their links. An agent's old messages still show, under its saved name or as "A deleted agent". Artifacts drops the folio and docs RPCs agents read and edited pages with, and an old agent member of a space can still be removed. Notifications drop "an agent is waiting on you". A deployment no longer trusts a pull request with secrets just because g1t's agent opened it, and the sign-up card no longer promises a team of agents.163 /**
164 * How member keys show. An agent's key (workspaces no longer have
165 * agents) shows as a deleted agent; anything else that isn't a person
166 * shows as g1t.
167 */
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.168 async profiles(workspace: Workspace, keys: string[]): Promise<Map<string, MemberProfile>> {
169 const principals = [...new Set(keys)].map((k) => parsePrincipalKey(k)).filter((p): p is Principal => !!p);
170 const userIds = principals.filter((p) => p.kind === "user").map((p) => p.id);
Chat, artifacts and notifications no longer serve g1t's own agents. Direct messages and conversation invites take people only, and a space or folio is shared with people and teams. Chat stops waking agents, drops the RPCs agents posted, read, searched and handed off through, and stops answering buttons on agent cards; old cards keep their links. An agent's old messages still show, under its saved name or as "A deleted agent". Artifacts drops the folio and docs RPCs agents read and edited pages with, and an old agent member of a space can still be removed. Notifications drop "an agent is waiting on you". A deployment no longer trusts a pull request with secrets just because g1t's agent opened it, and the sign-up card no longer promises a team of agents.171 const [, people] = await Promise.all([this.nameUsers(userIds), userIds.length ? this.members(workspace) : new Map<string, Member>()]);
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.172 const out = new Map<string, MemberProfile>();
173 for (const p of principals) {
174 if (p.kind === "user") {
175 const username = this.usernames.get(p.id) ?? null;
176 const person = username ? people.get(username.toLowerCase()) : undefined;
177 out.set(principalKey(p), { ...p, name: username ?? "ghost", display_name: person?.name || username || "Former member", avatar: person?.avatar ?? null, role: null, title: null, avatar_seed: null });
178 } else {
Chat, artifacts and notifications no longer serve g1t's own agents. Direct messages and conversation invites take people only, and a space or folio is shared with people and teams. Chat stops waking agents, drops the RPCs agents posted, read, searched and handed off through, and stops answering buttons on agent cards; old cards keep their links. An agent's old messages still show, under its saved name or as "A deleted agent". Artifacts drops the folio and docs RPCs agents read and edited pages with, and an old agent member of a space can still be removed. Notifications drop "an agent is waiting on you". A deployment no longer trusts a pull request with secrets just because g1t's agent opened it, and the sign-up card no longer promises a team of agents.179 out.set(principalKey(p), { ...p, name: p.id, display_name: "A deleted agent", avatar: null, role: null, title: null, avatar_seed: null, deleted: true });
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.180 }
181 }
182 for (const key of keys) {
183 if (!out.has(key)) out.set(key, { kind: "user", id: key, name: "g1t", display_name: "g1t", avatar: null, role: null, title: null, avatar_seed: null });
184 }
185 return out;
186 }
187
188 async viewerWorkspace(slug: string, viewer: Viewer): Promise<Result<Workspace>> {
189 if (!viewer?.id) return fail("unauthenticated", "Sign in to use Artifacts.");
190 if (!slug || !isMember(viewer, slug)) return fail("forbidden", "Only members of a workspace can use its Artifacts.");
191 const workspace = await this.workspace(slug);
192 return workspace ? ok(workspace) : fail("not_found", "No such workspace.");
193 }
194
195 viewerOwner(viewer: User, slug: string): boolean {
196 return !!viewer.workspaces?.some((m) => m.slug === slug.toLowerCase() && m.role === "owner");
197 }
198
199 /** A person as access sees them: their teams, and whether they own the workspace. */
200 async personOf(workspace: Workspace, user: Pick<User, "id" | "username">, owner: boolean): Promise<Person> {
201 const teams = (await this.teamsOf(workspace)).get(String(user.username ?? "").toLowerCase()) ?? new Set<string>();
202 return { user_id: user.id, owner, teams };
203 }
204
205 /** The viewer as access sees them. */
206 viewerPerson(workspace: Workspace, viewer: User): Promise<Person> {
207 return this.personOf(workspace, viewer, this.viewerOwner(viewer, workspace.slug));
208 }
209
210 /** People by user id as access sees them: members' teams and ownership; anyone else reads nothing. */
211 async peopleByIds(workspace: Workspace, ids: string[]): Promise<Person[]> {
212 const unique = [...new Set(ids.map(String))].slice(0, 200);
213 await this.nameUsers(unique);
214 const [members, teams] = await Promise.all([this.members(workspace), this.teamsOf(workspace)]);
215 return unique.map((id) => {
216 const username = this.usernames.get(id)?.toLowerCase() ?? "";
217 const member = members.get(username);
218 return { user_id: member ? id : `outside:${id}`, owner: member?.role === "owner", teams: member ? (teams.get(username) ?? new Set()) : new Set() };
219 });
220 }
221
222 /** Every space in the workspace (archived too), with members and projects. */
223 allSpaces(workspace: Workspace): Promise<Omit<Space, "role">[]> {
224 let found = this.spaces.get(workspace.id);
225 if (!found) {
226 found = (async () => {
227 const db = this.env.DB;
228 const [spaces, members, projects] = await Promise.all([
229 db.prepare("SELECT * FROM spaces WHERE workspace_id = ? ORDER BY is_default DESC, name COLLATE NOCASE").bind(workspace.id).all<SpaceRow>(),
230 db
231 .prepare("SELECT m.space_id, m.principal, m.role FROM space_members m JOIN spaces s ON s.id = m.space_id WHERE s.workspace_id = ?")
232 .bind(workspace.id)
233 .all<{ space_id: string; principal: string; role: DocRole }>(),
234 db.prepare("SELECT p.space_id, p.repo FROM space_projects p JOIN spaces s ON s.id = p.space_id WHERE s.workspace_id = ?").bind(workspace.id).all<{ space_id: string; repo: string }>(),
235 ]);
236 return spaces.results.map((row) => ({
237 row,
238 members: members.results.filter((m) => m.space_id === row.id).map((m) => ({ principal: m.principal, role: m.role })),
239 projects: projects.results.filter((p) => p.space_id === row.id).map((p) => p.repo),
240 }));
241 })();
242 this.spaces.set(workspace.id, found);
243 }
244 return found;
245 }
246
247 /** Forget cached spaces after one changed. */
248 forgetSpaces(): void {
249 this.spaces.clear();
250 }
251
252 /** The spaces with `person`'s role in each (null: they can't read it). Archived spaces too. */
253 async spacesFor(workspace: Workspace, person: Person): Promise<Space[]> {
254 const spaces = await this.allSpaces(workspace);
255 return spaces.map((s) => ({ ...s, role: roleOf(rulesOf(s), person) }));
256 }
257
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9258 /**
259 * Makes the workspace's General space, once. A deleted General still
260 * has its row (`deleted_at` set), so it is never made again: a
261 * workspace may have no spaces.
262 */
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.263 async ensureDefault(workspace: Workspace, viewer: User): Promise<void> {
A doc opens at once: the page carries the document's saved state, so the editor mounts without waiting for its live room, which connects in the background and sends only what changed, with a quiet dot that turns green when it's synced; edits made before then are kept and sent once. The editor's code is preloaded from the page and fetched early when you hover a doc, the app's shared code ships in a few chunks instead of a hundred small ones, socket tickets come with the page instead of a round trip, and the artifacts service answers a page, a sidebar and a room in parallel lookups instead of a dozen in a row, reporting its database round trips in Server-Timing. The artifacts guide says how a doc opens, and CONTRIBUTING's Speed section has the numbers.264 if (defaultsMade.has(workspace.id)) return;
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.265 const db = this.env.DB;
266 const found = await db.prepare("SELECT id FROM spaces WHERE workspace_id = ? AND is_default = 1").bind(workspace.id).first<{ id: string }>();
A doc opens at once: the page carries the document's saved state, so the editor mounts without waiting for its live room, which connects in the background and sends only what changed, with a quiet dot that turns green when it's synced; edits made before then are kept and sent once. The editor's code is preloaded from the page and fetched early when you hover a doc, the app's shared code ships in a few chunks instead of a hundred small ones, socket tickets come with the page instead of a round trip, and the artifacts service answers a page, a sidebar and a room in parallel lookups instead of a dozen in a row, reporting its database round trips in Server-Timing. The artifacts guide says how a doc opens, and CONTRIBUTING's Speed section has the numbers.267 if (found) {
268 defaultsMade.add(workspace.id);
269 return;
270 }
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.271 const taken = new Set((await db.prepare("SELECT slug FROM spaces WHERE workspace_id = ?").bind(workspace.id).all<{ slug: string }>()).results.map((r) => r.slug));
272 await db
273 .prepare(
274 "INSERT OR IGNORE INTO spaces (id, workspace_id, slug, name, description, icon, kind, team, default_role, agent_mode, is_default, created_by, created_at) VALUES (?, ?, ?, 'General', 'Everything the whole workspace should know.', '📚', 'workspace', NULL, 'edit', 'suggest', 1, ?, ?)",
275 )
276 .bind(newId("spc"), workspace.id, freeSlug("general", taken), userKey(viewer), now())
277 .run();
A doc opens at once: the page carries the document's saved state, so the editor mounts without waiting for its live room, which connects in the background and sends only what changed, with a quiet dot that turns green when it's synced; edits made before then are kept and sent once. The editor's code is preloaded from the page and fetched early when you hover a doc, the app's shared code ships in a few chunks instead of a hundred small ones, socket tickets come with the page instead of a round trip, and the artifacts service answers a page, a sidebar and a room in parallel lookups instead of a dozen in a row, reporting its database round trips in Server-Timing. The artifacts guide says how a doc opens, and CONTRIBUTING's Speed section has the numbers.278 defaultsMade.add(workspace.id);
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.279 this.forgetSpaces();
280 }
281
282 toSpace(space: Space, pageCount = 0): DocSpace {
283 const created = parsePrincipalKey(space.row.created_by) ?? { kind: "user" as const, id: space.row.created_by };
284 return {
285 id: space.row.id,
286 workspace_id: space.row.workspace_id,
287 slug: space.row.slug,
288 name: space.row.name,
289 description: space.row.description,
290 icon: space.row.icon,
291 kind: space.row.kind,
292 team: space.row.team,
293 default_role: space.row.kind === "private" ? null : space.row.default_role,
294 agent_mode: space.row.agent_mode,
295 editors_can_share: !!space.row.editors_can_share,
296 is_default: !!space.row.is_default,
297 projects: space.projects,
298 created_by: created,
299 created_at: space.row.created_at,
300 archived_at: space.row.archived_at,
301 viewer_role: space.role ?? "view",
302 page_count: pageCount,
303 };
304 }
305}

This file's history is long; its oldest lines are credited to the oldest commit read.