docs: record Phase 4-lite (group-owned scripts) as shipped, live-resolved
Mark §11.4 ✅ shipped with the live-resolution decision (no body materialization / invalidation fan-out, mirroring Phase 3's config), the shipped surface, and the deliberate Phase-4b deferrals (group modules + lexical import resolver, invoke-by-id, live auto-rebinding). Note the sharp edge: routes.script_id ON DELETE CASCADE means deleting a group script drops descendant routes. Update the §5.1 materialization box and CLAUDE.md (current-focus + the data-model invariant now covers group-owned scripts). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -440,6 +440,10 @@ Two distinct constraints:
|
||||
> **deferred plan for *script-body* inheritance (Phase 4)** — where a hot per-request path may
|
||||
> justify a cache — and as a future optimization for config if a deep tree ever demands it. Read the
|
||||
> rest of §5.1 as "the materialization design, if/when we need it," not as Phase 3's runtime.
|
||||
> **Update — Phase 4-lite (§11.4 status) also shipped live, not materialized:** group scripts are
|
||||
> referenced by id on the runtime hot path and resolved by name only at bind/invoke time, so even
|
||||
> script inheritance needed no materialized view or invalidation fan-out. The materialization design
|
||||
> now stands purely as a future optimization, gated on a *measured* cost.
|
||||
- manager-core **resolves-at-write into a materialized per-app effective view** (§3 rule applied:
|
||||
sparse merge, env filter, proximity, CoW, `enabled`).
|
||||
- The orchestrator/executor serve from that view, **keyed by `app_id` + a generation/version**. The
|
||||
@@ -942,6 +946,44 @@ Resolved items now live inline next to their topic. What genuinely remains:
|
||||
> apply reconciliation (Phase 5 — Phase 3 ships CRUD API + CLI), and materialization (above).
|
||||
4. **Group-inherited scripts/modules.** CoW overrides; the **scope-aware module/import resolver +
|
||||
extension points** (§5.5); cache-invalidation fan-out hardening; versioning/pinning if needed.
|
||||
|
||||
> **Status (Phase 4-lite): ✅ shipped — group-owned ENDPOINT scripts, live-resolved (no
|
||||
> materialization).** Like Phase 3's config, group scripts resolve **live**, not via a cache: a
|
||||
> script is referenced by id on the runtime hot path (routes/triggers store the resolved id; the
|
||||
> orchestrator's per-app route trie already scopes dispatch to one app and never reads
|
||||
> `script.app_id`), and by **name** down the chain only at bind/invoke time. No materialized
|
||||
> per-app body view, no generation counter, no fan-out invalidation — so §11.4's
|
||||
> "cache-invalidation fan-out hardening" is **moot for Phase 4-lite** (revisit only if a measured
|
||||
> hot-path cost appears).
|
||||
>
|
||||
> Shipped surface:
|
||||
> - **Schema:** `0050_group_scripts.sql` makes `scripts` polymorphic-owned (`group_id` XOR
|
||||
> `app_id`, `scripts_owner_exactly_one` CHECK, per-owner partial-unique `LOWER(name)` indexes),
|
||||
> `ON DELETE RESTRICT` (code is not data — a group can't be deleted out from under its scripts).
|
||||
> `Script.app_id` became `Option<AppId>` + `group_id`; the cross-app isolation backstops
|
||||
> (dispatcher, invoke, trigger-bind, orchestrator `/execute/{id}`) generalized from
|
||||
> `app_id == row.app_id` to **chain-membership**, fail-closed for app-owned (zero-cost in-memory
|
||||
> fast path; only a group candidate pays a chain query).
|
||||
> - **Resolver (reuses `CHAIN_LEVELS_CTE`):** `get_by_name_inherited(app_id, name)` (nearest-owner
|
||||
> wins; app's own script shadows the inherited group one — CoW) and `is_invocable_by_app(script_id,
|
||||
> app_id)` (the isolation predicate lifted to the hierarchy; sibling apps' *own* scripts are NOT
|
||||
> cross-invocable — only group scripts inherit).
|
||||
> - **Admin + CLI:** `GroupScripts{Read,Write}` caps (viewer+/editor+ on the group, resolved via the
|
||||
> group-ancestor walk); `POST/GET /groups/{id}/scripts` (endpoint-only, self-contained — modules +
|
||||
> imports rejected); the by-id `/scripts/{id}` handlers are owner-polymorphic; `pic scripts ls
|
||||
> --group` / `deploy --group` (create-or-update; `--app`/`--group` mutually exclusive).
|
||||
> - **`invoke("name", …)`** resolves inherited (a script runs a shared group endpoint; it always
|
||||
> executes under the *caller's* app context, never the owner's).
|
||||
> - **Declarative apply:** a manifest route/trigger `script = "name"` resolves to an inherited group
|
||||
> endpoint (`resolve_inherited_targets` → `name_to_id`), with the diff resolving the group-bound
|
||||
> route's id → name so **re-apply is idempotent**.
|
||||
>
|
||||
> Deliberate Phase-4-lite limits (deferred to Phase 4b): group **modules** + the origin-aware
|
||||
> (lexical) **import** resolver (§5.5) — group scripts must be self-contained; **invoke-by-id** stays
|
||||
> app-scoped (inheritance is by-name only); CoW is **redefine-in-app + re-apply** (no live
|
||||
> auto-rebinding). Sharp edge to track: `routes.script_id` is `ON DELETE CASCADE`, so deleting a
|
||||
> group script removes descendant apps' bound routes (within group-editor authority; the *group*
|
||||
> delete itself stays `RESTRICT`).
|
||||
5. **Project tool maps onto groups.** Nested manifests, attach point, single-owner, server-computed
|
||||
tree plan, per-env approval gating.
|
||||
6. **(Much later) group-level collections/topics** — the v1.3 cross-app data-sharing problem, with a
|
||||
|
||||
Reference in New Issue
Block a user