Skip to content

v0.65.0

Choose a tag to compare

@davidfarah2003 davidfarah2003 released this 05 Oct 09:37
281f31d

Cotal 0.65.0 is out!

Grab it: npm i cotal-ai@0.65.0

Changes in this release

  • A platform composition that hosts the auth context can now get the host incarnation that a delegated user intent pins as its executor (SPEC 13.16). PlatformControlInput takes an optional host: { endpoint, clusterDigest, artifacts }: the reverse-DNS endpoint of the host process that runs the intent flights, the closure digest of its §13.7 cluster and every contract artifact that closure needs. The auth plane self-authorizes that one name, and a single-label name is refused at start. With it, AuthServiceHandle gains registerHostIncarnation(instanceId), which publishes the artifacts, registers that instance through the same ceremony the auth plane runs for its own auth endpoint, and returns { instanceId, processEpoch } with the epoch that registration committed, observeHostGate(instanceId), the sweeper's point-in-time read of that endpoint's issuance gate over the context's own connection, and awaitHostFence(instanceId, processEpoch), which resolves with the gate once it is no longer open at that epoch, or with null once it is absent. A host that registers its persisted instance id at every start fences its predecessor and advances the process epoch, so a restarted host reads its earlier incarnation as gone. A start whose confirming read of the gate finds that a later start of the same instance registered is refused with conflict. The returned epoch stays current only until the next start registers, which can happen before the call returns, so the host arms awaitHostFence with it before it admits or recovers any flight and stops serving when the hook resolves. docs/embedding.md and docs/design/delegated-user-launch-intent.md describe all three members. Core's KV issuance gate adapters (serveIssuanceGateKv, provisionEndpointGateOpen, endpointRegistrationBarrier and readEndpointGateGeneration) tokenized the endpoint name twice, and the registration repair cursor rebuilt its key from the token, so every reverse-DNS endpoint threw before it reached its gate or partway through its registration. They now carry the name and leave the key builders to tokenize it. Single-label endpoints are unchanged. Core's registerServiceInstance also returns the processEpoch its completing reopen committed, so a caller no longer reads the epoch back from a gate that a later registration may already have advanced; the auth plane's own registration takes it from there.
  • A presence observer now drops a bucket record whose card.name or status is missing or has the wrong type, or whose ts is not a finite number, counts it in presenceBindingDropCount and reports it on the warning event. Before, a record whose ts was missing or text such as "nope" stayed live in the observer's roster after its key expired, because the staleness checks compared NaN with the liveness window. A record with a non-string card.name or a null value threw inside the presence watch loop, which ended the watch and surfaced as an error event.
  • A manager start now serves at the process epoch its own registration committed. Both the boot registration and registerRemoteManagerAuthority read the issuance gate again after registerServiceInstance returned and took that read's epoch, so a start that a second start of the same instance superseded in between authorized its serve grant at the successor's epoch, and the remote path returned the successor's epoch and registration revision as its own. registerServiceInstance now returns the processEpoch its completing reopen committed, both registration paths use it, and the serve grant's epoch check refuses a superseded start with expired.
  • A caller now validates a request's args in the form they are sent, so a key whose value is undefined, which JSON drops, no longer fails a responder's closed input contract. A cotal spawn --detach from a current CLI was refused by every manager released before defaultAgent existed, even with COTAL_DEFAULT_AGENT unset. A caller-side args refusal is now marked not-executed, since nothing was published. Args that JSON cannot carry, such as a BigInt, get the same bad-request refusal. A refusal that names a key the responder's contract does not declare carries an ai.cotal.ep.undeclared-arg detail. The CLI reads that detail on manager commands and reports version skew with its own version, where it used to print a bare schema error followed by a warning that the request may have run.
  • Add once, an at-most-once scope for cotal-lang steps that write to a far side. A resume that finds a step inside once begun and never settled does not dispatch it again: it opens a hold, a checkpoint minted under holdRequestId of the step's recorded request id, and the answer becomes the step's result, while an expired hold fails the step with the catchable L4027. A hold the host refuses leaves the step pending rather than refused, so no later host writes again, and its L5025 says so. A hold whose checkpoint answers an outcome other than resolved or expired fails the step as a handler fault. Only ask runs inside once; every other effect is refused before it begins (L4028), and a write from the body to a binding outside it is refused (L2032). The journal entry gains a hold field for the hold's own binding. The hosted runtime ends the held ask's open attempt pause before the hold binds, and cotal run answer, cotal run amend and cotal run journal read a held step at its hold. A fork may cut inside once, and a migration ignores an orphaned once. once becomes a reserved name, so a program that declares its own once binding is refused (L2002). The design record is docs/design/at-most-once-external-effect.md.
  • AG-UI frame metadata (CotalMeta) now declares a usage carrier for a run's model usage: input and output token counts, the cached and reasoning parts of those totals, and the cost in US dollars. It rides the event that closes the run (RUN_FINISHED or RUN_ERROR), and a count the harness does not report is left out rather than written as zero. Before, a connector that read these numbers from its harness had no declared key to publish them under, so it dropped them, and a connector that invented its own key would have disagreed with the next one about the name of the same quantity. No connector fills the carrier yet; each adopts it in its own change.
  • cotal_yield on a turn the run already settled now refuses with the turn id and its deadline. Before, a seat that yielded after its deadline was told "no turn is active" or that it held no such turn. One that yielded between the manager settling the turn and the seat's next poll was told the yield landed, although the run had recorded L4003.
  • A mesh registry record this build cannot use no longer refuses a command in the catalog preparation that runs before the command's own checks. While such a record is present the preparation neither refreshes nor applies a catalog, so cotal spawn reports its own usage errors and a managed handoff refuses with its handoff-phase sentence instead of the record's path. A snapshot an interrupted command left unapplied is applied once the record is restored or removed. Every command that resolves its target through the registry still refuses the record by name.
  • cotal web --detach on the default host and port now comes up on hosts whose system resolver has no answer for cotal.localhost, such as WSL2. The detached parent probed the branded http://cotal.localhost:7799/ for readiness through Node's resolver, so every probe failed while the child was already serving on 127.0.0.1:7799; after 30 seconds it reported web dashboard did not become HTTP-ready, stopped the healthy child, and left only the banner in web.log. The readiness probe now asks the bound host and port. The printed address is unchanged.

What's Changed

  • fix(manager): serve at the epoch the manager's own registration committed by @davidfarah2003 in #2639
  • fix(connector-core): declare a usage carrier on AG-UI frame metadata by @davidfarah2003 in #2650
  • fix(auth): register and observe the host incarnation a delegated intent pins by @davidfarah2003 in #2652
  • fix(connector-core): name the settlement when a yield targets a turn the run already settled by @davidfarah2003 in #2655
  • fix(core): drop a presence record whose card.name, status or ts has the wrong type by @davidfarah2003 in #2660
  • fix(cli): skip the catalog refresh while a registry record is unusable by @davidfarah2003 in #2661
  • feat(lang)!: add once, an at-most-once scope for steps that write to a far side by @davidfarah2003 in #2659
  • fix(core,cli): validate caller args as sent and name the version skew a closed contract refuses by @davidfarah2003 in #2666
  • fix(web): probe the bound address when a detached dashboard starts by @davidfarah2003 in #2669

Full Changelog: v0.64.0...v0.65.0