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

The forge's own repository is never the target of a new forge's first ask #11427

Merged opened this

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

Merged

scout/forge:scout/11417-the-forge-s-own-repository-is-never-the → main 3 commits fork 3 threads · 3 unresolved

  • Merged. scout/11417-the-forge-s-own-repository-is-never-the was merged into main as f866545.

Review

Sign in to review this change.
3 files changed +33 −2
src/actions/first_ask.ts +10 −1
@@ -11,9 +11,10 @@ import type { User } from "../core/users.ts";
1111 import { err, ok, type Result } from "../kernel/result.ts";
1212 import { newAppFromSentence, NEW_APP_ERRORS } from "./apps.ts";
1313 import { updateRepoSettings } from "./repos.ts";
14 import { NOT_TOLD_WORDS, SAY_SOMETHING_WORDS } from "./tell_words.ts";
14 import { FORGE_REPO_WORDS, NOT_TOLD_WORDS, SAY_SOMETHING_WORDS } from "./tell_words.ts";
1515 import { FROM_WORDS, TOO_LONG_WORDS, tell, type TellError, type Told } from "./tell.ts";
1616 import { parseFrom } from "./tell_from.ts";
17 import { deriveAppName } from "../web/pages/apps-name.ts";
1718 import { requireVerified } from "./verify.ts";
1819 import { MAX_BODY } from "../core/issues.ts";
1920 import { DIRECTION_NOTE } from "../crew/direct.ts";
@@ -33,6 +34,12 @@ export type FirstAskError = { readonly words: string };
3334 /** The note a person is given when the app was made and filed but autonomy could not be turned on. */
3435 export const AUTONOMY_FAILED_WORDS = "The app is made, but the crew could not be set to start on it by itself. Turn on autonomy in its settings.";
3536
37 /** Whether the app this sentence would be named from is the forge's own repository. */
38 function resolvesToForgeRepo(app: App, actor: User, sentence: string): boolean {
39 const name = deriveAppName(sentence);
40 return name !== undefined && app.config.forgeRepo?.toLowerCase() === `${actor.username}/${name}`.toLowerCase();
41 }
42
3643 export async function firstAsk(
3744 app: App,
3845 actor: User | undefined,
@@ -49,6 +56,8 @@ export async function firstAsk(
4956 if (actor === undefined || actor.kind === "org") return err({ words: DIRECTION_NOTE });
5057 if (!requireVerified(app, actor).ok) return err({ words: "Verify your email first." });
5158 if (from !== null && typeof parseFrom(from) !== "string") return err({ words: FROM_WORDS });
outdated

Runs after the app exists; unreachable now and untested. Refuse before newAppFromSentence, with words from tell_words.ts, and receipt it.

59 // The forge's own repository is never an ask's target: refused before any app exists.
60 if (resolvesToForgeRepo(app, actor, text)) return err({ words: FORGE_REPO_WORDS });
5261 // An empty description: the issue the tell files is the one issue of the ask.
5362 const made = await newAppFromSentence(app, actor, { sentence: text, description: "" });
5463 if (!made.ok) return err({ words: NEW_APP_ERRORS[made.code] ?? NOT_TOLD_WORDS });
src/actions/tell_words.ts +3
@@ -15,3 +15,6 @@ export const SAY_SOMETHING_WORDS = "Say something first.";
1515
1616 /** A report that did not reach the issue list. */
1717 export const NOT_SENT_WORDS = "That could not be sent.";
18
19 /** A first ask whose app would be the forge's own repository. */
20 export const FORGE_REPO_WORDS = "That name is this forge's own repository. Ask again in other words and it makes a new app.";
test/first-ask-new-app.test.ts +20 −1
@@ -7,12 +7,13 @@
77 import { afterAll, beforeAll, expect, test } from "bun:test";
88 import { listIssues, MAX_BODY } from "../src/core/issues.ts";
99 import { TOO_LONG_WORDS } from "../src/actions/tell.ts";
10 import { SAY_SOMETHING_WORDS } from "../src/actions/tell_words.ts";
10 import { FORGE_REPO_WORDS, SAY_SOMETHING_WORDS } from "../src/actions/tell_words.ts";
1111 import { listReposOwnedBy } from "../src/core/repos.ts";
1212 import { recentThoughts } from "../src/core/thoughts.ts";
1313 import { fundingOf, keepFunding } from "../src/spawn/funding.ts";
1414 import { crewCanRun } from "../src/web/studio/crew_key.ts";
1515 import { getUserByName } from "../src/core/users.ts";
16 import { deriveAppName } from "../src/web/pages/apps-name.ts";
1617 import { claimedForge, type ClaimedForge } from "./helpers/claimed-forge.ts";
1718 import { quietLogs } from "./helpers/globals.ts";
1819
@@ -91,3 +92,21 @@ test("an over-long ask makes no app and says so", async () => {
9192 expect(flashOf(res)).toContain(TOO_LONG_WORDS);
9293 expect(listReposOwnedBy(db, getUserByName(db, "maria")!.id).length).toBe(before);
9394 });
95
96 test("an ask that would name the forge's own repository is refused and makes no app", async () => {
97 const db = forge.child.db;

Asserts the opposite of box 1/2: should expect a refusal with the plain words, no repo made, and a receipt.

98 const maria = getUserByName(db, "maria")!;
99 const ask = "Quokka census tracker";
100 const before = forge.child.config.forgeRepo;
101 forge.child.config.forgeRepo = `maria/${deriveAppName(ask)!}`;
102 const count = listReposOwnedBy(db, maria.id).length;
103 try {
104 const res = await forge.post(forge.owner, "/tell", { app: "new-app", text: ask });
105 expect(res.status).toBe(303);
106 expect(flashOf(res)).toContain(FORGE_REPO_WORDS);
107 expect(listReposOwnedBy(db, maria.id).length).toBe(count);
108 } finally {
109 if (before === undefined) delete forge.child.config.forgeRepo;
110 else forge.child.config.forgeRepo = before;
111 }
112 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs