Test / test (push) Successful in 24s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
128 lines
5.7 KiB
Markdown
128 lines
5.7 KiB
Markdown
# Filesystem routes, sections, and where chains fit
|
|
|
|
Status: proposal (researched 2026-08-24, nothing implemented).
|
|
|
|
## What exists today, precisely
|
|
|
|
- A question's `id` doubles as its URL. Filenames are meaningless:
|
|
`questions/` is loaded as a **flat** directory (both the Gitea
|
|
loader and `question_lint --path`), every `*.yaml` becomes a
|
|
`Question` keyed by its own declared `id`.
|
|
- Hierarchy exists only as strings: `action: /proposed` edges, plus a
|
|
manual `followup: true` flag that hides post-submission pages from
|
|
the nav. The graph is invisible in a repo listing — you open every
|
|
file to learn the flow. (uhhm's actual graph: `/` fans out to three
|
|
followups; `/develop` → `/develop-proposal` → `/proposed` is a
|
|
two-step flow flattened into three top-level files.)
|
|
- Criteria are per-file: `qualifies: <kanidm-group>` on a question,
|
|
`requires_group` on a resource. Gating a whole area means
|
|
repeating the flag in every file of that area.
|
|
- Chains are query-string lineage: submitting hashes
|
|
`(question, parents, responses, ts)` into `chain_hash`, the next
|
|
page is `action + ?chain=<hash>`, and holding a chain is itself a
|
|
capability (`self_transition` + email second factor). `chain.rs`
|
|
reserves DAG shape (multiple parents) but nothing produces it yet.
|
|
- `Question.route` is a declared-but-never-read field — a fossil of
|
|
this exact idea.
|
|
|
|
## The proposal, in three phases
|
|
|
|
### Phase 1 — the tree is the router
|
|
|
|
Directory structure becomes the URL structure, next-js style:
|
|
|
|
```
|
|
questions/
|
|
index.yaml → /
|
|
applied.yaml → /applied
|
|
develop/
|
|
index.yaml → /develop
|
|
proposal.yaml → /develop/proposal
|
|
proposed.yaml → /develop/proposed
|
|
review/
|
|
index.yaml → /review
|
|
```
|
|
|
|
- `id:` becomes optional and **derived from the path** (`index.yaml`
|
|
names its directory). An explicit `id:` still wins so both content
|
|
repos keep working unchanged; lint warns when it disagrees with the
|
|
path, and the field can retire later along with `route`.
|
|
- `action:` accepts **relative references**: `action: proposed`
|
|
resolves against the file's directory, `action: /subscribed` stays
|
|
absolute. Resolution happens at load time, so validation and the
|
|
runtime see absolute ids exactly as today. A flow directory becomes
|
|
self-contained: rename `develop/` and every internal edge moves
|
|
with it.
|
|
- **`followup:` is inferred**: `index.yaml` files are nav pages,
|
|
non-index files are followups unless they say `nav: true`. This
|
|
matches the real content exactly (uhhm's four `followup: true`
|
|
files are precisely its non-index leaf pages) and deletes a flag
|
|
people must remember.
|
|
- Loader: switch from the per-directory contents API to Gitea's git
|
|
trees API (`/git/trees/{branch}?recursive=true`) — one request for
|
|
the whole tree instead of one per directory, which the flat loader
|
|
should be using anyway.
|
|
|
|
### Phase 2 — sections: criteria scoped by URL prefix
|
|
|
|
A `_section.yaml` in any directory applies to everything beneath it
|
|
(underscore = not a page, like next's private folders):
|
|
|
|
```
|
|
questions/review/_section.yaml:
|
|
qualifies: portal_owners
|
|
responsible: { name: Bendik, contact: … }
|
|
```
|
|
|
|
This is the URL/criteria interplay actually worth having: **a URL
|
|
prefix becomes a trust boundary**. "Everything under /review is
|
|
owner-only" is one line in one place, instead of a flag per file that
|
|
drifts. Per-question `qualifies` still overrides (tighter or looser —
|
|
lint should warn on looser). Sections are also the natural home for
|
|
shared `responsible` contacts and, later, per-section branding
|
|
accents.
|
|
|
|
### Phase 3 — dynamic segments, and chains stay out of the path
|
|
|
|
Two criteria axes exist: **who you are** (Kanidm group) and **what
|
|
you've done** (holding a chain). Phase 2 scopes the first by prefix;
|
|
phase 3 does the same for the second, plus gives records addresses:
|
|
|
|
- `[record].yaml` — a dynamic segment, one YAML file rendered per
|
|
record: `questions/review/[record].yaml` serves `/review/<key>`,
|
|
with the param available to the page's `ResourceSpec.key`. Today a
|
|
single record is only reachable through a desk row or a
|
|
`?chain=` link; this gives every record a real, gated URL —
|
|
linkable from review desks, automations, and n8n notifications.
|
|
- `requires_chain: <question-ref>` (page- or section-level): the page
|
|
only renders for a visitor whose chain tip answers the referenced
|
|
question. Today's followup pages are soft-hidden (out of nav) but
|
|
fully reachable; this makes "you must have come from X" an actual
|
|
criterion, declared with a relative ref like actions are.
|
|
|
|
**Deliberately not proposed: encoding the chain in the path.** The
|
|
page tree is static structure; the chain is runtime lineage and a
|
|
bearer capability. Putting it in the path makes it look canonical and
|
|
shareable — exactly what a capability URL shouldn't invite — and a
|
|
DAG (the reserved multi-parent shape) doesn't linearize into a path
|
|
anyway. `?chain=` stays a query parameter: pages keep one canonical
|
|
URL, lineage rides along only when it's actually held.
|
|
|
|
## Migration
|
|
|
|
Phase 1 is fully backward compatible (explicit `id` wins, flat repos
|
|
are just trees of depth one). The content repos migrate by `git mv`:
|
|
uhhm's develop flow nests under `develop/`, its followups either stay
|
|
top-level (`/applied` keeps its URL) or move with their flows if URL
|
|
churn is acceptable; redoal's three files are already the tree. Lint
|
|
learns the same resolution rules in the same commit, so a bad
|
|
reference stays a caught push, never a 404.
|
|
|
|
## Order of value
|
|
|
|
Phase 1 is cheap and pays immediately (the repo listing becomes the
|
|
sitemap). Phase 2 is small and unlocks gated areas properly. Phase 3
|
|
is the real feature work — `[record]` pages change what desks and
|
|
automations can link to — and can wait until something concrete needs
|
|
it.
|