Topolo Docs Topolo Platform
Open workspace

Publish a saved entry-experience revision

Publish an exact saved revision and reconcile retained catalog receipts before an explicit same-revision resume.

Publish a saved entry-experience revision

Publish an exact saved revision and reconcile retained catalog receipts before an explicit same-revision resume.

Action and scope

app_topolo_developers.apps.experience.publish — POST /api/developer-console/apps/{appId}/experience/publish. Required permission: app_topolo_developers.apps:write. Authorization mode: static; no platform principal or allowed-service override is declared.

Before using this action

Use the original authenticated organization, accessible Developers workspace and current action permission. Discover the canonical owning app, bind the workspace separately and compare the returned appId. An app identifier, static permission or canWrite projection alone is not an authorization grant.

Shared workspace context requires the app/workspace match and current app access. Reads permit a suspended profile but not revoked access; writes require app.canEdit and a writable profile. These routes do not require completed onboarding. Shared context can initialize a local workspace mirror and developer profile before a later refusal; it does not mint a canonical Auth workspace.

Use the current declared schema and actual actor policy before any authorized call. All examples use fictional identifiers, synthetic state and UNRUN outcomes; they are not receipts, evidence of deployment, a current private app read or a running login/landing UI check.

Requires app_topolo_developers.apps:write, current app.canEdit and a writable profile. Read apps.experience.get, review the exact saved draft and obtain authority for publication effects. expectedRevision is a positive safe integer; resume is optional boolean. A currently visible canWrite flag does not authorize publication.

The declared execution mode is automatic for inventory and confirmation-required for import/publish. Confirmation and current authorization must be evaluated for the selected action; these offline examples do not approve a mutation.

Inputs

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "appId": {
      "type": "string",
      "minLength": 1
    },
    "expectedRevision": {
      "type": "integer",
      "exclusiveMinimum": 0,
      "maximum": 9007199254740991
    },
    "resume": {
      "type": "boolean"
    }
  },
  "required": [
    "appId",
    "expectedRevision"
  ],
  "additionalProperties": false
}

Behavior and response

POST /api/developer-console/apps/{appId}/experience/publish requires expectedRevision exactly equal to saved state.revision and a saved draft. It does not increment the revision or accept a replacement draft. Already-recorded published state at the same revision returns HTTP200 immediately. Other attempts must pass catalog eligibility; exact eligibility rules remain delegated to the existing registry policy.

For an unresolved publishing/failed attempt, resume:true is required. The retained revision must match. If the retained draftDigest is truthy, it must equal the computed current draft digest; a missing or empty retained digest alone does not fail this guard. A found durable receipt must independently match the actual revision and digest. Before broker replay, the route reads the durable receipt for appId:entryExperience:attemptId. A matching receipt reconciles published control state by compare-and-swap without republishing. A mismatched receipt or unreadable outcome holds the attempt. If no receipt exists but a publishing attempt still has an executionId, the route refuses to take over that execution.

A new or resumable attempt preserves/creates attemptId, creates executionId, retains the exact draftDigest and publication timestamp, marks publishing and checkpoints service outcomes with full-state compare-and-swap. It records catalog outcome as unknown before publication. Previous successful outcomes are retained; a later non-success for that service is kept as serviceLatest instead of erasing the known success.

The entryExperience branch skips the commercial pricing broker. The catalog publisher can still activate declared permissions in Auth and synchronize eligible first-party Auth/launcher registration before catalog visibility. Registration includes current launcher/catalog/commerce metadata; this action is not a guarantee of zero Auth writes. Those delegated service implementations, delivery outcomes and eligibility races were not executed or fully audited here.

Eligibility preflight and the publisher’s later app reload are separate reads. If eligibility changes between them, the publisher’s ineligible branch can synchronize an organization-internal first-party Auth projection or request first-party retirement for other exclusion reasons, then delete catalog projections and purge the application partition. A false return is subsequently reported as a failed publication. The previous control-state published snapshot is therefore not a guarantee that the previous public catalog remains available after every failure; preserve the actual cross-service outcomes and source owner for recovery.

The reviewed store protects monotonic authoring revisions, prevents changing an immutable facet at the same revision, and preserves existing catalog commerce when committing an entryExperience operation. It prepares an invisible partition candidate, checks its read-back, then conditionally commits the directory, registry, overlay and generation with one exact durable receipt. Receipt fields are status,appId,operationId,generation,bindingDigest,manifestDigest,publishedAt and the authoring operation {kind,revision,attemptId,draftDigest}. A prior operation cannot change its source binding.

After a matching catalog_committed receipt, the route records catalog:{success:true,receipt}, a published {revision,draft,publishedAt} snapshot and publication.status published. HTTP200 uses the Developers success/message/data wrapper. Notifications and durable failure signals are additional publisher effects; a notification failure can follow a catalog commit.

An HTTP503 can contain success:false, an error string and data.state with a published snapshot when receipt read-back proves the catalog committed. If commit is unproved, the publication can be failed with retained unknown outcomes and the previous published snapshot. Failure to finalize control state leaves its durable persistence unknown. A 409 caused by newer ownership can also follow earlier effects. None of these statuses proves rollback, no effect or permission to repeat the whole attempt.

Errors and recovery

HTTP409 — Preserve current revision, owner, executionId, attemptId and digest. Read state/outcomes before any explicit same-revision resume. Missing draft, ineligible app, changed draft/receipt or newer control ownership each requires its own remedy; do not reset or create a new attempt to bypass a hold.

HTTP503 — Read actual retained outcomes and exact receipt through supported owner/read-back paths. Published status plus control.reconciliation_required is not a durable control write. CatalogReadback unknown is not absence of a commit; retain successful service effects.

HTTP401 — Restore original authorized identity; do not replay effects blindly.

HTTP403 — Respect actual current workspace/app authority and writable profile.

Error code labels in the typed layer are explanatory classifications unless explicitly present in the actual response. Shared authentication, permission, validation and rate-limit refusals may occur before these branches. Ordinary console errors use an error object; inventory forwards raw Auth errors; publication partial-failure responses can use an error string with data.state.

Verification

Retain the original organization/workspace, actual canonical appId, action inputs, status/error and request identifier. Never substitute a fixture identifier, revision, attempt or digest for actual read-back.

The captured output schema is structural and open. The detailed source shapes below explain reviewed branches without replacing the published schema or guaranteeing that every response has every illustrated field.

Compare exact state.revision/draft, publication revision/attempt/digest/status/outcomes and published snapshot. A receipt must identify this app, entryExperience attempt and draft; retain its generation, manifest/binding digests and publishedAt. Never fabricate a receipt from displayed fields.

Read-back may project a receipt-reconciled view with control.reconciliation_required without persisting control state. A successful catalog receipt proves the recorded catalog boundary only, not UI rollout, first-party registration adoption, notification delivery or a successful signup/login journey.

Recovery

After partial/unknown effects or lost response, preserve the original attempt and complete outcomes. Read back first; use resume:true only with the actual same saved revision and explicit authority. Matching durable receipts are reconciled before replay; active execution ownership, receipt mismatch or unreadability must remain held. There is no automatic rollback of a committed catalog or Auth effects. Restoring previous content requires a separate authorized saved revision/publication, not rewriting an immutable receipt.

Synthetic examples — UNRUN

Reconcile an already committed catalog receipt

Complete source-derived synthetic UNRUN HTTP200. Conditional fixture: current state has this exact draft/revision and failed attempt with only catalog:{status:"unknown",operationId}; eligibility passes and receipt lookup returns the illustrated matching receipt. The control compare-and-swap succeeds. All UUIDs, timestamp, generation and opaque b/c digest values are fictional fixture data; only draftDigest is calculated from the synthetic draft. This is reconciliation of a stipulated prior commit, not a freshly executed publication.

Input:

{
  "appId": "app_docs_fixture",
  "expectedRevision": 1,
  "resume": true
}

Output:

{
  "success": true,
  "message": "OK",
  "data": {
    "appId": "app_docs_fixture",
    "kind": "entryExperience",
    "state": {
      "revision": 1,
      "draft": {
        "login": {},
        "landing": {}
      },
      "published": {
        "revision": 1,
        "draft": {
          "login": {},
          "landing": {}
        },
        "publishedAt": "2026-10-23T00:00:00.000Z"
      },
      "publication": {
        "revision": 1,
        "attemptId": "00000000-0000-4000-8000-000000000001",
        "executionId": "00000000-0000-4000-8000-000000000002",
        "draftDigest": "a299c3e1d354090392b683091f18591783e4aaf94e45c871e24d6cfadd6264e5",
        "publishedAt": "2026-10-23T00:00:00.000Z",
        "status": "published",
        "outcomes": {
          "catalog": {
            "success": true,
            "receipt": {
              "status": "catalog_committed",
              "appId": "app_docs_fixture",
              "operationId": "app_docs_fixture:entryExperience:00000000-0000-4000-8000-000000000001",
              "generation": 1,
              "bindingDigest": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
              "manifestDigest": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
              "publishedAt": "2026-10-23T00:00:00.000Z",
              "operation": {
                "kind": "entryExperience",
                "revision": 1,
                "attemptId": "00000000-0000-4000-8000-000000000001",
                "draftDigest": "a299c3e1d354090392b683091f18591783e4aaf94e45c871e24d6cfadd6264e5"
              }
            }
          }
        }
      }
    }
  }
}

No catalog broker replay occurs in this branch. Retain the original attempt and receipt; the synthetic values must never be used as real evidence.

Explicit resume required

Complete source-derived synthetic UNRUN HTTP409. A saved draft exists at revision1 and publication is failed or publishing.

Input:

{
  "appId": "app_docs_fixture",
  "expectedRevision": 1
}

Output:

{
  "success": false,
  "error": {
    "message": "Read publication outcomes and explicitly resume this revision"
  }
}

Read full outcomes and actual authority first; do not automatically add resume:true.

A live execution retains ownership

Complete source-derived synthetic UNRUN HTTP409. Saved revision/digest and eligibility agree, retained status is publishing with executionId, and exact receipt lookup returns null.

Input:

{
  "appId": "app_docs_fixture",
  "expectedRevision": 1,
  "resume": true
}

Output:

{
  "success": false,
  "error": {
    "message": "This publication still has an execution owner; inspect its outcomes before resuming"
  }
}

Do not steal ownership, clear executionId or treat absent receipt as proof of no Auth effects.

Source provenance and limits

Reviewed 2026-10-23 against immutable Developers source 7b89bd3b39092cdc29f87540847bf99a243f0a40 and retained production declarations. The inventory receiver is independently pinned to Auth e2ea647f225b1842f629aa74f732fbfb6601b3e3. Declaration/source association is not evidence that either revision is serving or that the cross-service pair is deployed together.

The documented application action and examples were not executed for this guide. No private app, Auth inventory, provider, signup/login or runtime UI was accessed. Source imports, tests and builds were not run. Shared prerequisites reuse the accepted source qualification for experience.get/update; transitive service implementations beyond the listed boundaries were not audited.

Automatic Knowledge invocation during relevant builds has not been verified; its reliability remains UNKNOWN. Manual documentation review does not establish automatic build integration.

Change log

  • 2026-10-23: Added and published this guide with source-qualified behavior and synthetic, unrun examples. This is a documentation change; no application behavior change is asserted.