Skip to content

fix: make ScannerSession disposal terminal - #49

Open
Yangjunjie-Lin wants to merge 1 commit into
mainfrom
fix/scanner-terminal-lifecycle
Open

fix: make ScannerSession disposal terminal#49
Yangjunjie-Lin wants to merge 1 commit into
mainfrom
fix/scanner-terminal-lifecycle

Conversation

@Yangjunjie-Lin

Copy link
Copy Markdown
Owner

Base

BASE_MAIN_SHA: fa6c51a
HEAD_SHA: da4976c
Base: main

The branch was created directly from the fetched origin/main. develop was not merged or cherry-picked.

Reproduction

On the unmodified base implementation, the new scanner lifecycle test file produced 7 failures out of 19 tests. ScannerSession start -> dispose -> start resolved successfully and returned to scanning even though its default owned BrowserScannerFrameDecoder had already been permanently disposed. Dispose from idle, reset after dispose, switchSource after dispose, repeated decoder disposal, and injected-decoder session reuse exposed the same terminal-boundary gap.

Root cause

ScannerSession.dispose() stopped the source/scheduler, disposed only an owned decoder, and cleared listeners, but ScannerSession itself had no terminal lifecycle identity. start() treated stopped as restartable and called reset(), so the public session could re-enter scanning with a permanently disposed owned decoder.

Existing tests covered late completion of a pending start during stop/dispose, but did not attempt to reuse the object after dispose. They also did not distinguish ScannerSession lifecycle ownership from injected decoder resource ownership or assert coalesced repeated disposal.

Ownership distinction

ScannerSession lifecycle and decoder resource ownership are now independent:

  • owned decoder: disposed exactly once
  • injected decoder: never disposed by ScannerSession
  • both session forms: permanently terminal after ScannerSession.dispose()

Fix design

  • Add module-private WeakMap terminal state so no new private field appears in emitted declarations.
  • Mark the session disposed synchronously before asynchronous cleanup begins.
  • Coalesce all dispose calls through one shared cleanup promise.
  • Guard start(), reset(), and switchSource() with SdkException / session_disposed.
  • Keep stop() restartable and preserve start -> stop -> start.
  • Reuse the existing generation invalidation, AbortController cancellation, decoder cancellation, source stop, and scheduler drain.
  • Suppress state/result/observation/diagnostic publication from the moment terminal disposal begins.
  • Keep the public ScannerSessionState union unchanged.

Regression tests

LIFE-D1 through LIFE-D10 cover owned-decoder restart, idle disposal, restart after stop, idempotent concurrent disposal, pending start, active decode drain, source switching, reset, injected decoder preservation/session terminality, late callbacks, listener cleanup, frame release, scheduler counters, worker/task counters, and controlled memory. A separate dispose-from-stopped case completes the lifecycle matrix.

Red evidence before production changes: 12 passed / 7 failed.
Final targeted evidence: 7 files / 72 tests passed, including 20 scanner-lifecycle and 16 scanner-session tests.

Public API

npm run api:snapshot: PASS. No ScannerSessionOptions, ScannerSessionState, ScannerFrameDecoder, mandatory property, or public declaration snapshot change.

Commands executed

  • npm ci: PASS; 468 packages installed; 0 vulnerabilities
  • npm run lint: PASS
  • npm run typecheck: PASS
  • npm run test:unit: PASS; 90 files / 623 tests
  • npm run quality:static: PASS
  • npm run build: PASS
  • npm run api:snapshot: PASS
  • npm run packages:smoke: PASS; 15 public entry points
  • npm run packages:tarball: PASS; 10 publishable tarballs
  • npm run test:e2e:chromium: PASS; 42 tests
  • targeted scanner/frame-scheduler/worker/camera lifecycle set: PASS; 7 files / 72 tests on the final commit
  • npm run benchmark:browser: PASS on clean committed source; 48 tests across Chromium, Firefox, and WebKit
  • npm run check: PASS; coverage run 99 files / 677 tests, 92.39% statements and 79.78% branches, followed by build/API/package gates
  • npm run stable:manifest:verify: PASS; STABLE_MANIFEST_INTEGRITY_GO
  • npm run stable:publication:verify: PASS

The first browser benchmark attempt was correctly rejected by three stale ignored local tracking JSON files from an older source identity. They were isolated without modifying tracked evidence; the clean-source rerun passed 48/48, and the original ignored files were restored byte-for-byte by SHA-256.

Resource cleanup evidence

The active-decode disposal regression asserts decoder cancel once, owned frame dispose once, no result/observation/diagnostic/state publication after terminal entry, activeDecodeCount=0, pendingFrameCount=0, activeTaskCount=0, emittedEvents=0, and finalControlledMemory=0. Worker client/scanner worker tests also verify cancelled work drains and worker activeTaskCount returns to zero.

Stable artifact and physical truth integrity

No v2.0.0 tag, GitHub Release, npm package, Android AAR, stable manifest, publication record, RC evidence, or physical evidence was modified. Stable manifest and publication verification both pass.

Issue #13 remains OPEN and untouched. All physical rows remain POST_RELEASE_VALIDATION_PENDING and all physical counts remain zero; no physical PASS is claimed.

@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, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@vercel

vercel Bot commented Aug 29, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
qr-decoder Ready Ready Preview Aug 29, 2026 11:03pm

@Yangjunjie-Lin

Copy link
Copy Markdown
Owner Author

Terminal lifecycle audit result

All code, API, browser, benchmark, package, native, soak, tracking, industrial, and device-evidence checks passed for da4976c.

Fail-closed blocker: Stable Release Gate failed twice (initial run and one retry) before any stable verifier or publication action. Both attempts reached Verify required npm publication credentials and npm whoami returned E401 Unauthorized for registry.npmjs.org. This is an external repository-secret credential failure, not a ScannerSession test or stable-artifact integrity failure.

Local stable:manifest:verify and stable:publication:verify both pass. No release, publication, manifest, or physical evidence was changed. Issue #13 remains OPEN with POST_RELEASE_VALIDATION_PENDING and zero physical counts.

Final stage verdict while the required remote gate is red: SCANLY_SCANNER_TERMINAL_LIFECYCLE_NO_GO.

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