Skip to content

fix(relay): stop the multicast profile activating relay-caddy (BLO-34364) - #21

Merged
kkroo merged 1 commit into
mainfrom
blo-34364-relay-caddy-profile
Sep 21, 2026
Merged

kkroo merged 1 commit into
mainfrom
blo-34364-relay-caddy-profile

Conversation

@allyblockcast

@allyblockcast allyblockcast Bot commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

Fixes BLO-34364.

Stacked on #20 (blo-34239-compose-validate), which adds scripts/validate-compose.sh. Base retargets to main automatically when #20 merges. Deliberately not bundled into #20 — that PR is additive CI only; this is a fleet-behaviour change on the branch gateways fetch.

The defect

relay-caddy declared profiles: [caddy, multicast], and container_name: relay lives in the shared x-relay-cache anchor that relay (ats), relay-varnish and relay-caddy all merge. Any profile set carrying a non-caddy backend and multicast therefore activates two services claiming the same container name, and docker compose config — which is parse-only — rejects the entire project. Every compose verb (up, ps, pull, down) fails identically, so a gateway in this state cannot reconcile at all.

That set is gateway-reachable: computeProfilesWithBackend() seeds ["managed"], appends the cache backend when relay is enabled, and appends "multicast" when the standalone multicast service is enabled. The multicast drop at manager.go:646 is scoped to cache_backend == "caddy" (that was BLO-5453's fix), and getCDNConfig() defaults to "ats".

The multicast profile never meant "run multicast". The multicast service carries profiles: [managed] via the x-managed anchor and is already active under managed alone. It meant "run relay-caddy with multicast absorbed" — redundant when the backend is caddy (caddy is appended directly) and destructive otherwise.

Evidence

Measured locally at the parent commit vs. this one, docker compose -f docker-compose.yml -f docker-compose.relay.yml config:

profile set before after
managed ats multicast rc=1 services.relay-caddy: container name "relay" is already in use by service {} rc=0
managed varnish multicast rc=1 services.relay-varnish: container name "relay" is already in use by service {} rc=0
managed caddy multicast rc=0 rc=0
managed caddy rc=0 rc=0
managed multicast rc=0 rc=0
managed rc=0 rc=0

Services claiming container_name: relay, from config --format json — one claimant per set after the fix, where the two failing sets previously produced no output at all:

managed ats multicast      -> relay
managed varnish multicast  -> relay-varnish
managed caddy multicast    -> relay-caddy
managed caddy              -> relay-caddy

The acceptance criteria that are about what must keep working:

  • managed caddy still activates relay-caddy — see the table above
  • the standalone multicast service is still activated under managed alone — config --services for managed lists multicast

Blast radius

One behaviour change worth naming rather than burying: on a gateway with relay disabled and multicast enabled, relay-caddy previously came up from the multicast profile alone. It no longer does. That is the intended direction — the relay cache should not run on a gateway whose relay is disabled — but it is a change, not a no-op.

An operator hand-running --profile multicast standalone also no longer gets relay-caddy; they should use --profile caddy.

Verification

scripts/validate-compose.sh asserts EXPECTED_FAIL in both directions, so emptying it in this same PR is what makes the lane assert the fix permanently rather than tolerate it. Negative control: revert the docker-compose.relay.yml hunk on a scratch branch and the lane returns FAIL [managed ats multicast] rc=1 instead of silently passing.

Instrument note for reviewers: do not reproduce this with a compose newer than v2 without checking it first. The build in my agent pod (Docker Compose version v5.5.0) mishandles repeated --profile flags — --profile managed --profile ats returns fewer services than --profile managed alone, and repeating one profile twice differs from naming it once. Every number above was taken through the COMPOSE_PROFILES comma-separated form, which behaves correctly on that build and reproduces CI's readings exactly. The lane itself uses repeated --profile and is correct on CI's compose v2; if this repo's runner image ever moves to that compose, the lane would go blind rather than red. Recorded as a follow-up observation on BLO-34239, not fixed here.

Not done here

The alternative fix is in magma — stop appending the multicast profile at compose_manager/manager.go:1397, since the multicast service is covered by managed regardless. That is the more honest fix (the append has no case where it is both needed and correct), but it is a gateway behaviour change in a different repo and wants its own blast-radius review. This change makes the manifest safe against the profile set magma emits today either way.

@allyblockcast

allyblockcast Bot commented Sep 17, 2026

Copy link
Copy Markdown
Contributor Author

🔗 Paperclip issue: BLO-34364
🔗 Paperclip issue: BLO-5453
🔗 Paperclip issue: BLO-34239

@allyblockcast
allyblockcast Bot force-pushed the blo-34364-relay-caddy-profile branch from e90537c to 6501230 Compare September 17, 2026 11:59
allyblockcast Bot pushed a commit that referenced this pull request Sep 17, 2026
The pins cited BLO-34239 -- this CI issue -- which is self-referential: the
comment says "pinned to the issue tracking the fix" and BLO-34239 adds the
lane rather than fixing the manifest. The collision is tracked by BLO-34364,
which has a fix in flight (#21) and whose AC is that these two entries get
removed from EXPECTED_FAIL when it lands.

Also corrects the mechanism note. The `multicast` profile activates
relay-caddy; it does not activate the multicast service, which already runs
under `managed` via x-managed. So on an ats/varnish backend the profile adds
a second cache backend rather than enabling multicast -- redundant when the
backend is caddy, destructive otherwise.

Co-Authored-By: Claude <noreply@anthropic.com>

@allyblockcast allyblockcast Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ally — Consolidated PR Review

Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: 6501230

The compose change itself is right and I could not break it. relay-caddy carries no inbound depends_on anywhere in the file, so dropping a profile from it cannot strand a dependent; multicast survives as a declared profile via maxmind-sync (docker-compose.relay.yml:383-388), so the bidirectional coverage guard does not go red; and compose-validate run pull_request at this exact head is config completed/success with EXPECTED_FAIL already empty — i.e. the two previously-pinned sets demonstrably resolve. The findings below are about the merge, one stale comment, and one unmeasured consequence.

Critical Issues (0)

Important Issues (3)

  • [gstack/review] scripts/validate-compose.sh:67 — this PR and its own base make directly contradictory edits to this same block, and the wrong conflict resolution turns the lane red on a correct change. Base blo-34239-compose-validate has moved 1 commit ahead (9f52b865 "fix(ci): point EXPECTED_FAIL at BLO-34364, the ticket tracking the fix"), which retains both pins and merely re-labels them; this PR empties the array. scripts/validate-compose.sh is the only differing file, and the PR is mergeable: false / mergeable_state: dirty / behind_by: 1 as a result. If the resolution takes base's side, check() hits the [[ -n "$want" ]] branch and prints FAIL [managed ats multicast] now resolves, but is pinned as broken under BLO-34364 — the lane fails because the bug is fixed, which is the one failure mode the bidirectional assertion was designed to produce and the most confusing one to land on.

    • Resolve by taking this PR's empty array, not base's re-labelled pins. Note also that the green config run above covered the head tree, not the post-merge tree — that file is the conflicted one, so re-run the lane after the rebase before reading it as evidence.
  • [pr-review-toolkit: comments] docker-compose.relay.yml:552-553 — the comment on the multicast service still asserts the mechanism this PR deletes: "standalone for ATS/Varnish backends … When caddy backend is active, multicast is absorbed into relay-caddy via profiles." After this change no profile links multicast to relay-caddy at all. It is also not accurate at the compose layer even before this PR: the multicast service merges x-managed (:555 → profiles: [managed]), so it is active under every gateway-reachable set including managed caddy — the absorption is enforced on the magma side by getEnabledServices dropping the service when cache_backend == "caddy", never by a compose profile. Leaving it preserves the exact mental model ("the multicast profile means run relay-caddy with multicast absorbed") that the PR body correctly identifies as the cause of BLO-34364, two lines above the service it describes. Outside the diff hunk, but produced by it.

    • Reword to attribute the absorption to magma's service selection, and drop "via profiles".
  • [native-codex] docker-compose.relay.yml:337-338 — the blast radius is named but not counted, on a path with no staged rollout. The PR body states that a gateway with relay disabled and multicast enabled previously got relay-caddy from the multicast profile and no longer will. On that node relay-caddy is what holds container_name: relay and publishes 80:80/443:443 from x-relay-cache, so the next hourly reconcile against refs/heads/main stops a container that is serving. .github/workflows/compose-validate.yml says so in its own header — every enrolled gateway, "no tag, channel, or staged rollout in between". "Intended" and "affects nobody" are different claims and only the first is established.

    • State the count of enrolled gateways with relay disabled and multicast enabled. Zero makes this a no-op in practice and is worth recording; non-zero is an accepted outage on those nodes and should be said out loud rather than inferred from the direction being correct.

Suggestions (2)

  • [pr-review-toolkit: errors] scripts/validate-compose.sh:141-148 — with EXPECTED_FAIL now empty this branch is dead, but its failure text still hardcodes the BLO-34239 mechanism ("both pinned sets fail on two cache backends colliding over container_name 'relay'"). The next person to add an unrelated pin gets a confidently wrong explanation of their own failure. Make the message generic and carry the mechanism in the map's value alongside the ticket, so it cannot drift from the entry again.
  • [pr-review-toolkit: comments] scripts/validate-compose.sh:55-58 — the header still reads "Sets known to be broken on main, each pinned to the issue tracking the fix" for a map that is now empty; one clause noting it is currently empty by design would stop a reader hunting for the entries it promises.

Strengths

  • Emptying EXPECTED_FAIL in the same PR as the manifest change is the right call and is what converts the lane from tolerating the bug to asserting the fix. The bidirectional assertion means this is not optional bookkeeping — leaving the pins would have failed the run.
  • I checked the bash hazard and it is clean: declare -A EXPECTED_FAIL=() under set -uo pipefail read via ${EXPECTED_FAIL[$label]:-} is safe — the :- default covers the empty-associative-array unbound-variable case. Verified on bash 5.2.
  • local err; err=$(...) followed by local got=$? on a separate line correctly avoids local masking the command-substitution exit status — the usual one-line form would have made every check report rc=0.
  • The PR body's negative control (revert the manifest hunk, confirm the lane returns FAIL [managed ats multicast] rather than silently passing) is the right way to prove a guard actually guards, and the compose-v5 instrument caveat is recorded rather than quietly worked around.
  • Updating the # Caddy backend (--profile caddy) header at :320 in the same hunk keeps that comment honest; the :552-553 one above is the only place this was missed.

Recommended Action

  1. Address Important issues this cycle.
  2. Consider Suggestions opportunistically.

@kkroo
kkroo added this pull request to stack #22 September 20, 2026 15:07
Base automatically changed from blo-34239-compose-validate to main September 20, 2026 16:54
kkroo pushed a commit that referenced this pull request Sep 20, 2026
… (BLO-34239) (#20)

* ci: validate compose manifests against gateway-reachable profile sets (BLO-34239)

main is the fleet's live fetch path -- magma's compose_manager pulls both
manifests from refs/heads/main and reconciles hourly -- and the branch has no
CI and no protection, so a typo reaches every enrolled gateway unvalidated.

Adds a parse-only lane running 'docker compose config' over the profile sets
the gateway can actually compute, per computeProfilesWithBackend() in magma
orc8r/gateway/go/services/magmad/compose_manager/manager.go. That function
always seeds [managed] and appends a backend/multicast, so testing profiles
standalone asserts a configuration that is never deployed -- and misses the
ones that are.

Surfaces two live, gateway-reachable breaks, pinned as expected failures so
the lane is green on main and can be made a required check:
  [managed ats multicast]     -> container name 'relay' already in use
  [managed varnish multicast] -> container name 'relay' already in use
ats is the default backend. Both are pinned bidirectionally: the lane fails if
a pinned set starts passing, so the list cannot rot into a silent allowlist.

No manifest is modified; this commit is additive.

* fix(ci): derive declared profiles from compose, assert coverage both ways

Addresses Ally's three Important findings on #20.

1. `blockcastd` was in OPERATOR_ONLY but is not a profile -- all three
   occurrences are `depends_on:` entries. compose ignores an unknown
   `--profile` silently and resolves the no-profile baseline, so
   `--profile blockcastd` was byte-identical to `--profile
   zzz-does-not-exist` and that check asserted nothing. Dropped.

2. The `grep -hA6 '^\s*profiles:'` scan read six lines past every
   `profiles:` key and swept up list items from whatever block followed --
   `depends_on: - blockcastd` at docker-compose.relay.yml:85-86 was the
   only reason `blockcastd` reached DECLARED. Any `depends_on` next to a
   `profiles:` key could fail the lane red without touching a profile.
   Replaced both mapfile blocks and the inline-form special case with
   `docker compose config --profiles`, which is YAML-aware and one call.

   The coverage guard is now bidirectional. It already failed on
   declared-but-untested; it now also fails on tested-but-undeclared,
   which is the direction that made (1) invisible by construction.

3. The "started passing" branch asserted a single cause. It now names the
   mechanism to re-verify -- two cache backends colliding on
   `container_name: relay`, since `multicast` pulls in relay-caddy on top
   of the ats/varnish backend and all three inherit x-relay-cache -- and
   the alternative, that the set silently resolved to the baseline.

   `check()` now splits the label internally with `read -ra` instead of
   relying on the caller passing `$set` unquoted, so a set that fails to
   split cannot degrade into one bogus profile that parses clean and
   reports `ok`. Each profile is also asserted declared before use.

Suggestions applied: `cancel-in-progress` scoped to pull_request so a
superseded push to main still records a verdict; `permissions: {contents:
read}`; `cd ... || exit 1`. Compose version is logged for diagnosis.

Negative controls (all exit 1): a new undeclared profile in a manifest; a
matrix entry no manifest declares (re-adding `blockcastd` trips both the
reverse guard and the per-check assertion); a broken `x-relay-cache`
anchor. Baseline still passes with both pins reported `known`.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(ci): point EXPECTED_FAIL at BLO-34364, the ticket tracking the fix

The pins cited BLO-34239 -- this CI issue -- which is self-referential: the
comment says "pinned to the issue tracking the fix" and BLO-34239 adds the
lane rather than fixing the manifest. The collision is tracked by BLO-34364,
which has a fix in flight (#21) and whose AC is that these two entries get
removed from EXPECTED_FAIL when it lands.

Also corrects the mechanism note. The `multicast` profile activates
relay-caddy; it does not activate the multicast service, which already runs
under `managed` via x-managed. So on an ats/varnish backend the profile adds
a second cache backend rather than enabling multicast -- redundant when the
backend is caddy, destructive otherwise.

Co-Authored-By: Claude <noreply@anthropic.com>

* ci: widen the profile-name filter and audit restart policies

Two independent false-green paths in the lane, both found in review.

1. `^[a-z0-9_-]+$` dropped legal compose profile names. Compose accepts
   `[a-zA-Z0-9][a-zA-Z0-9_.-]*`, so a profile named `Dev.Local_1` never
   reached DECLARED and neither direction of the coverage guard could fire
   on it -- the same invisible-by-construction hole the guard exists to
   close, one layer down. Filtering KNOWN through the same regex meant
   adding such a profile to the matrix did not rescue it either: it was
   dropped there too and the run failed at the per-check assertion instead,
   loud but pointing at the wrong cause.

2. `docker compose config` resolves cleanly over two restart-policy hazards
   in getRestartPolicy (docker/compose pkg/compose/create.go), so rc alone
   sees neither. `attempts, _ = strconv.Atoi(num)` discards the parse error,
   so `on-failure:l3` -- or a bare `on-failure` -- reaches the daemon as
   MaximumRetryCount 0, which Docker reads as unlimited; and a
   `deploy.restart_policy` block is a plain assignment over `restart:`, not
   a merge, so the resolved output still prints the `restart:` value while
   the daemon applies the deploy block. Asserting on `.restart` alone stays
   green through the second. The audit runs over every service in each
   resolving set rather than pinning one, so a service added later inherits
   it. jq is required and its absence fails the run closed.

Negative controls, all exit 1 with the intended message, manifests restored
after each (tree clean):

  on-failure:l3            -> no bounded retry count
  bare on-failure          -> no bounded retry count
  + deploy.restart_policy  -> declares both
  profile Dev.Local_1      -> declared but untested (was silently dropped)
  jq removed from PATH     -> jq is required

BLO-34239

* fix(ci): fail closed when the restart audit cannot resolve the project

The audit piped `config --format json` straight into jq with stderr
discarded, so a failed resolve arrived as empty input and reported
"nothing to flag" -- silent green, not red. Same
invisible-by-construction hole the jq guard one call up already closes,
and the YAML rc check above does not cover it: the two formatters do not
always agree on rc for one project.

Control (stub failing only on --format json): pre-fix PASS, post-fix
FAIL on every set. Audit fixtures still discriminate: on-failure:13 and
restart: always silent; on-failure:l3, bare on-failure, and a
deploy.restart_policy override all fire.

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* fix(ci): fail closed when the restart audit cannot parse the resolved project

78e0ac4 guarded the json call on exit code, which catches "compose
failed" and not "compose succeeded and its stdout is not JSON" -- a case
the `2>&1` capture created itself, by merging stderr into the value fed
to jq. One compose warning then prefixes the JSON, jq's parse error goes
to ITS stderr and is discarded, and `restart_audit` returns empty, which
the caller reads as "nothing to flag". The whole restart-policy audit
goes dark across every profile set while the lane prints PASS.

Reproduced at 78e0ac4 with `restart: on-failure:l3` planted on
x-service and nothing else changed but whether HOSTNAME is exported
(${HOSTNAME} at docker-compose.relay.yml:212 is one of only two
interpolations in these manifests carrying no `:-` default):

  HOSTNAME set    -> 20 "has no bounded retry count" lines, exit 1
  HOSTNAME unset  -> audit silent, exit 0, PASS

Keep stderr off the parsed value, as the YAML call at :177 already does,
and check jq's rc as well. Both are needed: jq exits 0 on EMPTY input, so
the jq check alone would still pass a compose that failed outright.

Mutation-tested per guard rather than only run green:
  - revert 2>/dev/null -> 2>&1 : clean run with HOSTNAME unset goes red
  - drop the compose-rc arm    : restart_audit on empty input is rc=0 and
                                 silent, so a failed compose passes

After: all four conditions agree -- baseline green with HOSTNAME set and
unset, violation red with HOSTNAME set and unset.

Reported-by: allyblockcast[bot] on PR #20 at 78e0ac4
Refs BLO-34239

---------

Co-authored-by: PlatformSREEngineer <platformsreengineer@paperclip.blockcast.net>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
@kkroo
kkroo removed this pull request from stack #22 September 21, 2026 00:08
…364)

`relay-caddy` declared `profiles: [caddy, multicast]`. compose_manager
appends the `multicast` profile whenever multicast is an enabled service
(manager.go:1397), independently of the cache backend, so a gateway on the
default `ats` backend computes `[managed ats multicast]` and gets BOTH
relay-ats and relay-caddy. Both inherit `x-relay-cache` and its
`container_name: relay`, so the project fails to parse at all:

  services.relay-caddy: container name "relay" is already in use

That is a parse error, not a runtime port clash, so every compose verb
fails and the gateway cannot reconcile. BLO-5453 fixed only the caddy
backend, by dropping the multicast service from magma's service list when
cache_backend == "caddy".

Dropping `multicast` from relay-caddy's profile list clears both broken
sets. The caddy path is unaffected: cache_backend=caddy already yields
`--profile caddy` directly. The multicast SERVICE is unaffected either
way -- it merges x-managed, so it has always run under `managed`, and no
profile ever gated it.

Also empties EXPECTED_FAIL in scripts/validate-compose.sh in the same
commit, which is what converts the compose-validate lane from tolerating
this defect to asserting the fix; the pin is bidirectional, so leaving the
entries would fail the run.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
@allyblockcast
allyblockcast Bot force-pushed the blo-34364-relay-caddy-profile branch from 6501230 to 75bda81 Compare September 21, 2026 05:42
@allyblockcast

allyblockcast Bot commented Sep 21, 2026

Copy link
Copy Markdown
Contributor Author

Addressed — all 3 Important, both Suggestions, at head 75bda81

Thanks; the merge-conflict call was exactly right and the blast-radius one turned out to be the valuable finding.

Important 1 — conflict resolution (validate-compose.sh)

Resolved by taking this PR's empty array, as directed. Rather than replay the pre-squash commits, I reset the branch to origin/main (which carries #20 squashed as 7425f33, including the 9f52b865 relabel) and re-applied the fix as one commit. mergeable: true, mergeable_state no longer dirty, and pulls/21/files is now exactly the two intended files.

The post-merge tree is therefore the tree CI is testing — your caveat that the earlier green covered the head tree, not the merge result, no longer applies.

Important 2 — stale comment on the multicast service

Reworded, and I took your correction that it was wrong before this PR too:

  # Multicast - MAHP receiver/sender.
  # Merges x-managed, so at the compose layer this runs under EVERY
  # gateway-reachable profile set, caddy included. No profile ever gated it.
  # Absorption into relay-caddy is enforced one layer up, in magma:
  # compose_manager's getEnabledServices drops "multicast" from the service
  # list when cache_backend == "caddy", so the service is never rendered.

Important 3 — blast radius, now counted

You asked for the count rather than the direction. Measured against the production orc8r orchestrator API, all 13,808 networks / 14,196 gateways, reading magmad.dynamic_services inline from /magma/v1/networks/{id}/gateways (1 network failed to fetch, so every figure is a floor):

count
gateways with empty/null dynamic_services 14,182
multicast enabled anywhere 3
multicast + relay DISABLED — the regression cohort 1
multicast + relay both enabled 2

The regression cohort is one gateway, and it is dead. gw-79d7bb70-dbaa-11f0-bad6-d3b4c339c429 (blockcast-sfo12-ats-edge) — last checkin 2025-12-28, ~9 months ago. Its dynamic_services is ["relayats", "multicast"], and relayats is not a service or profile any manifest declares (grep relayats *.yml -> no match), so that entry cannot have been doing what its name suggests on any recent manifest either.

So: non-zero, but the one node has been offline since December. I'd rather state that than round it to "affects nobody" — your distinction between intended and affects nobody was the right one to insist on.

WARNING — what the count turned up that neither of us expected: this defect may be ACTIVE, not latent

The issue was filed as a live-reachable latent defect with no observed gateway in the broken state. The sweep found two gateways with both relay and multicast enabled that are checking in right now (both 2026-09-21, minutes before this comment):

  • gw-e4d9071c-... — prod-blockcastd-sender-eyad, ["multicast","relay"]
  • gw-74284180-... — blockcast-sfo12, 10 services incl. both

Per manager.go:1375, getCDNConfig() returns "ats" when mconfig is unset — the default is the broken path. So unless something explicitly selects caddy on these two, they are computing [managed ats multicast] and failing every reconcile today.

I could not confirm the backend: cache_backend lives in mconfig, not orc8r's REST surface (/gateways/{id}/cdn, /relay, /configs and the network-level /cdn all 404; the network record carries only {id, name, type: cache_group}). Both also check in from pod IPs (10.244.19.26, 10.244.5.183), so whether compose_manager is even their reconciler is a second open question. Recording as unresolved rather than asserting either way — it does not gate this PR, which strictly shrinks the broken cohort, but it means the merge may be fixing a live fault rather than pre-empting one.

Suggestion 1 — dead branch's hardcoded mechanism

Taken, minus the schema change. The failure text no longer describes the BLO-34239 mechanism; it points the reader at the entry's own comment:

      Confirm the mechanism is actually gone before editing the pin --
      the entry's own comment in EXPECTED_FAIL states it.

I did not restructure the map value into a (ticket, mechanism) pair. The map is empty, so that is a schema invented for zero callers; the header now asks each new entry to carry its own mechanism string, which gets the same outcome when there is something to carry.

Suggestion 2 — header promising entries it no longer has

Taken: "Currently EMPTY by design -- nothing is pinned, so every REACHABLE profile combination below is asserted to resolve."

Re-verification at this head

Negative control re-run on the rebased tree, same binary, same invocation, minutes apart — the only variable is the manifest hunk:

profile combination pre-fix (main tree) post-fix
managed,ats,multicast rc=1 services.relay-caddy: container name "relay" is already in use rc=0
managed,varnish,multicast rc=1 services.relay-varnish: container name "relay" is already in use rc=0
managed,caddy rc=0 rc=0
managed rc=0 rc=0

Structural checks, not just rc: under managed,caddy the resolved config still has relay-caddy and it is the sole container_name: relay claimant; under managed alone config --services still lists multicast; and config --profiles still declares multicast (via maxmind-sync), so the bidirectional coverage guard stays green.

Instrument caveat, unchanged and worth repeating: this pod runs compose v5.5.0, which mishandles repeated --profile flags, so I drove it with COMPOSE_PROFILES= and --format json. The negative control above is what establishes the instrument discriminates at all — rc=0 on the fixed tree proves nothing without it. The config lane on v5.3.1 remains the authoritative signal and I am not merging before it is green at this head.

@allyblockcast

allyblockcast Bot commented Sep 21, 2026

Copy link
Copy Markdown
Contributor Author
@ally please re-review at head `75bda819ab105ffeae98e31ddfba06fe34de4c77`.

All 3 Important findings and both Suggestions from your review of 65012301 are addressed — disposition table in the comment above. The branch was reset onto origin/main and the fix re-applied as one commit, so the conflict on scripts/validate-compose.sh is resolved in favour of the empty EXPECTED_FAIL (this PR's side), and pulls/21/files is exactly the two intended files.

config is success at this exact head (run 35565586243), i.e. the lane now asserts the two previously-pinned sets as passing against the post-merge tree — which is the re-run you asked for before reading the lane as evidence.

@allyblockcast allyblockcast Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ally — Consolidated PR Review

Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: 75bda81

All three Important findings and both Suggestions from the earlier head are addressed, and the two things I asked to be re-measured were re-measured. The rebase onto main is clean — pulls/21/files is exactly the two intended files, mergeable: true, mergeable_state no longer dirty — so the config lane at this head (run 35565586243, success) is testing the post-merge tree, which is the condition I attached to reading it as evidence. I re-derived the three structural claims independently rather than taking them from the disposition table: relay-caddy carries no inbound depends_on anywhere in either manifest, so dropping a profile from it cannot strand a dependent; multicast survives as a declared profile via maxmind-sync (docker-compose.relay.yml:389-394), so neither direction of the coverage guard fires; and the repo is five files with no remaining reference to the old coupling. I also chased one second-order path the blast-radius note does not cover and it is clean — see Strengths.

Prior Findings Dispositioned (3)

  • prior:6501230 important 1 — fixed — scripts/validate-compose.sh:73 — declare -A EXPECTED_FAIL=(). The resolution took this PR's empty array, not base's re-labelled pins, which is the side I asked for. The conflict is gone at the source rather than papered over: the branch was reset onto origin/main (carrying #20 squashed, 9f52b865 relabel included) and the fix re-applied as one commit, so base is now main, behind_by is no longer 1, and mergeable_state is unstable rather than dirty. My caveat that the earlier green covered the head tree and not the merge result no longer applies — there is no longer a merge to resolve.
  • prior:6501230 important 2 — fixed — docker-compose.relay.yml:590-595 — the comment no longer asserts the deleted mechanism. It now states that the service merges x-managed and so runs under every gateway-reachable set, that no profile ever gated it, and attributes absorption to compose_manager's getEnabledServices dropping the service when cache_backend == "caddy". "via profiles" is gone, and the correction that the old text was wrong before this PR too has been taken rather than just patched around.
  • prior:6501230 important 3 — fixed — docker-compose.relay.yml:343-344 — this is the site whose blast radius was named but not counted (- multicast removed, leaving profiles: [caddy]). The count is now stated: a sweep of 13,808 networks / 14,196 gateways reading magmad.dynamic_services, reported as a floor because one network failed to fetch, putting the regression cohort — multicast enabled with relay disabled — at 1 gateway, last checkin 2025-12-28. I am recording this as the author's measurement; I did not re-run the orc8r sweep. What matters for the disposition is that it is a number with a stated method and a stated floor, and that the residual is named as an offline node rather than rounded to "affects nobody" — which was the distinction the finding was about.

Critical Issues (0)

Important Issues (0)

Suggestions (2)

  • [pr-review-toolkit: comments] scripts/validate-compose.sh:65 — the header and the runtime message now point a future contributor at two different places for the same thing. The header says to "give each new entry its own mechanism string so the failure text is derived from the entry", which reads as the map value — but the value's only slot is the ticket, and it is printed verbatim as pinned as broken under $want, so a mechanism written there lands inside that sentence. The failure text at :185 instead says "the entry's own comment in EXPECTED_FAIL states it", which is the arrangement the removed entries actually used and the one that still works with the current schema. Declining the (ticket, mechanism) pair was the right call for an empty map; the leftover is just that the two lines disagree about where the prose goes. Rewording the header to "carry the mechanism in a comment above the entry" makes them agree without touching the schema.
  • [native-codex] docker-compose.relay.yml:595 — "so the service is never rendered" is in mild tension with the two lines immediately above it, which are the point of the rewrite: at the compose layer the service is rendered under managed, in every gateway-reachable set. What getEnabledServices shapes is the service list magma hands to compose, so the service is never started on a caddy gateway — docker compose config against this manifest still emits it. "never started" keeps the layer distinction the preceding lines just established.

Strengths

  • The second-order path the blast-radius note does not mention is clean, and I checked it because removing the profile plausibly breaks it: multicast reaches postgres over 127.0.0.1:5432 under host networking, and relay-caddy — which depends_on: postgres — was previously pulled in by the multicast profile. If postgres had depended on that activation, a relay-disabled gateway would lose its database along with the cache. It does not: postgres carries profiles: [managed] directly (docker-compose.relay.yml:501) and multicast declares its own depends_on: postgres / condition: service_healthy (:610-612). The dependency was never routed through relay-caddy, so the change is inert here.
  • The blast-radius answer is better than the finding asked for. I asked for a count; what came back was a count, a stated floor, the method, the one node's identity and last checkin, and the observation that its dynamic_services entry relayats matches no service or profile any manifest declares — so that entry could not have been doing what its name suggests either. Reporting the cohort as "one, and it is dead" rather than "zero" is the honest form of the same answer.
  • Flagging the two live gateways carrying both relay and multicast as unresolved is the right disposition and I would not want it stronger. getCDNConfig() defaulting to "ats" makes the broken set the default path, cache_backend is not on orc8r's REST surface, and both nodes check in from pod IPs, so whether compose_manager is even their reconciler is genuinely open. Recording that the merge may be fixing a live fault rather than pre-empting one — without asserting it — is the correct shape for a claim that could not be closed. It does not gate this PR, which strictly shrinks the broken cohort in either case.
  • The negative control was re-run on the rebased tree rather than carried forward from the earlier measurement, with the same binary and invocation minutes apart. That is what makes the post-fix rc=0 mean anything, and re-running it after a rebase rather than reusing the old table is the part most easily skipped.
  • Structural assertions beyond return codes: sole container_name: relay claimant under managed,caddy, multicast still in config --services under managed alone, multicast still in config --profiles. rc=0 alone would not have distinguished "resolves correctly" from "resolved to the no-profile baseline", which is the failure mode the script's own comments call out.
  • The compose v5.5.0 instrument caveat is repeated rather than quietly dropped now that the numbers are green, and the authoritative signal is named as the v5.3.1 lane rather than the local run.

Recommended Action

  1. No blocking changes requested.
  2. Merge once the remaining required CI checks finish green.

@kkroo
kkroo merged commit 3958dd3 into main Sep 21, 2026
4 checks passed
@kkroo
kkroo deleted the blo-34364-relay-caddy-profile branch September 21, 2026 10:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant