Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
b354db2e76
commit
b737e0a7e7
@@ -0,0 +1,127 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user