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
Enforcerdoes not constrain disk writes. (The BIM can now DECLARE a signedfilesystemsection; 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
processandsecretssections 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::checkand honouring the returnedEgressDecision. 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
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_hashfield → 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.