feat(cli): pic routes ls --group + group-route journey + docs (§11 tail R5)
Visibility + end-to-end coverage + docs for group route templates.
- ApplyService::route_report(Group) → GET /api/v1/admin/groups/{id}/routes
(viewer-tier GroupScriptsRead), surfacing a group's own route templates
(method, host, path, handler script, dispatch, enabled).
- CLI: `pic routes ls --group <g>` (the script-id positional now conflicts
with --group), client `group_routes_list` + RouteTemplateDto.
- Journey `group_routes.rs`: a group declares a `[[routes]]` template
binding a group handler; a DESCENDANT app serves it (via `routes match`
against the live RouteTable), a SIBLING-subtree app does not, `routes ls
--group` shows it, re-apply is a NoOp. Shadowing + isolation are
additionally pinned at the repo layer by group_route_templates.rs.
- Update the manifest unit test: a [group] now accepts route templates and
rejects only the stateful trigger kinds.
- Docs: design §4.5 (the live route-template model + full-live
invalidation + nearest-wins shadowing + deferrals), CLAUDE.md focus.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -392,10 +392,30 @@ Two distinct constraints:
|
||||
> sibling-subtree app never sees the template (pinned by `tests/group_trigger_templates.rs`). Authoring
|
||||
> is `[[triggers.kv]]` (etc.) on a `[group]`; read-only `pic triggers ls --group`. **Deferred:** the
|
||||
> **stateful** kinds (cron `last_fired_at`, queue advisory-lock, email sealed secret) need per-app rows
|
||||
> → materialization, rejected on a `[group]`; **route templates** (same live shape, but the in-memory
|
||||
> `RouteTable` would need inherited templates per app + new recompile-invalidation edges); per-app
|
||||
> opt-out / cross-owner dedup (a descendant re-declaring an identical trigger double-fires — "overlapping
|
||||
> triggers coexist").
|
||||
> → materialization, rejected on a `[group]`; per-app opt-out / cross-owner dedup (a descendant
|
||||
> re-declaring an identical trigger double-fires — "overlapping triggers coexist").
|
||||
>
|
||||
> **Shipped — group ROUTE templates (live, inherited).** The same live model, adapted to routes. A
|
||||
> `[group]` declares a `[[routes]]` template binding a group-owned endpoint; `routes` gained a
|
||||
> polymorphic owner (`0057_group_routes.sql`, nullable `group_id`, mirrors `0056`). Unlike triggers
|
||||
> (dispatched by a per-event SQL query), routes are served from the in-memory `RouteTable`, so the HTTP
|
||||
> hot path can't resolve inheritance per request — instead the table **rebuild** expands templates into
|
||||
> each descendant app's slice via `RouteRepository::list_effective` (the all-apps generalization of
|
||||
> `CHAIN_LEVELS_CTE`: every app × its ancestor chain ⋈ routes, tagged with the effective app + owner
|
||||
> depth). `compile_effective_routes` applies **nearest-owner-wins shadowing** — for one app, an
|
||||
> identical binding tuple (method+host+path) owned at multiple chain levels collapses to the nearest
|
||||
> (an app's own route shadows an ancestor-group template; a route picks one winner, unlike a fanning
|
||||
> trigger), while non-identical bindings coexist under the existing matcher precedence. The match +
|
||||
> dispatch paths are unchanged; the group handler runs under the firing app's `app_id`. **The chain
|
||||
> expansion is the isolation boundary** — a template lands only in the slices of apps whose ancestor
|
||||
> chain contains the owning group (pinned by `tests/group_route_templates.rs` + the `group_routes`
|
||||
> journey). Because the table is a cache, inheritance is rebuilt **full-live** on every edge that
|
||||
> changes it: route CRUD, apply, **and tree mutations** — app create/delete (`apps_api`) and group
|
||||
> reparent (`groups_api`) all route through the single `rebuild_route_table` chokepoint, so a new app
|
||||
> under a group serves its templates instantly. Host-claim validation is skipped for a group template
|
||||
> (no single app; descendants serve it on whatever host they claim, so templates use `host_kind = any`).
|
||||
> Authoring is `[[routes]]` on a `[group]`; read-only `pic routes ls --group`. **Deferred:** per-app
|
||||
> route opt-out, and multi-node route-snapshot propagation (cluster mode).
|
||||
|
||||
### 4.6 Secrets & `pull`
|
||||
|
||||
|
||||
Reference in New Issue
Block a user