Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
a15bae7
publisher: make the whole dataset catalog visible; keep writes owner-…
claude Jul 14, 2026
a1aadd5
publisher: give non-admin publishers read-only access to hero, analyt…
claude Jul 14, 2026
37bbdeb
events: read-all / write-own access model with approval-based ownership
claude Jul 14, 2026
e3753f6
blog: read-all / write-own access model (author-scoped writes)
claude Jul 14, 2026
3abb87b
publisher portal: surface Events + Blog to all publishers, gate write…
claude Jul 14, 2026
bb5dc51
docs: add publisher roles & capabilities scoping plan
claude Jul 15, 2026
5ced7a6
roles R1: capability layer (behavior-preserving)
claude Jul 15, 2026
21f821f
roles R2+R3: real Reviewer + Editor; canonical role names
claude Jul 15, 2026
0361798
roles R4: Contributor no-self-publish + events approval reconciliatio…
claude Jul 15, 2026
97d84e1
roles R5: portal — five-role Users tab, capability-gated controls
claude Jul 15, 2026
a4e6716
docs: mark publisher roles plan implemented (R1–R5)
claude Jul 15, 2026
2af5268
security: close authorization gaps found in the roles review
claude Jul 15, 2026
78a17f5
fix(ci): regenerate catalog schema snapshot for event owner_id
claude Jul 15, 2026
c381603
publisher: role → capability guide on the Team tab
claude Jul 15, 2026
2dcfcfa
i18n: refresh Team roles note for the five-role model
claude Jul 15, 2026
f4acd38
docs/i18n: align hero + events copy with the five-role matrix
claude Jul 15, 2026
06c2fc6
publisher: let non-owners view an event's video; sharpen blog publish…
claude Jul 15, 2026
55a4f2d
publisher: address second Copilot review round
claude Jul 15, 2026
de96601
publisher: render retired publisher/readonly roles as their canonical…
claude Jul 15, 2026
1472e51
publisher: gate the Import tab on content.create
claude Jul 15, 2026
e9459d5
publisher: hide Tours/Blog create buttons for non-authoring roles
claude Jul 15, 2026
790e64d
publisher: gate the Datasets New-draft button on content.create
claude Jul 15, 2026
8ac2686
publisher: fix import gate race + refresh stale authz docstrings
claude Jul 15, 2026
a7a2fbe
publisher: least-privilege provisioning defaults + first-admin bootstrap
claude Jul 15, 2026
a1039a6
docs/tests: correct stale authz descriptions to match enforced gates
claude Jul 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ npm run screenshots:smoke # gating interaction tests (search, Orbit, nav)
| `src/types/image-sequence-constants.ts` | Constants shared by the publisher API (`functions/`), the GHA runner (`cli/`), and the portal (`src/`) for the image-sequence upload pipeline |
| `src/types/zyra-workflow-constants.ts` | Constants shared by the publisher API (`functions/`), the GHA runner (`cli/`), and the portal (`src/`) for the Zyra workflow pipeline — stage/command allowlist, template fields, run statuses (`docs/ZYRA_INTEGRATION_PLAN.md`) |
| `src/types/node-features.ts` | Per-node feature-toggle constants shared by the publisher API (`functions/`) and the portal + public SPA — `FEATURE_KEYS` / `FeatureMap`, all-on defaults, fail-open normalization (missing/unknown keys resolve to enabled) |
| `src/types/publisher-roles.ts` | Publisher role → capability matrix shared by the publisher API (`functions/`) and the portal — `CAPABILITIES` / `ROLES` / `ROLE_CAPABILITIES`, `roleCan` / `capabilitiesForRole`, legacy-string `normalizeRole` (fail-closed to `reviewer`). The single source of truth for authorization (`docs/PUBLISHER_ROLES_PLAN.md`) |
| `src/data/regions.ts` | Common region bounding boxes for name-based region resolution |
| `src/services/orbitCharacter/index.ts` | `OrbitController` — public API for the Orbit character (owns the Three.js scene, rAF loop, state machine) |
| `src/services/orbitCharacter/orbitScene.ts` | Three.js scene + per-frame update for the Orbit character |
Expand Down Expand Up @@ -243,14 +244,14 @@ npm run screenshots:smoke # gating interaction tests (search, Orbit, nav)
| `src/ui/publisher/pages/workflows.ts` | `/publish/workflows` — Zyra workflow list |
| `src/ui/publisher/pages/workflow-detail.ts` | `/publish/workflows/:id` — workflow summary + run history + Run now |
| `src/ui/publisher/pages/workflow-edit.ts` | `/publish/workflows/new` + `…/:id/edit` — workflow form (YAML→JSON client-side, server-side Validate) |
| `src/ui/publisher/pages/featured-hero.ts` | `/publish/featured-hero` — set the "Right now" hero override (`docs/HERO_ADMIN_SCOPING.md`) |
| `src/ui/publisher/pages/featured-hero.ts` | `/publish/featured-hero` — set the "Right now" hero override (`hero.manage`: editors / admins); callers without it get a read-only view of the current pin (`docs/HERO_ADMIN_SCOPING.md`) |
| `src/ui/publisher/pages/node-profile.ts` | `/publish/node-profile` — edit the node / host-organization profile (org name, mission, about, region focus, tone, links) — the "about the host" context Phase 3d AI drafts ground themselves in |
| `src/ui/publisher/pages/blog.ts` | `/publish/blog` — blog authoring list (drafts + published, status badges, New post) |
| `src/ui/publisher/pages/blog-edit.ts` | `/publish/blog/new` + `…/:id/edit` — tabbed blog editor (Content / Sources / Media / AI draft): dataset/event grounding pickers, the **Media** tab (reuses the Events-tab `media-suggest` engine — Worldview / Commons / ShakeMap / NHC / agency YouTube + the cited event's story image — to insert imagery into the body or set the post's cover image), the AI Generate panel (tone/length/companion-tour → `POST /publish/blog/generate`), markdown body with the shared toolbar + sanitized Preview, Save/Publish/Unpublish |
| `src/ui/publisher/pages/feeds.ts` | `/publish/feeds` — the current-events feed console: registered connectors (pause/resume/remove, Run now, last-run status), the curated preset gallery, and the bring-your-own RSS/Atom form (`docs/CURRENT_EVENTS_PLAN.md` §9) |
| `src/ui/publisher/pages/events.ts` | `/publish/events` — current-events review queue: curator approve/reject of proposed events + their dataset links (`docs/CURRENT_EVENTS_PLAN.md` §5) |
| `src/ui/publisher/pages/analytics.ts` | `/publish/analytics` — privileged analytics dashboard over the D1 rollups, incl. the MapLibre spatial-attention heatmap (Phase B of `docs/ANALYTICS_STORAGE_AND_ADMIN_PLAN.md`) |
| `src/ui/publisher/pages/feedback.ts` | `/publish/feedback` — privileged feedback review (AI thumbs + bug/feature reports) over the D1 feedback tables; replaces the feedback-admin HTML dashboard (Phase C of `docs/ANALYTICS_STORAGE_AND_ADMIN_PLAN.md`) |
| `src/ui/publisher/pages/analytics.ts` | `/publish/analytics` — read-only analytics dashboard over the D1 rollups (open to any active publisher), incl. the MapLibre spatial-attention heatmap (Phase B of `docs/ANALYTICS_STORAGE_AND_ADMIN_PLAN.md`) |
| `src/ui/publisher/pages/feedback.ts` | `/publish/feedback` — read-only feedback review (AI thumbs + bug/feature reports; open to any active publisher) over the D1 feedback tables; replaces the feedback-admin HTML dashboard (Phase C of `docs/ANALYTICS_STORAGE_AND_ADMIN_PLAN.md`) |
| `src/ui/publisher/pages/me.ts` | `/publish/me` — current-user identity + role display |
| `src/ui/publisher/pages/users.ts` | `/publish/users` — admin-only Users tab: approve / reject / suspend / reactivate publishers and change roles (admin / publisher / readonly) |
| `src/ui/tourAuthoring/index.ts` | Tour-authoring public surface — detects `?tourEdit=` and mounts the dock |
Expand Down
1 change: 1 addition & 0 deletions docs/BACKEND_MODULES.md
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ design rationale in the `docs/CATALOG_*` plan docs.
| `functions/api/v1/_lib/preview-token.ts` | Short-lived signed preview tokens for unpublished datasets and tours |
| `functions/api/v1/_lib/publisher-mutations.ts` | Admin user-administration store helper — list / get / update publishers, self-lockout + last-admin guardrails |
| `functions/api/v1/_lib/publisher-store.ts` | D1 reader / writer for the `publishers` table |
| `functions/api/v1/_lib/capabilities.ts` | Server-side capability check (`can` / `canOwnOrAny`) — thin adapter over the shared role→capability matrix (`src/types/publisher-roles.ts`); the authorization primitive routes/mutations gate on (`docs/PUBLISHER_ROLES_PLAN.md`) |
| `functions/api/v1/_lib/r2-public-url.ts` | Build a publicly-readable URL for an R2 object key |
| `functions/api/v1/_lib/image-upload.ts` | Shared validation for small direct image uploads (base64-in-JSON): png/jpeg/webp allowlist, magic-byte check against the claimed type, bounded decode, sha256 for content-addressed keys — used by the node-profile logo and tour-media routes |
| `functions/api/v1/_lib/r2-store.ts` | R2 storage helpers — Phase 1b |
Expand Down
15 changes: 10 additions & 5 deletions docs/CATALOG_PUBLISHING_TOOLS.md
Original file line number Diff line number Diff line change
Expand Up @@ -704,11 +704,16 @@ Phase 3 ships a two-tier human role model behind Cloudflare Access:

- Cloudflare Access protects `/publish/**` and `/api/v1/publish/**`.
- On first login, the API handler reads the Access JWT and finds an
existing `publishers` row by email or JIT-provisions one. A login
from a `TRUSTED_PUBLISHER_DOMAINS` domain (or dev-bypass) provisions
as `role='admin', status='active'`; any other user login provisions
as `role='publisher', status='pending'`; service tokens as
`role='service', status='active'`.
existing `publishers` row by email or JIT-provisions one. Defaults are
least-privilege: a login from a `TRUSTED_PUBLISHER_DOMAINS` domain
provisions as `role='reviewer', status='active'` (auto-approved,
read-only); any other user login as `role='reviewer',
status='pending'`; service tokens as `role='service',
status='active'`; dev-bypass as `role='admin', status='active'`. The
one exception is the bootstrap: the first human on a deploy with **no
active admin** is provisioned `role='admin', status='active'` (service
tokens excepted) so there is always an operator to approve/promote the
rest.
- `affiliation` defaults to the deploying organisation's name (a
Wrangler env var) and is editable in the portal's profile page.
- **Admins** have full administrative authority over the deploying
Expand Down
Loading
Loading