Sidecat SystemWorking draft published for review · 25 September 2026
Working specification

This public review copy includes the four current draft pages and the author-profile fixtures. Earlier research sources remain cited by name but are not included in this bundle.

Sidecat System 0.1 — base author profile

Status: provisional companion to the working System 0.1.0 specification. This is an exact candidate for a small independent app and client trial. Its fields and negative cases should be changed when that trial exposes a poor choice. The local validator checks fixture consistency; until a released author kit and independently authored app/client exchange these operations, the profile is not an interoperability claim.

The sidecat.system/0.1 identifier below names the proposed compatible wire/profile line. The 0.1.0 document version names this draft revision; their different precision is intentional. Any incompatible wire meaning requires a new profile identifier.

The goal is one public path for an app author and one external caller. This profile describes an app-bearing package's application descriptor and call binding; it is not the package-manifest grammar for guidance-only or Lisp-only contributions. Such a package may have zero app descriptors and no handler. The internal Go fast path may use typed structs and direct handler dispatch. The external binding described here uses JSON control frames; bulk bytes and exact signed artifacts have separate profiles. This separation keeps a resident runtime and does not create a process or interpreter per invocation.

1. Names, versions and one logical descriptor

Public app IDs, interface IDs, type IDs, event IDs and permission/action IDs are absolute IRIs. Comparison is exact after UTF-8 validation; an IRI is a name, not permission or an instruction to fetch a web page. A released interface ID includes its incompatible major version. The descriptor also carries a package version. A compatible minor revision may add optional fields and operations with a declared unknown-field rule; it cannot change an existing operation's effect, authority, retry or value meaning. A breaking change receives a new interface ID.

For this local trial, the resident runtime mints a random UUIDv4 owner namespace when a stateful app creates its store and records urn:sidecat:owner:UUID in that store header and registry binding. The owner ID is independent of app code, process, socket, file path and signing key. An independent copy mints a new owner ID. A recovery copy may keep the old ID only after the former writer is fenced; a route move or key change must carry an authenticated continuity record and monotonic owner generation. A peer unable to establish current ownership refuses a consequential write. The exact federated continuity proof remains a separate open profile choice.

The app-bearing test package contains one JSON application descriptor, every referenced schema, help text and conformance fixtures. Its descriptor is inert to inspect; no package code is loaded for validation. A package with several apps can carry one descriptor per app under the same rule; a package with no app does not invent an empty application descriptor. The author may generate a descriptor from a Go public interface plus a Sidecat semantic overlay or generate Go bindings from the descriptor. In either case the released descriptor is the single logical agreement for that app, and the build fails on shape or semantic drift. Paths inside the package are relative, cannot escape its artifact root, and cannot fetch a network schema at validation time.

The descriptor grammar has these mandatory fields:

Field Type and meaning
systemProfile Exact string sidecat.system/0.1.
appID, appVersion Absolute app IRI and semantic version. The same code installed for two owners can have one appID but distinct owner IDs.
purpose One useful sentence, shown in discovery.
helpPath Relative path to concise package help for the app's public operations, inputs, outcomes and failure recovery. The CLI and other projections can render it without loading app code.
interfaces Nonempty array of distinct interface declarations.
interface.id, interface.major, interface.minor, interface.profiles Absolute versioned IRI, positive incompatible major, nonnegative compatible minor and advertised behavior profile IRIs. The activated descriptor digest fixes the exact interpretation.
operation.name Unique lowercase dotted name within the interface. No overload by input shape.
operation.inputSchema, outputSchema Relative paths to JSON Schema 2020-12 documents shipped in the same artifact. Schemas describe the portable JSON profile below and require a closed or explicitly extensible record rule.
operation.effect One of pure, read, local-mutation, external-effect, continuation.
operation.retry One of safe, deduplicated, unsafe. A deduplicated operation declares retry-key scope, validity and a status/reconciliation operation.
operation.authority An action IRI plus how the app identifies the domain resource; public is an explicit value, never an omitted default. The app is still responsible for its domain invariant.
operation.resourceClass One of pure, storage-read, storage-write, cpu, network, model, external. The host can apply narrower budgets.
operation.supportedGuarantees Possibly empty list of named assurance profiles that the consequence owner can actually enforce. Selection is resolved against current organizational requirements before the effect.
operation.atomicScope, concurrency, authorizationAt Observable owner boundary and decision point. A compare-at-commit operation names the expectedVersionField JSON pointer. A read names its read-time authority check. These fields do not replace the app owner’s domain rule.
operation.retryHorizonSeconds Required with deduplicated retry: minimum duration for key/status retention from first admission. Replay after the horizon is unsafe and cannot be inferred from a missing status.
operation.failures Stable named error codes from the common set or the interface's versioned namespace.
interface.events Possibly empty array; each names schema path and event version, signal/invalidation/retained class, owner order scope and gap rule. A retained event declares a minimum retention period in seconds; its frame carries the version and exact schema digest.
requires Possibly empty array of interface ID, incompatible major, minimum compatible minor and required behavior-profile IRIs. Activation refuses if no provider satisfies all constraints; it never imports another app's private Go or SQL package.

An object-owning app additionally declares its object type IRIs, supported read/write/append/query protocols and concurrency rule. A stateless pure app declares no object type and needs no database. A long or complex app is acceptable if one author can understand its owned invariants and public dependencies without reading unrelated app internals.

Here is a complete logical descriptor example for a small contact owner. Its exact schema documents are bundled under the listed paths in the test archive; this example is a conformance fixture candidate, not evidence an app has been built.

{
  "systemProfile": "sidecat.system/0.1",
  "appID": "https://example.invalid/sidecat/apps/contacts",
  "appVersion": "1.0.0",
  "purpose": "Keep contacts and replace them when the caller has their current version.",
  "helpPath": "docs/help.md",
  "interfaces": [
    {
      "id": "https://example.invalid/sidecat/interfaces/contacts/v1",
      "major": 1,
      "objects": [
        {
          "type": "https://example.invalid/sidecat/types/contact/v1",
          "protocols": [
            "read-current",
            "conditional-replace"
          ]
        }
      ],
      "operations": [
        {
          "name": "read",
          "inputSchema": "schemas/read-input.json",
          "outputSchema": "schemas/read-output.json",
          "effect": "read",
          "retry": "safe",
          "authority": {
            "action": "https://example.invalid/sidecat/actions/contact-read",
            "resourceField": "/ref"
          },
          "resourceClass": "storage-read",
          "failures": [
            "denied",
            "not-visible-or-missing",
            "unavailable"
          ],
          "atomicScope": "none",
          "concurrency": "none",
          "authorizationAt": "read",
          "supportedGuarantees": []
        },
        {
          "name": "replace",
          "inputSchema": "schemas/replace-input.json",
          "outputSchema": "schemas/replace-output.json",
          "effect": "local-mutation",
          "retry": "deduplicated",
          "retryKeyScope": "owner+enrollment+interface+operation",
          "retryStatusOperation": "replace-status",
          "authority": {
            "action": "https://example.invalid/sidecat/actions/contact-replace",
            "resourceField": "/ref"
          },
          "resourceClass": "storage-write",
          "failures": [
            "conflict",
            "denied",
            "not-visible-or-missing",
            "unavailable",
            "unknown"
          ],
          "atomicScope": "object",
          "concurrency": "compare-at-commit",
          "expectedVersionField": "/expected",
          "authorizationAt": "commit",
          "supportedGuarantees": [
            "https://example.invalid/sidecat/assurance/exact-candidate/v1"
          ],
          "retryHorizonSeconds": 86400
        },
        {
          "name": "replace-status",
          "inputSchema": "schemas/replace-status-input.json",
          "outputSchema": "schemas/replace-status-output.json",
          "effect": "read",
          "retry": "safe",
          "authority": {
            "action": "https://example.invalid/sidecat/actions/contact-replace-status",
            "resourceField": "/ref"
          },
          "resourceClass": "storage-read",
          "failures": [
            "denied",
            "not-visible-or-missing",
            "unavailable"
          ],
          "atomicScope": "none",
          "concurrency": "none",
          "authorizationAt": "read",
          "supportedGuarantees": []
        }
      ],
      "events": [
        {
          "name": "changed",
          "schema": "schemas/contact-changed.json",
          "version": 1,
          "delivery": "retained",
          "orderScope": "contact-owner",
          "minimumRetentionSeconds": 86400,
          "gap": "explicit"
        }
      ],
      "minor": 0,
      "profiles": []
    }
  ],
  "requires": []
}

The example intentionally names a replace-status operation because a response can be lost after replacement. A later read of current content alone may not prove which write occurred if another writer acted afterward. The app's result and status contract must answer that uncertainty, or its retry rule must be unsafe with explicit unknown outcome.

2. Portable JSON values

The baseline external profile is UTF-8 JSON with the following semantic restrictions. Objects reject duplicate member names and have no meaningful order; arrays preserve order. An absent object member differs from one present with null. Ordinary JSON numbers are integers within the exact interoperable range -9007199254740991 through 9007199254740991. Larger integers, exact decimals and binary floating point values use the tagged forms below. NaN, infinities and negative zero never travel as untagged JSON numbers; tagged integer or decimal coefficients also reject negative zero. UTF-8 and decoded Unicode scalar values are validated before schema interpretation, including rejection of an unpaired escaped surrogate. A frame is limited to 64 nested object/array levels in addition to its negotiated byte bound. A connector must not silently coerce one form into another.

Tagged form Required fields and meaning
integer {"$sidecat":"integer","decimal":"-12345678901234567890"}. Decimal string has no leading plus or redundant zeros.
decimal {"$sidecat":"decimal","coefficient":"12345","scale":2}; value is coefficient times 10 to the negative scale. No rounding on decode.
float64 {"$sidecat":"float64","bits":"3ff0000000000000"}; 16 lowercase hexadecimal digits are IEEE 754 binary64 bits.
object-ref {"$sidecat":"object-ref","owner":"owner IRI","type":"type IRI","key":"owner-local key"}. It names current state, not a version.
revision-ref {"$sidecat":"revision-ref","object":object-ref,"revision":"owner-issued key"}. Exact or unavailable; never silently current.
blob-ref {"$sidecat":"blob-ref","algorithm":"sha-256","digest":"64 lowercase hex digits","owner":"optional Sidecat owner IRI"}. Digest is over raw bytes, not a Git object's typed encoding. Without owner this asserts content identity only; a remote fetch requires an owner-qualified locator.
foreign-ref {"$sidecat":"foreign-ref","protocol":"protocol IRI","authority":"foreign scope","identifier":"verbatim foreign ID"}. It does not create a local owner.
opaque {"$sidecat":"opaque","type":"type IRI","codec":"codec IRI","data":"base64 bytes"}. Unknown values can be carried without interpretation.

The "$sidecat" member is reserved for these typed forms in portable objects at any nesting level; ordinary domain records cannot use it. A schema must state where a tagged form is permitted and what its nested type is. Base64 fields must pass strict decode and canonical re-encode checks; JSON Schema contentEncoding is an annotation, not this validation. No arbitrary object that merely looks like a reference becomes authority, and a live Lisp closure, file descriptor or session handle does not become portable by formatting its name as JSON. A raw text note, large attachment or document may travel as a blob reference and be fetched under its own authorization.

Every required CLI, Lisp, native and MCP connector exposes a generic structured-input path that accepts every value in this base portable profile, including nested records, ordered arrays, explicit null, large integers, exact decimals, typed references and opaque values. Friendly command flags may cover a subset; the generic path cannot. Boundary lowering preserves the type and semantic content of each field. An unsupported value, profile or schema version is a refusal, never an invisible drop into provenance.

JSON Schema 2020-12 is the proposed schema language for this binding, with its dialect pinned in every schema. The schema's structural validation is not domain permission or an effect check. An exact digest of schema bytes and a self-contained import closure are recorded in the package artifact; the contact test package includes the base author schema that its domain schemas reference. The installed resolver maps schema IDs and relative paths only to packaged bytes and refuses network dereferencing during an ordinary call. A separate signed-artifact profile can select deterministic bytes. This ordinary control profile does not claim a unique canonical JSON byte string.

3. Calls, results and the local transport

A local external client connects to the resident daemon through a Unix-domain socket. Frames are a 32-bit unsigned big-endian byte length followed by one UTF-8 JSON object.

The server first sends a challenge frame with a fresh 32-byte nonce, supported System and authentication profiles, a frame-size limit and challenge lifetime. The client answers with an authenticate frame selecting sidecat.system/0.1, an active enrollment and credential ID, its own fresh 32-byte nonce, and an Ed25519 signature. The signed bytes are UTF-8 "Sidecat local auth v1" followed by one zero byte, the raw server and client nonces, then three UTF-8 strings each preceded by its unsigned 16-bit big-endian byte length: selected System profile, enrollment ID and credential ID. The server validates the enrolled public key, nonce lifetime and single use, and current enrollment/credential state, then returns ready with connection ID, bound actor/enrollment, authentication epoch, negotiated frame bound and maximum connection lifetime in seconds. On refusal it returns auth-error with a safe code. Renewal uses a new connection and challenge; the maximum lifetime is not a revocation grace period. After an enrollment suspension or credential revocation is acknowledged, affected established connections and prepared sessions are invalid: the server closes them or refuses further calls under their old authentication epoch. No expired connection may submit work.

This local profile follows the accepted cooperative-same-UID harness model: separate Ed25519 credentials distinguish cooperating harnesses under one Unix UID. It does not isolate a credential from a hostile same-UID process that can read the other harness's secret. AAuth Agent-token authentication is a separate advertised profile. OS peer credentials protect socket access but are not the Sidecat actor. The prepared launch profile selects the harness credential once for the session, not as a required flag on every call. A frame above the negotiated bound is rejected before allocation.

An operator-authorized identity interface enrolls a client-generated Ed25519 public key to a stable enrollment ID and credential ID, binds the actor and permitted launch scope, and exposes rotation, suspension and revocation. The candidate operator entry is sidecat harness enroll --actor <actor-id> --public-key-file <path> --scope <organization-id>; actor and organization IDs are opaque identities resolved by the identity service, not assumed to be IRIs. The public-key file is exactly 32 raw Ed25519 bytes, and the command returns the enrollment and credential IDs or a typed refusal. sidecat harness prepare --enrollment <ID> --credential <ID> --private-key-file <path> --output <private-home-path> writes a mode-0600 launch profile whose schema names socket, IDs, key format and key path. In this profile the private-key file contains exactly the 32-byte Ed25519 seed, is owned by the invoking user and has mode 0600. A different key encoding requires a named profile, not guessing from file contents.

The private key is created and kept by the client/harness, not copied into the workspace or echoed by the enrollment command. An independent client reads that prepared profile once and uses the common handshake; it does not pass a launch flag on every call.

The operator may suspend or revoke the enrollment and rotate its credential without changing the stable enrollment ID. The daemon checks current enrollment state at call admission, invalidates affected sessions on acknowledged suspension or revocation, and uses the consequence fence below for an effect already admitted. Remote federation has a separate peer binding and does not replay a local socket proof as a remote grant. These command names and the preparation schema are proposed and need installed qualification.

A launcher such as agt that starts a named harness must carry its expected enrolled actor independently of the selected profile path and compare it with ready.actorID before submitting a call. A valid but wrong sibling profile is a launch error, not successful attribution. The launch-profile file's optional expectedActor can provide a further consistency check, but cannot serve as that independent expectation because a wrong profile could name its own wrong actor consistently. A generic client without a trusted harness launch expectation displays the authenticated actor and makes no claim about which model is running. The author trial includes a negative launch with a valid sibling credential and verifies that the launcher refuses before Work changes.

A caller frame contains call ID, target app/interface/operation and, for stateful calls, the selected owner namespace; it also carries typed input, optional deadline, retry key and requested guarantees.

The operation's declared primary authority.resourceField, when it contains an object reference owned by the target, must match the selected target owner. Other input references may name foreign owners: for example, an invoice owner may refer to a contact owner without granting either access to the other's store. An omitted owner is allowed only when an operation is stateless or a prepared context resolves to exactly one owner before dispatch; ambiguity refuses before domain execution. A retained event and its subscription likewise select their source owner, so order and cursors cannot drift between installations.

A caller frame cannot contain a runtime_context field. The daemon injects verified caller, represented actor when authorized, effective organization/session, trace/causation and selected policy into the in-process handler context. A delegated nested call either uses the service principal or carries an explicitly authorized representation of the original caller; it never inherits the latter by accident.

A call ID identifies one attempt; a retry key identifies the same intended effect across allowed attempts. For a deduplicated operation the key scope is the tuple (owner ID, stable enrollment ID, interface ID, operation, retry key), so credential rotation does not create a new intent. The owner stores a SHA-256 request fingerprint of the RFC 8785 canonical JSON for selected target, input and requested guarantees. A changed fingerprint under the same key conflicts.

For a local owner mutation, the retry identity, fingerprint, committed mutation and result/status update share the owner's atomic transaction; an outbox event promised for that mutation joins it. An external effect that cannot join that transaction first records a fenced intent and then reconciles applied, not-applied or unknown without blindly replaying it.

The descriptor states the minimum deduplication horizon in seconds from first admission; after it, a client must not retry an uncertain effect with the old key or infer nonapplication from an absent status. A status result returns the original fingerprint, attempt ID, state and observed outcome/currency when known. It says unknown when it cannot decide.

{
  "profile": "sidecat.system/0.1",
  "kind": "call",
  "callID": "01JZ6C0A9M57V1PZCQR8YB5K2D",
  "target": {
    "app": "https://example.invalid/sidecat/apps/contacts",
    "interface": "https://example.invalid/sidecat/interfaces/contacts/v1",
    "operation": "replace",
    "owner": "urn:sidecat:owner:89f93660-f35e-4efe-92f2-aa98119b0a09"
  },
  "input": {
    "ref": {
      "$sidecat": "object-ref",
      "owner": "urn:sidecat:owner:89f93660-f35e-4efe-92f2-aa98119b0a09",
      "type": "https://example.invalid/sidecat/types/contact/v1",
      "key": "c-123"
    },
    "expected": "v17",
    "contact": {
      "name": "Ari",
      "email": null
    }
  },
  "retryKey": "edit-c-123-from-v17",
  "requestedGuarantees": []
}

The corresponding result has the same callID and exactly one tagged disposition: completed(value), refused(code, message, next action when known), accepted(continuation reference), or unknown(reconciliation reference). A system-generated activation record binds exact package artifact, descriptor digest, compiled handler build, selected owner, interface versions and running instance. The registry routes a call only through a currently admitted activation. The descriptor cannot include its own artifact digest without a hash cycle, so activation is a separate record. The common codes include invalid, unsupported-profile, incompatible-interface, denied, not-visible-or-missing, conflict, busy, unavailable, cancelled and unknown. Application codes live in their versioned interface namespace. Code selection is structural at the failing boundary; no caller or projection derives it by matching English error text. Refusal text is safe to show and cannot contain a raw private wrapped error.

Before an external or selected gated effect, the consequence owner resolves the requested guarantees against applicable organization requirements and local policy.

If guarantees are requested or required, it emits a selection frame binding effective guarantees, owner, exact candidate fingerprint and relevant version before effect; a decision-required selection leaves the call accepted and pending until the decision arrives. If a required guarantee cannot be met, it refuses before effect.

The System's public continuation interface reads or requests cancellation of the pending call. Its decision interface accepts an authenticated allow/deny assessment against selection ID, candidate fingerprint, owner and expected version; the effect owner then checks the current decision and the exact candidate again before committing. Submission is not itself the effect and a cancelled continuation cannot claim rollback. The final outcome cites the selection ID and reports what was actually applied. A prior workflow approval is checked against the exact candidate, target and relevant version at the effect owner.

Ordinary calls with no selected gate acquire no additional custody ceremony. The app's permission decision point and freshness rule are part of its operation contract; a queued call cannot treat old ingress permission as current permission unless the declared bounded observation permits it.

For this local contact mutation, the host holds an authority epoch guard across the app's authorization check and SQLite commit. A local suspension/revocation acknowledgment waits for already guarded commits and blocks new guards, so no contact replacement may commit after that acknowledgment under the old epoch. This rule does not imply instantaneous revocation at an unreachable remote peer; such a profile must declare a bounded validity/freshness policy and report its exposure window.

The logical interface https://sidecat.dev/spec/system/0.1/interfaces/continuations/v1 has read and cancel operations addressed to the owner of the continuation reference. read takes { "ref": object-ref } and returns { "state": "pending|ready|refused|unknown", "selectionID": string?, "outcome": outcome? }; cancel takes the same reference and returns whether the stop request was recorded. Reading or cancellation requires the bound caller or separately authorized observer, and cancellation cannot claim a committed effect was undone.

The logical interface https://sidecat.dev/spec/system/0.1/interfaces/decisions/v1 has submit and read. submit takes { "selectionID": string, "targetOwner": owner-IRI, "candidateFingerprint": sha256-hex, "expectedVersion": string?, "verdict": "allow|deny", "reason": string? } and returns a decision reference and the selected candidate identity. The decision owner authenticates and authorizes the actual decider; a caller-supplied decidedBy is ignored. The consequence owner consumes the selected decision only after verifying its selection ID, target, candidate, relevant version, decider authority and current local permission. Reusing a decision for a different candidate or later version refuses.

These logical operations still need exact packaged schemas and a two-client installed trial; the supplied gated fixture tests selection preflight and pending identity, not completed gated execution.

The Go host profile admits a package's application through a public registrar generated from the released descriptor. The release assembler links approved native Go modules into a resident binary without an edit to core dispatch code; an external long-lived worker can implement the same operation binding when process separation is chosen.

Candidate Go author boundary

This is a source-level author profile, not a promise that an arbitrary compiled Go plugin can be loaded into a running process. The package can be inspected and validated without executing its code. An explicitly selected assembly step resolves source modules, generates the registry imports, builds the resident host, and records the exact module and descriptor digests in activation. Installing a descriptor or browsing a marketplace entry does not compile or run its implementation. Dynamic admission of a new native module requires a new host build and restart or a separately admitted long-lived worker. The author trial must measure whether this trade is acceptable; a module compiler and dependency graph are part of the trusted build boundary.

Supplied by the app author Supplied by the public System kit or host
A released descriptor, packaged schemas, concise help, fixtures and declared public dependencies. Descriptor validator, schema resolver, public Go SDK, registrar generator, generic CLI/Lisp/MCP projections and a fake host for tests.
One Go package with an exported registration entry named in the package assembly manifest, plus operation handlers. The descriptor remains inert data; its implementation path is assembly metadata, not an instruction to execute during inspection. An assembler that generates imports and calls those registration entries from a selected lockfile. No hand edit to the host router or internal registry.
Private domain model, storage schema and upgrades, query and concurrency rules, and tests for domain invariants. Verified caller/target context, bounded admission, cancellation, common result/error encoding, public cross-app caller, and selected assurance context.
An app-local transaction adapter or use of the kit's SQLite owner-store adapter when promising durable events or deduplicated mutation. A common outbox table/dispatch adapter inside that owner's SQLite store, not a global database that owns every app's domain records.

The minimum SDK shape for the independent trial is a registrar that binds each descriptor operation to exactly one typed handler and refuses a missing, extra or duplicate binding. A handler receives an immutable CallContext with authenticated actor/enrollment, selected owner, deadline, authority epoch and requested/effective guarantees; the input is decoded against the packaged schema before dispatch. The returned Outcome is one of completed, refused, accepted continuation or unknown. The SDK also exposes a public Caller for other apps, a RetainedPublisher for app-owned committed events and a ContinuationStore for accepted long work. These are capability interfaces, not direct pointers to other apps' private stores. An app may decline facilities it does not declare. The Go names and method signatures are candidates to be fixed by compiling one independently authored app and one second client, not by asserting that this prose is a working ABI.

For a SQLite owner promising a retained event, the adapter must stage its event in an outbox table using the same sql.Tx that changes the app's domain rows. The app's mutation result and deduplication status join that transaction when those promises are declared. The host cannot implement this guarantee by committing the app transaction and then publishing through a separate process or database. A non-SQLite store may supply an equivalent atomic local commit and recovery proof; if it cannot, the descriptor must not advertise that guarantee. Dispatch starts after commit, may duplicate, and reports an explicit gap or unknown when the declared retention cannot be met. The outbox adapter owns only its reserved records and dispatch cursor; domain schema and upgrade remain app-local. No cross-app transaction is implied.

The trial author receives only the public kit and the contact package source, with no permission to inspect host-private packages. A second agent builds the client from the descriptor and socket profile without contact source. The trial records the actual files read or edited, compile and installed-call results, restart/event behavior, and the optional nickname change. This is the falsifiable test of whether the boundary makes agent work smaller; a generated registry and passing validator alone are insufficient.

The generic CLI candidate is sidecat operation call OPERATION_REF --input-file - (or --input-json for short input). Discovery must return an exact reference binding app, interface version and operation, with a concise input/effect summary; friendly names are optional aliases with explicit collision handling. The current fixture archive does not define the reference's byte grammar or a discovery response, so it does not qualify the CLI or its large-catalog usability. A second client must test those together with the socket binding.

A handler receives a typed, immutable input and runtime-established context and returns a typed result; it may retain data only by copying it. The runtime may call a reentrant handler concurrently; an app declaring serialization names its scope.

For a SQLite owner, that scope includes bounded read connections and explicit per-database write admission; SQLite WAL permits readers with the writer but only one writer at a time for one database. The owner reports busy/admission refusal rather than letting unbounded goroutines pile up on database locks. It commits a promised retained event/outbox intent in the same transaction as the domain mutation, and never awaits another app or network action while holding that write transaction or an exclusive Lisp session lock.

Long waits yield to a durable continuation. Cancellation is cooperative; if an effect may already have happened, the app returns an unknown/reconcilable outcome instead of claiming rollback. Static descriptor inspection cannot invoke the registrar or application initialization.

A full Lisp evaluation is serialized within a mutable session; different sessions run under bounded admission. An evaluation has a deadline and an interruption path. If an unresponsive evaluator must be terminated, any selected retained definitions are restored without replaying external effects; an unretained session may be lost. A Lisp call into an app follows the same handler context and consequence rule as a socket call. The particular established Lisp dialect, process pool and snapshot format remain the System profile decision listed in the parent specification.

4. Events, subscriptions and continuing work

A signal, invalidation and retained event are distinct declarations. A retained event names event ID, source app/interface/owner, owner-scoped order token, event schema version and SHA-256 digest of the exact schema bytes, cause, and payload or references. That schema closure remains retrievable for the event's declared retention horizon; replay does not validate an old event against whatever schema happens to be current.

An invalidation names its source owner and an owner-issued monotonic watermark; consecutive invalidations may coalesce.

A retained or invalidation subscription names an owner-qualified source; a transient signal may name only its source app/interface. A subscription also names its permitted filter and item, byte and in-flight budgets. Retained delivery uses a start cursor or an initial read/watch handoff. A signal has no replay cursor. An owner that offers read-and-watch returns a watermark tied to the read or supports subscribe-before-read with version comparison; read-then-subscribe without a gap rule is invalid. Retained cursors bind to issuer, filter, visibility and retention horizon. A gap is explicit. Delivery acknowledgment names its stage: transport, retained, processed or domain committed. A transport acknowledgment is not the receiving app's effect receipt.

The supplied JSON schemas fix the shape of individual event and subscription requests, but do not yet fix the complete socket conversation: subscription acceptance and ID, delivery acknowledgment, gap/reset, cancellation, or concurrent call/stream multiplexing. A nonempty filter also needs a declared, versioned grammar rather than the present unconstrained JSON object. Those frames, filter rules and ordering rules must be specified and tested with a second client before this candidate can claim bus-wire interoperability. The contact mutation fixture exercises a retained event's identity and packaged schema, not a live subscriber.

When an app promises an event with a mutation, its own transaction includes the event/outbox intent, so a crash after commit cannot erase the promise and a rollback cannot emit it as committed. Delivery can duplicate. The bus indexes matching subscriptions and does not wake every installed app for an unrelated signal. It bounds bytes and in-flight work, not just event count, and accounts for hot and quiet apps separately.

Before an owner acknowledges accepted continuing work, it records current state, the next eligible wake/dependency, dispatch identity and reconciliation rule. The daemon resumes eligible work after restart without the originating harness. Each worker allocation and accepted completion has a distinct fence; a replaced worker cannot advance a current attempt. The next worker receives the retained purpose, exact applicable context and next action. Prompt traces and full custody remain selected enhancements; this minimum state is what makes an accepted continuation real.

5. Minimum negative and cross-surface fixtures

The public kit must contain exact positive and negative JSON frames, descriptor/schema validation, a Go host adapter test and a second client written without private app source. The minimum cases are:

  1. Nested ordered invoice lines, explicit null beside an absent field, a large integer, exact decimal, float bit pattern, typed object reference and unknown opaque value survive CLI/Lisp/MCP/native generic paths unchanged.
  2. A duplicate JSON member, invalid UTF-8, over-limit frame, unknown required profile, missing schema, conflicting descriptor and incompatible interface are refused before domain execution.
  3. Two contact replaces with one expected version yield one committed result and one typed conflict. A lost reply yields a stable status or an explicit unknown; a repeated retry key with different content conflicts.
  4. Two harnesses under the same Unix UID have distinct verified caller identities. A payload claiming another actor cannot change either identity. After acknowledged revocation, an already-ready connection and affected session cannot submit another call; a pre-commit effect follows the declared fence and preserves its honest outcome.
  5. A retained change survives owner commit followed by process crash before bus dispatch. A slow subscriber causes bounded lag/gap behavior without blocking unrelated work.
  6. An accepted continuation resumes after daemon restart under a replacement worker; the stale worker's late completion is fenced.
  7. A selected gate refuses if its exact candidate changed or assurance is unavailable; an ordinary ungated call proceeds under its own permission rule.
  8. An independent app package and independent client can install, discover, call and inspect help using only the released author kit. No core source edit, shared SQL access or maintainer-only registry switch is needed.
  9. Add an optional contact nickname with absent, null and string semantics. The app's interface and private store may change; old and new clients exercise the declared compatibility rule. A hand-edited host router, shared SQL migration, other app or separate CLI/MCP/Lisp interpretation fails the app-local change test.

The D-Bus specification informs local names, addressed calls and filtered signals; etcd watch and NATS JetStream inform replay gaps, bounded demand and acknowledgment distinctions. These are comparator mechanisms, not claims that Sidecat uses their wire formats. See the research bus candidate, value types, author trial and source register. The profile above is new candidate text and still needs an independent implementation and measured runtime behavior.

6. Reviewable profile files

The descriptor/frame schema, validator, and positive and negative fixtures are a provisional author kit.

The fixture set includes challenge, authenticated response, test-only key and signed preimage, activation, package archive, dependency resolution cases, version-bound retained event, gated call, selection before effect, and retry status.

The rebuild script refreshes packaged schemas and exact digests from the source fixtures. The key is a public test vector, not a production credential.

The local validator checks schema shape, duplicate JSON members, illegal raw numeric forms, strict byte decoding, contact payload schemas from the archive without network access, dependency resolution, retained event schema identity, the Ed25519 vector, exact package contents and digests, selected candidate identity, owner selection and retry identity. Its request-fingerprint fixture uses only ASCII keys and strings; full RFC 8785 Unicode and number cases still need cross-language vectors. The wrong-profile launch case states the expected refusal; it has not been run against a real launcher. The validator is one implementation of candidate rules, not a second interoperable client, a daemon, a released SDK or a runtime capacity test.