Deutschlandfunk stayed silent: its signed URL answers 302 to
f111.rndfnk.com, an origin the signed media-src never listed, so the
browser blocked the redirected load and reported only the station's own
URL in securitypolicyviolation.
The hub now resolves each https stream's redirect chain server side when
a settings save adds or changes it and stores the extra origins as the
stream's via list. media-src lists them next to the signed origins. A CDN
host of three or more labels widens to its siblings (*.rndfnk.com); an
IP, a two-label host and a name under a country code's generic second
level stay exact.
Hardening:
- Only the signed URL itself may lie at a non-global address; every
redirect hop dials a global address only, checked on the address
actually dialed (NAT64 and IPv4-compatible IPv6 count by the embedded
IPv4 address).
- A resolve answers within three socket timeouts even for a server that
drips its headers; a late probe goes on in a daemon thread and a url
still in flight is not probed twice.
- A failed resolution stores via null and retries on a save ten minutes
later; the radio page says so instead of blaming the CSP.
- A save whose settings changed while it was resolved fails instead of
overwriting them (compare and set under the settings lock).
- A new stream URL at an already signed origin takes the passkey too.
- Settings written before via existed load as via [] and never dial.
Residuals:
- The passkey proof is spent before the resolve, so a save racing
another one fails and the operator signs again.
- A save that hits a due retry blocks up to twelve seconds and may
reload the page.
- A page loaded before the upgrade throws on the missing via until it
reloads.
- A private multi-tenant suffix such as github.io still widens.
- getaddrinfo has no timeout; it runs inside the daemon thread.
Operator step after install: remove Deutschlandfunk in Radio, Sender
bearbeiten, save, add it again and save with the passkey.
🎯 Fix: 92 %
💥 Break: 4 %
💸 Cost: 100 %
⏱️ Time: –
🛡️ Security: 100 %
🏆 Score: 88
🚦 Gate: ✅ 90/5, cost and time <= 100, security 100 %
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Surface (requirement 001 phase 2): navigation Stream (Inbox, Regeln) / Agents / Artifacts with keys 1-3 and hash routes (#graph and #tree redirect), header with title, command palette and settings only, server-side badges (unsnoozed inbox, beats within 900 s, open jobs), a Mayday banner with "aufheben", four stream chips with agent:/art:/seit: search, Filter and time menus, the inbox in main, an agents toolbar (Funk an alle with Newsletter, Broadcast, Pan-Pan, Security, Mayday; Priorität; Tag/Nacht), Post or Call in the agent panel, Ambiente in the settings and read-aloud over the seven classes with Mayday locked.
Rename (decision 9): hub/ is orchestrator/, the package kvb_orchestrator, the unit kvb-orchestrator-runner.service, scripts/orchestrator.sh, environment KVB_ORCHESTRATOR_*, make targets orchestrator-*, the MCP server, image and compose project kvb-orchestrator, the guard header X-Kvb-Orchestrator and the message marker [kvb-orchestrator msg <id>]. install runs migrate first: it stops the old container, removes its image and code, moves state, approvals and pinned keys, swaps the MCP registration, the PermissionRequest hook and the runner unit, and changes nothing on a second run (scratch-HOME test with stub docker, claude and systemctl). Saved views fall back to the old localStorage key once.
MEDUSA acceptances carried to the renamed paths: 82 rows whose lines are unchanged (reason prefixed "renamed from <old path>, line unchanged"), and 7 rows whose text changed only in the product name, the package name, a temp prefix or a new class argument, each upheld by one kvb-think-criticize skeptic (approval.py:60, messaging.py:53, server.py:56, tests/hub_support.py:64, passkey.py:205, page-agent.js:75, language.py:46).
Cost 101 %: the longer name adds 15 tokens of skill text; the operator approved it with the commit.
🎯 Fix: 92 %
💥 Break: 4 %
💸 Cost: 101 %
⏱️ Time: –
🛡️ Security: 100 %
💰 Value: ?
🏆 Score: 87
🚦 Gate: ⚠️ cost 101 % > 100: ask the operator before the commit
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>