release(v0.46.0): multi-tenant hardening, no new features#177
Conversation
The MCP registry was the only release surface with no CI automation. PyPI, npm, and the GitHub Release all ride the version tag; the registry was hand- published, so it drifted: 0.43.0 and 0.45.0 were never published, and 0.44.0 and 0.45.1 landed late and by hand. Adds a publish-mcp-registry job that runs after publish-pypi and the GitHub Release. It installs mcp-publisher, authenticates by GitHub OIDC (no stored secret; needs id-token: write), publishes both manifests (server.json and server-vaara-server.json), then asserts the live registry's latest active version for both listings equals the released tag and fails the job loudly on any mismatch. mcp-publisher is pinned (MCP_PUBLISHER_VERSION) to match the repo's pinning convention; bump it when the registry deployment moves forward. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Makes the multi-tenant runtime-governance claim true and safe rather than aspirational. Spine: upgrade the registry-of-record entry to "multi-tenant runtime governance for MCP fleets". Security: - SEP-2787 verify_attestation now rejects a future-dated iat. The TTL check had only an upper bound, so a future issuance time kept the validity window open indefinitely. Enforces now >= iat - clock_skew. Conformance set gains future-dated cases. CLI signature-isolation pass moved off now=0.0 (which the new lower bound would reject) to the envelope's own iat. Fixed: - Race in AuditTrail's action-to-tenant map: unguarded read/evict/insert under concurrent writers could raise during eviction or cross-tenant the scope. Dedicated lock added. New tests run 16 tenants through full lifecycles concurrently, asserting chain integrity and per-tenant scope. Changed: - Wheel slimmed ~8MB to ~0.8MB: include-package-data was pulling all of v1-v8 model bundles into the wheel; only v9 loads at runtime. Ship v9 only. CI/tooling: - mypy build-failing gate on the strict set (vaara.policy.*), pinned 1.20.2. - gitignore SQLite WAL sidecars. RELEASE.md step 3 corrected to match the tag-origin/main-directly script. Bench: - bench/vaara-bench-v0.46.md: sub-2ms p50 governance overhead, flat across 1-8 upstream fan-out (bench/v046_fanout.json). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Warning Review limit reached
More reviews will be available in 37 minutes and 38 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughVersion 0.46.0 release hardens SEP-2787 attestation validation with future-dated Changesv0.46.0 Release: Attestation, Audit Concurrency, Package Distribution, and MCP Registry
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
| ) | ||
| aid = trail.record_action_requested(req) | ||
| trail._tenant_for(aid) # concurrent read against the evicting writer | ||
| except BaseException as exc: # noqa: BLE001 — surface any race to the assert |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/vaara/audit/trail.py`:
- Around line 597-602: Eviction of entries from _tenant_for_action causes
_tenant_for(action_id) to return "" for late
record_decision/record_execution/record_outcome calls, dropping tenant scope;
change the logic so when an entry is evicted (or when _tenant_for(action_id)
would return empty) you instead resolve the tenant by consulting the canonical
ACTION_REQUESTED record in _by_action for that action_id (the same structure
used by record_decision/record_execution/record_outcome), or persist the tenant
alongside the _by_action entry and never remove that canonical tenant info on
eviction; update the eviction block around _tenant_for_action and the
_tenant_for() helper to fall back to reading tenant_id from
_by_action[action_id] (or its stored tenant field) so long‑running actions keep
correct tenant scope (also apply the same fix at the other occurrence around
lines 658-659).
In `@src/vaara/cli.py`:
- Around line 1227-1237: The code treats any failure of the live verification as
TTL expiry; instead, separate "future-dated" (not-yet-valid) from "ttl_expired":
keep signature_ok = verify_attestation(envelope, now=isolation_now) and live_ok
= verify_attestation(envelope, now=real_now) (or call verify_attestation without
now but capture the real current time into a variable), then compute
future_dated = signature_ok and not live_ok and (envelope.iat > real_now) and
ttl_expired = signature_ok and not live_ok and not future_dated; update any JSON
output and --enforce-ttl logging to use ttl_expired only and report future_dated
when appropriate (refer to isolation_now, verify_attestation, signature_ok,
live_ok, ttl_expired).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: de00c683-3667-4525-ab69-f1e63072ed76
📒 Files selected for processing (19)
.github/workflows/ci.yml.github/workflows/release.yml.gitignoreCHANGELOG.mdbench/v046_fanout.jsonbench/vaara-bench-v0.46.mdclients/ts/package.jsonpyproject.tomlscripts/RELEASE.mdserver-vaara-server.jsonserver.jsonsrc/vaara/__init__.pysrc/vaara/attestation/_sep2787_emit.pysrc/vaara/audit/trail.pysrc/vaara/cli.pysrc/vaara/policy/modes.pytests/test_attestation_sep2787.pytests/test_attestation_vectors.pytests/test_v040_tenant.py
| with self._tenant_map_lock: | ||
| if len(self._tenant_for_action) >= self._MAX_ACTION_TENANT_MAP: | ||
| evict = max(1, self._MAX_ACTION_TENANT_MAP // 8) | ||
| for stale in list(self._tenant_for_action)[:evict]: | ||
| self._tenant_for_action.pop(stale, None) | ||
| self._tenant_for_action[action_id] = tenant_id |
There was a problem hiding this comment.
Preserve tenant scope after _tenant_for_action eviction.
Once an action_id is evicted here, _tenant_for() falls back to "", so any late record_decision / record_execution / record_outcome for that still-live action is written as single-tenant even though the original ACTION_REQUESTED record in _by_action still has the real tenant_id. In a multi-tenant release, that silently drops scope on long-running actions under load.
Proposed fix
def _tenant_for(self, action_id: str) -> str:
"""Resolve the tenant scope for an existing action lifecycle.
Returns the tenant_id captured at record_action_requested time so
every follow-up record (risk_scored, decision, execution,
escalation, outcome) carries the same scope automatically.
"""
- with self._tenant_map_lock:
- return self._tenant_for_action.get(action_id, "")
+ with self._tenant_map_lock:
+ tenant_id = self._tenant_for_action.get(action_id)
+ if tenant_id is not None:
+ return tenant_id
+ with self._lock:
+ records = self._by_action.get(action_id, [])
+ if records:
+ return records[0].tenant_id
+ return ""Also applies to: 658-659
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/vaara/audit/trail.py` around lines 597 - 602, Eviction of entries from
_tenant_for_action causes _tenant_for(action_id) to return "" for late
record_decision/record_execution/record_outcome calls, dropping tenant scope;
change the logic so when an entry is evicted (or when _tenant_for(action_id)
would return empty) you instead resolve the tenant by consulting the canonical
ACTION_REQUESTED record in _by_action for that action_id (the same structure
used by record_decision/record_execution/record_outcome), or persist the tenant
alongside the _by_action entry and never remove that canonical tenant info on
eviction; update the eviction block around _tenant_for_action and the
_tenant_for() helper to fall back to reading tenant_id from
_by_action[action_id] (or its stored tenant field) so long‑running actions keep
correct tenant scope (also apply the same fix at the other occurrence around
lines 658-659).
| # Evaluating at the envelope's own iat sits inside the validity window | ||
| # (both the lower and upper time bounds pass), isolating signature | ||
| # validity; a second pass at real time reveals whether the TTL expired. | ||
| # Durable evidence files are routinely checked long after exp, so TTL is | ||
| # reported but not enforced unless --enforce-ttl is set. | ||
| signature_ok = verify_attestation(envelope, verifying_material=material, now=0.0) | ||
| isolation_now = _attest_isolation_now(envelope) | ||
| signature_ok = verify_attestation( | ||
| envelope, verifying_material=material, now=isolation_now | ||
| ) | ||
| live_ok = verify_attestation(envelope, verifying_material=material) | ||
| ttl_expired = signature_ok and not live_ok |
There was a problem hiding this comment.
Don't collapse "future-dated" into ttl_expired.
After the lower-bound check in verify_attestation, live_ok == False now covers two different states: expired and not-yet-valid. ttl_expired = signature_ok and not live_ok mislabels future-dated envelopes as expired, so the JSON output and the --enforce-ttl stderr path report the wrong failure mode for one of the new hardening cases.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/vaara/cli.py` around lines 1227 - 1237, The code treats any failure of
the live verification as TTL expiry; instead, separate "future-dated"
(not-yet-valid) from "ttl_expired": keep signature_ok =
verify_attestation(envelope, now=isolation_now) and live_ok =
verify_attestation(envelope, now=real_now) (or call verify_attestation without
now but capture the real current time into a variable), then compute
future_dated = signature_ok and not live_ok and (envelope.iat > real_now) and
ttl_expired = signature_ok and not live_ok and not future_dated; update any JSON
output and --enforce-ttl logging to use ttl_expired only and report future_dated
when appropriate (refer to isolation_now, verify_attestation, signature_ok,
live_ok, ttl_expired).
The new mypy strict gate surfaced a real latent bug. apply_policy stored policy-schema SequencePattern objects (.pattern/.window_seconds) directly into the scorer's _sequences, but the sequence matcher reads the scorer's runtime fields (.actions/.window_size). A hot policy reload followed by a sequence match raised AttributeError. Tests passed only because none exercised reload-then-match. apply_policy now converts each policy pattern into the scorer's SequencePattern (actions=pattern, window_size=max(len(pattern),10) since window_size is a lookback count not a time window). Adds a regression test that reloads a sequence-bearing policy and asserts the match fires. Fixes the mypy strict CI failure on adaptive.py:726. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Hardening and multi-tenant-proof release. No new features. Makes the multi-tenant runtime-governance claim true and safe so the registry-of-record entry can read "multi-tenant runtime governance for MCP fleets".
Security
iat. The TTL check had only an upper bound, so a future issuance time held the validity window open indefinitely. Verification now enforcesnow >= iat - clock_skew. Conformance vectors gain future-dated cases.Fixed
Changed
include-package-datawas pulling all of v1-v8 model bundles into the wheel; only v9 loads at runtime. Packaging now ships v9 only. Older bundles stay in-repo for bench and cross-eval.CI / tooling
vaara.policy.*), pinned 1.20.2..gitignoreSQLite WAL sidecars.scripts/RELEASE.mdstep 3 corrected to matchrelease_merge_and_tag.sh.Bench
bench/vaara-bench-v0.46.md: sub-2ms p50 governance overhead, flat across 1-8 upstream fan-out.Verification
src/vaara/policy/) clean. Wheel built and confirmed at 770K shipping only the v9 bundle.🤖 Generated with Claude Code
Summary by CodeRabbit
Release Notes: Version 0.46.0
New Features
Bug Fixes
Security
Performance
Tests
Documentation