Cotal
DocsAPI Reference
Build integrationsEmbedding reference

Service endpoint hosting

Host operations

A second package-internal module builds service host operations on that writer: register admission, decide (approve or refuse), revoke, a create-only name binding, and the operation and proposal reads. It shares the writer's sealed host, plane-claim hold check and fatal latch, and each invocation opens its own custody host over a per-invocation effect registry. That host writes only the invocation's scope, the exact keys discovery located before the effect registry opened, and the effect registry is handed the same key set so a broker grant can match it. A step that needs a key outside the scope holds as scope-mismatch before anything is issued; replaying the same request discovers what is stored and scopes it. Each effect registry rides its own connection, whose credential may publish only on that key set's subjects (subject confinement, not operation confinement; the engine itself only creates and CASes those keys), never renews or reconnects, and expires at the invocation's original deadline rounded up to the whole second. With agreeing clocks and timely scheduling the broker's whole-second timer closes it within about 1999 ms after that deadline; that is granularity, not a guaranteed close time, and no close proves an earlier write cannot still settle. Losing the connection fences the invocation. Discovery's credential has no write at all. The module's records and their write order are in SPEC §13.1. Results name their operation once a binding exists or may exist: pending, held and refused all carry operationId. A result can carry debt, the capacity releases its operation still owed as far as that invocation observed. A result without debt does not mean nothing is owed; the operation read is the authoritative view of the debt. A held result by itself implies neither a binding nor an authorized granter: a tripped engine, or a granter without a current interactive row, gets one held answer for a register, decide or revoke before any read of the request's keys. denied means the invocation wrote nothing, and either the request can never bind or the invocation's own read found no binding. That read is point-in-time; another invocation of the same request already in flight can still bind it later. A revoke result reports denyNew when it has evidence: an endpoint-gate read, which gives unknown when the read failed or found another assignment's gate row, or an assignment that is already fenced, which gives landed. The early held answers of a tripped engine and of a granter without a current interactive row carry no denyNew, and a result without denyNew says nothing about deny-new. Completed results say whether this invocation applied them or returned a stored record. A stored approval returns unchanged, and its approvedGateRevision is null unless its own gate CAS was observed. The index does not export this module; the typed request, result and view parsers in the service host contract are public.

Hosted composition

openAuthAuthorityPlane and startAuthService take serviceEndpoints: { startup: { kind: "normal" } } to host these operations; it is off by default. cotal up --user-auth --service-endpoints sets it on the stock auth service, and a lane of auth-service --spaces sets it when its manifest entry has "serviceEndpoints": true. With it, the plane and the hosted handle gain serviceEndpointHost(request), which answers decide, revoke, name-bind, the four caller-policy operations and the instance, proposal, operation and name reads in process. instance-read returns the current ServiceInstanceView for the proved owner's selector, including its approval digest and registration state. instance-list walks the native assignment subjects with a cursor and returns only that owner's instances. Missing and foreign selectors both read absent. A hosted context rejects it as unavailable while it is not ready. The host authenticates no caller: the embedder must authenticate the operator or owner granter it passes. The stock service does that on POST /service-endpoint-host: the body is { idpToken, request } with no granter, and the route verifies the token and derives the granter. A decision, an owner-request revoke, the four caller-policy operations and the reads run as the proved owner on either face; name-bind, any other revoke and the admission operations are the operator's, served on the loopback face under the exchange capability only, with the proved subject as the audit id (the admission operations have no engine handler yet and answer unsupported). Mint requestIds unpredictably: they share one space-wide namespace. cotal service-endpoint drives that route as the signed-in user (docs/cli.mdx). Register admission is reachable only through the service authority route. name-bind needs an operator granter and expectedRevision: 0. It creates the endpoint's name binding once, with your claimReference, and only the identical request recovers it; nothing updates, transfers or releases a name. The host checks no domain control, so verify the claim, or delegate a namespace you control, before you bind. The host runs over the plane's claim hold, data account and actor ledger directory. A restore or missing startup refuses the plane open, which unwinds and releases the claim. If that unwind cannot close an effect connection, it stops there: the claim stays held, the plane's clients, scanners and cleanup timer stay live, and there is no handle to retry. Only the process exiting ends them, after which the existing crash reclaim applies. Its discovery connection is an ordinary renewing read-only client: a broker loss fails its reads until the stock client reconnects, and those calls answer unavailable with nothing written. A renewal failure closes it permanently, and the host stays unavailable until the plane is opened again. Both plane fatal paths trip the host, and either one trips it even while the plane closes, without making the plane fatal. Plane close refuses new host calls from the moment it is called, waits for every call that already entered, from entry through discovery and effect open, closes discovery and then releases the claim. An effect connection that failed to close stays owned past its call: close closes it again first, and rejects with the claim still held while it stays open, so you can retry close. Until a retry succeeds or the process exits, discovery, the administrative rail, both scanners and the remaining clients stay live, so no successor can take over the claim.

Multi-space admission

On a multi-space host the setting is per space. The manifest entry's serviceEndpoints is the only place it is set, and --service-endpoints refuses beside --spaces (server daemons). Check a manifest you write with parseAuthHostManifest(text) from @cotal-ai/auth before you rename it into place: it is the parser the host applies. A callout replica cannot issue a service-endpoint connection, so it refuses a space whose admission is open: a single-space replica refuses to start, and a callout host names the space and retries it every 5 seconds while it answers for the others. The first open with service endpoints opens the admission and nothing closes it, so keep every space that has ever run with service endpoints off replica manifests, also after its entry drops the field. admitsServiceEndpoints({ space, server, secrets }) from @cotal-ai/auth tells you which: it resolves true for the spaces a callout lane refuses for their admission, with the read the lane makes on the lane's own store grant, and throws when the space's service keys are missing or its stores cannot be read. Nothing turns a true answer back to false. To turn service endpoints on for a space a replica lists, remove it from every replica manifest and wait for each <manifest>.applied first: a lane checks at open, so a lane already open keeps answering and denies the service connects the broker hands it.

On this page