diff --git a/AGENTS.md b/AGENTS.md index 216f602d..d12ff51e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -59,8 +59,10 @@ PORT=1xxxx AGENT_MODE=true pnpm dev ``` `AGENT_MODE` (development only) signs in a fake administrator with every permission and disables the auth redirects between -`/login` and `/dashboard`, so open `/dashboard/...` or `/login` directly. Data still comes from `BACKEND_URL` (default -`http://localhost:3000`). When changing auth or those redirects, also verify the normal flow with `AGENT_MODE=false`. +`/login` and `/dashboard`, so open `/dashboard/...` or `/login` directly. It has no access token, so the backend +(`BACKEND_URL`, default `http://localhost:3000`) refuses its calls: pages that read the backend show their error state, +and People serves its sample data. To see real backend data, sign in through the IdP with `AGENT_MODE=false`. When +changing auth or those redirects, also verify the normal flow with `AGENT_MODE=false`. > [!IMPORTANT] > Never run a destructive action on more than one row unless the prompt explicitly asks for it; otherwise ask the user diff --git a/README.md b/README.md index 4996fc63..afedd772 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ Set the following runtime variables (the build does not need secrets): | `SESSION_SECRET` | Independent random secret of at least 32 characters, encrypting the 10-minute state/nonce/PKCE cookie. Rotation invalidates pending login attempts. | | `AGENT_PERMISSIONS` | Development only. Comma-separated permissions for the agent-mode user (default: all). `people:self` alone previews the restricted People view. | | `AGENT_PERSON` | Development only. The agent-mode user's People record (default `P-0007`, Direttivo and President). `P-0098` is a Capo Admin and the Vice-president, `P-0034` the IT Team Lead, `P-0115` an IT member, `P-0045` has a fee receipt to review, `P-0021` a rejected one; `match:P-0115` previews the first sign-in, `none` a sign-in without a record. Sending an invite prints its link in the dev server's console. | -| `AGENT_MODE` | Fake all-permission user in development only. Backend must be a local fake or local legacy test instance. Production ignores this mode. | +| `AGENT_MODE` | Fake all-permission user in development only. It has no access token, so the backend refuses its calls; People serves sample data. Production ignores this mode. | | `PORT` | Local listening port (3001 by default); use a port ≥10000 for agent previews. | | `TEST_REDIS_URL` | Opt-in disposable Redis for integration tests. Tests create/delete only UUID session keys. | diff --git a/src/features/dashboard/overview.functions.ts b/src/features/dashboard/overview.functions.ts index bdf746a8..9973b428 100644 --- a/src/features/dashboard/overview.functions.ts +++ b/src/features/dashboard/overview.functions.ts @@ -46,7 +46,7 @@ export const getOverviewCounts = createServerFn() /** * `forbidden` without `tg:users:read`; `unavailable` while the backend predates `tg.users.getCreationHistory` or refuses - * the call as unauthenticated (agent mode's anonymous path), so the Overview shows the total without the chart instead + * the call as unauthenticated (agent mode has no token), so the Overview shows the total without the chart instead * of an error. */ export type TelegramUserHistory = @@ -98,8 +98,8 @@ export const getTelegramUserHistory = createServerFn() if (result.error !== null) throw new Error(result.error) return { status: "ok", days: result.days, today: romeToday() } } catch (error) { - // NOT_FOUND: a backend that predates the procedure. UNAUTHORIZED: the procedure is token-only, and agent mode - // (development) calls the backend anonymously. Either way the Overview shows the total without the chart. + // NOT_FOUND: a backend that predates the procedure. UNAUTHORIZED: agent mode (development) has no token. Either + // way the Overview shows the total without the chart. if ( error instanceof TRPCClientError && (error.data?.code === "NOT_FOUND" || error.data?.code === "UNAUTHORIZED") diff --git a/src/features/people/people.contract.ts b/src/features/people/people.contract.ts index 55d43982..a8f508e4 100644 --- a/src/features/people/people.contract.ts +++ b/src/features/people/people.contract.ts @@ -1,8 +1,8 @@ import { z } from "zod" /** - * The `people.*` backend contract (docs/people/contract.md). The backend doesn't serve it yet: agent mode answers from - * `people.fake.server.ts`, and real responses are parsed with these schemas before they reach a page. + * The `people.*` backend contract (docs/people/contract.md), served by the backend's `src/people/router.ts`. Agent mode + * answers from `people.fake.server.ts`; real responses are parsed with these schemas before they reach a page. */ export const TEAM_IDS = ["it", "hr", "design", "intl", "events"] as const @@ -387,7 +387,7 @@ export type ImportPreview = z.infer export const capoAreaSchema = z.object({ scope: z.string(), - // `admins` is null while the backend does not know a group's Telegram admins. + // `admins` is null until the bot has reported the group's Telegram admins. groups: z.array(groupSchema.extend({ admins: z.number().int().nullable() })), admins: z.array(personRowSchema), requests: z.array(z.object({ id: z.string(), person: personRefSchema, group: groupSchema, requestedOn: z.string() })), diff --git a/src/features/telegram/grants.functions.ts b/src/features/telegram/grants.functions.ts index a9461098..1ffb96a2 100644 --- a/src/features/telegram/grants.functions.ts +++ b/src/features/telegram/grants.functions.ts @@ -23,7 +23,7 @@ const grantInput = z }) .refine(({ since, until }) => until > since, { message: "The grant end must be after its start.", path: ["until"] }) -// The backend takes the author from the access token; actor fields are only read from legacy callers. +// The backend takes the author from the access token. export const createTelegramGrant = createServerFn({ method: "POST" }) .middleware([grantsWriteMiddleware]) .validator(grantInput) diff --git a/src/features/telegram/user-detail/profile.tsx b/src/features/telegram/user-detail/profile.tsx index 0c4d76bf..efe381bd 100644 --- a/src/features/telegram/user-detail/profile.tsx +++ b/src/features/telegram/user-detail/profile.tsx @@ -292,7 +292,6 @@ type AuditActor = { name: string; title?: string } function auditActor(audit: Audit): AuditActor { if (audit.admin) return { name: telegramUserName(audit.admin) } if (audit.actorTgId !== null) return { name: `User ${audit.actorTgId}` } - if (audit.adminId !== null) return { name: `User ${audit.adminId}` } if (audit.actorSub) { return { name: audit.client === "dashboard" ? "Dashboard admin" : "PoliNetwork account", diff --git a/src/server/backend.server.ts b/src/server/backend.server.ts index 528302d5..d6bacaa4 100644 --- a/src/server/backend.server.ts +++ b/src/server/backend.server.ts @@ -13,7 +13,7 @@ import { env } from "@/env" /** * A backend client for one request, carrying the signed-in user's access token (RFC v3 §11): the backend verifies it - * and enforces the user's permissions. `null` only in agent mode, which uses the backend's legacy anonymous path. + * and enforces the user's permissions. `null` only in agent mode, which has no token: the backend refuses its calls. */ export function createBackendClient(accessToken: string | null) { return createTRPCClient({ links: backendLinks(accessToken) })