Skip to content

refactor(activity): qualify Valkey activity identifiers - #914

Open
seonghobae wants to merge 70 commits into
mainfrom
fix/activity-stream-semantic-identifiers
Open

refactor(activity): qualify Valkey activity identifiers#914
seonghobae wants to merge 70 commits into
mainfrom
fix/activity-stream-semantic-identifiers

Conversation

@seonghobae

@seonghobae seonghobae commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Scope / owner boundary

LineageWeave-owned activity-stream read-model identity and retained-history compatibility at the Valkey boundary. PostgreSQL source_post.post_id remains authoritative; Valkey remains a derived operational projection. Persisted activity fields and API response shape are preserved. ADR 0363 remains Proposed until an unchanged release-ready head is independently approved.

Concurrent non-force deltas extending historical UUID alias compatibility, bounded alias cardinality/scan behavior, single-writer deployment prerequisites, and bounded multi-stream merge remain adopted. Predecessor workflow/approval evidence does not transfer.

Protected-main convergence

Protected main remains 83eba56149eb802cd63642c507c324c9976ec78e at this fresh sweep. Exact current head is 61ed3a3712d252e3c179a71d297c52f05e1bac20; the branch remains directly descended from that protected base without force push or destructive rebase. No open PR directly bases on this branch at the latest descendant sweep.

Buyer-path read repair lineage

Earlier repair lineages preserve bounded canonical reads, one-exchange canonical UUID admission/read, bounded startup alias-index writes, and bounded retained-alias page prefetch.

Review 5122107164 found a remaining deterministic extra exchange: when the first pipelined SSCAN returned a complete non-empty alias page, the code discarded that page and its already-fetched canonical XREVRANGE, then rescanned aliases and re-fetched canonical history before merging. RED 15746e2be27effca16c71f445f8d77f5d55fe6b8 tightens the one-alias fixture to exactly two network exchanges with pipeline batches [2, 1]. Production repair 07fa552f9fecbe1cda79a9fccc7eeacd82c4fba5 carries a complete first alias page plus canonical page into compatibility merge, trims the canonical page to the per-stream prefetch budget, and fetches only retained alias pages in the second pipeline exchange.

Follow-up verification review 5122123572 caught a regression before GREEN: reused complete SSCAN pages initially bypassed _activity_read_stream_keys() and therefore bypassed its existing <=999 aliases + canonical stream fan-out guard. RED a9c080c1f2fa030bd2b0cec0e2fb6b43e4328bbc returns cursor 0 with 1,000 aliases and requires the request to fail closed after the first two-command admission/data exchange, before retained-alias fan-out. Production repair 8ef3a42608739e5a32b5841e4a14494df5326a3f reapplies the 1,000 distinct canonical-plus-alias stream ceiling on the reused-page path. ADR convergence 61ed3a3712d252e3c179a71d297c52f05e1bac20 records both the transport optimization and the preserved fan-out invariant.

Central CodeQL handoff RCA

Required CodeQL run 33979627116 is not a LineageWeave scan finding. On the exact head, language detection succeeded; python job 101357239026 and the javascript-typescript/actions siblings revalidated the live PR/head, found no authenticated codeql-dispatch/<language> terminal status, obtained the OIDC/repository-scoped app-token boundary, POSTed three native codeql-scan repository_dispatch events, wrote verdict=pending, then intentionally failed to release their runners.

All three central dispatches are now terminal and expose the precise owner-side root cause. .github runs 33988646498, 33988691535, and 33988735145 each obtained a runner, succeeded at OpenCode app-token exchange, then failed in validate-dispatch -> Bind workflow inputs to live organization pull request metadata; their scan jobs were skipped. Exact python-shard run 33988646498, job 101366754283, records DISPATCH_ACTOR=opencode-agent[bot], DISPATCH_SENDER=opencode-agent[bot], but ALLOWED_DISPATCH_ACTOR=github-actions[bot], then fails with repository_dispatch authorization rejected ... because both must match one configured scheduler identity. The serialized matrix is valid and reaches this gate, so canonical .github#1926 already fixed the earlier matrix-template defect.

The workflow-side authorization representation is also already repaired: .github#1932 merged as 6f8c51d7389c22ebaf294fe8fe9ef495257883c0 and makes opencode-review-dispatch, codeql-scan-dispatch, and pr-review-fix-scheduler parse OPENCODE_REPOSITORY_DISPATCH_ACTOR identically as a comma-separated allowlist while still requiring actor and sender to match the same listed identity. Owner history establishes that both identities are intentional: the merge scheduler legitimately uses github-actions[bot], while .github#1497 explicitly moved the Required OpenCode path onto GitHub OIDC + the OpenCode App (opencode-agent[bot]). Therefore the remaining owner/admin action tracked in .github#1927/#1929 is to set the variable to the two-entry trusted list github-actions[bot],opencode-agent[bot]; replacing one identity with the other would simply break the opposite trusted dispatcher. This LineageWeave lane has no Actions-variable mutation surface and does not widen central authorization from a leaf repository.

No codeql-dispatch/python, /javascript-typescript, or /actions terminal status was published to this head. Exact #914 evidence and the owner-intent correction have been added to .github#1927 as a post-change canary.

A separate recovery issue remains ordered after the identity repair: .github#1902 is Draft/unmerged and removes the required-workflow RUN_ATTEMPT != 1 inference so an unchanged consumer head without a trusted terminal verdict can redispatch after an earlier failed/cancelled handoff. Manually rerunning #914 before the dispatcher allowlist value is corrected would repeat the known authorization rejection; after that correction, rerun behavior must still respect the protected central recovery contract rather than assuming attempt number is a dispatch receipt. Therefore no leaf no-op head churn or gate bypass is used.

Exact live boundary

  • PR: open / Ready / mechanically mergeable; Ready is validation admission, not merge readiness
  • protected base observed in the fresh sweep: main@83eba56149eb802cd63642c507c324c9976ec78e
  • exact current head: 61ed3a3712d252e3c179a71d297c52f05e1bac20
  • latest review/RED/fix/ADR: 5122123572 -> a9c080c1... -> 8ef3a426... -> 61ed3a371...
  • preceding transport RED/fix: 5122107164 -> 15746e2b... -> 07fa552f...
  • source delta from fan-out RED to repair is exactly two added lines in backend/app/activity_stream.py
  • exact-head Tests 33979633195: success
  • exact-head Security Scan 33979627218: success
  • exact-head SAST Semgrep 33979627205: success
  • exact-head CodeQL PR 33979627116: failure/pending handoff
  • exact central CodeQL dispatches 33988646498, 33988691535, 33988735145: failure at dispatcher authorization; scan jobs skipped; no source result produced
  • central parser support: .github#1932 merged; both trusted identities can be expressed safely
  • remaining authorization action: owner/admin must change OPENCODE_REPOSITORY_DISPATCH_ACTOR from one entry to github-actions[bot],opencode-agent[bot]
  • canonical later-attempt recovery owner: ContextualWisdomLab/.github#1902, still Draft/unmerged
  • predecessor hosted results do not transfer across moved heads
  • current-head independent APPROVE is absent; historical Noema APPROVE is explicitly tied to f9645d01eda570e32b0f229c0f8af1a23faee06e
  • authenticated Valkey/k6 p95 and measured rollout/readiness timing are not claimed
  • ADR 0363 remains Proposed

Do not self-approve, bypass required gates, force-push, mutate organization authorization from this leaf repository, hide retained activity for latency, churn the leaf head merely to retrigger central CodeQL, or promote predecessor results to current-head evidence.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

This report is out of date. Scroll down for Devin Review's latest report on this PR.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Devin Review

@coderabbitai

coderabbitai Bot commented Sep 2, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: 4bb8615c-df09-43be-8ba0-c1ebbf786ef1

📥 Commits

Reviewing files that changed from the base of the PR and between 2905bd5 and e9dcc6b.

📒 Files selected for processing (4)
  • backend/app/activity_stream.py
  • tests/test_activity_stream_identity_types.py
  • tests/test_activity_stream_retry_limit.py
  • tests/test_activity_stream_watch_observability.py

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

Valkey 활동 스트림에 문자열 입력 검증과 WATCH/MULTI/EXEC 기반 동시성 제어를 추가했습니다. 동기 게시 경로는 충돌을 최대 8회 재시도하고, 한도 초과 시 키와 원인을 노출하지 않는 RuntimeError를 발생시킵니다. 관련 멱등성 및 관측성 테스트도 추가했습니다.

Changes

활동 스트림 동시성 변경

Layer / File(s) Summary
활동 스트림 계약 및 이벤트 필드
backend/app/activity_stream.py
공개 매개변수 이름과 설명을 갱신했습니다. post_id, event_type, actor_account_id, summary의 문자열 타입을 검증합니다. 이벤트 전체 필드 비교를 추가했습니다. 비동기 XADD와 조회 범위는 유지합니다.
동기 게시의 낙관적 동시성 제어
backend/app/activity_stream.py
publish_activity_event_syncWATCHMULTI/EXEC를 사용해 중복 없는 이벤트만 추가합니다. WatchError 충돌을 최대 8회 재시도합니다. 한도 초과 시 키와 예외 원인을 포함하지 않는 RuntimeError를 발생시킵니다.
동시 게시와 멱등성 검증
tests/test_activity_stream.py
가짜 WATCH/MULTI 파이프라인과 경합 스트림을 추가했습니다. 전체 스트림 검색, 이벤트 유형·행위자·요약 기반 멱등성, 50개 초과 이벤트 조회 및 충돌 후 재시도를 검증합니다.
입력 경계와 관측성 검증
tests/test_activity_stream_identity_types.py, tests/test_activity_stream_retry_limit.py, tests/test_activity_stream_watch_observability.py
숫자 identity를 Valkey 접근 전에 거부하는 동작을 검증합니다. 8회 충돌 후 오류와 예외 비노출을 검증합니다. 복구된 충돌은 실패 XADD로 기록하지 않고, 최종 시도만 오류로 기록하는지 검증합니다.

Estimated code review effort: 4 (Complex) | ~45 minutes

Merge Risk: 🔵 Low · up to e9dcc

The PR hardens activity-stream identity validation and reseeding while preserving the Valkey wire format. It is mergeable with owner awareness that external administrative or recovery scripts using the former client= or summary= keyword names may need updating.

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant publish_activity_event_sync
  participant Valkey
  Caller->>publish_activity_event_sync: 활동 이벤트 게시 요청
  publish_activity_event_sync->>Valkey: WATCH 및 기존 이벤트 조회
  Valkey-->>publish_activity_event_sync: 이벤트 필드 반환
  publish_activity_event_sync->>Valkey: MULTI/EXEC 및 XADD
  Valkey-->>publish_activity_event_sync: 게시 결과 또는 WatchError
  publish_activity_event_sync->>Valkey: 충돌 시 다음 시도
  publish_activity_event_sync-->>Caller: 게시 완료 또는 8회 후 RuntimeError
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 55 functions across 5 files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목은 Valkey 활동 식별자 정규화라는 실제 변경 내용을 정확히 나타냅니다. 제목은 간결하고 구체적이며 변경 사항과 관련됩니다.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/activity-stream-semantic-identifiers

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

Copy link
Copy Markdown
Contributor Author

Cycle retry 2026-09-02T08:15Z: write-path probe on #914 (Valkey activity identifiers). Independent APPROVE still required before squash-merge; this is not a self-approve.

@seonghobae seonghobae left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Performance/operability finding on the current head: index_legacy_activity_stream_aliases() runs synchronously in FastAPI lifespan before app.state.valkey and request service become available, and it awaits one SADD per discovered legacy UUID alias inside scan_iter. With N retained aliases, startup adds N serial Valkey write round trips on top of SCAN pagination. That makes rollout/readiness latency proportional to historical alias cardinality even though these writes are independent. Preserve the one-time compatibility evidence, but batch independent alias-index SADDs through a bounded non-transactional redis-py pipeline (with bounded in-memory batch size) and add a regression that rejects per-alias direct network writes. Do not remove alias discovery or hide historical streams to meet latency.

@seonghobae
seonghobae marked this pull request as draft September 5, 2026 15:25
@seonghobae
seonghobae marked this pull request as ready for review September 5, 2026 15:31

@seonghobae seonghobae left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fresh current-head buyer-path performance finding: the alias-preserving compatibility branch still performs one XREVRANGE count=1 per stream and then one further awaited XREVRANGE count=1 for every returned event. asyncio.gather only overlaps the initial probes; a normal 50-event read for a post with one retained UUID alias can still incur roughly 50 sequential follow-up Valkey waits after alias admission, even though the output budget is already bounded. That leaves the exact historical customers for whom the compatibility bridge exists outside the p95<=20ms commercial path. Preserve complete retained-alias history, deterministic cross-stream ordering, and the 1..1000 output budget, but add a realistic RED that counts compatibility-path network exchanges and then batch a bounded amount of per-stream history through non-transactional pipeline reads before local merge. Keep a fixed memory ceiling and fall back/refill rather than buffering event_count * alias_count without a bound.

@seonghobae
seonghobae marked this pull request as draft September 5, 2026 15:52
@seonghobae
seonghobae marked this pull request as ready for review September 5, 2026 15:57

@seonghobae seonghobae left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Current-head performance finding: retained UUID-alias reads still pay a redundant Valkey exchange before compatibility merge. _activity_canonical_entries_if_alias_free() pipelines SSCAN + canonical XREVRANGE, but when an alias is present it discards both results; read_activity_events() then calls _activity_read_stream_keys() (another SSCAN traversal) and re-fetches the canonical stream in the compatibility pipeline. The existing compatibility-budget regression permits round_trips <= 3, so it does not fail on this duplicated admission/read work. Keep the current retained-history semantics and bounded 1,000-entry buffer, but make the complete first SSCAN page reusable: when cursor=0, carry its aliases plus the already-fetched canonical page into the compatibility merge, trim the canonical page to the per-stream prefetch budget, and fetch only retained alias pages in the second exchange. Realistic acceptance for the one-alias fixture is exactly two network exchanges, not three. Do not hide aliases, scan unrelated keys, or weaken the 1..1000 result bound.

@seonghobae
seonghobae marked this pull request as draft September 5, 2026 16:52
@seonghobae
seonghobae marked this pull request as ready for review September 5, 2026 16:56
@seonghobae
seonghobae marked this pull request as draft September 5, 2026 16:58

@seonghobae seonghobae left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up verification finding on the admission-reuse repair: the complete first SSCAN page now bypasses _activity_read_stream_keys(), so it also bypasses that function's <=999 aliases + canonical stream fan-out guard. A complete page containing 1,000 retained aliases would produce 1,001 stream keys and enter compatibility prefetch instead of failing closed. Preserve the existing total-stream budget on the reused-page path before issuing the alias pipeline. Add a regression where the first SSCAN returns cursor 0 with 1,000 aliases; it must raise Activity history has too many retained compatibility streams after the first two-command admission/data exchange and before any alias fan-out.

@seonghobae
seonghobae marked this pull request as ready for review September 5, 2026 17:01

Copy link
Copy Markdown
Contributor Author

@codex review

Please review exact current head 61ed3a3712d252e3c179a71d297c52f05e1bac20. Treat predecessor reviews as stale evidence. Focus on the retained-alias admission reuse, 1,000-stream fail-closed guard, bounded compatibility prefetch/refill, deterministic cross-stream ordering, and ADR 0363/source/test consistency. Do not suggest weakening the required central CodeQL gate.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant