Skip to content

Latest commit

 

History

History
1117 lines (991 loc) · 66.5 KB

wire-contract-v1.md

File metadata and controls

1117 lines (991 loc) · 66.5 KB

Synapse wire contract v1 (consumer snapshot)

Status: SNAPSHOT of v1.1 with explicit catalog installation and owned-only local embedding/reranking. Authoritative examples: crates/synapse-module/tests/skeleton_e2e.rs and crates/synapse-module/tests/soak.rs. This document restates the consumer contract; on disagreement, fix the document against the executable API tests.

1. Envelope (inference payloads)

Checks: embed_query_preloaded_minilm_returns_vectors_and_envelope, embed_batch_preloaded_minilm_preserves_order_and_envelope (skeleton e2e), and tests::wire_contract_documents_every_embed_vector_field (module library).

Management responses wrap operation payloads as {"result": <payload>}. The fields below describe inference payloads, not every management result. Download statuses are flat inside result.

{
  "fingerprint": "…",            // strict identity string — persist VERBATIM, compare only, never parse
  "table_epoch": 0,               // alias-table version; persist (fingerprint, table_epoch) per index
  "dims": 384,
  "provenance": { "engine": { "engine": "owned-metal", "version": "…", "build_flags": {} } },
  "module_generation": 3,         // bumps every module boot — cheap restart detection mid-conversation
  "equivalent_to": [],            // live alias reads; inline, no second call
  …op payload flattened here…
}
  • fingerprint = hash(model digest, quant, numeric_profile_id); the numeric profile folds engine identity, sanitized-tokenizer digest, pooling, normalization, dtype, shape envelope, thread class. Anything that moves vector space mints a new string. Alias flips do NOT change fingerprints.
  • Per-item fields on embed responses: real_token_counts, truncation_disclosures (submitted_tokens, effective_tokens, truncated), and vectors with id, vector, content_sha256, and submitted_sha256 (see Divergence detector below).

2. Errors (stable codes, typed recovery)

Checks: stable_errors_are_exhaustively_documented_and_keep_wire_names (wire_contract integration target), remote_error_classes_are_frozen (core), and embed_query_deadline_one_returns_typed_rejection (skeleton e2e).

Every error: {code, class: transient|permanent, retry_after_ms?, safe_to_retry_same_request, message, details?}. details is an optional object. Transient errors may carry retry_after_ms. Never poll-hammer a permanent code.

The code field uses two vocabularies. Core StableErrorCode supplies queue_full, deadline_exceeded, model_loading, not_certified, substitution_rejected, artifact_invalid, engine_crashed, probe_required, migration_required, and module_restarted here, plus the remote additions below. Owned-decode refusals use a separate vocabulary: grammar_disabled is not a StableErrorCode; it is emitted for constrained microllm.oneshot requests when grammar is disabled by module or approval configuration, or when no certified enabled owned-decode grammar lane is available.

  • sequence_too_long — class permanent, no retry_after_ms, not safe to retry the same request. A catalog-installed lane refuses a composed input exceeding its 8192-token limit before job diversion or execution. details: {tokens, max_tokens, item_id?}; item_id identifies the submitted item when available. No truncation disclosure or job id is emitted. Consumer disposition: split or trim the input before resubmitting.
  • owned_cuda_unsupported — class permanent. Emitted before an owned-CUDA worker is created when capability gating finds no usable CUDA capability evidence, a driver API below 12040, or compute capability below 7.5. Consumer disposition: do not retry on the unchanged host; select a non-CUDA lane or provide a compatible driver and device before retrying.
  • op_not_supported_for_remote — class permanent. Emitted when gateway v1 receives a remote request or calibration for a task it cannot serve, including remote rerank.score and microllm.oneshot requests and non-embed remote profiles. Consumer disposition: do not retry the same operation against that remote profile; select a compatible local lane or supported remote embed operation.
  • sentinel_calibration_refused — class permanent. Emitted when remote sentinel calibration detects provider self-noise that prevents a trustworthy drift gate from being derived. Consumer disposition: treat the profile as uncertified; do not retry the same calibration, and start a new probe only after correcting the provider or profile behavior.
  • model_not_installed — class permanent, no retry_after_ms, not safe to retry the same request. Emitted when a catalog model is requested on a backend that has no install record for the entry's current manifest. details: {catalog_id, lane_id | null, download_op: "models.download", download_job_id | null}; download_job_id names the non-terminal download job for the entry's current manifest, if one is running. Consumer disposition: call models.download (or wait for the named job) before retrying.
  • unknown_model — class permanent, no retry_after_ms, not safe to retry the same request. Emitted when the requested id is neither a catalog id, a declared catalog lane id, nor a registered model. details: {model_id} (the requested id). Consumer disposition: do not retry; correct the id.
  • self_check_failed — class permanent, no retry_after_ms, not safe to retry the same request. Emitted when a catalog lane's persisted self-check has failed for the current self-check key. details: {model_id, backend, check_id, reason}; check_id is the lowercase hex sha256 of the JCS serialization of the self-check key. Consumer disposition: do not retry on the unchanged install; the check reruns only after the key changes or the model is removed and downloaded again.
  • backend_unavailable — class permanent, no retry_after_ms, not safe to retry the same request. Emitted when a catalog entry has no backend runnable on this machine, or the selected backend is not runnable here. details: {catalog_id, lane_id | null, backends[] {backend, reason}}. Consumer disposition: do not retry on this host; select another model or host.
  • model_in_use — class permanent, no retry_after_ms, not safe to retry the same request. Emitted when a removal or unload would disturb a model that is still held. details: {catalog_id, holders[]}, each holder {kind: "loaded", model_id} | {kind: "job", job_id} | {kind: "self_check", check_id} | {kind: "request", lease_id}. Consumer disposition: release or wait out the named holders, then issue a new request.
  • download_failed — class transient, retry_after_ms: 1000, safe to retry the same request. Emitted when a model download fails on the network, on an HTTP error status, or on full storage. details: {file, reason: network | http_status | storage_full, http_status | null}. Consumer disposition: retry the download after retry_after_ms.

Catalog result-envelope rule and reused errors

Semantic refusals specified with details use {"result":{"module_generation": <u64>, "error": <wire error>}}, including semantic invalid_request from download, remove, unload and wrong-task serving. Malformed or unknown-field params remain channel-level invalid_request with a message. A failed download carries error beside state in its flat status.

  • artifact_invalid — permanent, not safe to retry unchanged. Digest/size mismatch or missing/wrong-size cached blob: {file, expected_sha256, actual_sha256, expected_size, actual_size, recovery_op: "models.download"}. Loaded fingerprint mismatch: {model_id, expected_fingerprint, actual_fingerprint}. Corrupt blobs are quarantined and affected installs/pins invalidated; other blobs are untouched. Explicit download repairs the file; serving never fetches.
  • module_restarted — permanent, not safe to retry the same job. Non-terminal downloads fail and clean up on restart; committed installs survive. Submit a new download attempt. No catalog-specific details object is added.
  • deadline_exceeded — transient, not safe to retry the same request; request budget expired before readiness. No catalog-specific details object is added.
  • Wrong-task invalid_request — permanent, not safe to retry unchanged; {catalog_id, requested_task, catalog_task}, without load or network I/O.
  • Derived-id management invalid_request — permanent, not safe to retry unchanged; download/remove carry {model_id, catalog_id}, including undeclared reserved backends. Unload with a catalog id carries {catalog_id, lane_ids[]}.
  • substitution_rejected — permanent, not safe to retry unchanged. A lane id combined with another fingerprint carries {lane_id, required_fingerprint}. Unknown fingerprints also refuse without load. Aliases and allow_equivalent never substitute catalog fingerprints.
  • engine_crashed — transient on catalog paths, retry_after_ms: 250, safe to retry; includes load/runtime failure or the 2000 ms self-check ceiling. A quarantined catalog worker also uses this code, never probe_required: retry_after_ms is the remaining time in its fixed ten-minute cool-down and details is {lane_id, quarantined: true}. The next serving request after expiry starts a fresh worker with zero strikes. Unload/reload or a module restart clears catalog quarantine immediately. Legacy/free-form workers keep permanent probe_required and their existing recovery contract. Worker crash, protocol fault and no-progress hang each add one consecutive strike; two strikes quarantine. Only fully drained successful serving work (embed, rerank/score or generation) clears strikes. LOAD/reload, PING, admission/eviction and setup never clear them. Caller-deadline expiry on live work neither strikes nor clears; its eventual detached serving success does.
  • model_loading — transient, retry_after_ms: 250, safe to retry. Inline serving waits up to min(remaining deadline, 5000 ms) for load/self-check; this code is used only when that cap expires with deadline remaining. A request that reaches a lane while its load-time numerical self-check runs waits for it until its own deadline and is then answered model_loading. Never returned because another request is executing or a Neural Engine shape is compiling. Admitted batch jobs wait within their execution budget and never fail with it.
  • shape_compiling — transient, retry_after_ms: 1000, safe to retry the same request. Direct Neural Engine lanes only. The worker compiles each padded sequence shape the first time it is used (tens of seconds) and cannot run any inference while it compiles. A request waits through a compile as long as its own deadline allows; this code is returned only when that deadline expires while the request is blocked on a compile: either the shape it needs is being compiled (several requests share one compile), or it was waiting for the lane's worker while another shape's compile occupied it at some point of that wait. When the module can no longer tell (more than 16 compiles since the wait began), it answers shape_compiling. details: {lane_id, shape}, where shape is the shape being compiled. Waits that expire on anything else keep deadline_exceeded. Consumer disposition: the model is loaded; retry after retry_after_ms. The compile continues without the request, so a retry joins it or finds the shape ready. A deadline that covers a first-time compile (about 30 s) avoids this code.
  • idempotency_conflict — permanent, not safe to retry unchanged; replace a download key bound to a different digest.

3. Common request fields (acceptance constraints)

Checks: embed_query_deadline_one_returns_typed_rejection (skeleton e2e) and catalog_multi_backend_resolver_refuses_substitution_before_load (catalog e2e).

deadline_ms (absolute budget), max_queue_ms (fast-fail bound — admission is atomic accept-or-reject against it), required_fingerprint, allow_equivalent (default false: hard substitution rejection), required_epoch, target_fingerprint (embed.query: route only to a matching or certified-equivalent profile, else substitution_rejected/not_certified).

4. Flow-scoped routes

Checks: tests::flow_routes_get_queries_only_and_other_routes_are_unchanged and tests::the_flow_refusal_names_the_method_and_the_flow (module library).

Synapse does not declare the flow-scopes/v1 capability, so the daemon keeps routes whose scope carries a flow_id away from it. If such a route is bound anyway, synapse serves on it only the operations declared query in its management registry. Every other method, including the mutations and any undeclared name, gets the channel-level error flow_scope_refused, naming the method and the flow. Routes without a flow_id are unaffected.

5. Operations

Checks: management_registry_matches_wire_contract, inventory_parse_survives_a_crlf_checkout, and drift_scanner_detects_added_ops_and_missing_documentation (wire_contract integration target). The operation-specific skeleton and catalog e2e tests prove runtime behavior; inventory checks alone do not prove numerical execution.

The management registry in this snapshot is embed.query, embed.batch, embed.result, job.resume, rerank.score, microllm.oneshot, owned_decode.admit_session, owned_decode.decode, owned_decode.snapshot, owned_decode.continue, owned_decode.abort, owned_decode.close, owned_decode.session_status, owned_decode.disable, owned_decode.revoke, model.load, model.status, model.unload, models.list, models.catalog, models.download, models.download.cancel, models.remove, probe.start, probe.status, probe.report, aliases.check_index, alias.retract, alias.declare, cache.pin, cache.gc, admission.status, approvals.migrate_owned_decode, approvals.enable, approvals.disable, approvals.emergency_rollback, certify.observations, lanes.pause, lanes.resume, and lanes.status.

  • lanes.pause {scope: "neural_engine", duration_ms, reason} — mutate. Starts a volatile pause of every direct-ANE and Core ML ANE lane, including cold lanes. duration_ms must be positive and at most 7,200,000 (120 minutes); a blank reason, unknown scope, or a second active pause is invalid_request. Returns {paused_until_ms, reason, lane_ids, phase, hardware_free} immediately. phase: "draining" means admitted requests are finishing normally. The module then unloads affected workers and releases the global direct-ANE lock. The duration starts at acceptance and includes draining; it is never extended to compensate for a slow or failed drain. Expiry stops waiting for drain and immediately re-acquires the lock instead of starting another hardware-free window. Wait for lanes.status to report phase: "paused", hardware_free: true before starting a private measurement daemon. Metal, rerank on non-ANE engines, and other unaffected lanes continue serving.
  • lanes.resume {} — mutate. Requests early expiry; idempotent when running. Returns the same status shape. It stops waiting for drain immediately, without cutting off admitted requests or abandoning an unload's lifecycle guards.
  • lanes.status {} — query. Returns the pause status above, or {phase: "running", hardware_free: false, lane_ids: []} with the pause fields absent. Expiry is automatic using a monotonic timer, even without another call. After expiry or resume the module re-acquires the global lock before opening admission. If a private daemon still holds it, phase: "waiting_for_lock" remains paused and a warning is logged on each one-second retry. No worker is loaded without the lock. The pause is not persisted: a module restart ends it. models.list adds paused_until_ms and pause_reason on affected lane rows only while paused, including drain and lock-reacquisition time. phase: "drain_failed" reports an unsuccessful drain with drain_error: {lane_id, stage, code, message} (lane_id: null for the global lock). It always has hardware_free: false; measurement scripts must not start on this status. Retained lock descriptors are retried until expiry or resume. Unload failures cannot be treated as confirmed worker exit and do not release the global lock. The deadline/resume ends either failure state, logs the blocked lane or global stage and error, then proceeds through lock re-acquisition and clears the pause. An unload already running keeps its lane non-ready and its lifecycle/serving guards until it finishes. Resumed callers use ordinary model loading/deadline errors for that lane rather than an indefinitely extended lane_paused. hardware_free is never reported for an incomplete drain.

While a lane is paused, inference is refused before admission, loading, numerical self-checks, or shape compilation with stable code lane_paused, class transient, safe_to_retry_same_request: true. retry_after_ms is the remaining pause duration, with a minimum of 1000 (also used while waiting for the global lock after expiry). Details are {lane_id, fingerprint, paused_until_ms, reason}. lane_id is the exact serving model/lane id, not a backend label or worker id. For catalog lanes fingerprint is the pinned catalog fingerprint, the same value as a successful reply, readable without loading. For legacy lanes it is omitted if not known while paused; absence means not known while paused, not null or a different identity. Callers may use lane_id plus fingerprint to confirm the pause is for their lane. Semantic refusals use the normal result error envelope. New durable embed.batch submissions are refused before job diversion. Already queued jobs that encounter the fence fail transiently with the same lane_paused error; admitted execution drains normally. Replay the identical embed.batch request (same request_key and content) after the pause clears to start a fresh attempt and reuse retained checkpoints. job.resume is for re-authentication, not lane pauses. Checkpoint reuse is bounded by job result retention. Pause/resume are mutate operations and therefore refused on flow-scoped routes.

Operator examples: ck-synapse-opctl lanes pause --minutes 45 --reason measurement, ck-synapse-opctl --json lanes status, ck-synapse-opctl lanes resume.

  • certify.observations {} — query, certification-only surface. Consumers must not use this operation or depend on its output. It refuses with the typed channel error certify_observation_disabled unless the generated certification config enables certify_observation (false by default). When enabled, it returns inventories, per-worker worker_requests counts, an independent supervisor admitted_count, and available. In-process Metal uses no worker IPC, so its complete snapshot has empty inventories and request counts and zero admissions. CUDA and Vulkan report request counts without ANE placement inventories. Direct ANE reports placement from its residency supervisor. Supervisor observation capture retains every successful ADMITTED inventory across eviction and confirmed-exit restart, separately from its cumulative admission count. Observation never changes computation.

  • Certification observation responses — consumers must not use this surface. With certify_observation enabled, inline embed and rerank responses include a top-level observation: input_ids are the engine-bound token sequences; catalog Qwen rerank also includes readout_ids: [yes, no]. Legacy unframed worker rerank instead discloses query_ids and candidate_ids as sent. Disabled mode omits the block entirely and records nothing.

  • embed.query {model, text, …constraints} — interactive class, the latency path. Full envelope incl. fingerprint/equivalent_to (one-comparison hot path).

  • embed.batch {model, items[{id, text}], …} — inline under the byte/item budget; over budget → job: {job_id} back immediately.

    • embed.result {job_id, cursor?, max_page_bytes?} — committed pages are readable while the job is still running; pages_available on job status is the visible committed-page count. The default page is 512 KiB; page order is job-internal (length-sorted execution) — items carry ids, KEY WRITES BY ID, never by position. This behavior is identical for local and remote jobs. Pages survive restart until the result-retention TTL. request_key makes resubmission idempotent: the same key and digest attach to or resume the same work; a different digest is idempotency_conflict. After a terminal restart failure, a fresh attempt keeps prior committed pages readable and resumes by item id.
  • rerank.score {model, query, candidates[], …} — RAW per-candidate scores (no server-side ordering opinions); interactive ≤20 candidates, bulk beyond. Catalog qwen3-reranker-0.6b declares ANE, CUDA and Vulkan lanes, but no Metal lane. Free-form local reranking accepts owned engines only, never llama.cpp.

  • microllm.oneshot {model, prompt, max_tokens, grammar?, …} — greedy. max_tokens is capped by module config microllm_max_tokens (default 512); requests above the ceiling are rejected with both numbers in the error. grammar (GBNF): unset or empty = free-text. When set, constrained decoding is permitted only if module config grammar_enabled is true (default false); otherwise grammar requests fail closed with typed grammar_disabled. When the constrained runtime is unavailable or not certified, grammar requests fail closed with the same reason; the retired grammar_unavailable_in_build code is not emitted.

  • models.catalog (query) {query?, task?, runnable_here?, installed?} → {catalog_revision, models[]}. Unknown params are rejected. Filters intersect; query is trimmed, case-insensitive substring search over id, name and description; task matches exactly. Rows are ordered by id. Each row carries {id, task, name, description, default_for_task, upstream {hf_repo, revision}, manifest_digest, download_bytes, install_state, backends[]}. Install state is not_installed, installed or stale; installed: false includes both not-installed and stale. A backend row is {backend, lane_id, fingerprint, runnable, reason, installed, self_check}; self-check is null without a current install. Reason is null when runnable, otherwise not_supported_on_platform, device_missing, device_below_floor or worker_missing. Download bytes sum distinct manifest files needed by runnable backends, independent of installation (zero with none). Catalog values and pinned upstream revisions come from the compiled document.

  • models.download (mutate) {catalog_id, request_key?} → a flat download status, initially queued or downloading, before off-request-path fetching completes. No automatic downloads occur. Checks, in order: a derived lane id is invalid_request {model_id, catalog_id}; an absent catalog id is unknown_model {model_id}; no runnable backend is backend_unavailable {catalog_id, lane_id | null, backends[] {backend, reason}}. Blank request keys are omitted; default is models.download:<catalog_id>:<manifest_digest>. Keys bind to the JCS SHA-256 of {catalog_id, manifest_digest}: different content is idempotency_conflict; non-terminal requests share one job per entry manifest. A committed binding is reusable while the entry is complete. Size-correct digest blobs are reused without network; only missing or wrong-size blobs are fetched. Every fetched body is size- and SHA-256-verified. The shared flat status shapes are {job_id, state: queued}, {job_id, state: downloading, bytes_done, bytes_total}, {job_id, state: verifying}, {job_id, state: committed}, {job_id, state: failed, error} and {job_id, state: cancelled}. Byte counts appear only while downloading and error only on failure. A zero-network-byte job moves directly from queued to committed. model.status {job_id} accepts these jobs and adds kind: "models.download". Failure codes include download_failed {file, reason: network | http_status | storage_full, http_status | null}, artifact_invalid {file, expected_sha256, actual_sha256, expected_size, actual_size, recovery_op: "models.download"} and module_restarted. Downloads have a 60000 ms no-body-byte timeout, not an absolute execution TTL. Terminal jobs and key bindings expire together after the configured result-retention TTL; non-terminal jobs are not purged.

  • models.download.cancel (mutate) {job_id} → the same flat download status. Cancellation is idempotent and affects every caller attached to the shared job. Terminal jobs remain unchanged. An unknown or expired job id is invalid_request. Publication, commit, cancel and failure share a publish fence: exactly one terminal transition wins, and a committed job is never cleaned up by cancellation. Cancel/failure removes staging and only newly published blobs with no remaining install, pin, registration or job root.

  • models.remove (mutate) {catalog_id} → {catalog_id, removed_manifests[], freed_bytes}. Removes every current and stale install and its self-check rows, then unlinks only unreferenced member blobs and owned compiled packages. Freed bytes are the on-disk bytes actually unlinked. Checks, in order: a derived lane id is invalid_request {model_id, catalog_id}; an absent catalog id is unknown_model {model_id}; any active holder is model_in_use {catalog_id, holders[]}. Holders are {kind: loaded, model_id}, {kind: job, job_id}, {kind: self_check, check_id} or {kind: request, lease_id}. An active first download is a holder even before any install commits. Only after the in-use check may an uninstalled entry return empty manifests and zero freed bytes. Malformed or unknown-field params remain channel errors; the listed semantic refusals are result-envelope wire errors with details.

  • models.list — catalog + per-model state, fingerprints, alias rows, recommended_batch, per-row token ceiling (max_tokens), ceiling source (max_tokens_source), bucket/shape ladder (bucket_ladder), output dimensions (dims), dtype/precision (dtype), device class (device_class), serving certification flag (certified), and warm-load cost hint (warm_load_cost_hint_ms).

    • recommended_batch (ADVISORY batch sizing per model; admission remains the enforcement — consumers should not carry their own batch knobs). When present, recommended_batch is { "rows": positive_integer, "token_budget": positive_integer }; rows is the advised request-row cap and token_budget is the corresponding aggregate token cap across an entire batch. The field is omitted when an engine has no measured policy.
    • max_tokens (positive integer) — the per-row token ceiling the lane will accept. Sourced from what the module actually enforces at request time (the largest loaded bucket on bucketed lanes such as ANE and Metal, or catalog config when unloaded). Consumers must use this row limit rather than inferring a per-row cap from recommended_batch.token_budget.
    • max_tokens_source ("worker_bucket" | "runtime_bucket" | "catalog_unloaded" | "catalog") — discloses the origin of max_tokens. Distinguishes live enforced bucket limits from unloaded catalog values so consumers know whether live worker state was consulted.
    • bucket_ladder (array of positive integers, e.g. [128, 256, 512]) — the ladder of sequence lengths the lane accepts. Present when the lane is loaded and enforces discrete bucket envelopes; omitted when unloaded or when the lane accepts continuous lengths up to max_tokens.
    • dims (positive integer) — output embedding vector dimensions. Present when reported by the loaded model or declared by a remote profile; omitted when unknown.
    • dtype (string, e.g. "f16", "f32", "bf16") — numeric precision of the lane.
    • device_class ("metal" | "ane" | "cpu" | "cuda") — execution device class.
    • certified (boolean, optional) — whether verified certification evidence exists for this lane under the machine profile the module is currently running. For lane classes that have a certification concept (owned engines, embed and rerank lanes) this is true or false; it is omitted for lane classes that have no certification concept (such as legacy worker-backed generate lanes). certified: true does not mean the lane will serve. Certification and serving approval are separate facts recorded by separate writes: a lane that is certified but not approved refuses every request (or, for a substitutable unconstrained request, is answered by a different lane). A consumer deciding where to send traffic wants serving_admission below.
    • serving_admission ("enabled" | "disabled", optional) — whether this lane is approved to take traffic, from the same projection probe.report uses, so the two surfaces agree. "enabled" requires both an enabled approval row and current certification evidence. Omitted for lane classes with no approval concept — absence means "not applicable", never "enabled".
    • serving_admission_reason (string, optional) — why serving_admission is "disabled", one of approval_absent, approval_disabled, not_certified, approval_unavailable, self_check_failed, or a recorded operator reason. Absent when the lane is enabled or has no approval concept.
    • warm_load_cost_hint_ms (float) — observed load duration hint in milliseconds, sourced from real measurements (such as observed load duration or benchmark probe data). This is an advisory hint, NOT a performance guarantee or upper bound; actual load latency depends on residency (whether the model is already loaded in memory) and system load. Omitted if no measurement has been recorded for the model.

    Local embed/rerank rows never report device_class: cpu; the current catalog reports metal only. Other device classes describe legacy or future lanes. Catalog rows are an exception to approval-based fields above. They add self_check (state: pending | passed | failed, with identity/evidence when available); free-form embed/rerank rows use state: not_applicable. For one release catalog certified mirrors self_check.state == passed and serving_admission is enabled unless self-check failed, then disabled with reason self_check_failed. No approval row is consulted. Next release removes these two compatibility fields from catalog rows in favor of self_check. Catalog rows exist for runnable, current-manifest installed backends only, have lane id <catalog_id>-<backend>, one fingerprint, and initial state unloaded with max_tokens_source: catalog_unloaded. Unload keeps the row; remove deletes it. Free-form field presence and decode gates are unchanged.

  • model.load / model.status, probe.start / probe.status / probe.report — load and probe start/status are job-shaped (poll-first); probe.report is a query. Probe is EXPLICIT, never auto-triggered; certification + perf rows are keyed by machine profile and stamped with os_build/module_generation; the report returns the full measured capability table plus per-knob assignments. Free-form embed/rerank ops and microllm.oneshot routed to an owned-engine lane refuse (not_certified/probe_required) until the (machine, fingerprint) pair is certified. Lane-1 worker-backed microllm.oneshot routes retain their existing dispatch path and do not acquire the owned-lane certification gate.

Catalog serving and migration

The compiled catalog has four frozen ids: gte-modernbert-base (embed, Alibaba-NLP/gte-modernbert-base, revision e7f32e3c00f91d699e8c43b53106206bcc72bb22), gte-reranker-modernbert-base (rerank, Alibaba-NLP/gte-reranker-modernbert-base, revision f7481e6055501a30fb19d090657df9ec1f79ab2c), qwen3-embedding-0.6b (embed, Qwen/Qwen3-Embedding-0.6B, revision 97b0c614be4d77ee51c0cef4e5f07c00f9eb65b3), and qwen3-reranker-0.6b (rerank, Qwen/Qwen3-Reranker-0.6B, revision e61197ed45024b0ed8a2d74b80b4d909f1255473). The first three declare only Metal; the last has no backends or files. No catalog ANE/CUDA/Vulkan backend ships in this release. On Linux/Windows the Metal rows remain visible but report not_supported_on_platform; runnable_here: true returns no entries and download_bytes is zero. There is no CPU fallback.

A catalog id selects the first runnable backend in catalog order; a declared lane id or fingerprint pins a backend. The selected lane never falls back to another backend when loading, failed or uninstalled. Serving uses cached local files only, lazily loads and runs self-check, never network I/O. Catalog lanes never read knob assignments or require probes/approvals. The self-check key is catalog id, manifest digest, backend, fingerprint, engine identity, OS build and fixture revision. Key changes reset it to pending; persisted numerical failure refuses without rerunning. Remove and redownload also permits a new check.

Worker quarantine is also a catalog serving error, not a probe/approval gate: it returns transient engine_crashed with {lane_id, quarantined: true} and a positive retry_after_ms equal to the remaining fixed ten-minute cool-down. The lane stays selected; no other backend is substituted. On expiry the next serving request starts a fresh worker with zero strikes. Explicit unload/reload or module restart clears the cool-down immediately. This automatic recovery is catalog-only; legacy/free-form lanes retain probe_required.

The same consecutive-fault/cool-down rule applies to direct ANE channels. Quarantine closes and confirms the old worker's exit before refunding resident shapes, and does not spawn/recompile replacements until recovery is allowed. Live work has no worker total-time cap: only lack of completed-work progress for 120 seconds during inference (600 during load/compile/setup) is a hang. Caller deadlines return deadline_exceeded without a strike and submitted work drains on its owner so the next request cannot receive a stale reply. Existing direct-ANE compile-blocked deadline waits retain shape_compiling.

Removed or rejected paths and their replacements

  • ORT and MiniLM product/catalog lanes are removed: use a catalog id and explicitly download it. ORT remains only as a bench reference, not a portable CPU floor.
  • Catalog-reserved probe.start targets return invalid_request: use automatic self-check after explicit installation, not operator probe or approval.
  • Omitted-model local embed/rerank knob routing is removed: specify a model or use the task's default_for_task entry (the two GTE entries).
  • Unregistered embed/rerank ids no longer return model_loading: use the unknown_model correction flow. Uninstalled catalog ids/lanes return model_not_installed; call models.download rather than polling a load.
  • Free-form embed/rerank with ort, llama or any non-owned engine is refused with invalid_request before network I/O: use an owned engine (owned-metal, ane, owned-cuda, or the owned Vulkan value when available). Invalid engine preload entries are skipped with one WARN; disallowed persisted rows are not restored and their pins are removed. Free-form lanes keep their probe gates.
  • Catalog certified/serving_admission are one-release compatibility projections: consumers must move to self_check before those fields are removed next release.
  • Unpinned source=hf, revision main, URLs containing resolve/main, and resolved HTTP(S) assets without SHA-256 are rejected before network I/O: supply a pinned 40-hex revision and a digest for each resolved network asset, regardless of source (including source=file).
  • model.load or preload using any catalog id or <catalog_id>-<backend> for metal, ane, cuda or vulkan is rejected: use models.download. Load returns invalid_request; reserved preload ids fail startup naming the entry. Undeclared derived ids are unknown_model for serving/status/unload, but invalid_request for download/remove; use a declared lane or catalog id.
  • model.unload with a catalog id is rejected: unload a declared lane id from details.lane_ids, then use models.remove to delete installation files.

Legacy preload lanes (including gte-modernbert-base-f16, gte-reranker-modernbert-base-f32 and the ANE package-set lane) remain separate: same ids, config, fingerprints and probe gates, with no implicit catalog mapping. Moving to a catalog id is deliberate and requires re-embedding. Owned decode routing, certification, probes and approvals are unchanged.

Fixed-bucket ANE package sets

An ANE model.load manifest may put the primary compiled Core ML package in files.model and additional fixed-sequence packages in ordered files.extra entries. Each entry is a detailed {url, sha256} file specification. The module caches every file separately, then derives one composite artifact digest from the ordered role set before minting the model fingerprint. The ANE worker loads all packages and selects the smallest bucket that can hold the longest item in an embed.batch; it never silently truncates an item to a smaller bucket. files.tokenizer remains required because Synapse owns tokenization.

The production worker accepts a zipped .mlmodelc bundle for each package. The archive is only a transport/cache representation; the worker verifies its per-file digest, materializes it into a temporary compiled-model directory, and loads it with CPU_AND_NE. A package set's placement gate is the minimum reported Neural Engine share across its buckets.

  • aliases.check_index {index_fingerprint, provenance_set[]} → valid | migration_required {retracted_pair, rebuild_target} + table_epoch. Call at write-commit when your index holds mixed provenance. Revocation is never retroactive: vectors written under a certified fingerprint stay readable; demotion affects the future only.
  • admission.status — advisory snapshot: queue depths, current knob, catalog-wide certification/performance health, and job outcome counters. Job counters are per-process since start:
    • jobs_minted: jobs admitted in this process;
    • jobs_completed: minted jobs that reached a successful terminal state;
    • jobs_failed: minted jobs that reached a failed terminal state;
    • jobs_inherited: jobs reaching a terminal state in this process that were admitted by a previous process (startup reconciliation of orphaned jobs or resumed jobs);
    • jobs_open: derived in-flight count satisfying the identity jobs_open = jobs_minted + jobs_inherited - jobs_completed - jobs_failed (saturating to zero, never underflowing). Download jobs count as minted; committed counts completed, and failed or cancelled counts failed, preserving the identity above. catalog_lanes counts every known lane. certified_lanes, certification_stale and performance_stale count only probe-gated lanes (free-form and decode), including unloaded ones, and exclude catalog lanes. Catalog lanes report certification_required: false, certification_status: not_required, and their self_check; probe.report lists known catalog lanes even when unloaded. The lanes array remains resident-only because its meeting_deadlines, rolling start-delay, waiter, and in-flight values describe live execution state. The contract lives in per-request budgets, not this snapshot.
  • cache.pin / cache.gc — model cache management (content-addressed, shared-lease readers, two-phase GC; GC never deletes under a live reader or a foreign pin).

Decode lane certification (additive)

The shipped decode fixture is selected by model family and dtype. For Qwen/Qwen3-0.6B f16 it contains 20 raw-completion prompts, each with 64 token IDs generated by the pinned greedy reference implementation. Decode certification is token-exact across every prompt; there is no numerical similarity tolerance. An uncertified row includes blocking_reason and the first differing token position, expected/actual token IDs, and the diverging prompt in its evidence.

probe.report.result.lanes[] adds:

  • certification_required: whether requests on this lane are gated;
  • certification_status: certified, uncertified, or not_required, based only on current-profile certification evidence;
  • serving_admission: enabled or disabled for owned-decode lanes, and null for lanes without approval-backed admission;
  • serving_admission_reason: null when enabled or not applicable, otherwise the approval's disable reason or a stable reason such as approval_absent or not_certified;
  • certification.status: certified or uncertified for a persisted probe outcome;
  • blocking_reason: token_mismatch, fixture_unavailable, a typed probe failure reason, or the existing probe_required/platform/quarantine reason.

A failed re-probe replaces the current machine-profile outcome with uncertified, so later owned-lane microllm.oneshot calls fail closed with not_certified. This demotion is never retroactive: results produced while the pair was certified remain valid. Successful probes also write the decode performance row used by knob assignment: cold-load milliseconds, median single-stream generated tokens/second over the fixture, and median one-item latency.

Sampling and all other non-greedy certification semantics are deliberately out of scope until sampling ships.

Owned-decode session operations (envelope v2)

Every successful operation below has the management response envelope {"result": <payload>}. Malformed parameters are channel errors with invalid_request; the owned-decode domain refusals below instead use {"result":{"module_generation": <u64>, "error": {"code", "class": "permanent", "safe_to_retry_same_request": false, "message"}}}. The domain refusal list is exhaustive for the checks in these handlers; a worker dispatch can additionally return the normal owned-route errors described by its route.

owned_decode.admit_session

  • Params: {catalog_fingerprint, caller_id, context_ceiling_tokens, generation: {mode: "greedy_top1"}, kv_configuration: {block_size_tokens, recurrent_state_grain_tokens}}. All fields are required and unknown fields are rejected. The only accepted generation mode is greedy_top1; KV block size is one of 256, 512, or 1024 tokens and the recurrent-state grain must be nonzero.
  • Result payload: {session_id, catalog_fingerprint, approval_generation, reservation: {reserved_embed_rerank_bytes, reserved_artifact_weight_bytes, reserved_session_kv_bytes, context_ceiling_tokens}}.
  • Errors: invalid_request, sampling_unsupported, artifact_unapproved, artifact_disabled, artifact_revoked, artifact_mismatch, unsupported_machine, invalid_context_ceiling, insufficient_memory, incompatible_resident_artifact, invalid_kv_configuration, owned_decode_unavailable, and store_failure.
  • Semantics: admission reserves the certified artifact and session KV budget, records the serving-session approval generation, and writes no session on a refusal. A session can only be admitted against the requested catalog fingerprint's current serving approval and matching certification.

owned_decode.decode

  • Params: {session_id, req_id, prompt, max_tokens, grammar?, deadline_ms?, max_queue_ms?}. All fields before ? are required; req_id is nonempty and max_tokens is positive. grammar is free text when absent or empty.
  • Result payload: {session_id, req_id, frames: [FrameEnvelope...]}. Each frame is envelope-v2: {protocol: "owned-decode-envelope-v2", protocol_version: 2, req_id, session_id, stream_seq, kind, ...}. stream_seq starts at 1 and is monotonic. A progress frame has {kind: "progress", progress: {committed_token_ids: [u32...], committed_token_count}}; the IDs are newly committed, while the count is cumulative. The terminal is {kind: "final"|"error", terminal: {req_id, session_id, committed_token_count, tokens_emitted, decode_fingerprint, processing_fingerprint, runtime_config_digest, worker_generation, derived_digest?, terminal_state, decode_mode, speculative_telemetry?}}. The session handler emits serial terminals; speculative_telemetry is absent.
  • Errors: invalid_request, unknown_session, session_still_in_flight, decode_scheduler_busy, request_cancelled, worker_protocol_error, scheduler_boundary_error, store_failure, owned_decode_unavailable, and normal owned-route errors such as grammar_disabled, queue_full, deadline_exceeded, model_loading, artifact_invalid, and engine_crashed.
  • Semantics: the response carries every frame produced by this invocation. A token becomes visible only after its scheduler and durable serving-session boundary both commit. The terminal accounts for exactly that committed prefix; an emergency revoke returns an artifact_revoked error terminal after the last committed progress frame rather than retracting tokens.

owned_decode.snapshot

  • Params: {session_id, position_tokens}; both fields are required and unknown fields are rejected.
  • Result payload: {session_id, retained_kv_session_id, retained_position, reused_blocks}.
  • Errors: invalid_request, unknown_session, session_still_in_flight, invalid_kv_configuration, invalid_kv_alignment, artifact_unapproved, artifact_disabled, artifact_revoked, artifact_mismatch, incompatible_resident_artifact, store_failure, and owned_decode_unavailable.
  • Semantics: snapshots are accepted only while the session is idle, at the LCM boundary of its KV block size and recurrent-state grain. The retained state is durable and belongs to that session and catalog fingerprint.

owned_decode.continue

  • Params: {session_id, retained_kv_session_id, req_id?}; unknown fields are rejected.
  • Result payload: {session_id, retained_kv_session_id, retained_position, reused_blocks}.
  • Errors: invalid_request, unknown_session, session_still_in_flight, retained_kv_unavailable, artifact_unapproved, artifact_disabled, artifact_revoked, artifact_mismatch, incompatible_resident_artifact, store_failure, and owned_decode_unavailable.
  • Semantics: continuation is only for the matching session's retained state at an idle boundary. When req_id is supplied, its retained stream prefix must match the snapshot position, so continuation neither replays nor skips an already committed token.

owned_decode.abort

  • Params: {session_id, req_id, retain_kv}; all fields are required and unknown fields are rejected.
  • Result payload: {session_id, req_id, abort: "requested", disposition: "removed_queued"|"deferred_to_boundary"}.
  • Errors: invalid_request, unknown_session, session_still_in_flight, and owned_decode_unavailable.
  • Semantics: abort is an acknowledgement, not a terminal. The active decode observes it at its next committed boundary. If retained, its decode result carries cancelled: {req_id, generation_id, committed_token_count} beside the final error frame; status recovery uses that same committed count.

owned_decode.close

  • Params: {session_id}; unknown fields are rejected.
  • Result payload: {session_id, closed: true, unload_artifact}.
  • Errors: invalid_request, unknown_session, session_still_in_flight, routing_error, store_failure, and owned_decode_unavailable.
  • Semantics: close is idempotent after a successful close, but cannot close an active decode. It completes durable serving state and may release the artifact only when no active sessions still require it.
  • Retention: a closed session (closed by owned_decode.close or by an artifact_revoked terminal) stays known for 15 minutes after it closed, so a retried close and a status query after a lost frame both still see it. After that window the module forgets the session: owned_decode.close, owned_decode.session_status, owned_decode.snapshot, owned_decode.continue, and owned_decode.decode all return unknown_session for it. A repeated close does not restart the window. Open sessions are never forgotten, however long ago they were admitted.

owned_decode.session_status

  • Params: {session_id, req_id}; both fields are required and unknown fields are rejected.
  • Result payload: {session_id, req_id, committed_token_count, state, retained_kv_session_id?}. state is either "in_flight" or {state: "terminal", terminal_state: "completed"|"aborted"| "artifact_disabled"|"artifact_revoked"|"failed"}.
  • Errors: invalid_request, unknown_session, unknown_request, and owned_decode_unavailable.
  • Semantics: use this result after a lost or gapped envelope-v2 frame. It is authoritative for the monotonic committed-token count and never revises history already committed by a progress frame. A closed session's terminal state stays available for 15 minutes after it closed; after that window the session is no longer known and status returns unknown_session (see the retention rule under owned_decode.close).

owned_decode.disable

  • Params: {catalog_fingerprint, reason}; both fields are required and unknown fields are rejected.
  • Result payload: {approval: {schema_revision, catalog_fingerprint, certification_record_id, artifact_id, state: "disabled"|"revoked", reason, approved_by, approved_at_ms, updated_at_ms, generation, semantic_digest}, invalidated_retained_states, active_sessions, termination_requested_sessions, unload_artifact}.
  • Errors: invalid_request and store_failure.
  • Semantics: disable fences new session admissions and invalidates retained KV states for that catalog fingerprint, but lets an already active decode finish normally. A previously revoked serving approval remains revoked. When unload_artifact is true, the module also unloads the resident decode worker for the model named by the catalog's certification record, whether or not any session for that catalog is still known.

owned_decode.revoke

  • Params: {catalog_fingerprint, reason}; both fields are required and unknown fields are rejected.
  • Result payload: the same ServingControlOutcome shape as owned_decode.disable, with approval.state: "revoked".
  • Errors: invalid_request and store_failure.
  • Semantics: revoke fences new admissions, invalidates retained state, and marks active sessions for artifact_revoked terminal accounting at their next committed boundary. It also requests the in-memory scheduler revoke and can unload the artifact when no active session remains.

Model and alias mutations

model.unload

  • Params: {model_id}. model_id is required; this handler does not reject additional fields.
  • Result payload: {module_generation, model_id, fingerprint, state: "unloaded", engine, task}.
  • Errors: invalid_request, unknown_model, model_in_use, model_loading, and engine_crashed. A catalog id carries {catalog_id, lane_ids[]}; use a declared lane id. An undeclared derived lane is unknown_model.
  • Semantics: unloading a ready model removes its loaded engine and owned-decode dispatch cache, but keeps the catalog model registered and its cached artifact available for later lazy reload.

alias.declare

  • Params: {left, right, evidence?}; fingerprint_a and fingerprint_b are accepted aliases for left and right. Both fingerprints must be nonempty; omitted evidence becomes {}. This handler does not reject additional fields.
  • Result payload: {module_generation, changed, table_epoch}.
  • Errors: substitution_rejected, invalid_request, and store_failure.
  • Semantics: alias administration must be enabled in module configuration. Declaring an already-live pair is idempotent (changed: false) and preserves its epoch; a new live pair records the evidence and advances table_epoch.

alias.retract

  • Params: the same shape as alias.declare.
  • Result payload: {module_generation, changed, table_epoch}.
  • Errors: substitution_rejected, invalid_request, and store_failure.
  • Semantics: retracting a live pair records its end time and evidence, then advances table_epoch; retracting a pair that is not live is idempotent and leaves the epoch unchanged. Retraction affects future alias reads only, not vectors or job pages already produced under the pair.

Owned-decode approval administration

Approval rows are identified by the pair (model_id, decode_fingerprint), not by model ID alone. The approval mutation path is a fenced store transaction: each changed identity receives a monotonic generation, and migration compares its single pinned seed marker before writing (the store epoch/CAS boundary). These wire operations do not accept a caller-supplied expected epoch. The server recomputes the row's semantic digest in the same transaction.

approvals.migrate_owned_decode

  • Params: {seed_revision, schema_revision}; both fields are required and unknown fields are rejected.
  • Result payload: {outcome, seed_revision, rows, marker, rendering}. Outcomes include applied, already_applied, invalid_seed, unmappable_identity, duplicate_identity, and transaction_failed; non-applied outcomes leave approval rows and the migration marker unchanged.
  • Errors: invalid_request, approval_migration_state_corrupt, and store_failure.
  • Semantics: the one-shot migration validates the pinned seed, resolves every seed identity to exactly one eligible catalog model, compares its marker under the fence, then writes all approval rows and the marker atomically.

approvals.enable

  • Params: {model_id, decode_fingerprint, grammar_enabled}; all fields are required and unknown fields are rejected.
  • Result payload: {model_id, decode_fingerprint, enabled, grammar_enabled, approved_by, approved_at_ms, semantic_digest, generation}.
  • Errors: invalid_request, operator_identity_unavailable, and store_failure.
  • Semantics: an authenticated operator identity supplies approved_by. Enable creates or re-enables exactly the requested identity; a new row starts at generation 0 and a re-enable increments its generation. The model ID must resolve to exactly one owned-metal-decode generate catalog entry.

approvals.disable

  • Params: {model_id, decode_fingerprint, reason}; all fields are required and unknown fields are rejected.
  • Result payload: {model_id, decode_fingerprint, enabled: false, disabled_reason}.
  • Errors: invalid_request and store_failure.
  • Semantics: disable changes exactly one approval identity. The reason must be nonempty; disabling a missing identity or a storage validation failure is reported as store_failure by this handler.

approvals.emergency_rollback

  • Params: {reason}; reason is required and unknown fields are rejected.
  • Result payload: {disabled, reason}.
  • Errors: invalid_request and store_failure.
  • Semantics: the reason must be nonempty. One fenced transaction disables every owned-decode approval identity across the approval lanes, writes that reason, recomputes each changed row digest, and increments each changed row generation. This approvals-table rollback does not itself invoke owned_decode.revoke or terminate already active serving sessions.

6. Divergence detector (content_sha256 and submitted_sha256)

Check: embed_query_row_over_ceiling_reports_submitted_and_content_hashes (skeleton e2e) proves submitted versus effective content disclosure.

Each embed item response echoes both content_sha256 and submitted_sha256. submitted_sha256 is the SHA-256 hex digest of the exact received text bytes before tokenization and before any truncation. content_sha256 is the SHA-256 hex digest of the text that was ACTUALLY EMBEDDED (the decoded kept tokens when the input was truncated).

A difference between content_sha256 and submitted_sha256 means the item was truncated and is not a transport error; truncation_disclosures details the submitted versus effective token counts. Consumers using hashes as transport- divergence detectors should verify against submitted_sha256. Equal hashes mean nothing was dropped; different hashes mean the row was truncated. This field is additive and backwards-compatible; existing consumers reading only content_sha256 are unaffected.

7. Consumer integration

Checks: job_resume_respawns_remote_job_and_pages_are_readable and embed_query_row_over_ceiling_reports_submitted_and_content_hashes (skeleton e2e).

  • Persist the envelope fingerprint verbatim and the alias-table epoch with an index. Treat a different fingerprint as a different vector space unless an explicitly accepted certified equivalence permits it.
  • Resume durable work with the same request key and bound digest, and collect every committed page by item ID rather than assuming page order.
  • Verify transport input against submitted_sha256; use content_sha256 and truncation disclosures to identify the text actually represented by a vector.

8. Operational notes

Checks: serving_admission_channel_fault_restarts_exactly_once (worker host) and job_resume_respawns_remote_job_and_pages_are_readable (skeleton e2e).

  • Crash domain: llama.cpp runs in supervised single-lane workers; engine crashes are typed (engine_crashed, transient with budget → quarantine as permanent probe_required on probe-gated lanes). ORT is not shipped; catalog lanes use self-check instead. Every request ends in result, typed error, or transport closure.
  • Health: cached state only; worker liveness/crash-window and probe-row staleness ride models.list / probe.report / health detail. ANE worker PING refreshes the last Neural Engine placement share used by explicit certification probes.
  • module_generation on every envelope: if it changes mid-conversation, re-poll jobs (prior-generation jobs are terminal module_restarted).

9. Additive wire contract v1.1: remote gateway and durable pages

Checks: remote_gateway_declares_calibrates_checkpoints_trips_and_recovers and job_resume_respawns_remote_job_and_pages_are_readable (skeleton e2e); credential_failure_dispositions_follow_class_not_code (remote runtime library).

All v1.1 fields are additive. Local responses retain their existing shapes unless this section explicitly aligns durable-page behavior across both lanes.

Provenance

Remote responses use the real EngineIdentity object and add remote as an optional sibling of engine:

"provenance": {
  "engine": {
    "engine": "remote_openai_compatible",
    "version": "<adapter semver>",
    "build_flags": {}
  },
  "remote": {
    "provider": "example-provider",
    "deployment": "api.example.org",
    "assurance": "declared"
  }
}

build_flags is a string-keyed object: it is an empty object or is omitted by serde when empty, never an array. remote is absent for local lanes. No existing provenance field changes shape. Owned-decode responses may add the optional chain_k provenance field; legacy readers must ignore additive provenance fields and no request wire field is introduced.

For assurance: "declared", content_sha256 hashes the exact post-truncation text Synapse submitted to the provider. In this lane, “embedded” means “submitted to the provider”; provider-side preprocessing is part of the declared risk accepted by the caller. submitted_sha256 echoes the SHA-256 of the received text bytes before any truncation.

Request opt-in

embed.query, embed.batch, rerank.score, and microllm.oneshot accept accept_declared: bool, defaulting to false. Requesting a model whose identity assurance is declared without accept_declared: true fails permanently with declared_identity_not_accepted.

Durable job status and resume

A credential pause has this status payload:

{
  "state": "paused_needs_reauth",
  "logical_handle": "provider-handle",
  "paused_at_ms": 1780000000000,
  "resume_deadline_ms": 1780086400000,
  "action": "reauth"
}

The execution TTL is suspended while paused; resume_deadline_ms is the only active clock. Consumers resume with job.resume {job_id} after repairing credentials, or by resubmitting the same request key and digest. Deadline expiry transitions durably to terminal needs_reauth_expired; result retention starts at that terminal transition.

Every embed job status carries pages_available (download jobs do not). embed.result serves any committed page whose index is below that count while the job is queued, running, paused, done, or failed. A page commit atomically makes its item results, page metadata, and incremented visible count observable. Previously committed pages remain readable after a later failure, including continuity quarantine. This page-while-running rule applies equally to local and remote jobs.

The request digest is bound at admission to the operation, synapse_model_id, constraints, ordered item ids, and per-item content hashes. Remote-bound digests also bind remote_profile_hash and logical_handle; local digests omit those two fields. Reusing a request key with different bound content is the permanent idempotency_conflict error. Same-key/same-digest resubmission resumes from committed item ids.

Provider request ids

When the provider returns no request id, provider_request_id is omitted entirely; it is never null or an empty string. Success envelopes and error payloads carry the final attempt's provider_request_id, limited to 128 bytes. Job pages carry provider_request_ids: [string], one per sub-batch, limited to 16 entries. Missing entries are omitted, and provider_request_ids itself is omitted when the resulting array is empty.

Stable error-code union and credential mapping

Every error retains {code, class, retry_after_ms?, safe_to_retry_same_request, message, details?}. The complete remote additions are:

  • declared_identity_not_accepted — permanent.
  • remote_identity_drift — permanent; the model is quarantined.
  • provider_unavailable — transient.
  • provider_protocol_violation — permanent; the model is quarantined.
  • idempotency_conflict — permanent.
  • needs_reauth — permanent-shaped inline rejection naming the logical handle.
  • needs_reauth_expired — permanent terminal job failure.
  • remote_deployment_changed — permanent boot or serve refusal.
  • credential_config_invalid — permanent vault-handle not_found or malformed configuration; it is never retried.

Remote paths reuse these baseline codes unchanged: module_restarted (the existing post-restart job state), invalid_request, and probe_required. Credential mapping is exact: vault not_found or malformed data maps to credential_config_invalid; an unreachable or restarting credentials module maps to transient provider_unavailable; vault_locked or needs_reauth enters the pause path for jobs and returns inline needs_reauth for inline operations.

10. Compatibility and load-time verification

Checks: tests::wire_contract_documents_every_management_operation (module library), all_sixteen_profiles_have_sealed_model_specific_subsets and tampered_subset_digest_refuses_and_names_profile (certification self-checks).

2026-08-29: complete management-operation coverage

Added the owned-decode session controls, owned-decode approval administration, model.unload, and alias declaration/retraction operations. The snapshot now records their parameter and result shapes, refusal codes, durable identity and fencing semantics, and envelope-v2 decode frames.

Grammar refusal retirement

grammar_unavailable_in_build is retired in favor of grammar_disabled. For a constrained microllm.oneshot request on a platform or machine without a certified owned-decode lane, the stable grammar_disabled refusal is returned; when applicable, the original owned-decode refusal remains available as additive underlying_owned_decode_refusal_id provenance. Constrained requests remain owned-only: they never fall back to llama GBNF or retry unconstrained.

Consumer notice: callers matching the legacy grammar_unavailable_in_build string must switch to grammar_disabled; the legacy code is no longer emitted.

Profile preload load-time checks

A preload with a manifest profile (<slug>.<lane>) uses catalog-style numeric admission, independently of certify_observation. Before serving it, the module runs a quiet, persisted self-check using a compact sealed subset derived from bench/parity/fixtures. All sixteen profiles use this single reference source; the existing catalog-install references are unchanged. An absent reference, invalid seal, or numerical mismatch refuses with self_check_failed, naming the profile. Failed checks stay failed for the same model fingerprint, engine, OS build, manifest and subset seal. A preload without profile retains its existing probe/approval gates, including probe_required before probing.

The fixed subset is short-0, boundary-127, boundary-128, boundary-129, plus pool10-000, pool10-001, pool10-002 for rerankers. It covers short input, both sides of the 128-token shape boundary, and a small ranking pool without running the costly 8192-token case at every load. The original fixture seals are verified during deterministic subset generation; at load the subset digest is checked against its committed subset index, the source seal is matched to the original committed fixture index, and fixture/model/revision identity is checked by the parity evaluator. Numerical and semantic gates and manifest tolerances are unchanged. Full-corpus and 8192-token coverage remain certification gates, not claims made by the small load-time check.

The ckdev-synapse-certify run --row <row> --model <catalog_id> --assets <aggregate-dir> --checkout <root> --mirror <dir> producer certifies each catalog backend on its declared hardware rows: metal-m5, ane-m5, cuda-*, and vulkan-*. The mirror serves pinned files from <hf_repo>/resolve/<revision>/<file>. The runner checks models.catalog, downloads through models.download and polls model.status to committed. Its first fixture request loads and checks <catalog_id>-<backend>; model_loading replies are retried within a bounded deadline and never count as completed fixtures. models.list must then report a passed self-check and exactly the catalog pin, and every successful fixture response must carry that same fingerprint. Direct ANE records include every successful ADMITTED inventory and the independent admission count. An unavailable observation stream is never interpreted as an empty, complete inventory. Current schema-3 records never hold a hardware UUID, only its salted SHA-256 digest (machine.platform_uuid_sha256 on Apple rows, machine.gpu.uuid_sha256 on CUDA and Vulkan rows); crates/synapse-certify/README.md gives the exact construction.