{"ports": {"2222": 222}} for the forge was refused — "no container of its publishes 2222" —
although 2222 is exactly what the machine publishes and what the manifest names in listens, in serves and in every ${port:}. Two defects, one root:
The key.containerPorts read each ports entry by its last segment only, so "2222:22"
registered as 22 and nothing else. The only key the validator accepted was the container's
end — and the inventory stores a node ports layer without a manifest, so the setting was
accepted where it was set and refused at composition, which is why every push to the node was
refused while it existed.
The lookup.Rendering.Given was written under whatever the setting said and read under two
different keys: the port the module declares (the filter, the openings, the guard, ServedOn, ${port:N}) and the port inside the container (the mapping handed to the runtime). For "2222:22" those differ, so exactly one reader ever found the entry — keyed 22, the mapping
moved and nothing else did, leaving a firewall, a set of openings and a consumer pointing at a
port the software had left.
Now a given port names either end of a mapping and answers to both, so every reader finds the
same number under the key it happens to hold. Real ambiguity is still refused: one number naming
two different mappings, and the two ends of one mapping given two different machine ports. {"22": N} keeps working — it was the only key that ever worked, and it now means what {"2222": N} means.
Tests, each verified to fail with the source reverted: the setting accepted and answered under both
ends; the machine side reaching the filter, the opening (tcp-222-forwarded → to: 22), the guard
and the consumer; the mapping moved under either name; the real catalogue forge manifest composed
end to end; and the whole path through the plan on an adopted node, which reverted fails at exactly
the reported symptom — the push, not the setting. One test deliberately passes without the fix: the
no-setting baseline, recording that the openings derivation was never at fault.
Whole suite green against a real database.
Fixes novox/hq issue 094.
`{"ports": {"2222": 222}}` for the forge was refused — "no container of its publishes 2222" —
although 2222 is exactly what the machine publishes and what the manifest names in `listens`, in
`serves` and in every `${port:}`. Two defects, one root:
1. **The key.** `containerPorts` read each `ports` entry by its *last* segment only, so `"2222:22"`
registered as `22` and nothing else. The only key the validator accepted was the container's
end — and the inventory stores a node `ports` layer without a manifest, so the setting was
accepted where it was set and refused at composition, which is why every push to the node was
refused while it existed.
2. **The lookup.** `Rendering.Given` was written under whatever the setting said and read under two
different keys: the port the module *declares* (the filter, the openings, the guard, `ServedOn`,
`${port:N}`) and the port *inside the container* (the mapping handed to the runtime). For
`"2222:22"` those differ, so exactly one reader ever found the entry — keyed `22`, the mapping
moved and nothing else did, leaving a firewall, a set of openings and a consumer pointing at a
port the software had left.
Now a given port names **either end** of a mapping and answers to both, so every reader finds the
same number under the key it happens to hold. Real ambiguity is still refused: one number naming
two different mappings, and the two ends of one mapping given two different machine ports.
`{"22": N}` keeps working — it was the only key that ever worked, and it now means what
`{"2222": N}` means.
Tests, each verified to fail with the source reverted: the setting accepted and answered under both
ends; the machine side reaching the filter, the opening (`tcp-222-forwarded` → `to: 22`), the guard
and the consumer; the mapping moved under either name; the real catalogue forge manifest composed
end to end; and the whole path through the plan on an adopted node, which reverted fails at exactly
the reported symptom — the push, not the setting. One test deliberately passes without the fix: the
no-setting baseline, recording that the openings derivation was never at fault.
Whole suite green against a real database.
A module publishing `2222:22` — the machine's own ssh daemon holds 22, so
the module takes 2222 and says so in `listens` — could not be moved. The
setting was read against the last segment of each mapping alone, so the
number the module uses everywhere else was refused as a port it does not
publish, and the node's every push failed for as long as the setting was
stored. The one key that was accepted, the container's own port, was then
read only when the container's mapping was rewritten: the mapping moved
and the ports map, the filter, the adopted node's openings, its guard and
what a consumer is told all stayed on the number the software had left.
Either end of a mapping now names it, and a given port comes back under
both, so every reader finds the same number under the key it holds.
Ambiguity is refused where it is real — one number naming two different
mappings, or the two ends of one mapping given two different numbers.
Review found the first pass aliased its answer under both ends of a mapping, which is
wrong wherever two mappings share a number: the alias lands on a key belonging to another
mapping, the later write wins, and the filter and the container then disagree — the very
fault this change exists to close. Two reproduced cases: a module publishing 8080:80 beside
9090:8080 had an explicit setting silently overwritten; a module publishing 4001:80 beside
4002:80 composed both containers onto one machine port, where before it was safely refused.
Now a mapping's answer is filed once, under the end the module names in its listens — the
number the plan, the filter, the openings, the guard and the consumer all ask for — and a
key that names two mappings is refused in the same words as a setting that does.
Also: the guard assertion in the end-to-end test failed open when the resource was absent;
the plan-mirroring helper now says it stands in only where the plan does not allocate, and
the assertions it feeds are narrowed to the port under test.
Reviewed, and the first pass was wrong in a way worth recording.
The review verdict was merge with changes, with three real findings:
An aliased key silently overwrote an explicit setting. Filing the answer under both ends of a mapping is safe only while no two mappings share a number. Reproduced: a module publishing 8080:80 beside 9090:8080, given {"80": 1234, "9090": 5678}, came back {80:1234, 8080:5678, 9090:5678} — the alias for the second mapping landed on the first mapping's key, the mapping-rewriter preferred it, and the container went to 5678 while the filter, the opening and the consumer still said 1234. That is exactly the fault this change exists to close, reintroduced by the fix for it.
A safe refusal became a broken declaration. A module publishing 4001:80 and 4002:80, given {"4001": 1234}, was refused before and composed both containers onto machine port 1234 after. Nothing anywhere checks that two containers on a node do not publish the same machine port, so it would have failed at the runtime.
Issue 094's push-blocking half is untouched — the setting is still stored without a manifest in view. Out of scope here, now hq issue 096.
Neither 1 nor 2 is reachable against today's catalogue — every long-form mapping in it is unambiguous — but both are latent and silent, which is the worst pair.
Fixed by dropping the aliasing. A mapping's answer is now filed once, under the end the module names in its own listens — which is the number the plan, the filter, the openings, the guard and the consumer all ask for. A setting may still name either end; it resolves to the same single entry. A key that would name two mappings is refused in the same words a setting naming two is.
One behaviour change worth stating plainly: {"8080": N} on a module publishing ["8080:80", "9090:8080"] was accepted before and is refused now. It never worked — it moved the second container's mapping while leaving the filter on 9090 — so this turns a silent misconfiguration into a refusal. No module in the catalogue is shaped that way.
Also from the review: the guard assertion in the end-to-end test failed open when the resource was absent (now fails closed); and the plan-mirroring helper now says it stands in only where the plan does not allocate, with the assertions it feeds narrowed to the port actually under test.
Whole suite green against a real database, and the tests still fail against main's source.
**Reviewed, and the first pass was wrong in a way worth recording.**
The review verdict was *merge with changes*, with three real findings:
1. **An aliased key silently overwrote an explicit setting.** Filing the answer under *both* ends of a mapping is safe only while no two mappings share a number. Reproduced: a module publishing `8080:80` beside `9090:8080`, given `{"80": 1234, "9090": 5678}`, came back `{80:1234, 8080:5678, 9090:5678}` — the alias for the second mapping landed on the first mapping's key, the mapping-rewriter preferred it, and the container went to 5678 while the filter, the opening and the consumer still said 1234. That is exactly the fault this change exists to close, reintroduced by the fix for it.
2. **A safe refusal became a broken declaration.** A module publishing `4001:80` and `4002:80`, given `{"4001": 1234}`, was refused before and composed *both* containers onto machine port 1234 after. Nothing anywhere checks that two containers on a node do not publish the same machine port, so it would have failed at the runtime.
3. Issue 094's push-blocking half is untouched — the setting is still stored without a manifest in view. Out of scope here, now hq issue 096.
Neither 1 nor 2 is reachable against today's catalogue — every long-form mapping in it is unambiguous — but both are latent and silent, which is the worst pair.
**Fixed by dropping the aliasing.** A mapping's answer is now filed **once**, under the end the module names in its own `listens` — which is the number the plan, the filter, the openings, the guard and the consumer all ask for. A setting may still name either end; it resolves to the same single entry. A key that would name two mappings is refused in the same words a setting naming two is.
One behaviour change worth stating plainly: `{"8080": N}` on a module publishing `["8080:80", "9090:8080"]` was accepted before and is refused now. It never worked — it moved the second container's mapping while leaving the filter on 9090 — so this turns a silent misconfiguration into a refusal. No module in the catalogue is shaped that way.
Also from the review: the guard assertion in the end-to-end test failed open when the resource was absent (now fails closed); and the plan-mirroring helper now says it stands in only where the plan does not allocate, with the assertions it feeds narrowed to the port actually under test.
Whole suite green against a real database, and the tests still fail against `main`'s source.
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.
Fixes novox/hq issue 094.
{"ports": {"2222": 222}}for the forge was refused — "no container of its publishes 2222" —although 2222 is exactly what the machine publishes and what the manifest names in
listens, inservesand in every${port:}. Two defects, one root:containerPortsread eachportsentry by its last segment only, so"2222:22"registered as
22and nothing else. The only key the validator accepted was the container'send — and the inventory stores a node
portslayer without a manifest, so the setting wasaccepted where it was set and refused at composition, which is why every push to the node was
refused while it existed.
Rendering.Givenwas written under whatever the setting said and read under twodifferent keys: the port the module declares (the filter, the openings, the guard,
ServedOn,${port:N}) and the port inside the container (the mapping handed to the runtime). For"2222:22"those differ, so exactly one reader ever found the entry — keyed22, the mappingmoved and nothing else did, leaving a firewall, a set of openings and a consumer pointing at a
port the software had left.
Now a given port names either end of a mapping and answers to both, so every reader finds the
same number under the key it happens to hold. Real ambiguity is still refused: one number naming
two different mappings, and the two ends of one mapping given two different machine ports.
{"22": N}keeps working — it was the only key that ever worked, and it now means what{"2222": N}means.Tests, each verified to fail with the source reverted: the setting accepted and answered under both
ends; the machine side reaching the filter, the opening (
tcp-222-forwarded→to: 22), the guardand the consumer; the mapping moved under either name; the real catalogue forge manifest composed
end to end; and the whole path through the plan on an adopted node, which reverted fails at exactly
the reported symptom — the push, not the setting. One test deliberately passes without the fix: the
no-setting baseline, recording that the openings derivation was never at fault.
Whole suite green against a real database.
Reviewed, and the first pass was wrong in a way worth recording.
The review verdict was merge with changes, with three real findings:
8080:80beside9090:8080, given{"80": 1234, "9090": 5678}, came back{80:1234, 8080:5678, 9090:5678}— the alias for the second mapping landed on the first mapping's key, the mapping-rewriter preferred it, and the container went to 5678 while the filter, the opening and the consumer still said 1234. That is exactly the fault this change exists to close, reintroduced by the fix for it.4001:80and4002:80, given{"4001": 1234}, was refused before and composed both containers onto machine port 1234 after. Nothing anywhere checks that two containers on a node do not publish the same machine port, so it would have failed at the runtime.Neither 1 nor 2 is reachable against today's catalogue — every long-form mapping in it is unambiguous — but both are latent and silent, which is the worst pair.
Fixed by dropping the aliasing. A mapping's answer is now filed once, under the end the module names in its own
listens— which is the number the plan, the filter, the openings, the guard and the consumer all ask for. A setting may still name either end; it resolves to the same single entry. A key that would name two mappings is refused in the same words a setting naming two is.One behaviour change worth stating plainly:
{"8080": N}on a module publishing["8080:80", "9090:8080"]was accepted before and is refused now. It never worked — it moved the second container's mapping while leaving the filter on 9090 — so this turns a silent misconfiguration into a refusal. No module in the catalogue is shaped that way.Also from the review: the guard assertion in the end-to-end test failed open when the resource was absent (now fails closed); and the plan-mirroring helper now says it stands in only where the plan does not allocate, with the assertions it feeds narrowed to the port actually under test.
Whole suite green against a real database, and the tests still fail against
main's source.