> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.cessy.ai/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.cessy.ai/_mcp/server.

# Workspaces And Changesets

> Use Cessy workspaces and changesets to isolate, validate, review, and promote app design changes.

Workspaces and changesets are the safe path for design work. They keep exploratory edits away from published runtime behavior until validation and review are complete.

## Workspace

A workspace is an isolated design area for one app. Use it to add or change commands, events, projections, policies, permissions, adapters, agents, or pages without disturbing the published model.

Keep the workspace focused. A workspace called `invoice-approval-v2` should not also contain unrelated page cleanup or adapter experiments.

Agent artifacts are stored as `model:agent:<stable AgentSpec.id>`. Design MCP read and patch tools also accept `model:agent:<AgentSpec.name>` for existing agents and return the resolved id-backed artifact ref.

Workspace lists return a bounded, newest-updated-first page from the maintained summary cache.
The default page contains at most 25 workspaces and `limit` is capped at 100. Pass
`pagination.nextCursor` back unchanged as `cursor` with the same app and filter values to continue,
or use `workspaceId` for an exact bounded lookup. Set `compact: true` to omit the legacy top-level
structured-content mirror while preserving JSON text and the canonical payload under `data`.
Candidate filtering is bounded, so sparse pages can return fewer than `limit` matches while still
providing `nextCursor`; `totalCount` remains `null` until `totalIsExact` is true. Pagination reports
`truncated: true` if the next raw cursor start would exceed the defensive position-10,000 ceiling.
Exact `workspaceId` lookup remains available for a known older workspace.

Each list summary has a Git-style divergence view. `aheadCount` is the number of workspace-local
artifact changes. The cached list path reports `behindCount` as `null` because it does not load and
diff the full published graph; `isBehind` reports identity-based freshness only when the latest
production identity shares a release ID or has compatible content-hash provenance. A validation
target hash is not compared with a canonical published artifact-graph hash: that case reports
`isBehind: null` (unknown), not behind. List diagnostics include the latest Release/hash provenance
and comparison policy. Use the exact workspace-status read when you need the full
published-artifact behind count. List filters can focus on active, closed, behind, current, or
unknown-freshness workspaces. By default, workspaces whose latest changeset has already been
promoted are hidden from active lists but remain available in closed/history views.

Production stores an active custom Page Source as its slug and exact TSX source,
while the Release snapshot retains the full validated Artifact node, including
resource and SDK dependencies. Creating a Workspace from that Release restores
those dependencies from the snapshot. Exact status checks compare the
materialized Page Source bytes and still require complete Page Resource and
published-model coherence; missing or changed runtime bytes fail closed instead
of producing a guessed base.

Workspace runtime execution can validate a new `wake_agent` policy before promotion: commands,
events, policy triggers, target agent references, rendered wake messages, and Workspace-only
agent wakes all run against the workspace model. When a `wake_agent` policy targets an agent that
only exists in the workspace, `execute_command` with `waitForPolicies: true` provisions a temporary
Workspace-scoped agent container on demand, routes the wake to it, and removes that preview
container after the wake completes. If preview provisioning is unavailable or fails before the wake
can run, the command returns a non-failing `wake_agent.workspace_preview_unavailable` diagnostic
instead of a generic container error.

Workspace runtime execution also uses unpromoted `custom_code` policy handlers. If a command
triggers a Workspace `custom_code` policy, `execute_command` resolves the worker from the
materialized Workspace `policy-code` artifact rather than any promoted Production deployment. This
lets new and edited policy handlers be validated end-to-end before promotion. With
`waitForPolicies: true`, the response includes a `policySettlements` entry for each exact durable
invocation created by the command. `completed` includes handlers that successfully return `null`;
`failed` and `not_started` carry a stable error code plus any available worker, correlation, and
policy-execution identities. A Workspace execution never falls back to promoted policy code.

Create a new custom-code policy in one `upsert_workspace_artifact(kind: "model:policy")` call by
including its inline `code`, package `dependencies`, and any finite `targetCommands`. Cessy validates
the handler and atomically creates the internal metadata/source pair, so callers do not need a
metadata-only write followed by an intermediate-hash `replace_policy_handler` call. Continue to use
`patch_policy_handler` or `replace_policy_handler` for edits to an existing handler.

A new `dispatch_command` policy uses the same Workspace upsert with `commandType` and
`commandMapping`, but no code or handler hash; Cessy stores it as a single `model:policy` artifact.

Literal command `type` values and homogeneous batch `commandType` values are discovered
automatically. If either value is computed dynamically or otherwise unresolved, pass an explicit
finite `targetCommands` list to `replace_policy_handler`, or patch `/targetCommands` together with the
code. Changeset validation blocks promotion when a detected command is missing, a dynamic target
has no explicit finite declaration, or the policy's runtime `allowedCommands` does not enforce the
same set.

For side-effect-only handlers, omit `return` or directly return `null` or `[]`. An indirect
zero-target result such as `return commands` is treated as unresolved and requires an explicit
empty `targetCommands` attestation. An empty attestation is rejected when the handler definitely
returns a dynamic `type` or batch `commandType` field.

`targetCommands` is a finite upper bound, not a requirement that every invocation dispatches.
A conditional handler may declare targets and still return `null` or `[]` while it waits for a
settle condition; that invocation is recorded as a successful no-command result, not an error.

Each awaited `ctx.getProjection(...)` call bypasses response caches and executes a new live read
against the active Production or Workspace runtime source. A bounded polling loop can therefore
observe projection writes that commit while the same handler is waiting. Separate calls, including
calls to different projections, are not one atomic snapshot; require two consecutive stable exact
reads before returning a completion command. Add a delay or backoff between attempts and set a
finite attempt/deadline limit. Projection list reads remain capped at 1,000 documents per call, and
the cache bypass applies only to the internal policy projection callback.

Existing policies with `blockedCommands` but no `allowedCommands` remain valid when their stored
target declaration is complete and every handler target is statically resolvable. Runtime enforces
the stored targets as an upper bound before applying the blocklist. A handler with dynamic targets
must carry an explicitly attested finite target set and cannot rely on the blocklist alone.

For older published handlers that have `targetCommands` but no `allowedCommands`, runtime uses the
stored targets as a finite compatibility allowlist. Commands from a previously undetected dynamic
branch are rejected until you replace the handler and explicitly attest its complete target list.
Older custom-code handlers with neither stored `targetCommands` nor a nonempty `allowedCommands`
allowlist fail closed at dispatch time and must be replaced to establish finite target metadata.

These canonical target-declaration errors do not make the whole Workspace unavailable. Pages,
projection reads, and unrelated command flows can still use the isolated Workspace runtime while
you repair the handlers. Policies with those errors are omitted from the isolated runtime snapshot,
so their worker code does not run and cannot perform external side effects. Their stored Workspace
artifacts remain available for remediation. Valid handlers continue to use the normal Workspace
HTTP policy and command guards.

ChangeSet validation and production activation compare canonical target-declaration and allowlist
findings with the Workspace base. An exact inherited finding is reported as non-blocking debt only
when both the policy metadata and handler artifacts are unchanged. A new or changed finding blocks,
and touching either `model:policy:<name>` or `policy-code:<name>` makes matching inherited debt block
again until the handler is remediated. Invalid policy-code attachments and unrelated configuration
errors remain blocking.

## Changeset

A changeset packages workspace changes for validation, review, and promotion. It should tell reviewers:

* what business capability changed
* which artifacts changed
* which validation errors are fixed or remain
* which runtime contracts will change
* which environment is the promotion target

Omitting `refs` when creating a changeset gives it full-workspace scope. Passing
explicit refs creates an immutable selected scope. Cessy closes that selection
over changed dependencies (or fails when dependency inclusion was not
authorized), applies the captured unit to the pinned base, and validates exactly
the graph that promotion would activate. Unselected workspace edits are neither
deleted nor silently included; they remain available for a later changeset and
can change without making the selected unit stale.

Promotion still verifies the selected refs and dependency closure against the
source workspace. A captured artifact that changed or disappeared yields
`stale_changeset_validation` with a machine-readable `reasonCode`. Full/default
changesets retain their existing behavior: any later workspace-diff change
requires a fresh changeset and validation. Exact Production-base and
environment-pointer leases apply equally to both scopes.

For Custom Pages, validation separates technical integrity from Design System
guidance. Syntax, imports, resource/security boundaries, CSS parse/encoding
failures, and unavailable validation remain blocking. Visual conventions,
hard-coded values, undeclared/local CSS custom properties, and unknown additional
component props are advisory. Missing required props and incompatible value shapes
block only when the page is changed or transitively affected; identical debt on
unchanged, unaffected pages is reported as a warning and does not block the
changeset. A default Page component that requires host-provided props follows the
same affected-page ratchet: changed or affected sources fail because the hosted
runtime mounts `<PageComponent />` without props, while unrelated inherited debt
remains non-blocking. Use `useParams()` or `useDocument()` for hosted context.

Creating a ChangeSet also repairs legacy Workspace Page graphs that reference
published modules, assets, or Design System artifacts without owning them. The
server verifies and pins only the reachable transitive resource closure, proves
the projected ChangeSet is complete, and only then persists the Workspace
repair. The response reports the additions as `materializedDependencyRefs`.
Explicit Workspace deletions and missing non-Page dependencies still fail
closed; no partial repair is stored.

If `validate_changeset` returns `workspace_database_admission_unavailable`, call
`get_changeset` before retrying. A matching Validation Artifact means the
immutable validation committed even if the original response was lost. When no
match exists, follow `retryAfterSeconds` and retry the exact same request.

## AI builder workflow

When using Claude, Codex, or another agent through Design MCP, ask it to:

1. inspect the current app
2. create or select a focused workspace
3. make the smallest coherent set of edits
4. validate before asking to promote
5. summarize model, API, permission, and runtime impact

Agents should not promote broad changes without an explicit builder approval step.

## Promotion discipline

During the bounded v1/vNext migration window, a ChangeSet can be promoted even
when the current Production Release came from a vNext Candidate. Rebase the
Workspace onto that exact Release and create a fresh validation first. The
ChangeSet and Candidate paths share one current Release pointer and publication
fence, so stale work cannot overwrite the winner. Raw hosting writes and
arbitrary historical Legacy rollback are not part of this compatibility path.
There is one bounded cutover recovery: an app administrator can use **Restore
previous Release** when the current first vNext Candidate Release points to an
exact retained Legacy base. Cessy reactivates that immediate base snapshot
without creating another Candidate Release. The raw Legacy rollback tool stays
blocked, and no other historical Legacy Release can be selected.

Promotion captures the current pointer and fence before it claims Production,
then proves the complete current target once under that claim. If an exact
storage read is temporarily unavailable before activation, the tool returns
`target_observation_unavailable` with `retryable: true` and releases its claim.
Retry the same immutable ChangeSet and Validation Artifact with the same
expected Production Release. Do not recreate the Workspace or repair Production
data. The response includes exact `inspect_design_target_diagnostic` arguments
and a matching `ces://` diagnostic Resource. Inspection is read-only and reports
whether the condition is still active, has been superseded by a newer bounded
diagnostic, or has resolved. The same inspection contract applies when a
temporary read blocks Workspace creation or rebase. Proven missing or mismatched Release lineage still fails closed with
`target_not_coherent` and is not made retryable by this contract.

Long Legacy release histories do not by themselves make Production incoherent.
Cessy limits historical Page ownership discovery to 256 Releases, including the
current Release. If no Candidate ownership boundary is proven within that
budget, it compares the complete current bound snapshot with live Page
materializations. Missing or changed owned Page bytes still block rebase and
promotion; observed lineage corruption remains an error.

If a request disappears after the fence reaches `activating`, call
`get_workspace_status`. While the deadline is live, wait. When recovery reports
`resume_reconciliation`, retry `promote_changeset` with the same ChangeSet,
Validation Artifact, and expected Production Release. Cessy verifies the
content-addressed Release identity in a bounded interval immediately before the
durable fence claim, deterministically rebuilds and verifies the prepared
Snapshot, and resumes that same operation. An already committed Domain event is reconciled
through the bounded Page-deployment path; otherwise deterministic preparation is
continued without duplicating Page-source state. For that exact activating
operation, an owned live Page Source or Resource may be at the bound base or at
the reconstructed candidate bytes while the Domain is still at the base. Once
the Domain has moved, every owned Page value must already match the candidate.
Any other live value still fails closed with `target_not_coherent`. A different
ChangeSet cannot take over the activation, and an unverified activating fence
cannot be force cleared.

If required Page deployment reaches a temporary database-admission deadline,
`promote_changeset` returns `workspace_database_admission_unavailable` with the
`required_page_deployment` phase, `retryAfterSeconds`, an exact retry payload,
and a recovery lookup. Use `get_changeset` to obtain the Workspace ID, inspect
`get_workspace_status`, then retry the unchanged promotion after the indicated
delay. Do not call `target_claim_recover` for an `unknown` post-activation
claim—the exact promotion retry performs bounded, idempotent Page reconciliation.

Before promotion, verify:

* validation passes
* generated OpenAPI changes are expected
* permission changes are intentional
* projection rebuild needs are understood
* custom pages and agents affected by the change are named

After promotion, run runtime smoke tests in the target environment.