Skip to content

Pimatika Architecture

Code-grounded architecture overview for pimatika-core. For the trust-boundary statement see SECURITY-MODEL.md; for runtime usage see README.md.

Premise

Modern software has a structural gap: the build knows what the program is supposed to do; the runtime sees what it actually does; the audit log records facts after the fact. No artifact ties these three together at the moment of action.

Pimatika is the runtime piece of a five-brick chain that closes that gap by making the build's intent enforceable and the runtime's behaviour provable:

Brick Repo Role
Source analysis (upstream tooling) Extracts egress facts (URLs, hosts, methods, sinks) from source — produces a draft BIM
BIM (Build Intent Manifest) pimatika-core::bim Signed YAML constitution — the law of what the workload is allowed to do
Pimatika pimatika-core::enforcer In-process judge — checks each runtime action against the BIM, returns a decision
JAQ (Jagora Adaptive Quarantine) pimatika-core::drift Adaptive containment — when behaviour drifts, restrict before killing
Litatoli litatoli Cryptographically chained evidence log — every decision becomes a signed, audit-grade record

pimatika-core ships the middle three (BIM, Enforcer, drift accumulator + JAQ L1/L2). It links to litatoli-core for evidence emission and consumes a BIM that a source-analysis tool generated.

What this MVP is — and isn't

v1 in scope

  • Semantic egress control (default-deny outbound; allow only declared usage_contracts).
  • Signed BIM (keyed-BLAKE3 MAC).
  • Signed evidence (chain-linked via litatoli-core).
  • Adaptive containment — JAQ L1/L2 overlays, signed, time-bound, reduction-only.

v1 out of scope (for the in-process Enforcer)

  • Filesystem-mediated egress — the Enforcer does not constrain disk writes. (The BIM can now DECLARE a signed filesystem section; it is carried and signed here but enforced downstream by NaZelo, not by the Enforcer.)
  • Process-spawn / env-var / secrets governance — likewise declare-only: the BIM process and secrets sections are signed here, enforced downstream.
  • DNS pinning (Pimatika compares the string the host supplies; DNS rebinding between Pimatika's check and the actual connection is not detected).
  • Kernel- or network-level impossibility guarantees (see Trust Boundary below).

Declared-capability sections (delivered, declare-model). The BIM schema now carries optional filesystem, process, secrets, service_graph, and action_contracts sections under runtime_capabilities, and richer L7 fields on each usage contract (max_rate_per_min, body_schema, allowed_fields, blocked_data_classes). Pimatika signs all of them as part of the canonical BIM; the in-process Enforcer still governs egress only (target/purpose/data_class/ method/payload), and the declared capabilities are projected and enforced by NaZelo's L7 stream proxy.

Post-v1 addition (delivered): the BIM now supports an optional Ed25519 dual signature (policy_identity.ed25519_signature + ed25519_pubkey) over the same canonical bytes as the MAC — the non-repudiable, third-party-verifiable path (sign-bim / verify-bim-ed25519, no shared secret needed, fail-closed pubkey pinning). The keyed-BLAKE3 MAC remains the mandatory in-domain signature.

Filesystem is out of constitutional scope for v1, not out of security scope — OS-level hardening (read-only rootfs, gVisor, etc.) remains a deployment prerequisite.

Trust Boundary (cooperative model)

Pimatika v1 is an in-process cooperative enforcement control. The host process is responsible for submitting each candidate outbound action to Enforcer::check and honouring the returned EgressDecision. A host that does not call the API can still emit outbound connections.

This bypass risk is an explicitly accepted MVP limitation on a bare host. Inside a NaZelo sandbox it is closed: pimatika-nazelo compiles the manifest's targets into NaZelo's eBPF allow-list, and the kernel drops anything the manifest does not name — the workload cannot decline to be enforced. Off that path the control stays cooperative, and deliberately so: filtering egress at the node is a solved problem with mature implementations, and rebuilding it would put us where they already are. See SECURITY-MODEL.md §MVP Trust Boundary Statement.

The cooperative model was chosen deliberately to prove the BIM/judge/ evidence thesis with the smallest engineering footprint before tackling network-topology interception.

BIM Schema (v0.1)

A BIM is a signed YAML document. Required top-level sections:

runtime_identity:    # opaque structured field — preserved verbatim
workload_identity:   # opaque — typically image digest + repo URL
policy_identity:
  signer_id:         # human-readable signer identity (struct field `signer_id`)
  trust_anchor:      # trust-anchor identifier
  bim_digest:        # blake3:<hex> of canonicalised BIM (sans signature)
  signature:         # keyed-BLAKE3 MAC over the canonical bytes
  signature_algorithm: blake3-keyed   # optional, pinned when present
  signature_key_id: <key-id>          # optional, enables rotation
component_scope:     # opaque — which sub-component this BIM governs
attestation_proof:   # opaque — link to build attestation (in-toto, SLSA, …)
intent_profile:
  usage_contracts:           # the heart of v0.1
    - name: <handle>
      target: <scheme://host[:port]>   # http(s): origin-only; grpc: grpc://service/Method; OT: host[:port]
      purpose: <string>      # e.g. fetch reference data, ota_metadata, …
      data_class: <string>   # e.g. public, telemetry, …
      allowed_methods: [POST, GET, ]   # http(s): required uppercase HTTP verbs; grpc: ignored; OT: optional string allow-list
      max_payload_kb: 8
      # Optional richer L7 constraints — carried and SIGNED by Pimatika, enforced
      # downstream by NaZelo's L7 stream proxy (the Enforcer does not read them):
      max_rate_per_min: 120        # optional request-rate ceiling
      body_schema: "<json-schema>" # optional JSON Schema the request body must satisfy
      allowed_fields: [a.b, c]     # optional dotted field-path allow-list for the body
      blocked_data_classes: [phi]  # optional data classes the body must not carry
                                   # CLOSED set — see the note below `data_class`
runtime_capabilities:
  network_egress:
    default_policy: DENY     # MUST be "DENY" (uppercase); load fails otherwise
  # Optional declared-capability sections (declare-model): filesystem,
  # process, secrets, service_graph, action_contracts. Pimatika signs them;
  # NaZelo projects and enforces them. The Enforcer itself governs egress only.

Note: all closed BIM structs reject unknown fields (deny_unknown_fields), so a stray/misspelled key fails at load rather than being silently ignored.

Target grammar (normative): usage_contract.target MUST be scheme-prefixed and MUST NOT carry userinfo (credentials such as user:pass@host are rejected at resolution across BIM, overlay, and runtime). http(s) targets are origin-only (scheme://host[:port], no path); grpc targets are grpc://service-name/MethodName. OT/industrial schemes are supported too — modbus, s7, mqtt, mqtts, opc.tcp, coap, coaps, dnp3, bacnet — as scheme://host[:port] (host:port only, no path). Each scheme has a well-known default port (http 80; https/grpc 443; modbus 502; s7 102; mqtt 1883; mqtts 8883; opc.tcp 4840; coap 5683; coaps 5684; dnp3 20000; bacnet 47808), so the port may be omitted. The vocabulary is a table, not a closed enum: a scheme with no known default is not rejected — it is valid when the target names a port explicitly. Bare hostnames are rejected at BIM load.

Method grammar (scheme-aware): for http/https contracts, allowed_methods MUST be non-empty uppercase ASCII HTTP verbs and the request method is matched against them. For grpc contracts the RPC method is the /MethodName segment of the target and is enforced by exact target resolution; allowed_methods is not consulted (it may be empty and its entries need not be HTTP verbs). For OT/other schemes the operation vocabulary (function codes, topics) lives at the protocol layer, so allowed_methods is optional — an empty list matches any method; a non-empty list is honoured as a plain string allow-list and its entries need not be HTTP verbs.

HUMAN_REQUIRED fields: any usage_contract shipped with purpose, data_class, or max_payload_kb left as the literal string HUMAN_REQUIRED causes Bim::load_and_verify to fail — even after signing. The string fields (purpose, data_class) are checked (after trimming whitespace) in validate_schema; the numeric max_payload_kb rejects the placeholder earlier, at YAML parse time (a string cannot deserialize into u64). A draft BIM from source analysis must be reviewed and completed by a human before it is signed.

Unfilled identity PLACEHOLDER: the industry BIM templates (and any draft) ship their identity fields — signer_id, trust_anchor, the runtime/workload/attestation digests, owner/environment, signed_at — as the literal sentinel PLACEHOLDER (bare, or the blake3:PLACEHOLDER digest form). validate_schema rejects any of these left unfilled, so signing a template verbatim cannot produce a signature-valid but unattributable BIM. The check is case-sensitive: lowercase placeholder strings are treated as real (filled) values.

Recommended data_class / purpose vocabulary (automotive / SDV). data_class and purpose are open strings (bim.rs), so a domain may use any value — but a consistent vocabulary makes the signed evidence comparable across runs and reports. For Software-Defined-Vehicle workloads the recommended values are:

Field Recommended automotive values
data_class telemetry, perception_metadata, control_plane_command, diagnostic_data
purpose diagnostics, ota_metadata, simulation_feedback, fleet_evidence

data_class and blocked_data_classes do not share a vocabulary. The singular data_class labels what a contract carries: an open string, compared literally to the request, so any word works. The plural blocked_data_classes names what the enforcer must FIND in a body, so it is limited to the classes the enforcer can actually detect — pci, phi, secret, pii, public, plus whatever the deployment has added. A name outside that set is a rule that can never fire, and the run is refused before it starts rather than passing with a silent rule. diagnostic_data is a valid data_class and not a valid blocked_data_classes entry.

This is a naming convention, not new enforcement: Pimatika v1 stays cooperative and egress-centric — no kernel/network enforcement, no hard real-time safety (see "What this MVP is — and isn't"). The vocabulary only lets an ISO 26262 / TARA report group egress contracts by automotive data class; it implies no safety guarantee and no "certified" claim.

Signature: bim.rs canonicalises the document (stable key order, no signature field), digests the canonical bytes with BLAKE3, prepends blake3: to the hex, stores that as bim_digest, and signs the same canonical bytes with the keyed-BLAKE3 MAC. Verification recomputes the digest and the MAC; mismatch on either is fail-closed.

Ed25519 dual signature (optional, non-repudiable): policy_identity may additionally carry ed25519_signature + ed25519_pubkey, produced by Bim::sign_yaml when an Ed25519 key is provisioned (litatoli key resolution; litatoli keygen). Both signatures cover the same canonical bytes; the pubkey is inside the signed content (only the signature fields are zeroed during canonicalization), so it cannot be re-bound without re-signing. MAC-only BIMs keep byte-identical canonical output — full backward compatibility. Bim::verify_ed25519 is the auditor path: it verifies digest + Ed25519 only, needs no shared secret, and fails closed unless the expected public key is pinned (or unpinned mode is explicitly allowed). stamp-bim timestamps the Ed25519 signature when present, keeping the whole auditor chain (pubkey → signature → RFC 3161 time) verifiable without the MAC key.

Enforcer call-flow

host process
   │ EgressRequest { request_id?, run_id?, workload_id, target, method,
   │                 payload_kb, purpose, data_class }
Enforcer::check(&request)                     // &self; check the loaded BIM
   ├── 0. Expire the active overlay if due (clear it; emit OVERLAY_EXPIRED once)
   ├── 1. Resolve target → ResolvedTarget (scheme/host/port[/grpc method])
   ├── 2. Collect ALL usage_contracts on that target (candidates)
   ├── 3. For each candidate: purpose, data_class, method (scheme-aware),
   │      payload_kb ≤ max_payload_kb — first full match wins
   ├── 4. Apply overlay if any (overlay ∩ BIM — reduction only)
   └── 5. Emit Evidence Event via EvidenceWriter (fail-closed when strict)
EgressDecision { event_type, decision_reason?, decision_detail,
                 request_id, request_id_missing_warning, run_id,
                 idempotency_key, event_id, bim_digest, overlay_id?,
                 workload, purpose, target, method, payload_kb,
                 data_class?, timestamp }
   │   event_type   ∈ { EGRESS_ALLOWED, EGRESS_DENIED }
   │   decision_reason (denials only) ∈ {
   │        TARGET_NOT_IN_USAGE_CONTRACTS, METHOD_NOT_ALLOWED,
   │        PAYLOAD_EXCEEDS_LIMIT, PURPOSE_MISMATCH, DATA_CLASS_MISMATCH,
   │        OVERLAY_RESTRICTED_DESTINATION, OVERLAY_RATE_LIMITED,
   │        BIM_SIGNATURE_INVALID, BIM_SCOPE_MISMATCH,
   │        EVIDENCE_WRITE_FAILED (fail-closed: strict evidence write failed) }
host honours (or rejects) the decision and issues — or doesn't — the call

When the evidence writer is strict (audit-grade, e.g. LitatoliEvidenceWriter), an allowed egress whose evidence record cannot be persisted is downgraded to EGRESS_DENIED — the host never proceeds without the promised audit record.

Pimatika never transports the request itself. It is a judge, not a proxy. This is what "cooperative" means in practice.

Every EgressRequest SHOULD carry a caller-supplied request_id for end-to-end correlation. When missing, Pimatika generates a UUIDv7 and sets request_id_missing_warning: true on the decision — downstream idempotency must not assume Pimatika-generated IDs are stable across retries.

Drift accumulator + JAQ overlay

DriftAccumulator (drift.rs) ingests MetricsSnapshots (volume, frequency, anomaly signals) and emits JaqProposals when behaviour diverges from the signed intent. Each check_anomaly compares the active window against both the recent-window baseline (a burst that is anomalous for this workload) and the absolute DriftBaseline floor (the configured production ceiling), returning the more severe. The absolute floor is evaluated on every check — not only before window history exists — so sustained abuse that becomes the recent baseline keeps emitting proposals carrying the real (hot) observed rate, and downstream L4 escalation does not go silent. Levels:

Level Effect Duration Where
L1 Logging burst reduced, observability raised auto-expire 15 min pimatika-core
L2 Throughput reduced, destinations minimised auto-expire 30 min, limited renewal pimatika-core
L3 Tuned production-grade containment pimatika-pro (premium)
L4 Hard isolation pimatika-pro (premium)

Overlay fusion rule: the effective policy is

P_eff = BIM ∩ Overlay_t

Overlays can never widen rights — only reduce. They are signed (sign_overlay), time-bound (expires_at), and rate-limited (no infinite renewal). On expiry the effective policy falls back to the unmodified BIM.

L3/L4 escalation thresholds are intentionally not in pimatika-core; they are the commercial differentiator shipped in pimatika-pro. The public OSS surface tops out at L2 by design.

Evidence (Litatoli integration)

The default EvidenceWriter is LitatoliEvidenceWriter, which appends each decision to .jagora/evidence/pimatika-egress.jsonl via litatoli-core::AppendOnlyLog. Each entry is:

  • canonicalised (stable key order)
  • chain-linked (prev_hash field → keyed-BLAKE3 hash of the previous entry's canonical bytes)
  • signed (keyed-BLAKE3 MAC over the canonical bytes)
  • monotonically sequenced

Verification is litatoli-cli verify-ed25519 <file> (replays the chain; fails on any seq/prev_hash/signature break). The verify-chain sub-command for richer integrity checks is in the litatoli repo.

Open-core layout

Crate / repo Licence What it contains
pimatika-core (this repo) BSL 1.1 (→ Apache-2.0 on Change Date) Bim, Enforcer, DriftAccumulator (L1/L2), Overlay, LitatoliEvidenceWriter
pimatika-cli (this repo) BSL 1.1 (→ Apache-2.0 on Change Date) Integration-test harness (verify-bim, verify-bim-ed25519, sign-bim, check, sign-overlay, overlay-canonical, stamp-bim, stamp-overlay, bundle-create, verify-bundle)
pimatika-pro Proprietary L3/L4 drift heuristics, tuned production thresholds, industry BIM templates
pimatika-saas Proprietary Managed BIM signing service, multi-tenant trust anchors

Each tier extends the trust boundary documented in this repo's SECURITY-MODEL.md. The pimatika-core surface is stable and OSS-only; pimatika-pro and pimatika-saas are private.

What is out of scope, and where enforcement lives

The v1 listed above is the egress-constitutional tranche. Future tranches (deliberately not scoped here) include filesystem-constitutional governance, process/env governance, semantic payload contracts (gRPC/ Protobuf schemas), inter-component graph governance, and agent-native governance (tools, memories, action classes, HITL approval gates).

The enforcement axis — closing the cooperative-bypass gap at the kernel — ships as integrations/nazelo, which joins the manifest to NaZelo's eBPF egress filter. It is deliberately scoped to that path: a node-level filter is something the ecosystem already provides, and what it cannot provide is a verdict derived from a signed manifest and sealed as evidence.