mesh/merge-gate head 48f0662fdea2 merged into trunk 5ce2d07c7f47 — pass: builds gitea → novox; no bus step; every machine composes with the change as it di…
mesh/repo-check head 48f0662fdea2 merged into trunk 5ce2d07c7f47 — pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-…
mesh/delivery delivered
The tools' token reached everything the administrator may do, so their boundary rested on their own guards. The gitea module now makes a plain account, mesh-tools, with the admin login, gives it write on code, issues and pull requests of every novox repository through one team, and mints the tools' token for it; the admin's old token goes only after the new one is kept. The Go forge bundle sends that token, never the admin login. The forge shows protection rules only to a repository administrator, so every check reads a branch's protection from the branch. mesh-admin stays a ready author, so marks made before the move keep counting. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
53 lines
3.5 KiB
TypeScript
53 lines
3.5 KiB
TypeScript
// The forge's tools act as an account of their own, not as the site administrator (novox/hq ADR 0365 §2,
|
|
// issue 469).
|
|
//
|
|
// The administrator's token reached everything the forge holds, so the tools' boundary rested on their own guards
|
|
// (guard.ts): a protection edit, say, the forge itself would have taken. Now the tools' token is minted for a plain
|
|
// account, `mesh-tools`, which is no administrator and writes to the mesh's repositories through one team of the
|
|
// mesh's organisation:
|
|
//
|
|
// - **the account**: made by the module with the administrator's login (that login sets the forge up and does
|
|
// nothing else), never an administrator — one found made an administrator is made a plain account again. Its
|
|
// password is random, sent once and kept nowhere: the account is reached only by the tokens the administrator
|
|
// mints for it (gitea lets an administrator mint for another account: `reqSelfOrAdmin`).
|
|
// - **the team**: `mesh-tools` in the organisation `novox`, on every repository of it, writing code (pushes to a
|
|
// branch, commit statuses, merges), issues (notes, comments, labels) and pull requests. No administration of a
|
|
// repository, so the forge refuses the account a protection edit, a repository's settings and its deletion;
|
|
// no repository made in the organisation, since its maker would be that repository's administrator.
|
|
//
|
|
// Every step is idempotent (GiteaAdmin's, client.ts), and run with the administrator's login before each mint of the tools' token, so a team
|
|
// or a membership lost behind the module's back is made again at the next mint.
|
|
|
|
/** The account the tools act as. */
|
|
export const TOOLS_ACCOUNT = "mesh-tools";
|
|
/** The organisation whose repositories are the mesh's, and the team through which the tools' account writes there. */
|
|
export const TOOLS_ORG = "novox";
|
|
export const TOOLS_TEAM = "mesh-tools";
|
|
|
|
/** What the team grants: write on code, issues and pull requests of every repository of the organisation, and no more. */
|
|
export const TOOLS_TEAM_UNITS = {
|
|
permission: "write",
|
|
units_map: { "repo.code": "write", "repo.issues": "write", "repo.pulls": "write" },
|
|
includes_all_repositories: true,
|
|
can_create_org_repo: false,
|
|
} as const;
|
|
|
|
/** One call to the forge's API with the administrator's login: a path under /api/v1, the status and body answered. */
|
|
export type AdminCall = (method: string, path: string, body?: unknown) => Promise<{ status: number; body: any }>;
|
|
|
|
/** What setting the account up needs of the forge's admin API: GiteaAdmin's own (client.ts), shared with the
|
|
* package provisioner, so a create race (422) and a user made by hand are handled once. */
|
|
export interface ToolsForge {
|
|
ensureUser(username: string, password: string, email: string, plain?: boolean): Promise<void>;
|
|
ensureTeamWith(org: string, team: string, units: Record<string, unknown>): Promise<number>;
|
|
addUserToTeam(teamId: number, username: string): Promise<void>;
|
|
}
|
|
|
|
/** Make the tools' account, plain, a member of its team on every repository of the organisation. Its password is
|
|
* set afresh each time and kept nowhere: the account is reached only by the tokens the administrator mints. */
|
|
export async function setUpToolsAccount(forge: ToolsForge, account = TOOLS_ACCOUNT, password: () => string): Promise<void> {
|
|
await forge.ensureUser(account, password(), `${account}@noreply.localhost`, true);
|
|
const team = await forge.ensureTeamWith(TOOLS_ORG, TOOLS_TEAM, { name: TOOLS_TEAM, ...TOOLS_TEAM_UNITS });
|
|
await forge.addUserToTeam(team, account);
|
|
}
|