Repository navigation
Description
Parent: #173
P9a-5b: the legal pages go live from the platform
Caller: #173, slice row P9a-5: "The tunnel line and hal's apex move, his sentence stands, hal's exit is named, the live body equals built/, and an item on marketplace's board drops the legal-texts skill's sentence that hal still assembles and serves the pages (marketplace#561's follow-up)." After P9a-5a, which makes platform's copy hold every sentence hal serves and the ruled 30-day sentence, and the sections' owner.
Ruled
- The operator, 08.11.2026 (P9: legal pages, person export and erasure, AI keys and expiry checks move from hal #173's body): "P9a: the legal pages move as soon as the platform builds them, before the big cutover." It replaces Platform on k3s and Flux: from an empty PC to every app live, with hal archived #1's card line 1 (A, 06.11.2026: the live pages stay on hal until the cutover) for the legal pages.
- P9: legal pages, person export and erasure, AI keys and expiry checks move from hal #173 card line 2, ruled „other“ on 09.11.2026 („…wir global und die apps halten sich dran!“), read by the head in P9: legal pages, person export and erasure, AI keys and expiry checks move from hal #173's comment of 09.11.2026 09:19Z as option A's sentence: "his sentence stands" at the go, since P9a-5a puts it into the general part.
- P9: legal pages, person export and erasure, AI keys and expiry checks move from hal #173 invariant 2: "hal's copy of a tool leaves with hal (P11), and until then only one of the two serves a given address."
- hal#110 (ruled shape, 18.10.2026): the three addresses never change and are never redirected. Its live checks C1-C5: the public body hash equals the origin's; no injected script;
legal@hallucinai.deliterally in the HTML; nonel,report-toorreporting-endpointsheader and no CSP reporting; a browser at 390 and 1440 px requestshallucinai.deonly, with no CSP violation. - Owners: tunnel lines change only in a pull request.
How the go works (hal at origin/main e9cfd360)
- The edge stays hal's. Cloudflare's redirect ruleset (
external/cloudflare.tf:278-342,fleet.yaml:3-8) answers every request tohallucinai.deandwwwat the edge, before any tunnel: the four pages and/_rechtliches/…pass to the origin, every other form of a page goes 302 to its exact path, and everything else,/included, goes 302 tohttps://auth.hallucinai.de/. Moving the record changes none of that. So no path hal serves at the apex stops answering: hal's nginx (legal/nginx/default.conf) answers exactly the five paths platform's Ingress routes,try_files $uri.htmlfor the pages and/favicon.ico(404, as platform's), andaliasfor/_rechtliches/. One unverified difference: a file in hal's git-ignoredlegal/assets/on the PC beyond platform'sassets/(an olderappkit-<version>/) stops answering; the committed pages link onlyappkit-0.15.0andlegal-2.css, andCache-Control: no-cachemakes a cached page ask again. - The record moves by one catalog key.
tunnel: platformon thelegalentry's"@"host (fleet.yaml:43):catalog.py's_tunnelaccepts it (onlytargetand the wildcard are refused),external/cloudflare.tf:17-22points the apex's proxied CNAME at the tunnelplatform,external/tunnel.tfdrops the apex's line from hal's ingress, the publish phase skipsreadyfor a host on another tunnel, andwwwkeeps hal's tunnel record (a redirect-only name), which never reaches an origin.cloudflare.redirect.serveneeds the apex as an enabled host, so thelegalentry stays enabled: hal's legal unit keeps running and serving nothing, the rollback target until P11. This is hal#1100's and hal#1148's mechanism; hal's freeze admits it.
What it builds
- The tunnel line.
infrastructure/cloudflared/cloudflared.yamlgains- hostname: hallucinai.detohttp://traefik.traefik.svc.cluster.local:80, before the finalhttp_status:404, without apath: the edge and the Ingress each already admit only the five paths.checksum/configfollows, computed astests/cloudflared/ingress_from_pods_built_from_its_template.shdoes. - The cloudflared cell. For a line without a path, the cell today asks
/and, for a host an Ingress routes, fails on 404 (:92,:115,:123-126); the apex's Ingress routes no/. The cell asks such a host on the firstpathof its Ingress rule instead of/; hopin and skat route/and stay as they are. - The comment at
infrastructure/legal/legal.yaml:104-106drops "and no tunnel line sends the host here yet". - The hal script's end.
tests/legal/legal_sections_assets_and_headers_equal_hals_at_commit.shloses the stylesheet and header comparisons,hal_commitand hal's fetch, since hal serves nothing after the go and platform's copies are the owners. The appkit check stays with its own pin: the wheel's URL and sha256 (today hal's pin, v0.15.0,710bed31…) move into the script, renamedtests/legal/appkit_assets_equal_their_release_wheel.sh; it still fails whenassets/appkit-<version>/differs from the wheel'sappkit/static/orassets/holds anything else. Weighed: deleting it, which drops the only proof that ten vendored files equal a release, or keeping hal's pin, which keeps a predecessor as a source of truth. AGENTS.mdis not changed (the operator, 09.11.2026, platform carries the fleet document set: AGENTS.md within its budget, an architecture overview and runbooks #134 comment 6738858275: no lane changes it). The head names on platform carries the fleet document set: AGENTS.md within its budget, an architecture overview and runbooks #134 the sentences this makes false: "No tunnel line names the host, so hal keeps the public address." (P0 class R, destination item: Platform on k3s and Flux: from an empty PC to every app live, with hal archived #1 card line 1, which the 08.11.2026 ruling replaces for these pages; the ruling stands in P9: legal pages, person export and erasure, AI keys and expiry checks move from hal #173's body), and theassets/sentence ending "until hal no longer serves the apex" (class D); the hal script's clause if P9a-5a left one.docs/personal-data.md: the "The promise:" lists gainallgemein#server-protokolle, so a lane changing retention finds the one general promise (the gate of The speech section takes hal's last ruled sentence; platform owns the legal sections #324, F3).
Size: three production files (cloudflared.yaml, legal.yaml's comment, ci.yml's step), about 6 changed lines, plus the two test scripts and docs/personal-data.md.
The live go, in order
| # | Step | Who |
|---|---|---|
| 1 | This slice lands; on the PC a delegated agent, through k3s's own kubeconfig, sees tunnel and legal Ready at the landed revision and the cloudflared pod rolled to the new checksum/config; a throwaway client Pod in cloudflared gets 200 from Traefik with Host hallucinai.de for the three pages, each body's sha256 equal to built/ at the landed main, and records the in-cluster ETag of /datenschutz; the Pod is deleted on exit. Until hal moves the record, the line serves nothing. |
agent |
| 2 | hal's commit: tunnel: platform on services.legal.hosts[0] in fleet.yaml, and its docs' sentences that say hal serves the apex. |
hal's head |
| 3 | The operator's apply, below. If hal#1148's checkout of ~/srv/skat has not run, its block from hal#1148's pull request comes first, since every hal apply refuses without it; the apply also waits for speech-runtime's GPU test, as hal#1148 and hal#1168 do. |
operator |
| 4 | The live check, below. | agent; hal check hosts hal's head |
| 5 | Open the cross-repository items below. | head |
The operator's block (he answers yes at the plan, which shows the apex record moving to the tunnel platform and hal's apex line leaving hal's ingress):
cd ~/srv/hal && hal update && hal applyRollback: hal's head reverts step 2; the operator runs the same block. The record returns to hal's tunnel and hal's still-running legal unit answers, with the text of hal's last apply. platform's tunnel line stays and serves nothing. hal's frozen legal/sections/allgemein has no server-logs section, so after a rollback the public page lacks the ruled 30-day log sentence until the record returns to the platform (the gate of overnightworks/hal#1199).
Proof
CI. cluster, part core: the cloudflared cell finds the apex's line pointing at Traefik's web and a client Pod in cloudflared reaching the route with nginx's answer, checksum/config matches, hopin's and skat's lines unchanged; the five-paths cell unchanged (three pages and every asset with the committed checksums, nginx's headers, Traefik's 404 for /). checks: the appkit script with its own pin.
Live, after step 3, from outside, by a delegated agent with curl, at the landed main:
curl -fsS https://hallucinai.de/<page> | sha256sumequalssha256sum infrastructure/legal/built/<page>.htmlforimpressum,datenschutzandnutzungsbedingungen;- the outside
ETagof/datenschutz(aW/prefix ignored) equals the in-cluster one of step 1, so the platform served it, not hal; - every
/_rechtliches/…the pages reference answers 200 with the committed file's sha256; - each answer carries
default.conf's four headers, nonel,report-toorreporting-endpointsheader; no body holds/cdn-cgi/,__cf_email__,data-cfemail,email-decodeorchallenge-platform, andlegal@hallucinai.destands literally (hal#110 C2-C4); - the edge is unchanged:
/and/foogive 302 tohttps://auth.hallucinai.de/,/impressum/andhttps://www.hallucinai.de/impressumgive 302 tohttps://hallucinai.de/impressum,/favicon.icogives 404; - each page's answer carries
strict-transport-securityexactly once: Cloudflare's zone adds it and neither nginx'sdefault.confnor Traefik adds a second (required by hal'sdocs/external.md; asked by hal's head for The platform serves the legal pages at the apex hal#1199); - a browser at 390 and 1440 px opens
/datenschutz, requestshallucinai.deonly and reports no CSP violation (hal#110 C5); hal check hostsexits 0 (hal's head).
Trace (main 0bf0c46; hal origin/main e9cfd360)
infrastructure/cloudflared/cloudflared.yaml:10-15(lines),:37(checksum/config).tests/cloudflared/ingress_from_pods_built_from_its_template.sh:85-131.infrastructure/legal/legal.yaml:104-106(comment),:108-150(Ingress, five paths).tests/legal/legal_sections_assets_and_headers_equal_hals_at_commit.sh:23-31(stylesheet, headers),:33-56(wheel);.github/workflows/ci.yml:72-73,:336-338,:496-503.AGENTS.md:692-696(D),:703(R row of P0 part c, base line 700).- hal:
fleet.yaml:3-8,39-43;src/hal/stack/catalog.py:501-513(_tunnel),:583-598(_check_serve);external/cloudflare.tf:17-22,153-162,278-342;external/tunnel.tf:7-10;legal/nginx/default.conf; hal#1148's trace (the publish phase skipsreadyon another tunnel).
Risk
Material: a public legal page and a public route. Opus review at high and a cross-provider final gate. The first review drives:
| # | Sentence | Cell | Driven by |
|---|---|---|---|
| 1 | The apex's line points at Traefik's web and rolls cloudflared |
cloudflared cell, checksum/config |
CI, PC step 1 |
| 2 | A line without a path is judged on a path its Ingress routes; hopin and skat unchanged | cloudflared cell | CI |
| 3 | Only the five paths reach nginx; everything else gets Traefik's 404 | five-paths cell | CI |
| 4 | The live body equals built/ for each page |
curl and sha256 |
live |
| 5 | The platform serves the apex, not hal | ETag outside equals in-cluster |
live |
| 6 | Every referenced asset answers with the committed bytes | curl per asset |
live |
| 7 | Headers, no injected script, literal address | hal#110 C2-C4 | live |
| 8 | The addresses never redirect; the edge's redirects are unchanged | curl of /, /foo, /impressum/, www |
live |
| 9 | No request leaves hallucinai.de, no CSP violation, at 390 and 1440 px |
browser | live |
| 10 | The rollback restores hal's answer | review of hal's revert path; not driven | review |
| 11 | The appkit files equal the pinned wheel, and nothing reads hal | appkit script | CI |
Deliberately untouched
- hal's
cloudflare.redirectand the zone: hal's catalog until platform's OpenTofu takes over the zone hallucinai.de from hal at the cutover #48 takes the zone over at the cutover. - hal's legal unit, legal and appkit steps,
hal legalandlegal/: the predecessor; hal's exit item below names its exit, P11. - The strict rule carried over from hal#110 (no public app without a privacy section) and the blessed look: P9: legal pages, person export and erasure, AI keys and expiry checks move from hal #173's, not this slice's; hal's apply keeps the rule only for hal's own pages, which serve nothing after the go.
built/impressum.htmlis hal's frame byte for byte (AGENTS.md:684) stays true.
Cross-repository items, opened at step 5
- hal, owner hal's head (hal-af): "hal's apex host names
tunnel: platform, hal'slegal/takes no further change (a change of a shared section goes to platform, hal#1154 moves to platform's board), and hal's legal unit, legal and appkit steps andhal legalleave with hal's archive in P11, the unit staying the rollback target until then." Step 2's commit is its first part; hal#110 names platform for what remains. The item also moves the pointer in hal'sdocs/keycloak.mdthat calls hal'slegal/sections/allgemein/nutzungsbedingungen.html"the central terms page" (the gate of The platform serves the legal pages at the apex hal#1199). - songmaker, owner songmaker's head: "songmaker's privacy part refers to the general part's 30-day period for its server logs instead of stating size rotation and no fixed maximum, and the vendored ACE-Step fork writes no log file that outlives 30 days." It is songmaker#1714 finding 6 with a pointer to this item; the wording is the operator's yes in songmaker's repository. Its
Audit-Log: wird derzeit ohne feste Frist gespeichertbeside the general part's 30-day sentence needs the same owner's decision (the gate of The speech section takes hal's last ruled sentence; platform owns the legal sections #324, F2). - presentator, owner presentator's head: "presentator's privacy part refers to the general part's 30-day period for its server logs instead of „Eine Aufbewahrungsdauer ist nicht festgelegt“." Its wording is the operator's yes in presentator's repository.
- marketplace, owner marketplace's head: "The
legal-textsskill names platform as the owner that assembles and serves the pages and holds the shared sections, and drops the sentence that hal still does until platform's assembler lands." (plugins/atelier/skills/legal-texts/SKILL.md:21-24, marketplace#561's follow-up.)
Done when
hallucinai.de's three pages are served through platform's tunnel line, with each live body equal to built/ at main and the platform's ETag; the edge's redirects are unchanged; the hal script no longer reads hal; the head has named on #134 the sentences of AGENTS.md this makes false; and the four cross-repository items are open with their owners, hal's naming its exit in P11.
version = 1
now = "Cut from #173 on 09.11.2026 as the second half of P9a-5; hal's catalog mechanism for another tunnel (hal#1100, hal#1148) carries the apex unchanged."
next = "The tunnel line, the cell and the script's end have landed (#333, ab4d602) and step 1 of the live go passed at 7ba3a15: hal's head's commit (overnightworks/hal#1199), then the operator's hal update && hal apply, the live check and the four items."
done_when = "hallucinai.de's three pages are served through platform's tunnel line, each live body equal to built/ at main with the platform's ETag; the edge's redirects are unchanged; the hal script no longer reads hal; the head has named on #134 the sentences of AGENTS.md this makes false; the hal, songmaker, presentator and marketplace items are open, hal's naming its exit in P11."
scope = [".github/workflows/ci.yml", "docs/personal-data.md", "infrastructure/cloudflared/cloudflared.yaml", "infrastructure/legal/legal.yaml", "tests/cloudflared/ingress_from_pods_built_from_its_template.sh", "tests/legal/appkit_assets_equal_their_release_wheel.sh", "tests/legal/legal_sections_assets_and_headers_equal_hals_at_commit.sh"]
whole = "One public route: the tunnel line, the cell that judges it, and the end of the hal comparison land together, and the live go after them is one hal commit and one apply."
size = "S"
Activity
FlexOr2 commentedon Nov 9, 2026
Landed in #333 as ab4d602. Reopened: its Done when also needs the live go (the tunnel line on the PC, hal's catalog flip and the live check).
FlexOr2 commentedon Nov 9, 2026
Landed code: #333 (ab4d602). Freed #325 (dispatched, its blocker on this item dropped since it needed only the code) and #277 (deferred until this goes live).
FlexOr2 commentedon Nov 9, 2026
Live go, step 1 held on the PC at R = 7ba3a15 (a descendant of ab4d602):
tunnelandlegalReady (ReconciliationSucceeded) at R, no reconcile by hand.cloudflared-65b5f48ff8-c9cxjReady since 19:37:35Z;checksum/config523b0e28…49a9 equals the sha256 ofconfig.yamlat R and of the live ConfigMap.cloudflared(deleted on exit),GETthrough Traefik'swebwithHost: hallucinai.de:impressum,datenschutz,nutzungsbedingungeneach 200 at the first try, each body's sha256 equal tobuilt/<page>.htmlat R.ETagof/datenschutz:"6af20540-1315f".Note for step 4: #344 (design v0.18.0, #325) changes the built pages' asset paths; if it lands before the live check, the check compares with
built/at the then-current main and takes the ETag anew in-cluster.Next: step 2, hal's head's commit (
tunnel: platformonservices.legal.hosts[0]infleet.yaml).FlexOr2 commentedon Nov 9, 2026
Step 2 is overnightworks/hal#1199 (hal's head, build running; review Opus high, gate Opus xhigh). Step 3's preconditions per hal's head: hal#1148's
~/srv/skatcheckout has run, speech's GPU window is done; the apply also carries hal#1193 step 1.hal check hostsin step 4 is the operator's. Next: hal#1199 on hal's main, then the operator's block.FlexOr2 commentedon Nov 9, 2026
Step 1 re-run at bb9e579 (R, main) after #344 (a1da90f) changed the built pages.
tunnelandlegal: lastAppliedRevision main@sha1:bb9e579..., Ready True both. a1da90f is an ancestor of R.legal: rolled out, podlegal-66b8ff89c5-9dbn8, 1/1 Ready, started 2026-11-09T20:14:57Z.cloudflared:checksum/config= 523b0e2806a230f2ccd169f28126505606d4f2110bccb04acb2e7aad300449a9 = sha256 of config.yaml at R (also equals the live ConfigMap's); podcloudflared-65b5f48ff8-c9cxjReady.cloudflared(curl image of CLIENT_IMAGE, restricted context), GET http://traefik.traefik.svc/, Host hallucinai.de, status 200 each; served sha256 equalsbuilt/<page>.htmlat R:FlexOr2 commentedon Nov 9, 2026
Step 4, the outside check, at 21:16 UTC on 09.11.2026 (origin/main's built files hash as in step 1, so main did not move for them). The earlier empty-body 404s were observed between about 21:10 and 21:12 UTC, before this check.
FlexOr2 commentedon Nov 9, 2026
Journal, 09.11.2026, after step 4:
hal check hosts, run by the operator:no findings (7 hosts)(hal's head; The platform serves the legal pages at the apex hal#1199 is closed with it).Cache-Control: max-age=14400at the edge is the zone's default browser cache TTL, which overrides nginx'sno-cacheon static file types; it was the same on hal, and hal's head keeps the zone as it is. The rule that makes it harmless holds on the platform now: a changed stylesheet takes a new name (legal-1becamelegal-2), and the design files carry their version in the path. The pages themselves are never cached.FlexOr2 commentedon Nov 9, 2026
Done when is met. The slice landed as PR #333 (ab4d602); the live go ran on 09.11.2026: hal's commit overnightworks/hal#1199 (b08960ed), the operator's
hal update && hal apply, the outside check (comment 6751670562: every line held) andhal check hostswith no findings. The sentences ofAGENTS.mdthis makes false are named on #134. The four cross-repository items are open with their owners: overnightworks/hal#1202 (hal's legal unit, steps andhal legalleave with the archive in P11; the unit stays the rollback target), overnightworks/songmaker#1714 (finding 6, sharpened in comment 6751744517, with the open Audit-Log line), overnightworks/presentator#988 and overnightworks/marketplace#575.The Keycloak terms page points at the public terms page (#1203)