feat(cli): collections manifest + ls + journeys; rename to kv::shared_collection (§11.6 C5)

Finish the §11.6 KV slice: declarative authoring, read-only inspection, the
runtime journey, and docs — plus a forced SDK-name fix.

- SDK name: `shared` is a Rhai RESERVED keyword, so `kv::shared(...)` is a parse
  error. Renamed the handle constructor to `kv::shared_collection(...)`
  (keeps the user's "shared" intent; unambiguous). Updated everywhere.
- CLI manifest: `collections = [...]` on `[group]` only (ManifestApp has no such
  field + deny_unknown_fields → an app manifest carrying it is a hard error);
  Manifest::collections() accessor; build_bundle emits it.
- plan/apply: a `collection` row group in `pic plan`; created/deleted counts in
  the apply summary; collections threaded through PlanDto/NodePlanDto/
  ApplyReportDto.
- read-only `pic collections ls --group` → GET /admin/groups/{id}/collections
  (viewer+), backed by ApplyService::collection_report + CollectionInfo.
- journey: a group declares `catalog`; app A writes, app B (same subtree) reads
  it back; a sibling-subtree app gets CollectionNotShared; `collections ls` +
  re-apply NoOp. Full suite 114/114.
- docs: groups-and-project-tool §11.6 (KV-only MVP shipped + deferrals),
  sdk-shape.md (the shared-collection handle + trust model), CLAUDE.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-06-29 22:26:43 +02:00
parent b79d8ef47d
commit 0973344515
19 changed files with 398 additions and 13 deletions

View File

@@ -1044,6 +1044,25 @@ Resolved items now live inline next to their topic. What genuinely remains:
real shared-scope authz model. Optionally, trigger/route **templates** (§4.5) if cardinality
demands.
> **Shipped — §11.6 KV slice (full shared read/write).** A group declares a collection
> group-shared (`[group]` manifest `collections = ["catalog"]`, owner-polymorphic marker table
> `0052_group_collections`, `kind='kv'`); the data lives in `0053_group_kv_entries`, keyed by the
> owning `group_id` (NOT app — a shared row belongs to the group). Scripts read/write via the
> **explicit** `kv::shared_collection("catalog")` handle (a distinct `GroupKvHandle`; `shared`
> alone is a Rhai reserved word, hence `shared_collection`). The service resolves the owning group
> from `cx.app_id`'s ancestor chain (nearest-group-wins) — **that walk is the isolation boundary**:
> a foreign app's chain never contains the owning group, so the name returns `CollectionNotShared`.
> **Trust model:** reads are open to any subtree script (anonymous public HTTP included — the
> declaration *is* the grant); **writes require an authenticated editor+** on the owning group
> (`GroupKvWrite`, fails closed on an anonymous principal). CASCADE on group delete; an app delete
> leaves the shared data. Live- + journey-validated (app A writes, app B reads; a sibling-subtree
> app gets `CollectionNotShared`).
>
> **Deferred (documented gaps):** write-triggers/events on shared collections (the "group trigger
> has no app to watch" problem); docs/files/topics/queue shared collections (the `kind` column
> generalizes); per-group total-size quotas + write-rate limits; CAS/`set_if`; app-declared
> collections. Multi-node tree-apply leans on the runtime backstop for no-op edges, as elsewhere.
### 11.1 Re-sequencing review (post-Phase-3)
A review after Phase 3 shipped surfaced three adjustments to the 4→6 plan; carried here as decisions