// gitea's provisioner — the adapter that makes gitea a provider of the mesh // `npm-package-registry` interface. The reconcile loop, the contributions file, and reading the // mesh's minted password are the sdk harness's; this writes only the per-service half: how gitea // creates and removes a consumer's npm credential (novox/hq ADR 0048/0076). // // **A package registry seat is one per ecosystem (novox/hq ADR 0109).** gitea holds the npm seat // (ADR 0110). Adding cargo or PyPI is adding a provision — another `provides` entry, another // `receives` path and another registration below — not widening this one. `git`, which gitea also // provides, mints nothing and so registers nothing here: the mesh's own repositories are public, // and a clone credential is not yet decided (ADR 0111). // // The `npm-package-registry` interface: a consumer authenticates to the npm registry at // `/api/packages/novox/npm/` with basic auth, as `as` with the password the mesh minted, and can // read and write packages under the `@novox` scope. The registry's npm owner is the gitea org // `novox`; a consumer is a gitea *user* placed on that org's package team. // // **The user name and password are the mesh's, not the provisioner's (ADR 0048).** The mesh derives // the login and hands it to both ends, and mints the password. gitea creates a user under exactly // that login and sets exactly that password every run — so a rotation takes — and seals nothing: the // consumer already has its copy through the mesh's own channel. // // The admin calls run through GiteaAdmin (basic auth as the mesh's gitea admin), which is the // module's one boundary to the forge's admin API (see client.ts). import { runProvisioner, type Provision } from "@novox/mesh-sdk/provisioner"; import { GiteaAdmin } from "../client.js"; // The npm registry owner: a gitea org named `novox`, whose package team every consumer joins so it // can read and write packages under the `@novox` scope (ADR 0076). const ORG = "novox"; const PACKAGE_TEAM = "packages"; const gitea = GiteaAdmin.fromEnv(); // Where this registration's contributions land comes from $MESH_RECEIVES, never a path written // here: the mesh writes the file where the manifest's `receives` says, and a second copy of that // path in code would drift from it. One variable carries one path, so a second registration in this // module needs the mesh to say where each provision's file is — not yet possible, and not faked. runProvisioner("npm-package-registry", { async create(p: Provision): Promise { // The org and its package team are the same for every consumer; ensuring them per-create is // idempotent and needs no separate bootstrap step. await gitea.ensureOrg(ORG); const teamId = await gitea.ensureTeam(ORG, PACKAGE_TEAM, true); // The user carries the consumer's login and the mesh's minted password, set every run so a // rotation takes. Membership of the package team is what grants read+write on packages. // // The address is gitea's own convention for one that is not real: its email validation // requires a dotted domain, so `@localhost` was refused at create — the fault that had this // grant retrying for a day — while `@noreply.localhost` is the shape gitea itself gives // hidden addresses. await gitea.ensureUser(p.as, p.password, `${p.as}@noreply.localhost`); await gitea.addUserToTeam(teamId, p.as); }, async remove(p: { as: string }): Promise { await gitea.deleteUser(p.as); }, // Asked every minute by the harness: whether the backend still holds this consumer exactly as // the mesh gave it, so a login lost behind the provisioner's back is made again (novox/hq issue 120). async holds(p: Provision): Promise { return gitea.holdsTeamMember(ORG, PACKAGE_TEAM, p.as, p.password); }, });