Repository navigation
v0.100.0
Install or update: npm i -g cotal-ai@0.100.0
Changes in this release
cotal channel removeno longer says a channel was removed when it had no saved entry, such as a mistyped name or one already removed. That run is now ano-op: the receipt readsNothing removedwithMetadata: no saved entry, andresult.removedisfalse. With--purge, the channel's retained messages are still purged and the receipt says so.deleteChannelsnow returns the channels it removed, andclearChannelsays whether it removed the channel's entry.--credsreads and checks its file before dialing. A missing, empty or malformed file (CRLF line
endings, a truncated JWT, no seed block) and a directory are refused naming the file and the cause,
instead of reporting a serving broker as unreachable or dumping the command's help. A file other
accounts can read is still used, with a warning that nameschmod 600.- A broker that accepts the connection and never answers, as a stopped or overloaded one does, is no longer reported as not running, and
statusno longer hangs on it.ps,who,sendandhistoryrefused such a broker with "no mesh running at ; ... recorded at but not running". They now say it "accepted the connection but is not answering", keep the registry entry, and name checking that the broker is not stopped or overloaded.cotal meshes addand a raw--serverconnect use the same wording.statusprinted its report and then never exited, because its manager service probe dialed the broker without first reading its greeting and the client library left that socket open.statusandstatus --componentsnow read the greeting first, report the manager service as unchecked or refused, and exit. - A channel subscription that the broker refuses after the join was confirmed, for example after a
permission reload, now reaches the endpoint'serrorevent once, asleft channel "<channel>", with
the broker's permission violation as itscause. It was also reported a second time as a generic
NATS permission deniederror, so an embedder could not count or alert on it once. - With the space's manager stopped,
cotal runand its hosted verbs andcotal modelsrefuse at once, ascotal psdoes:TARGET_UNAVAILABLE(exit 2) with the Mesh header, the registry's own answer as the native detail, and barecotal upas the next step. They used to wait about 10 seconds for a manager answer that could not come and then print a refusal about theep.v1rail with no code or next step. An unpinned operator call reads the service registry first and refuses only when it lists no live manager; an unreadable registry, a pinned call and a user's manager view go to the manager as before. Core marks the registry's empty answer with a newai.cotal.ep.no-live-instancesdetail, so a caller can tell it from an unreadable registry. - Create a missing persona catalog
.cotal/agentswith mode 0700 from every writer. Under a
group-writable umask,space create,upandsetupmade it 0775 while seeding the default
persona, so until the firstpersona createtightened it, a group member could replace
default.md. The setup log's.cotalis created 0700 the same way. An existing directory keeps
its mode. - A persona file with CRLF line endings or a UTF-8 BOM now reads like the same file with LF endings, so
persona create --file,persona list,persona show,persona editand launches accept it.persona create --filenow names the file it read when it refuses a definition, not the catalog path it would have written. - Observers and chat sessions no longer hang silently after a broker outage the client could not ride out.
watch --plainandconsole --plainnow resume when the broker comes back, ashistory --followalready did: the endpoint re-subscribes their feed on every reconnect, where before it subscribed once and stayed silent while reporting itself connected. Achat --stdioor--plainsession whose inbox is gone from the broker after the outage, as whencotal uprestarted the space and retired it, no longer reports reconnecting forever. It ends by itself with that reason, its--stdioend record andclosecarry it, and it exits 5. An endpoint stopped from its own warning listener also exits promptly instead of waiting out the reconnect backoff. cotal run showandcotal run journalnow say why a step failed. A failed or refused step's journal row carries its recordederrorMessagebesideerrorCodein--json, and the text view lists each such step with its outcome and message underStep errors, escaping control characters. Before, both views printed onlyfailed (L4000), and the reason, such as a hosted run that is not admitted to read a channel, was only in the manager's log. The manager'srun-statusreply schema admits the new field.cotal send --credswith a credential that may not read the space's roster now refuses withPERMISSION_DENIEDas soon as the broker denies the read. It used to wait about five seconds and report an unknowntimeout. A batched Direct Get in core now ends on the broker's permission denial for its request.cotal whoon a mesh where nobody is present now answers at once that nobody is there, and exits 0.
It used to wait 3 seconds and report the roster as partial. A presence observer that does not
register now completes its snapshot on an empty bucket, so its view is current.ADMITTED_NODES,STRUCTURAL_NODES,RESERVED_NAMES,FORBIDDEN_GLOBALSandMUTATING_METHODSare now exported as frozen arrays instead ofSets, andCATALOGis frozen. Code that imports the package could calladdordeleteon one of those sets, or assign aCATALOGtitle, and change whatvalidateaccepted or how its errors were titled for the rest of the process. The tables can no longer be changed: a write throws in strict-mode code, such as an ES module, and changes nothing elsewhere. This changes the exported type, so a caller that used.hasor.sizeon one of these tables uses.includesor.lengthinstead.- A declared adapter path that other local writers could change is refused with every unsafe entry
at once, the reason group write is not accepted, and the onechmodthat fixes them. Before, the
refusal named only the shallowest directory, with no reason and a Next that pointed at
cotal service. An install made bynpm i -gunder umask 0002 left the bundlednats-serverand
each of its directories group-writable, so a controlleruprefused one directory per attempt. cotal service addnow says plainly when a declared adapter path crosses a symlink, such as--handler /bin/shor--handler /usr/bin/python3for theexec-endpointpreset. It names the link and what it points to, and its Next names the physical path to use instead. The refusal used to report the symlink's own mode, 0777, as if group or others could write it, and a symlinked file was refused asDEPENDENCY_UNAVAILABLE. A symlink on any part of the path now refuses asPERMISSION_DENIED, because a link could be repointed after the check.- Output is no longer colored on a terminal whose
TERMisdumb, unlessFORCE_COLORasks for it,
andFORCE_COLORset tofalsein any letter case, such asFALSE, now turns color off asfalse
does. - A command given
--creds <file>and no--server, such ascotal sendorcotal history, no longer presents that credential to the default addressnats://127.0.0.1:4222. It goes to the broker the--spaceis registered at (mainwhen no space is named), with that registration's TLS requirement. A space not registered here is refused withTARGET_NOT_FOUNDbefore anything is dialed. Before, the registration was ignored and the credential was offered to whatever listened on the default port. COTAL_DEBUG=1now prints a stack trace for a command that fails with a reported error, such as a failedep describe, as docs/config.md says. It prints the failure's stack and then the stack of each error that caused it, so an endpoint call's underlying error, for exampleno describe reply ... within 10000ms, is visible. It used to print nothing for these failures. The JSON envelope is unchanged.backup cleanup-fallbacknow retires the journal of a restore made with--accept-missing-source.
That restore replaced a missing source and keeps no old store, so cleanup refused with an unknown
outcome, every other recovery route refused too, and the root could never start another backup
cycle. Its plan, prompt and receipt now say that nothing is removed, andclean restore-fallback
does the same.- A controller stack no longer stays blocked after a
cotal downexits partway through a member's stop, for example because it was killed or its terminal closed. A manager stop that the deaddownhad not yet handed to the manager is settled by the next bareupordown, and the stop reservation thatdownleft is released once its pid has exited. A reservation held by a live process is refused with that pid named. A broker or delivery daemon that the deaddownleftstoppingis named in the refusal withcotal down --abandon-stop <request-id> --yes, which now takes that stop once the process is gone. Before, everyup,down,--abandon-stopandservice stoprefused. cotal downof a controller stack keeps a selection that names its space. The stopped space stays prepared at its root, so the next command run without--spacerefuses withTARGET_UNAVAILABLEand names the root to start it in, as the docs describe. It used to clear the selection, so that command quietly ran against another running space.- A paired endpoint caller file from
cotal auth mint <name> --endpoint <class> --out <file>now works withep describe,ep invokeandinvokeunder--creds <file>. The mint recorded the endpoint's wire token in<file>.issuance.json, and reading that intent back refused the token, so every such call failed with "the endpoint contract read did not complete" before any request was sent. The intent now keeps the dotted endpoint name. An intent written by an earlier release is refused withSCHEMA_INVALIDand a remedy to issue a new caller. config,config getandstatusnow warn withPREFERENCE_INVALIDwhen a variable such as
COTAL_DETACH_KEY,COTAL_DEFAULT_AGENTorCOTAL_DEFAULT_PERSONAholds a valueconfig set
refuses, naming the variable and the reason. The variable still wins.- A command such as
cotal channelrun in the folder of a forgotten prepared space, with no broker at the default address, now says that no space is registered or prepared for that folder and namescotal space create <name> --root <root>. It used to say that meshmainwas recorded there on 127.0.0.1:4222 and to runcotal upto restart it, though nothing recordedmainthere. - After
space forgeton a stopped controller stack,statusand every command in that root now say
to prepare the space again withcotal space create <space> --root <root>beforecotal up. Before,
they said to runcotal up, which refuses there until the space is prepared again. - A
backup createstopped by Ctrl-C now says what it left and how to clear it, and the claim it leaves no longer locks the cut for 30 minutes. Interrupted after it claims the cut, it exits 130 (143 for SIGTERM, 129 for SIGHUP) and prints its attempt id, the partial artifact andcotal backup recover <attempt> --yes.backup recovernow takes a create'sbackup-<uuid>as well as a restore's id. Once the backup process and the broker it started are proven gone, which takes seconds, it removes the partial artifact, the clone and the broker files, each matched by inode, and returns the cut to ready. A create to another directory does the same first. While the claim is live,up, another create andbackup recoverrefuseLIFECYCLE_BLOCKED(exit 2) and name the backup process and the deadline, whereupused to say "follow the recorded recovery" without naming one and a new create exited 3.cotal statusshows the create's own attempt and its next step. A create naming the partial artifact refusesCONFLICTand names the recovery, andbackup verifyorbackup inspecton a directory with nomanifest.jsoncalls it an incomplete artifact. - A
backup restore(orup --restore) stopped by Ctrl-C no longer locks its root for 30 minutes without saying so. Interrupted inside its pre-commit window, it now exits 130 (143 for SIGTERM, 129 for SIGHUP) and prints its attempt id andcotal backup recover <attempt> --yes. The claim it leaves is stale as soon as its restore process and every broker it started are proven gone on this host, which takes seconds because each broker's watchdog stops it when the restore process exits. So that recovery,clean restore-attempt, or a rerun of the restore rolls it back at once. The 30-minute deadline still applies when a broker cannot be proven gone. While the claim is live,up, a repeated restore andbackup recoverrefuseLIFECYCLE_BLOCKED(exit 2) and name the restore process and the deadline, rather than exiting 3 with an unknown outcome. A restore or recovery that finds the root's maintenance lock held names the pid holding it. The status and receipt hints now say--yes, which the apply needs, andstatusno longer offersclean store --forcewhile a maintenance journal holds the store. - Under a locale whose character set is not UTF-8, such as
LANG=C,POSIXor ISO-8859-1, reports,
refusals and usage errors now write their marks and punctuation in ASCII, as--plaindoes, and
keep their color. With no locale variable set, the marks stay UTF-8. - A managed verb such as
cotal ps,stop,attachorspawnthat cannot choose between several of your live managers now shows the choice. It used to refuse with only the instance ids inside one sentence. It now lists each manager on its own row with its instance id, host, activity (which names its runtime) and the time its process started, and its Next gives your own command again with--on <instanceId>for each one. The code and the exit code stay as they were, and--jsoncarries the rows undererror.cause.detail.managers. cotal auth mint --expires-atrefuses a time that is not in the future, before anything is written. It used to write a credential the broker already refused, under a label that was then taken, anddoctor authreported it as expired.cotal persona --server <address>with an address that no registered space uses no longer turns the current folder into a catalog. Outside a project it listed an empty catalog for a space namedmain, andpersona createwrote.cotal/agents/<name>.mdinto whatever folder you were in. It now refuses withTARGET_NOT_FOUNDand writes nothing. In a project it still reads that folder's catalog, as launch does.@cotal-ai/workspaceexportsisGenuineSpacefor this check.config setandconfig unsetin a preferences folder you cannot write are now refused with
PERMISSION_DENIED, exit 2, naming the folder and how to make it writable. Before, they reported
a rawEACCESas an unknown outcome, exit 3, although nothing had changed.- A
preferences.jsonthat is a symbolic link is now refused, naming its target, as a private
record's link is.config setused to replace the link with a regular file while the file it named
kept the old value. - A selected prepared space whose project folder was deleted now says the folder no longer exists and
namescotal space forget <space>. Before,use,status,psandup --spacesaid to start it
withcotal upin the missing folder,personarefused with no path or remedy, andspace forget
said the files under that folder were kept. - A command that targets the selected space from inside a project whose root has a different
registered space now says so before it acts. It warns on stderr and in its JSONwarningswith
SELECTION_NOT_FOLDER_SPACE, naming both spaces and the--spacethat targets the folder's space.
send,status,who,ps,whoami,endpointsand a nameddown <service>used to act on the
selection there with only aMesh:header to show it. cotal space forgetnow removes the per-space preferences saved under the name it forgets, so a later space with the same name no longer inherits them. The receipt lists each removed key and value with theconfig setthat restores one. The removal takes the preferences file's lock and revision check; when it fails after the record is gone, the run is partial and names theconfig unsetthat finishes it.- When a root's controller stack runs under another
COTAL_HOME,statusandupnow say so, name the
Cotal home in use and tell the operator to run with the one the stack was started under.status
said to start the stack withcotal up, anduprefused its own members as legacy pidfile-owned
processes with no next step. cotal statusin the folder of a forgotten prepared space now namescotal space create <name> --root <root>as the next step. It used to name a barecotal up, which started the default spacemainthere and then blocked preparing the forgotten space in that folder again.- A refusal of a mesh target now names the space and root it concerns under
targetin--json, where they are known. A stopped space'sps,who,whoami,endpointsandhistoryrefusals carried"target": nullalthough the message named the space, so a script had to parse the message to tell which space refused. cotal upnow refuses a project root that other local accounts can write, with the same checkspace createapplies, before it writes anything. In a group-writable folder on a host without a private-group proof, such as one whose/etc/nsswitch.confnamessss,space createrefused butupstarted a legacymainstack, and baredownthen refused "the additive attempt inventory could not be read" with no cause and no next step. The refusal from every command that checks a path now names every unsafe entry in its chain at once, with one remedy such aschmod g-w <project> <parent>. Baredownandstatusalso name that cause for a stack an earlier release started there.- On macOS,
cotal setupandcotal updateno longer write Cotal's entry into Claude Desktop's
claude_desktop_config.jsonwhile Claude Desktop is running. Desktop reads that file when it starts
and writes the copy it holds back on ordinary use, such as switching a Code session or changing the
model, so an entry added while it ran was dropped. Setup now finishes its other steps and reports
that the Desktop entry was not added, with the fix: quit Claude Desktop, then runcotal setup
again from a terminal outside it. Windows is unchanged. - The
cotal historydocs now say that the text and plain reports show a line break or other control character in a message as its\uXXXXescape, and that--jsoncarries the text as it was sent. cotal run validate,planandsimulatenow say what each validator warning is: its code, title, position, cause and fix, on stderr and in the--jsonenvelope'swarnings, andvalidate --jsonlists each one underresult.warningswith the same fields. Before, a warning printed only "The language validator reported a warning at this source position.", so afanOutover a literal list with nokeyvalidated with no visible reason and then failed its run with L3021.- The auth ledger, obligation and manager goal-index scans now run one shared consumer lifecycle: the pre-clean, forced create, bind check, drain and final delete. A manager goal-index scan whose consumer create fails now fails closed as
unavailableand names its stream and filter, as the other two scans do. It used to surface the raw JetStream error. - A
backup createthat recovers an interrupted create's claim first now backs up the cut's recorded
store. On a root whose store is a custom--store-dir, it looked for.cotal/natsinstead, removed
the partial artifact and then failed with an unknown outcome, "store path is missing". backup createwithout--store-dirnow backs up the preserved cut's recorded source store. It used
.cotal/nats, so after a restore into another store it refusedpath-missingalthough the cut was
ready.- A
backup createrefusal or failure now shows its cause as native detail, such as a broker still
listening at the preserved cut's endpoint. Since the command line redesign it printed only a generic
sentence, whilebackup restoreanddown --preserve-statekept naming the same cause. cotal channel memberson an open mesh now refuses withUNSUPPORTED, exit 2, and says why: the delivery service writes the membership feed, and an open mesh runs none. It used to report an unestablished census, exit 4, and tell you to check a delivery service the mesh does not run. The remedy namescotal whofor who is present.cotal channeland its subcommands now report a target they cannot use, such as a space that is prepared but not started, the wayps,whoandhistorydo: a refusal under its own code, such asTARGET_UNAVAILABLE, in one JSON envelope with--json, exit 2, and the remedy under Next. They printed one line and exited 1, the usage exit, with no JSON./helpinchatlists the slash commands. It used to be refused as an unknown command./leaveof a channel the chat session never joined now answersnot on #<channel>. It used to say
still on #<channel>, as if the session were reading that channel./historyinchatworks on an open mesh. It reads the channel the waycotal historydoes,
because an open mesh runs no delivery daemon. It used to fail with "no responders" and a raw
delivery subject./quitinchat --plain's numbered recipient list leaves the session. It used to print the list
again, so only the end of input left.- In
cotal chat --plain,:to <target>now sets the recipient.:to eveor:to @evebecame a draft to the current channel, and answeringywould have posted that text.:to @eve,:to #<channel>,:to @<owner>.<actor>and:to role:<role>now choose that recipient, and a target in another form, such as a bare name, names the forms it accepts and opens the numbered list. The docs now describe:to,:multiand the other line commands. /reconnectinchatreports the outage it causes once. It used to print the gap line twice.chatnow shows when a static or signed-in session ends: the banner gives the time, and a notice a
minute before says the session is ending. In--plain,/quitleaves a session that has already
ended. It used to be answered with "end input (Ctrl-D) to leave".- A static
chatsession can/joina channel again after/leave. Leaving used to empty the
session's read list, so/joinrefused with a message saying the session was opened on no channel. cotal chat --stdionow answers every line in the order it arrived, as the docs say. A request refused at validation, such as an unknowntype, a line that is not JSON or one too long to read, was answered at once, ahead of the requests queued before it. A request reusing the id of one still queued was refused under that id before the first request's result, so a client matching by id read the first request as refused. Each refusal now waits its turn, so the first request's result comes before the duplicate's refusal, and the refusal says the id was still in flight when the request arrived.cotal completion installwith a symlinked startup file, such as a~/.bashrcthat stow links into a dotfiles folder, now names the file, says it is a symlink and gives the line to add to the file it points to. It used to refuse with "could not be opened safely" and no path or next step. Barecotal completionalso warns that the file is a symlink.cotal completion installanduninstallnow name the directory they refuse and why. The refusal said only that completion paths "cross a symlink, untrusted owner or writable ancestor", with no path and no next step. It now names the directory and whether it is a symlink, not a directory, owned by another user, or writable by group or others. For a writable one, Next giveschmod go-w <path>. Otherwise Next says to wire completion yourself fromcotal completion script <shell>.cotal completion installwith a group-writable~/.bashrcor~/.zshrc, as a umask of 0002 leaves
one, now names the file, its mode and thechmod go-wthat fixes it. It used to refuse with "shell
integration needs an owned, bounded regular file" and no path or next step. One refusal names every
unsafe startup file, stub and folder on the way to them, and nothing is created before it.cotal config set personanow refuses a value that can name neither a persona nor a persona file. It saved anything that was not empty, such as" spaced "or a name holding a terminal escape. A name must use letters, digits,_and-, aspersona createrequires, and a path must hold no control characters or surrounding spaces. Whether the persona exists is still checked when a launch uses it. Theno harnessrefusals ofconfig set harnessandsetup --harnessnow escape the value as the other keys do, so a control character in it no longer reaches the terminal.cotal console --plainandcotal chat --plainnow write their lines in ASCII, as every plain report does: no status marks, and·,…and→as-,...and->. They drew the marks and punctuation under any locale. The console line stream that a pipe gets does the same under a locale that is not UTF-8, asLC_ALL, thenLC_CTYPE, thenLANGdecide.cotal consolenow shows a control character in a peer's message body, name or other text as its\uXXXXescape, in both--plainand the TUI, ashistoryandwatch --plainalready did.console --plainwrote a body to the terminal as it arrived, so a message could clear the screen, set the terminal title, plant an OSC 8 hyperlink, ring the bell or overwrite its own line with a carriage return. The TUI passed the title and hyperlink sequences through too. The TUI still shows a body's line breaks.- A controller-stack
upnow says when another space stays selected, with the same line a legacyup
prints:"<space>" up; current is still "<selected>" - cotal use <space> to switch. - A copied or moved controller project folder no longer gets an "unsupported shape" refusal. The stack journal records the absolute folder it was written in, and in a
cp -acopy or a renamed folder it named the old path, soup(and every other command that reads the journal) refusedSCHEMA_INVALIDwith a literal<root>in its Next. A well-formed journal that names another folder now refusesCONFLICT(exit 2) and names that folder: a copy is told to use the stack from the original, and a moved folder to move back there first. A malformed journal still refusesSCHEMA_INVALID, and its Next now names the journal's real path.space createin a copy whose records name another root as their space's root says that the records were copied or moved from there, rather than calling the copy that space's root whilestatusnames the original. statusno longer suggests the deprecatedcotal deliverwhen the delivery responder is not bound;
it namescotal up. The delivery daemon's missing-credential error namedcotal go, which does not
exist, and now says thatcotal upmints and writes the credential.cotal doctor authnames a credential file that other accounts can open by its mode, withchmod 600on the file as the repair, instead of reporting a parse failure. It also parses each file the way--credsdoes, so a file the NATS client cannot parse, such as one with Windows (CRLF) line endings, is a finding rather than healthy.cotal downnow runs to its end when the reader of its output goes away, ascotal down --yes | head -1does. It used to exit 0 at its next write, partway through a member's stop, and the nextdownoruprefusedLIFECYCLE_BLOCKEDbecause that stop was never confirmed.cotal downrun in a folder that holds no stack, while--spaceor the selected space names a controller stack rooted in another folder, now refuses withTARGET_NOT_FOUNDand names that space and its root, with a next step that runs the samedownfrom there. It used to blame--yesor--space, or say there was no stack, and point at adownthat stopped nothing.- A bare
cotal down --space <name>orcotal down --dry-run --space <name>run outside the folder where that space's stack is recorded now refuses the same way for both,TARGET_NOT_FOUND(exit 2), naming that folder and the stop or preview to run there. The stop used to exit 1 with a usage error and the whole help, and neither named the folder. - A repeated
cotal down --with-agents --yesnow reports that the stack is not running and exits 0 when the stack's last completeddownalready took--with-agents, and so does--with-agentson a root that never started a stack. It used to refuse withLIFECYCLE_BLOCKEDand ask to start the stack first. After adownwithout--with-agentsit still refuses, because agents that run outside the manager may remain. cotal down --yesnow works on every root. On a root without a controller stack, such as a prepared space that never started or a stackup -fstarted, it gives the same answer ascotal down: it stops the running stack, or reports that the stack is not running. It used to refuse withUNSUPPORTEDbecause--yesapplied only to a controller stack.auth mint --endpoint,ep describeandep invokenow say when no instance of the endpoint
answered its contract read: they refuse withTARGET_UNAVAILABLE, exit 2, and their Next says to
start the service that serves it. They used to fail with "the endpoint contract read did not
complete", exit 5, and no Next.cotal endpointsnow shows each participant's name and role in separate columns, ascotal whodoes. The Participant column held<name>/<role>, such asmanager/manager, and following the Next line with that value,cotal endpoint describe manager/manager, was refused. The Participant column now holds the nameendpoint describetakes. The--jsonrows are unchanged.cotal ext removecreates a missing process-record folder under a mesh root's.cotalwith mode
0700. Under a group-writable umask, the reservation it takes for a provider whose records sit in a
folder of their own left that folder 0775.- A first
cotal upthat launched nothing no longer wedges its folder. When another NATS broker already answered at the address, the refusedupleft a stack journal that every later command treated as a stack that once ran:up,up --server <other>,reset spaceanddown --yesall refusedLIFECYCLE_BLOCKED. A journal whose members never launched now pins nothing. The nextupplans a first start, at the same address once it is free or at another--server, anddownreports that nothing runs there and exits 0.reset spaceandreset storerefuseTARGET_NOT_FOUNDand namecotal up, because no broker wrote a store. - A foreground
cotal team bootthat another command'scotal downreleases now exits 0, as a foregroundcotal updoes. It used to keep the exit code 4 of its partial start receipt, so a script waiting on it read a normal stop as a failure. - Ctrl-C or SIGTERM on a foreground
cotal upwhile the controller stack is still starting now stops
what was started. The start settles first, then the same guarded whole-stack stop as bare
cotal downruns, and the command exits 130 or 143. Before, the interrupt ended the command at
once with no output and left the broker, delivery and manager running. space forgetnow removes the stopped stack definitionscotal upsaved for the space at its root, so another folder can start a stack under the same name. When a member was not stopped cleanly, the definitions are kept and the result warns withSTACK_DEFINITIONS_KEPT, naming the route that releases them. A bareupthat meets another folder's stack definitions names that folder and every definition saved for it.- A controller
upwhose broker address another broker already answers now says where to go next. The refusal said only "something already answers at the stack's broker address", without the address, and its next step was to inspect the stack's members. It now names the address and any space this machine registered there. For a first start, the next step iscotal up --serverat another port, or stopping that space and runningcotal upagain. A stack that ran keeps its recorded address, so the next step is stopping that space, then a barecotal up. - Bare
cotalnow offerscotal upfirst for a prepared space that has not started. Before, it offered
cotal spawnfor a selected prepared space, which refuses until the space runs. With nothing
selected, it also names the space prepared in the current folder, including right afterdown
there, rather than saying no space is selected and offeringcotal setup. With no agent application,
statusNext no longer says setup prepares a space when one is running or prepared, and on a
running space it still listspsanduse. join --credsor--tokenandfeedback-intake --credswith no--servernow dial the broker the
space is registered at, keeping its TLS requirement, and refuse an unregistered space before any
dial. Before, they sent the credential to the default address.- On a running legacy root, the
reset storeandreset spaceverdicts now namecotal downand then
cotal clean store --forceorcotal clean all --forcefrom that root. Before, they named the reset
again afterdown, which refuses because a legacydowndrops the registration a reset selects. - A bare
cotal upon a stopped legacy root now starts it at the address its last start used, and in
open mode when that start was open, and says so. Before, a legacydowndropped the registration
that kept the address, so the next bareupstarted on 127.0.0.1:4222 in static auth. After
clean allon a root deployed withup -f, a bareupthat would start another address, mode or
space refuses and namescotal up -f <manifest>. - A legacy project folder that was renamed or moved after
up -fno longer leaves a stack that nodownstops. The launch summary thatup -fwrites records its absolute folder, and after a move it named the old path, so it was counted as an unreadable record.statusthen exited 4,team show <id>refusedSCHEMA_INVALID, and baredownrefusedLIFECYCLE_BLOCKEDwhile the broker kept running. A summary that reads cleanly but names another folder is now listed bystatuswith the folder it was recorded at, blocks noupordownin the new folder, andteam show <id>refuses it withCONFLICT, naming that folder. A summary that does not parse is still reported unreadable and still blocks. A copy of a running folder still refusesdown, so the original keeps running. - A bare
cotal upin a prepared project folder that was moved no longer starts the legacy launcher under the default "main" space. A prepared space records its folder by path, so after the move nothing in the folder names it, andupfell back to the default. When the selected space is prepared in a folder that no longer exists and the current folder names no space,upnow refusesTARGET_UNAVAILABLE(exit 2) with nothing written, and says to forget the space and prepare it again in its new folder, or to pass--spaceto start another space there.space create <name>for a space whose recorded folder no longer exists now says so and says to forget it first, rather than "start it withcotal upthere". - A project folder moved after
down --preserve-stateno longer blocks resume and restore with an unknown outcome and no path. The maintenance journal records the preserved store by absolute path. When that path is gone and the folder's own.cotal/natsis the same store by device, inode and generation,up,backup createandbackup restore(with or without--accept-missing-source) now refuseCONFLICT(exit 2), naming the folder it was moved from, and say to move it back there first.statusgives the same next step instead of "resume with bare cotal up". A store that is missing from its recorded path is refusedTARGET_NOT_FOUNDwith that path, rather than exiting 3, andbackup restore --accept-missing-sourcestill restores over a lost store. Previouslyupexited 3 with "store path is missing", restore exited 3 with "preserved source is missing", the flag gave "store path must be absent", andbackup createblamed a--store-dirthat was never passed. cotal persona editkeeps your edited draft when it refuses withCONFLICTbecause the persona changed or was removed while the editor was open. It deleted the draft, so the edit was lost. The refusal now names the draft and how to use it, and a persona file that is gone is reported as removed.cotal persona editwith an editor the shell cannot find or run now refuses withDEPENDENCY_UNAVAILABLE, exit 2, as it does when no editor is set. It failed withOPERATION_FAILED, exit 5, and "editor exited with status 127", with no next step. The refusal names the editor and says to setVISUALorEDITORto an installed editor or runcotal config set editor vim. It also notes that the value runs as a shell command, so a path that holds spaces needs quotes. An editor that runs and exits non-zero still fails as before.persona create <name> --file <path>with a file that names another persona now says which name the
file has and points tocotal persona create <file's name> --file <path>. Before, it said only that the
name must match the catalog name.cotal persona create --harness <name>refuses a harness this machine does not know, withUSAGE_INVALIDand the list of known harnesses, the same checkcotal config set harnessmakes. It used to save the pin as given, so a typo surfaced only when the persona was spawned.- A refused persona name now says which rule it broke. A name over 128 characters said only
invalid persona name; it now gives the limit and the name's length, and the character rule names the limit too. cotal personawith no space selected, outside a project, now says that no space is selected and this folder is not a space's project. It saidno mesh running, though the catalog is read offline and needs no running mesh. Commands that read the same catalog, such ascotal team, give the same message.cotal persona create,editandremove, and the legacypersonas new,editandrm, refuse a persona catalog folder you made read-only, such as.cotal/agentsset to mode 0500, withPERMISSION_DENIED, and leave the folder and its files as they were. They used to set the folder back to 0700 and write into it without a word.editrefuses before the editor opens.cotal personas listprints the YAML diagnostic of an unparseable persona as lines: the reason, then the excerpt with a caret under the fault. It printed them on one line, with each line break shown as\u000a.personas listandpersonas list -vnow escape control characters in the persona names, roles,
models, descriptions and bodies they print, aspersona showdoes.persona createand
personas newrefuse a--roleor--modelthat holds control characters.personas showon a terminal now escapes control characters in the persona file, aspersona show
does. Piped or redirected, it still writes the file byte for byte.cotal help personas <subcommand>andcotal personas <subcommand> --helpprint that subcommand's usage forlist,show,edit,newandrm.help personas newandhelp personas listsaid the subcommand did not exist, althoughpersonasruns them.down --preserve-statenow cuts the store the mesh record names, such as a
backup restore --store-dirtarget. It used.cotal/natsunless--store-dirwas passed, so after
a restore into another store it stopped the delivery daemon, left a mismatched prepare intent and the
manager fenced, and exited with an unknown outcome. A--store-dirthat differs from the recorded
store now refusesCONFLICTbefore anything stops.- A re-entry
upthat adopts the surviving listener of an interrupted restore or resume now
recreates the space's memory-only buckets before the daemons start. When the firstupstopped
after binding its listener and before it made them, the delivery daemon exited with "stream not
found" and the re-entry reported a degraded control plane. space addandmeshes addagainst a broker that requires auth, with a--rootthat holds another
space's trust, now say that the folder holds the trust for that space and refuse with
DEPENDENCY_UNAVAILABLE. Before, they said to copy the mesh's credentials there and re-run with
--mode auth. The note on what such a copy carries is now part of the same line, sospace addno
longer prints a literal\u000ain its message.- A command run from a folder that no longer exists is refused with
USAGE_INVALID, exit 1, before
it does anything, and says tocdto an existing folder.statusand other commands used to
report an unknown outcome, exit 3, with Node's rawuv_cwderror.help,versionand
completionstill work there. - After a crash on a controller stack,
cotal down --yesnow leaves a root thatcotal reset store,cotal reset spaceandcotal clean store|all --forceaccept. Thatdownfinds every member gone and records it as already stopped, and these commands, which remove the store, now count such a member as stopped. They used to refuse it for having no stop receipt and name adownthat changed nothing, so the only way out was a fullupand anotherdown. A reset refused before thatdownnow namescotal down --yes. Backup still requires each member's own stop receipt. backup restoreandup --restorewith an artifact of another space now refuseCONFLICT(exit 2)
and name both spaces. They reported an unknown outcome (exit 3) and sent the operator to check a
restore attempt that never existed, although the check runs before the preserved cut changes.backup restoreandup --restorewith the preserved source missing and no
--accept-missing-sourcenow refuseTARGET_NOT_FOUND(exit 2), naming the missing path and the
--accept-missing-sourceroute. They reported an unknown outcome (exit 3) with a Next about a
restore attempt that did not exist, although nothing was changed.cotal backup restore <dir> --store-dir <path>andcotal up --restore <dir> --store-dir <path>refuse a store path that already exists, or whose parent directory is missing, asUSAGE_INVALID(exit 1), naming the path, before anything is staged. Both used to exit 3 withOPERATION_UNKNOWN, "store path must be absent" or a rawENOENT, and a Next that said the journal names no restore attempt.cotal sendrefuses a lone-and names--stdinand--file -, which read stdin. It used to
publish the text "-". An empty quoted body is refused as empty. It used to be told to choose one
body. These refusals now suggest the destination you gave.cotal send --file -now reads the message from stdin, as--stdindoes and aspersona create --from -already did. It was refused withTARGET_NOT_FOUNDand "the explicit message input could not be read". A message file that cannot be read is now refused with its path, in the message and in the--jsontarget.cotal sendnow says what to do when its target or a mention cannot be resolved. A name that several participants share is refused with the full identities that carry it, and one that is not present points atcotal who --all. Both reacherror.remedyand the human "Next" line.cotal space add --mode opennow checks that the broker serves a space by that name, as its streams show. A name it does not serve, such as a typo, is refused withTARGET_NOT_FOUNDnaming the spaces it does serve. Before, an open broker's answer was enough, so the mistyped space was recorded as healthy and every later operation on it failed without naming the name.cotal space addoutside a project with no--rootnow refuses with one line and a remedy that says what to pass, instead of printing the wholespacehelp. The line no longer describes the folder only in terms of credentials: an open space keeps none there, so any folder will do.space addof a space this machine started, or one a catalog lists, now says so in its refusal
and points tocotal space show <name>. Before, the refusal offered--force, which is then
refused for that space.space createrefuses a name thatcotal upcannot start, such as one with a space, a dot or a
non-ASCII letter, withUSAGE_INVALIDbefore anything is written. Such a name used to be prepared,
and the firstupin its folder then refused it and left the folder held by that name.cotal space create <name> --root <dir>on a folder this account cannot write, such as/, now refusesPERMISSION_DENIED(exit 2) with " is not writable by this account, so its .cotal could not be made; nothing was written" and a next step, rather than exiting 3 with an unknown outcome and a rawEACCESerror. Nothing is written in either case.cotal setupprepares a space through the same step and refuses the same way.- The
space deletereceipt now has a Next that names the route back: on a space this machine started,cotal down --yes(orcotal downon a legacy root) and thencotal upstart it again with empty streams, orspace forgetafter that stop drops it. A barecotal upunder a broker whose streams were deleted now refusesTARGET_NOT_FOUND(exit 2), saying the broker no longer holds the space's streams and naming that restart, instead of exiting 3 with a raw JetStream code 10059. space deleteof a stopped stack now namescotal reset store <space> --yes, which removes its stored
streams, andspace forget. Before, it said to start the space and delete it then, which meets the
running-stack refusal again. A prepared space that never ran names onlyspace forget. The
--dry-runwarning on a running stack no longer says that no broker was contacted.- When a controller stack's manager or delivery daemon has died,
statusnow leads its Next with
cotal up --root <root>, which restarts it.psagainst a stack whose manager died refuses with
"no manager is running for this space" and names barecotal up, as it does for a stack started
with--no-manager. It used to end with "no manager reachable on the ep rails" and no Next. - A controller stack's first
uprecords its broker address, and anup --servernaming another address refusesCONFLICT. That refusal said only "this root's stack journal names another space or broker address; it was not rebound", with no address and no next step, after adown, after a successful run, and afterspace forgetandspace create. It now names the recorded address and the address thisupasked for, and its next step is the barecotal upthat starts the stack at the recorded address. A different--store-dir,--max-file-storeor--hostnames each input it changes with its recorded value, and gives the same next step.up --helpand docs/cli.md no longer call--serveran override: a stack's firstuprecords it. status --componentsno longer fails a healthy stack because the optional web dashboard is not
installed. With no dashboard extension and no dashboard process record, the web row reads
not-installed · optionaland exits0. An installed dashboard that is not running still reads
absentand exits1.cotal statusno longer reports the delivery responder as bound right after the delivery daemon is killed. The dead daemon's ready row stays in the lease for up to 30 seconds and names this folder's own credential, so status read it as healthy while the same report showed the delivery process gone. When this folder's delivery pidfile names a process that has exited, status now reports that row asstale: not bound, a dead daemon's record that expires on its own.cotal statuson an open mesh no longer reports the delivery responder asNOT BOUNDwith acotal upremedy. Open mode runs no delivery daemon, so when no delivery lease is recorded the row says the responder is not used, andstatus --jsongivesdeliveryResponder: null. A lease that is recorded is still reported as before.- A command whose output cannot be written no longer crashes with a Node stack trace.
cotal version >/dev/fullprinted a 21-line trace and exited 1, and every command did the same on a failed stdout write. It now prints one line,could not write to stdout, with the reason, and exits 3, or with the non-zero status it had already set. A--jsonrefusal whose envelope cannot be written keeps its own exit code, and a reader that closes the output early, as| headdoes, stays quiet there too. up,up --restoreandbackup restorerefuse a--store-dirthat is a control directory or lies
in one, such as a root's.cotalor the machine home's.cotal/nats, unless it is the root's own
.cotal/nats. Before, the broker wrote its store among the root's credentials and records, or into
the home stack's store, andreset storethen could not inspect it or offered to remove the home
store.- The existing part of an explicit
--store-dirpath oncotal up,cotal up --restoreorcotal backup restoreis now held to the rulespace createapplies to a project root. A group- or world-writable directory in it refusesPERMISSION_DENIEDbefore anything is written, naming the path and itschmodremedy.upused to start the stack on such a store and leave the directory writable by other local accounts. A non-empty directory is still accepted. - An explicit
--store-dironcotal up,cotal up --restoreorcotal backup restorethat names a regular file, or a path under one, now refusesUSAGE_INVALID(exit 1) before anything is written, naming the file as not a directory.upused to exit 3 withOPERATION_UNKNOWN"the controller stack did not start every planned member" without naming the path, after writing its stack journal, and the restores exited 3 with "store path must be absent". team bootof a manifest that requests no agents now returnsokwith a complete receipt. Before, it
returnedpartialwith exit 4 because agent joins were not checked, though there were none. The docs
now give the reason everyteam remove --dry-runispartial.team check,team bootandteam deploy --dry-runnow name each manifest problem by file, line,
key and reason, and--jsonlists them undertarget.issues. An alias bomb is reported as a
manifest problem. Before, every mistake gave one identical sentence.team deploynow names each manifest problem by file, line, key and reason, asteam checkand
team bootdo. Before, it refused every mistake with one sentence.cotal telemetry <level>no longer creates or rewrites the settings file while the environment forces telemetry off. UnderDO_NOT_TRACK,COTAL_TELEMETRY_DISABLEDorCOTAL_TELEMETRY=offit created~/.cotal/telemetry.jsonwith an install id, though the docs say the file is neither read nor created then, and it reportedokon every repeat while the receipt showed no install id. It now saves nothing and reports ano-opwith anENVIRONMENT_OPT_OUTwarning that names the variable to unset.cotal up --channels <path>with a file that is missing, unreadable or not JSON now refusesUSAGE_INVALID(exit 1) and names the file, before anything is written. It used to exit 3 withOPERATION_UNKNOWNand no path, after writing the stack journal and creating the broker store directory, and every laterupin that folder then refused.cotal up -f <file>run again in the folder whose mesh for that manifest's space is already running now says the mesh is already running and namescotal team deploy <file>andcotal downbefore anotherup -f. It used to report the folder's own broker as one that a fresh boot will not adopt and advise selecting another address.up -fon a root that holds a stack preserved bydown --preserve-statenow refuses
LIFECYCLE_BLOCKED(exit 2) and names barecotal up, which resumes it. It reported an unknown
outcome (exit 3) with no next step, although nothing had started.cotal uprefuses a URL or port-bearing--host, or one naming another address than--server,
before anything is written. On a prepared root the controller stack used to take such a value, exit
3, and leave a stack journal and trust behind, so every lateruprefused withLIFECYCLE_BLOCKED.- A
cotal upthat loses the root to another whole-stack or maintenance command now refuses withLIFECYCLE_BLOCKED, names that command's pid, and gives a next step, ascotal downalready did. It used to exit 3 with an unconfirmed outcome and a bare "maintenance lock is held by a live owner". - A start that names no store no longer opens an empty second store over data
downstopped
tracking. Afterbackup restore --store-dirand a plaindown,upandup -fopened a new
.cotal/natsand left the restored messages unused in the other store, with no warning. They now
refuseCONFLICTand name the store the last broker served, andup -faccepts--store-dirto
reopen it. cotal up --space <name> --server <url>from another folder, where<url>is the address that space already runs on, now refusesCONFLICT(exit 2), saying the space runs there and naming the folder it was started in, instead of blaming "a broker not started here" and suggesting a free port that would start a second broker.- The receipt of a controller
cotal upnow offerscotal spawn --space <space>.upkeeps an existing selection, so after starting a second space the barecotal spawnit used to offer launched into the other, still-selected space. upnow refuses atls://broker address, from--serveror a manifest'sbroker.servers, when no
TLS certificate and key are configured. It used to start a plaintext broker and record thetls://
address, which every later dial reads as required TLS. The refusal isUSAGE_INVALIDbefore anything
starts, and it names--tls-certand--tls-key, or anats://address.cotal watch --jsonnow names the resolved space and the refusal's own reason in itserrorrecord, as the plain form does.watch --json --view dmson an authenticated space used to report onlyrequestedSpace: nulland a generic sentence. The plain refusal also begins with itsMesh:line.- A manager now reports the machine it runs on in its presence, as an agent does, so
cotal who --allfills the Host column formanager/managerrows. An owner with several managers can tell them apart by it. cotal manager reconcileon a healthy stack now says the gate is open and there is nothing to repair,
and its Next says nothing needs doing unless you meant another gate. It used to point at the delivery
liveness oracle. A missing gate reads the same way. The code and exit code are unchanged.- The manager's teardown target now declares the admitted credential renewal that the static terminal drains before it begins. The free, stop and shutdown paths build that target from the agent's fields, so the agent's handle, seed and restart state no longer ride into the detached teardown. Retirement still drains an accepted renewal before it revokes and cleans up the credential family.
cotal run checkno longer tells you to pass--discard-approvalswhen an edit drops an answered checkpoint, an override thatrun checkrefuses because it decides what a migration commit does. The L5004 reason now says to keep the checkpoint in the edited program, or to discard the decision in a migration commit under the--discard-approvalsoverride.- A hosted
cotal runverb the manager refuses now reports the outcome and code as other commands do, with--jsonas one refusal envelope.run showof a run the manager holds no record of isTARGET_NOT_FOUND, exit 2, and namescotal run ps. It used to printrun status: …on stderr, write nothing on stdout and exit 1. - A workflow program file that cannot be read is now refused with
TARGET_NOT_FOUND, exit 2, bycotal run start,run start --local,run resume --local --fileandrun check, asrun validate,planandsimulatealready refused it.run startandresumereported the rawENOENTas an unknown outcome with exit 3, andrun checkprinted it as a bare error with exit 1. The--jsonenvelope now names the file as its target. - The
cotal run answerandcotal run amendreceipts now name the run in their Next line and their JSON target. They printedcotal r show <run-id>although the command had named the run. cotal run showandrun journallist a pause underNeeds an answeronly while its last journal row is pending, and offeransweras a next step only when one is open. A checkpoint that was answered or expired kept its earlier pending rows in the journal, so completed and failed runs still listed it as needing an answer. A pause settled with an accepted answer now prints that answer's value, answerer, artifact when present, time and answer id, then oneamendedline per later amendment, as the docs describe; before, only--jsoncarried them.cotal run simulaterefuses a results script whose scripted checkpoint result is not onecheckpoint()can return, withSCHEMA_INVALID(exit 1) naming the checkpoint and the problem. A status other thanresolvedorexpired, a missing status, a non-object entry, an unknown field or a scriptedatused to simulate as a resolved answer and reportok, and a scriptedatwas dropped without a word.cotal run validate,run planandrun simulatelist every problem in a program that does not validate: its code, title, position, cause and fix, as a hostedrun startrefusal lists them. They used to print onlythe workflow is not valid cotal-langwith no problem at all, and--jsoncarried only each problem's code and span. The JSON envelope now carries the full problems underresult.problems, beside the unchangedSCHEMA_INVALIDrefusal.- A workflow
waitgiven a value that is not an event, such aswait("never-sent"), now fails its step with a message that names the four event constructors and the value it was given. Before, the hosted and local runtime failed it with the host's ownCannot read properties of undefined (reading 'split').
What's Changed
- fix(cli): stop an interrupted foreground start and restart legacy roots where they ran by @davidfarah2003 in #4447
- fix(workspace): let a paired endpoint caller call, and refuse symlinked adapter paths plainly by @davidfarah2003 in #4449
- fix(runtime): list validation problems, settle pauses in run show, and refuse at once with no manager by @davidfarah2003 in #4450
- fix(cli): refuse cross-space restores and up -f over a preserved stack, and name another COTAL_HOME by @davidfarah2003 in #4451
- fix(cli): answer chat --stdio in arrival order, set :to targets, and refuse unreadable run programs by @davidfarah2003 in #4452
- fix(workspace): never present --creds to the default broker address, and refuse read-only persona catalogs by @davidfarah2003 in #4453
- fix(cli): escape peer text in the console, and give send refusals a next step by @davidfarah2003 in #4454
- fix(cli): point down, up and status at the right space and root by @davidfarah2003 in #4455
- fix(core): read CRLF and BOM persona files, and name a symlinked shell startup file by @davidfarah2003 in #4456
- fix(cli): point status and the home screen the right way for prepared and dashboard-less spaces by @davidfarah2003 in #4457
- fix(cli): refuse unusable persona preferences and name the input behind five refusals by @davidfarah2003 in #4458
- fix(cli): give space add refusals a next step that works by @davidfarah2003 in #4459
- fix(cli): keep credentials off the default address and name manifest and preference problems by @davidfarah2003 in #4460
- fix(cli): recover a folder whose first up launched nothing and refuse a bad channels file early by @davidfarah2003 in #4461
- fix(cli): refuse unsafe roots and store dirs before up writes, and name recorded and busy addresses by @davidfarah2003 in #4462
- fix(cli): refuse tls:// served in plaintext and unstick restore, re-entry and custom-store starts by @davidfarah2003 in #4465
- fix(cli): escape personas show on a terminal, name team deploy problems, warn on invalid preference variables and respect TERM=dumb by @davidfarah2003 in #4466
- fix(cli): unblock stacks after a crash, a killed down or a lost output pipe, and settle repeated down and up -f by @davidfarah2003 in #4473
- fix(cli): resume observers after an outage, report a silent broker as not answering, and report a failed stdout write in one line by @davidfarah2003 in #4474
- fix(cli): say what an interrupted restore or backup create left, and release it once its processes are gone by @davidfarah2003 in #4475
- fix(lang)!: export the validator's name and node tables as frozen arrays and freeze CATALOG by @davidfarah2003 in #4476
- fix(cli): refuse a past mint expiry, classify hosted run refusals, and name the run and space in receipts and watch errors by @davidfarah2003 in #4477
- fix(cli): list the live managers when a managed verb cannot choose one by @davidfarah2003 in #4478
- fix(cli): preserve and back up the store the mesh record names by @davidfarah2003 in #4479
- fix(cli): check --creds files before dialing and name the target in refusals by @davidfarah2003 in #4481
- fix(manager): declare the admitted renewal on the teardown target by @davidfarah2003 in #4480
- fix(core): report a late channel subscription refusal once by @davidfarah2003 in #4482
- fix(auth): run the three sealed scans through one consumer lifecycle by @davidfarah2003 in #4483
- fix(cli): name the kept selection, the stopped-space delete route and a persona file's own name by @davidfarah2003 in #4484
- fix(cli): name every unsafe adapter path in one refusal and refuse a control-directory store by @davidfarah2003 in #4485
- fix(cli): refuse unstartable space names, removed folders and a bad up --host, and name a selection that is not the folder's by @davidfarah2003 in #4486
- fix(cli): remove a forgotten space's preferences and give channel refusals their own code by @davidfarah2003 in #4487
- fix(cli): let a static chat session rejoin a channel it left and report one outage per reconnect by @davidfarah2003 in #4488
- fix(runtime): show each failed step's recorded error and say what validator warnings mean by @davidfarah2003 in #4489
- fix(cli): make persona, plain chat output and telemetry opt-out say what happened by @davidfarah2003 in #4494
- fix(cli): name the original folder of a moved or copied project, and refuse an unwritable space root by @davidfarah2003 in #4495
- test(workspace): classify the presence-render census drift on main by @davidfarah2003 in #4496
- fix(cli): back up the recorded store on a recovered claim and name unsafe completion files by @davidfarah2003 in #4497
- fix(workspace): refuse a linked preferences file, naming its target by @davidfarah2003 in #4498
- fix(cli): say what reconcile, who, contract reads, chat sessions, send bodies and dead members found by @davidfarah2003 in #4499
- fix(cli): name the way out of a forgotten, deleted, moved or elsewhere-running space by @davidfarah2003 in #4501
- fix(connector-claude-code): skip the Claude Desktop entry while Desktop runs on macOS by @davidfarah2003 in #4500
- chore(release): version packages by @davidfarah2003 in #4448
Full Changelog: v0.99.3...v0.100.0