Skip to main content

Bundle policy & freshness

Concept

The verifier needs two kinds of local input: trust material (which public keys to trust) and route policy (what each route requires). The policy bundle (trustplane-bundle-v1) holds the per-route rules: which sources are allowed, the minimum key binding, and — crucially — how fresh the bundle must be for that route. Freshness is what lets high-risk routes fail closed when policy might be out of date, while low-risk routes keep working offline.

Implementation

  • Built by trustplane bundle build (in pkg/bundle); local-only, no hosted services.
  • Uses the trustplane-bundle-v1 envelope and validates required fields, key-binding classes, freshness classes, and route source rules.
  • Freshness classes:
ClassBehaviorStale deny
realtimeRequires a current bundle viewstale_bundle_fail_closed
boundedAllows age up to max_staleness_secondsfail-closed after the window
offline-okExplicitly allows stale/offline use (alias offline_ok normalized)never (by design)
  • Unknown class → bundle_freshness_unknown; bounded without a positive max → fail-closed.
  • Route resolution in the adapter:
    • --route-id mode: a single configured route.
    • --route-policy-mode request mode: match the actual request method/path against route method + path_template, derive the matched route_id. Exact paths win over conservative single-segment {name} templates.
    • Missing policy → bundle_policy_missing; unknown route → bundle_route_missing (both before replay consume).
  • File-backed realtime freshness: when the adapter reads --policy-bundle, a realtime route is considered current only within --policy-bundle-freshness-window (default 5s) after the file mtime, then fails closed. bounded/offline-ok keep their normal semantics.

Example

Build a policy bundle from an authoring config:

trustplane bundle build --config bundle.config.json --out trustplane.bundle.json

The multi-anchor example authors the hosted-demo GET /orders route:

trustplane bundle build \
--config cmd/trustplane-cli/testdata/bundle-build/acme-demo.config.json \
--out trustplane.bundle.json

A route entry, showing freshness + a source rule:

{
"route_id": "acme.demo.orders.read",
"method": "GET",
"path_template": "/orders",
"freshness_class": "bounded",
"max_staleness_seconds": 300,
"allowed_sources": [
{
"issuer": "https://issuer.acme.demo/external-jwks",
"trust_domain": "acme.demo.external",
"subject_exact": "external:jwks:hosted-demo-caller",
"required_key_binding": "software"
}
]
}

The provider/gateway demo enforces this end to end (route selection, freshness, deny reasons):

make demo-provider-gateway

What to notice

  • The bundle is the policy. There is no central policy server in the request path — the adapter decides from the mounted file.
  • Freshness is a per-route risk knob: make GET /orders bounded for resilience, but make a money-moving route realtime so it fails closed the moment policy goes stale.
  • trustplane bundle build emits a deterministic skeleton. Sign reviewed trust and policy outputs with trustplane bundle sign before production-style adapter loading.

→ Next: Signed bundle lifecycle.