V1 Web PWA · Journey

Journey: Tenant bulk operations and exports

A source-reconciled journey record for V1 Web PWA — shipped behavior, state boundaries, failure modes, and the automation evidence available today.

automated · 2 specs verdict: partial· 2026-06-25
9sections6 minread1table

On this page
Journey at a glance
ActorsPlatform operatorTenant operations specialistCompliance reviewerWorkspace tenant admin
Automation2 E2E spec(s) · verdict: partial· 2026-06-25

Journey flow#

Generated from the authored steps below — click a node to jump to that section.

flowchart TD s1["1. Draft an operator-owned bulk operation"] s2["2. Validate, dry-run, and stage"] s3["3. Commit through the exact executor matrix"] s4["4. Verify mutation read-backs"] s5["5. Create an integrity-manifest export"] s6["6. Verify and decide release"] s7["7. Inspect durability and audit evidence"] s1 --> s2 --> s3 --> s4 --> s5 --> s6 --> s7 click s1 href "#1-draft-an-operator-owned-bulk-operation" click s2 href "#2-validate-dry-run-and-stage" click s3 href "#3-commit-through-the-exact-executor-matrix" click s4 href "#4-verify-mutation-read-backs" click s5 href "#5-create-an-integrity-manifest-export" click s6 href "#6-verify-and-decide-release" click s7 href "#7-inspect-durability-and-audit-evidence" s7 --> v(["verdict: partial"]) click v href "../results/tenant-bulk-operations-export.html" style v stroke:#d6a531,color:#d6a531

The platform-admin APIs ship two durable, operator-owned workflows. Bulk operations validate, dry-run, stage, and commit four mutation kinds through real executors; unsupported kinds fail loudly. Bulk exports enforce the resource/format/residency matrix, create SHA-256 manifests, require a release decision, and gate raw-file reads. /operator/tenant exposes the export workflow only; there is no browser UI for drafting or committing bulk operations.

Personas#

  • Platform operator — holds admin:* or admin:studio and owns operation and export records under their authenticated user id.
  • Tenant operations specialist — prepares user, entitlement, roster, or persona rows for a target tenant.
  • Compliance reviewer — verifies an export manifest and approves or rejects release.
  • Workspace tenant admin — intentionally receives 403; the routes are not available to admin:workspace:tenant-console.

Pre-conditions#

  • The BFF is running with an admin bearer. Both route families also expose legacy /admin/* aliases, while the UI uses /v1/admin/*.
  • Admin snapshot persistence is configured if operations/archives must survive restart. Without the admin database, their singleton stores are process-local.
  • Commit targets are available: customer auth/profile, tenant seat allocation, OneRoster, and active persona stores.
  • Export records include their own resourceKind; residency validation also reads each record's residency field.

Steps#

1. Draft an operator-owned bulk operation#

POST /v1/admin/bulk-operations accepts kind, title, rationale, target tenant, and rows. The response starts at drafted; another operator's list remains empty. Validation derives required-field and type issues, while dry-run classifies each row as create, update, or skip and records a field-level plan.

No CSV/file upload parser feeds this request. Operators or automation must construct the typed JSON rows directly.

2. Validate, dry-run, and stage#

Stable transitions are drafted → validated → dry_run_complete → staged. Validation errors remain inspectable at validated and block the dry-run transition. Invalid or repeated transitions return 409. Creation and every successful transition append admin.bulk_operation.* audit events.

3. Commit through the exact executor matrix#

Commit requires rationale. Non-skip rows run sequentially through these real effects:

Kind Commit effect
users Provision customer account, then allocate a target-tenant seat
entitlements Update an existing account plan to free/pro/premium
rosters Apply one durable OneRoster commit per row
persona_assignments Set the subject's active persona and surface
content, taxonomy Fail with “no commit executor … nothing was changed”

A row failure stops subsequent rows and marks the operation failed, but earlier successful rows remain applied and are retained in commitOutcomes. This is an honest partial-commit ledger, not a transaction or automatic rollback.

4. Verify mutation read-backs#

The real-BFF journey reads the created user and plan through /v1/profile, the roster through /v1/admin/lms/oneroster, and the persona selection through /v1/personas/active. It also reads both universal admin search and structured audit-log events. Unsupported content commit stays failed with null committedAt and an explicit reason.

5. Create an integrity-manifest export#

POST /v1/admin/bulk-exports validates the resource/format matrix, record-kind equality, and per-record residency against the target region. A successful archive contains version oshun-bulk-export-v1, file summaries and content, file SHA-256 values, a manifest SHA-256, a pending release, and a 30-day retention timestamp. List/create projections omit raw file content; detail read includes it for operator inspection.

The browser panel is intentionally narrower than the API: it always creates a two-record content/json/us archive from two typed titles. It does not expose other resource kinds, formats, regions, or arbitrary records.

6. Verify and decide release#

POST /:opId/verify recomputes the file/manifest integrity verdict. The API accepts approved or rejected release decisions with rationale. The browser renders only Approve. The archive owner is also recorded as the approver; there is no distinct reviewer identity or four-eyes gate.

Before approval, the file endpoint returns 409 bulk_export_release_not_approved. After approval it returns the raw content with file/manifest hash, residency, retention, and content-disposition headers. The browser panel calls getText and renders the content inside the page despite the button label “Download released file”; it does not create a browser download artifact.

7. Inspect durability and audit evidence#

When configured, operation states and full export archives—including file content—write through to durable admin snapshots. Structured audit events cover operation lifecycle/failure and export execute/release/download. Retention is metadata only: the store does not purge expired archives or block a read after retentionExpiresAtUnixSeconds.

Post-conditions#

  • Supported mutation rows have observable downstream effects and per-row outcomes; unsupported kinds cannot masquerade as committed.
  • Operation and export lists remain isolated by operator user id.
  • An approved export has verifiable file/manifest hashes and release/audit metadata.
  • The UI can create, inspect, verify, approve, and display one fixed-shaped content export.
  • Bulk mutation UI, upload parsing, atomic rollback, reviewer separation, retention enforcement, and a real browser file download are absent.

Failure modes#

  • Workspace scope — tenant-console workspace admins receive 403 even for their own tenant.
  • Validation block — malformed user rows reach validated with issues but cannot enter dry-run complete.
  • Partial commit — earlier executor effects survive when a later row fails; retry/compensation must inspect commitOutcomes.
  • Unsupported kind — content/taxonomy operations end failed with no false commit timestamp.
  • Duplicate transition/op id — invalid state changes and duplicate export ids are rejected per operator.
  • Residency/kind mismatch — schema-valid export requests fail 422 before an archive is created.
  • Same-actor approval — the archive owner can approve their own release; compliance separation is descriptive, not enforced.
  • Retention metadata only — an expired timestamp does not delete or deny an archive/file.
  • UI format narrowing — operators cannot use the page to export rights, users, OneRoster, CSV, or a non-US region.
  • Misnamed download — the web control displays response text in the panel rather than handing a saved file to the browser.

E2E coverage#

  • apps/oshun/web/e2e/tenant-bulk-operations-export-bff.spec.ts covers auth/isolation, users/entitlements/rosters/persona executors, validation, unsupported failure, downstream read-backs, export matrix, integrity, release gate, response headers, and audit/search evidence over the real BFF.
  • apps/oshun/web/e2e/tenant-bulk-operations-export-ui.spec.ts drives the fixed content export panel through create, inspect, verify, approve, get-text “download,” and audit read-back.
  • Coverage depth: deep for the implemented APIs and export UI, partial for the named operator workflow. Mutation draft/diff/commit has no browser path; upload parsing, compensation, dual control, expiry enforcement, and varied export UI are unimplemented.

Per-view files touched#

Cross-references#

Open questions#

  • Which operator UI owns JSON/CSV upload, row diff review, commit rationale, partial-outcome inspection, and compensation?
  • Should row effects be transactional per operation, or what resumable idempotency/rollback contract makes partial commits safe?
  • How will two-person release approval ensure the requester cannot approve their own export?
  • What job enforces retention expiry and records purge evidence?
  • Should the browser create a real downloaded file and verify response hashes client-side instead of only rendering response text?