Implements novox/hq ADR 0108, closing issue 116. The proxy's request path was a host lookup and a forward, so it applied nothing — while the ingress it replaces relies on four things it had none of. Path scoping came first because it is a prerequisite, not a sibling. The table mapped a host to one target, so a host could not be routed two ways, and the refusal this issue turns on matches a path on a host already routed to a workload. No amount of authentication or source filtering would have made it expressible. The table is now host to an ordered list of rules, matched on path prefix. The order is total, not just by priority. Sorting on priority alone leaves rules that share one in whatever order the map produced, so the same declaration would serve differently between restarts — a fault that works, and works differently each time, which is the hardest kind to believe when reported. Within a priority the longer path wins, which is also the intuitive reading. auth names a secret and never holds one. A declaration carrying a credential is refused whole rather than served unprotected, so the option ADR 0108 rejected cannot return by accident. A secret that cannot be read makes the route refuse and say so, rather than serve the workload unprotected — a gate that cannot check is not a gate that opens, and the alternative turns a missing file into a silently public admin surface. Authentication costs one bcrypt comparison on every path including an unknown user, so an unknown user is not measurably faster than a known one with a wrong password. That difference is a way to enumerate a route's users from outside it. Redirects keep the request's own path and query, or canonicalising one name onto another would land every deep link on the front page and raise no error doing it. Eleven tests, four of them for the capabilities and two for the failure modes that rot quietly: the credential-in-a-declaration refusal, and the unreadable secret failing closed. Nothing else breaks if those stop working, so nothing else would report it. No new dependency: bcrypt comes from the x/crypto module already required.
examples
Things that run, kept here because a contract is easier to read as working code than as prose.
Nothing here is part of the control plane. The control plane decides and never touches a machine (README); everything in this directory runs on a machine and touches it. These are reference implementations of contracts the control plane defines, and a real one ships with the module that ships the software it configures.
postgres-provisioner |
the last step of a credential: reads what the mesh delivered and makes PostgreSQL accept it |
Running the provisioner
--watch reconciles now and again whenever what the mesh delivered changes. That is what lets it
be a module: an ordinary long-running service the host supervises, rather than something that has
to be invoked after every declaration by a timer or a unit wired to a file.
It polls rather than watching the filesystem, because the host writes atomically — the file is replaced, so a watch on the path stops seeing anything after the first replacement. A watcher that silently stops working is worse than a poll.
Credentials are compared by digest and never by content. This runs for as long as the machine is up, and a secret does not belong in a long-lived variable when a hash answers the same question.