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(inpkg/bundle); local-only, no hosted services. - Uses the
trustplane-bundle-v1envelope and validates required fields, key-binding classes, freshness classes, and route source rules. - Freshness classes:
| Class | Behavior | Stale deny |
|---|---|---|
realtime | Requires a current bundle view | stale_bundle_fail_closed |
bounded | Allows age up to max_staleness_seconds | fail-closed after the window |
offline-ok | Explicitly allows stale/offline use (alias offline_ok normalized) | never (by design) |
- Unknown class →
bundle_freshness_unknown;boundedwithout a positive max → fail-closed. - Route resolution in the adapter:
--route-idmode: a single configured route.--route-policy-mode requestmode: match the actual request method/path against routemethod+path_template, derive the matchedroute_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, arealtimeroute is considered current only within--policy-bundle-freshness-window(default5s) after the file mtime, then fails closed.bounded/offline-okkeep 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 /ordersboundedfor resilience, but make a money-moving routerealtimeso it fails closed the moment policy goes stale. trustplane bundle buildemits a deterministic skeleton. Sign reviewed trust and policy outputs withtrustplane bundle signbefore production-style adapter loading.
→ Next: Signed bundle lifecycle.