Reference

Action-only Observation Reference

Raw input admission, normalized types, matching semantics and implementation boundaries.

Action-only observation reference

Development-branch contract. Raw admission, bounded normalization, typed collections, desired comparisons, durable decisions, approval/automatic dispatch and shared REST/GraphQL/UI reads are implemented. Execution verification uses independent reads under frozen terms. Synthetic runtime/server/DNS workflows are verified; native execution, real-provider and release qualification remain separate. Unsupported persisted state is refused without conversion or deletion.

Definition contract

Define producers, subscribers, comparisons, policies and action-local finality in actions/*.toml. Use module.toml for module identity, parameters and dependencies. See authoring.

Producer identity is module:actions/path.toml. observe.from references a producer directly. Producers own normalization, fixed string scope and type overrides. Subscribers cannot override them. A producer can emit multiple named collections without executable commands or desired graph resources.

Artifact boundary

Only raw-input HTTP admission publishes observations. Result/state-file uploads do not normalize, reserve observation ordering, or produce observation receipts. Artifact uploads reject X-Rescile-Verification-Read; use the raw-input endpoint for an eligible controller-ticketed independent read. Ordinary result limits and state bookkeeping remain unchanged.

Comparison contexts

Controller consumers use InputService::comparison_context(as_of) to pin canonical action-selected desired values, immutable configuration and a durable observation revision together. New contexts refuse stale configuration, recovery or backward clock state rather than returning empty success. A previously pinned context remains a consistent read after reload or new observations; it is not fresh authorization for a later approval or dispatch.

origin_resource and match_on select intent using ordinary action semantics. Matching retains all ambiguous provider keys and never fabricates desired vertices. The action-only context does not use a rule/binding registry, persist a finding cache, normalize data or create decisions/jobs. The public APIs below consume the same pinned projection; policy reconciliation is a separate writer.

Offer projection

The read-only service aggregates action-owned comparisons. Any usable watched difference yields difference; all equal yields converged; missing or incompatible comparisons yield unknown. Unmatched, ambiguous or unavailable resources yield not_matched, never implicit creation, deletion or adoption. Explicit approval/auto mode does not itself grant execution authority. These projections do not create approvals, episodes or jobs, and do not release possible work.

Raw input

POST /api/v1/observation-inputs

Required content type: application/json. Content encoding is not supported. The active workspace is implicit.

{
  "version": 1,
  "producer": "cloud-servers:actions/inventory.toml",
  "submission_id": "11111111-1111-4111-8111-111111111111",
  "read_at": 1793692800,
  "collections": {
    "server": {
      "coverage": "complete"
    }
  },
  "payload": {
    "instances": [
      {
        "id": "vm-739",
        "name": "server-a",
        "vcpus": 4,
        "network_ids": [
          "network-9"
        ]
      }
    ]
  }
}
Field Meaning
version Exactly 1
producer Known captured producer declaration
submission_id Non-nil UUID; never reuse for another input
read_at Nonnegative Unix seconds when the provider read occurred
collections Named coverage claims: complete, partial, or failed
payload Raw JSON supplied to pure Jsonnet

Unknown envelope fields are rejected. Scope, params, lookups and authority are not caller fields. New timestamps allow at most 60 seconds of future skew. A controller-issued verification-read header binds a separate independent execution read; ordinary raw inputs never establish terminality. Verification requires the captured producer, scope and recipe, complete target evidence, and same-read provider plus runner/root/descendant/resumable terminal assertions. Equality or exit zero is not proof. Durable finalization and journal scope closure remain separate.

A successful 202 response contains producer, submission ID, durable per-producer sequence, disposition (accepted or replayed) and normalization status. Statuses are pending, applied, partial, failed, late, or superseded. Admission is durable before acknowledgement; normalization is asynchronous.

Replay, ordering and retention

Canonical interpreted envelope content detects exact replay and changed-content conflicts for 1024 receipts per producer. Exact wire JSON is retained separately. A persisted retired-read floor refuses expired unknown submissions. This is a finite replay guarantee: UUID reuse with changed time outside the window is undetectable and forbidden. Execution deduplication must never use this window.

Read time prevents late inputs replacing newer facts; controller sequence orders equal-time inputs. Complete coverage replaces membership. Partial coverage replaces whole reported rows without refreshing unreported rows. Failures retain unusable history. Missing output/coverage, type failures and duplicate keys fail collection-locally when the normalizer can isolate them.

Active raw/context retention is capped at 16 inputs per producer and 32 MiB aggregate. After 24 hours, unpinned completed raw inputs are eligible for admission-triggered cleanup. Pending and proof-pinned inputs cannot be evicted. Capacity refusal leaves durable pending inputs intact, without partially publishing results.

These limits do not erase immutable generations or backups and do not bound total historical disk usage. Normalized rows retain self-contained provenance.

Normalization and types

The result is an object mapping collection names to row arrays. Each row has a nonblank unique string key, optional string name, and values object. Logical types are null, boolean, integer, float, string, array, and object. Inline overrides use observe.types.<collection>.

No string/boolean coercion occurs. Integer requires an integer JSON representation; float permits numeric JSON and retains float metadata even for an integer representation. Explicit null is distinct from absence. Non-null overrides reject null; nullable override syntax is deferred.

Jsonnet numbers must stay within ±(2^53−1). Large provider IDs must already be strings. Unsafe input is a global failure; unsafe output can fail its collection independently.

The owned worker receives only payload, frozen params, controller context (producer_id, scope) and explicit lookups. No imports, application native functions, network, environment or Vault acquisition. Limits: one active child per controller process, 5-second active wall/CPU budget, 1 GiB address space, logical stack 128, 2 MiB request, 4 MiB response and JSON depth 64. Cancellation kills/reaps the child. Unsupported worker platforms refuse. Address space is not RSS.

HTTP limits: 1 MiB body, four shared body readers, 16 pending inputs, 60 requests/minute/known producer and a bounded body-read deadline. Existing CE open access and optional EE authentication remain unchanged.

Matching and comparison

origin_resource selects desired graph vertices. observe.match selects one desired property and one observed property. Observed name/key are reserved metadata; other names address values. No fallback, string coercion, graph-UUID identity, implicit adoption or deletion occurs.

Matching preserves ambiguity. Desired-only, observed-only, invalid-match, evidence-unavailable and matched states are distinct. Partial inventory cannot prove absence. Failed, stale, future-dated or configuration-invalid evidence does not produce usable differences. Each row keeps its own read time.

Comparisons have stable IDs: explicit id, otherwise property. IDs are unique within an action; repeated properties require explicit IDs. observed_property defaults to property; operator defaults to equal. Logical types must agree: integer and float are not implicitly interchangeable. Same-kind floats compare numeric values. Arrays use ordered equality; DNS sets require explicit normalization.

Equality, difference and unknown are read results, not execution authorization. Only watched/matching desired properties are projected, not arbitrary graph fields. Default comparison freshness is 60 seconds; action-local max_age overrides it.

Frozen material and software compatibility

Recipes freeze an evaluator ABI derived from normalization source, dependencies, compiler and target/code-generation inputs. Controller and worker must agree before results are accepted. Incompatible retained recipes refuse store reopen before cache publication/pruning; incompatible candidate state refuses before commit. A worker ABI mismatch leaves admitted input pending and prior collections unchanged. Older development state without the required stamp is also refused. These stamps check compatibility, not binary provenance or attestation; they are conservative and can change between builds. No automatic migration, replay under new semantics or reset.

Full configuration pins include captured observation material and execution-policy compatibility. Capture checks source bytes against both the immutable declaration and its file manifest, including runtime files of zero-instance declarations. Source/parameter/registry changes cannot silently substitute new policy at handoff. Execution integration uses these pins before protected delivery and independent reads.

Shared public reads and explicit decisions

REST uses the active workspace implicitly: GET /api/v1/observations/collections, resources, differences, or decisions. GraphQL uses the stable observedState root with kind selecting the same four views. Its context, items and page JSON scalars have the same shape as REST. No producer-specific schema file or schema rebuild follows an observation update.

Each envelope contains status (available or unavailable), context, items and page (has_more, next). Context binds configuration/observation revisions, Unix-second as_of, latest read time/age, normalization backlog, ceiling and pause controls. GraphQL aliases share one pinned context. Unavailable is never empty success. A declared but unreported collection is not_reported; complete empty is an available collection with zero rows. Failed collections retain unusable history.

Typed values expose kind, JSON value, and lossless exact_json text. Use exact_json for exact display: ordinary JavaScript JSON numbers can round large desired integers. Null and missing stay distinct. Resource availability is not execution eligibility; action comparisons enforce row freshness and matching. Observed-only rows, provider keys and desired matches remain visible without creating graph vertices.

Exact filters: producer, collection, name, key, action, status. Apply filters relevant to the selected kind; resource action selects desired matches. first defaults to 25, bounded to 1–100; filter strings are bounded to 2048 bytes. after binds kind/filters/configuration/observation revision and as_of, expiring in five minutes. Changed context/cursor requires refresh (REST 400 or GraphQL error), not merged pages. A GraphQL request has a bounded projection budget. Reads never normalize, reconcile episodes, approve, collect, enqueue or dispatch. Raw inputs, normalizer source and frozen lookup payloads are not public read items.

Decision items include frozen configuration/material, independent finality target, effective mode, CAS revision, approval and durable execution status. finalized is not a claim that the journal scope is free; scope_closure: not_asserted is explicit. closed_and_rearmed records both closure and fresh valid convergence.

POST /api/v1/observations/decisions/{id}/approve or /reject accepts JSON with exactly configuration, material, expected_revision copied from the displayed record. No scope/action/input override is accepted. Approval revalidates current material and independent finality identity. Conflicts return 409: refresh/review, never automatically retry an uncertain acknowledgement. Approval does not bypass current dispatch checks. Queued/possible work cannot be rejected into an unlocked state.

POST /api/v1/observations/pause accepts expected_revision, paused, optional action; use context.control_revision. Omit action for workspace pause. Controls cannot raise ceilings. Commands use existing CE/EE authentication and bounded JSON bodies. The decision book retains at most 10,000 records, then refuses new decisions rather than evicting execution history.

Policy and finality boundary

trigger.on = "difference" selects approval (default) or auto policy. Startup auto_execute is unrelated. Approved episode rules require configuration-bound approvals and fresh valid convergence plus closure of previous possible work before rearming. Durable decisions deduplicate equivalent reports/restart. Rejection suppresses the episode even as observations change. Changed material invalidates unsent approval. Both normalization recipes are bound. A durable collection authority epoch prevents failure/recovery between policy ticks from reviving an old approval. Competing automatic actions for one logical scope are held.

An executable action’s [execution_finality] declares producer, collection, settle_timeout, timeout, and max_age. Positive whole-second durations are required, with timeout greater than settling. References resolve directly to producers. These declarations do not replace independent proof or runtime/scope qualification.

Possible execution never unlocks from equality, exit zero, timeout, cancellation or disconnect. Independent same-read state and provider plus runner/root/descendant/ resumable terminal assertions remain mandatory. Existing records and journals are preserved; unsupported state is refused, not overwritten or automatically migrated.