feat(registry)!: a forge namespace is a rule, not a charset #8
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/the-forge-namespace-is-a-rule"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The rule was in a comment
forgejo_organizationwas stylefree: the caller supplied the name, the kit validated the charset and the length. The rule — a namespace is an org's long form, plus the reservedshared— lived in the type's ownnotes, where nothing could enforce it.It answers no question the convention asks. A forge namespace is named by the org axis, because that is what it answers: whose is this repository. The long form and not the code — a human types it into a URL, and
see/infra-conventionswould read as noise.What this adds
A new style,
namespace, in all three implementations:context.orgsharedgets inAnd a
namespacesblock inregistry/conventions.yamldeclaring what the names are drawn from (org.long) beside the reserved ones. Derived, never written out — adding an org adds its namespace:Two lints, because the set has to stay unambiguous
A reserved name that is also an org long form would make one namespace answer two questions — "the org
seeds" and "held over every org" — and no rename repairs it: a forge namespace sits in every clone URL already written down.And
namespaces.fromis confronted with the one value that is implemented, so a typo there cannot silently mean nothing.Verification
make checkgreen. The module precondition was mutated to always pass andnamespace_outside_the_rulefailed — which is the only thing that makes its green mean anything. OpenTofu refuses a constant precondition outright, so the mutation had to stay referential (>= 0rather thanlength([])).What stays as it was
A repository stays
free. It is a functional name like a DNS record, and recomposing it by the grammar would produce a URL nobody would type either.full_nameis not governed.stacks/forge/namespacescurrently writestitle(var.organization)→Shared. If the display label should come from the vocabulary'slabelfield ("Seeds, the operator of the platform"), say so and it becomes a second rule — the kit has no notion of a display label today.A second commit: who may read a reserved namespace
Asked while reviewing this: does the convention need to say that
sharedexists in the forge with no infrastructure behind it, public, and with no code or label?Two of the three were already here, in
registry/conventions.yamland inCONVENTION.mdsection 4:The third was missing, and it turns out the kit already depends on it. "The kit is public, so no consumer's CI carries a read token" is two facts: the repository must be publicly readable — measured, an unauthenticated read answers 200, and the README says so — and so must the namespace holding it. A namespace set to
limitedmeans signed-in users only, which is the same token wearing another hat. Nothing recorded that half.Not a rule about every namespace. An org's own (
seeds,personal) has no reason to be public, and making it so would publish whatever it ever comes to hold. Only a namespace that exists to be read by everyone declares it.And nothing here can test it — a visibility is an API answer and this suite reaches no network. The value is declared so that whoever lays the namespace down takes it from the convention instead of rediscovering it, and so the consumer that can test it has something to confront:
stacks/forge/namespacesinseeds-infraalready asserts exactly this value, on the organisation and on the repository. It is written into the README's known limits as a declared-not-verified fact, beside the others.Four more refusals, each mutation-tested:
18lint mutations, 0 silent. The rest of the suite did not move: the reserved map went fromname -> stringtoname -> object, and both the tool and the policy read its keys.🤖 Generated with Claude Code
`forgejo_organization` was style `free`: the caller supplied the name and the kit validated the charset and the length. The rule — a namespace is an org's long form, plus the reserved `shared` — lived in the type's own notes, where nothing could enforce it. `dataseeds` was accepted: typeable, inside the charset, under 40 characters, and an answer to no question the convention asks. A forge namespace is named by the ORG AXIS, because that is what it answers: whose is this repository. The long form and not the code — a human types it into a URL, and `see/infra-conventions` would read as noise. So: a new style, `namespace`, in all three implementations. name supplied by nobody the module derives it from context.org name supplied accepted only if the rule already allows it anything else refused, by the module AND by the policy And a `namespaces` block in `registry/conventions.yaml` declaring what the names are drawn from (`org.long`) beside the reserved ones (`shared`). Derived, never written out: adding an org adds its namespace. see -> seeds per -> personal dgt -> digitank + shared TWO LINTS, because the set has to stay unambiguous. A reserved name that is also an org long form would make one namespace answer two questions — "the org seeds" and "held over every org" — and no rename repairs it: a forge namespace sits in every clone URL already written down. And `namespaces.from` is confronted with the one value that is implemented, so a typo there cannot silently mean nothing. Verified, and each guard mutation-tested: 75 module tests (+5: 4 shared cases, 1 refusal) 55 policy tests (+5: the org form, the reserved one, a free name, an org code, and a repository that stays free) 13 lint mutations (+2), 0 silent The module precondition was mutated to always pass and `namespace_outside_the_rule` failed, which is the only thing that makes its green mean anything. OpenTofu refuses a constant precondition outright, so the mutation had to stay referential. A repository stays `free`: it is a functional name like a DNS record, and recomposing it by the grammar would produce a URL nobody would type either. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>15d6e3c5d9b176a88c29"The kit is public, so no consumer's CI carries a read token" is TWO facts and the kit recorded one. The repository must be publicly readable - measured, an unauthenticated read answers 200, and the README says so. So must the NAMESPACE holding it: a namespace set to `limited` means signed-in users only, which is the same token wearing another hat. Nothing here said that, which is how a dependency goes unrecorded until it breaks. So a reserved namespace declares its visibility: reserved: shared: reason: "What is held over every org and owned by none" visibility: public NOT A RULE ABOUT EVERY NAMESPACE. An org's own - seeds, personal - has no reason to be public, and making it so would publish whatever it ever comes to hold. Only a namespace that exists to be read by everyone declares it. AND NOTHING HERE CAN TEST IT. A visibility is an API answer and this suite reaches no network. The value is declared so that whoever lays the namespace down takes it FROM the convention instead of rediscovering it, and so that the consumer which CAN test it has something to confront - stacks/forge/namespaces in seeds-infra already asserts exactly this value, on the organisation and on the repository. Written into the known limits as a declared-not-verified fact, beside the others. What lint-registry now refuses, each mutation-tested: a reserved name that is also an org long form it would answer two questions a reserved namespace with no reason nobody can say why it exists a reserved namespace with no visibility the promise above rests on it a visibility no forge knows public | limited | private 18 lint mutations, 0 silent. The rest of the suite is unchanged: the reserved map went from name -> string to name -> object, and both the tool and the policy read its KEYS, which is why neither moved. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>