Runtime scaling and environment relocation
Documentation version: 858adedd187db2e94614b1dfd389e4bf98e6e202:ac5d527caaa1f97eee9d71262e7c843ae7b24000e01ba5b44931108b2b489c0d
Status: accepted controlled Shared profile on 29 October 2026, including final tests and cleanup. Implementation and validation evidence are included in this repository checkpoint. This is the version 1 management API, not a claim of regional high availability.
One owner per responsibility
The existing Go coordinator, PostgreSQL metadata/outbox and pg-boss delivery own environment placement operations. Kubernetes schedules pods inside a cell. Knative Serving owns replica counts, readiness, request activation and draining. The release controller supplies immutable templates and never writes a competing replica count. The routing owner binds exact environment names to the committed cell and epoch. There is no deployment implementation per Git provider or app.
onDemand allows zero application replicas. alwaysOn retains a positive native
minimum. Sleeping does not stop the database, identity authority, metadata queue
or independent work. Future customer jobs and schedules must join this same
ownership/inventory protocol when step 6 implements them. They are not currently
offered by this profile.
Replica scaling uses already registered capacity. It does not rent servers, increase provider quotas or spill into another location. Server expansion is a separate authorized installation action. Insufficient local capacity returns an explicit bounded error, rather than placeholder successful content.
Scoped API and SDK
Use the server-only @astrogate/sdk/management entry point with a registered,
scoped management credential. Application-user database sessions cannot call it.
The wire types are generated from api/openapi.json.
import { createManagement } from '@astrogate/sdk/management';
const management = createManagement({
endpoint: installation.managementOrigin,
tenant: authorizedTenant,
token: scopedManagementToken,
deadlineMs: installation.observationDeadlineMs,
maxAttempts: installation.requestAttempts,
});
const locations = await management.environments.listLocations(project, environment);
const current = await management.environments.getPlacement(project, environment);
const deployment = await management.environments.getDeploymentState(project, environment);
const accepted = await management.environments.changePlacement(project, environment, {
expectedRevision: deployment.revision,
policyRevision: operatorRegisteredPolicyRevision,
desired: {
capacityProfile: operatorRegisteredProfileId,
location: selectedLocationId,
standbyLocation: selectedStandbyLocationId,
runtimeMode: 'onDemand',
},
}, { idempotencyKey: persistedLogicalChangeKey });
const operation = await management.operations.get(accepted.operationId);The installation supplies the exact origin/trust and observation policy; source
configuration does not grant a profile, location or higher budget. An empty
location requests automatic selection among the registered policy's eligible
cells. An empty standbyLocation requests no recovery copy. Automatic selection
is stable for an existing eligible assignment. A location list is informational:
the operator's policy revision is independently checked on mutation.
Persist the logical key and submitted body across an acknowledgement loss. Reuse
them unchanged. Poll the operation within the caller's observation deadline;
aborting that wait does not cancel server work. A new desired change needs a new
key and fresh environment/policy revisions. A succeeded operation is followed by
getPlacement to observe the effective result.
desired is intent. effectiveCell, effectiveLocation, effectiveProfile and
epoch describe the committed owner. phase and reason expose incomplete
work. reserved includes primary, standby and required overlap reservations.
protection: snapshot-ready describes the verified point-in-time recovery copy
identified by recoveryPoint.capturedAt; it is not continuous replication.
Important failures include stale revisions, a conflicting idempotency key,
capacity_unavailable, unsafe_capacity_shrink, missing native dependencies and
unprovable fencing. Inspect operation identity and the limiting installation
before retrying. Never turn a failed admission into automatic paid provisioning.
Capacity and storage meaning
An operator registers immutable profile IDs, allowed cells, measured allocatable capacity and explicit cost policy. Admission reserves all selected cells in one database transaction, including rollout overlap and queue-proxy overhead. A larger Shared profile remains Shared. Downgrade checks require fresh actual usage and reject smaller-than-required storage or memory. Missing/stale usage does not authorize a shrink.
The rehearsal's corrected storage profiles use the existing native 6 GiB artifact quota plus the scoped 16 MiB database allowance as their storage maximum. The 1 GiB placement reservation is a guaranteed admission floor, not permission to consume every environment's maximum simultaneously. Native byte quotas and the artifact store's physical 2 GiB reserve independently refuse publication when storage is exhausted. These are measured rehearsal settings, not subscription limits. Capacity admission and native exhaustion must both remain visible. Workspace and build volumes use the separate installation-owned 8 GiB ext4 allocation in this rehearsal, outside the served-application artifact/database maximum. A capacity-profile change does not resize those filesystems. Native physical admission still includes their reserved bytes; completed test workspace source needs explicit retirement, unlike a disposable build cache. Running workspace usage is not reported as zero to authorize a downgrade.
On the two final 8 GiB workers, Kubernetes advertises the reviewed 2,800 millicpu
and approximately 5 GiB application pool. Docker builds/workspaces use a separate
native systemd slice capped at one CPU and 1.5 GiB; host processes retain their
reserve. Docker's systemd driver must inherit that slice through the daemon's
cgroup-parent. Buildx's similarly named cgroupfs-only option is insufficient.
The agent validates the actual process cgroup before executing untrusted input.
The concurrent native measurement
records the observed partition and application traffic, rather than assuming
requested container settings took effect.
Only the tested Shared profiles are offered by this slice. Dedicated/private resize and complete isolated variants remain explicit step 7 acceptance gates. The data model's ability to represent an isolation name does not offer it.
Relocation and return
The placement owner inventories the current application, retained artifacts, database schema/rules/data, scoped credential references, subject mappings and mutable workspace before effects. It drains accepted application traffic, fences source execution and native writes, transfers and verifies the chain, commits one effective owner and only then changes routing. Retries resume native receipts under the current lease and epoch. A stale worker cannot commit, reactivate source writes or retire a newer generation.
Failback is another migration of current data, including writes made at the target. It is not restoring an old source pointer. Retained code artifacts do not rewind database state. Browser bundles containing old environment bindings must reload/reconnect after a database move; cross-location movement is a planned interruption profile. Earlier accepted requests drain before session revocation.
A stopped workspace retains its edited source and stays stopped. Snapshot transfer excludes dependency/compiler caches and generated runtime helpers. The target rebuilds the immutable workspace image and rehydrates dependencies without replacing transferred edits. Running workspaces require stop/restart and client reconnection, not transparent live process migration. See the ledger for the exact running/stopped test coverage.
Recovery packages include native logical database archives, source snapshots, immutable artifacts and the manifest needed to interpret them. Verification reads back complete bytes and hashes before retiring an earlier valid copy. Partial replacements preserve that earlier copy. Ordinary logs can expire without making the retained package unusable. Build/execution file transfer is scoped and deduplicated; this slice does not yet claim capture of every native Kubernetes pod stdout stream.
For a standby-only change within the current cell, prepare and read back the copy before draining the primary route. The native database exporter reads schema, rules and data in one read-only repeatable-read transaction. This online point-in-time snapshot does not fence application writes. A full/unavailable backup destination leaves the primary serving and its previous recovery point intact; the same waiting operation can resume after capacity returns. It is not a continuously refreshed copy. Actual location changes still require source write fencing. Mutable workspace snapshots require stopped source, so stop a running workspace before requesting a same-cell standby change; admission rejects that unsupported combination before creating an operation or reservation.
Availability limits
The rehearsal measured 100 cold probe starts at p95 3,983 ms and p99 4,385 ms. The initial 3-second p95 target was missed. Framework/database-connected starts have separate, slower measurements. A bounded 503 during overload or planned data migration is part of this profile, not zero-downtime acceptance.
Distributed application replicas do not remove the single ingress dependency. The tested database groups are separate, but shared metadata, identity and secret authority still depend on the trusted control chain. An independently restored standby archive proves recovery bytes, not autonomous regional takeover. Automatic whole-region failover, distributed control services and every supported Private variant remain step 7 gates. See operations before resuming a partially completed move.