The playbook, the README and the status skill knew five statuses; the cycle check knew a sixth, 'fixed', and not 'wontfix'. Eleven issues sat in the sixth for weeks with their fixes shipped, one step short of closed. They are resolved; the check refuses the word from now on and accepts the one the playbook allows.
91 lines
4.0 KiB
Markdown
91 lines
4.0 KiB
Markdown
---
|
|
status: resolved
|
|
opened: 2026-09-01
|
|
located-in: [mesh-control]
|
|
fixed-by: mesh-control df62bb5
|
|
amended-design:
|
|
---
|
|
|
|
# 021 — A consumer on the provider's machine is given no credential
|
|
|
|
## Symptom
|
|
|
|
A module that requires something answered **on the same machine** resolves cleanly and is given
|
|
**no credential at all**. Two modules, zero needs:
|
|
|
|
```
|
|
postgres provides postgres-database, grants /var/lib/postgres/grants
|
|
keycloak requires postgres-database, secrets /var/lib/keycloak/database.env
|
|
→ modules: 2, needs: 0
|
|
```
|
|
|
|
Nothing is refused and nothing is reported. The consumer's `secrets:` path is simply never
|
|
written, and whatever reads it fails later, somewhere else.
|
|
|
|
## Where it comes from
|
|
|
|
The world a node resolves against is **every other node**:
|
|
|
|
```go
|
|
for _, n := range nodes {
|
|
if n.Name == exclude { continue }
|
|
```
|
|
|
|
So a provider on the same machine is never a `Provider` in `world.Offered`, never becomes a
|
|
`Needed`, and the credential loop — which walks `resolved.Needs` — has nothing to walk. Every step
|
|
is individually reasonable and the sum is a silent gap.
|
|
|
|
## Why it was not noticed
|
|
|
|
**Everything proven so far was cross-machine.** The lab's provisioner scenarios put the consumer on
|
|
one node and the provider on another, which is the interesting case for a *mesh* and the rare case
|
|
in practice. The first module to want a database on its own machine was the first real one.
|
|
|
|
The postgres provisioner even records the assumption in passing — *"Node is empty for a module on
|
|
this machine, which is asking for something local and is not this provisioner's business"* — which
|
|
reads as a deliberate exclusion of local consumers.
|
|
|
|
## Why the assumption is wrong
|
|
|
|
It holds for a process on the machine reaching a unix socket, where the operating system can vouch
|
|
for who is calling. **It does not hold for containers**, which is how nearly everything runs here: a
|
|
module's containers reach a provider's containers over TCP on a shared network, and the database
|
|
asks for a password exactly as it would from another machine.
|
|
|
|
**The machine is not a trust boundary once both sides are containers.** Treating it as one gives
|
|
the most common arrangement — a service and its database on one node — the weakest handling.
|
|
|
|
## What it is not
|
|
|
|
Not the same as [`020`](../020-a-certificate-is-issued-and-never-collected/00-report.md) or a
|
|
provisioner defect. The provisioner never sees these consumers because the mesh never records
|
|
them as consumers.
|
|
|
|
## What a fix has to keep
|
|
|
|
- **A local consumer still appears in the provider's grants**, so its provisioner creates the role
|
|
or bucket or client, exactly as for a remote one.
|
|
- **The credential is still sealed**, to the one node that is both ends. The mesh holding a
|
|
readable secret for local consumers would be a hole opened for convenience.
|
|
- **Refusing must stay refusing.** A requirement nothing answers is still refused; this is about a
|
|
requirement that *was* answered.
|
|
|
|
## Fixed
|
|
|
|
A requirement answered on this machine is still a requirement. Resolution now records a need for
|
|
it, so a credential is made, the provider is told who asked, and the consumer's file is written —
|
|
the same as if the two were on different machines.
|
|
|
|
The reasoning that made it a gap is now written where it was assumed: the machine is not a trust
|
|
boundary once both ends are containers, and treating it as one gave the commonest arrangement of
|
|
all — a service and its database on one node — the weakest handling.
|
|
|
|
Two later issues came out of the same mistaken instinct and are worth reading together:
|
|
[`022`](../022-one-credential-per-node-per-provision-not-per-module/00-report.md), where the
|
|
machine was treated as an *identity* rather than a boundary, and
|
|
[`023`](../023-a-consumer-cannot-build-a-connection-string/00-report.md), where the consumer was
|
|
given a password and never told the name to present with it.
|
|
|
|
*Closed 2026-09-01. The fix landed the same day and this record was left open by oversight — the
|
|
code and the tests were in place for hours while the record still said `located`.*
|