Skip to main content

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 seeRoot causeOne-line opt-out
UnknownModelError from token-tracking callbackModel name not in pricing table; v0.6.0 returned 0.0 silentlyGOVERNANCE_COST_STRICT_UNKNOWN_MODELS=false
StoreUnavailableError at max_eventsIn-memory audit store no longer silently truncatesInMemoryAuditStore(on_full="evict")
revoke_with_chain_event(...) raisesAudit chain write failed; refuses to orphan revocation with chain_event_id=NoneRevocationStore(strict_chain=False)
Rare duplicate approval.granted audit row for one request_idAudit 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=false

Note: 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: true
  • Sunset: Thu, 01 Oct 2026
  • X-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. Explicit include_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_reconcile as 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