carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1# Findings on the media roadmap
2
3Reviewed by **GPT-6 (Codex), 2026-09-07**, independently of the draft author, from the brief in `CODEX-REVIEW-BRIEF-MEDIA.md`. Unedited apart from this note. The roadmap's revision 2 records which findings were accepted and how.
4
5Reviewed commit `fd3a671d8c9b8876b940cdc5ae558ade3f0cdba7`. Roadmap line references below refer to [that worktree’s roadmap](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/media-roadmap-spec/docs/superpowers/specs/2026-09-07-media-platform-roadmap-design.md). No files were changed.
6
71. **P1 — §2, stage 1: changing the URL bypasses private and secret access controls.**
8
9 **Problem and evidence:** Lines 200–206 explicitly map `/p/`, `/s/` and `/v/` onto the same underlying prefix. A person who knows `/v/contracts/customer.pdf` can request `/p/contracts/customer.pdf` anonymously. An expired secret link can likewise be changed to `/p/`, with its signature removed. This works on a cold cache; whether the app previously distributed a public URL is irrelevant.
10
11 **Correction:** Make visibility an enforced property of the object or use disjoint underlying namespaces. The caller must not select a weaker access policy merely by changing the URL. Apply this rule to source objects, derivatives and renditions. Stage 1’s acceptance tests must attempt these substitutions against both cold and warm caches.
12
132. **P1 — §2, stage 1: private browser requests have no defined way to carry identity.**
14
15 **Problem and evidence:** Lines 117–119 forward the viewer’s cookies and `Authorization`, but lines 188–198 put media on a different hostname. Host-only app cookies do not accompany requests to that hostname, and an Oficina custom domain cannot set cookies for `oncarlos.com`. Ordinary image and native video requests do not automatically carry an application bearer header. The [home identity design, §5.3](/home/paulca/amadan.net/oficina/home/docs/superpowers/specs/2026-08-30-home-design.md:327) specifies an audience-bound, single-use assertion delivered by auto-POST; it cannot be replayed for every media segment.
16
17 **Correction:** Specify a concrete browser authentication path before treating private media as buildable: for example, a media-session bootstrap whose credential identifies the person while the app still authorizes every fetch. Cover custom domains, native video, session expiry and sign-out. Broadening app cookies to a shared parent domain would require a separate security justification.
18
193. **P1 — §2, stage 1: “authorization never cached” is enforced at only one layer.**
20
21 **Problem and evidence:** Exempting the media route from the shared cache does not exempt an authorization subrequest sent through the app’s ordinary route. [The edge cache lookup](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/media-roadmap-spec/internal/edge/edge.go:1300) returns before the handler, and a cookie-authenticated, positive-`max-age` response can be admitted unless its response headers prevent that. The roadmap does not define a bypassing transport.
22
23 The mandatory media headers at lines 228–234 also omit downstream caching policy. A browser or intermediary can reuse a private response without asking this edge again. A cached secret response can similarly outlive its URL’s expiry. This is the class of failure named in [doctrine leak item 11](/home/paulca/amadan.net/oficina/suite/docs/superpowers/specs/2026-09-05-suite-sharing-doctrine.md:1026).
24
25 **Correction:** Require authorization checks to bypass every response cache, use an app-bound transport, and fail closed on timeout, redirect or malformed response. Require `no-store` on private HTTP responses and authorization responses; keep the byte cache internal. Define secret-response freshness so reuse cannot exceed token validity. Perform authorization before returning `HEAD`, `304`, range metadata or cache-hit information.
26
274. **P1 — Stage 1 and stage 5 safety: the new reader bypasses existing malware enforcement.**
28
29 **Problem and evidence:** Lines 208–214 grant host roles `GetObject`, while lines 437–440 defer scanning. Existing enforcement is already implemented as a deny on the **store user’s inline policy**, keyed on an infected-object tag. A separate host role does not inherit it. See [policy construction](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/media-roadmap-spec/internal/objstore/aws.go:129) and [the infected-object deny](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/media-roadmap-spec/internal/objstore/aws.go:211). An infected object refused through app credentials would remain readable through media. A warm cache adds another bypass after a verdict changes.
30
31 **Correction:** Preserve enforcement for the new principal and cached bytes. Define how verdict changes invalidate or block cached objects and their derivatives. Prove that an enforced infected object fails through direct storage and cold/warm media access.
32
335. **P2 — Stage 1 signing: the roadmap selects cryptography without defining the authorization it signs.**
34
35 **Problem and evidence:** Lines 193–197 and 216–222 specify URL fields and Ed25519 keys, but not the signed claims. There is no stated binding for app/store identity, object versus prefix scope, expiry, path normalization or permitted operations. A textual prefix such as `photos/a` must not silently authorize `photos/another`; encoded separators and duplicate query parameters must not be interpreted differently by the signer and reader.
36
37 Stage 2 compounds this at lines 285–286 by requiring parameters to be covered by “the signature” for private objects, although `/v/` has no signature.
38
39 **Correction:** State the required signed claims and scope rules now; leave the serialization format to the stage spec. Include domain separation, canonical object identity, boundary-aware prefix matching, expiry and allowed transformations. Private transforms must be approved by the app check or restricted to presets. Define maximum token life, overlapping-key retirement, emergency revocation and stale-edge behavior. Also require capability-query redaction from logs and an explicit referrer policy.
40
416. **P1 — Stages 1 and 3: a prefix signature in the playlist query does not authorize its segments.**
42
43 **Problem and evidence:** Lines 104–111 and 350–352 promise HLS playback through one prefix-scoped URL. A relative reference such as `720p/index.m3u8` or `segment001.m4s` does **not** inherit the base playlist’s query parameters. Resolving the roadmap’s signed playlist URL therefore produces unsigned child requests. This follows [URI reference resolution](https://www.rfc-editor.org/rfc/rfc3986#section-5.2.2) and [HLS’s relative-URI rules](https://www.rfc-editor.org/rfc/rfc8216#section-4.1).
44
45 **Correction:** Choose a transport that carries the capability to every playlist, initialization section and segment: a capability-bearing path, playlist rewriting, or another explicitly supported mechanism. If rewriting playlists per capability, do not cache the resulting bearer-bearing playlist solely by object key. Prefix scope alone does not remove this requirement.
46
477. **P2 — Stage 1: range and conditional handling do not come free from S3.**
48
49 **Problem and evidence:** Lines 48–51 and 225–227 equate passing headers to S3 with `http.ServeFile`. The [static handler](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/media-roadmap-spec/internal/edge/static.go:361) delegates local-file behavior to Go; a streaming S3 handler must implement its own HTTP mapping. S3 also does not support multiple ranges in one `GetObject` request. See [GetObject’s documented behavior](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObject.html).
50
51 The proposed object-key-and-range cache needs rules for `If-Range`, validators, changed object lengths, suffix ranges and partial fills. Otherwise a cached range can be combined with metadata or other ranges from a different object version.
52
53 **Correction:** Define supported range forms, conditional precedence, `HEAD`, response statuses and validator handling. Bind cached data to an object version and publish cache entries only after a complete successful fill. Evaluate request conditions against that version before replaying bytes.
54
558. **P2 — Stage 1 cache: per-route caps plus a global LRU do not guarantee tenant isolation.**
56
57 **Problem and evidence:** Lines 236–243 specify per-route byte limits and a “fleet-wide budget, LRU eviction,” without saying which route loses entries when total capacity is exhausted. A busy tenant can remain below its own cap while repeatedly displacing another tenant’s entries from a global LRU. This is the same failure the roadmap identifies in the shared response cache; [current capacity enforcement](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/media-roadmap-spec/internal/edge/cache.go:330) illustrates it.
58
59 Arbitrary overlapping ranges also allow many stored copies of substantially the same bytes.
60
61 **Correction:** Specify per-box total capacity, tenant-fair admission and eviction, and account-level bounds across multiple routes. Reserve filesystem headroom and bound in-flight fills. Normalize ranges into reusable blocks or impose equivalent limits. A fleet-wide accounting total is distinct from a disk budget enforceable on one machine.
62
639. **P2 — Stage 1 purge: the claimed regional fanout does not exist.**
64
65 **Problem and evidence:** Lines 245–248 say purge reaches every region through the existing listener. [That listener](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/media-roadmap-spec/internal/edge/purge.go:108) is a local Unix socket operating on one host’s in-process cache. [The response-header mechanism](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/media-roadmap-spec/internal/edge/purge_header.go:64) can target a same-app sibling hostname, but only on the current edge. Neither mechanism distributes invalidations or addresses the proposed disk cache.
66
67 **Correction:** Make durable regional invalidation an explicit stage 1 deliverable, including disconnected-edge recovery and protection against an in-flight fetch repopulating an entry after purge. The API and CLI must distinguish request acceptance from completion. Define disable, source deletion and derivative deletion through the same lifecycle. Purge cannot withdraw copies already delivered to browsers.
68
6910. **P2 — Stage 1 residency: DNS steering is not a data-residency boundary.**
70
71 **Problem and evidence:** Lines 250–262 promise that unsteered media stays in its bucket’s region, yet lines 208–210 grant every regional role access. The [multi-region ingress design](/home/paulca/github.com/carlosframework/platform-infrastructure/platform-design/superpowers/specs/2026-08-20-multi-region-ingress-design.md:18) deliberately makes every edge serve every app and separates serving capability from DNS steering. A client can address a different edge directly; DNS defaults do not prevent a foreign-region cache fill.
72
73 Steering is also per route, so an app with mixed route settings has no single existing value for media to inherit.
74
75 **Correction:** Put allowed serving/cache regions in the media record and enforce them in the handler and grants. Define an explicit media steering default. Include derivative processing and the Ireland worker in residency policy: foreign-region source processing is another copy, even without an edge cache.
76
7711. **P2 — §2: the doctrine mapping is not exact.**
78
79 **Problem and evidence:** Lines 90–98 present public/secret/private as the doctrine’s three visibilities. [Ruling 15 and §4.2](/home/paulca/amadan.net/oficina/suite/docs/superpowers/specs/2026-09-05-suite-sharing-doctrine.md:283) instead define `private`, `granted` and `team`, with a read-only link capability on a separate axis. A team-readable item still needs authenticated delivery; it is not publicly readable. A link does not confer sibling custody.
80
81 **Correction:** Describe these as media delivery mechanisms. Explicitly map all three item rungs to app-authorized delivery and preserve links as an orthogonal capability. Media publication must not become evidence that a sibling may ingest the source item.
82
8312. **P2 — §7: the configurable third-party relay contradicts the doctrine.**
84
85 **Problem and evidence:** Lines 505–508 call a per-instance ICE-server setting admissible under ruling 6 and retain it for teams choosing their own relay. [Doctrine §8](/home/paulca/amadan.net/oficina/suite/docs/superpowers/specs/2026-09-05-suite-sharing-doctrine.md:934) expressly says the app chooses the named recipient and forbids a team-configurable endpoint. It also requires disclosure in the README’s first paragraph and on produced artefacts, not merely unspecified disclosure.
86
87 **Correction:** Distinguish the operator’s platform relay from an app-selected external provider with per-team opt-in and the required working floor. Arbitrary team-selected external relays need an explicit doctrine amendment. The platform-relay custody argument does not authorize that separate exception.
88
8913. **P2 — Stage 4a: local verification does not require every relay to hold every account’s minting secret.**
90
91 **Problem and evidence:** Lines 374–385 put a durable account secret in app config and every relay’s route record, then claim this is unavoidable. Local verification requires appropriate credential-verification material; it does not require distributing the same durable minting authority to both apps and all regions. Account-wide material also lets one enabled app mint credentials indistinguishable from another app’s, undermining the promised per-app accounting.
92
93 **Correction:** Prefer an authenticated credential-issuance API that returns short-lived, region/app-scoped TURN credentials, with relay-owned secrets or temporary verifier material. Verification during TURN operation can remain local. If offline app minting is a firm requirement, state that tradeoff explicitly and define app scope, maximum expiry, rotation, disable behavior and secret storage outside ordinary route records. TURN’s [third-party authorization specification](https://www.rfc-editor.org/rfc/rfc7635) is another option to evaluate against actual browser support.
94
9514. **P2 — Stage 4a: relay authentication does not constrain where traffic goes or how much it consumes.**
96
97 **Problem and evidence:** The relay plan defines credentials and metering but no peer-address policy or allocation limits. A credential holder can use TURN outside Meet’s call protocol. Pion’s [default permission handler admits all peers](https://github.com/pion/turn/blob/main/server_config.go), so an unconfigured service can relay into addresses reachable from the box, including internal networks. Short-lived credentials alone do not prevent port exhaustion or sustained bandwidth consumption.
98
99 **Correction:** Include peer-address restrictions for loopback, link-local, private/infrastructure ranges and relay loops, with explicit exceptions where needed. Bound allocations, creation rate, bandwidth and lifetimes per app/account and box. Define what happens to established allocations when credentials expire or relay access is disabled. Operational limits belong in stage 4a even if commercial quotas wait.
100
10115. **P2 — Stage 4a: the port and placement plan promises more connectivity than it establishes.**
102
103 **Problem and evidence:** Lines 371–388 offer TCP 3478 and TLS 5349 while reserving 443 for the edge. Networks allowing only outbound 443 will still fail. The plan also needs the relay allocator to advertise the box’s public Elastic IP and allocate only inside the opened range; Pion exposes these as [explicit configuration](https://raw.githubusercontent.com/pion/turn/master/relay_address_generator_range.go).
104
105 Ordering servers by the app instance’s region does not identify the region nearest either participant. ICE selects and nominates viable candidate pairs according to its procedures; “ICE picks the fastest” is not a latency-selection guarantee. See [RFC 8445](https://www.rfc-editor.org/rfc/rfc8445).
106
107 **Correction:** Either provide TURN/TLS on a separate address using 443 or state the remaining network limitation. Define public-address advertisement, transport URLs, certificates and constrained allocation. Test a 443-only network and geographically separated participants. Treat client-aware relay selection as a separate decision.
108
10916. **P2 — §3 and stage 2: the in-process transform candidate does not satisfy the helper isolation rule.**
110
111 **Problem and evidence:** Section 3 requires a separate program under a hardened unit with a CPU quota. Lines 293–295 put the WASM candidate inside the edge binary. That computation shares the edge’s CPU and memory limits; it does not acquire a separate systemd resource boundary. The [edge unit](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/media-roadmap-spec/internal/host/units/carlos-edge.service:72) already permits substantial host-memory use.
112
113 The spike measures one image and one latency threshold. It does not establish behavior under concurrent requests, expensive AVIF encoding, malformed inputs or sustained load. “Roughly twice as slow” is also unsupported by a pinned dependency and fleet measurement.
114
115 **Correction:** Run every candidate behind equivalent worker isolation or explicitly amend the rule. Benchmark peak memory, sustained throughput and serving latency as well as thumbnail time. Set output-dimension, memory, wall-time, scratch-space and process limits. Ship helpers with an authenticated manifest, their required libraries/codecs, and an update/rollback path for existing boxes—not bootstrap installation alone.
116
11717. **P2 — Stage 2: mutable sources and negotiated formats have no stable derivative identity.**
118
119 **Problem and evidence:** Lines 310–313 derive the storage path from source key and parameters, although stage 1 permits mutable keys. Replacing `photos/cat.jpg` leaves the old derivative at the same path unless the app performs a separate purge. A transform already running during purge can restore stale output afterward. Lines 304–305 also select format from `Accept`, which must participate in representation identity.
120
121 **Correction:** Include source version, normalized parameters, resolved output format and relevant preset/engine version in derivative identity. Bind authorization to that source identity, and fence publication against deletion or replacement. Define `Vary: Accept` or redirect to explicit-format URLs. Apply resource limits fairly across tenants; a process-wide single-flight and concurrency cap alone do not prevent one tenant monopolizing all work slots.
122
12318. **P2 — Stage 3: job submission, worker authority and crash recovery are missing architectural dependencies.**
124
125 **Problem and evidence:** Lines 328–335 say an app writes a job into a platform data bucket, after which the worker reads source objects, writes derivatives and calls the app’s socket. Delivered store credentials authorize the app’s own bucket, and stage 1’s new host-role grant is read-only. No app submission authority or worker output grant is specified. A worker in Ireland also cannot directly call a Unix socket on another host.
126
127 A conditional claim alone does not define crash recovery. A permanent claim strands a job after a crash; an expiring claim allows overlapping workers unless publication is fenced.
128
129 **Correction:** Specify an authenticated app/API submission door, server-derived source and destination scope, worker read/write authority and remote completion delivery. Use leases with fencing, bounded retries, cancellation, terminal failure states and atomic publication of complete rendition sets. App deletion must cancel work and prevent late writes.
130
13119. **P2 — §1 and stage 3: the proposed queue overlooks an existing queue and repeats its documented deletion trap.**
132
133 **Problem and evidence:** Line 70 says no job queue exists. There is no generic media queue, but [the scan queue](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/media-roadmap-spec/internal/objstore/queue.go:1) already stores one bucket object per job, with enqueue and bounded-drain machinery. Its header explicitly explains why jobs live under the app prefix: top-level queues survive app deletion invisibly. Stage 3 proposes a top-level `jobs/` prefix without addressing that problem.
134
135 **Correction:** Narrow the claim to “no generic media job queue.” Reuse or explicitly adapt the existing queue’s ownership and lifecycle rules. Put jobs where app deletion finds them, or extend deletion and name-reuse checks deliberately.
136
13720. **P2 — Stage 3 and §9: the Ireland worker’s capacity guarantee is unsupported.**
138
139 **Problem and evidence:** Lines 339–342 say `Nice=` and a CPU quota ensure serving is never starved. They do not isolate memory, disk capacity, I/O or network contention. A `t4g.medium` has the same two vCPUs and 20% baseline per vCPU as a `t4g.small`; it primarily adds memory. Sustained encoding spends CPU credits or incurs surplus charges, depending on configuration. See [AWS’s baseline table](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/burstable-credits-baseline-concepts.html).
140
141 The two-minute acceptance test at lines 538–540 has no source duration, resolution, codec or queue-depth bound.
142
143 **Correction:** Limit the first worker’s admitted source size, duration and concurrent jobs. Set memory and scratch limits and measure serving latency, I/O and credit consumption under sustained load. The first expected sustained bottleneck is encode throughput; without memory and scratch limits, host pressure can fail earlier. Move workers based on those measurements, before queues reach hours.
144
14521. **P2 — Stage 3: remuxing cannot produce the promised adaptive ladder.**
146
147 **Problem and evidence:** Lines 344–348 promise three resolutions and a remux-only fast path whenever input is H.264/AAC in MP4. Remuxing preserves encoded video; it cannot create lower-resolution rungs. Codec names alone also do not establish suitable keyframe spacing, profile or other playback properties. FFmpeg’s [segmenting documentation](https://ffmpeg.org/ffmpeg-formats.html#hls-2) makes keyframe placement material to segment boundaries.
148
149 **Correction:** Separate single-rendition remux from adaptive-ladder generation. Define fast-path eligibility and probe the actual input. State when lower rungs arrive and when re-encoding is required. Bound the Safari acceptance test to representative input fixtures.
150
15122. **P2 — Stage 4b: LL-HLS needs serving behavior beyond an ordinary object cache.**
152
153 **Problem and evidence:** Lines 406–408 describe live streaming as the same worker packaging LL-HLS for the existing serving path. LL-HLS requires partial segments, delivery directives, blocking playlist reloads and freshness behavior that an S3 object reader does not automatically supply. These are explicit [Apple delivery requirements](https://developer.apple.com/documentation/http-live-streaming/enabling-low-latency-http-live-streaming-hls).
154
155 LiveKit is also proposed as a §3 helper at lines 401–402, although that rule forbids network access. An SFU requires network access.
156
157 **Correction:** Keep live packaging as a distinct long-running workload with an explicit serving extension and latency contract. Separate network-facing media services from no-network codec helpers. The implementation may reuse components, but the roadmap must not promise reuse of unchanged serving semantics.
158
15923. **P2 — Stages 1 and 5: browser CORS requirements are incomplete, and upload CORS cannot be prefix-scoped.**
160
161 **Problem and evidence:** Lines 420–424 say S3 CORS is set “on the media prefix.” S3 CORS is a bucket-wide configuration; its [rule elements](https://docs.aws.amazon.com/AmazonS3/latest/userguide/ManageCorsUsing.html) have no object-prefix selector.
162
163 Separately, `Cross-Origin-Resource-Policy: cross-origin` at line 230 is not CORS permission. Cross-origin JavaScript HLS playback, fonts, authenticated fetches and canvas use need an actual media-response CORS policy.
164
165 **Correction:** Define bucket-wide CORS ownership, merge and disable behavior. Include multipart initiation/completion and exposed response headers. Specify media-origin CORS independently, including allowed origins, credentials, preflights and `Vary: Origin` where necessary. Keep CORS distinct from object authorization.
166
16724. **P2 — Stage 1: the chosen hostname falls outside the existing wildcard certificate shape.**
168
169 **Problem and evidence:** Lines 186–198 introduce `media.<app>.<sqid>.<apps-domain>`. [Wildcard parent derivation](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/media-roadmap-spec/internal/edge/wildcard.go:186) recognizes exactly two labels below the apps domain and obtains `*.<sqid>.<apps-domain>`. That certificate does not cover the proposed three-label media name. Per-host issuance may provide a fallback, but it changes issuance volume and operational assumptions.
170
171 **Correction:** Choose a hostname compatible with the existing wildcard, such as `media-<app>.<sqid>.<apps-domain>`, or explicitly budget a different certificate strategy. Include platform namespace ownership and custom-domain support in the stage spec.
172
17325. **P2 — Stage 1: enable/disable needs an authority lifecycle, not just a bucket-policy edit.**
174
175 **Problem and evidence:** Lines 208–222 cover attaching read permission and distributing keys, but not partial completion, store revocation, route replacement or stale readers. The current [provisioner interface](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/media-roadmap-spec/internal/objstore/provisioner.go:12) has no bucket-policy operation. The new policy writer therefore needs explicit privileged authority and ownership of its statements. Removing bucket access also does not remove bytes already cached on disk.
176
177 **Correction:** Define an additive authoritative media record and a reconciled enable/disable lifecycle. Include safe policy composition, permitted regional principals, key propagation, cache retirement and observable completion through the API/CLI. Define support or explicit refusal for adopted and non-AWS stores. Stage 2 must separately identify who may write and delete derivatives.
178
17926. **P2/P3 — §1, stage 5 and §8: remaining factual claims need correction.**
180
181 **Problem and evidence:**
182
183 - **P2:** The scanner does not await #19’s outcome. It is implemented, and [the sweep explicitly says there is no upload to hook](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/media-roadmap-spec/internal/objstore/sweep.go:1), because writes go directly to S3. “One callback” does not discover those uploads.
184 - **P3:** `infra/modules/region-host/main.tf:76` points to dynamic ingress configuration. The residency header is at [lines 6 onward](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/media-roadmap-spec/infra/modules/region-host/main.tf:6), and bucket permissions begin at [line 106](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/media-roadmap-spec/infra/modules/region-host/main.tf:106). Correct both the evidence and amendment target.
185 - **P3:** “Meet needs STUN to work across the internet at all” is too absolute. Directly reachable ICE candidates can work without STUN; STUN and TURN improve coverage across different network conditions.
186 - **P3:** TURN bytes traversing the box twice are not automatically twice the billable internet egress. Received, transmitted and inter-region bytes need separate meter definitions.
187
188 **Correction:** Update the scanner integration to the existing sweep/queue model, repair the citations, and replace absolute networking and billing claims with explicit coverage and accounting definitions.
189
19027. **P3 — §4, §6 and §9: the stage order imposes unnecessary dependencies.**
191
192 **Problem and evidence:** Lines 493–495 call stage 4a independent and urgently needed, yet still place it after stage 1. Section 9 additionally frames every stage’s proof as a prerequisite for starting the next. Meanwhile stage 1 carries uploads, multipart helpers and a substantial accounting surface that are not all necessary to prove the serving class.
193
194 **Correction:** Let relay design and implementation start independently. Keep access control, browser identity, cache isolation, revocation, enforcement and residency in the stage 1 gate. Upload conveniences and richer dashboards can follow a working serving class. Replace the single sequence with explicit dependencies, and expand security gates to cover the attacks above rather than only successful playback.
195
196**Verdict: request changes.** The roadmap’s current URL mapping defeats its central access guarantees, private browser authentication is unspecified, caching can bypass live authorization, and the new reader bypasses existing scan enforcement. HLS capability transport also fails as written. These require roadmap-level decisions before stage specifications proceed. The remaining corrections identify missing operational contracts and overstated reuse claims; they do not require implementing every later-stage feature now.