Merge pull request 'Issues 225, 226 and 227 resolved with their live proofs; 232 opened and resolved' (#357) from issues/228-photos-authenticates-against-admin into main

This commit was merged in pull request #357.
This commit is contained in:
2026-10-04 10:37:53 +00:00
4 changed files with 122 additions and 6 deletions
@@ -1,8 +1,8 @@
---
status: open
status: resolved
opened: 2026-10-04
located-in: [mesh-host internal/apply, mesh-controller internal/catalogue]
fixed-by:
fixed-by: mesh-controller#263
amended-design:
---
@@ -73,3 +73,25 @@ their containers and made a silent fault visible as a restarting one. The dates
is right once and wrong four thousand times is a message that hides its own meaning.
- Which other modules' files did the runtime move leave behind? The sweep is the same question
for every path the mesh writes for a module: directories, bundles, received files.
## Resolved, 2026-10-04
A grant secret is composed with the account that will read it: the node's account where the
module's code is a bundle the runtime runs, its declared `secrets-owner` where it is still a
container, root where it says neither. The same rule the module's own secrets already followed,
reaching the other kind of secret the mesh writes for a module (mesh-controller#263).
`givenTo` could not have reached these: it claims the files a bundle's *words* name, and the
harness composes a grant secret's path from the contributions file, which no word names.
And the harness stops calling a permanent refusal a race (mesh-sdk#22, 0.1.9). After about a
minute it says so plainly, rarely rather than every five seconds, and names what to look at —
who owns the file and who the process runs as. That is the half that cost three hours.
**How it was checked:** on the control machine, after the push — the grant secrets belong to the
operator's account, **zero `EACCES` since 12:30:10** where there had been 4330, both users were
created, and `mongodb-server` logs `Authentication succeeded` for each.
It also uncovered [issue 232](../232-a-consumer-authenticates-against-a-database-its-user-does-not-live-in/00-report.md),
which this fault had been hiding: with no user anywhere, "not found in admin" was a complete
account of *this* issue and said nothing about the consumer asking the wrong database.
@@ -1,8 +1,8 @@
---
status: open
status: resolved
opened: 2026-10-04
located-in: [mesh-controller cmd/mesh-controller/collect.go, mesh-controller internal/inventory/collection.go]
fixed-by:
fixed-by: mesh-controller#263
amended-design:
---
@@ -56,3 +56,21 @@ reports the right number while doing it, which is what made it look healthy.
- Only a refusal *by the store* ends a sweep.
- **How it is checked:** a sweep over records holding one address-recorded reference and one kept
one collects the second; a sweep against a store that refuses stops at the first.
## Resolved, 2026-10-04
Two changes, deliberately separate.
**Normalising moved to where the provenance is known.** Every reference the sweep handles came
from a build record, so every one is the mesh's own and `Recorded` may read an address-era
reference as the kept one. Not in `LetGo` — it cannot tell `docker.io` from the mesh's store, and
an attempt to put it there was caught at once by the test that says a foreign reference is never
asked about. The guard stays strict; the records speak the one vocabulary.
**A reference the sweep will not address is `ErrNotOurs`:** skipped, not marked collected, never
a reason to stop. Only the store refusing ends a sweep.
**How it was checked:** the first build after the roll-out printed *"the artifact store let go of
200 artifact(s) the mesh no longer keeps — 1126 more to collect; the next build asks again"*.
Two hundred is the per-sweep bound working as intended; the backlog is falling with every build
instead of standing at 1681 for ever.
@@ -1,8 +1,8 @@
---
status: open
status: resolved
opened: 2026-10-04
located-in: [mesh-catalog modules/photos]
fixed-by:
fixed-by: mesh-catalog#263, mesh-controller#263
amended-design:
---
@@ -42,3 +42,19 @@ gradually.
is composed, rather than failing on the machine every pass. The mesh assigns ports
([ADR 0038](../../02-DECISIONS/0038-the-mesh-assigns-the-port.md)); a fixed 80 beside a proxy is
a statement it could check.
## Resolved, 2026-10-04
The three photo modules publish the endpoint they declare — `4001:80`, `4012:80`, `4013:80` —
so the software's own port reaches the machine at the port the mesh assigned, and nothing asks
for 80.
**The rule, rather than three repairs.** A container may publish only a port its module declares:
the short form `"80"` means *publish what the software calls 80*, and the mesh fills in the
machine's half from the port it assigned — which it can only do for a port the module declared.
Four modules publish 80 quite safely because they declare 80. The difference is the declaration,
not the number. A catalogue-wide test in mesh-controller#263 says so, and names all three
offenders against the catalogue as it was.
**How it was checked:** `photos-admin-client` has been up since the push, where before it failed
on every pass.
@@ -0,0 +1,60 @@
---
status: resolved
opened: 2026-10-04
located-in: [mesh-catalog modules/photos]
fixed-by: mesh-catalog#264
amended-design:
---
# 232 — A consumer authenticates against a database its user does not live in, and one fault hid it behind another
## What was observed
Fixing [issue 225](../225-a-provisioner-cannot-read-the-grant-secrets-since-its-code-left-the-container/00-report.md)
on the control machine, 2026-10-04. With the grant secrets readable again the provisioner created
both consumers' users at once, and one consumer still could not connect:
```
Authentication succeeded | user: mesh_novox_invoice | authDb: mesh_novox_invoice
Authentication succeeded | user: mesh_novox_photos | authDb: mesh_novox_photos
Authentication failed | user: mesh_novox_photos | authDb: admin
UserNotFound: Could not find user "mesh_novox_photos" for db "admin"
```
The provider creates each consumer's user **in that consumer's own database**, which is what the
first two lines are. `photos` asks for `admin`. Its sibling `invoicing`, against the same
provider, asks for `${bound:mongodb-database:as}` — the name the mesh gave it — and works.
The password was never the problem: the grant secret and the value in the consumer's environment
hash identically.
## Why it matters beyond this instance
**One fault wore the other's clothes.** While the provisioner could not read its secrets at all,
*no* user existed, so `UserNotFound ... for db "admin"` was a true and complete account of issue
225. Fixing 225 is what made the wrong database visible — before that, every symptom pointed at
the thing that was already broken, and a second fault behind it was indistinguishable.
That is the general shape worth keeping: **a fault that explains the symptom is not evidence
there is only one.** The check is to fix the first and look again, rather than to close both on
one explanation.
It also says something about the interface. Which database a consumer authenticates against is
part of what `mongodb-database` means, and it is spelled out by hand in each consumer. Two
consumers of one provider wrote two different answers, and only one was right; nothing compared
them. That is the shape [issue 124](../124-a-consumer-cannot-be-told-what-its-provider-derived/00-report.md)
records for a derived bucket, here for the authentication database —
[ADR 0202](../../02-DECISIONS/0202-a-provider-declares-what-it-derives-for-each-consumer.md)'s
mechanism is what would remove it, by letting the provider say it once.
## Resolved
`photos` asks for `${bound:mongodb-database:as}`, as `invoicing` already did (mesh-catalog#264).
**How it is checked:** the module is rebuilt and pushed, and `photos-server` connects — verified
on the control machine rather than inferred from the manifest.
## What this leaves open
Nothing compares two consumers' idea of one interface. The provider could serve the
authentication database as a derived value under ADR 0202 and neither consumer would state it;
that is a candidate for the next consumer of this interface, not a repair of this one.