@@ -269,18 +269,161 @@ MUST NOT fail if IPv6 probes return network-unreachable errors.
269269On dual-stack systems (the common case), the binding and probing rules
270270in Sections 1 and 2 apply. IPv6 is preferred, IPv4 is the fallback.
271271
272- == 7. Summary
272+ == 7. IPv6 Transition and IPv4 Sunset
273+
274+ === 7.1. IPv6-Only Mode (RECOMMENDED for Localhost)
275+
276+ For localhost Groove (the default scope), IPv6-only binding is RECOMMENDED.
277+ Every operating system capable of running Groove has a functional IPv6
278+ loopback (`::1`). There is no legitimate reason to use IPv4 loopback for
279+ local Groove discovery.
280+
281+ **Startup procedure:**
282+
283+ . Probe `::1` — attempt to bind a test socket to `[::1]:0`
284+ . If successful → bind to `[::1]:PORT` only. Do not create an IPv4 socket.
285+ . If unsuccessful (no IPv6 stack) → fall back to `127.0.0.1:PORT` and emit
286+ a deprecation warning to the system log.
287+ . Report the binding mode in the Groove Manifest:
288+
289+ [source,a2ml]
290+ ----
291+ @groove-manifest(version="0.1.0"):
292+ @system(id="my-service", version="0.1.0")
293+ @transport:
294+ @binding(protocol="ipv6", address="::1", port=6473)
295+ @ipv4-sunset(date="2027-01-01", status="deprecated")
296+ @end
297+ @end
298+ ----
299+
300+ === 7.2. IPv4 Sunset Headers
301+
302+ During a transition period, a Groove service that still accepts IPv4
303+ connections MUST include sunset headers signalling that IPv4 support will
304+ be removed:
305+
306+ [source,http]
307+ ----
308+ HTTP/1.1 200 OK
309+ Content-Type: application/groove+a2ml
310+ Groove-IPv6-Preferred: [::1]:6473
311+ Groove-IPv4-Sunset: 2027-01-01
312+ Groove-Upgrade-To: ipv6
313+ Sunset: Sat, 01 Jan 2027 00:00:00 GMT
314+ ----
315+
316+ [cols="2,3"]
317+ |===
318+ | Header | Purpose
319+
320+ | `Groove-IPv6-Preferred`
321+ | The IPv6 endpoint the client SHOULD use for all subsequent traffic
322+
323+ | `Groove-IPv4-Sunset`
324+ | The date after which IPv4 will no longer be accepted (ISO 8601)
325+
326+ | `Groove-Upgrade-To`
327+ | Directive to the client to upgrade its connection to IPv6
328+
329+ | `Sunset` (RFC 8594)
330+ | Standard HTTP sunset header for the IPv4 endpoint
331+ |===
332+
333+ This follows the same pattern as HTTP Strict Transport Security (HSTS):
334+ the client is told to upgrade, and all subsequent connections use the
335+ preferred transport.
336+
337+ === 7.3. Strip-and-Upgrade on Arrival
338+
339+ When a Groove service receives an IPv4 connection and IPv6 is available,
340+ the service SHOULD perform a strip-and-upgrade:
341+
342+ . Accept the IPv4 connection (refusing causes a confusing connection error)
343+ . Read the Groove request
344+ . Respond with the manifest including `Groove-Upgrade-To: ipv6` header
345+ . For connection handshake (`POST /.well-known/groove/connect`):
346+ accept the connection but return the IPv6 endpoint for all data-plane
347+ traffic
348+ . All subsequent data-plane traffic uses IPv6 exclusively
349+ . The IPv4 socket serves only as a discovery doorbell — the real
350+ conversation happens on IPv6
351+
352+ [source]
353+ ----
354+ Client (IPv4) Groove Service
355+ │ │
356+ │──── GET /.well-known/groove ────────→ │ ← IPv4 accepted (doorbell)
357+ │ │
358+ │←── 200 OK + Groove-Upgrade-To: ipv6 ──│ ← "Come to the IPv6 entrance"
359+ │ │
360+ │──── POST /connect (on [::1]:6473) ──→ │ ← IPv6 connection (real door)
361+ │ │
362+ │←──────── Connected (IPv6) ────────────│ ← All subsequent traffic is IPv6
363+ ----
364+
365+ === 7.4. IPv6 Strict Mode
366+
367+ Groove services MAY support an IPv6 strict mode where IPv4 connections are
368+ refused entirely:
369+
370+ [source,a2ml]
371+ ----
372+ @transport:
373+ @binding(protocol="ipv6-strict", address="::1", port=6473)
374+ @ipv4(status="disabled")
375+ @end
376+ ----
377+
378+ In strict mode:
379+
380+ . No IPv4 socket is created
381+ . IPv4 probes receive no response (connection refused)
382+ . The manifest declares `ipv4.status = "disabled"`
383+
384+ This is RECOMMENDED for new deployments. Legacy compatibility mode
385+ (dual-stack or strip-and-upgrade) is available for transition.
386+
387+ === 7.5. Client Behaviour
388+
389+ Groove clients (including browser extension harnesses) MUST:
390+
391+ . Probe `[::1]` first for all local discovery
392+ . Respect `Groove-Upgrade-To: ipv6` headers by reconnecting on IPv6
393+ . Cache the IPv6 preference for the duration of the session
394+ . NOT fall back to IPv4 after a successful IPv6 connection
395+ . Fall back to IPv4 ONLY if IPv6 probing produces no results AND the
396+ `Groove-IPv4-Sunset` header is absent or the sunset date has not passed
397+
398+ === 7.6. LAN IPv6 Forward Strategy
399+
400+ For LAN discovery, IPv6 provides inherent advantages:
401+
402+ . **Link-local addresses** (`fe80::...`) exist on every interface without
403+ DHCP — zero-configuration addressing
404+ . **mDNS multicast** over `ff02::fb` is cleaner than IPv4 `224.0.0.251`
405+ . **No NAT** — IPv6 link-local addresses are directly reachable on the
406+ segment without NAT traversal
407+
408+ Groove services offering LAN discovery SHOULD use IPv6 link-local multicast
409+ exclusively. IPv4 LAN discovery (mDNS over `224.0.0.251`) is OPTIONAL and
410+ SHOULD be sunset on the same timeline as IPv4 loopback.
411+
412+ == 8. Summary
273413
274414[cols="2,3"]
275415|===
276416| Requirement | Specification
277417
278- | Binding | Dual-stack `[::]:PORT` or both `127.0.0.1` and `[::1]`
279- | Probe order | `[::1]` first, `127.0.0.1` fallback, 500ms timeout each
418+ | Localhost binding | IPv6-only `[::1]:PORT` RECOMMENDED. Dual-stack as transition.
419+ | Probe order | `[::1]` first, `127.0.0.1` fallback only if IPv6 absent
420+ | IPv4 sunset | `Groove-IPv4-Sunset` header with date, `Groove-Upgrade-To: ipv6`
421+ | Strip-and-upgrade | Accept IPv4 as doorbell, redirect data plane to IPv6
422+ | Strict mode | IPv6-strict available: no IPv4 socket created at all
280423| CORS | `Access-Control-Allow-Origin: *` on all groove endpoints
281424| Preflight | `OPTIONS` → `204 No Content` with CORS headers
282- | LAN discovery | mDNS/DNS-SD over `ff02::fb`, explicit opt-in only
425+ | LAN discovery | mDNS/DNS-SD over IPv6 multicast `ff02::fb`, opt-in only
283426| Default scope | Localhost only (loopback interface)
284- | Data plane | Any transport, declared in Idris2 ABI
285- | IPv6 preference | IPv6-preferred, IPv4-fallback , never single-stack
427+ | Data plane | Any transport (HTTP/WS/gRPC/WebRTC/Unix/BEAM) , declared in ABI
428+ | Client behaviour | Cache IPv6 preference , never fall back after successful IPv6
286429|===
0 commit comments