CLI shutdown
down stops a stack or saved owned services. clean removes selected retained state.
reset previews and applies guarded cleanup of one stopped local store or space identity.
down
Every down runs to its end when the reader of its output goes away, as | head does. It
keeps stopping, writes nothing more, and exits with the stop's own status.
down <service-name> routes one saved owned definition through the
same selection, confirmation, reservation, native manager-impact and stop controller as
service stop. Use --instance <full-id> when selection needs a full instance pin. --yes,
--dry-run, --space, --json and --plain keep that controller's meanings; structured receipts
name down while retaining the service-stop result and actual identity. Unknown definitions do not
fall back to legacy component pidfiles. A controller stack names its members stack-broker,
stack-auth, stack-delivery and stack-manager. A preset name reaches the member built on that
preset, as in service, so cotal down manager stops stack-manager and
cotal down delivery stops stack-delivery through the service stop, and the receipt's target
names the member and the name you gave as requested. Several names in one down resolve each the
same way, so cotal down manager delivery stops stack-manager and stack-delivery, and each
member of the target carries its own requested. A name that is no preset, such as nats,
refuses TARGET_NOT_FOUND there and names the stack- member to use, and a name with no member
names cotal service, which lists the saved definitions. Among several names such a name refuses
the whole set with the same next step, so no service stops. A set never stops the broker, so
nats, broker and stack-broker each refuse the whole set TARGET_NOT_FOUND before any member
resolves, with the same message for each name. The next step is a bare cotal down, which stops
the broker with the whole stack. A saved service of your own named nats or broker is that
service. A set that names another saved service running a broker refuses UNSUPPORTED and names
that service's own stop. A stop of stack-broker refuses
LIFECYCLE_BLOCKED while a legacy daemon record under the root may front a process, or another
service instance of the space is live or unresolved. For those instances it names bare cotal down,
or cotal service stop <name> --instance <id> --space <space> --yes for each one. When one of them
is not a member of the stack, such as one added with cotal service add, bare cotal down refuses
too, so the refusal names that service's stop first, then bare cotal down, or the stops of the
stack's own members and the broker stop again. Its dry run warns with that refusal. Completion offers saved definitions without imports or a network request. No hidden connector repair or shared catalog preparation precedes this route.
Multiple names resolve as one selected-space set. All owned targets and process roles are checked
before effects. After confirmation, the controller reserves every definition in stable order and
rechecks all records and native impact before the first stop. Stops follow the displayed input
order, with each native guard rechecking at effect. A later failure stops the sequence; one receipt
retains completed, failed/unknown and unattempted members. Reservation/preflight refusals also
retain the full selected set. Release is attempted for every acquired definition hold; a reported
release error makes an otherwise known outcome partial without erasing known stop results. An
already unknown stop stays unknown. Lock cleanup is not independently proved. This is not atomic and no rollback or cleanup is claimed. Dry-run reads metadata without reservations, provider imports or native impact
queries. Duplicate names and --instance with multiple names are usage errors. Named --with-agents refuses and points to
cotal service stop <manager-name> --instance <full-id> --with-agents; it is never dropped.
Stack preview
Bare down --dry-run reads local process records for one registered space at the current
project root. A root with no .cotal/ directory, or with no stack registered at it, refuses
TARGET_NOT_FOUND, exit 2, because there is no stack there to preview. Its next step is the folder
where cotal up started a stack, or, when this machine has no stack started or registered at all,
cotal up in a project, as cotal status says. A bare down in such a folder refuses the same
way. A root whose stack is
already stopped shows the no-op described below instead. In the machine home, a stack an earlier
release started there is read the same way, and the Next names cotal down --preserve-state, its
stop, or says that none of it is running. Add --space <name> to
confirm that exact space belongs to this root;
--json and --plain produce a partial, finite inspection receipt. It lists pidfile
liveness, readable additive deployment IDs, retained additive attempts and
unreadable paths under this root, and saved service definitions, instance IDs, recorded states and
hosts, supervisor receipts, and retained install, uninstall and unit-action
attempts in the selected space's workstation store. Saved records are not attributed to this project root, and their recorded
states do not prove live process or supervisor status. A submitted native attempt may have
changed state; the preview neither reconciles nor retries it. For each saved service instance it
also shows whether it blocks bare down: one that still runs or cannot be verified blocks it, with
the reason, and one already stopped is skipped. When one blocks, the preview warns with the refusal
the real stop would give. That check observes processes on this host and changes nothing. Other local registrations with
the exact recorded broker address appear as possible sharing. No match does not exclude
address aliases, remote tenants or live attachments. The preview does not establish process
ownership, the complete service set, exclusive broker use or agent impact.
--with-agents only marks the requested policy. It does not reserve or signal
a process, and the receipt is not an authorized stop plan. A fresh team show
summary points to this read-only command from the recorded root.
On a root with a controller journal, bare down reads only its recorded full instance IDs. On an
interactive terminal it shows the member set and the live manager's native agent impact and asks to
confirm, then stops manager, delivery and broker in that order. --yes acknowledges that scope in
scripts and prints no plan. The receipt names the mesh once, lists each member with its result in
stop order, and names cotal up as the next step. --with-agents
requests the manager's native retirement policy. Another live service instance in the space
refuses LIFECYCLE_BLOCKED before the first stop; the Next names the
cotal svc stop <name> --instance <id> --space <space> --yes command for each one. So does a
process record under the root that may front a process, such as a daemon an earlier release
started; the message and the Next name each record's path and pid. A record whose process is gone,
or whose pid now runs another start than its identity pin records because the pid was reused, fronts
nothing. It refuses neither bare down nor a stop of stack-broker, down --dry-run names it, and
a complete bare down removes it with its pin and names it in a STALE_PROCESS_RECORD warning. The
process that holds a reused pid is never signalled. The broker scope
is checked again after its dependants stop. A failure stops the sequence and preserves completed and
unattempted members. A member stopped by signal that has not exited 15 seconds after SIGTERM gets
SIGKILL. Its row then reads stopped with SIGKILL, and a STOP_ESCALATED warning names its pid.
Only a complete confirmed stop removes the delivery credential and local registration. After the
delivery daemon stops, down reads its lease. When the daemon was already gone, or the lease still
names the daemon that credential gave, as after a kill or a release a full store refused, the
credential stays, and the next up uses it to recognise the row and wait it out before it mints a
new one. When the lease cannot be read, the credential stays too, and a DELIVERY_LEASE_UNREAD
warning says so. The JSON cleanup field then reads registration-removed-delivery-credential-kept. Saved
definitions and the journal remain for the next up, and the space stays prepared at its root. A
selection that names it stays, so the next command run without --space refuses with
TARGET_UNAVAILABLE, exit 2, and names the root to start it in, rather than reach another space.
No descendant, lease or authority cleanup is
implied. A dry run reads records and, with a live manager, how much the space's store holds. It takes
no lock, does not establish native impact and exits 0;
its JSON marks completeness as partial. It lists members in stop order, says when the stop would
first settle an earlier operation, and names a scope check the real stop would refuse as a warning.
A member whose recorded process is already gone is recorded as stopped without loading its adapter
or credential, so a second down --yes stops nothing and is a no-op. Before it stops a live
manager, down reads the workflow runs it hosts and records the unfinished ones, with the steps
they wait on, in the stack journal, where reset store lists them. A read that fails is recorded
with its reason and does not stop the down; the JSON receipt carries the record as
operation.runs. When the manager is
live but the broker is not, down cannot read the manager's agent impact and refuses before any
stop, from a managed seat's shell too. Run cotal up --space <space> to restart the broker from its recorded inputs while the
manager keeps running, then run cotal down. On Linux, a broker process stopped by a signal, such
as SIGSTOP, is read from its process state, and down refuses TARGET_UNAVAILABLE, exit 2, at
once, naming its pid, and stops nothing. When the broker's process runs but does not answer, the
manager read waits out its bound and down refuses the same way. The broker stops last and
is never force killed, so down does not signal it. Resume a suspended broker with
kill -CONT <pid> and run cotal down again; end one that stays unresponsive, then take the route
above. The manager's stop rides a control credential whose issuance record the broker stores first,
and a store over its cap refuses that write. With a live manager, down then refuses
DEPENDENCY_UNAVAILABLE, exit 2, before any stop, naming the store and the --max-file-store cap
this machine recorded. Make room with cotal history clear --yes, then run cotal down. That
purges all of the space's channel history. To keep it, end the manager on the stack's host with
kill -TERM <pid>, using the pid the refusal names. A plain SIGTERM stops every agent that manager
runs by its default policy, as supervise describes, and it needs no credential. Then
cotal down stops the other members, and cotal up --max-file-store <bytes> with a larger cap
starts the stack again with its history. The signal reaches only a process on that host.
cotal service stop stack-manager and restart refuse the same way before submitting a stop, and
name cotal history clear --space <space> --yes and then the same command again. A stop also
names kill -TERM <pid> as the way to keep that history. A dry run
names that refusal as a warning when the space holds more than the recorded cap.
nats-server disables JetStream when its store refuses a write for permission or for space, and
only a restart brings it back. While the broker's log on this machine shows that, the manager's
stop cannot record its credential either, so down, service stop stack-manager and restart
refuse DEPENDENCY_UNAVAILABLE, exit 2, before any stop, and name the logged failure. up refuses
the same way, because it cannot check the space's streams on that broker. A dry run names the
refusal as a warning. Each names the way out: end the manager on the stack's host with
kill -TERM <pid>, which stops every agent it runs by its default policy, then run cotal down,
fix the logged cause, and run cotal up. Fixing the cause alone does not bring JetStream back.
Controller roots refuse
preservation and manifest flags rather than switching to the legacy launcher. Outside a terminal,
a bare down that would stop a running member refuses without --yes, exit 1, and prints that
line and its remedy without the command help. A stop records each step in the stack journal in the root's
.cotal. When this account cannot write that folder, down refuses PERMISSION_DENIED, exit 2,
before the plan, names the folder and the chmod u+w that fixes it, and stops nothing. Each
member's stop is also recorded in the space's service instance records, under services/ in the
Cotal home, and a folder there this account cannot write refuses the same way. A dry run names
either as a refusal the real stop would hit.
A controller stack's members are stopped through the registration cotal up wrote for it. When an
older meshes rm --force dropped it, or an older meshes add --force replaced it with one added by
hand at the stack's own root and broker, down rebuilds it from the stack journal for the stop and
says so with a REGISTRATION_REBUILT warning, as --dry-run does. It does that only while the
stack's broker is the launch the journal records at that root and address, and otherwise refuses
CONFLICT, exit 2, before any member stops. A registration added by hand at another address or
folder is not replaced: down refuses CONFLICT and names cotal space forget <space>, after
which down stops the stack through its journal. While another registered space names this
stack's exact broker address, bare down refuses LIFECYCLE_BLOCKED, exit 2, and stops nothing,
because one listener answers for both registrations. It names each of those spaces with the
cotal space show <space> that reads one, the cotal space forget <space> that takes it off this
address once its own stack is stopped, and the cotal down to run again after that. A user-auth stack's registration is not rebuilt,
so it refuses UNSUPPORTED. A stop that refuses or is cancelled before any member stops puts the
registration back as it was.
A bare down stops only the stack of the folder it runs in. In a folder whose root holds no
stack, when the space named by --space, or else the selected space, is a stack this machine
started in another folder, down refuses with TARGET_NOT_FOUND, exit 2, and stops nothing. A folder whose
own .cotal holds no stack, such as one a failed start left, answers for itself unless --space
names another folder's stack. In a folder with its own controller stack, a --space that names
another space refuses with CONFLICT, exit 2, and names this folder's stack. Each refusal names
the space and, when this machine started it elsewhere, its root, and its next step runs the same
down from that root. --json and --plain carry that refusal as structured output, because the
folder has no stack for a bare stop to print its own lines about.
When the folder of a running controller stack is deleted, its stack journal and broker
configuration go with it, so no member's own stop can run. A running broker writes .cotal/nats
back into the folder, which does not bring them back. down --space <space> from any folder stops
that stack from the instance records this machine keeps, and so does a bare down in the folder
the broker wrote back, or in a folder with no .cotal of its own while the space is selected. Each
running member gets SIGTERM at its recorded process, manager first and broker last, after down
checks that the process is still the one its record pinned, and its record is marked stopped. The
manager stops its agents by its default policy, so --with-agents and the other stop flags are
refused. A member that does not exit within its window, 90 seconds for the manager and 30 for the
others, ends the stop with OPERATION_UNKNOWN and later members are not stopped. Once every member
is stopped, down removes the stack's saved definitions, their instance records and the
registration that names the folder, so the space can be created and started in another folder.
The same stop runs for a folder moved or renamed while its stack ran, which took its journal, store
and trust with it. Its Next names cotal space create <space> --adopt --root <folder> first, which
adopts those records where the folder is now and keeps its history, then
cotal space create <space> for another folder. Outside a terminal it needs --yes, and
--dry-run lists each member with its pid and process.
cotal status marks such a project folder as gone, with selected.rootGone set to true in its
JSON, and its Next names cotal down --space <space> --yes. A command that connects to a static-auth
space, such as send or history, is refused with DEPENDENCY_UNAVAILABLE, because the seed went
with the folder, and its Next names the same down.
Already-stopped stacks
A bare down in a folder whose stack is already stopped stops nothing. It reports that the stack
in that folder is not running and names cotal up, outcome no-op, exit 0, and writes nothing,
so it needs no --yes. --dry-run, --json and --plain show the same. A controller stack
counts as stopped when its last whole-stack operation was a completed down and no member and no
other service instance of its space runs. That check reads no process record under the root; those
are read only as described below. A stack that stopped any other way takes the full stop, which
also finishes its cleanup. A legacy root counts as
stopped when no registration names it, or only the one a space named after its folder keeps, and no
*.pid record remains at the top of its .cotal, and the root still holds the broker configuration a start rendered there or a space prepared there.
Without that evidence a legacy root still refuses, with one next step: TARGET_NOT_FOUND when
nothing shows a stack there, and CONFLICT when several registrations name the root or a process
record remains that no registration explains. Before either kind of root reports the no-op, down
reads every record a local-process descriptor names under the root, an installed extension's
included, at any path and name under .cotal. A descriptor whose record name holds a space is read
for every space its files under the root name, as well as the stack's own. A record whose process
runs, whose liveness the kernel does not answer, or that cannot be read or attributed refuses
LIFECYCLE_BLOCKED, and so does a file whose space part decodes to no space. Nothing outside the
root's .cotal is read: a link anywhere on a record's path refuses at the link, unread, as does a
record that is not a regular file, is larger than a process record, cannot be opened, or changes
while it is read. Each is named once, at the path where reading stopped. A .cotal that is itself
a link never gives the no-op: on a stopped controller stack down refuses at .cotal, and on a
legacy root it refuses CONFLICT before any record is read. A template whose {space}
normalizes away names one path for every space and is read as that path. The refusal lists each
record by name, path under the root and pid, and its next step names the pids and paths to check.
It stops, adopts and removes nothing. --with-agents on a stopped stack refuses
LIFECYCLE_BLOCKED, because no running manager can stop the agents; start the stack first. Its
preview, cotal down --dry-run --with-agents, shows the no-op and warns with that refusal. When the
stack's last completed down took --with-agents, or the root never started a stack, no agent of
it can remain, and --with-agents reports the same no-op.
Legacy stacks
The legacy bare-stack and manifest apply paths described below are retained. They do not
accept the new named-service JSON/plain/yes apply receipt flags, except that --json and
--plain show the no-op of a stack that is already stopped. A bare stop of such a root asks for no
confirmation, and --yes there changes nothing, so cotal down --yes stops a stack on any root.
Before a retained bare apply
reaches its pidfile sweep, it now requires one locally started registration under
a real isolated project root and refuses pending maintenance transitions, any valid or
unreadable additive ledger or attempt under that root, or any other local registration at
the exact recorded broker address. The additive ledger refusal names each ledger with the command
that finishes it: cotal down --run <id> for a readable one, and for an unreadable one the reason it
could not be read and the same command once it is repaired. It reads each record its sweep stops as the stopped-stack check
above does. A record that check leaves unread, such as one behind a link, refuses LIFECYCLE_BLOCKED
before the first stop and is named once, where reading stopped. No process behind it is read or
signalled, and no record is removed. When a manager or delivery daemon has no pidfile but its lease
row is still there, down prints <daemon> has no pidfile; waiting up to <n>s to see whether its lease is renewed and watches the row until the lease TTL ends. A row that is written again refuses
LIFECYCLE_BLOCKED, exit 2, before the broker stops. The refusal names that daemon and says to stop
it, or put its pidfile back, then run cotal down again. A row that expires, or that nothing writes
until the TTL and a five-second grace have passed, was left by a daemon that exited without
releasing it, such as one whose broker stalled past its broker-gone window, and down goes on.
A delivery daemon gets two seconds to release shard 0 of its lease after the signal and then exits
anyway. A signal that lands while its connection redials first waits up to five seconds from the
drop for the redial. So after it stops, the bare stop reads the lease while the broker still runs.
When the row
still names the credential this folder gave that daemon, or the lease cannot be read, the credential
stays and down prints a ! line saying so. The next up uses it to wait the row out, as after a
crash, and then starts the new daemon. These are known-blocker
checks, not a complete live broker-sharing or stop plan; aliases, remote tenants,
agent impact and process identity remain unproved. These paths do not check full
whole-stack target ownership, set exclusion, confirmed agent impact or schema
reconciliation. The retained down --preserve-state
route separately refuses a retained or unreadable additive record under its
maintenance lock, without adopting its effects into the preserved set.
A validated completed restore in active for the registered space does not block ordinary
bare down. The other root, ownership, sharing and native process guards still apply, and
stopping leaves the restore journal and fallback store unchanged.
The machine home is not an isolated project root, so bare down refuses the home stack. Stop it
with cotal down --preserve-state, which keeps its state for the next cotal up. The refusal names
that command. --preserve-state takes its agent inventory from the manager that serves the space.
When no manager holds the space's lease, as on a stack started with --no-manager, it refuses
TARGET_UNAVAILABLE, exit 2, before it stops anything or writes a maintenance record. Start the
manager with cotal up, then run cotal down --preserve-state again.
The cut stops the delivery daemon before it takes the manager's inventory, so no durable join or
leave can race it. A refusal after that point (the manager refuses to prepare, an endpoint the
manager does not manage is live, or a seat cannot be checkpointed) starts the daemon again and
leaves the mesh running and unfenced; a live unmanaged endpoint refuses LIFECYCLE_BLOCKED, exit 2,
and names the endpoints. When the daemon cannot be started again, the refusal says so and names
cotal up. An open mesh runs no delivery daemon, so a refusal there starts none, also when the cut
stopped one that an earlier up left running.
It also refuses LIFECYCLE_BLOCKED, exit 2, before it stops anything, while a saved service in the
space still runs or cannot be verified, because the service depends on the broker it would stop.
The refusal names each one and the cotal service stop <name> --instance <id> --space <space> --yes
that stops it. Bare down refuses the same way.
The preserved state records its store by absolute path. If the project folder is moved after
down --preserve-state, up, backup create and backup restore refuse CONFLICT, exit 2, name
the folder it was moved from, and say to move it back there first. A store missing from its recorded
path is refused TARGET_NOT_FOUND with that path; only backup restore --accept-missing-source
restores over a lost store.
After those checks, a retained bare apply refuses LIFECYCLE_BLOCKED, exit 2, before its pidfile
sweep while a service instance in the selected space still runs or cannot be verified, because the
service depends on the broker the sweep stops. It stops nothing. As on a controller stack, bare
down does not stop a service itself: the refusal names each instance with the
cotal service stop <name> --instance <id> --space <space> --yes that stops it, then cotal down
again. An instance whose process is already gone is skipped, and saved definitions stay. An
unreadable service record keeps the code of its read refusal. A retained native attempt, an
unsettled request, a process that cannot be observed, or a record from another host, which bare
down does not touch, refuses too, and the remedy names the command that inspects or stops it.
cotal down
cotal down --with-agents
cotal down --abandon-stop <request-id> --yes
cotal down --abandon-launch <instance-id> --yes
cotal down --preserve-state [--store-dir <dir>] [--session-store <dir> …]
cotal down --dry-run [--space <name>] [--json|--plain]
cotal down <service-name> [<service-name> …] [--instance <id>] [--space <s>] [--yes|--dry-run] [--json|--plain]
cotal down -f <cotal.yaml> | --run <id> [--dry-run]| Flag | Default | Meaning |
|---|---|---|
--file <cotal.yaml>, -f | none | Tear down this manifest's deploy |
--run <id> | none | Tear down one additive ledger, written by the retired spawn -f deploy, by its run id |
--space <name> | current space | With service names: the space whose saved definitions to stop. With bare --dry-run: confirm that space belongs to this root. A bare stop refuses it |
--dry-run | off | Print the manifest teardown, the named service stop or the bare stack preview, mutate nothing |
--with-agents | off | Bare whole stack only: also stop and deprovision every managed agent |
--abandon-stop <request-id> | none | Controller stack, with --yes: abandon a member's stop that no receipt settles, then stop the stack. See the settlement rules under up |
--abandon-launch <instance-id> | none | Controller stack, with --yes: record failed a member launch whose record names no process, then stop the stack. See the settlement rules under up |
--preserve-state | off | Bare whole stack only: fence the manager, retain principals and durable state, stop and prove the stack down, then publish ready |
--store-dir <dir> | the recorded store, else .cotal/nats | With --preserve-state: the store to cut. The mesh record names the store the broker runs on, such as a backup restore --store-dir target, and a different path refuses CONFLICT before anything stops |
--session-store <dir> | none | With --preserve-state: a harness transcript store directory to capture with every continuation-capable retained seat. Repeatable. No default and never inferred from a connector name; a path that does not exist or is not a directory is refused before anything stops |
For an additive ledger from the retired spawn -f, down -f keeps an authenticated channel when
membership cannot be read. Its output includes the read failure, rather than calling every failure
an absent feed. A user-auth teardown uses the signed-in user's control authority and an admin read
view, plus the channel-writer view for deletion. Run it from the registered checkout as the owner
who deployed the agents, with the admin scope needed to inspect membership and remove channels.
It does not use the local static signer to stand in for that user's identity. If the issuer predates
the membership snapshot read, upgrade it and obtain a new credential before retrying the retained
run. See membership snapshot reads.
Agent policy
Bare cotal down stops the whole local stack in dependency order and leaves managed agents running
when their runtime lets them outlive the manager. Before signalling the manager it verifies the spare
capability of the exact recorded manager, which records what that manager's stop does with its
seats, and it reports the agents left behind plus cotal down --with-agents as the explicit reap.
When the manager had no managed agents, it prints no report. When they cannot be listed, for
example on a user-auth mesh with no login on this machine, it prints
could not list managed agents (<reason>) and the stop continues.
The built-in pty runtime keeps each PTY inside the manager process, so those seats cannot outlive
it: every manager stop stops and deprovisions them, and down reports them as stopped. A manager
retires at most 8 seats and chat sessions at a time, and the rest wait their turn. Every manager
stop the CLI makes runs this one path: down, Ctrl-C on a foreground cotal up, the teardown after
that up's broker exits, the leftover-manager stop before cotal up -f, and the delivery cutover.
Each holds the manager's stop reservation, so a second stop while one is in flight is refused,
names the process holding it, and leaves that stop's --with-agents policy in place. On Windows,
manager and delivery stops send an authenticated local cleanup request because Windows process
signals terminate without running cleanup handlers. The request requires the process's private
capability, its creation identity and the live stopper's identity and reservation. Other platforms
use SIGTERM. A cotal supervise that receives SIGTERM or SIGINT while it is still starting
lets the start settle, then runs the same stop and exits without printing manager up, so its
liveness lease is released and an immediate restart is not refused. When that start fails, the stop
still releases the lease before the process prints the start error and exits 1. A process still running after 20s on the Windows cooperative path, or 15s after
SIGTERM, receives SIGKILL. Ctrl-C stops the manager first; when that
stop is refused or the manager's exit cannot be confirmed, Ctrl-C signals nothing else, prints the
refusal with the reap route, and leaves the stack running; end it with cotal down --with-agents.
--with-agents is a one-shot destructive policy bound to the exact verified manager process
and the exact live down stop reservation; a stale, malformed, crashed, or different stop attempt
cannot turn a later bare shutdown destructive. If a managed agent cannot be proven stopped within
the manager's stop timeout, the manager logs which one, still closes its broker connections and
console listener, and exits with code 1. It does not release its pidfile, liveness lease or
service registration in that case, so no successor is handed authority while that agent may still
run; the lease lapses on its TTL. A named down is the service stop above and never stops a
legacy component by its pidfile, so cotal down manager or cotal down web on a legacy stack is
refused unless a saved definition has that name. Only bare cotal down stops a legacy root's
components, all together, under the folder you run it in. It takes no --space. Its stop lines
follow a Mesh: <space> heading, and its Next names cotal up to start the stack again. A
component pidfile that holds content that is not a pid names no process, so down says so, keeps
the file, does not stop the broker, and keeps the artifacts and the registration. Its Next first asks
you to confirm that no such component still runs for the folder, because cotal cannot check a record
that names no process and a manager that does not answer on the mesh may still be running. It then
names rm <pidfile> and the same cotal down. A bare down or
down --dry-run whose --space names a space whose stack is recorded in another folder refuses
TARGET_NOT_FOUND, exit 2, naming that folder and the stop or preview to run there. The
-f / --run forms tear down a manifest deploy without stopping the whole mesh
and cannot be combined with service names or --space.
A pinned manager with no spare-capability record is not signalled by bare cotal down. A current
manager always publishes the record, so a missing one means an older
manager: one that predates capability reporting, or one whose pty runtime reported that it cannot
detach its agents. Stop each managed agent explicitly, then run cotal down --with-agents from the
mesh root to stop the whole stack. An older manager does not understand
the reap request, which is why the agents must already be stopped.
After the known-blocker admission checks above, the retained bare cotal down
inventories by pidfile. When this folder's registered broker answers and no
nats.pid records it, the command does not say nothing is running. It names the space and the
broker address, says no pidfile records that process, says it will not stop a process it did not
start, and exits 1. Stop that broker with whatever started it (an init unit, a container, or the
hand-run process). cotal meshes rm <space> only drops the registration. The probe runs whether or
not other owned components were running: they stop and clear their artifacts first, then the broker
is named. A --dry-run does not run this probe.
down reads each process record once. A component that exits and removes its own record while
down runs counts as having no record, so the stop goes on. Any other failed read is an error.
An extension-removal reservation in a component's pidfile refuses the stop: it reports the removal in
progress while the owner is live, calls the reservation stale only when the owner is proven gone or is
not a pid, and otherwise refuses without advising deletion because the owner's liveness could not be
confirmed.
Whole-stack shutdown keeps delivery credentials until every required component has stopped.
A refused stop preserves both the space-scoped credential and its legacy copy. Credential removal
errors remain failures. down removes the manager's delivery-aware marker, shutdown intent and
spare capability only after every component has stopped, so a refused stop keeps them too.
Process identity
Teardown verifies pinned process identity before signalling. PIDs are recycled by every OS,
so a recorded pid alone is not a durable target identity. up and the dashboard's native child record each
process's creation identity in a sibling <pidfile>.identity pin, which holds the pid and the
process start reported by the OS. Every stop path, including down for the broker, web and
extension components, and the manager, delivery and auth-service stops, applies the same rule. A pin
that names a different start means the pid was reused, so teardown refuses and preserves it. A
controller stack's bare down signals no such record, so it removes it, as described above. A torn
or unreadable pin also refuses. On macOS, the reader calls sysctl through a bundled Node-API
binding for x64 or arm64 and records the process start in microseconds. A macOS pin that an earlier
release wrote holds the ps -o lstart start date instead and is verified with that same reader. That
date keeps its spaces, including the padded day, in both the identity pin and the one-shot shutdown
intent. A single record cannot span lines.
On Windows, the reader calls GetProcessTimes through a bundled Node-API binding for x64 or
arm64. It requests limited process-query access and preserves the creation FILETIME as a decimal
string. It launches no subprocess. Access denial leaves identity unverified; an unreadable pin
still refuses teardown. On either platform, a missing or damaged binding is an installation error
and has no fallback.
The pidfile and its pin are published by renames, and the pidfile rename is the commit point. Just
before it, the pin holds two lines: the old process's and the new one's. A launcher that dies
mid-publish therefore leaves the old record or the new one, each checked against its own pin line,
never a pidfile without its pin. An old record with no pin is legacy, so its line holds - in place
of the token and it stays legacy until the commit. A CLI older than this change reads a two-line pin
as torn and refuses.
Publishes of one pidfile are serialized by a lock file beside it, <pidfile>.publish.lock, because
the launcher and the daemon it starts both publish the same record. The next publisher reclaims a
lock left by a crashed one. When no start token can be read for the new process, its pin line holds
- in place of the token, which reads as a legacy record, and the publish ends in the legacy shape:
a pidfile with no pin. Teardown, and a daemon removing its own record on exit, take the same lock and
remove the record only while the pidfile still names the pid they stopped, so a stop that races a
publish leaves the new record whole. A directory at the pin path is refused by name and the record
is kept, because no publish writes one and removing it could delete files Cotal did not write.
The web dashboard service claims web.pid with an exclusive create, so a second dashboard for the
same mesh is refused, and writes its pin right after the claim. A stop that runs between the two
reads a legacy record. Any existing web.pid refuses the start, whether or not its process still
runs, and the start neither adopts nor reclaims it.
The pidfile pid and the pin pid are two coordinates. Automatic cleanup follows proven death of
the pidfile target (ESRCH on that pid): a torn or unreadable sibling pin does not wedge a dead
pidfile pid.
A torn pairing where the pin names another pid, while the pidfile pid is still live or not proven
dead, still refuses. Inspect both pids with ps. Do not delete <pidfile>.identity to force a
stop; that weakens target-identity protection. Once the pidfile process is dead, rerunning
teardown clears the stale record automatically.
A Windows manager or delivery daemon without the cooperative capability must be stopped through its original supervisor before restarting with this version. Teardown refuses it without signalling or deleting its live record. It does not wait for a lease to expire or delete a lease by local pid.
For components using signals, the first teardown after upgrading a running pre-pin stack has a
narrower guarantee. A live record with no identity pin is signalled after a loud warning that it predates identity pinning. Restarting
the component writes the pin, so later teardowns receive full match and mismatch protection. The
same warning applies on platforms where no stable start token is available. For a legacy manager,
bare cotal down also warns that agent sparing cannot be verified before it signals. Because the
CLI cannot establish which SIGTERM handler that already-running binary carries, it never presents
the pre-signal seat inventory as confirmed spared; a genuinely older destructive handler may still
reap those agents. --with-agents publishes a one-shot reduced-guarantee handoff bound to the
recorded manager pid and the live .stopping reservation's inode, then signals unconditionally.
That handoff cannot be replayed by a later stop attempt. A pin that exists and does not match the
live process still refuses before signal.
--with-agents performs the old destructive logical teardown: managed processes stop and their
credentials, ACL rows, and delivery footprints are deprovisioned. --preserve-state is a different
maintenance transition: it stops retained processes while suppressing leave/deprovision cleanup, persists the manager's
same-principal resume inventory, stops the entire stack without removing run/auth artifacts, and
publishes a stable inode-bound cut only after every recorded process is proven stopped and the exact
recorded NATS endpoint is unreachable. A missing or stale broker pidfile never counts as stopped. The
attempt is bound durably before the manager is fenced, the resume document and attempt-bound
cut-intent are fsynced before manager commit, and the manager's commitment itself is journaled
(cut-committed) before any process stops. A retry after a crash at any of those boundaries reuses
the exact recorded attempt and finishes the remaining stop and endpoint proofs idempotently, without
needing the (by then intentionally dead) manager. A partial cut never publishes ready. It cannot
be combined with component names, manifest teardown, or --dry-run.
On a mesh that runs the delivery daemon, the cut stops it first so no join or leave can change
membership under the manager's inventory. Before any manager-owned endpoint stops, the cut then
reads the presence roster and refuses while an endpoint the manager does not own is live. A refusal
leaves the delivery daemon stopped. A deleted presence key is the only row it reads as absent. Any
other row that is not a card with an id equal to the presence key it was read under and a string
name refuses the cut with presence record <subject> is malformed. A participant can write any
value under its own key, so the key is the identity the cut trusts.
Seat checkpoints
Seat checkpoints. After the stack is proven down, the cut writes one checkpoint per retained
seat under .cotal/maintenance/v1/checkpoints/<attempt>/<seat>/, and prints the path, the
continuity class and the generation for each. The path carries the preservation attempt because a
checkpoint is immutable once sealed: a shared directory would make the second cut in a root refuse
on the first cut's leftovers, and clearing it would destroy an artifact a rollback still needs. The
capture happens only at that point because anything earlier races a harness that is still writing
its transcript and its working tree.
Each checkpoint directory is created 0700, refuses a destination that already exists, and holds:
repo.bundle, the seatcwd's reachable history, anchored on the base commit the record names by full object id;repo.index.diffandrepo.worktree.diff, the staging state as two diffs, base to index and index to worktree. Two rather than one because a single combined diff restores a mixed tree with the right bytes and the wrong index: a source reportingMM READMEwould come back asM README;repo.untracked.tar, the untracked files in scope;- the harness session pointer, when the seat's connector declares one, and the transcript store
files the operator named with
--session-store. Each records where the destination puts it back as an anchor (the workspace root, the account home, or the seat'scwd) plus a relative path, because the destination's root and home are its own and the source host's absolute spelling would either miss them or write outside them; checkpoint.json, written last, after every digest is computed over the bytes that landed.
The record carries the manager's resume entry unchanged as its first field, then the space, the seat
name, the recovered lifecycleUid, the writer generation the cut was taken at, capturedAt, the
recency horizon, the applied profile revision, the seat's git status --porcelain as the cut read
it, and the continuity class. Every captured file is
listed with its byte size and sha256, so an operator verifies the whole artifact with sha256sum
and git bundle verify. No secret values, no operator keys and no source-host launch material
enter it.
The generation is the highest one this root has claimed for the seat in .cotal/auth. A
generation entry there that is a symlink, a directory or anything else that is not a regular file
refuses the cut, as every other auth record does.
The continuity class is what the connector declares, capped by what the checkpoint carries. A
connector declaring session continuation classifies as exact, but reopening a session takes both
halves, the pointer that names it and the store that holds its transcript. A checkpoint missing
either one cannot reopen that session, so it is recorded as fresh when the connector declares a
fresh start and drain-only otherwise. A pointer with no store is capped the same way as a cut
carrying neither, because it names a session whose bytes the artifact does not contain. A class is a promise the destination is entitled
to act on, so it never describes bytes the artifact does not contain. The transcript store stays an
operator input: this repository does not know where a harness keeps its transcript, so exact
requires --session-store to name one.
The recorded status is read under the same selection rule as the untracked set, so it describes the state the captured bytes can reproduce. The destination re-reads it in the promoted tree and refuses a difference.
The untracked selection rule is recorded in the record and is
git ls-files --others --exclude-standard -z, excluding .cotal/. It honors .gitignore, so an
ignored file the seat needs does not travel and has to be moved separately. The .cotal/ exclusion
is a secrecy boundary rather than a size one: when a seat's cwd is also the mesh root, the control
directory is untracked, and without the exclusion the broker trust material, the space account, the
manager instance identity's private seed and the seat's own credentials would land inside the
artifact. A checkpoint carries credential references only; the destination resolves that material
itself.
A seat whose launch options could not be resolved is refused rather than checkpointed, with the
manager's own wording: imperative launch options have no non-secret durable source (<keys>). The
refusal arrives at prepare time, so the cut stops before any child does.
A delegated seat (SPEC §13.17) is refused at prepare time too, with
a delegated seat is not resumed by a later manager; stop it before preserving. A manager stop
after a refused cut retires that seat through its retirement path.
clean
cotal clean <history|store|all> --force
cotal clean <store|all> --dry-run
cotal clean restore-attempt --attempt <id> --force
cotal clean restore-fallback --attempt <id> --force [--discard-history]| Flag | Default | Meaning |
|---|---|---|
--space <s> / --server <url> / --creds <path> | resolved mesh | history: target mesh |
--dms | off | history: also clear DM history |
--store-dir <dir> | .cotal/nats | store/all: JetStream store directory |
--force | none | Required: destructive, no prompting |
--dry-run | off | store/all: list what would be removed and remove nothing; needs no --force |
--attempt <id> | none | restore-attempt: exact stale pre-commit attempt; restore-fallback: matching healthy committed restore |
--discard-history | off | restore-fallback: also delete an old store that holds the only copy of the history or workflow runs the restore left out |
One configurable cleanup verb; every target requires --force, except a --dry-run of store or all.
It takes one target and no extra positional arguments. A channel name after history refuses
USAGE_INVALID (exit 1); it does not narrow the purge. The dry run quotes an explicit --store-dir
value in its printed force command so it stays one shell argument.
Only history takes --space, --server or --creds. Every other target acts on the root of the
folder it runs in, so it refuses them USAGE_INVALID (exit 1) before local cleanup inspects or removes
root state.
historypurges the retained message backlog on the running broker (channels, plus DMs with--dms). The same operation ashistory clear, which stays as an alias.storedeletes the stopped mesh's JetStream store (.cotal/nats): streams, durable consumers, and messages. This is the reset for stale on-disk broker state, e.g. durables minted by an older, incompatible Cotal generation surviving adown/upcycle. It removes the store'sjetstreamdirectory, its.cotal-store-idmarker and the broker'socspfolder, then the empty store directory. A store directory that holds anything else refusesCONFLICT(exit 2), names those entries and removes nothing; move them out and run it again.allisstoreplus the space identity (.cotal/auth), the local creds and markers tied to it, any crash residue a normaldownwould have swept (stale pidfiles,run/), and the mesh's registry entry; the nextcotal upmints a fresh identity. On a root deployed withup -f, plaincotal upthen refuses and namescotal up -f <manifest>when the deployment ran at another address, mode or space. A full backup is bound to the space trust it was taken under, so full backups taken beforeclean allno longer restore into this root. Only the registry of such a backup still restores, withbackup restore <dir> --only registry, and chat and DM history start empty. Its dry run and its receipt say so when it removes that trust; take a new full backup after the nextcotal up. On a user-auth space it also removes the space's IdP trust and its actor ledger, so every actor grant is lost. Its dry run and its receipt say so, and a laterupthen needscotal up --user-auth --idp <auth base URL>and each actor granted again withcotal actor grant.
store and all move the store aside to a .cotal-clean-<id> folder beside it, then remove its
entries there, as reset store and reset space do. A removal that stops partway, such as at a
folder in the store this account cannot write, ends OPERATION_PARTIAL (exit 4). It names that
folder and what is left in it, and its Next makes the folder removable and removes it. Nothing else
records the folder, so a later clean or reset finds no store and reports nothing to remove.
An existing store path that is a symbolic link refuses CONFLICT (exit 2), naming the link and
its target. Replace the link with the directory it points to, then run the command again. The
link and its target are left unchanged, including for --dry-run.
Cleanup eligibility
history needs the mesh up; store and all refuse while any recorded mesh process may still be
running or any same-root recorded broker endpoint remains reachable (run cotal down first). On a
controller root, a member whose process is gone counts as stopped once a completed down recorded it
as already stopped, so down --yes after a crash is enough. A
record counts as stopped only when it is absent, empty, or names a pid the kernel reports gone, so
unattributable content, a removal reservation, or a pid whose liveness cannot be read refuses. They
also refuse outright on a root that holds accounts for several spaces: the store and the broker
trust record are shared by every space on the broker, so both targets would take out all of them
and no --space can narrow that. down, backup and up --restore refuse there for the same
reason. cotal status lists the tenants on such a root. Personas
(.cotal/agents) and logs are never touched. Without --store-dir, store and all read the
store the root's last broker served, as a start does: while the default .cotal/nats holds no
JetStream data and that store does, such as after backup restore --store-dir, they remove that
store, and the dry run names it. Custom cleanup targets must contain either the Cotal store-generation marker or a
real jetstream/ store directory; filesystem roots, project roots, and Cotal auth/maintenance trees
are always refused. A store in the project folder itself, as up --store-dir . makes, refuses
CONFLICT (exit 2), since the folder holds the project's own files; its Next names the store's own
entries to remove by hand once the space is stopped. Its contents are not checked, so a directory that holds only a jetstream/
directory is removed as a store, whatever that directory holds. The one exception is another
space's data: when another space uses the store folder, as up checks it, store and all
and their dry runs refuse CONFLICT (exit 2), name that space, its root and its streams, and remove
nothing. Keep the folder while either space needs its history; once neither does, stop both and
remove it by hand.
In a folder whose .cotal is a link, every target but history, and both dry runs, refuse before
anything reads through it: CONFLICT, exit 2, naming the link and where it leads, or
TARGET_NOT_FOUND for a link that leads nowhere, which is not passed over to the records of a
folder above it. When the link leads to this folder's own records, moved away and linked back, the
Next names moving them back, as up does.
In a copy, in a folder moved away from its recorded path, and in the folder a later up took its
space's name over from, the store may hold that space's only history. There store and all, and
both dry runs, refuse TARGET_UNAVAILABLE, exit 2, before the liveness checks, naming the folder
the stack runs from and the step the commands that read there name, and remove nothing.
--dry-run runs the same checks as store or all and prints would remove <path> for each
store, credential, record and registration the command would remove, then removes nothing and takes
no maintenance lock. A check that would refuse the command refuses its dry run the same way.
Ctrl-C or SIGTERM during store or all releases the maintenance lock, reports what the clean
left and exits 130 or 143. The outcome is failed when nothing was removed yet, and unknown, naming
the paths removed, once removal began.
store and all also refuse every maintenance journal state, with LIFECYCLE_BLOCKED, exit 2. The
refusal names the attempt and, as Next, the step cotal status shows for it, such as bare
cotal up to resume a preserved cut; once the root holds no maintenance state and its mesh is
stopped, the clean runs. After a healthy committed restore,
restore-fallback is the only supported way to remove the recorded unchanged old-store inode; it
never deletes the active target, requires both the exact attempt id and --force, and retires the
completed restore journal so a later down --preserve-state can start a new backup cycle. After a
restore that left history or workflow runs out it also requires --discard-history, as
backup cleanup-fallback does, and its refusal names the flag without printing the command help.
After an --accept-missing-source restore there is no old store, so it only retires the journal.
An attempt the root's maintenance record does not name refuses with TARGET_NOT_FOUND, exit 2, and
deletes nothing. A repeat after a completed cleanup is such an attempt, and its refusal says the
restore has settled and names cotal status. Without --force, restore-attempt and
restore-fallback name the command to run with its --attempt. restore-attempt refuses the same
way, with TARGET_NOT_FOUND, for an attempt the record does not name, and with LIFECYCLE_BLOCKED
for a recorded attempt past its pre-commit point, which recovers forward. Without --attempt both
verbs refuse with USAGE_INVALID. Each refusal's Next names the attempt the root records and its
step.
reset
cotal reset store <space> [--dry-run] [-y|--yes] [--json|--plain]
cotal reset space <space> [--dry-run] [-y|--yes] [--json|--plain]Both routes select a space this machine started. A space with a locally started registration is
selected by it. A controller stack that a completed down stopped has no registration; its
prepared record and stack journal at the same root name its broker address, store and mode, and a
reset keeps both, so a later up starts it again in the same mode. No reset changes a stack's mode;
up names the way to run another one. After reset space of a user-auth space, that
up needs --user-auth --idp <auth base URL>, as described below. A stack none of whose members ever started has
no store to reset, so both refuse TARGET_NOT_FOUND and name cotal up. So does a prepared space
no stack has run for, which also names cotal space forget. A stack whose last
operation is not a completed down refuses LIFECYCLE_BLOCKED and names cotal down --yes. Each
refusal names one reason and the command that clears it. A space that may still run names
cotal down from its root, and lists each process that may run once. A stack member whose
process also holds its component's record, such as the manager's pidfile, is listed as that member
with the record's pid. A member that exited without a recorded stop, as after a crash, is
settled by cotal down --yes: it finds the member gone and records it as already stopped, and the
reset then accepts it. Only a member that no completed down recorded that way needs cotal up and
then cotal down --yes. A legacy root has no stack journal, and its down drops the registration
a reset selects, so no reset reaches it once stopped. A space named after its folder keeps that
registration, so a reset reaches it stopped. For any other legacy root, the step after cotal down is
cotal clean store --force from its root for reset store, or cotal clean all --force for
reset space, with --store-dir <dir> when the registration records a custom store. Once stopped,
such a space has no record here, like a mistyped name, so a reset of either refuses
TARGET_NOT_FOUND, says no registered or prepared space has that exact name, and names cotal space
and then that step.
Removing the store removes what the broker kept in it: workflow runs and their journals, channel
history, stored messages and every other record. A run still waiting for an answer goes with it and
cannot be answered or resumed afterwards. Only the broker reads the store, and it is stopped, so
cotal down reads the stack's runs from its manager before stopping it and records them in the
stack journal with the steps they wait on. The preview, the confirmation and the receipt list those
runs as of the last down, and --json carries them as storeContents.runsAtLastDown and
removedRuns. When that down could not read them, or no down recorded them, the preview says so.
To see the runs then, run cotal up and cotal run, then cotal down --yes.
Store preview
The store dry-run is a local-impact preview that exits 0. It requires a space selected that way, a
real project .cotal and a root readable as one local tenant. It opens with a verdict: the apply is
offered, with cotal reset store <space> --yes as Next, or it would refuse, with the apply's own
reason and remedy. The JSON result carries it as applyVerdict, and completeness stays
partial. Completion suggests locally started names, without proving
eligibility. The preview inspects the store path with the existing guarded
cleanup path checker, reads the maintenance journal, legacy process records,
service-controller instances, supervisor receipts, retained native attempts
and root-local additive ledgers and attempts plus fresh boot attempts, and
makes a credless broker probe. Readable deployment IDs, unsettled attempts and unreadable
record paths remain visible; these records do not
prove live ownership or a complete broker tenant inventory. An orphan receipt
or attempt remains visible without an instance record; these space-scoped
records are not proven owned by this root. The preview also lists other local
registrations at the exact
recorded broker address. A match blocks store apply, while no match does not
exclude aliases, remote tenants or live attachments. A negative credless probe
cannot prove absence for TLS-first listeners. A custom store path must be
absolute and one real directory. A recorded store path that is a link refuses CONFLICT in the
preview and the apply of both resets, naming the path and the directory it points to, and nothing
is removed. Replace the link with that directory to reset it. A selected root whose .cotal is a
link refuses both resets and their previews CONFLICT, exit 2, before anything reads through it,
naming the link, and nothing is removed; when the records were moved away and linked back, the Next
names moving them back. A store in the project folder itself
refuses both resets CONFLICT as it refuses clean store, naming the store's own entries to remove
by hand. The preview takes no maintenance lock and cannot establish exclusive
custody or a complete identity deletion set.
Store reset
Bare reset store <space> checks those conditions, displays a scoped terminal
confirmation or requires --yes in a script, and acquires the maintenance lock. While another
command holds it, such as an up started at the same time, reset store and reset space refuse
LIFECYCLE_BLOCKED, exit 2, name that command's pid and remove nothing.
Under the lock it rechecks the exact registration or stopped-stack records, one-tenant root, absent
maintenance journal, legacy process records, service instances, supervisor
receipts and retained native attempts for that space, additive ledgers and
attempts plus unsettled fresh boot attempts under the root, credless broker
nonresponse and pinned filesystem identity before calling the existing
store-only cleanup. A readable or unreadable additive ledger or attempt
blocks reset before confirmation and under the lock. It refuses another
local registration at the exact broker address before confirmation and
rechecks under the lock. This does not prove
exclusivity against aliases or remote tenants. An orphan receipt or attempt
blocks reset even when no instance record remains; a submitted native step may
have changed state. A TLS-required or TLS-first target refuses
because a credless probe cannot prove it is down. The route preserves trust,
credentials and registration. A cleanup with an unconfirmed outcome is reported
as unknown and is not retried. Credless nonresponse and the recorded process
checks are not proof that no unrecorded process exists. A store directory that holds anything
besides the store refuses both routes and their dry runs as it refuses clean store, naming those
entries. A store folder another space uses refuses both routes and their dry runs CONFLICT the
same way, naming that space, its root and its streams; the store dry-run shows it as its verdict.
Space identity reset
reset space <space> adds local identity cleanup through the existing native
clean-all operation. This route currently requires Linux, a space selected as above and a
plain-NATS loopback listener. It removes a locally started registration; a stopped controller stack
keeps its prepared record and stack journal. On a user-auth space it also removes the IdP trust and
the actor ledger, so every actor grant is lost. The preview and the receipt say so on their own
line: a later up needs cotal up --user-auth --idp <auth base URL>, and each actor needs
cotal actor grant again. TLS-first, remote, shared-root
and non-Linux targets refuse. It applies the same maintenance, deployment and
process-custody guards as store reset. It does not stop a process to become
eligible.
Its --dry-run validates the existing local trust chain and inventories the
native identity paths, including provider-declared secret keys, without running
deprovisioning hooks. The inventory is bounded to 10,000 paths. Foreign space
segments, symlinks, hard-linked files, foreign ownership, group/world-writable
paths and providers without bounded cleanup metadata refuse. The refusal names the path and its
reason; for a group- or world-writable path it names chmod go-w <path>. Values and content
fingerprints are not printed. No maintenance lock is taken by the preview. Like the store dry-run, it
has a Store contents section when a store is present, naming what removing it removes and the runs
the last down recorded, and --json carries them as storeContents. The apply's confirmation shows
the same section. A preview that passes every check opens with a verdict, as the store dry-run
does: the apply is offered, with cotal reset space <space> --yes as Next.
Apply shows the path inventory and defaults to cancellation, or requires
--yes in a script. Under the maintenance lock it repeats the eligibility
checks and compares the reviewed file identities. Native secret deletion is
restricted to the planned keys; each filesystem removal checks the remaining
reviewed tree. Added paths and changed files do not enlarge the confirmed scope.
The native operation deprovisions secrets before raw identity removal and
removes trust last. After checking the planned targets absent, it removes only
the unchanged eligible local registration and releases its matching selection.
A replacement registration is kept and reported as partial.
Receipt limits
The receipt retains acknowledged deletion paths even if later cleanup fails, and lists each path once.
Ctrl-C or SIGTERM during either apply releases the maintenance lock, reports what the reset left
and exits 130 or 143. Before the removal was submitted the outcome is failed and the store and
identity are as they were. After it, the outcome is unknown, reset space lists the paths removed
so far, and Next is the route's --dry-run.
Unconfirmed effects are unknown and are not retried. Personas, saved service
definitions and stopped-instance records, installed packages, login sessions
and remote data remain. Local cleanup does not revoke a remote credential or
clear Cloud data.
A saved service definition whose credentialRef names a credential this space's account issued,
such as one cotal auth mint wrote, would hand its child a credential the next identity rejects.
Reset reads the issuing account from each such file and clears that credentialRef, and both the
preview and the receipt name the service and the file. A file it cannot read as a credential is
left as it is. To give the service a credential again, mint one after cotal up, then remove the
service and add it again with that credentialRef.
Named services
See the current command contract for this behavior.
Controller stacks
See the current command contract for this behavior.
Delivery shutdown
See the current command contract for this behavior.
Preserved cuts
See the current command contract for this behavior.
Maintenance attempts
See the current command contract for this behavior.