Commit Graph

18 Commits

Author SHA1 Message Date
MechaCat02
3df37b2b85 docs(suppress): flag advisory-by-default trust model; test dangling-suppress warning (§11 tail review)
Two review findings.

1. Per-app opt-out changes the group-template guarantee from "runs on every
   descendant" to "runs unless the descendant declines" — a footgun for
   compliance hooks (an audit/security trigger a tenant can silently opt out
   of). There is no non-suppressible flag. Document this in design §4.5 +
   CLAUDE.md, and record the deferred mitigation: a `sealed`/`mandatory`
   group-template marker the trigger anti-join + route filter skip (cheap —
   both filters are centralized).

2. The dangling-suppress warning path had no coverage. The suppress journey
   now applies a `[suppress] triggers=["does-not-exist"]`, asserts the apply
   still succeeds and the report warns "no effect".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 18:59:20 +02:00
MechaCat02
b74e1a401b feat(suppress): suppression journey + docs (§11 tail S5)
- Journey tests/suppress.rs: a group declares a `[[routes]]` template; a
  descendant app applies `[suppress] routes=["/ghello"]` → stops serving it
  (via `routes match`), a sibling still serves it, `pic suppress ls --app`
  shows it, re-apply is a NoOp; pruning the block re-inherits. The
  trigger-side filter + isolation are pinned at the repo layer by
  template_suppression.rs.
- Docs: design §4.5 (per-app opt-out closing the deferred gap for both
  templates — coarse-by-reference, inheritance-only, the two consumption
  points; group-level suppress still deferred), CLAUDE.md current-focus.

Full journey suite 121/121; workspace tests 34 suites/0 failures.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 08:05:06 +02:00
MechaCat02
b048daa700 refactor(routes): drop prod-dead compile_routes; document disabled-shadow (§11 tail review)
Two review findings.

1. After R3 every production rebuild path uses `compile_effective_routes`;
   `compile_routes` (the old app-only compile) was called only by its own
   two unit tests, and its "runs in build_app" doc was stale — so the
   lenient-skip + disabled-drop regression guards no longer covered the
   live path. Remove `compile_routes` + its re-export, fold the lenient-skip
   rationale onto `compile_one`, and port both guards to
   `compile_effective_routes` (app-only rows are just depth-0 effective
   rows). Add a third guard pinning nearest-owner shadowing at the pure-
   compile layer. Fix the stale `compile_routes` mention in apply_service.

2. Document the disabled + inherited semantic: the `enabled` filter runs
   before the shadow check, so a disabled own-route does not claim its
   binding and an enabled ancestor template at the same path falls through
   ("disabled = absent", §4.3). Recorded on `compile_effective_routes` and
   in design §4.5 — the corollary (can't 404 an inherited route by
   disabling a same-path own route) points at the deferred per-app opt-out.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 22:14:09 +02:00
MechaCat02
a977ff6a32 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>
2026-06-30 22:03:59 +02:00
MechaCat02
c492a08775 feat(cli): pic triggers ls --group + group-template journeys + docs (§11 tail T4)
- apply_service: trigger_report(group) → TriggerTemplateInfo
  (kind/target/script/enabled); resolve_inherited_targets_for(Group) now
  surfaces the group's OWN endpoint scripts so a template's handler
  validates (fixes "binds to unknown script" when the handler is a
  pre-existing group script, not declared in the same manifest).
- apply_api: GET /groups/{id}/triggers (GroupScriptsRead).
- CLI: `pic triggers ls --group <g>` (--app/--group mutually exclusive)
  + the client method + DTO.
- tests/group_trigger_templates.rs (manager-core, live DB): the chain
  union matches a descendant app's kv insert against the group template
  and NOT a sibling subtree — the isolation boundary, deterministic.
- tests/group_triggers.rs (journey): apply a kv template, ls --group
  shows it, re-apply NoOp, cron-on-group rejected.
- docs: design §4.5 (live-event-kinds decision + deferrals), CLAUDE.md.

Full journey suite 119/119; workspace tests 0 failures; clippy -D clean;
schema unchanged (blessed in T1).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 21:02:03 +02:00
MechaCat02
9c6d690792 test(cli): shared-files journey + .gitignore + docs (§11.6 files C5)
- collections.rs: shared_files_collection_is_read_write_across_the_subtree
  — a group declares kv+docs+files in one string-or-table manifest; authed
  app A creates a blob via files::shared_collection("assets").create(...),
  app B lists + gets the SAME bytes back across the subtree, a
  sibling-subtree app gets CollectionNotShared, collections ls shows all
  three kinds. Live-verified the bytes land under
  files/groups/<group_id>/assets/<id[0:2]>/<id>.
- .gitignore: ignore /crates/picloud-cli/data (the CLI journey harness
  spawns the server from that dir; the files journey is the first CLI test
  to write blobs).
- docs: design §11.6 (KV+DOCS+FILES shipped; topics/queue still deferred),
  sdk-shape (files::shared_collection), CLAUDE.md current-focus.

Full journey suite 117/117; schema blessed (55 migrations); workspace
clippy -D clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 19:56:47 +02:00
MechaCat02
a00454e5de test(cli): shared-docs journey + docs (§11.6 docs C5)
End-to-end docs journey: a group declares a kv AND a docs collection via the
string-or-table manifest; an authenticated app A docs::shared_collection(
"articles").create(#{...}), app B finds it back across the subtree (exercising
the shared find DSL); a sibling-subtree app gets CollectionNotShared;
collections ls shows both kinds; re-apply NoOp. Full journey suite 116/116.

Docs: groups-and-project-tool §11.6 (KV+docs slices shipped; files/topics/queue
deferred), sdk-shape.md (docs::shared_collection + string-or-table), CLAUDE.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 19:09:41 +02:00
MechaCat02
0973344515 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>
2026-06-29 22:26:43 +02:00
MechaCat02
a049397bb0 test(cli): extension-point journeys + node-key manifest fix + docs (§5.5 C5)
- Move the manifest `extension_points` from a top-level key to an `[app]`/
  `[group]` node key (`ManifestApp`/`ManifestGroup` + a `Manifest::extension_points()`
  accessor). A bare top-level array placed after the node header was silently
  re-nested by TOML into that table and dropped; as a node key it parses
  unambiguously wherever it sits. build_bundle/pull/init updated.
- Journeys (live server + Postgres): a group default body resolves for an
  inheriting app; the app provides its own module and OVERRIDES the EP (the
  inverse of the Phase 4b sealed import); `extension-points ls` shows the
  provider; a body-less EP with no provider fails `pic plan`.
- Re-bless the schema snapshot (new `extension_points` table).
- Docs: §5.5 marked complete (extension points ) + CLAUDE.md current-focus.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 20:53:18 +02:00
MechaCat02
52da8a8704 test(cli): group-module lexical-resolution journeys + Phase 4b docs (C5)
Some checks failed
CI / Rust — fmt, clippy, test (push) Failing after 17m58s
CI / Dashboard — check (push) Successful in 9m45s
Two journeys against a live server + Postgres:
- `group_module_is_lexically_sealed_under_inheritance`: a group module +
  a group endpoint that imports it, inherited by an app — proves the §5.5
  trust boundary (a leaf's same-named module does NOT shadow the inherited
  endpoint's import) and app-origin CoW (an app endpoint's import resolves
  the app's module).
- `dangling_import_is_rejected_by_plan`: a manifest script importing a
  non-existent module is a `pic plan` error.

Docs: mark §5.5 residual resolved + §11 Phase 4b  in the design doc;
update CLAUDE.md current-focus (extension points are the remaining §5.5
piece).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 07:39:08 +02:00
MechaCat02
3ef3038eb7 test(cli): project-tree journeys + Phase 5 docs
Phase 5 C5. Three `pic ... --dir` tree journeys:
  * a group + a nested app apply atomically as one tree (the app's route binds
    the group's same-apply `shared` script); the group node's script is
    created; re-plan is all-noop (idempotent),
  * one invalid node aborts the whole tree — the valid group node's script is
    NOT created (true all-or-nothing),
  * a `pic groups reparent` between `pic plan --dir` and `pic apply --dir` trips
    the bound-plan (StateMoved) check via the tree's structure version.

Docs: `groups-and-project-tool.md` §11.5 status ( shipped — single-repo
nested tree apply; multi-repo single-owner + per-env approval gating deferred
per §11.1) + CLAUDE.md current-focus.

Gates: fmt, clippy -D warnings, cargo test --workspace, check-versioning (50
migrations — Phase 5 added none), schema snapshot unchanged, full journey suite
108/108. (Pre-existing flake `queue_e2e::queue_receive_acks_on_success` — a
racy immediate `count==0` after the async ack; flaky at the pre-Phase-5
baseline too, unrelated to this work.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 22:43:20 +02:00
MechaCat02
9598db149b 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>
2026-06-25 20:44:51 +02:00
MechaCat02
b46667c309 docs: reconcile the groups/project-tool initiative into the roadmap
The groups/project-tool work had no home in the blueprint's version
scheme, two "Phase N" numbering schemes overlapped, and CLAUDE.md's
"current focus" was stale (claimed v1.1.0 SDK foundation; we're at v1.1.9
with the SDK line complete). Reconcile all three:

* blueprint §12 Phase 5 (v1.2, already titled "Advanced Workflows &
  Hierarchies") — split into a Hierarchies track (the groups + project
  tool initiative, with §11 Phases 1–3 marked shipped) and a Workflows
  track, with sequencing flagged as an open call. This gives the work an
  explicit version home: the Hierarchies half of v1.2.
* design doc header — Status Draft → Active, "scheduled as the Hierarchies
  track of v1.2", and an explicit warning that §11's local Phase 1–6
  numbering is distinct from the blueprint product-phase numbering.
* design doc §11.1 — a post-Phase-3 re-sequencing review: resolve Phase 4
  scripts LIVE (drop the fan-out-invalidation sub-project, per the Phase 3
  result); consider a "Phase 5-lite" (declarative apply of vars/secrets +
  app scripts) before the hard group-scripts work; and force the two
  deferred decisions (trigger/route templates vs tenant cardinality;
  multi-repo ownership vs a single-repo start).
* CLAUDE.md — refresh "current focus" to v1.2 Hierarchies, and correct the
  now-broken "every v1.1+ table is app_id NOT NULL" invariant: the new
  group-inheritable config tables (vars, secrets) use a polymorphic owner
  (group_id XOR app_id), not app_id NOT NULL.

Doc-only; no code change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 07:12:25 +02:00
MechaCat02
ca360d84cb docs(design): record Phase 3 shipped + the live-resolution decision
Phase 3 (group-inherited config) is implemented, so fold the outcome back
into the design doc (per its own "before it drifts" note) — and flag the
one deliberate divergence from the plan.

§5.1 / §11.3 / §12 prescribed a *materialized* per-app effective view with
a generation counter and fan-out invalidation. We shipped **live,
resolve-on-read inheritance** instead: one recursive CTE per
`vars::get`/`secrets::get`/`config/effective`, no cache. The org tree is
small and there was no pre-existing config cache to fight, so this is
cheap and sidesteps §10's unsolved invalidation SLA.

Records, mirroring the Phase 1/2 status blocks:
* a "Status (Phase 3):  shipped" block in §11.3 with the full surface
  (0048/0049 schema incl. `apps.environment`, the §3 resolver, vars SDK +
  CRUD, group-secret AAD + inherited read, the two secret gates, masked
  `config/effective`, `--explain`) and the live-vs-materialized rationale;
* §5.1 runtime-model bullet annotated as the deferred (Phase-4 script
  inheritance) design, not Phase-3 runtime;
* §10 "invalidation SLA" and "inherited-membership revocation lag" marked
  moot/resolved — no cache means immediate propagation;
* §12 resolver contract + the header framing updated to live resolution.

Doc-only; no code change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 22:53:58 +02:00
MechaCat02
49c4fb41ce docs(design): mark Phase 2 (groups) shipped + record decisions/deferrals
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 20:16:29 +02:00
MechaCat02
695987d6b7 docs(design): record async disabled-execution + cross-app backstops
Update §4.3: the fire-time `enabled` re-check is shipped and test-backed on
BOTH async arms (the unified outbox `!resolved.active` gate and the queue
arm's fresh-script nack), with the specific regression tests cited, plus the
same-app backstop now present on every async build path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 19:06:52 +02:00
MechaCat02
5e62f4acfe docs(design): mark Phase 1 shipped + record decisions/deferrals
Fold the implementation outcome back into the project-tool spec (§11) as
the design asks. Records what landed (init/pull/config/plan/apply/prune,
env overlays, enabled lifecycle, trigger name+backfill, content-fingerprint
bound-plan, apply lock) and the deliberate deferrals: tree-structure
version → groups; vars/--explain → Phase 3; trigger upsert-by-name + the
dashboard enabled toggle → tracked follow-ups beyond the §11 bullets.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 21:20:32 +02:00
MechaCat02
c600177fd6 docs(design): groups & declarative project-tool spec
The architecture spec for the full vision — a declarative project tool
(picloud.toml + pull/plan/apply/prune) and a server-side nested-groups
hierarchy with live-resolve inheritance. Reviewed across several passes
for consistency, gaps, and contradictions; resolutions are folded in
beside the topics they settle.

This commit lands the spec only. The first slice of implementation (the
single-app reconcile foundation) follows; groups, env-scoping, and the
`enabled` toggle are later milestones called out in the doc.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 21:52:01 +02:00