Blocking the live minio migration: minio needs two public hostnames — files-api.novox.be for the S3 API and files.novox.be for the console — which is two separate contributions to route from one module. Contributes is map[string]map[string]any, a plain JSON object keyed by requirement name, so a module could only ever answer route once.
Generalizes the same fix ADR 0094 already gave secrets ("a module may need more than one value from a provider that gives one per pair"), applied to the other half of the edge: contributes now accepts either the ordinary single object, or an object of local names to several such objects — "route": {"api": {"label": "files-api", "port": 9000}, "console": {"label": "files", "port": 9001}}.
Detection. Unlike secrets, both shapes are JSON objects (no string-vs-object split to key off), so the split is by what's inside: an ordinary contribution's fields are scalars (label, port); the several-instance shape is local-name → object. Verified against every route (and every other) contribution in the whole mesh-catalog catalogue before relying on this — nothing anywhere has an object-valued field that could be misread.
What changed:
internal/catalogue/manifest.go: new ContributesMany field, custom unmarshal/marshal (mirrors SecretsMany), Wants() and ParseManifest validation extended.
internal/catalogue/declaration.go: contributions() now also walks ContributesMany, emitting one Contribution per local name.
No changes needed on the receiving side. Both route-proxy (examples/route-proxy/main.go) and the migration-era route-adapter key their generated routers off the composed hostname (Values["name"]), not the contributing module's name — confirmed by reading both directly. Two contributions with the same From already produce two independent routes with zero collision risk.
Verified:go build ./..., go vet ./..., and go test ./... — new tests added in several_contributions_test.go (shape round-trip, local-name validation, and an end-to-end resolve→declaration test confirming two named routes from one module reach the provider as two entries). Full suite passes except two pre-existing failures unrelated to this change (confirmed present on main before this branch: TestTheForgesSshPortIsGivenByTheNumberTheForgeCallsIt, TestTheResolverAndWhatAsksItComposeOnOneMachine).
Known scope boundary:cmd/mesh-controller/plan.go's grant-minting loop (for to := range m.Contributes) still only walks the single-shape map. That's correct for route today — a route contribution mints no credential, so it never went through that path — but if a future provision needs both multiple contributions and per-instance credentials (e.g. a module wanting two separate s3-bucket grants), that loop would need the same ContributesMany treatment. Not done here since nothing in this catalogue needs it yet.
Not merging — leaving for review.
Blocking the live minio migration: minio needs two public hostnames — `files-api.novox.be` for the S3 API and `files.novox.be` for the console — which is two separate contributions to `route` from one module. `Contributes` is `map[string]map[string]any`, a plain JSON object keyed by requirement name, so a module could only ever answer `route` once.
Generalizes the same fix ADR 0094 already gave `secrets` ("a module may need more than one value from a provider that gives one per pair"), applied to the other half of the edge: `contributes` now accepts either the ordinary single object, or an object of local names to several such objects — `"route": {"api": {"label": "files-api", "port": 9000}, "console": {"label": "files", "port": 9001}}`.
**Detection.** Unlike `secrets`, both shapes are JSON objects (no string-vs-object split to key off), so the split is by what's *inside*: an ordinary contribution's fields are scalars (`label`, `port`); the several-instance shape is local-name → object. Verified against every `route` (and every other) contribution in the whole `mesh-catalog` catalogue before relying on this — nothing anywhere has an object-valued field that could be misread.
**What changed:**
- `internal/catalogue/manifest.go`: new `ContributesMany` field, custom unmarshal/marshal (mirrors `SecretsMany`), `Wants()` and `ParseManifest` validation extended.
- `internal/catalogue/declaration.go`: `contributions()` now also walks `ContributesMany`, emitting one `Contribution` per local name.
- **No changes needed on the receiving side.** Both `route-proxy` (`examples/route-proxy/main.go`) and the migration-era `route-adapter` key their generated routers off the composed hostname (`Values["name"]`), not the contributing module's name — confirmed by reading both directly. Two contributions with the same `From` already produce two independent routes with zero collision risk.
**Verified:** `go build ./...`, `go vet ./...`, and `go test ./...` — new tests added in `several_contributions_test.go` (shape round-trip, local-name validation, and an end-to-end resolve→declaration test confirming two named routes from one module reach the provider as two entries). Full suite passes except two pre-existing failures unrelated to this change (confirmed present on `main` before this branch: `TestTheForgesSshPortIsGivenByTheNumberTheForgeCallsIt`, `TestTheResolverAndWhatAsksItComposeOnOneMachine`).
**Known scope boundary:** `cmd/mesh-controller/plan.go`'s grant-minting loop (`for to := range m.Contributes`) still only walks the single-shape map. That's correct for `route` today — a route contribution mints no credential, so it never went through that path — but if a future provision needs *both* multiple contributions *and* per-instance credentials (e.g. a module wanting two separate `s3-bucket` grants), that loop would need the same `ContributesMany` treatment. Not done here since nothing in this catalogue needs it yet.
Not merging — leaving for review.
A module's contributes was map[string]map[string]any — one JSON object key
per requirement, structurally exactly one contribution to "route" ever.
minio needs two public hostnames (the S3 API and the console), which is
two different contributions to route from one module, and nothing let it
say so.
This is the same shape of problem ADR 0094 solved for secrets (a module
needing several values from one provider that gives one per pair):
contributes now accepts either the ordinary {label, port} object, or an
object of local names to several such objects. Detected per requirement
key by what's inside, since (unlike secrets' string-vs-object split) both
shapes are JSON objects: an ordinary contribution's fields are scalars, the
several-instance shape is local-name -> object. Confirmed against every
module.json in mesh-catalog before relying on that split.
Both route-proxy and the migration-era route-adapter already key generated
routers off the composed hostname (Values["name"]), not the module name,
so two contributions with the same From reach them as two independent
routes with no changes needed on the receiving side.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Blocking the live minio migration: minio needs two public hostnames —
files-api.novox.befor the S3 API andfiles.novox.befor the console — which is two separate contributions toroutefrom one module.Contributesismap[string]map[string]any, a plain JSON object keyed by requirement name, so a module could only ever answerrouteonce.Generalizes the same fix ADR 0094 already gave
secrets("a module may need more than one value from a provider that gives one per pair"), applied to the other half of the edge:contributesnow accepts either the ordinary single object, or an object of local names to several such objects —"route": {"api": {"label": "files-api", "port": 9000}, "console": {"label": "files", "port": 9001}}.Detection. Unlike
secrets, both shapes are JSON objects (no string-vs-object split to key off), so the split is by what's inside: an ordinary contribution's fields are scalars (label,port); the several-instance shape is local-name → object. Verified against everyroute(and every other) contribution in the wholemesh-catalogcatalogue before relying on this — nothing anywhere has an object-valued field that could be misread.What changed:
internal/catalogue/manifest.go: newContributesManyfield, custom unmarshal/marshal (mirrorsSecretsMany),Wants()andParseManifestvalidation extended.internal/catalogue/declaration.go:contributions()now also walksContributesMany, emitting oneContributionper local name.route-proxy(examples/route-proxy/main.go) and the migration-eraroute-adapterkey their generated routers off the composed hostname (Values["name"]), not the contributing module's name — confirmed by reading both directly. Two contributions with the sameFromalready produce two independent routes with zero collision risk.Verified:
go build ./...,go vet ./..., andgo test ./...— new tests added inseveral_contributions_test.go(shape round-trip, local-name validation, and an end-to-end resolve→declaration test confirming two named routes from one module reach the provider as two entries). Full suite passes except two pre-existing failures unrelated to this change (confirmed present onmainbefore this branch:TestTheForgesSshPortIsGivenByTheNumberTheForgeCallsIt,TestTheResolverAndWhatAsksItComposeOnOneMachine).Known scope boundary:
cmd/mesh-controller/plan.go's grant-minting loop (for to := range m.Contributes) still only walks the single-shape map. That's correct forroutetoday — a route contribution mints no credential, so it never went through that path — but if a future provision needs both multiple contributions and per-instance credentials (e.g. a module wanting two separates3-bucketgrants), that loop would need the sameContributesManytreatment. Not done here since nothing in this catalogue needs it yet.Not merging — leaving for review.
A module's contributes was map[string]map[string]any — one JSON object key per requirement, structurally exactly one contribution to "route" ever. minio needs two public hostnames (the S3 API and the console), which is two different contributions to route from one module, and nothing let it say so. This is the same shape of problem ADR 0094 solved for secrets (a module needing several values from one provider that gives one per pair): contributes now accepts either the ordinary {label, port} object, or an object of local names to several such objects. Detected per requirement key by what's inside, since (unlike secrets' string-vs-object split) both shapes are JSON objects: an ordinary contribution's fields are scalars, the several-instance shape is local-name -> object. Confirmed against every module.json in mesh-catalog before relying on that split. Both route-proxy and the migration-era route-adapter already key generated routers off the composed hostname (Values["name"]), not the module name, so two contributions with the same From reach them as two independent routes with no changes needed on the receiving side.