Public: anyone can read this; the crew can change it.

The /__forge/ door says who of the crew is working on this app or preview now, as the pill does #7857

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

scout/forge:scout/7853-the-forge-door-says-who-of-the-crew-is-w → main 1 commit fork

  • Merged. scout/7853-the-forge-door-says-who-of-the-crew-is-w was merged into main as 83ebfcd.

Review

Sign in to review this change.
3 files changed +60 −2
src/apps/door.ts +10 −1
@@ -7,6 +7,7 @@
77 import { createHash } from "node:crypto";
88 import { escapeAttr, type PillWhere } from "./toolbar.ts";
99 import type { Served, Serving } from "./serving.ts";
10 import type { CrewNow } from "./crew.ts";
1011
1112 export type DoorFacts = {
1213 where: PillWhere;
@@ -24,6 +25,8 @@ export type DoorFacts = {
2425 ssh?: string | null;
2526 /** Open pulls a viewer may see, each with where to go: its preview or the forge. */
2627 pulls?: DoorPullLine[] | undefined;
28 /** Who of the crew is working here now, as the pill's crew line says it. */
29 crew?: CrewNow | undefined;
2730 };
2831
2932 /** How many pulls the door lists; one more read than this means there are others. */
@@ -108,6 +111,12 @@ function pullsHtml(pulls: DoorPullLine[] | undefined, morePulls: string | undefi
108111 return `<h2>Open pulls</h2><ul>${items.join("")}</ul>${more}`;
109112 }
110113
114 /** The pill's crew line: who is working on this, linked to that run on the forge. */
115 function crewHtml(crew: CrewNow | undefined): string {
116 if (crew === undefined) return "";
117 return `<p><a href="${escapeAttr(crew.url)}">${escapeAttr(`${crew.who} is working on this`)}</a></p>`;
118 }
119
111120 export function doorPage(facts: DoorFacts, head: boolean): Response {
112121 const { where, forgeUrl } = facts;
113122 const links = [link(forgeUrl, "Home")];
@@ -129,7 +138,7 @@ export function doorPage(facts: DoorFacts, head: boolean): Response {
129138 : `<p>Terminal:</p><pre><code>${escapeAttr(facts.ssh)}</code></pre>`;
130139 const html = `<!doctype html>
131140 <html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="robots" content="noindex"><title>${escapeAttr(where.app)}, ${escapeAttr(doorWhere(where))}</title><style>${STYLE}</style></head>
132 <body><main><h1>${escapeAttr(where.app)}</h1><p>${whereLine(where, forgeUrl)}</p>${servingHtml(facts)}${pullsHtml(facts.pulls, morePulls)}<ul>${links.join("")}</ul>${terminal}</main></body></html>
141 <body><main><h1>${escapeAttr(where.app)}</h1><p>${whereLine(where, forgeUrl)}</p>${crewHtml(facts.crew)}${servingHtml(facts)}${pullsHtml(facts.pulls, morePulls)}<ul>${links.join("")}</ul>${terminal}</main></body></html>
133142 `;
134143 return new Response(head ? null : html, {
135144 headers: {
src/apps/proxy.ts +9
@@ -910,11 +910,20 @@ export async function proxyApp(
910910 const pulls = timePhase(request, "pulls", () =>
911911 doorPulls(runtime, target, signed?.who),
912912 );
913 const crew = crewNow(
914 runtime.app.db,
915 runtime.options.forgeUrl,
916 target.app,
917 target.env,
918 signed?.who?.id,
919 request,
920 );
913921 const page = timePhase(request, "render", () =>
914922 doorPage(
915923 {
916924 where,
917925 pulls,
926 ...(crew === undefined ? {} : { crew }),
918927 ...(serving === undefined ? {} : { serving }),
919928 forgeUrl: runtime.options.forgeUrl,
920929 ...(ssh === undefined ? {} : { ssh }),
test/apps-door.test.ts +41 −1
@@ -12,6 +12,8 @@ import { signGrant, type AppTarget } from "../src/apps/proxy.ts";
1212 import { AppsRuntime } from "../src/apps/runtime.ts";
1313 import { getAppByRepo, type AppRecord } from "../src/core/apps.ts";
1414 import { createIssue } from "../src/core/issues.ts";
15 import { claimRun, createRun, finishRun } from "../src/core/runs.ts";
16 import { setSealed } from "../src/core/sealed.ts";
1517 import { createUser, getUserById } from "../src/core/users.ts";
1618 import { serve, type Servers } from "../src/http/server.ts";
1719 import { quietLogs } from "./helpers/globals.ts";
@@ -319,7 +321,7 @@ test("the door and the crew line name the phases they read, signed in, on produc
319321 [`notes--${pull}.${DOMAIN}`, grantFor(`pull-${pull}`)],
320322 ];
321323 const reads: Record<string, string[]> = {
322 "/__forge/": ["session", "app", "pull", "serving", "pulls", "render"],
324 "/__forge/": ["session", "app", "pull", "serving", "pulls", "working", "render"],
323325 "/__forge/crew": ["session", "app", "pull", "working", "side", "render"],
324326 };
325327 for (const [host, cookie] of hosts)
@@ -429,3 +431,41 @@ test("the door links a dormant preview to itself, and says so when more than fiv
429431 expect(html).toContain("More pulls are open on the forge.");
430432 expect((html.match(/ · check /g) ?? []).length).toBe(5);
431433 });
434
435 test("the door names the crew run working here, linked, to who may see it", async () => {
436 const bob = await createUser(app.db, {
437 origin: "seed",
438 trusted: true,
439 username: "scoutbot",
440 email: "[email protected]",
441 password: "a-good-passphrase",
442 });
443 if (!bob.ok) throw new Error(bob.error);
444 const repoId = record.repoId;
445 const open = createIssue(app.db, { repoId, authorId: alice, title: "o", kind: "pull" });
446 const sealed = createIssue(app.db, { repoId, authorId: alice, title: "s", kind: "issue" });
447 if (!open.ok || !sealed.ok) throw new Error("issue");
448 setSealed(app.db, sealed.value.id, true);
449 const target = { app: record, env: "production" } as AppTarget;
450 const cookie = `forge_grant_${record.id.slice(-8)}=${signGrant(dataDir, target, 60_000, Date.now(), { id: alice, name: "alice", role: "admin" })}`;
451 const start = (issueId: string) => {
452 const run = createRun(app.db, { repoId, kind: "agent", issueId, actorId: bob.value.id, reason: "assigned" });
453 claimRun(app.db, run.id);
454 return run;
455 };
456 const idle = await (await door(`notes.${DOMAIN}`, cookie)).text();
457 expect(idle).not.toContain("is working on this");
458 const hidden = start(sealed.value.id);
459 // A signed-in reader who may not see the sealed issue is not told of its run.
460 const other = signGrant(dataDir, target, 60_000, Date.now(), { id: bob.value.id, name: "scoutbot", role: "user" });
461 const otherCookie = `forge_grant_${record.id.slice(-8)}=${other}`;
462 expect(await (await door(`notes.${DOMAIN}`, otherCookie)).text()).not.toContain("is working on this");
463 expect(await (await door(`notes.${DOMAIN}`)).text()).not.toContain("is working on this");
464 const run = start(open.value.id);
465 const html = await (await door(`notes.${DOMAIN}`, cookie)).text();
466 expect(html).toContain(`<a href="${servers.url}/alice/notes/runs/${run.id}">scoutbot is working on this</a>`);
467 expect(html).not.toContain("<script");
468 finishRun(app.db, run.id, { state: "ok", summary: "d" });
469 finishRun(app.db, hidden.id, { state: "ok", summary: "d" });
470 expect(await (await door(`notes.${DOMAIN}`, cookie)).text()).not.toContain("is working on this");
471 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs