Upgrade guide / v0.6.2
Upgrading to v0.6.2
v0.6.2 flips four defaults to fail-closed instead of silent-degrade. If your code catches broad exceptions or relies on silent-zero accounting, read this first. Each flip has a one-line opt-out.
Release status
v0.6.2is the current release on PyPI. Most existing deployments are on v0.5.4 or v0.6.0 — this page is written for you. If you landed here before your production rollout, read the TL;DR table first; everything below expands on one of its rows.
TL;DR
Four fail-closed flips. Scan the symptom column, copy the opt-out, then come back and read the preferred fix once production is calm.
| Symptom you’ll see | Root cause | One-line opt-out |
|---|---|---|
UnknownModelError from token-tracking callback | Model name not in pricing table; v0.6.0 returned 0.0 silently | GOVERNANCE_COST_STRICT_UNKNOWN_MODELS=false |
StoreUnavailableError at max_events | In-memory audit store no longer silently truncates | InMemoryAuditStore(on_full="evict") |
revoke_with_chain_event(...) raises | Audit chain write failed; refuses to orphan revocation with chain_event_id=None | RevocationStore(strict_chain=False) |
Rare duplicate approval.granted audit row for one request_id | Audit now runs inside gate-resolve; cross-engine atomicity is v0.7+ | None — correctness improvement, not a flag |
1. CostModule.strict_unknown_models=True
Symptom
UnknownModelError raised from your token-tracking callback or LLM wrapper. Stack trace points at codeatelier_governance.cost.pricing.
Root cause
Your model name (my-ft-claude-foo, custom-model-x, bedrock-...) isn’t in the SDK’s MODEL_PRICING table. v0.6.0 returned 0.0 silently, which meant budget gates never fired for fine-tuned or self-hosted models. v0.6.2 raises.
Preferred fix — add a pricing entry
Best for accurate budget tracking. Open codeatelier_governance/cost/pricing.py, add your model to MODEL_PRICING with input and output USD-per-million rates. One-line PR against the SDK, or register dynamically at SDK init.
Quick opt-out
from codeatelier_governance import GovernanceSDK
sdk = GovernanceSDK(
database_url=...,
cost_strict_unknown_models=False,
cost_unknown_model_fallback_usd_per_million=2.50, # your best estimate
)export GOVERNANCE_COST_STRICT_UNKNOWN_MODELS=falseNote: the bundled LangChain handler is already wired to never raise — it logs and falls through to 0.0so your chain doesn’t die mid-run. Direct SDK callers see the raise.
2. InMemoryAuditStore.on_full="raise"
Symptom
StoreUnavailableError when your audit event count hits max_events. Previously the oldest rows were dropped silently, which broke chain verification and masked outages.
Root cause
In-memory mode (no Postgres) now refuses to silently truncate the audit chain. A truncated chain fails verification — which defeats the point of running the SDK at all.
Preferred fix
Provide a Postgres connection string. In-memory mode is for tests and local dev; production deployments should have a database_url.
Quick opt-out
from codeatelier_governance.audit import InMemoryAuditStore
store = InMemoryAuditStore(max_events=100_000, on_full="evict")Use on_full="evict" only if you deliberately want ring-buffer semantics and accept that chain verification will report a truncated prefix.
3. RevocationStore.strict_chain=True
Symptom
revoke_with_chain_event(...) raises instead of completing silently.
Root cause
If the audit chain write fails, v0.6.2 refuses to orphan the revocation with chain_event_id=None. An orphaned revocation is invisible to compliance exports — worse than no revocation at all, because operators think the action is recorded.
Preferred fix
Fix the underlying audit availability problem. Check GOVERNANCE_AUDIT_SECRET is set, the DB is reachable, and the audit store is configured. If the chain write fails you have a bigger problem than a revocation call.
Quick opt-out
from codeatelier_governance.identity import RevocationStore
store = RevocationStore(strict_chain=False)
# Emits identity.revocation_without_chain_event WARN and proceeds.4. Gate resolve + audit serialization
Symptom
In rare failure modes, a duplicate approval.granted audit row appears for one request_id.
Root cause
The audit write now runs inside the gate-resolve path — previously they were fire-and-forget separate, which let a gate transition land without a chain row. The narrow residual: audit row commits, then gate rollback fires. True cross-engine atomicity is a v0.7+ item.
Preferred fix
Monitor for duplicate approval.grantedentries in your chain exports as a signal of the reverse failure. A duplicate means the gate engine retried successfully after the audit commit — the user experience is correct, but the chain has two rows.
Quick opt-out
None — this is a correctness improvement, not a flag. Opting out would mean re-introducing the silent-drop failure mode.
5. New wire contract — /api/gates/pending
The legacy /api/gates/pending still works (returns a plain array) but is now admin-only, capped at 500 rows, and responds with these headers:
Deprecation: trueSunset: Thu, 01 Oct 2026X-Truncated: true— present only when the cap is hit
Migrate to /api/v2/gates/pending which returns {items, has_more, next_cursor} with keyset pagination:
// Paginated walk of pending gates
async function* walkPendingGates(fetchOpts) {
let cursor = null;
while (true) {
const qs = cursor ? `?cursor=${encodeURIComponent(cursor)}` : "";
const res = await fetch(`/api/v2/gates/pending${qs}`, fetchOpts);
const { items, has_more, next_cursor } = await res.json();
for (const item of items) yield item;
if (!has_more) return;
cursor = next_cursor;
}
}
for await (const gate of walkPendingGates({ headers: { Authorization: ... } })) {
// handle gate
}6. New token format — gate approval tokens v2
v0.6.2 mints v1 tokens by default (enable_v2_tokens=False). v2 is opt-in for this release so you can roll out at your own pace.
Opt in to v2 if you have rotated or plan to rotate the audit secret — v2 tokens survive HMAC key rotation; v1 do not.
from codeatelier_governance.gates import GatesModule
gates = GatesModule(enable_v2_tokens=True)
# or via env:
# GOVERNANCE_GATES_ENABLE_V2_TOKENS=true- v1 tokens remain valid until
accept_v1_until(default: ~2026-07-17, 90 days post-release). - v0.6.3 will flip the default to True. Plan rolling deploys to set
enable_v2_tokensexplicitly if you’re crossing that version boundary — pinning the value prevents surprise behaviour changes between pods on different SDK minor versions.
7. Streaming accounting — Anthropic + OpenAI wrappers
Streaming responses now reconcile actual token usage at end-of-stream rather than estimating from chunk counts. Expect cost numbers to be higher — and more accurate — than v0.6.0.
- OpenAI callers: the wrapper auto-injects
stream_options={"include_usage": True}unless you’ve set it yourself. Explicitinclude_usage=Falseis honored but logs a WARN — you’ll lose reconciliation for those calls. - Anthropic callers: reconciliation reads the final usage block on stream completion. No caller-side change required.
- Abandoned streams (consumer dropped without reading the final chunk) emit
governance.stream.abandoned_without_reconcileas an audit event so you can detect leaks. - Cancelled streams reconcile with a conservative
chunks × 8estimate — better than zero, slightly over-counted on short cancellations.
8. Rolling deploy notes
v0.5.4 pods + v0.6.2 pods on the same Postgres
v1 tokens work on both. Audit chain writes from v0.5.4 and v0.6.2 interleave safely on the same table — the chain HMAC doesn’t care which SDK minor emitted the row. Caveat: the four strict-default flips only fire on the v0.6.2 pods; any code path that relies on v0.5.4’s silent-degrade will keep degrading on those pods until they’re drained.
v0.6.0 pods + v0.6.2 pods on the same Postgres
v1 tokens work on both. Audit chain is still compatible — no schema migration required between v0.6.0 and v0.6.2. If you set enable_v2_tokens=True on v0.6.2 pods, make sure the cohort that validates the approval is also on v0.6.2; v0.6.0 will reject a v2 token as malformed.
Downgrade v0.6.2 → v0.6.1
Supported for tokens (both are v1 by default). Downgrading will undo the v0.6.2 strict defaults— host code that came to depend on the raise-path must handle both. If you’ve added a try/except UnknownModelErrorthat prints a helpful message, on v0.6.1 you’ll silently get 0.0 instead. Log both branches.
Multi-region / active-active Postgres
The HMAC chain assumes a single-writer topology per chain. Active-active Postgres is out of scope for this upgrade note; see concepts for the chain-design trade-offs before going multi-writer.
Still stuck?
If none of the opt-outs above resolve your upgrade, book a 20-minute triage call — we’ll walk your stack trace live and identify whether the fix is a config flag, a pricing-table entry, or a genuine bug on our side.
Book an upgrade-triage call