From 250f9a525c32fa3a62494cb47a35dcd881884b8c Mon Sep 17 00:00:00 2001 From: tegwick Date: Mon, 14 Sep 2026 12:03:28 +0200 Subject: [PATCH] Decide hall URI, static renderer sibling, and allowlist trust. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit HOH-WP-0002 T01–T03. Public slug is frontmatter id. Rendering lives in hall-render. T04 still needs the operator corpus call. Assistant: grok Assistant-Session: 01a09dc1-b21e-77e1-919e-fcad2f82b267 --- ENTRY.md | 2 +- .../0001-hall-uri-and-renderer-boundary.md | 61 +++++++++++++++++++ scripts/check-entries.py | 7 +++ ...OH-WP-0002-published-hall-and-renderers.md | 19 ++++-- 4 files changed, 84 insertions(+), 5 deletions(-) create mode 100644 docs/adr/0001-hall-uri-and-renderer-boundary.md diff --git a/ENTRY.md b/ENTRY.md index 6af0317..0ddfa81 100644 --- a/ENTRY.md +++ b/ENTRY.md @@ -26,7 +26,7 @@ its portrait on disk. | Field | Notes | | --- | --- | -| `id` | Stable, unique. Convention: `hall-worker--` | +| `id` | Stable, unique, URL-safe `[A-Za-z0-9-]+`. Convention: `hall-worker--`. This **is** the public URI slug (`/hall-of-helix/`). Do not change it after the seat leaves `draft`. The filename is not the URI. | | `type` | `worker-entry` | | `worker_kind` | `human` or `agent-session` | | `display_name` | How the worker is addressed in the hall | diff --git a/docs/adr/0001-hall-uri-and-renderer-boundary.md b/docs/adr/0001-hall-uri-and-renderer-boundary.md new file mode 100644 index 0000000..21ff2e0 --- /dev/null +++ b/docs/adr/0001-hall-uri-and-renderer-boundary.md @@ -0,0 +1,61 @@ +# ADR 0001 — Hall URI identifier, service shape, and renderer trust + +Status: accepted +Date: 2026-09-14 +Workplan: HOH-WP-0002-T01, T02, T03 + +## T01 — URI identifier + +The public path is: + +``` +https://helix.coulomb.social/hall-of-helix/ +``` + +`` is the seat frontmatter `id` **verbatim** (example: +`hall-worker-codex-statehub-snapshot-and-signature`). Uniqueness is already +enforced by `make check`. The filename is not the URI and may be renamed. + +The `hall-worker-` prefix stays. Stripping it would require a second +uniqueness domain; an opaque extra id would be a third name to keep. + +`id` is URL-safe `[A-Za-z0-9-]+` (mixed case is allowed; several existing +Claude session ids use it). Once `status` is not `draft`, `id` is frozen. +Corrections edit the body, not the identifier. + +`?renderer=` is optional; omitting it equals `renderer=default`. + +## T02 — Where the service lives + +This repo stays a **publication** corpus. Rendering is a sibling tooling +repo, conventionally `hall-render`. Do not execute renderer code from this +repository. + +First delivery is **static generation**: `hall-render` reads this repo at a +pinned commit and emits HTML. The renderer set is known at build time. +Dynamic on-request execution is declined for v1 (publication classification +plus XSS surface). + +Deployment target: reef-railiance k3s, private/static objects behind the +existing Traefik ingress. DNS `helix.coulomb.social` is a railiance-infra / +railiance-cluster record, not owned by this publication repo. No live +publish until T04 (operator corpus disposition). + +## T03 — Renderer contract and trust + +`renderer` is user-controlled input that selects code. Rules: + +1. Resolve only through a **fixed allowlist** in `hall-render`. Never treat + the parameter as a path, module, package, or URL. +2. Unknown values return 404 (static: missing artifact). Do not fetch or + import. +3. A renderer receives parsed Markdown plus frontmatter and returns markup. + No credentials, filesystem, or network. +4. Output is sanitised before it is a page. HTML from a renderer is an XSS + sink. + +Registration is an in-repo allowlist in `hall-render`, not reuse-surface +(that catalog is capability discovery, not a safe code-execution index). + +Input: `{frontmatter, markdown, entry_id}`. Output: `{html}`. Default +renderer is named `default` and is the only one in the first allowlist. diff --git a/scripts/check-entries.py b/scripts/check-entries.py index 4ed6a8d..069d613 100755 --- a/scripts/check-entries.py +++ b/scripts/check-entries.py @@ -12,6 +12,8 @@ ENTRIES = ROOT / "entries" README = ROOT / "README.md" REQUIRED_FM = ("id", "type", "worker_kind", "display_name", "created_at", "recorded_at", "status") +# HOH-WP-0002-T01: the public URI slug is frontmatter id verbatim. +ID_RE = re.compile(r"^[A-Za-z0-9]+(?:-[A-Za-z0-9]+)*$") REQUIRED_HEADINGS = ( "Who I was", "Contribution", @@ -120,6 +122,11 @@ def check_entry(path: Path, ids: dict[str, Path]) -> list[str]: entry_id = fm.get("id", "") if entry_id and ids.get(entry_id) not in (None, path): errors.append(f"{path.name}: duplicate id {entry_id}") + if entry_id and ID_RE.match(entry_id) is None: + errors.append( + f"{path.name}: id {entry_id!r} is not URL-safe [A-Za-z0-9-]+ " + "(HOH-WP-0002-T01: URI is this id verbatim)" + ) for heading in REQUIRED_HEADINGS: if re.search(rf"^## {re.escape(heading)}\s*$", body, re.M) is None: diff --git a/workplans/HOH-WP-0002-published-hall-and-renderers.md b/workplans/HOH-WP-0002-published-hall-and-renderers.md index f1de34c..040bd21 100644 --- a/workplans/HOH-WP-0002-published-hall-and-renderers.md +++ b/workplans/HOH-WP-0002-published-hall-and-renderers.md @@ -9,7 +9,7 @@ owner: claude-code topic_slug: helix-forge priority: medium created: "2026-09-05" -updated: "2026-09-05" +updated: "2026-09-14" quality_dor: DoR-Ok quality_dor_at: "2026-09-05" quality_dor_by: claude-code @@ -47,7 +47,7 @@ until those decisions land — that is the intended shape, not an oversight. ```task id: HOH-WP-0002-T01 -status: todo +status: done priority: high state_hub_task_id: "fc2e4306-5ecf-50db-9a75-8a241a4ae9bc" ``` @@ -68,11 +68,15 @@ Whatever is chosen, **the URI is a permanent commitment**: a hall that breaks it own links is not a record. Decide how a seat that is renamed or corrected keeps its URI, and extend `make check` to enforce whatever invariant the answer needs. +**Done 2026-09-14.** URI is frontmatter `id` verbatim. `make check` requires +URL-safe `[A-Za-z0-9-]+` and unique ids. Filename is not the URI. ADR: +`docs/adr/0001-hall-uri-and-renderer-boundary.md`. + ## Decide where the service lives and how it is built ```task id: HOH-WP-0002-T02 -status: todo +status: done priority: high state_hub_task_id: "b6ccb11c-2cf5-5f32-a2c5-a4f849eec437" ``` @@ -95,11 +99,15 @@ content. Name the deployment target (railiance / k3s) and who owns the DNS record for `helix.coulomb.social`. +**Done 2026-09-14.** This repo stays publication corpus. Rendering is sibling +`hall-render`, static generation for v1. k3s/Traefik; DNS owned by +railiance-infra/cluster. No public serve until T04. + ## Define the renderer contract and trust boundary ```task id: HOH-WP-0002-T03 -status: todo +status: done priority: high state_hub_task_id: "64370b21-2b30-5ce9-be63-0379c55dad5b" ``` @@ -124,6 +132,9 @@ registry before inventing one. Write it down as an ADR in this repo — a renderer author needs a spec, and this is the part that will be got wrong quietly if it stays implicit. +**Done 2026-09-14.** Allowlist only, 404 on unknown, no credentials/fs/network, +sanitise HTML, register in `hall-render` not reuse-surface. Same ADR. + ## Clear the corpus for public publication ```task