HydraIssues

Self-service experience management for external creators (Cyborn / Gallo-Romeins) via hydramancer — synthesis + plan
open unclassified Project: hydramancer Reporter: 15 Aug 2026 11:55

Description

Self-service experience management for external creators (Cyborn / Gallo-Romeins) via hydramancer

Synthesis of three investigations (hydramancer lens #488, library lens #487, authz lens #489). This is the consolidated, decisive findings + implementation plan.

1. Summary

The Cyborn pipeline now works end to end: Cyborn submits a build to Perforce, hydraperforcewatcher zips and publishes it to the mirror, POST /api/v1/builds/notify fires, and hydraexperiencelibrary registers it as a development build against the gallo-romeins-museum experience. What Cyborn still cannot do is see their experience's state or drive its lifecycle (stage, promote, rollback, pause). Today an operator does every one of those steps with the library's admin token. This work gives Cyborn a self-service, org-scoped view and a scoped set of lifecycle controls, delivered through the existing hydramancer creator portal — reusing the exact sign-in + privileged-proxy pattern already shipped for the "Get Perforce access" panel. Now is the moment because the plumbing is proven and the only missing piece is a safe, org-scoped control surface.

2. What already exists to leverage

hydramancer (the creator portal) already has the whole front half:

  • iamnim sign-in wired on /experience: GET /experience/login -> iamnim /login?redirect_uri=.../experience/authed; GET /experience/authed stores the token in the iamnim_session cookie (HttpOnly/Secure/Lax/24h); GET /experience/logout clears it. Session read by iamnimSession() from cookie / X-Iamnim-Session header / ?token=. Files: internal/api/handlers_experience.go:14-50, handlers_provision.go:59-67.
  • Identity + memberships via internal/iamnim/client.go: Me(session) (GET /api/me), Memberships(session) (GET /api/me/memberships -> {organization_slug, organization_name}).
  • The exact proxy pattern to copy — the Perforce panel: POST /api/v1/provision/perforce (server.go:50) -> handleProvisionPerforce (handlers_provision.go:16-55): require session, forward downstream as X-Iamnim-Session, portal holds no privileged creds, upstream status/body streamed back. Upstream URL from config (config.go:17-20, env HYDRAMANCER_PROVISION_PERFORCE_URL); empty URL disables the panel.
  • The panel UI is data-driven: experience.html:148-171 gated on .CanProvision, backed by experienceData{SignedIn,Email,Orgs,CanProvision} (server.go:71-94), with fetch+render JS at experience.html:445-480.

hydraexperiencelibrary already has the whole back half — a full lifecycle API and admin UI:

  • JSON API (all requireAuth, server.go:124-155): GET /api/v1/experiences, GET /api/v1/experiences/{name}, GET .../builds, PUT, POST .../promote ({"to":"staging"|"production"}, empty = auto-detect, handlers.go:197-274), .../rollback (288-320), .../pause / .../resume / .../retire (322-371), .../stream, POST /api/v1/builds/notify.
  • Rich build state via GET /pipeline/health?experience= (handlers_pipeline.go:264-326, last 10 builds) and GET /experiences/{name}/builds (handlers.go:453-474). The Experience record carries only int pointers DevelopmentBuild/StagingBuild/ProductionBuild/PreviousBuild (store.go:25-28); build detail lives in a separate builds.yaml store (build.go).
  • Admin web UI (all RequireWebAuth cookie session, server.go:99-109): GET /admin, GET /admin/experiences/{name}, POST /admin/experiences/{name}/{promote,rollback,pause,resume,retire,stream}.
  • The org tie is already in the data: Experience.Developer and Experience.Owner (store.go:17-43); gallo-romeins-museum has developer="cyborn", owner="gallo-romeins-museum".

3. The authorization gap and the decision

The gap. The library API authorizes on a single shared admin bearer token (middleware.go:10-24) — all-or-nothing, no org/tenant awareness anywhere. handleListExperiences (handlers.go:17-24) returns the entire catalog unfiltered. The admin web UI is the same token behind a cookie. There is no per-developer/owner code path in the library at all.

Decision: Option A — enforce org scoping in a hydramancer proxy that holds the library admin token, mirroring handleProvisionPerforce. Leave the library API unchanged for the initial rollout. This is unanimous across all three lenses. Rationale:

  • It reuses a proven, already-deployed pattern (the Perforce proxy) and keeps the privileged token server-side in the portal, never in the browser.
  • It touches only hydramancer (a low-risk portal), not the live experience-library scale that sits behind Traefik and drives real venues.
  • Option B (make the library itself org-aware via a forwarded X-Iamnim-Session, exactly like hydraperforceprovision) is the correct durable end state and should be the follow-up once a second creator org exists — but it is a larger change to a live service and is not justified for a single-tenant MVP.

The one inversion that makes this harder than Perforce — and the scoping rule. In the Perforce proxy the client names its own org and receives a depot named after it, so naming a foreign org buys the caller nothing (hydraperforceprovision/internal/api/handlers.go:21-92, IsMember exact-match at internal/iamnim/client.go:63-75). With experiences, the request names an experience {name}, and the owning org is a property of the stored record, not of the request. Therefore the proxy must never trust any org field from the client. The rule:

Which experiences may this creator touch = experiences whose developer (control) — or owner (read-only candidate) — org field is one of the caller's iamnim membership slugs.

Concretely, every action path is: (1) authenticate the session with Me() (fail closed on error); (2) resolve the caller's membership slugs via Memberships(); (3) load the target experience from the library first and read its developer; (4) allow only if that org is in the caller's slug set; (5) only then forward to the library with the admin token. List views filter the full catalog down to the caller's orgs — they never return it whole.

Making org identifiers match reliably (a real correctness risk, not cosmetic). experience.developer is free-text operator-set YAML compared against Pantheon slugs. The known mismatch: the experience carries developer="cyborn" and owner="gallo-romeins-museum", while the Perforce/depot normalization uses galloromeinsmuseum (see memory: Gallo-Romeins depot galloromeinsmuseum, user koen). For the MVP the control field is developer="cyborn" and the iamnim org slug is cyborn — these match exactly, so Cyborn works today. But before scoping is enabled for any second org, we must: (a) apply one canonical normalization (lowercase, strip non-alphanumerics) on both the membership slug and the experience org field before comparison, and (b) add a startup validation that every experience's developer/owner resolves to a known org slug, logging mismatches. Treat experience.developer == iamnim organization_slug as a documented invariant until (b) enforces it.

4. Scope: what a creator can do

Read (MVP): list the experiences their org owns/develops; per-experience status and build state (draft/development/staging/live, the DevelopmentBuild/StagingBuild/ProductionBuild pointers, and recent build history via pipeline/health?experience=).

Lifecycle actions to expose (Phase 2), all org-scoped:

  • promote -> staging (POST .../promote {"to":"staging"}) — staging nodes only, safe.
  • rollback (POST .../rollback) — reverts to the previous build, safe and reversible.
  • pause / resume (POST .../pause / .../resume) — safe operational toggles.

Gated / operator-only:

  • promote -> PRODUCTION ({"to":"production"}) invokes reprovisionProduction, which pushes to live public venues (handlers.go:246-263). Keep this operator-gated for the MVP; a later phase may allow it behind an explicit typed confirmation + audit entry. Justification: production promotion changes what the public sees in a venue; a self-service creator should not trigger that unsupervised on first release.
  • retire — near-terminal; operator-only.
  • create / PUT / DELETE — these set the boundary fields themselves (developer, owner, districts, watch_name, auto_promote, exe_path, release_path). A creator editing them could re-point scoping or change what gets executed. Hard out of scope.

Explicitly out of scope: creating or deleting experiences; editing exe_path / release_path / watch_name / districts / auto_promote; any cross-org visibility.

5. Implementation plan (mirrors the Perforce panel)

hydramancer — config

  • Add an ExperienceLibrary{ base_url, admin_token } block to config.go, with env overrides HYDRAMANCER_EXPERIENCE_LIBRARY_URL and HYDRAMANCER_EXPERIENCE_LIBRARY_TOKEN. Empty base_url disables the panel (same convention as HYDRAMANCER_PROVISION_PERFORCE_URL). Token is server-side only, never rendered.

hydramancer — iamnim client

  • Add IsMember(session, orgSlug) to internal/iamnim/client.go (copy the exact-match scan from hydraperforceprovision/internal/iamnim/client.go:63-75) plus a normalized-compare helper.

hydramancer — proxy routes + scoping middleware (handlers_experiences.go)

  • GET /api/v1/experiences (scoped): require session -> Memberships() -> call library GET /api/v1/experiences with the admin token -> filter to experiences whose normalized developer/owner is in the caller's slug set -> return the filtered list (+ status/build fields).
  • GET /api/v1/experiences/{name} (scoped): load from library, org-check on the record, return or 403.
  • POST /api/v1/experiences/{name}/{action} where action ∈ {promote-staging, rollback, pause, resume}: require session -> load the experience from the library first -> derive the authorizing org from experience.developer (never from the request body) -> IsMember check -> forward to the matching library route with the admin token -> stream status/body back. Reject any action not in the allow-list (this is what keeps production-promote / retire / PUT out). Reuse iamnimSession() and writeProvisionError().
  • Fail closed on any iamnim or library error (deny, do not default-allow).
  • Add an audit log line per privileged forward (who, which experience, which action).

hydramancer — UI

  • Add a "Manage your experiences" panel to experience.html, data-driven exactly like the Perforce panel (a CanManageExperiences flag on experienceData, populated when the library config is present and the user has ≥1 experience). List the org's experiences with status + build info and the scoped action buttons; production-promote and retire are absent (not merely disabled). Reuse the existing fetch/render JS shape at experience.html:445-480.

Optional library-side hardening (Phase 3, Option-B-lite): add a developer= query filter to the library's GET /api/v1/experiences as a defense-in-depth second fence, so even a proxy bug cannot list foreign experiences. This is additive and does not change the auth model.

Tests + docs: unit tests for the scoping middleware (member allowed, non-member 403, unlisted experience filtered out, foreign org name in body ignored, iamnim error -> deny); a runbook in hydramancer documenting the config, the scoping invariant, and the developer == org slug normalization rule (memory requires a runbook to back any operational memory entry).

6. Risks & mitigations

  • Authorization boundary (top risk). Always resolve the authorizing org from the stored experience record, never the request. List views filter; they never return the full catalog. Unit-tested member/non-member/foreign-body cases. This is the single most important correctness property.
  • Slug/name mismatch. experience.developer is free-text YAML vs Pantheon slugs; gallo-romeins-museum/galloromeinsmuseum vs cyborn. Mitigation: one canonical normalization on both sides + startup validation that every experience org resolves to a known slug; document developer == org slug as an invariant. Cyborn's developer="cyborn" matches the slug cyborn today, so the MVP is safe.
  • Admin-token blast radius. The proxy holds a full-privilege token. Mitigation: server-side only, per-experience scoping on every path, explicit action allow-list, audit log, empty-config disables the panel.
  • Promoting untested builds. A creator could promote a broken development build to staging. Mitigation: staging is the safe target by design (staging nodes, not public venues); rollback is one click; production stays operator-gated.
  • exe_path / packaging. Promotion assumes the build's exe_path/release_path are correct — creator-visible but creator-uneditable (operator-only fields). If a build is mis-packaged, promotion to staging surfaces it safely before any operator touches production.
  • watch_name fan-out. builds/notify keys on watch_name via FindByWatchName (store.go:221-229) and fans one build to every experience sharing it. This is inbound-only and unchanged by this work, but reinforces that watch_name must stay operator-only (it is, under "out of scope").
  • Fail-open on identity errors. Any iamnim/membership/library failure must deny, never default-allow.

7. Effort estimate and phasing

Estimate: ~2-3 engineering days for Phases 1-2 (the pattern is copy-adapt from the Perforce proxy; the library needs no changes), plus ~1 day for tests, runbook, and the optional library developer= filter.

  • Phase 1 (MVP) — read-only. Config block + iamnim IsMember; GET /api/v1/experiences (scoped, filtered) and GET .../{name}; the "Manage your experiences" panel showing each experience's status and build state. No mutations. Ships Cyborn immediate visibility with near-zero blast radius.
  • Phase 2 — scoped actions. Add POST .../{name}/{promote-staging,rollback,pause,resume} behind the action allow-list and org check, with the audit log. Production-promote and retire stay operator-only.
  • Phase 3 — hardening / durability. Optional library-side developer= filter (defense in depth); startup org-slug validation; and, once a second creator org exists, migrate to Option B (library-side iamnim auth via forwarded X-Iamnim-Session, mirroring hydraperforceprovision) as the long-term auth model.

Related: this synthesizes and supersedes the per-lens issues #487, #488, #489. No code, deploy, or live-state change was made.

Sub-issues (3)

closed #749 Default Unreal delivery to both Windows and Linux; publish tool and guide
closed #747 HydraMancer: simplify onboarding around HydraUnrealEngine and publish the guide
closed #742 HydraMancer: guide developers from the Unreal template to a verified body experience

Comments (2)

creator-portal-assessment 15 Aug 2026 20:58

Broaden #490 from "experience management" to a creator dashboard on hydramancer

Synthesis of five tool-landscape investigations (build-delivery, perforce, experience-venue, observability, support-docs). This comment does not change the existing lifecycle plan — it widens the scope around it and records the negative space. All claims are grounded in the repos under review; no live change was made.

1. Reframe

The lifecycle plan in #490 is right, but it is one panel of a bigger job. Cyborn's mental model is a delivery loop: submit to Perforce -> package/upload -> publish to mirror -> registered as a build -> stage/promote -> live on a venue body (fluffy) -> something breaks -> report it. #490 today only lights up the middle (stage/promote/rollback/pause). Everything upstream ("did my submit land? did my package build? where's the download?") and downstream ("is it actually running at the museum right now? who do I tell when it crashes?") is still invisible or operator-relayed. The proposal: keep experience lifecycle as the anchor panel (unchanged) and reframe the issue as a single org-scoped creator dashboard where Cyborn sees their whole loop and holds the few controls they need. Crucially, almost every additional panel reuses the exact org-scoping proxy #490 already designs for the library — so the marginal cost per panel is small once that middleware exists.

2. Recommended panels, prioritized

# Panel Source tool(s) / URL What the creator sees / does Org-scoping today Priority Effort
1 Experience lifecycle (anchor) hydraexperiencelibrary List experiences; status + build pointers (dev/staging/prod); promote->staging, rollback, pause/resume. Prod-promote & retire stay operator-gated. Single admin token, all-tenants — needs the #490 scoping proxy (scope on Experience.developer) P0 Per #490 (~3d)
1a "Runs at" + "Live now?" (extend panel 1) hydraexperiencelibrary Show Experience.Districts (which venues it's assigned to) and a live indicator via GET /api/v1/experiences/live intersected with the owned experience. Same record the proxy already loads/org-checks — no new scoping surface P0 XS (Districts already fetched; one extra scoped read)
2 Report a problem hydraissue (POST /api/v1/issues, .../comments) File an issue straight from the portal with session.experience/venue auto-stamped and reporter = iamnim identity; stamp custom_fields.owner_org. Filing needs no upstream change — pure proxy holds the api_token P1 S (pure proxy)
3 Build pipeline / source control hydraperforce (hydraperforce.experiencenet.com) "Did my submit land?" — last processed changelist, last poll time, recent-CL history + descriptions, watcher online/offline. Single admin_token, all-tenants HTML, no per-org JSON read. Needs a small scoped GET /api/v1/agents/{agentID}/state + an org->agent_id map (cyborn/galloromeins -> galloromeins) in the proxy. Do NOT iframe /admin (leaks every venue). P1 S–M (JSON model exists; add one scoped read handler)
4 Build & delivery status hydraunrealengine-server (hydraunrealengine.experiencenet.com) The stage before the library's "development" build: preflight results, package/upload progress, failure reason, delivered download URL. GET /api/v1/jobs, /jobs/{id}. Single admin token AND no usable org key — the only tenancy-ish field Client is the workstation hostname (report.go:132), not an org. Needs a scoping key first (reporter emits org/developer, or a hostname->org map) before proxy filtering works. P1 M + small CLI/reporter change for the org key
5 Live / run status hydrabodystatus (.../bodies) + hydrastreamingmonitor (sessions/stream) Is the body at the venue online, and is my experience the active stream? Recent session durations + end_reason; body online + basic health. Worst instance of the gap: Body has no owner/org field at all — must cross-join owned-experience -> its districts/venues -> filter bodies/sessions by location, then field-allow-list the payload (strip GPU/power/other tenants' StreamSessions, raw body logs). Scoping key is experience->developer, not node Owner (that's the museum, not Cyborn). P2 M–L (cross-service join + payload reduction)
6 Docs & getting started hydrabooks (books.experiencenet.com) Getting-started / "delivering a build to Gallo-Romeins" runbook + the experience testbook, searchable. GET /api/v1/books/{project}?format=html, /search. Reads are public + project-keyed by design — no private-data gap, no scoping proxy needed. Prereq: author the creator-facing runbook into a project namespace. P2 S (read-only embed / link-out)
6a Venue context (enrich panel 1a) hydravenues (hydravenues.experiencenet.com) Address, bandwidth, floor/layout, technical rider/checklist for the museum, fetched by venue id. Public reads, scoped implicitly by the venue ids the owned experience references. Do not use its ?organization= filter — that keys on venue-owner org (museum), not developer (Cyborn). P2 S (public reads + /docs link-out)
7 "My issues" list hydraissue Watch status/comments on issues the creator filed, without operator relay. Recurring gap: no owner/org field or filter; the public index (GET /{$}) renders every tenant's issues. Must filter server-side in the proxy by the stamped owner_org/reporter_email; never link to the public index. P2 M (proxy-side filter; long-term: add owner_org field + owner= filter upstream)

Considered and REJECTED (record the negative space):

  • hydrapipeline — operator/SRE platform-health dashboard; no experience/venue/org dimension; would leak the full service/infra inventory. Not relevant.
  • hydramirror — internal artifact store; API-only, single-token all-tenants index; a creator wants the resulting build record + download URL, not the mirror. Do not surface.
  • hydraperforcewatcher — push-only agent with no web API; its data reaches the portal only through hydraperforce (panel 3). Don't build a new API on it.
  • hydratransfer — the delivery pipe, but the download URL is already captured on the packaging job record (panel 4). Admin views are all-tenants; do not proxy them. Link-out by unguessable ID only if richer progress is ever wanted.
  • hydraunrealengine CLI / hydrarelease — the packager runs on Cyborn's own Windows box; only a link-out ("download the packager" from releases.experiencenet.com) belongs in the portal, not a hosted panel.

3. The cross-cutting authorization theme (the central architectural point)

Almost every backend above is single-token / all-tenants — the exact gap #490 already names for the experience library recurs in hydraperforce, hydraunrealengine-server, hydrabodystatus, hydrastreamingmonitor, and hydraissue. That is not five separate problems; it is one. The org-scoping proxy #490 introduces (fail-closed: authenticate with iamnim Me(), resolve membership slugs, load the target record, authorize on the stored org field never the request, filter list views to the caller's orgs) is a reusable middleware. Build it once for the library, then every additional panel is "add a scoped route behind the same fence." Two scoping-key nuances the middleware must encode, because they trip up naive filtering:

  • The creator axis is developer (Cyborn), not owner (Gallo-Romeins Museum). Node Owner, venue OrganizationID, and Perforce depot owner all key on the customer org and are the wrong axis for a creator. The reliable link is experience.developer == iamnim org slug, then experience -> its districts/venues/watch-agent for anything that lacks a developer field.
  • Some tools carry no org field at all (hydrabodystatus) or no org-usable field (hydraunrealengine-server's Client = hostname). Those need either a cross-service join (experience -> location) plus payload allow-listing, or a small upstream change to emit an org key, before they can be scoped. Flag each as blocked-on-scoping, not shippable read-as-is.

4. Suggested concrete adjustments to #490

  • Retitle / reframe from "Self-service experience management" to "Creator dashboard for external creators (Cyborn / Gallo-Romeins) via hydramancer"; make the existing lifecycle plan panel #1 (the anchor / MVP), not the whole scope. Keep sections 3–6 of #490 (the Option-A proxy, scoping rule, action allow-list) exactly as-is — they become the shared middleware.
  • Extend Phase 1 (read-only) at near-zero cost with "Runs at" (Experience.Districts) and "Live now?" (GET /api/v1/experiences/live) — both ride the record the proxy already loads. (XS)
  • Add P1 panel "Report a problem" (hydraissue POST /api/v1/issues, session/experience auto-stamped): pure proxy, no upstream change, shippable independently of and before the lifecycle work. Note the "My issues" read view is P2 and needs proxy-side owner filtering (never the public index).
  • Add P1 panel "Build pipeline / source control" (hydraperforce): requires a small upstream addition — a scoped GET /api/v1/agents/{agentID}/state JSON endpoint (data model already has json tags) plus an org_slug->agent_id map in the proxy. Do not iframe /admin.
  • Add P1 panel "Build & delivery status" (hydraunrealengine-server): highest-value build-delivery add, fills the visibility gap before the library's development state. Blocked on a scoping key — needs the reporter to emit an org/developer field (small CLI change) or a hostname->org map; otherwise it degrades to all-tenants and cannot ship scoped.
  • Add P2 panels: Live/run status (hydrabodystatus + hydrastreamingmonitor, cross-service join + field allow-list, blocked on scoping); Docs & getting started (hydrabooks, read-only, needs content authored first); Venue context (hydravenues public reads + /docs link-out).
  • Record the rejected set (hydrapipeline, hydramirror, hydraperforcewatcher, hydratransfer, hydraunrealengine CLI, hydrarelease) with the one-line reasons above, so the issue captures the negative space.
  • Call out the single architectural decision: the scoping proxy is reusable middleware, and backend additions are the exception, not the rule — only hydraperforce (scoped read endpoint), hydraunrealengine-server (org key on the reporter), and long-term hydraissue (owner_org field) need small upstream work; everything else is pure proxy, public read, or link-out.

5. Phasing

  • MVP = #490's existing Phase-1 read-only lifecycle panel + the XS "Runs at"/"Live now?" extension + the "Report a problem" proxy (independent, no upstream change, highest value-per-effort). This ships Cyborn real visibility and their first self-service action with near-zero blast radius.
  • Follow-on A (P1 reads) — "Build & delivery status" (after the reporter emits an org key) and "Build pipeline / source control" (after the scoped hydraperforce read endpoint). These close the upstream half of the delivery loop.
  • Follow-on B (P2) — Live/run status (the hardest scoping: cross-service join + payload allow-list), "My issues" list, Docs & venue context. These close the downstream half.
  • Lifecycle write actions (promote->staging, rollback, pause/resume) land as #490's Phase 2 exactly as written; prod-promote and retire stay operator-gated throughout. Reserve all write/control surface for the lifecycle anchor — every added panel is read-only.
claude-ops 15 Aug 2026 21:00

Comment posted to #490 (HTTP 201, now 1 comment by creator-portal-assessment). Synthesis below is the exact text posted.


Broaden #490 from "experience management" to a creator dashboard on hydramancer

Synthesis of five tool-landscape investigations (build-delivery, perforce, experience-venue, observability, support-docs). This comment does not change the existing lifecycle plan — it widens the scope around it and records the negative space.

1. Reframe

Cyborn's mental model is a delivery loop: submit to Perforce -> package/upload -> publish to mirror -> registered build -> stage/promote -> live on fluffy -> something breaks -> report it. #490 lights up only the middle. Keep experience lifecycle as the unchanged anchor panel and reframe #490 as a single org-scoped creator dashboard. The marginal cost per added panel is small once the #490 scoping middleware exists.

2. Prioritized panels

# Panel Source Scoping today Priority Effort
1 Experience lifecycle (anchor) hydraexperiencelibrary needs #490 proxy (scope on developer) P0 per #490
1a "Runs at" (Districts) + "Live now?" (experiences/live) hydraexperiencelibrary rides record proxy already loads P0 XS
2 Report a problem hydraissue POST /issues filing needs no upstream change; pure proxy P1 S
3 Build pipeline / source control ("did my submit land?") hydraperforce all-tenants HTML, no per-org JSON read; needs scoped GET /agents/{id}/state + org->agent map; don't iframe /admin P1 S–M
4 Build & delivery status (preflight/package/upload/download) hydraunrealengine-server single token AND no org key (Client=hostname); needs reporter to emit org key first P1 M + CLI change
5 Live / run status (body online, my experience active) hydrabodystatus + hydrastreamingmonitor no org field; cross-join experience->district/venue + field allow-list P2 M–L
6 Docs & getting started hydrabooks public + project-keyed; no proxy P2 S
6a Venue context (address/bandwidth/rider) hydravenues public reads; NOT its ?organization= filter (owner axis) P2 S
7 "My issues" list hydraissue no owner filter; must filter proxy-side; never link public index P2 M

Rejected: hydrapipeline (operator SRE, leaks infra inventory); hydramirror (infra artifact store); hydraperforcewatcher (push-only, surfaces via hydraperforce); hydratransfer (download URL already on job record; admin all-tenants); hydraunrealengine CLI + hydrarelease (run on Cyborn's box — link-out only).

3. Cross-cutting authorization theme

The single-token/all-tenants gap recurs in hydraperforce, hydraunrealengine-server, hydrabodystatus, hydrastreamingmonitor, hydraissue. It is one problem. The #490 org-scoping proxy (fail-closed: iamnim Me() -> membership slugs -> load record -> authorize on stored org field, never the request -> filter lists) is reusable middleware — build once, add panels behind it. Two encoded nuances: the creator axis is developer (Cyborn) not owner (museum); tools with no org field need a cross-service join or a small upstream org-key change before they can scope.

4. Concrete #490 adjustments

Retitle to "Creator dashboard…"; make current lifecycle plan panel #1 (keep its Option-A proxy/scoping/allow-list intact as the shared middleware); add XS Runs-at/Live-now to Phase 1; add P1 panels 2–4 (only hydraperforce + hydraunrealengine-server need small upstream additions, rest pure proxy); add P2 panels 5–7; record the rejected set with reasons; state that backend additions are the exception, not the rule.

5. Phasing

  • MVP = #490 Phase-1 reads + Runs-at/Live-now (XS) + Report-a-problem proxy (independent, no upstream change).
  • Follow-on A (P1 reads): Build & delivery status (after reporter org key) + Build pipeline (after scoped hydraperforce read) — upstream half of the loop.
  • Follow-on B (P2): Live/run status (hardest scoping), My issues, Docs & venue context — downstream half.
  • Lifecycle writes stay #490 Phase 2; prod-promote/retire operator-gated; every added panel is read-only.

Files cited across buckets: hydraexperiencelibrary internal/store/store.go, internal/api/handlers.go/handlers_pipeline.go; hydraperforce internal/server/server.go, pkg/state/state.go; hydraunrealengine-server internal/server/{server.go,store.go} + hydraunrealengine pkg/report/report.go; hydrabodystatus internal/store/store.go, internal/api/*; hydrastreamingmonitor internal/*/handler.go; hydravenues internal/api/server.go, internal/store/venue.go; hydraissue internal/store/issue.go, internal/api/server.go; hydrabooks internal/api/handlers_api.go. Rejected: hydrapipeline, hydramirror, hydraperforcewatcher, hydratransfer, hydrarelease, hydraunrealengine CLI.