Skip to content

feat(BL-P2-093): live-sync actor_ctx.user_id on cloud switch#86

Merged
bluejayA merged 2 commits into
mainfrom
feat/bl-p2-093-actor-user-id-sync
May 12, 2026
Merged

feat(BL-P2-093): live-sync actor_ctx.user_id on cloud switch#86
bluejayA merged 2 commits into
mainfrom
feat/bl-p2-093-actor-user-id-sync

Conversation

@bluejayA
Copy link
Copy Markdown
Owner

Summary / 요약

  • EN: BL-P2-085 follow-up. Threads the Keystone token's user.id UUID through Token, AppEvent::ContextChanged, and the App::handle_event handler so the shared actor_ctx.user_id follows a runtime cloud-switch — closing the multi-cloud audit attribution gap and the fingerprint v1 dedup leak.
  • KR: BL-P2-085 fast-follow. actor_ctx.cloud만 live update되고 user_id는 wire-startup에 고정되던 문제. 다른 자격증명으로 재인증한 후 cross-project block audit이 이전 user_id로 attribute되어 멀티클라우드 추적성을 망가뜨리고, 동시에 fingerprint v1 canonical (v1\|user\|active\|origin\|target\|action\|resource_id)에 포함된 user가 cloud별로 갈라져 dedup이 깨지던 부분을 해결.

What changed

Layer Change
Token (src/port/types.rs) New user_id: String field, #[serde(default)] empty fallback
Keystone parse (src/adapter/auth/keystone.rs) Optional KeystoneUser { id } block; parse_token extracts user.id; sample JSON + assertions updated
AppEvent::ContextChanged (src/event.rs) New user_id: String field with docstring naming BL-P2-093 intent
App::handle_event (src/app.rs:635) Writes actor_ctx.user_id only when non-empty (preserves prior value on test/rescope paths that lack a user block)
App::spawn_switch{,_back} (src/app.rs:~1715/1756) Carry snapshot.token.user_id into the event
main.rs startup Prefers Keystone UUID from get_token_info() over wire username; falls back to "unknown"
Token literal call sites (11) + ContextChanged construction sites (8) Mechanically updated

TDD trail

  • RED: test_context_changed_updates_actor_user_id (compile error E0559 — no field user_id on ContextChanged).
  • GREEN: same test passes after wiring; asserts both cloud and user_id reflect the event.
  • Bonus: test_parse_token_no_catalog asserts empty fallback; test_parse_token_from_keystone_response asserts the populated path.

Verification

  • cargo test1493 passed (main = 1492 → +1 new test)
  • cargo clippy --all-targets -- -D warnings → clean
  • cargo fmt --all -- --check → clean

Out of scope (registered follow-ups)

  • Token refresh hooks may swap user_id mid-session (BL-P2-052 token refresh part)
  • Fingerprint v2 schema bump remains BL-P2-094 (v1 still LOCKED)
  • Username/UUID display mapping (UI side) — not affected; current path is UUID-first

Risk

  • All non-Keystone Token constructions (tests/mocks) populate empty user_id and remain GREEN under the "empty = no change" semantics in handle_event.
  • #[serde(default)] on the new field keeps disk-cached token JSON forward-compatible.

Test plan

  • DevStack manual: switch from cloud-A → cloud-B with distinct user credentials; trigger cross-project mutation; confirm audit user reflects cloud-B's UUID and fingerprint differs from a same-user/same-pattern cloud-A entry.
  • Unit: test_context_changed_updates_actor_user_id (cloud + user_id propagation)
  • Unit: test_parse_token_from_keystone_response + test_parse_token_no_catalog (populated + missing user block)
  • Suite: all 1493 tests pass
  • Lint: clippy + fmt clean

Refs: cargo-review branch-full report 2026-05-12 (Suggestions #1). Parent: #85 (BL-P2-085 atomic security).

🤖 Generated with Claude Code

bluejayA and others added 2 commits May 12, 2026 20:12
BL-P2-085 Phase 7 wired actor_ctx.cloud through the shared RwLock so the
worker's cross-project block audit picks up runtime cloud switches, but
left actor_ctx.user_id pinned to the wire-startup wire_username. In a
multi-cloud rescope flow, every audit entry emitted after the switch
attributed the block to the previous user_id — and because fingerprint
v1 canonical includes `user`, dedup also broke (the same actor across two
clouds produced two distinct fingerprints).

This change threads the Keystone token's `user.id` UUID through:
- Token.user_id: new field, populated by parse_token from the Keystone
  response's optional `user` block (empty string fallback when missing).
- AppEvent::ContextChanged.user_id: carried alongside target so the
  handler in App::handle_event refreshes the shared actor_ctx.
- Empty user_id is treated as "no change" so legacy fixtures, mocks, and
  rescope paths that lack a user block don't overwrite a valid prior
  value.
- main.rs now prefers the Keystone UUID at startup over wire_username
  (still falls back to "unknown" if both are unavailable).

TDD: added test_context_changed_updates_actor_user_id covering both
cloud and user_id propagation through the ContextChanged handler.
test_parse_token_no_catalog now asserts the empty-fallback behavior;
test_parse_token_from_keystone_response asserts the populated path.

cargo test = 1493 passed (1492 → +1). clippy clean. fmt clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Codex review P2: `Token.user_id` deserializes to `""` via `#[serde(default)]`
for tokens cached before BL-P2-093. `KeystoneAuthAdapter::new` loads those
straight into `token_map`, so a post-upgrade switch into the cached scope
emits `AppEvent::ContextChanged` with an empty `user_id` and
`App::handle_event` preserves the prior `actor_ctx.user_id` — defeating
the live-sync invariant the rest of this PR establishes.

`load_token_file` now treats `user_id.is_empty()` as a cache miss so the
next `get_token` reauths against Keystone, parses the fresh `user.id`, and
overwrites the file via the existing `save_token` path on the auth-success
arm. The legacy file is intentionally NOT auto-deleted (only expired
tokens are) so a transient reauth failure doesn't wipe a still-valid
catalog/roles payload that was recoverable.

Tests: bumped `sample_token` user_id to a non-empty fixture, added
`test_load_legacy_token_without_user_id_treated_as_cache_miss` covering
both the skip and the no-auto-delete behavior. cargo test = 1494 passed
(+1). clippy clean. fmt clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@bluejayA bluejayA merged commit ef65122 into main May 12, 2026
3 checks passed
@bluejayA bluejayA deleted the feat/bl-p2-093-actor-user-id-sync branch May 12, 2026 11:34
bluejayA added a commit that referenced this pull request May 12, 2026
BL-P2-093 PR #86 codex-review surfaced one open question: App's CUD
success audit entries still attribute via the wire username, while
worker cross-project block entries now use the Keystone UUID. Same
actor in same log can show two different `user` values, breaking
grep/dedup across both streams. Registering BL-P2-095 (Medium) so the
follow-up doesn't get lost.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant