Skip to content

fix(browser-session): require aggregate-issued lifecycle request authority - #317

Draft
seonghobae wants to merge 50 commits into
feat/privacy-presentation-identityfrom
fix/browser-session-lifecycle-request-capability
Draft

fix(browser-session): require aggregate-issued lifecycle request authority#317
seonghobae wants to merge 50 commits into
feat/privacy-presentation-identityfrom
fix/browser-session-lifecycle-request-capability

Conversation

@seonghobae

@seonghobae seonghobae commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Prerequisite repair for #312 and #314/#316, stacked on #229 exact 6d87dff5dc572fbd74d06309d574a998f23cf02f.

Current exact head is f73cc5def267b99f43986cd3c504b86cb3d489d7. This PR remains Draft during source repair and is not merge-ready until repository contracts, canonical formatting, locked tests, strict Clippy, rustdoc/API docs, production function/line/region/branch coverage exactly 100%, and fresh independent review all accept the same exact head.

Active Browser Session blockers:

  • 5176486914: duplicate/unsettled create recovery loses aggregate-issued attempt_epoch; accepted same-valued ownership and a later failed attempt must remain separate lifecycle facts.
  • 5177440777: CreateFailedUncertain(Some/None) must preserve the exact aggregate-issued create attempt identity even when no complete handle exists.
  • 5178435963: AuthorizedContextOperationRequest must carry the already-validated BrowserContextEpoch as non-authorizing execution/provenance correlation; stale authority remains zero-I/O.
  • 5178943747: destroy request and unproven-destruction evidence must preserve the exact validated BrowserContextEpoch; command ACK is not destruction proof.
  • 5179537115: RecoveryRequired/TransportLost needs a purpose-bounded consuming handoff preserving the exact same adapter plus exact recovery evidence without restoring ordinary mutation authority. No raw P, second adapter, Drop I/O, or id reconstruction.
  • 5177951332: proven-destroyed history must not make command-authority hot state grow unbounded or create validation degrade with historical tombstones. Keep live/uncertain duplicate detection and stale-authority rejection; separate audit history from hot ownership state.
  • 5180191348: Browser Session maps DisposableIsolationId one-to-one to WebDriver BiDi browser.UserContext, but production currently rejects UTF-8 byte length >4096 although the 9 September 2026 W3C WD defines browser.UserContext as text and no such protocol limit. Commit 9818ada98cbdb9ed4054e96b7c7c4f3e52d51f3a adds intentional RED user_context_identity_length.rs; otherwise-valid remote identity must remain losslessly representable for exact destroy/recovery. Resource bounds belong at a cited protocol/frame/runtime/deployment boundary, not an uncited domain constant.
  • exact Clippy/dead-mutability failures from predecessor full CI remain to be repaired; do not weaken lint or coverage gates.

Standards/repository-contract repair completed on this lineage:

  • ADR 0114 records the W3C 9 September 2026 Working Draft (WD-webdriver-bidi-20260909, previous 3 September) without silently repinning the separately qualified runtime revision.
  • 9c17e7c8d6e491a2610e7cc5b48089a6980bfebe removes the stale repository-contract requirement for WD-webdriver-bidi-20260824 and gates the ADR on WD-webdriver-bidi-20260909 instead.
  • f73cc5def267b99f43986cd3c504b86cb3d489d7 synchronizes docs/traceability/browser-session-lifecycle-authority.md, records the arbitrary 4096-byte user-context ceiling as an open active-branch mismatch, and keeps standards freshness separate from runtime qualification.
  • docs/product-technical-gap-baseline.md still contains the stale 24 August continuity note and must be currentized before documentation is code-current; do not treat the trace/ADR repair alone as documentation completion.

Already repaired lineage remains valid: RecoveryRequired sibling exact-handle evidence (5175575251), non-consuming failed finish(&mut self) same-owner retry (5175813759), structural single-adapter binding with no raw port accessor, aggregate-issued create attempt/completion boundary, redacted adapter Debug, idempotent transport-loss evidence, and non-I/O abandonment signaling.

Hosted verification:

  • latest non-skipped full CI is predecessor exact 56a96ad8407b418d1775cfdf091519b57ad1e893 / run 34578759212: repository contracts 176/176 GREEN, fmt GREEN, locked workspace tests GREEN, strict Clippy RED (double_must_use plus unnecessary mut), rustdoc not reached.
  • predecessor coverage artifact 10190957720 (sha256:a6757604038ab58d7d4d18a359f5e3d5d1c6495ddd40d37586298ccbe59eb155) measured branches 800/800, functions 699/699, lines 5920/5924, regions 7314/7318. Repair semantics; no exclusions or coverage-only branches.
  • exact 9818ada... push CI 34614061293 and eb2701b... push CI 34614356319 were Draft-policy skipped, not GREEN evidence. No newer exact-head GREEN is claimed for f73cc5d....
  • local isolated execution of the intentional user-context RED was attempted on the predecessor lineage, but the execution environment could not resolve github.com; this is not a code result and is not promoted as verification evidence.

Shortest causal path: create-attempt recovery correlation → operation/destroy epoch correlation → same-adapter recovery takeover → bounded hot ownership state → remove/replace arbitrary user-context cap with an authoritative boundary and turn user_context_identity_length.rs GREEN → product-baseline sync → Clippy/dead-mutability repair → repository contracts → fmt → locked tests → strict Clippy → rustdoc/API docs → function/line/region/branch 100% → fresh independent review → ordinary #317 adoption → #316 non-force restack/remote-liveness reconciliation → explicitly qualified Chromium navigation/interaction/destruction/post-condition evidence.

Buyer acceptance beyond this foundation remains #316 pending→accepted/quarantined integration, durable crash/process-restart recovery, real browser-observed destruction/post-conditions on an explicitly qualified Chromium lane, #299 3/3 replay, and protected-main release/SBOM/provenance/reproducibility/rollback.

No #229/main/#316 source mutation, force/destructive restack, self-approval, bypass, workflow/ruleset/secret change, sandbox weakening, provider/model pin, coverage weakening, merge, tag, or release.

@coderabbitai

coderabbitai Bot commented Sep 10, 2026

Copy link
Copy Markdown

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: d786d13b-2d0e-4bcf-b949-eeb64ea36473

📥 Commits

Reviewing files that changed from the base of the PR and between 6d87dff and 9cde981.

📒 Files selected for processing (10)
  • crates/originweave-browser-session/src/lib.rs
  • crates/originweave-browser-session/tests/destroy_failure_requires_recovery.rs
  • crates/originweave-browser-session/tests/lifecycle_port_authority.rs
  • crates/originweave-browser-session/tests/lifecycle_port_preflight_side_effect.rs
  • crates/originweave-browser-session/tests/lifecycle_port_same_id_spoof.rs
  • crates/originweave-browser-session/tests/sequential_incarnation_reuse.rs
  • docs/adr/0114-browser-session-disposable-context-authority.md
  • docs/traceability/browser-session-lifecycle-authority.md
  • docs/uml/browser-session-lifecycle-authority.md
  • tests/test_browser_session_lifecycle_contract.py

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


📝 Walkthrough

Walkthrough

BrowserSession이 구체적인 포트를 소비하는 BoundBrowserSession을 제공합니다. 생성·삭제 요청은 aggregate 내부에서 opaque 타입으로 발행됩니다. 포트 계약, 테스트, 권한 문서와 UML이 새 호출 흐름에 맞게 갱신되었습니다.

Changes

라이프사이클 포트 권한

Layer / File(s) Summary
요청 계약과 바운드 세션 API
crates/originweave-browser-session/src/lib.rs
DisposableContextCreateRequestDisposableContextDestroyRequest를 추가했습니다. DisposableContextPort는 요청 객체를 받습니다. bind_lifecycle_port는 aggregate와 구체 포트를 BoundBrowserSession으로 결합합니다.
구현 상태와 포트 호출 검증
crates/originweave-browser-session/src/lib.rs, crates/originweave-browser-session/tests/*
내부 테스트 포트와 통합 테스트를 새 API로 변경했습니다. 바운드 포트만 생성·삭제 호출을 받으며, 바인딩 중 adapter 콜백은 실행되지 않고, 다른 포트로 교체할 수 없음을 검증합니다. 순차 incarnation과 삭제 실패 복구 검사도 갱신했습니다.
권한 설계 문서 갱신
docs/adr/0114-browser-session-disposable-context-authority.md
선형 lifecycle-port binding, opaque 요청, 콜백 금지, 대체 포트 차단 및 마이그레이션 내용을 반영했습니다.
추적성, UML, 계약 테스트 갱신
docs/traceability/browser-session-lifecycle-authority.md, docs/uml/browser-session-lifecycle-authority.md, tests/test_browser_session_lifecycle_contract.py
추적성 문서와 UML을 새 호출 흐름으로 변경했습니다. 계약 테스트는 새 공개 타입과 메서드, 제거된 API, 생성자 비공개 상태를 검사합니다.

Priority: ➖ Normal

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

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant BoundBrowserSession
  participant DisposableContextPort
  Caller->>BoundBrowserSession: bind_lifecycle_port(port)
  Caller->>BoundBrowserSession: create_disposable_context()
  BoundBrowserSession->>DisposableContextPort: create_disposable_context(request)
  Caller->>BoundBrowserSession: destroy_disposable_context(authority)
  BoundBrowserSession->>DisposableContextPort: destroy_disposable_context(request)
Loading

Merge Risk: ⚪ Minimal · up to 9cde9

No actionable current-head defect remains; complete the normal required checks before merging.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 63.64% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 66 functions across 7 files. (3 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 제목은 aggregate-issued lifecycle request authority를 요구하도록 변경한 핵심 API 및 권한 설계를 정확하고 간결하게 설명합니다.
Full details: Docstring Coverage

Explanation

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

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/browser-session-lifecycle-request-capability

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.

@seonghobae
seonghobae marked this pull request as draft September 10, 2026 19:22
@seonghobae
seonghobae marked this pull request as ready for review September 10, 2026 19:27
@seonghobae
seonghobae marked this pull request as draft September 10, 2026 19:33

@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 security finding on 97e0a4d875166ad733e78c1d3f213454ee615f01: the new lifecycle request is non-caller-constructible, but its port ownership is still self-asserted through public DisposableContextPortId::new(u64) plus DisposableContextPort::port_id(). Two distinct adapter instances can both report port_id=101; after A is bound, Browser Session's equality check will accept B as the same port. More strongly, A can relay the borrowed aggregate-issued create/destroy request to B, and B can satisfy the same scalar equality and reach remote lifecycle I/O even though B was never the aggregate-approved adapter instance. The current hostile test only uses 101 vs 102, so it proves mismatch rejection but not non-forgeable adapter ownership.

Required RED before this prerequisite can be GREEN: bind adapter A with id 101, then use distinct adapter B also claiming id 101; B must be rejected before create/destroy I/O and must not be able to consume/replay A's request. Do not repair this by documenting port-id uniqueness or randomizing a public scalar. The binding itself needs a non-caller-constructible Browser Session-approved/linear port capability or equivalent identity that a second adapter cannot self-select or replay. Keep remote BiDi identifiers outside Browser Session domain truth.

Separately, current hosted CI 34520503299 is still RED at rustfmt before tests/Clippy/rustdoc, and coverage measurement also fails; fix that operational RED without weakening gates after the authority model is corrected.

@seonghobae
seonghobae marked this pull request as ready for review September 10, 2026 19:37
@seonghobae
seonghobae marked this pull request as draft September 10, 2026 19:47

Copy link
Copy Markdown
Contributor Author

Exact-head RED evidence for 9caf9bbe4228c443b7d5a4279831765a6a38765a is now causal and reproducible. CI 34522121442 reached the Browser Session integration suite after repairing the pre-existing lifecycle-port test-signature drift. All earlier Browser Session unit/integration cases passed; only the new same-id/different-adapter hostile tests failed.

Coverage job 103021942177 observed:

  • distinct_port_with_same_claimed_id_cannot_create: actual Ok(PresentationMutationAuthority { browser_session: 17, isolation: "spoofed-isolation", browsing_context: 42, context_epoch: 2, ... }), expected Err(LifecyclePortMismatch).
  • distinct_port_with_same_claimed_id_cannot_destroy: actual Ok(()), expected Err(LifecyclePortMismatch).

So a distinct adapter B that self-reports the already-bound scalar port id can both reach create I/O and reach destroy I/O. This falsifies the current claim that aggregate-issued request + DisposableContextPortId binds lifecycle mutation to one adapter instance. Keep this RED until the port ownership is non-forgeable/non-replayable; do not weaken or delete the hostile fixture.

Repository status on this exact head is independently RED at cargo fmt --check as well. Rustfmt diagnostics artifact 10170079709, digest sha256:982aee39ad1383379288ff82575a950d0d72cbee6b2c28b8de62d15d10734068, remains available. Production coverage cannot proceed to exact enforcement while the intentional hostile tests fail. A separate nightly diagnostic also flags AtomicU64::fetch_update as deprecated in favor of try_update; treat that as root-fix work only after compatibility with the pinned stable toolchain is verified, not as a warning suppression exercise.

@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 security finding on 9caf9bbe4228c443b7d5a4279831765a6a38765a: the current port-binding preflight still cannot guarantee the stated “reject before lifecycle I/O” boundary because bind_lifecycle_port / require_bound_lifecycle_port call the public trait method DisposableContextPort::port_id(&self) before issuing or validating the aggregate request. Rust &self is not a purity guarantee: an implementation can use interior mutability, a socket/client handle, atomics, or delegated state and perform remote side effects from port_id(). The rustdoc requirement that port_id be side-effect-free is therefore a convention, not an enforced security invariant. The same problem remains even after replacing the scalar id with a harder-to-forge value if Browser Session must first invoke an arbitrary adapter callback to discover/validate that value.

Required RED: use a hostile port whose identity/preflight callback records a lifecycle side effect, then exercise (a) a first create attempt and (b) a mismatched destroy/create attempt that Browser Session is expected to reject. Both must show zero adapter/browser mutation before an aggregate-issued authority exists and is validated. Do not satisfy this by documenting port_id() purity or by testing only the reviewed WebDriver adapter implementation.

Causal direction: remove authority-sensitive identity discovery from an arbitrary preflight callback. Establish the lifecycle-port binding through a non-caller-constructible composition/binding capability (or a session-owned/linear port wrapper) before lifecycle mutation, then invoke the adapter only under the already validated create/destroy request. If DisposableContextPort remains externally implementable, the contract must not rely on &self implying side-effect freedom. Preserve the current same-id/different-adapter hostile RED and the requirement that requests cannot be replayed into a different bound adapter. Keep remote BiDi identifiers outside Browser Session domain truth.

@seonghobae
seonghobae marked this pull request as ready for review September 10, 2026 20:09
@seonghobae
seonghobae marked this pull request as draft September 10, 2026 20:13
@seonghobae
seonghobae marked this pull request as ready for review September 10, 2026 21:14

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

Please review exact head 9cde981899950b900698a17e7fa739af59f6bb4f, especially the security/API claim that consuming BrowserSession + P into BoundBrowserSession<P> removes pre-authority adapter callbacks and ordinary distinct-adapter substitution/replay without moving protocol-specific BiDi ownership into the Browser Session domain.

@coderabbitai

coderabbitai Bot commented Sep 10, 2026

Copy link
Copy Markdown
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '== Exact revision and PR diff summary =='
git rev-parse HEAD
git status --short
git diff --stat 6d87dff5dc572fbd74d06309d574a998f23cf02f..HEAD
git diff --name-only 6d87dff5dc572fbd74d06309d574a998f23cf02f..HEAD

printf '%s\n' '== Candidate lifecycle files =='
fd -t f -e rs . | while IFS= read -r file; do
  if rg -q -i 'BrowserSession|BoundBrowserSession|DisposableContextPort|bind_lifecycle_port|DisposableContext(Create|Destroy)Request' "$file"; then
    printf '%s\n' "$file"
  fi
done

printf '%s\n' '== Rust API outline for candidate files =='
fd -t f -e rs . | while IFS= read -r file; do
  if rg -q -i 'BrowserSession|BoundBrowserSession|DisposableContextPort|bind_lifecycle_port|DisposableContext(Create|Destroy)Request' "$file"; then
    ast-grep outline "$file" --items all --type struct,trait,impl,function,method --match 'BrowserSession|BoundBrowserSession|DisposableContextPort|bind_lifecycle_port|DisposableContextCreateRequest|DisposableContextDestroyRequest' || true
  fi
done

printf '%s\n' '== Lifecycle declarations, calls, and port-ID remnants =='
rg -n -C 4 --type rust \
  '(\bbind_lifecycle_port\s*\(|\b(create|destroy)_(disposable_)?context\s*\(|\bDisposableContextPort\b|\bBoundBrowserSession\b|\bDisposableContextPortId\b|\bport_id\s*\()' \
  .

printf '%s\n' '== Browser Session BiDi/protocol-specific references =='
rg -n -i -C 3 --type rust \
  '(bidi|webdriver|pending|quarantin|remote.liveness|remote_liveness|accepted)' \
  . \
  -g '!**/test/**' -g '!**/tests/**' || true

printf '%s\n' '== Documentation and external fixtures relevant to binding =='
fd -t f . | while IFS= read -r file; do
  if rg -q -i 'BoundBrowserSession|bind_lifecycle_port|DisposableContextPortId|distinct.adapter|hostile|linear.lifecycle|ADR.?.?0114' "$file"; then
    printf '%s\n' "$file"
  fi
done

Length of output: 147304


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '== Browser Session public types and lifecycle implementation =='
sed -n '1,720p' crates/originweave-browser-session/src/lib.rs

printf '%s\n' '== Focused external hostile tests =='
for file in \
  crates/originweave-browser-session/tests/lifecycle_port_authority.rs \
  crates/originweave-browser-session/tests/lifecycle_port_preflight_side_effect.rs \
  crates/originweave-browser-session/tests/lifecycle_port_same_id_spoof.rs \
  crates/originweave-browser-session/tests/sequential_incarnation_reuse.rs \
  crates/originweave-browser-session/tests/destroy_failure_requires_recovery.rs \
  tests/test_browser_session_lifecycle_contract.py
do
  printf '\n== %s ==\n' "$file"
  cat -n "$file"
done

printf '%s\n' '== Browser Session dependency boundary =='
browser_manifest="$(fd -t f '^Cargo\.toml$' crates/originweave-browser-session | head -n 1)"
printf '%s\n' "$browser_manifest"
cat -n "$browser_manifest"
printf '%s\n' '== Direct Browser Session crate imports =='
rg -n '^\s*use\s+originweave_[A-Za-z0-9_]+' crates/originweave-browser-session/src crates/originweave-browser-session/tests

printf '%s\n' '== Public mutable extraction and lifecycle helper reachability =='
rg -n -C 3 --type rust \
  '(pub(\([^)]*\))?\s+(fn|struct|enum|trait)|fn\s+(create|destroy)_disposable_context_with_port|fn\s+(into_|as_mut|lifecycle_port_mut)|&mut\s+(self|P)|->\s*&mut)' \
  crates/originweave-browser-session/src/lib.rs

printf '%s\n' '== Focused PR diff for implementation and contracts =='
git diff --find-renames --find-copies --unified=50 \
  6d87dff5dc572fbd74d06309d574a998f23cf02f..HEAD -- \
  crates/originweave-browser-session/src/lib.rs \
  tests/test_browser_session_lifecycle_contract.py \
  docs/adr/0114-browser-session-disposable-context-authority.md \
  docs/traceability/browser-session-lifecycle-authority.md \
  docs/uml/browser-session-lifecycle-authority.md

printf '%s\n' '== Changed documents =='
for file in \
  docs/adr/0114-browser-session-disposable-context-authority.md \
  docs/traceability/browser-session-lifecycle-authority.md \
  docs/uml/browser-session-lifecycle-authority.md
do
  printf '\n== %s ==\n' "$file"
  cat -n "$file"
done

Length of output: 215228


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '== Repository-wide lifecycle API references =='
rg -n -C 3 --type rust \
  '(\bcreate_disposable_context\s*\(|\bdestroy_disposable_context\s*\(|\bbind_lifecycle_port\s*\(|\blifecycle_port_mut\b|\binto_(inner|port|browser_session)\b|\bDisposableContextPortId\b|\bport_id\s*\()' \
  .

printf '%s\n' '== Public methods on BrowserSession and BoundBrowserSession =='
rg -n -U --type rust \
  'impl(?:<[^>]+>)?\s+(?:BrowserSession|BoundBrowserSession(?:<[^>]+>)?)\s*\{(?s:.*?)^\}' \
  crates/originweave-browser-session/src/lib.rs || true
rg -n -C 2 --type rust \
  'pub\s+(?:const\s+)?fn\s+(?:create_disposable_context|destroy_disposable_context|bind_lifecycle_port|lifecycle_port|browser_session|into_[A-Za-z_]+|[A-Za-z_]+_mut)\s*\(' \
  crates/originweave-browser-session/src/lib.rs

printf '%s\n' '== Trait implementation locations =='
rg -n -C 2 --type rust 'impl\s+(?:[A-Za-z0-9_<>:]+\s+for\s+)?DisposableContextPort\b' .

printf '%s\n' '== Patch whitespace integrity =='
git diff --check 6d87dff5dc572fbd74d06309d574a998f23cf02f..HEAD

printf '%s\n' '== Exact-head PR checks and review state =='
gh pr view 317 --repo ContextualWisdomLab/OriginWeave \
  --json number,headRefOid,isDraft,mergeStateStatus,reviewDecision,statusCheckRollup \
  --jq '{number, headRefOid, isDraft, mergeStateStatus, reviewDecision, checks: [.statusCheckRollup[] | {name, status, conclusion, workflowName}]}'

Length of output: 68284


@seonghobae Review complete for exact head 9cde981899950b900698a17e7fa739af59f6bb4f.

I found no code-level issue in the requested security/API change.

  • BrowserSession::bind_lifecycle_port(self, port) only moves the aggregate and port into private BoundBrowserSession<P> fields. It invokes no adapter callback.
  • The public lifecycle methods exist on BoundBrowserSession<P>. They accept no replacement port.
  • Repository-wide inspection found no DisposableContextPortId, port_id(), mutable port accessor, extraction method, or alternate public lifecycle entry point.
  • Create validates Active state and reserves the epoch before it calls the bound port.
  • Destroy validates state, session, incarnation, isolation, context, and epoch before it calls the bound port.
  • The request types have private fields and no public constructors.
  • The external hostile fixtures cover the pre-authority callback case, ordinary distinct-adapter substitution, sequential-incarnation replay, and recovery behavior.
  • originweave-browser-session depends only on originweave-core. It does not import or depend on the BiDi crate. The BiDi-specific pending → accepted/quarantined state and remote-liveness responsibility remain outside the Browser Session domain.

The structural binding prevents ordinary callers from substituting or replaying a different adapter after binding. It cannot prevent a malicious trusted DisposableContextPort implementation from internally forwarding an authorized call. ADR 0114 states this TCB boundary.

Exact-head verification is not complete. GitHub reports Rust contracts and Production coverage as queued, and the PR state is UNSTABLE. I cannot determine readiness or approve the PR until the required exact-head checks are terminal and GREEN.

✅ Action performed

Review finished.

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

Merge-blocking integration finding on exact 9cde981899950b900698a17e7fa739af59f6bb4f: the linear BoundBrowserSession<P> closes caller-side port substitution, but the lifecycle contract still has no aggregate→adapter completion phase for creation, and DisposableContextCreateRequest is not unique per create attempt. Every create request in one Browser Session incarnation exposes only the same (BrowserSessionId, BrowserSessionIncarnation) pair, even though BrowserSession already reserves a distinct context epoch before remote I/O. After port.create_disposable_context(&request) returns, the aggregate may accept the handle or reject it as duplicate/ambiguous and enter RecoveryRequired, but DisposableContextPort receives no accept/reject completion carrying a per-attempt identity. A BiDi adapter implementing #316 therefore cannot deterministically move the exact remote tuple from pending→accepted or pending→quarantine without inventing adapter-local correlation/authority, keying only by the same session/incarnation, relying on call order, or prematurely authorizing before Browser Session validation.

Required RED before adoption: one bound port produces at least two creation candidates in the same Browser Session incarnation, retains them as separate pending protocol tuples, and Browser Session accepts one while rejecting the other (e.g. duplicate domain context/isolation). The adapter must promote only the accepted candidate and quarantine exactly the rejected candidate; neither candidate may collide/overwrite because their create requests are indistinguishable. No remote tuple may become authorizing before aggregate acceptance.

Causal repair should extend the Browser Session-owned transaction boundary, not move BiDi ids into this domain: mint a non-caller-constructible per-attempt lifecycle identity/capability (the already-reserved epoch is a natural candidate if its semantics fit), pass it in the create request, and provide an aggregate-issued accept/reject completion that the exact bound port consumes. The adapter keeps protocol tuples pending until that completion. Preserve structural port binding and the existing no-preflight/same-id hostile tests. #316 remains the owner of remote BiDi pending/accepted/quarantine data; #317 should only provide the domain transaction identity/completion contract it needs.

@seonghobae
seonghobae marked this pull request as ready for review September 11, 2026 08:10
@seonghobae
seonghobae marked this pull request as draft September 11, 2026 08:16
@seonghobae
seonghobae marked this pull request as ready for review September 11, 2026 08:21

@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 recovery-correlation finding on 56a96ad8407b418d1775cfdf091519b57ad1e893: create-attempt identity is now correctly minted as attempt_epoch and adapters settle the exact pending tuple through DisposableContextCreateCompletion, but the recovery evidence produced when that settlement itself fails drops the transaction identity. UnsettledAdapterHandle(DisposableContextHandle) records only the remote handle, and DuplicateAdapterHandle(DisposableContextHandle) likewise does not identify the rejected create attempt. This is ambiguous in the exact case the per-attempt protocol was added to solve: a second create attempt can return a handle equal to an already accepted owned handle. The duplicate candidate and the pre-existing owned record then share the same isolation+browsing-context value, while the adapter's pending/quarantine ledger is keyed by the distinct attempt epoch. A separate recovery/reconciliation path cannot prove which protocol tuple failed to settle from the current public evidence alone, and enter_recovery_required() can also deduplicate the pre-existing sibling because DuplicateAdapterHandle(existing) == handle, collapsing two semantically distinct lifecycle facts into one value-level record.

Required hostile RED: in one Browser Session incarnation, accept attempt 1 for handle H; attempt 2 returns the same H; make the aggregate issue Rejected(attempt=2) and make the adapter fail that exact completion. Recovery evidence must retain enough non-authorizing transaction correlation to distinguish attempt 2's unsettled/rejected candidate from attempt 1's previously owned handle after the aggregate enters RecoveryRequired. A recovery owner must be able to address the exact pending/quarantined protocol transaction without reconstructing authority from raw ids or relying on call order.

Minimal causal direction: make create-related recovery evidence carry the aggregate-issued create-attempt identity (and, where needed, intended disposition) alongside the handle, e.g. a purpose-bounded recovery record rather than bare DisposableContextHandle. Preserve the existing handle evidence and do not expose a public constructor for attempt authority. RecoveryRequiredOwnedHandle should continue representing the previously accepted owned record independently; do not deduplicate it merely because a failed new candidate returned the same handle value. Keep BiDi tuple contents/persistence in #316; #317 only needs to preserve the domain transaction correlation required for deterministic recovery.

@seonghobae
seonghobae marked this pull request as draft September 11, 2026 08:34

Copy link
Copy Markdown
Contributor Author

Exact-head CI 34578759212 RCA on 56a96ad8407b418d1775cfdf091519b57ad1e893 is now terminal and code-caused, not runner-caused.

Rust contracts 103197204149: repository contracts 176/176 GREEN, cargo fmt --all --check GREEN, and cargo test --locked --workspace --all-targets GREEN. Strict Clippy then fails at crates/originweave-browser-session/src/lib.rs:583 with clippy::double_must_use: bind_lifecycle_port(...) -> BoundBrowserSession<P> has a bare #[must_use] even though BoundBrowserSession<P> is itself #[must_use]. Under -D warnings this is the exact gate failure; rustdoc/API docs never ran. Minimal fix is not a lint allow: remove the redundant function-level attribute, or give it a genuinely additional contract message only if one exists. The same compilation also reports an unnecessary mut in the duplicate-handle test fixture at line 1347; remove the dead mutability rather than tolerate it.

Production coverage 103197204361: artifact 10190957720, digest sha256:a6757604038ab58d7d4d18a359f5e3d5d1c6495ddd40d37586298ccbe59eb155. Exact totals are branches 800/800, functions 699/699, lines 5920/5924, regions 7314/7318. The only zero-execution source arms in originweave-browser-session/src/lib.rs are inside enter_recovery_required() at the PartialCreationIsolation(_) => false arm and the RecoveryRequiredOwnedHandle | TransportLossOwnedHandle equality arm. This is not a reason to add synthetic coverage: it intersects review 5176486914. The current value-level dedup mixes create-candidate/recovery causes with prior accepted ownership. The causal repair should narrow dedup to semantically identical recovery facts and add the create attempt correlation required by that review; then tests should naturally cover the remaining branches.

Nightly coverage compilation additionally warns that AtomicU64::fetch_update is renamed to try_update on that nightly. Do not suppress or blindly switch while the canonical stable toolchain is Rust 1.97.1; resolve it through a compatibility-proven toolchain/API root fix, not a warning allow.

I returned #317 to Draft because the exact head is RED and review 5176486914 is a real source blocker. No rerun is useful until the source writer produces a successor head.

Copy link
Copy Markdown
Contributor Author

docs/product-technical-gap-baseline.md is no longer fully current after review 5176486914. Its 2026-09-11 continuity note accurately records the existing Browser Session repairs, but it still frames #317 as awaiting verification only and does not record the newly proven create-recovery transaction-correlation gap. On the successor source fix, update that note together with ADR 0114/traceability: active-PR truth must state that create-related recovery evidence must preserve aggregate-issued attempt identity separately from any previously accepted same-valued handle, and that #317 remains source-blocked until the hostile duplicate-H/rejected-completion-failure case is GREEN. Keep protected-main truth separate; do not promote the active PR capability as shipped behavior.

@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 authoritative standards re-check finds a new documentation/provenance blocker on this exact head. The canonical W3C https://www.w3.org/TR/webdriver-bidi/ currently resolves to WebDriver BiDi Working Draft, 9 September 2026, dated version WD-webdriver-bidi-20260909, with 3 September 2026 as the previous version. ADR 0114, lifecycle traceability, product-gap baseline, and this PR body currently state that 24 August 2026 is the latest published Working Draft and that the 9 September snapshot could not be verified; that statement is now directly contradicted by the authoritative W3C TR.

Required repair: update the standards/APA trace to the 9 September 2026 published WD without changing the separately runtime-qualified protocol/browser compatibility pin. Treat this as standards provenance only: do not silently repin runtime behavior, weaken any Browser Session authority invariant, or mix it with #316 protocol tuple ownership. Repository contracts should assert the currently authoritative dated TR only if the project intentionally wants freshness to be a code gate; otherwise assert the semantic boundary and keep publication freshness in traceability/docs so a future W3C publication does not require production-code changes. Exact source blocker 5176486914 and CI RED remain independently unresolved.

@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 recovery-correlation gap remains broader than review 5176486914: CreateFailedUncertain also drops the aggregate-issued create-attempt identity. create_disposable_context_with_port reserves epoch before adapter I/O, but on DisposableContextCreateError::CreateFailedUncertain(isolation) it records only PartialCreationIsolation(isolation) when Some, and records no create-specific recovery evidence at all when None; it then enters RecoveryRequired. The adapter received the exact (session, incarnation, attempt_epoch) request and may still hold a pending/quarantined protocol transaction, but Browser Session's public recovery projection cannot identify which aggregate-issued attempt became uncertain without reconstructing it from next_epoch, call order, or adapter-local state.

This is especially important because ADR 0114 describes lifecycle failure evidence as lossless and #316 must later reconcile protocol pending/quarantined state without moving protocol tuple truth into Browser Session. An optional isolation id is not the transaction identity; None must not erase the fact of the unresolved attempt.

Please broaden the existing create-recovery repair rather than adding another unrelated mechanism. Hostile RED: accept attempt 1 normally; attempt 2 returns CreateFailedUncertain(None) (and separately Some(isolation)); after RecoveryRequired, non-authorizing recovery evidence must identify attempt 2 by its aggregate-issued attempt epoch while preserving any previously accepted owned handle as a separate recovery fact. Do not infer the attempt from next_epoch - 1, call order, or raw protocol ids. A purpose-bounded create-recovery record carrying attempt_epoch, failure/disposition kind, and optional candidate isolation/handle is sufficient; BiDi tuple contents and durable persistence remain #316/recovery-owner concerns.

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

New current-head resource/performance finding: destroyed contexts remain permanently in BrowserSession.contexts, and every later create linearly scans all historical records for isolation aliasing before also checking the BTreeMap key. A long-lived governed session that repeatedly creates → proves destruction → creates again therefore grows hot ownership state without bound and makes creation cost O(total historical contexts), with cumulative O(n²) scan work. This is especially problematic for an enterprise browser runtime where disposable contexts are expected to be churned, and it conflicts with the runtime/resource-limit objective even though authority safety is otherwise monotonic.

The stale-authority invariant does not require retaining every destroyed record in the hot ownership map: PresentationMutationAuthority is bound to session incarnation + monotonically reserved context epoch + isolation + browsing context, so after proven destruction a later reused remote identity can still reject an older authority by epoch mismatch. If immutable audit/provenance retention is required, keep it out of the mutable hot ownership index or bound it explicitly; do not preserve unbounded tombstones in the command-authority structure.

Hostile RED: run a large sequential create → destroy-success cycle with unique handles under one session and prove (a) the hot ownership set stays bounded by unresolved/live ownership rather than total history, (b) an authority from an earlier destroyed cycle remains rejected before adapter I/O even if the adapter later reuses the same browsing-context/isolation values, and (c) create validation does not degrade linearly with the count of already-destroyed historical contexts. Minimal causal fix can remove or compact proven-destroyed records while relying on incarnation+epoch for stale-authority rejection, or split bounded active ownership from a separate immutable audit/provenance sink. Do not weaken duplicate detection for concurrently live/uncertain ownership.

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

AuthorizedContextOperationRequest<O>가 current authority 검증 뒤 exact handle과 operation만 adapter에 전달하고 PresentationMutationAuthority::context_epoch를 버립니다. 따라서 같은 (session, incarnation, isolation, browsing_context)에서 epoch 1→2→3으로 authority가 회전해도 adapter/evidence 쪽에서는 각 실행이 어느 aggregate-issued epoch의 fresh validation을 통과했는지 구분할 수 없습니다. 즉 synchronous call 자체는 stale authority를 막더라도 #316이 요구하는 fresh-authority-at-I/O 및 durable provenance를 exact epoch에 결박할 수 없고, adapter-side queued/retry/evidence가 raw target tuple만으로 서로 합쳐질 위험이 있습니다.

Hostile RED를 추가해 주세요: context 생성 후 epoch를 2로 advance하고 authorized operation을 실행한 뒤, 다시 epoch 3으로 advance해 두 번째 operation을 실행합니다. Adapter fixture가 두 호출에서 각각 정확한 BrowserContextEpoch(2)(3)을 관찰·기록해야 하며 stale epoch 2 재사용은 adapter I/O 전에 0-call로 거절돼야 합니다. GREEN은 opaque AuthorizedContextOperationRequest에 Browser Session이 검증한 exact context epoch를 non-authorizing correlation field로 포함하고 accessor를 제공하는 정도면 충분합니다. 이 값은 adapter addressability/evidence correlation일 뿐 별도 mutation authority나 durable bearer token으로 사용하면 안 됩니다. BiDi-specific pending/accepted/quarantined persistence와 remote-liveness는 계속 #316 owner에 둡니다.

@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 destruction-provenance finding on 56a96ad8407b418d1775cfdf091519b57ad1e893, distinct from authorized-operation review 5178435963: Browser Session validates the full current PresentationMutationAuthority, including context_epoch, immediately before destruction, but DisposableContextDestroyRequest carries only (browser_session, incarnation, DisposableContextHandle). If destruction is unproven, BrowserSessionRecoveryEvidence::UnprovenDestruction likewise retains only the handle. The exact aggregate-issued authority epoch that authorized the destructive I/O is therefore discarded at both the adapter boundary and the recovery projection.

This does not currently let a stale authority through—the stale authority is rejected by context_for_authority_mut before I/O—but it breaks exact causal provenance and deterministic reconciliation for the destructive path. A reviewed BiDi adapter/recovery owner can prove which user-context handle was targeted, but not which Browser Session authority generation was freshly validated for the unproven browser.removeUserContext attempt. That becomes materially ambiguous after one or more advance_context_epoch transitions on the same live handle and conflicts with the stack's requirement that fresh authority be attributable at browser I/O rather than reconstructed from call order/private aggregate state.

Required hostile RED: create one context at epoch 1, advance it to epoch 2, then force destroy_disposable_context to return DestroyFailed. The adapter fixture must observe exact epoch 2 in the opaque destroy request, recovery evidence must preserve the same non-authorizing (handle, epoch 2) destruction-attempt correlation, and stale epoch 1 must still produce zero destroy I/O. Repeat with another context/epoch to prove evidence is not inferred from a global latest epoch or call order.

Minimal causal direction: carry the already-validated BrowserContextEpoch in DisposableContextDestroyRequest as non-authorizing correlation and preserve it with UnprovenDestruction (or a purpose-bounded destroy-recovery record). Do not turn the epoch into a serializable bearer capability, do not move BiDi tuple truth into Browser Session, and do not treat command ACK as destruction proof. The same bound adapter and existing fail-closed validation remain authoritative.

@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 recovery-handoff finding on 56a96ad8407b418d1775cfdf091519b57ad1e893: BoundBrowserSession<P> now structurally owns the only accepted lifecycle adapter, and its #[must_use] contract says unresolved ownership must be finished or handed to recovery. However there is still no public, non-authorizing handoff path that preserves that exact bound adapter together with the Browser Session recovery ledger once the aggregate enters RecoveryRequired or TransportLost.

This is not just an API-completeness concern. context_for_authority_mut() calls require_active(), so after a create/destroy uncertainty or transport loss, normal destroy/authorized-operation paths are intentionally fail-closed. browser_session() exposes only an immutable borrow, port is private, and dropping the wrapper merely increments the process-local abandonment counter before the concrete adapter value is dropped. A separately reviewed recovery owner therefore cannot take custody of the exact adapter plus BrowserSessionRecoveryEvidence; #316 cannot deterministically reconcile its pending/quarantined BiDi tuple through the same bound adapter without adding an ambient/second-adapter path, which this PR explicitly forbids.

Required hostile RED: create an accepted context, force destruction to DestroyFailed (and separately exercise transport loss), observe RecoveryRequired/TransportLost, then transfer the unresolved session into a recovery-only owner. Prove the recovery owner still holds the exact same adapter instance and exact recovery evidence, while ordinary presentation mutation/create paths remain unavailable. Dropping that recovery owner unresolved may count as abandonment; explicit handoff itself must not lose evidence or imply browser cleanup.

Minimal causal direction: add a purpose-bounded consuming transition such as into_recovery(self) -> BoundBrowserSessionRecovery<P> (name not prescribed) that retains the original wrapper/adapter and exposes only reviewed reconciliation capabilities. Do not expose P raw, do not reconstruct an adapter from ids, do not perform browser I/O in Drop, and do not let the handoff mint presentation authority. Protocol-specific BiDi recovery commands/persistence remain #316-owned; #317 only needs a lossless same-owner domain handoff boundary. Until such a path exists, the current statement that unresolved ownership can be “handed to recovery” is not implementable by consumers.

@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 compatibility/security finding on 56a96ad8407b418d1775cfdf091519b57ad1e893: DisposableIsolationId::parse rejects any browser-issued isolation identity whose Rust UTF-8 byte length exceeds 4096 (TooLong). The current Browser Session docs map this value one-to-one to WebDriver BiDi browser.UserContext, but the current W3C WebDriver BiDi Working Draft (9 September 2026) defines browser.UserContext as text and requires it to uniquely identify a user context; it does not define a 4096-byte protocol limit for this identifier. See https://www.w3.org/TR/2026/WD-webdriver-bidi-20260909/#type-browser-UserContext and browser.createUserContext.

This hard-coded domain limit can therefore make OriginWeave reject a protocol-valid browser-issued identity solely because it crossed an implementation-chosen threshold. That is especially problematic here because the value is remote lifecycle addressability needed for exact destroy/recovery; refusing to represent it can turn a successfully created remote user context into an ownership/recovery failure. The use of value.len() also makes the cutoff UTF-8-byte based rather than a specification-defined text constraint.

Required RED: a reviewed lifecycle adapter returns a unique, otherwise valid browser.UserContext whose encoded length is >4096 and a valid browsing context. Browser Session must not lose exact remote ownership merely because of an arbitrary local identifier-length constant. If resource-exhaustion protection is required, bound the authoritative protocol/frame/resource boundary with a cited standard/runtime limit or an explicit deployment policy and preserve lossless recovery evidence; do not silently redefine the semantics of browser.UserContext with an uncited constant.

Minimal causal repair: remove this arbitrary protocol-identity cap, or replace it with a documented/versioned bound backed by an authoritative WebDriver BiDi/Chromium/transport limit plus tests demonstrating that all accepted remote identities remain losslessly representable for create/destroy/recovery. Keep surrounding-whitespace/control-character handling only where it is actually justified by the remote identifier contract; do not normalize or truncate browser-issued identity. This is independent of the existing create-attempt/authority-epoch/recovery-handoff blockers and should not weaken any fail-closed authority rule.

@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 repository-contract blocker on eb2701bcd41aca1e442e8919e8dcef7bde805d47: ADR 0114 was correctly currentized to the W3C 9 September 2026 WebDriver BiDi WD, but tests/test_browser_session_lifecycle_contract.py::test_architecture_decision_and_traceability_are_explicit still requires the literal token WD-webdriver-bidi-20260824 to appear in ADR 0114. The current ADR no longer contains that token. Therefore the next non-skipped exact-head repository-contract run will fail before this branch can earn fresh GREEN, independently of the intentional user_context_identity_length.rs RED.

Repair the contract causally with the standards policy rather than reintroducing stale prose into ADR 0114. If publication freshness is intentionally a repository gate, assert WD-webdriver-bidi-20260909 and synchronize traceability/product baseline in the same source-writer change. If the repository test is meant to protect the semantic boundary rather than every W3C publication date, assert the authoritative-link/semantic contract and keep dated freshness in traceability so the next W3C editorial publication does not create an unrelated code-contract failure. In either case, do not weaken the full repository gate and do not count the current Draft-policy skipped CI as evidence.

@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 capability leak: BoundBrowserSession::browser_session() is documented as the read-only policy/ACL view, but it returns &BrowserSession, and BrowserSession::presentation_authority(&self, BrowsingContextId) is public. After one owned context exists, code that was given only that purportedly read-only aggregate reference can call bound.browser_session().presentation_authority(raw_context_id) and obtain a live PresentationMutationAuthority. That contradicts this slice's core invariant that raw browser identifiers do not become caller-mintable mutation authority and collapses the read-model/capability boundary without any adapter I/O.

Keep this blocking. Hostile acceptance: after normal create, expose only the API intended for read-only policy/ACL inspection and prove external code cannot obtain/clone a presentation capability from that view using only BrowsingContextId; the normal bound owner must still be able to issue current authority through its explicit capability surface. Minimum causal repair is to stop exporting the aggregate method through the read-only view (for example, make BrowserSession::presentation_authority non-public and/or return a dedicated non-authorizing BrowserSessionView from browser_session()). Do not replace this with a runtime check: the unwanted authority-minting path should be absent from the public type surface. Also keep recovery identities purpose-bounded rather than broadening the read-only projection while repairing it.

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

5180191348browser.UserContext 호환성 결함은 4096-byte 상한만 제거해서는 닫히지 않습니다. current exact head의 DisposableIsolationId::parse는 여전히 (a) empty string, (b) leading/trailing whitespace, (c) Unicode control character를 각각 Empty/InvalidCharacter로 거절합니다. 그런데 현재 authoritative WebDriver BiDi WD(2026-09-09)는 browser.UserContext = text이고 user-context id를 “unique string set upon creation”으로만 정의합니다. 이 CDDL text에는 .size, .regexp, non-empty, trim, control-character 제한이 없습니다(RFC 8610 standard prelude에서 text = tstr). 따라서 이 lexical restrictions 역시 WebDriver BiDi domain truth가 아니라 OriginWeave가 임의로 추가한 조건입니다.

이 값은 remote lifecycle addressability이므로 adapter가 browser-issued id를 받은 뒤 표현 단계에서 거절하면 이미 생성됐을 수 있는 user context의 exact browser.removeUserContext/recovery 주소를 잃게 됩니다. 일반적인 ‘sanitization’도 허용하면 안 됩니다. 공백을 trim하거나 control을 drop/normalize하면 다른 remote identity로 바뀔 수 있습니다.

user_context_identity_length.rs 수리를 넓혀 hostile cases를 추가해 주세요: otherwise-valid remote result가 "", " context ", 그리고 JSON에서 escape되어 전달된 control-containing text(예: decoded "ctx\n")를 user-context id로 반환했을 때 DisposableIsolationId가 byte-for-byte/Unicode-scalar-for-scalar 동일한 string을 보존하고 destroy/recovery addressability까지 유지해야 합니다. 특정 Chromium/runtime qualification이 더 좁은 identifier grammar를 실제로 보장한다면 그 제약은 versioned adapter qualification/deployment policy로 증명하되, remote create 이후 exact id 보존을 막는 domain parser로 구현하지 마십시오. 이 finding은 authority/recovery blockers를 완화하지 않으며, browser.UserContext one-to-one mapping을 계속 주장하는 한 lexical normalization/rejection도 함께 제거하거나 authoritative 근거로 재정의해야 합니다.

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

Merge-blocking lifecycle-invalidation gap on exact f73cc5def267b99f43986cd3c504b86cb3d489d7: #312's Browser Session invariant says navigation/renderer replacement invalidates stale presentation authority unless the lifecycle explicitly re-establishes it, but the current foundation has only caller-invoked advance_context_epoch(BrowsingContextId). There is no protocol-agnostic Browser Session transition for an observed navigation/document/renderer replacement. Consequently an epoch-1 PresentationMutationAuthority can remain current across a real remote navigation if no caller happens to rotate the epoch, and execute_authorized_context_operation can then validate that stale pre-navigation authority and reach adapter I/O.

W3C WebDriver BiDi 9 September 2026 exposes browsingContext.navigationStarted as an observable navigation-start event. #316 already correctly states that local Rust lifetimes are not remote-liveness proof; the same principle applies to navigation freshness. Required hostile RED: create an owned context and retain authority epoch 1; record a browser-observed navigation for that exact owned context through a Browser Session-owned, protocol-agnostic invalidation transition; then prove the retained epoch-1 authority is rejected before adapter I/O. The event itself must not mint a replacement mutation capability. A fresh authority may be explicitly re-established only through the owning Browser Session policy after invalidation. Foreign/unknown context events must not invalidate another owner, and repeated delivery must not keep manufacturing epochs or evidence solely because an adapter retried an event.

Keep the mapping direction one-way: #316 may map the reviewed BiDi navigation event (and separately a qualified renderer/document-replacement signal) into the Browser Session invalidation operation, but WebDriver BiDi navigation ids/event payloads remain adapter addressability/evidence, not Browser Session authority. Do not solve this by asking callers to remember to call advance_context_epoch; freshness must be causally tied to the observed lifecycle transition.

@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 standards verification found a provenance regression that is still present on this exact head. The W3C search result for the TR identifies the latest published WebDriver BiDi Working Draft as 24 August 2026 (WD-webdriver-bidi-20260824), while the current Editor's Draft is 9 September 2026 at https://w3c.github.io/webdriver-bidi/. The branch currently states that 9 September is the latest W3C-published Working Draft and tests/test_browser_session_lifecycle_contract.py now hard-requires WD-webdriver-bidi-20260909; that conflates Editor's Draft freshness with W3C TR publication status.

Please repair the traceability model rather than merely changing a date literal: keep separate fields/claims for (a) latest published W3C TR = WD-webdriver-bidi-20260824, and (b) current Editor's Draft observed 2026-09-09. If runtime qualification pins a different revision, keep that third axis separate too. Update ADR 0114, traceability/product baseline, repository contract, and PR body consistently. Do not mark the 9 September Editor's Draft as a published WD. The navigationStarted/UserContext semantics used by the active findings remain available in the current Editor's Draft, so this correction changes provenance status, not the underlying lifecycle finding.

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