Skip to content

feat: add Browser Session lifecycle authority - #313

Merged
seonghobae merged 59 commits into
feat/privacy-presentation-identityfrom
feat/browser-session-lifecycle-authority
Sep 10, 2026
Merged

feat: add Browser Session lifecycle authority#313
seonghobae merged 59 commits into
feat/privacy-presentation-identityfrom
feat/browser-session-lifecycle-authority

Conversation

@seonghobae

@seonghobae seonghobae commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Scope

Implements #312 as the Browser Session bounded-context foundation stacked on #229 exact 7ec83c1be1a8e8724d37c2d6ebbdb215b1b10e23. Browser-domain truth remains in OriginWeave; this PR does not copy browser/sandbox/workflow authority from #148/#212/.github or turn WebDriver/BiDi identifiers into policy authority.

Current head is exact bab489ab244e0edfdb309692ee1f9ecd2e7adde0, produced by the ordinary merge of reviewed child #315 exact 0c6ed89c09ad16b5e170fd80604c29b16ce4f212 into this branch. No force push or destructive restack was used.

Domain contract

originweave-browser-session owns the Browser Session aggregate and disposable-context lifecycle boundary.

  • DisposableContextPort returns the exact browser-issued isolation/user-context identity together with its browsing-context address; these values are lifecycle addressability, not authority.
  • Opaque PresentationMutationAuthority binds Browser Session transport identity, process-local monotonic BrowserSessionIncarnation, isolation identity, browsing context, and context epoch.
  • The incarnation flows through both create and destroy lifecycle I/O, preventing a later aggregate from revalidating retained authority when external session/isolation/context/epoch values are sequentially reused.
  • Uncertain partial creation preserves known remote identity as recovery evidence; duplicate adapter output preserves the complete offending handle outside normal active ownership.
  • Failed destruction preserves the exact handle as UnprovenDestruction and enters RecoveryRequired; no command acknowledgement is treated as destruction proof.
  • Transport loss is tracked orthogonally to ownership recovery. RecoveryRequired remains fail closed for normal authority issuance, context creation, epoch advance, destruction, and normal end.
  • Context epoch and Browser Session incarnation allocation fail closed before wrap. Exhaustion is tested through the same private production allocator path, without mutating the process-global counter.

The lifecycle adapter must eventually map this contract to real browser.createUserContext / browsingContext.create / browser.removeUserContext behavior and prove browser-observed destruction. That transport work is intentionally not implemented here. #314 owns the next Browser Session → BiDi ACL projection and may only consume live aggregate authority after this foundation is verified.

Verification lineage

The pre-adoption #313 head 6486e916dceb4ab5f33f7b390cd76fd4673d6007 was RED: CI 34440868057 failed canonical formatting and exact production coverage. Child #315 repaired the later review findings and exact coverage gaps causally, then reached exact-current GREEN at 0c6ed89... in CI 34475535820: Rust contracts 102865268437 passed repository contracts, formatting, locked tests, strict Clippy, and rustdoc/API docs; Production coverage 102865268210 passed exact function/line/region/branch enforcement. Coverage artifact 10151357764 is bound to that child exact head with digest sha256:fef75f5a1c604731c5a8a62d0a5ced3e6256a070552b77079af88c51264600b0.

CodeRabbit independently reviewed exact child head 0c6ed89... after the final coverage repair and reported no blocking defect in the requested lifecycle scope. That review was not a formal GitHub approval and is not transferred as parent-head verification.

The merge-generated parent CI 34476134476 for bab489ab... skipped both jobs because this parent was Draft, so it is not parent GREEN. This PR must run a fresh exact-head repository generation before any further adoption.

Gate

Keep this PR unmerged until current bab489ab... has exact-head repository contracts, canonical formatting, locked tests, strict Clippy, rustdoc, exact function/line/region/branch coverage, independent current-head review/thread settlement, and any required central control checks for its target path. Earlier child/predecessor GREEN is evidence of lineage only and does not replace parent verification.

Real Chromium user-context creation/removal, destruction post-condition, #299 historical Chrome 150 replay, and a separate current-Stable Chromium qualification remain buyer acceptance under #292. Historical #299 evidence must not be repinned.

No force push, destructive rebase, self-approval, protection bypass, workflow/ruleset/secret mutation, provider/model pin, sandbox weakening, --no-sandbox, protected-main merge, tag, or release is part of this foundation step.

@coderabbitai

coderabbitai Bot commented Sep 10, 2026

Copy link
Copy Markdown

Review Change StackReview Change Stack

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 178656fc-1423-4bb5-ae73-6e070625131f

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

originweave-browser-session 크레이트가 추가되었습니다. 크레이트는 disposable context 소유권, epoch 기반 PresentationMutationAuthority, 실패 격리, 전송 손실 및 세션 종료 조건을 구현합니다. 관련 ADR, 추적성 문서, UML 문서와 계약 테스트도 추가되었습니다.

Changes

브라우저 세션 수명주기

Layer / File(s) Summary
세션 계약과 권한 모델
ARCHITECTURE.md, Cargo.toml, crates/originweave-browser-session/Cargo.toml, crates/originweave-browser-session/src/lib.rs, docs/README.md, docs/adr/*
워크스페이스에 새 크레이트를 추가했습니다. DisposableContextPort, 검증된 격리 ID, context handle, epoch, opaque authority 및 세션 상태·오류 계약을 정의했습니다. ADR 0114를 등록했습니다.
컨텍스트 생성과 수명주기 제어
crates/originweave-browser-session/src/lib.rs
context 생성, authority 발급·재발급, epoch 전진, 정확한 handle 기반 삭제, transport loss, 정상 종료 로직을 구현했습니다. 삭제 실패와 미완료 context는 세션 종료를 차단합니다.
실패 격리와 계약 검증
crates/originweave-browser-session/src/lib.rs, docs/traceability/*, docs/uml/*, tests/*
생성 실패, 중복 ID, stale·foreign authority, epoch 고갈, 삭제 실패, transport loss 및 종료 조건을 테스트했습니다. 추적성 문서와 UML 문서에 구현 상태와 후속 통합 조건을 기록했습니다.

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

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant BrowserSession
  participant DisposableContextPort
  Caller->>BrowserSession: create_disposable_context
  BrowserSession->>DisposableContextPort: create_disposable_context
  DisposableContextPort-->>BrowserSession: DisposableContextHandle
  BrowserSession-->>Caller: PresentationMutationAuthority
  Caller->>BrowserSession: destroy_disposable_context(authority)
  BrowserSession->>DisposableContextPort: destroy_disposable_context
  DisposableContextPort-->>BrowserSession: destruction result
  Caller->>BrowserSession: end
Loading

Merge Risk: 🟡 Moderate · up to 6486e

Duplicate adapter output can leave a browser isolation boundary untracked while the session reports normal termination. This lifecycle leak should be fixed before merge.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 76.74% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 43 functions across 3 files. (8 skipped: … Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
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 제목은 Browser Session 수명주기 권한 추가라는 주요 변경을 정확하고 간결하게 설명합니다.
Full details: Docstring Coverage

Explanation

Docstring coverage is 76.74% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 43 functions across 3 files. (8 skipped: 8 unsupported.)

✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/browser-session-lifecycle-authority

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.

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

Please review exact head 9146d62aa71a8e480dd6ec58258e2217c7d0b293 with emphasis on the Browser Session aggregate boundary: raw browsing-context addressability must not mint authority; session/context/epoch binding and stale/foreign rejection; duplicate adapter output; destruction failure/transport loss quarantine; and whether the narrow DisposableContextPort keeps WebDriver BiDi/CDP as adapters rather than policy authority. Do not treat planned BiDi user-context integration or current runnerless CI as implemented/green evidence.

@coderabbitai

coderabbitai Bot commented Sep 10, 2026

Copy link
Copy Markdown
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

target='9146d62aa71a8e480dd6ec58258e2217c7d0b293'
base='7ec83c1be1a8e8724d37c2d6ebbdb215b1b10e23'

echo '== Exact head verification =='
git rev-parse HEAD
git cat-file -t "$target"
git show -s --format='commit=%H%nparents=%P%nsubject=%s' "$target"

echo '== Commit range and changed files =='
git diff --name-status "$base" "$target"
git diff --stat "$base" "$target"

echo '== Workspace and CI candidates =='
fd -HI -t f '(^package\.json$|^pnpm-workspace\.yaml$|^turbo\.json$|^nx\.json$|^.*\.(ya?ml|json|ts|tsx|js|mjs|cjs)$)' . \
  | rg -n '(^|/)(package\.json|pnpm-workspace\.yaml|turbo\.json|nx\.json)$|\.github/workflows/|browser|session' \
  | head -300

echo '== Changed-file outlines =='
git diff --name-only "$base" "$target" | while IFS= read -r f; do
  echo "--- $f"
  case "$f" in
    *.ts|*.tsx|*.js|*.mjs|*.cjs)
      ast-grep outline "$f" --items all --view expanded 2>&1 || true
      ;;
    *)
      sed -n '1,260p' "$f"
      ;;
  esac
done

echo '== Full target diff =='
git diff --find-renames --find-copies --unified=80 "$base" "$target"

echo '== Authority and lifecycle references across repository =='
rg -n -i -C 4 \
  'DisposableContextPort|BrowserSession|browsing.?context|user.?context|epoch|quarantine|transport.?loss|destroy|dispose|WebDriver|BiDi|CDP' \
  -g '!node_modules/**' -g '!dist/**' -g '!build/**' . | head -1200

Length of output: 201886


🏁 Script executed:

#!/bin/bash
set -euo pipefail

target='9146d62aa71a8e480dd6ec58258e2217c7d0b293'
base='7ec83c1be1a8e8724d37c2d6ebbdb215b1b10e23'
git checkout --detach "$target" >/dev/null 2>&1 || true

echo '== Exact head and changed paths =='
printf 'HEAD='; git rev-parse HEAD
git diff --name-status "$base" "$target"
echo

echo '== Browser Session source with line numbers =='
nl -ba crates/originweave-browser-session/src/lib.rs | sed -n '1,340p'
echo

echo '== Core identity definitions =='
rg -n -C 8 'struct BrowserSessionId|impl BrowserSessionId|struct BrowsingContextId|impl BrowsingContextId' \
  crates/originweave-core
echo

echo '== BrowserSession construction and authority call sites =='
rg -n -C 5 'BrowserSession::start\s*\(|BrowserSession\s*\{|PresentationMutationAuthority\s*\{' \
  -g '*.rs' -g '*.py' -g '*.md' . \
  | head -700
echo

echo '== Documentation navigation entries =='
nl -ba docs/README.md | sed -n '82,108p'
echo
rg -n -C 3 '0114-browser-session|Browser Session lifecycle authority' docs README.md ARCHITECTURE.md CHANGELOG.md
echo

echo '== PR `#313` checks and metadata (informational only) =='
gh pr view 313 --json headRefOid,baseRefOid,statusCheckRollup,reviewDecision,isDraft,title,url \
  --jq '{headRefOid,baseRefOid,reviewDecision,isDraft,title,url,statusCheckRollup:[.statusCheckRollup[]? | {name,conclusion,status,detailsUrl}]}'

Length of output: 41064


@seonghobae Reviewed exact head 9146d62aa71a8e480dd6ec58258e2217c7d0b293.

Blocking finding — Browser Session identity can alias across aggregates

BrowserSession::start accepts a public, caller-constructed BrowserSessionId at crates/originweave-browser-session/src/lib.rs Line 149. BrowserSessionId::new only checks for a nonzero number. Each new aggregate starts its epoch at 1.

If two aggregates use the same BrowserSessionId and an adapter returns the same BrowsingContextId, each aggregate records the same (session, context, epoch) tuple. The duplicate check at Line 185 is local to one aggregate. The authority check at Lines 325-338 then accepts an authority minted by the other aggregate. The second aggregate can call its port to destroy the first aggregate’s context.

Make aggregate identity non-aliased for a browser session. For example, make lifecycle construction consume a non-duplicable session lease from the owner, or include an aggregate-incarnation value in PresentationMutationAuthority and validate it before port I/O. Add a hostile test that starts two aggregates with the same BrowserSessionId, receives the same context ID, and proves that cross-aggregate authority cannot destroy the context.

The remaining reviewed properties are correctly fail-closed within one aggregate: raw context lookup does not mint authority; stale and numerically foreign authority is rejected; destruction failure and transport loss quarantine authority; and DisposableContextPort remains independent of BiDi, CDP, MCP, and policy code.

The BiDi integration and real-browser cleanup evidence remain explicitly deferred. The exact-head checks are queued, so I did not treat CI as passing evidence.

⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

seonghobae commented Sep 10, 2026

Copy link
Copy Markdown
Contributor Author

Exact-current reviewer handoff on 797157a02547e7054e6290090d148483cc7ad560: the blocking cross-aggregate authority-alias finding from CodeRabbit review 5613071726 is still present in production source. The only intervening commit from reviewed 9146d62aa71a8e480dd6ec58258e2217c7d0b293 to current head changes ARCHITECTURE.md; crates/originweave-browser-session/src/lib.rs is unchanged. BrowserSession::start(BrowserSessionId) remains publicly caller-seeded, every aggregate starts next_epoch = 1, and PresentationMutationAuthority still contains only (browser_session, browsing_context, context_epoch). take_context_for_authority therefore cannot distinguish two aggregate incarnations that reuse the same session ID and adapter-returned context ID/epoch.

Preserve this as an executable hostile RED before repair: start two aggregates with the same BrowserSessionId, have separate ports return the same BrowsingContextId, mint epoch-1 authorities, then prove authority A cannot cause aggregate B to call its destroy port. Minimum causal fix should make aggregate identity non-aliased through an owner-issued, non-duplicable lease/incarnation (or equivalent domain proof) and validate it before any port I/O.

Important acceptance nuance: adding an incarnation field only to PresentationMutationAuthority is insufficient if DisposableContextPort is still scoped solely by the aliasable external BrowserSessionId. Aggregate B could mint its own valid incarnation token after a faulty/shared adapter returns A's same context ID and then legitimately reach destroy_disposable_context with an indistinguishable (BrowserSessionId, BrowsingContextId) port call. The non-duplicable lifecycle identity therefore needs to scope creation/destruction at the port/ACL boundary as well, or the owner must otherwise prove globally non-aliasing disposable context identity before ownership is recorded. Do not paper over this with a process-global registry, random unchecked adapter identifier, or public authority constructor.

The active #312/#313 writer lease remains authoritative; this is review evidence only, not a competing source write.

@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.

Exact-head RCA confirms two independent blockers on 797157a. Hosted Rust-contract job 102744511014 executed and failed only because docs/README.md omits ADR 0114 from the canonical ADR index (175 Python tests, exactly one failure). Coverage job 102744510755 executed; functions/lines/branches are 100%, but regions are 485/487 (99.5893%), with the remaining inactive-session error regions in presentation_authority and advance_context_epoch. The security finding from review 5613165179 remains valid: BrowserSessionId+browsing-context+local epoch can alias across aggregate incarnations, and destroy I/O is still scoped only by those aliasable identifiers. I am repairing this by carrying a non-aliasing disposable isolation identity from the browser lifecycle port through stored context, mutation authority, pre-I/O validation, and destruction. For WebDriver BiDi the adapter proof obligation is a one-to-one mapping to the spec-defined unique user-context id; raw context/session identifiers remain addressability, not authority. Hostile acceptance must cover two aggregates with the same external session/context IDs and prove A's authority cannot trigger B I/O while B's authority destroys only B's isolation boundary. No gate weakening, force update, or mutable cross-repo dependency is part of the fix.

…recovery-required

fix: quarantine uncertain Browser Session creation
@seonghobae
seonghobae marked this pull request as ready for review September 10, 2026 12:21

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

Please review exact parent head bab489ab244e0edfdb309692ee1f9ecd2e7adde0 after ordinary adoption of #315. This is a fresh parent-head review request; do not transfer the child review. Exact CI 34476233914 is GREEN: Rust contracts 102867553488 passed repository contracts, formatting, locked tests, strict Clippy and API docs; Production coverage 102867553175 passed exact production function/line/region/branch enforcement. Focus on the Browser Session aggregate, recovery evidence, transport-liveness orthogonality, incarnation/ABA protection through lifecycle I/O, epoch exhaustion, and whether the ordinary merge introduced any parent-scope regression. No formal approval is assumed from the child review.

@coderabbitai

coderabbitai Bot commented Sep 10, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

target='bab489ab244e0edfdb309692ee1f9ecd2e7adde0'
base='7ec83c1be1a8e8724d37c2d6ebbdb215b1b10e23'

echo '== Exact target and topology =='
git cat-file -t "$target"
git show -s --format='commit=%H%nparents=%P%nsubject=%s%nbody=%B' "$target"
git merge-base --is-ancestor "$base" "$target" && echo "base_is_ancestor=true"
echo '== Commits from stacked base =='
git log --oneline --decorate "$base..$target" --max-count=80
echo '== Files changed from stacked base =='
git diff --name-status "$base" "$target"

echo '== Browser Session source outline =='
ast-grep outline crates/originweave-browser-session/src/lib.rs --items all --view expanded
echo '== Browser Session implementation =='
git show "${target}:crates/originweave-browser-session/src/lib.rs" | nl -ba | sed -n '1,680p'

echo '== Browser Session tests and contract tests =='
git ls-tree -r --name-only "$target" | rg '(^|/)(test_.*browser.*session.*\.py|.*browser.*session.*test.*|lib\.rs)$' \
 | while IFS= read -r f; do
    case "$f" in
      crates/originweave-browser-session/src/lib.rs|*browser*session*)
        echo "--- $f"
        git show "${target}:${f}" | nl -ba | sed -n '1,760p'
        ;;
    esac
   done

echo '== Recovery, transport, incarnation, ABA, epoch, and port references =='
git grep -n -I -E \
 'RecoveryRequired|OwnershipUncertain|TransportLost|incarnation|ABA|epoch|EpochExhausted|DisposableContextPort|DisposableContextHandle|destroy_disposable_context|create_disposable_context|cleanup|rollback|duplicate' \
 "$target" -- '*.rs' '*.py' '*.md' | head -1800

echo '== Target changes relevant to this review =='
git diff --find-renames --unified=80 "$base" "$target" -- \
 crates/originweave-browser-session/src/lib.rs \
 tests \
 docs \
 ARCHITECTURE.md Cargo.toml Cargo.lock | head -3000

echo '== Hosted PR exact-head checks =='
gh pr view 313 --repo ContextualWisdomLab/OriginWeave \
 --json headRefOid,baseRefOid,isDraft,reviewDecision,statusCheckRollup,url \
 --jq '{headRefOid,baseRefOid,isDraft,reviewDecision,url,statusCheckRollup:[.statusCheckRollup[]? | {name,status,conclusion,detailsUrl}]}'

Length of output: 181839


🏁 Script executed:

#!/bin/bash
set -euo pipefail

target='bab489ab244e0edfdb309692ee1f9ecd2e7adde0'

echo '== Target topology and recent commits =='
git show -s --format='commit=%H%nparents=%P%nsubject=%s' "$target"
git log --graph --oneline --decorate -25 "$target"

echo '== Browser Session crate manifest and all standalone Rust tests =='
git show "${target}:crates/originweave-browser-session/Cargo.toml" | nl -ba
git ls-tree -r --name-only "$target" crates/originweave-browser-session/tests \
  | while IFS= read -r f; do
      echo "--- $f"
      git show "${target}:${f}" | nl -ba | sed -n '1,520p'
    done

echo '== Focused source: public API and state transition methods =='
git show "${target}:crates/originweave-browser-session/src/lib.rs" | nl -ba \
  | sed -n '1,460p'

echo '== Parent-only commits after `#315` repair tip candidates =='
git log --format='%H %P %s' --ancestry-path 6486e916dceb4ab5f33f7b390cd76fd4673d6007.."$target" \
  | head -80

echo '== Changed files in each recent target ancestor =='
git log --format='%H' -12 "$target" | while IFS= read -r c; do
  echo "--- $c $(git show -s --format=%s "$c")"
  git diff-tree --no-commit-id --name-status -r "$c" | head -120
done

echo '== Public uses and implementations outside the crate =='
git grep -n -I -E \
 'BrowserSession::start|impl[[:space:]]+DisposableContextPort|PresentationMutationAuthority|BrowserSessionRecoveryEvidence|BrowserSessionIncarnation' \
 "$target" -- ':!crates/originweave-browser-session/src/lib.rs' \
 | head -1000

Length of output: 53655


@seonghobae Reviewed exact parent head bab489ab244e0edfdb309692ee1f9ecd2e7adde0 as a fresh review.

No blocking finding in the requested Browser Session scope.

  • The ordinary merge adopts #315 without merge-specific source changes. I found no parent-scope regression in the Browser Session boundary.
  • PresentationMutationAuthority binds session, BrowserSessionIncarnation, isolation, context, and epoch. Its fields remain private and it has no public constructor.
  • BrowserSession::context_for_authority_mut validates all five values before destruction I/O. It rejects foreign and stale authority before DisposableContextPort::destroy_disposable_context.
  • DisposableContextPort receives the same incarnation for creation and destruction. This closes the lifecycle-I/O alias path that an aggregate-local token alone could not close.
  • The external sequential hostile test reuses the same external session, isolation, context, and local epoch. It rejects aggregate A authority in aggregate B with zero B destruction calls. It then accepts only aggregate B authority and passes B’s incarnation to the port.
  • RecoveryRequired retains PartialCreationIsolation, DuplicateAdapterHandle, or UnprovenDestruction evidence. This evidence does not grant cleanup authority.
  • Transport liveness is orthogonal to recovery. A transport-loss report after RecoveryRequired sets transport_lost without replacing the recovery state or deleting recovery evidence.
  • record_transport_loss invalidates active contexts when the state is Active. Repeated reports are idempotent.
  • reserve_epoch fails before creation I/O at exhaustion. Unknown-context epoch advance fails before epoch reservation. Failed epoch reservation leaves the existing authority valid.
  • The crate remains domain-only. It has no production DisposableContextPort implementation and depends only on originweave-core. WebDriver BiDi and CDP remain future adapters.

Hosted exact-head evidence matches the stated status. Rust contracts and Production coverage are successful on run 34476233914. I did not treat the check result as a formal approval.

You are interacting with an AI system.

@seonghobae
seonghobae merged commit 6d87dff into feat/privacy-presentation-identity Sep 10, 2026
6 checks passed
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