Skip to content

sync(desktop): propagate finalized runtime into transport epic - #1979

Open
integry wants to merge 6 commits into
1953-epic-desktop-transport-apifrom
1952-epic-electron-runtime-app
Open

sync(desktop): propagate finalized runtime into transport epic#1979
integry wants to merge 6 commits into
1953-epic-desktop-transport-apifrom
1952-epic-electron-runtime-app

Conversation

@integry

@integry integry commented Aug 29, 2026

Copy link
Copy Markdown
Owner

Integration-only sync of the finalized Electron runtime into the desktop transport/auth sub-epic.

Source d5465a3 passed independent clean Node 22 audits, desktop/UI typechecks, 26 Electron tests, focused deep-link tests, production packaging, sandboxed 1280x820 visual smoke, all current-head CI, and final Ultrafix review 9/10 with no blockers. This PR exists only to give #1977 the exact reviewed runtime/CSS/deep-link foundation. It must merge only into 1953-epic-desktop-transport-api, never main.

propr-dev[bot] added 2 commits August 29, 2026 18:53
…the #1960/#1961 placeholder boundary.

Implemented the packaged-renderer CSS fix without changing the #1960/#1961 placeholder boundary.

Key changes:

- Desktop Vite now loads `propr-ui`’s PostCSS pipeline using native cross-platform paths.
- Tailwind resolves its config/content relative to `propr-ui`.
- Production builds fail if emitted CSS contains `@tailwind`/`@apply` or lacks `.h-5`, `.space-y-5`, `.bg-primary-500`, or `.dashboard-card`.
- Packaged smoke now measures the 1280×820 window, logo bounds, controls, help text, button, and runtime footer spacing.

Validation passed:

- Production desktop package
- Emitted CSS inspection
- Desktop/UI typechecks
- 24 Electron tests
- 21 DesktopExperience tests
- Web UI production build
- `git diff --check`

The sandboxed launch was attempted but blocked before window creation because this non-root container cannot configure Electron’s root-owned `4755` sandbox helper or create a user namespace. The smoke remains strict and does not use `--no-sandbox`; CI already provisions the helper correctly.

PR: #1971
Comment by: @integry (ID: 5464159479)
Model: gpt-5.6-sol
Implemented F3 only; S1 remains untouched.

- Hardened `propr://open` validation in [security.ts](/tmp/git-processor/worktrees/integry/propr/pr-1971-followup-2026-08-29T19-10-30/apps/desktop/src/security.ts:25), rejecting external/protocol-relative paths, traversal, controls, malformed encoding, login/pairing routes, and hosted-flow parameters.
- Added ordered startup buffering and post-load hash routing in [desktop-deep-link.ts](/tmp/git-processor/worktrees/integry/propr/pr-1971-followup-2026-08-29T19-10-30/propr-ui/src/desktop-deep-link.ts:3), integrated at [desktop.tsx](/tmp/git-processor/worktrees/integry/propr/pr-1971-followup-2026-08-29T19-10-30/propr-ui/src/desktop.tsx:145).
- Added `/tasks`, startup-buffer, post-load, and unsafe-input regressions.

Validation:

- Electron tests: 26/26 passed
- Focused UI tests: 5/5 passed
- Desktop/UI typechecks: passed
- UI lint: passed
- Production package: passed
- `git diff --check`: passed
- Sandboxed layout/API-origin smoke: attempted twice, but the container cannot launch Electron because generated `chrome-sandbox` is `node:node 0755`; Electron requires root ownership and mode `4755`. No sandbox-disabling workaround was used.

PR: #1971
Comment by: @integry (ID: 5464313572)
Model: gpt-5.6-sol
@integry

integry commented Aug 29, 2026

Copy link
Copy Markdown
Owner Author

/review

Integration-only review. Confirm this exact already-reviewed runtime head can merge cleanly into 1953-epic-desktop-transport-api without dropping client/instance-pairing API behavior or changing the target to main. Source validation and 9/10 no-blocker review are documented in the PR body.

@integry

integry commented Aug 29, 2026

Copy link
Copy Markdown
Owner Author

/review

@integry integry added the AI label Aug 29, 2026
@integry

integry commented Aug 29, 2026

Copy link
Copy Markdown
Owner Author

/review

@propr-dev

propr-dev Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

AI Code Review Complete requested by @integry

Posted 1 review:

View Task Details

@propr-dev

propr-dev Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

🔍 AI Code Review — codex:gpt-5.6-sol

Overall Evaluation

This integration sync is ready to merge into 1953-epic-desktop-transport-api, subject to the four pending checks completing successfully. The changed code preserves the existing desktop bridge/profile contracts while adding validated dashboard deep links and stronger packaged-renderer verification; it should not be retargeted to main.

Defense-in-depth deep-link handlingapps/desktop/src/security.ts validates protocol structure, decoded paths, reserved authentication parameters, and pairing routes before the renderer updates the hash router.

Startup delivery is preservedpropr-ui/src/desktop-deep-link.ts buffers accepted navigation until an active profile has loaded the dashboard, with tests covering ordering and unsafe inputs.

Packaged CSS and layout are verified — The renderer build rejects uncompiled Tailwind output, while the packaged smoke test checks representative selectors and real 1280×820 control bounds.

Merge blockers

No merge blockers.

Suggestions

These are optional follow-ups and are not sent to /fix.

S1: 🟢 Bound the pending-link queue

DesktopDeepLinkNavigation retains every accepted link received while the dashboard is unavailable. Applying a modest queue limit or last-navigation-wins policy would prevent unusual repeated protocol activations from growing memory indefinitely. This is optional hardening because each entry is validated and length-limited, and ordinary startup behavior is covered.

S2: 🟢 Add a mounted-router test

A future integration test mounting DesktopRoot with the real HashRouter could confirm that buffered links are observed after React commits the dashboard, complementing the current navigation-class tests. The existing implementation remains correct because the final hash is available when HashRouter mounts.

Score

The implementation is focused, well-tested, and has no verified code blocker or current-head failure. The remaining checks are pending rather than failing.

Score: 9/10


🤖 Review Details

  • Model: GPT-5.6 Sol
  • Time: 2m 4s
  • Tokens: 45,346 (40,823 in / 4,523 out)
  • Cost: $0.29

View Task


💡 Next step: Comment /fix to address F# merge blockers only.
F# IDs increment across review comments and remain permanent, so selectors such as /fix F3 F5 stay unambiguous across cycles. Suggestions require a separate ordinary follow-up request.

🤖 Review by ProPR

@github-actions

Copy link
Copy Markdown

Full Test Suite Results

Validation failed during: Full test suite.

  • Dependency install: success
  • Workspace build: success
  • Docs validation: success
  • Redis startup: success
  • Full test suite: failure
View Test Output

> propr@0.8.15 test:full:prepared
> npm run test:server


> propr@0.8.15 test:server
> node scripts/run-test-suite.mjs


[1/320] apps/desktop/src/deep-link-delivery.test.ts
TAP version 13
# Subtest: desktop deep-link delivery
    # Subtest: delivers a link received after did-finish-load but before global window assignment
    ok 1 - delivers a link received after did-finish-load but before global window assignment
      ---
      duration_ms: 1.714011
      type: 'test'
      ...
    1..1
ok 1 - desktop deep-link delivery
  ---
  duration_ms: 2.746399
  type: 'suite'
  ...
1..1
# tests 1
# suites 1
# pass 1
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 242.769386

[2/320] apps/desktop/src/ipc.test.ts
TAP version 13
# Subtest: desktop session IPC operations
    # Subtest: logs out through the active Electron session with credentials and without following redirects
    ok 1 - logs out through the active Electron session with credentials and without following redirects
      ---
      duration_ms: 24.185897
      type: 'test'
      ...
    # Subtest: rejects untrusted logout endpoints before making a session request
    ok 2 - rejects untrusted logout endpoints before making a session request
      ---
      duration_ms: 0.686092
      type: 'test'
      ...
    1..2
ok 1 - desktop session IPC operations
  ---
  duration_ms: 26.023918
  type: 'suite'
  ...
1..1
# tests 2
# suites 1
# pass 2
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 290.27746

[3/320] apps/desktop/src/preload-bridge.test.ts
TAP version 13
# Subtest: desktop preload bridge
    # Subtest: exposes only the narrow frozen namespaces
    ok 1 - exposes only the narrow frozen namespaces
      ---
      duration_ms: 1.531289
      type: 'test'
      ...
    # Subtest: maps profile and credential operations to fixed channels
    ok 2 - maps profile and credential operations to fixed channels
      ---
      duration_ms: 0.550088
      type: 'test'
      ...
    # Subtest: does not expose Electron event objects to deep-link listeners
    ok 3 - does not expose Electron event objects to deep-link listeners
      ---
      duration_ms: 0.348731
      type: 'test'
      ...
    # Subtest: buffers startup and second-instance deep links until the renderer subscribes
    ok 4 - buffers startup and second-instance deep links until the renderer subscribes
      ---
      duration_ms: 0.280644
      type: 'test'
      ...
    1..4
ok 1 - desktop preload bridge
  ---
  duration_ms: 3.988038
  type: 'suite'
  ...
1..1
# tests 4
# suites 1
# pass 4
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 236.826867

[4/320] apps/desktop/src/profile-store.test.ts
TAP version 13
# Subtest: desktop profile store
    # Subtest: persists validated profiles and active selection
    ok 1 - persists validated profiles and active selection
      ---
      duration_ms: 16.805233
      type: 'test'
      ...
    # Subtest: encrypts credentials before writing app-owned storage
    ok 2 - encrypts credentials before writing app-owned storage
      ---
      duration_ms: 7.349084
      type: 'test'
      ...
    # Subtest: serializes concurrent credential writes with last-write semantics
    ok 3 - serializes concurrent credential writes with last-write semantics
      ---
      duration_ms: 5.90125
      type: 'test'
      ...
    # Subtest: orders concurrent credential writes and removals by invocation
    ok 4 - orders concurrent credential writes and removals by invocation
      ---
      duration_ms: 9.377813
      type: 'test'
      ...
    # Subtest: refuses plaintext fallback when encryption is unavailable or basic_text
    ok 5 - refuses plaintext fallback when encryption is unavailable or basic_text
      ---
      duration_ms: 2.823754
      type: 'test'
      ...
    # Subtest: rejects unsafe endpoints and path-like profile identifiers
    ok 6 - rejects unsafe endpoints and path-like profile identifiers
      ---
      duration_ms: 8.237453
      type: 'test'
      ...
    1..6
ok 1 - desktop profile store
  ---
  duration_ms: 52.869121
  type: 'suite'
  ...
1..1
# tests 6
# suites 1
# pass 6
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 293.959125

[5/320] apps/desktop/src/security.test.ts
TAP version 13
# Subtest: desktop URL security
    # Subtest: only accepts HTTPS and loopback HTTP API endpoints
    ok 1 - only accepts HTTPS and loopback HTTP API endpoints
      ---
      duration_ms: 1.475576
      type: 'test'
      ...
    # Subtest: denies unsafe external browser schemes and credential-bearing URLs
    ok 2 - denies unsafe external browser schemes and credential-bearing URLs
      ---
      duration_ms: 0.201156
      type: 'test'
      ...
    # Subtest: requires an exact loopback development origin
    ok 3 - requires an exact loopback development origin
      ---
      duration_ms: 0.254125
      type: 'test'
      ...
    # Subtest: retains IPC trust for hash-routed packaged renderer URLs only
    ok 4 - retains IPC trust for hash-routed packaged renderer URLs only
      ---
      duration_ms: 0.284321
      type: 'test'
      ...
    # Subtest: allowlists custom protocol actions and extracts them from argv
    ok 5 - allowlists custom protocol actions and extracts them from argv
      ---
      duration_ms: 0.325909
      type: 'test'
      ...
    # Subtest: accepts a normal internal dashboard route from an open deep link
    ok 6 - accepts a normal internal dashboard route from an open deep link
      ---
      duration_ms: 0.675231
      type: 'test'
      ...
    # Subtest: rejects malformed and unsafe open deep-link paths
    ok 7 - rejects malformed and unsafe open deep-link paths
      ---
      duration_ms: 1.031557
      type: 'test'
      ...
    # Subtest: publishes a restrictive production policy
    ok 8 - publishes a restrictive production policy
      ---
      duration_ms: 0.33229
      type: 'test'
      ...
    # Subtest: relaxes inline scripts only while Vite serves the development renderer
    ok 9 - relaxes inline scripts only while Vite serves the development renderer
      ---
      duration_ms: 0.366905
      type: 'test'
      ...
    1..9
ok 1 - desktop URL security
  ---
  duration_ms: 6.638908
  type: 'suite'
  ...
1..1
# tests 9
# suites 1
# pass 9
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 252.987075

[6/320] apps/desktop/src/vite-file-system-url.test.ts
TAP version 13
# Subtest: Vite filesystem renderer URLs
    # Subtest: preserves an absolute POSIX path after the /@fs/ prefix
    ok 1 - preserves an absolute POSIX path after the /@fs/ prefix
      ---
      duration_ms: 0.950395
      type: 'test'
      ...
    # Subtest: normalizes a Windows drive-letter path and separators
    ok 2 - normalizes a Windows drive-letter path and separators
      ---
      duration_ms: 0.185687
      type: 'test'
      ...
    1..2
ok 1 - Vite filesystem renderer URLs
  ---
  duration_ms: 2.341833
  type: 'suite'
  ...
1..1
# tests 2
# suites 1
# pass 2
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 248.905736

[7/320] apps/desktop/src/window-options.test.ts
TAP version 13
# Subtest: desktop BrowserWindow security
    # Subtest: isolates and sandboxes the renderer without Node or webviews
    ok 1 - isolates and sandboxes the renderer without Node or webviews
      ---
      duration_ms: 1.250285
      type: 'test'
      ...
    # Subtest: uses the native inset title bar only on macOS
    ok 2 - uses the native inset title bar only on macOS
      ---
      duration_ms: 0.181168
      type: 'test'
      ...
    1..2
ok 1 - desktop BrowserWindow security
  ---
  duration_ms: 2.449164
  type: 'suite'
  ...
1..1
# tests 2
# suites 1
# pass 2
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 241.715058

[8/320] packages/api/test/agentLoginRoutes.test.ts
TAP version 13
# Subtest: agent login session manager
    # Subtest: builds an allowlisted Docker login container without forwarding host secrets
    ok 1 - builds an allowlisted Docker login container without forwarding host secrets
      ---
      duration_ms: 3.043624
      type: 'test'
      ...
    # Subtest: maps a ProPR-managed account to the managed host root and marks its container ownership as safe to normalize
    ok 2 - maps a ProPR-managed account to the managed host root and marks its container ownership as safe to normalize
      ---
      duration_ms: 1.305939
      type: 'test'
      ...
    # Subtest: creates a ProPR-managed credential directory before starting Docker
    ok 3 - creates a ProPR-managed credential directory before starting Docker
      ---
      duration_ms: 2.250513
      type: 'test'
      ...
    # Subtest: fails clearly instead of resolving a default home path inside a containerized API
    ok 4 - fails clearly instead of resolving a default home path inside a containerized API
      ---
      duration_ms: 0.697132
      type: 'test'
      ...
    # Subtest: rejects unsafe credential roots and option-like image names
    ok 5 - rejects unsafe credential roots and option-like image names
      ---
      duration_ms: 0.504422
      type: 'test'
      ...
    # Subtest: streams sanitized output, accepts input, and records successful completion
    ok 6 - streams sanitized output, accepts input, and records successful completion
      ---
      duration_ms: 1.781046
      type: 'test'
      ...
    # Subtest: prevents concurrent logins that write the same credential directory
    ok 7 - prevents concurrent logins that write the same credential directory
      ---
      duration_ms: 0.665433
      type: 'test'
      ...
    # Subtest: pulls a missing agent image before creating the login container
    ok 8 - pulls a missing agent image before creating the login container
      ---
      duration_ms: 0.746364
      type: 'test'
      ...
    # Subtest: renews the session deadline when the user sends input
    ok 9 - renews the session deadline when the user sends input
      ---
      duration_ms: 0.688476
      type: 'test'
      ...
    # Subtest: sweeps only orphaned login containers from the current stack scope
    ok 10 - sweeps only orphaned login containers from the current stack scope
      ---
      duration_ms: 0.518117
      type: 'test'
      ...
    1..10
ok 1 - agent login session manager
  ---
  duration_ms: 13.595549
  type: 'suite'
  ...
# Subtest: agent login routes
    # Subtest: starts and returns only the requesting user login session
    ok 1 - starts and returns only the requesting user login session
      ---
      duration_ms: 0.818548
      type: 'test'
      ...
    # Subtest: accepts the same agent alias on start and follow-up calls, including while disabled
    ok 2 - accepts the same agent alias on start and follow-up calls, including while disabled
      ---
      duration_ms: 0.641619
      type: 'test'
      ...
    # Subtest: rejects interactive login for an unsupported agent type
    ok 3 - rejects interactive login for an unsupported agent type
      ---
      duration_ms: 0.521965
      type: 'test'
      ...
    1..3
ok 2 - agent login routes
  ---
  duration_ms: 2.126901
  type: 'suite'
  ...
# [2026-08-29 19:34:13.816 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-iYQPtJ/008-agentLoginRoutes.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 19:34:13.828 +0000] �[32mINFO�[39m: �[36mSQLite database connection closed�[39m
# [2026-08-29 19:34:13.828 +0000] �[32mINFO�[39m: �[36mShutting down queue...�[39m
# [2026-08-29 19:34:13.828 +0000] �[32mINFO�[39m: �[36mQueue was never initialized, nothing to shutdown�[39m
1..2
# tests 13
# suites 2
# pass 13
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 2285.201997

[9/320] packages/api/test/agentRuntimeRoutes.test.ts
TAP version 13
# Subtest: agent runtime package routes
    # Subtest: queues one validated package profile for the unified agent image
    ok 1 - queues one validated package profile for the unified agent image
      ---
      duration_ms: 4.399937
      type: 'test'
      ...
    # Subtest: persists a failed state when queue submission fails
    ok 2 - persists a failed state when queue submission fails
      ---
      duration_ms: 0.915399
      type: 'test'
      ...
    # Subtest: rejects a package missing from an effective runtime before queueing
    ok 3 - rejects a package missing from an effective runtime before queueing
      ---
      duration_ms: 0.84597
      type: 'test'
      ...
    # Subtest: times out slow runtime package validation before queueing
    ok 4 - times out slow runtime package validation before queueing
      ---
      duration_ms: 5.809449
      type: 'test'
      ...
    # Subtest: returns package suggestions from configured runtimes
    ok 5 - returns package suggestions from configured runtimes
      ---
      duration_ms: 0.874403
      type: 'test'
      ...
    # Subtest: enforces PROPR_ADMIN_USERS when configured
    ok 6 - enforces PROPR_ADMIN_USERS when configured
      ---
      duration_ms: 0.384698
      type: 'test'
      ...
    # Subtest: denies runtime package changes by default when no admin policy is configured
    ok 7 - denies runtime package changes by default when no admin policy is configured
      ---
      duration_ms: 0.45539
      type: 'test'
      ...
    # Subtest: requires runtime-management permission before returning runtime package state
    ok 8 - requires runtime-management permission before returning runtime package state
      ---
      duration_ms: 0.409695
      type: 'test'
      ...
    # Subtest: warms the package catalog when an admin loads runtime package state
    ok 9 - warms the package catalog when an admin loads runtime package state
      ---
      duration_ms: 0.521514
      type: 'test'
      ...
    # Subtest: does not warm the package catalog when a member is rejected
    ok 10 - does not warm the package catalog when a member is rejected
      ---
      duration_ms: 2.912569
      type: 'test'
      ...
    # Subtest: resolves the runtime build queue lazily when queueing
    ok 11 - resolves the runtime build queue lazily when queueing
      ---
      duration_ms: 0.922222
      type: 'test'
      ...
    # Subtest: reports apply load failures through the route response
    ok 12 - reports apply load failures through the route response
      ---
      duration_ms: 0.458957
      type: 'test'
      ...
    # Subtest: verifies the current effective images through the runtime-management boundary
    ok 13 - verifies the current effective images through the runtime-management boundary
      ---
      duration_ms: 0.624486
      type: 'test'
      ...
    # Subtest: rejects runtime verification without runtime-management permission
    ok 14 - rejects runtime verification without runtime-management permission
      ---
      duration_ms: 0.275214
      type: 'test'
      ...
    1..14
ok 1 - agent runtime package routes
  ---
  duration_ms: 21.705084
  type: 'suite'
  ...
# [2026-08-29 19:34:15.936 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-iYQPtJ/009-agentRuntimeRoutes.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 19:34:15.954 +0000] �[32mINFO�[39m: �[36mSQLite database connection closed�[39m
1..1
# tests 14
# suites 1
# pass 14
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 2126.887247

[10/320] packages/api/test/attachmentUploadCleanup.test.ts
TAP version 13
# Subtest: upload handler removes Multer files rejected before attachment processing
ok 1 - upload handler removes Multer files rejected before attachment processing
  ---
  duration_ms: 7.459089
  type: 'test'
  ...
# Subtest: attachment processing removes temporary and final files when persistence fails
ok 2 - attachment processing removes temporary and final files when persistence fails
  ---
  duration_ms: 5.559121
  type: 'test'
  ...
# Subtest: temporary cleanup refuses paths outside its configured root
ok 3 - temporary cleanup refuses paths outside its configured root
  ---
  duration_ms: 0.650094
  type: 'test'
  ...
# Subtest: temporary cleanup rejects dot-segment paths that resolve above its root
ok 4 - temporary cleanup rejects dot-segment paths that resolve above its root
  ---
  duration_ms: 0.514561
  type: 'test'
  ...
# Subtest: attachment processing rejects path-like draft IDs and still cleans its temp file
ok 5 - attachment processing rejects path-like draft IDs and still cleans its temp file
  ---
  duration_ms: 2.041522
  type: 'test'
  ...
# [2026-08-29 19:34:18.178 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-iYQPtJ/010-attachmentUploadCleanup.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 19:34:18.193 +0000] �[32mINFO�[39m: �[36mSQLite database connection closed�[39m
1..5
# tests 5
# suites 0
# pass 5
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 2122.223233

[11/320] packages/api/test/authGithubTokens.test.ts
TAP version 13
# Refreshing GitHub token for user octocat (force=true)
# Successfully refreshed GitHub token for user octocat
# Refreshing GitHub token for user octocat (force=true)
# GitHub token refresh failed with status 503
# Refreshing GitHub token for user octocat (force=true)
# Subtest: isGitHubTokenExpired handles missing, future, exact, and past expiry values
ok 1 - isGitHubTokenExpired handles missing, future, exact, and past expiry values
  ---
  duration_ms: 2.560071
  type: 'test'
  ...
# Subtest: ensureAuthenticated refreshes an expired GitHub token before continuing
ok 2 - ensureAuthenticated refreshes an expired GitHub token before continuing
  ---
  duration_ms: 6.077188
  type: 'test'
  ...
# Subtest: ensureAuthenticated reports a temporary error when refresh fails recoverably
ok 3 - ensureAuthenticated reports a temporary error when refresh fails recoverably
  ---
  duration_ms: 1.644531
  type: 'test'
  ...
# Successfully refreshed GitHub token for user octocat
# Saved refreshed GitHub token for concurrent request by user octocat
# Refreshing GitHub token for user octocat (force=true)
# GitHub token refresh response missing access_token
# Refreshing GitHub token for user octocat (force=true)
# GitHub token refresh error: bad_refresh_token - The refresh token is invalid.
# Marked GitHub OAuth session for user octocat as requiring re-authentication (bad_refresh_token)
# GitHub token expired or revoked, attempting token refresh
# Refreshing GitHub token for user octocat (force=true)
# Successfully refreshed GitHub token for user octocat
# Token refresh successful, client should retry
# Subtest: ensureAuthenticated coalesces concurrent expired-token refreshes for one session
ok 4 - ensureAuthenticated coalesces concurrent expired-token refreshes for one session
  ---
  duration_ms: 10.662971
  type: 'test'
  ...
# Subtest: ensureAuthenticated rejects an expired GitHub token with no refresh token
ok 5 - ensureAuthenticated rejects an expired GitHub token with no refresh token
  ---
  duration_ms: 0.623524
  type: 'test'
  ...
# Subtest: ensureAuthenticated reports a temporary error when refresh omits an access token
ok 6 - ensureAuthenticated reports a temporary error when refresh omits an access token
  ---
  duration_ms: 0.992103
  type: 'test'
  ...
# Subtest: ensureAuthenticated clears the session after an unrecoverable refresh error
ok 7 - ensureAuthenticated clears the session after an unrecoverable refresh error
  ---
  duration_ms: 1.262026
  type: 'test'
  ...
# Subtest: GitHub route auth error emits TOKEN_REFRESHED after a successful refresh
ok 8 - GitHub route auth error emits TOKEN_REFRESHED after a successful refresh
  ---
  duration_ms: 1.13533
  type: 'test'
  ...
# [2026-08-29 19:34:20.943 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-iYQPtJ/011-authGithubTokens.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 19:34:20.961 +0000] �[32mINFO�[39m: �[36mSQLite database connection closed�[39m
1..8
# tests 8
# suites 0
# pass 8
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 2622.041525

[12/320] packages/api/test/authRedirect.test.ts
TAP version 13
# Subtest: auth redirect allowlist treats FRONTEND_URL as exact host only
ok 1 - auth redirect allowlist treats FRONTEND_URL as exact host only
  ---
  duration_ms: 47.683714
  type: 'test'
  ...
# Subtest: proxy mode auth redirect fallback returns the hosted UI origin
ok 2 - proxy mode auth redirect fallback returns the hosted UI origin
  ---
  duration_ms: 7.910792
  type: 'test'
  ...
# Subtest: auth redirect allowlist permits subdomains only for explicit wildcard-style hosts
ok 3 - auth redirect allowlist permits subdomains only for explicit wildcard-style hosts
  ---
  duration_ms: 4.94249
  type: 'test'
  ...
# Subtest: auth redirect allowlist permits leading-dot COOKIE_DOMAIN subdomains
ok 4 - auth redirect allowlist permits leading-dot COOKIE_DOMAIN subdomains
  ---
  duration_ms: 4.533816
  type: 'test'
  ...
# Subtest: auth redirect allowlist permits exact additional hosts without permitting their subdomains
ok 5 - auth redirect allowlist permits exact additional hosts without permitting their subdomains
  ---
  duration_ms: 6.679693
  type: 'test'
  ...
# Subtest: auth redirect allowlist permits wildcard entries with protocol prefixes
ok 6 - auth redirect allowlist permits wildcard entries with protocol prefixes
  ---
  duration_ms: 6.193316
  type: 'test'
  ...
# Subtest: auth redirect allowlist rejects invalid URLs and non-http protocols
ok 7 - auth redirect allowlist rejects invalid URLs and non-http protocols
  ---
  duration_ms: 6.333016
  type: 'test'
  ...
# Subtest: auth redirect allowlist only permits cleartext HTTP for localhost
ok 8 - auth redirect allowlist only permits cleartext HTTP for localhost
  ---
  duration_ms: 5.793147
  type: 'test'
  ...
# Subtest: auth redirect allowlist permits configured local IP literals
ok 9 - auth redirect allowlist permits configured local IP literals
  ---
  duration_ms: 5.299867
  type: 'test'
  ...
# Subtest: auth redirect allowlist ignores malformed additional host entries
ok 10 - auth redirect allowlist ignores malformed additional host entries
  ---
  duration_ms: 5.199207
  type: 'test'
  ...
# [2026-08-29 19:34:23.588 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-iYQPtJ/012-authRedirect.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 19:34:23.679 +0000] �[32mINFO�[39m: �[36mSQLite database connection closed�[39m
1..10
# tests 10
# suites 0
# pass 10
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 2526.353803

[13/320] packages/api/test/configAgentValidation.antigravity.test.ts
TAP version 13
# Subtest: agent config validation accepts antigravity and rejects gemini for new configs
ok 1 - agent config validation accepts antigravity and rejects gemini for new configs
  ---
  duration_ms: 1582.510626
  type: 'test'
  ...
# [2026-08-29 19:34:25.844 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-iYQPtJ/013-configAgentValidation.antigravity.test.ts/propr.test.sqlite"
#     environment: "test"
1..1
# tests 1
# suites 0
# pass 1
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 2143.808555

[14/320] packages/api/test/configReloadSubscription.test.ts
TAP version 13
# Subtest: API config subscription serializes the startup reload with settings updates
ok 1 - API config subscription serializes the startup reload with settings updates
  ---
  duration_ms: 5.03506
  type: 'test'
  ...
# Subtest: API config subscription reloads repository updates
ok 2 - API config subscription reloads repository updates
  ---
  duration_ms: 0.544267
  type: 'test'
  ...
# Subtest: API config subscription ignores unrelated and malformed events
ok 3 - API config subscription ignores unrelated and malformed events
  ---
  duration_ms: 0.529809
  type: 'test'
  ...
# Subtest: a failed API settings reload does not block the next notification
ok 4 - a failed API settings reload does not block the next notification
  ---
  duration_ms: 0.394626
  type: 'test'
  ...
1..4
# tests 4
# suites 0
# pass 4
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 241.807955

[15/320] packages/api/test/configRoutesIndexingSummarization.test.ts
TAP version 13
# [2026-08-29 19:34:28.512 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-iYQPtJ/015-configRoutesIndexingSummarization.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 19:34:28.541 +0000] �[32mINFO�[39m: �[36mRunning database migrations...�[39m
# Subtest: postSummarizationSettings trims model-specific aliases before saving
ok 1 - postSummarizationSettings trims model-specific aliases before saving
  ---
  duration_ms: 1879.394211
  type: 'test'
  ...
# Subtest: postSummarizationSettings rejects enabled summarization without a primary alias
ok 2 - postSummarizationSettings rejects enabled summarization without a primary alias
  ---
  duration_ms: 3.11715
  type: 'test'
  ...
# [2026-08-29 19:34:28.796 +0000] �[32mINFO�[39m: �[36mDatabase migrations completed successfully�[39m
# [2026-08-29 19:34:28.798 +0000] �[32mINFO�[39m: �[36mSuccessfully loaded agents configuration�[39m
#     agentCount: 2
# [2026-08-29 19:34:28.798 +0000] �[32mINFO�[39m: �[36mSuccessfully loaded summarization settings�[39m
#     summarization: {
#       "enabled": false,
#       "agent_alias": "",
#       "fallback_agent_alias": "",
#       "custom_prompt": ""
#     }
# [2026-08-29 19:34:28.800 +0000] �[32mINFO�[39m: �[36mSuccessfully saved summarization settings�[39m
#     summarization: {
#       "enabled": true,
#       "agent_alias": "codex:gpt-5.5",
#       "fallback_agent_alias": "fallback:gpt-5.4",
#       "custom_prompt": ""
#     }
# [2026-08-29 19:34:28.800 +0000] �[32mINFO�[39m: �[36mSuccessfully loaded summarization settings�[39m
#     summarization: {
#       "enabled": true,
#       "agent_alias": "codex:gpt-5.5",
#       "fallback_agent_alias": "fallback:gpt-5.4",
#       "custom_prompt": ""
#     }
# [2026-08-29 19:34:28.810 +0000] �[32mINFO�[39m: �[36mSQLite database connection closed�[39m
1..2
# tests 2
# suites 0
# pass 2
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 2328.838245

[16/320] packages/api/test/connectAuth.test.ts
TAP version 13
# Subtest: relay tunnel mode uses Connect without local OAuth credentials
ok 1 - relay tunnel mode uses Connect without local OAuth credentials
  ---
  duration_ms: 1.413098
  type: 'test'
  ...
# Subtest: relay tunnel mode preserves Connect for operator-supplied service endpoints
ok 2 - relay tunnel mode preserves Connect for operator-supplied service endpoints
  ---
  duration_ms: 0.250748
  type: 'test'
  ...
# Subtest: local relay mode uses Connect without a per-instance OAuth App
ok 3 - local relay mode uses Connect without a per-instance OAuth App
  ---
  duration_ms: 0.359171
  type: 'test'
  ...
# Subtest: off-tunnel relay inference rejects callbacks outside the exact loopback allowlist
ok 4 - off-tunnel relay inference rejects callbacks outside the exact loopback allowlist
  ---
  duration_ms: 0.357708
  type: 'test'
  ...
# Subtest: off-tunnel custom relay enrollment does not infer hosted Connect auth
ok 5 - off-tunnel custom relay enrollment does not infer hosted Connect auth
  ---
  duration_ms: 0.192891
  type: 'test'
  ...
# Subtest: literal example OAuth placeholders do not enable GitHub web auth
ok 6 - literal example OAuth placeholders do not enable GitHub web auth
  ---
  duration_ms: 0.163495
  type: 'test'
  ...
# Subtest: explicit custom GitHub web auth remains supported
ok 7 - explicit custom GitHub web auth remains supported
  ---
  duration_ms: 0.196637
  type: 'test'
  ...
# Subtest: explicit custom GitHub web auth wins over relay inference off-tunnel
ok 8 - explicit custom GitHub web auth wins over relay inference off-tunnel
  ---
  duration_ms: 0.191056
  type: 'test'
  ...
# Subtest: Connect authorization URL carries the exact callback and CSRF state
ok 9 - Connect authorization URL carries the exact callback and CSRF state
  ---
  duration_ms: 0.550528
  type: 'test'
  ...
# Subtest: redeems a Connect code server-to-server without exposing the relay token in the body
ok 10 - redeems a Connect code server-to-server without exposing the relay token in the body
  ---
  duration_ms: 27.875971
  type: 'test'
  ...
# Subtest: binds the Connect identity username to the validated token owner
ok 11 - binds the Connect identity username to the validated token owner
  ---
  duration_ms: 0.853754
  type: 'test'
  ...
1..11
# tests 11
# suites 0
# pass 11
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 312.925148

[17/320] packages/api/test/connectAuthCallback.test.ts
TAP version 13
# [2026-08-29 19:34:31.826 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-iYQPtJ/017-connectAuthCallback.test.ts/propr.test.sqlite"
#     environment: "test"
# Subtest: Connect login preserves a validated redirect_to across Passport session regeneration
ok 1 - Connect login preserves a validated redirect_to across Passport session regeneration
  ---
  duration_ms: 3043.483718
  type: 'test'
  ...
# [2026-08-29 19:34:34.866 +0000] �[32mINFO�[39m: �[36mSQLite database connection closed�[39m
1..1
# tests 1
# suites 0
# pass 1
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 5622.55286

[18/320] packages/api/test/corsValidation.test.ts
TAP version 13
# Subtest: CORS allows the hosted UI origin under proxy mode
ok 1 - CORS allows the hosted UI origin under proxy mode
  ---
  duration_ms: 1.084785
  type: 'test'
  ...
# Subtest: CORS rejects unrelated origins under proxy mode
ok 2 - CORS rejects unrelated origins under proxy mode
  ---
  duration_ms: 0.237213
  type: 'test'
  ...
# Subtest: CORS allows requests with no origin
ok 3 - CORS allows requests with no origin
  ---
  duration_ms: 0.138388
  type: 'test'
  ...
# Subtest: CORS allows only the exact packaged desktop renderer custom origin
ok 4 - CORS allows only the exact packaged desktop renderer custom origin
  ---
  duration_ms: 0.199703
  type: 'test'
  ...
# Subtest: CORS allows HTTP(S) loopback origins for development
ok 5 - CORS allows HTTP(S) loopback origins for development
  ---
  duration_ms: 0.247262
  type: 'test'
  ...
# Subtest: CORS rejects unsafe schemes and non-loopback hosts
ok 6 - CORS rejects unsafe schemes and non-loopback hosts
  ---
  duration_ms: 0.173214
  type: 'test'
  ...
# Subtest: CORS allows COOKIE_DOMAIN subdomains for preview environments
ok 7 - CORS allows COOKIE_DOMAIN subdomains for preview environments
  ---
  duration_ms: 0.211905
  type: 'test'
  ...
# Subtest: CORS preserves http COOKIE_DOMAIN preview compatibility
ok 8 - CORS preserves http COOKIE_DOMAIN preview compatibility
  ---
  duration_ms: 0.162103
  type: 'test'
  ...
# Subtest: CORS validator factory throws on an invalid FRONTEND_URL
ok 9 - CORS validator factory throws on an invalid FRONTEND_URL
  ---
  duration_ms: 0.39658
  type: 'test'
  ...
# Subtest: Express CORS boundary is sanitized in development mode
ok 10 - Express CORS boundary is sanitized in development mode
  ---
  duration_ms: 74.135158
  type: 'test'
  ...
# Subtest: Express CORS boundary is sanitized in production mode
ok 11 - Express CORS boundary is sanitized in production mode
  ---
  duration_ms: 28.402855
  type: 'test'
  ...
# Subtest: Socket.IO applies the shared CORS validator to the packaged desktop renderer
ok 12 - Socket.IO applies the shared CORS validator to the packaged desktop renderer
  ---
  duration_ms: 8.306381
  type: 'test'
  ...
1..12
# tests 12
# suites 0
# pass 12
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 582.201226

[19/320] packages/api/test/demoMode.test.ts
TAP version 13
# Subtest: demoModeReadOnlyMiddleware rejects mutating requests in demo mode
ok 1 - demoModeReadOnlyMiddleware rejects mutating requests in demo mode
  ---
  duration_ms: 6.447408
  type: 'test'
  ...
# Subtest: demoModeReadOnlyMiddleware blocks auth metadata mutations
ok 2 - demoModeReadOnlyMiddleware blocks auth metadata mutations
  ---
  duration_ms: 2.049437
  type: 'test'
  ...
# Subtest: isDemoMode accepts common truthy environment values
ok 3 - isDemoMode accepts common truthy environment values
  ---
  duration_ms: 1.155197
  type: 'test'
  ...
# Subtest: configured demo mode keeps auth and middleware on the same startup value
ok 4 - configured demo mode keeps auth and middleware on the same startup value
  ---
  duration_ms: 1.776617
  type: 'test'
  ...
# Subtest: demo Redis facade covers read-only route Redis usage
ok 5 - demo Redis facade covers read-only route Redis usage
  ---
  duration_ms: 3.594762
  type: 'test'
  ...
# Subtest: demo Express GET routes work with the in-memory Redis facade
ok 6 - demo Express GET routes work with the in-memory Redis facade
  ---
  duration_ms: 60.051861
  type: 'test'
  ...
# Subtest: ensureAuthenticated attaches the synthetic demo user
ok 7 - ensureAuthenticated attaches the synthetic demo user
  ---
  duration_ms: 1.581773
  type: 'test'
  ...
# Subtest: ensureAuthenticated ignores bearer auth and attaches the synthetic demo user in demo mode
ok 8 - ensureAuthenticated ignores bearer auth and attaches the synthetic demo user in demo mode
  ---
  duration_ms: 1.49406
  type: 'test'
  ...
# Subtest: demo repository metadata resolves enabled configured repositories
ok 9 - demo repository metadata resolves enabled configured repositories
  ---
  duration_ms: 14.177744
  type: 'test'
  ...
# Subtest: repository config branch validation documents ProPR-supported branch names
ok 10 - repository config branch validation documents ProPR-supported branch names
  ---
  duration_ms: 2.482235
  type: 'test'
  ...
# [2026-08-29 19:34:38.465 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-iYQPtJ/019-demoMode.test.ts/propr.test.sqlite"
#     environment: "test"
# Subtest: /api/github/repos returns configured and persisted repositories in demo mode
ok 11 - /api/github/repos returns configured and persisted repositories in demo mode
  ---
  duration_ms: 266.263296
  type: 'test'
  ...
# Subtest: demo repository metadata resolves persisted repositories without configured allowlists
ok 12 - demo repository metadata resolves persisted repositories without configured allowlists
  ---
  duration_ms: 10.466794
  type: 'test'
  ...
# Subtest: demo repository metadata ignores malformed database repository names
ok 13 - demo repository metadata ignores malformed database repository names
  ---
  duration_ms: 1.669177
  type: 'test'
  ...
# Subtest: planner demo reads use the curated database without owner or repository allowlists
ok 14 - planner demo reads use the curated database without owner or repository allowlists
  ---
  duration_ms: 8.671121
  type: 'test'
  ...
# [2026-08-29 19:34:38.818 +0000] �[32mINFO�[39m: �[36mSuccessfully saved monitored repositories�[39m
#     repos: [
#       {
#         "id": "1",
#         "name": "integry/propr",
#         "enabled": true
#       },
#       {
#         "id": "2",
#         "name": "integry/private",
#         "enabled": true
#       },
#       {
#         "id": "3",
#         "name": "integry/disabled",
#         "enabled": false
#       }
#     ]
# [2026-08-29 19:34:38.820 +0000] �[32mINFO�[39m: �[36mSuccessfully loaded all monitored repositories�[39m
#     total_repos: 3
# [2026-08-29 19:34:38.831 +0000] �[32mINFO�[39m: �[36mSuccessfully loaded all monitored repositories�[39m
#     total_repos: 0
# [2026-08-29 19:34:38.841 +0000] �[32mINFO�[39m: �[36mSuccessfully saved monitored repositories�[39m
#     repos: [
#       {
#         "id": "1",
#         "name": "integry/propr",
#         "enabled": true
#       },
#       {
#         "id": "2",
#         "name": "integry/private",
#         "enabled": true
#       }
#     ]
# Subtest: repo todo demo reads use the curated database without owner filters
ok 15 - repo todo demo reads use the curated database without owner filters
  ---
  duration_ms: 16.920304
  type: 'test'
  ...
# Subtest: auth demo-mode metadata endpoint reports startup environment value
ok 16 - auth demo-mode metadata endpoint reports startup environment value
  ---
  duration_ms: 7.404626
  type: 'test'
  ...
1..16
# tests 16
# suites 0
# pass 16
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 2876.699999

[20/320] packages/api/test/desktopAuth.test.ts
TAP version 13
# [desktop-auth] {
#   action: 'pairing_started',
#   actorUserId: undefined,
#   pairingId: 'dpr_2tsiCQPnFeBj4z3lsENVpw',
#   tokenId: undefined,
#   clientName: 'Work Laptop'
# }
# [desktop-auth] {
#   action: 'pairing_started',
#   actorUserId: undefined,
#   pairingId: 'dpr_1tAR3cfB7ubbZj4odG1qZw',
#   tokenId: undefined,
#   clientName: 'Windows desktop'
# }
# [desktop-auth] {
#   action: 'pairing_started',
#   actorUserId: undefined,
#   pairingId: 'dpr_KpdvFCxEjnXZiXyBU03gIw',
#   tokenId: undefined,
#   clientName: 'MacBook Pro'
# }
# [desktop-auth] {
#   action: 'pairing_approved',
#   actorUserId: '101',
#   pairingId: 'dpr_KpdvFCxEjnXZiXyBU03gIw',
#   tokenId: undefined,
#   clientName: 'MacBook Pro'
# }
# [desktop-auth] {
#   action: 'token_issued',
#   actorUserId: '101',
#   pairingId: 'dpr_KpdvFCxEjnXZiXyBU03gIw',
#   tokenId: '147430e9-89fb-4f17-94c6-6d8574c9a3e4',
#   clientName: 'MacBook Pro'
# }
# [desktop-auth] {
#   action: 'pairing_started',
#   actorUserId: undefined,
#   pairingId: 'dpr_y2kWPNI6jErygDmMn3Ayzg',
#   tokenId: undefined,
#   clientName: 'Linux workstation'
# }
# [desktop-auth] {
#   action: 'pairing_approved',
#   actorUserId: '101',
#   pairingId: 'dpr_y2kWPNI6jErygDmMn3Ayzg',
#   tokenId: undefined,
#   clientName: 'Linux workstation'
# }
# [desktop-auth] {
#   action: 'pairing_started',
#   actorUserId: undefined,
#   pairingId: 'dpr_VXjTKZE9uxB9--NXDzTJOg',
#   tokenId: undefined,
#   clientName: 'Old laptop'
# }
# [desktop-auth] {
#   action: 'pairing_started',
#   actorUserId: undefined,
#   pairingId: 'dpr_6WxvpxZkyzXJPkn81B2cBg',
#   tokenId: undefined,
#   clientName: 'Desktop app'
# }
# [desktop-auth] {
#   action: 'pairing_approved',
#   actorUserId: '101',
#   pairingId: 'dpr_6WxvpxZkyzXJPkn81B2cBg',
#   tokenId: undefined,
#   clientName: 'Desktop app'
# }
# [desktop-auth] {
#   action: 'token_issued',
#   actorUserId: '101',
#   pairingId: 'dpr_6WxvpxZkyzXJPkn81B2cBg',
#   tokenId: '78c43271-476f-415a-86bf-f484d1cdbb7a',
#   clientName: 'Desktop app'
# }
# [desktop-auth] {
#   action: 'token_revoked',
#   actorUserId: '101',
#   pairingId: undefined,
#   tokenId: '78c43271-476f-415a-86bf-f484d1cdbb7a',
#   clientName: undefined
# }
# [desktop-auth] {
#   action: 'pairing_started',
#   actorUserId: undefined,
#   pairingId: 'dpr_0PWsF3-6TWsMo_UPhAcEbQ',
#   tokenId: undefined,
#   clientName: 'Desktop app'
# }
# [desktop-auth] {
#   action: 'pairing_approved',
#   actorUserId: '101',
#   pairingId: 'dpr_0PWsF3-6TWsMo_UPhAcEbQ',
#   tokenId: undefined,
#   clientName: 'Desktop app'
# }
# [desktop-auth] {
#   action: 'token_issued',
#   actorUserId: '101',
#   pairingId: 'dpr_0PWsF3-6TWsMo_UPhAcEbQ',
#   tokenId: '1d46701a-4a03-4034-b8eb-042634b59971',
#   clientName: 'Desktop app'
# }
# Subtest: desktop browser pairing
    # Subtest: stores only a device-secret hash and builds a fixed trusted approval URL
    ok 1 - stores only a device-secret hash and builds a fixed trusted approval URL
      ---
      duration_ms: 18.601968
      type: 'test'
      ...
    # Subtest: uses the configured API browser entry and preserves only a managed hosted tunnel selector
    ok 2 - uses the configured API browser entry and preserves only a managed hosted tunnel selector
      ---
      duration_ms: 5.498272
      type: 'test'
      ...
    # Subtest: issues an opaque token once, resolves its owner, and never stores plaintext credentials
    ok 3 - issues an opaque token once, resolves its owner, and never stores plaintext credentials
      ---
      duration_ms: 15.739126
      type: 'test'
      ...
    # Subtest: rejects the wrong secret without revealing pairing state
    ok 4 - rejects the wrong secret without revealing pairing state
      ---
      duration_ms: 6.179232
      type: 'test'
      ...
    # Subtest: expires unapproved pairings and cleans retained expired records
    ok 5 - expires unapproved pairings and cleans retained expired records
      ---
      duration_ms: 6.893165
      type: 'test'
      ...
    # Subtest: rejects unsafe names and non-HTTPS approval origins
    ok 6 - rejects unsafe names and non-HTTPS approval origins
      ---
      duration_ms: 4.193908
      type: 'test'
      ...
    1..6
ok 1 - desktop browser pairing
  ---
  duration_ms: 58.63215
  type: 'suite'
  ...
# Subtest: instance token ownership and revocation
    # Subtest: lists safe metadata only and limits revocation to the owner
    ok 1 - lists safe metadata only and limits revocation to the owner
      ---
      duration_ms: 13.991774
      type: 'test'
      ...
    # Subtest: honors optional token expiry
    ok 2 - honors optional token expiry
      ---
      duration_ms: 6.881714
      type: 'test'
      ...
    # Subtest: REST authentication accepts instance tokens while optional GitHub bearer auth is disabled
    ok 3 - REST authentication accepts instance tokens while optional GitHub bearer auth is disabled
      ---
      duration_ms: 3.547491
      type: 'test'
      ...
    1..3
ok 2 - instance token ownership and revocation
  ---
  duration_ms: 24.886879
  type: 'suite'
  ...
# Subtest: pairing approval request protection
    # Subtest: accepts only the exact HTTPS frontend origin
    ok 1 - accepts only the exact HTTPS frontend origin
      ---
      duration_ms: 2.112944
      type: 'test'
      ...
    # Subtest: requires a browser session even when another authentication method supplied the user
    ok 2 - requires a browser session even when another authentication method supplied the user
      ---
      duration_ms: 2.832015
      type: 'test'
      ...
    1..2
ok 3 - pairing approval request protection
  ---
  duration_ms: 5.058581
  type: 'suite'
  ...
# [2026-08-29 19:34:41.213 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-iYQPtJ/020-desktopAuth.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 19:34:41.296 +0000] �[32mINFO�[39m: �[36mSQLite database connection closed�[39m
1..3
# tests 11
# suites 3
# pass 11
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 2934.543564

[21/320] packages/api/test/dockerCommandSafety.test.ts
TAP version 13
# Subtest: accepts Docker IDs and ProPR-generated container names
ok 1 - accepts Docker IDs and ProPR-generated container names
  ---
  duration_ms: 0.970632
  type: 'test'
  ...
# Subtest: rejects container references that could be parsed as options or shell syntax
ok 2 - rejects container references that could be parsed as options or shell syntax
  ---
  duration_ms: 0.153627
  type: 'test'
  ...
# Subtest: production subprocess call sites do not invoke a command shell
ok 3 - production subprocess call sites do not invoke a command shell
  ---
  duration_ms: 80.892406
  type: 'test'
  ...
# Subtest: no-shell guard rejects aliases and enabled shell options
ok 4 - no-shell guard rejects aliases and enabled shell options
  ---
  duration_ms: 2.582971
  type: 'test'
  ...
1..4
# tests 4
# suites 0
# pass 4
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 919.891375

[22/320] packages/api/test/instanceAuthorization.test.ts
TAP version 13
# Subtest: instance authorization
    # Subtest: defaults unassigned authenticated users to members on a new installation
    ok 1 - defaults unassigned authenticated users to members on a new installation
      ---
      duration_ms: 11.321791
      type: 'test'
      ...
    # Subtest: refuses startup until a bootstrap or durable administrator exists
    ok 2 - refuses startup until a bootstrap or durable administrator exists
      ---
      duration_ms: 4.75789
      type: 'test'
      ...
    # Subtest: grants full admin permissions to PROPR_ADMIN_USERS
    ok 3 - grants full admin permissions to PROPR_ADMIN_USERS
      ---
      duration_ms: 2.761875
      type: 'test'
      ...
    # Subtest: resolves durable roles by numeric GitHub ID after a username change
    ok 4 - resolves durable roles by numeric GitHub ID after a username change
      ---
      duration_ms: 2.685282
      type: 'test'
      ...
    # Subtest: resolves demo users without installation permissions
    ok 5 - resolves demo users without installation permissions
      ---
      duration_ms: 2.157116
      type: 'test'
      ...
    # Subtest: permission middleware rejects members with a stable error code
    ok 6 - permission middleware rejects members with a stable error code
      ---
      duration_ms: 2.000333
      type: 'test'
      ...
    # Subtest: current-user responses expose capabilities without OAuth credentials
    ok 7 - current-user responses expose capabilities without OAuth credentials
      ---
      duration_ms: 1.765224
      type: 'test'
      ...
    1..7
ok 1 - instance authorization
  ---
  duration_ms: 28.722126
  type: 'suite'
  ...
# Subtest: instance member service
    # Subtest: does not let an unassigned user claim the initial administrator role
    ok 1 - does not let an unassigned user claim the initial administrator role
      ---
      duration_ms: 3.259794
      type: 'test'
      ...
    # Subtest: stores a bootstrap administrator against their numeric GitHub ID
    ok 2 - stores a bootstrap administrator against their numeric GitHub ID
      ---
      duration_ms: 6.374257
      type: 'test'
      ...
    # Subtest: adds explicit members and writes an audit entry
    ok 3 - adds explicit members and writes an audit entry
      ---
      duration_ms: 3.58993
      type: 'test'
      ...
    # Subtest: keeps environment authority separate from durable role assignments
    ok 4 - keeps environment authority separate from durable role assignments
      ---
      duration_ms: 2.591186
      type: 'test'
      ...
    # Subtest: prevents removing the last durable administrator
    ok 5 - prevents removing the last durable administrator
      ---
      duration_ms: 4.638276
      type: 'test'
      ...
    # Subtest: allows role changes once another administrator exists
    ok 6 - allows role changes once another administrator exists
      ---
      duration_ms: 8.9689
      type: 'test'
      ...
    1..6
ok 2 - instance member service
  ---
  duration_ms: 29.826808
  type: 'suite'
  ...
# Subtest: instance catalog
    # Subtest: returns operational agent and repository fields without installation internals
    ok 1 - returns operational agent and repository fields without installation internals
      ---
      duration_ms: 2.239159
      type: 'test'
      ...
    # Subtest: projects indexing status only for enabled repository and branch entries
    ok 2 - projects indexing status only for enabled repository and branch entries
      ---
      duration_ms: 1.738285
      type: 'test'
      ...
    1..2
ok 3 - instance catalog
  ---
  duration_ms: 4.066039
  type: 'suite'
  ...
# Subtest: instance admin routes
    # Subtest: resolves a GitHub username and creates one durable member assignment
    ok 1 - resolves a GitHub username and creates one durable member assignment
      ---
      duration_ms: 2.681915
      type: 'test'
      ...
    # Subtest: rejects a non-numeric audit limit with a 400
    ok 2 - rejects a non-numeric audit limit with a 400
      ---
      duration_ms: 1.399642
      type: 'test'
      ...
    # Subtest: rejects GitHub usernames with trailing or consecutive hyphens
    ok 3 - rejects GitHub usernames with trailing or consecutive hyphens
      ---
      duration_ms: 1.554972
      type: 'test'
      ...
    1..3
ok 4 - instance admin routes
  ---
  duration_ms: 5.749561
  type: 'suite'
  ...
# [2026-08-29 19:34:44.998 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-iYQPtJ/022-instanceAuthorization.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 19:34:45.061 +0000] �[32mINFO�[39m: �[36mSQLite database connection closed�[39m
1..4
# tests 18
# suites 4
# pass 18
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 2232.933915

[23/320] packages/api/test/listenAddress.test.ts
TAP version 13
# Subtest: direct API execution binds to loopback by default
ok 1 - direct API execution binds to loopback by default
  ---
  duration_ms: 1.037217
  type: 'test'
  ...
# Subtest: containerized API execution remains reachable through its published port
ok 2 - containerized API execution remains reachable through its published port
  ---
  duration_ms: 0.167041
  type: 'test'
  ...
# Subtest: an explicit API listen host overrides environment defaults
ok 3 - an explicit API listen host overrides environment defaults
  ---
  duration_ms: 0.149318
  type: 'test'
  ...
1..3
# tests 3
# suites 0
# pass 3
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 241.552269

[24/320] packages/api/test/liveDetailsAntigravity.test.ts
TAP version 13
# Failed to read GitHub App private key: ENOENT: no such file or directory, open '/tmp/missing-key.pem'
# Ensure GH_PRIVATE_KEY_PATH is set correctly in your .env file and points to a valid private key file.
# Subtest: stored output detection recognizes Antigravity stream JSON
ok 1 - stored output detection recognizes Antigravity stream JSON
  ---
  duration_ms: 2.346331
  type: 'test'
  ...
# Subtest: stored output detection keeps Codex message JSONL classified as Codex
ok 2 - stored output detection keeps Codex message JSONL classified as Codex
  ---
  duration_ms: 1.416866
  type: 'test'
  ...
# Subtest: stored output detection keeps Codex result JSONL classified as Codex
ok 3 - stored output detection keeps Codex result JSONL classified as Codex
  ---
  duration_ms: 0.858443
  type: 'test'
  ...
# Subtest: stored output detection keeps Codex tool result JSONL classified as Codex
ok 4 - stored output detection keeps Codex tool result JSONL classified as Codex
  ---
  duration_ms: 0.233176
  type: 'test'
  ...
# Subtest: stored output detection does not treat generic init JSONL as Antigravity
ok 5 - stored output detection does not treat generic init JSONL as Antigravity
  ---
  duration_ms: 0.290593
  type: 'test'
  ...
# Subtest: stored output detection recognizes Antigravity JSONL from result stats when init is missing
ok 6 - stored output detection recognizes Antigravity JSONL from result stats when init is missing
  ---
  duration_ms: 0.337
  type: 'test'
  ...
# Subtest: stored output detection keeps generic result token stats classified as Codex
ok 7 - stored output detection keeps generic result token stats classified as Codex
  ---
  duration_ms: 0.226904
  type: 'test'
  ...
# Subtest: stored output detection recognizes truncated Antigravity result JSON
ok 8 - stored output detection recognizes truncated Antigravity result JSON
  ---
  duration_ms: 0.164568
  type: 'test'
  ...
# Subtest: stored output detection recognizes Antigravity message JSON with model metadata
ok 9 - stored output detection recognizes Antigravity message JSON with model metadata
  ---
  duration_ms: 0.358559
  type: 'test'
  ...
# Subtest: stored output parsing renders only Antigravity analysis events through live details
ok 10 - stored output parsing renders only Antigravity analysis events through live details
  ---
  duration_ms: 72.770405
  type: 'test'
  ...
# Subtest: Antigravity 1.1.12 stream text remains visible through live details
ok 11 - Antigravity 1.1.12 stream text remains visible through live details
  ---
  duration_ms: 1.964789
  type: 'test'
  ...
# Subtest: stored output detection and live-details rendering consume Antigravity stream arrays
ok 12 - stored output detection and live-details rendering consume Antigravity stream arrays
  ---
  duration_ms: 1.508457
  type: 'test'
  ...
# Subtest: Antigravity cache-only stream usage remains visible through live details
ok 13 - Antigravity cache-only stream usage remains visible through live details
  ---
  duration_ms: 0.775719
  type: 'test'
  ...
# Subtest: stored output parsing filters Antigravity transcript tool items
ok 14 - stored output parsing filters Antigravity transcript tool items
  ---
  duration_ms: 1.127996
  type: 'test'
  ...
# Subtest: Claude-format Antigravity conversation files render only planner analysis
ok 15 - Claude-format Antigravity conversation files render only planner analysis
  ---
  duration_ms: 1.477408
  type: 'test'
  ...
# Subtest: execution detail fallback filters Antigravity transcript tool rows
ok 16 - execution detail fallback filters Antigravity transcript tool rows
  ---
  duration_ms: 0.903868
  type: 'test'
  ...
# [2026-08-29 19:34:47.728 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-iYQPtJ/024-liveDetailsAntigravity.test.ts/propr.test.sqlite"
#     environment: "test"
1..16
# tests 16
# suites 0
# pass 16
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 2145.763653

[25/320] packages/api/test/liveDetailsCodexDbFallback.test.ts
TAP version 13
# [live-details] jobId: integry-propr-1915-codex, taskId: integry-propr-1915-codex
# [live-details] Trying Redis fallback
# [live-details] stateKey: worker:state:integry-propr-1915-codex, hasData: false
# [live-details] No state data found in Redis
# [live-details] Fetching sessionId from SQLite for taskId: integry-propr-1915-codex
# [live-details] Found sessionId in SQLite: codex-db-session
# [live-details] Using sessionId: codex-db-session
# Subtest: Codex database lifecycle fallback has exact canonical event parity
ok 1 - Codex database lifecycle fallback has exact canonical event parity
  ---
  duration_ms: 4.453308
  type: 'test'
  ...
# Subtest: Codex command lifecycle pairs starts and completions and recovers an unmatched completion
ok 2 - Codex command lifecycle pairs starts and completions and recovers an unmatched completion
  ---
  duration_ms: 0.284141
  type: 'test'
  ...
# Subtest: Codex database fallback retains row content for minimal error metadata
ok 3 - Codex database fallback retains row content for minimal error metadata
  ---
  duration_ms: 0.168635
  type: 'test'
  ...
# Subtest: Codex database fallback retains row content and error flag for minimal tool-result metadata
ok 4 - Codex database fallback retains row content and error flag for minimal tool-result metadata
  ---
  duration_ms: 0.150061
  type: 'test'
  ...
# Subtest: Codex database fallback ignores envelopes while retaining text, errors, and todos
ok 5 - Codex database fallback ignores envelopes while retaining text, errors, and todos
  ---
  duration_ms: 0.422129
  type: 'test'
  ...
# [live-details] Checking Claude conversation path: not found
# [live-details] Claude conversation file not found, trying active Redis output
# [live-details] Claude conversation file not found, trying stored execution output fallback
# [live-details] No stored execution logs found in Redis for session fallback
# [live-details] Stored execution output fallback unavailable, trying database fallback
# [live-details] jobId: integry-propr-1915-codex, taskId: integry-propr-1915-codex
# [live-details] Trying Redis fallback
# [live-details] stateKey: worker:state:integry-propr-1915-codex, hasData: false
# [live-details] No state data found in Redis
# [live-details] Fetching sessionId from SQLite for taskId: integry-propr-1915-codex
# [live-details] Found sessionId in SQLite: codex-db-session
# [live-details] Using sessionId: codex-db-session
# [live-details] Checking Claude conversation path: not found
# [live-details] Claude conversation file not found, trying active Redis output
# [live-details] Claude conversation file not found, trying stored execution output fallback
# [live-details] No stored execution logs found in Redis for session fallback
# [live-details] Stored execution output fallback unavailable, trying database fallback
# Subtest: live-details database fallback preserves token usage and stable event IDs
ok 6 - live-details database fallback preserves token usage and stable event IDs
  ---
  duration_ms: 19.150911
  type: 'test'
  ...
# [2026-08-29 19:34:49.944 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-iYQPtJ/025-liveDetailsCodexDbFallback.test.ts/propr.test.sqlite"
#     environment: "test"
1..6
# tests 6
# suites 0
# pass 6
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 1907.800354

[26/320] packages/api/test/liveDetailsOpenCodeParser.test.ts
TAP version 13
# Subtest: detectStoredOutputFormat keeps Codex message streams with session_id as codex
ok 1 - detectStoredOutputFormat keeps Codex message streams with session_id as codex
  ---
  duration_ms: 1588.565141
  type: 'test'
  ...
# Subtest: detectStoredOutputFormat does not classify bare conversation_id JSON as Claude
ok 2 - detectStoredOutputFormat does not classify bare conversation_id JSON as Claude
  ---
  duration_ms: 4.771342
  type: 'test'
  ...
# Subtest: detectStoredOutputFormat keeps Claude-shaped conversation_id JSON as Claude
ok 3 - detectStoredOutputFormat keeps Claude-shaped conversation_id JSON as Claude
  ---
  duration_ms: 1.671903
  type: 'test'
  ...
# Subtest: parseOpenCodeOutputToConversationResult separates structured assistant text parts
ok 4 - parseOpenCodeOutputToConversationResult separates structured assistant text parts
  ---
  duration_ms: 17.011675
  type: 'test'
  ...
# Subtest: detectStoredOutputFormat recognizes whole-document pretty OpenCode JSON
ok 5 - detectStoredOutputFormat recognizes whole-document pretty OpenCode JSON
  ---
  duration_ms: 1.568971
  type: 'test'
  ...
# Subtest: detectStoredOutputFormat recognizes OpenCode assistant parts without session identifiers
ok 6 - detectStoredOutputFormat recognizes OpenCode assistant parts without session identifiers
  ---
  duration_ms: 1.071843
  type: 'test'
  ...
# Subtest: detectStoredOutputFormat recognizes whole-document pretty Claude JSON arrays
ok 7 - detectStoredOutputFormat recognizes whole-document pretty Claude JSON arrays
  ---
  duration_ms: 0.937541
  type: 'test'
  ...
# [2026-08-29 19:34:52.030 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-iYQPtJ/026-liveDetailsOpenCodeParser.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 19:34:52.057 +0000] �[32mINFO�[39m: �[36mSQLite database connection closed�[39m
1..7
# tests 7
# suites 0
# pass 7
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 2176.480406

[27/320] packages/api/test/mergedPrTaskCancellation.test.ts
TAP version 13
# [webhook] Merged PR acme/widgets\#42: cancelled 3/3 active task(s)
# [webhook] Failed to cancel task for merged PR {
#   taskId: 'task-a',
#   repository: 'acme/widgets',
#   prNumber: 42,
#   error: Error: container stop blew up
#       at Object.stopTask (/home/runner/work/propr/propr/packages/api/test/mergedPrTaskCancellation.test.ts:89:34)
#       at stopNext (/home/runner/work/propr/propr/packages/api/webhookHandler.ts:210:35)
#       at <anonymous> (/home/runner/work/propr/propr/packages/api/webhookHandler.ts:236:104)
#    

...(truncated)

View Workflow

@propr-dev

propr-dev Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Applied the requested follow-up changes in commit 6fe54e8

Summary of Changes

Fixed the flaky full-suite failure in DesktopExperience.test.tsx.

The test now opens instance management through the title-bar control, avoiding an unrelated shortcut-listener timing race. Production behavior is unchanged.

Validation:

  • UI suite: 70 files, 500 tests passed
  • UI typecheck: passed
  • git diff --check: passed

No commit created.


🤖 Implementation Details

  • Model: GPT-5.6 Sol (high)
  • Requested By: @github-actions[bot]
  • Time: 4m 1s
  • Tokens: 1,125,371 (1,121,161 in / 4,210 out)
  • Cost: $1.02

Undo ChangesView Task Execution


💡 ProPR Slash Commands
Command Description Example
/merge Merge target branch into this PR and resolve conflicts /merge
/review Request an AI code review /review or /review claude-sonnet
/fix Implement fixes for issues found by /review /fix or /fix address the null check issue
/switch Change the AI model for this PR /switch claude-opus
/use Override the model for a single follow-up run /use claude-sonnet
/ultrafix Loop review→fix cycles until score goal is met /ultrafix or /ultrafix goal=8 max=10

🤖 Generated by ProPR
Processing comment ID: 5464467107✓

…st.tsx](/tmp/git-processor/worktrees/integry/propr/pr-1979-followup-2026-08-29T19-43-24/propr-ui/src/desktop/DesktopExperience.test.tsx:293).

Fixed the flaky full-suite failure in [DesktopExperience.test.tsx](/tmp/git-processor/worktrees/integry/propr/pr-1979-followup-2026-08-29T19-43-24/propr-ui/src/desktop/DesktopExperience.test.tsx:293).

The test now opens instance management through the title-bar control, avoiding an unrelated shortcut-listener timing race. Production behavior is unchanged.

Validation:

- UI suite: 70 files, 500 tests passed
- UI typecheck: passed
- `git diff --check`: passed

No commit created.

PR: #1979
Comment by: @github-actions[bot] (ID: 5464467107)
Model: gpt-5.6-sol
@integry integry removed the AI label Aug 29, 2026
propr-dev[bot] added 2 commits August 29, 2026 19:58
…54e8667a648429c5e4080d44bb0d5ff936abe`.

Reapplied the canonical deep-link repair on exact head `6fe54e8667a648429c5e4080d44bb0d5ff936abe`.

Changed only:

- [security.ts](/tmp/git-processor/worktrees/integry/propr/pr-1980-followup-2026-08-29T19-52-01/apps/desktop/src/security.ts:32)
- [security.test.ts](/tmp/git-processor/worktrees/integry/propr/pr-1980-followup-2026-08-29T19-52-01/apps/desktop/src/security.test.ts:77)
- [desktop-deep-link.test.ts](/tmp/git-processor/worktrees/integry/propr/pr-1980-followup-2026-08-29T19-52-01/propr-ui/src/desktop-deep-link.test.ts:30)

Verification:

- Desktop: 27/27 passed
- Focused UI: 25/25 passed
- Full UI: 500/500 passed
- Desktop/UI typechecks: passed
- Production package: passed
- Exact encoded `#` and `?`, single/double-encoded traversal variants: all returned `null`
- Normal `/tasks?status=open#recent`: preserved
- CI-only `DesktopExperience.test.tsx` change: preserved
- Final diff: three requested files only

Packaged smoke was attempted but the non-root worker cannot configure the required root-owned setuid Chromium sandbox helper and lacks Xvfb. The application correctly refused to launch without sandboxing.

Per instruction, I did not commit or push. `git ls-remote` therefore still reports the published head as `6fe54e8`; post-publication verification can only occur after the system creates and publishes its automatic commit.

PR: #1980
Comment by: @integry (ID: 5464512567)
Model: gpt-5.6-sol
….test.ts](/tmp/git-processor/worktrees/integry/propr/pr-1980-followup-2026-08-29T19-58-19/packages/api/test/notificationManagementRoutes.test.ts:65).

Fixed the full-suite flake in [notificationManagementRoutes.test.ts](/tmp/git-processor/worktrees/integry/propr/pr-1980-followup-2026-08-29T19-58-19/packages/api/test/notificationManagementRoutes.test.ts:65).

Node occasionally emits a 31-byte ECDH private scalar by omitting a leading zero. The fixture now pads it to VAPID’s required 32-byte width, preventing valid mismatched-key cases from being misclassified as malformed. No production code changed.

Validation:

- Previously failing test: 3/3 passed
- Desktop tests: 27/27 passed
- Focused UI tests: 25/25 passed
- Desktop/UI typechecks: passed
- Production package: passed
- `git diff --check`: passed
- Final diff: one test file only

The published PR head is `c524a9e5`, contains the canonical deep-link fix, and still targets `1951-epic-desktop-experience-uxs`.

A local full-suite rerun was unavailable because this worker has neither Docker nor Redis. Sandboxed smoke was also blocked by the environment’s missing root-owned Chromium sandbox helper and Xvfb. No commit was created.

PR: #1980
Comment by: @github-actions[bot] (ID: 5464573988)
Model: gpt-5.6-sol
@integry integry added the AI label Aug 29, 2026
@integry

integry commented Aug 29, 2026

Copy link
Copy Markdown
Owner Author

/review\n\nReview exact shared runtime head 48f8581 against the transport epic base. Re-audit canonical deep-link normalization across every decoding layer, prohibited /login and /desktop/pairing routes, reserved parameters, external/protocol-relative/traversal forms, startup buffering, renderer isolation, deterministic DesktopExperience coverage, and confirm the VAPID fixture padding is test-only and mathematically correct. Treat any validate-one-representation/return-another behavior or security regression as a blocker. Do not modify code; report score and findings on this exact head.

@propr-dev

propr-dev Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Failed to apply follow-up changes requested by @integry

An error occurred while processing your request:

Not a git repository (or any of the parent directories): /tmp/git-processor/worktrees/integry/propr/pr-1979-followup-2026-08-29T20-06-56

Comment ID: 5464613320✓
Please check the logs for more details.

@integry integry added AI and removed AI labels Aug 29, 2026
@integry

integry commented Aug 29, 2026

Copy link
Copy Markdown
Owner Author

/review\n\nRetry after the transient missing-worktree failure. Review exact SHA 48f8581 against the transport epic base only. Re-audit canonical deep-link validation/output equivalence, prohibited auth/pairing routes, reserved parameters, external/protocol-relative/traversal forms, deterministic UI coverage, renderer isolation, and safe test-only VAPID scalar padding. No code modifications; report blockers and score.

@propr-dev

propr-dev Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Failed to apply follow-up changes requested by @integry

An error occurred while processing your request:

Not a git repository (or any of the parent directories): /tmp/git-processor/worktrees/integry/propr/pr-1979-followup-2026-08-29T20-23-31

Comment ID: 5464690693✓
Please check the logs for more details.

@integry integry added AI and removed AI labels Aug 29, 2026
@integry

integry commented Aug 29, 2026

Copy link
Copy Markdown
Owner Author

/review

Review exact SHA 48f8581 against transport epic base 1953. Confirm canonical deep-link validation/output equivalence; encoded delimiter/traversal and auth/pairing paths are rejected; normal task query/hash works; renderer isolation and deterministic UI coverage remain; VAPID padding is safe test-only scope. No code changes. Report blockers and score.

@propr-dev

propr-dev Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

AI Code Review Complete requested by @integry

Posted 1 review:

View Task Details

@integry integry removed the AI label Aug 29, 2026
@propr-dev

propr-dev Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

🔍 AI Code Review — codex:gpt-5.6-sol

Overall Evaluation

This PR correctly integrates the Electron runtime, hardened dashboard deep links, compiled renderer styling, and deterministic packaged-layout coverage into the transport epic. It needs one localized correction before merge: deep-link validation is performed against the input representation, while a potentially longer canonical representation is returned.

Layered route protection — Encoded delimiters and traversal are decoded and checked at every layer; external/protocol-relative paths, /login, /desktop/pairing, and reserved authentication parameters are rejected, while /tasks?status=open#recent is preserved.

Renderer coverage remains isolated — Layout inspection runs only for packaged smoke tests, and the build verifies that representative Tailwind and dashboard selectors were compiled.

VAPID padding is test-only and correct — The change is confined to packages/api/test, left-pads the big-endian P-256 scalar to its required 32-byte width, and does not affect production key handling.

Merge blockers

Every finding below was introduced by this PR and must be resolved before merging.

F1: 🔴 Revalidate the canonical deep link

  • Required behavior: The accepted deep-link representation and the representation returned for delivery must be equivalent and pass the same validation constraints.
  • Evidence: apps/desktop/src/security.ts:140normalizeDeepLink validates the original string and then returns url.href without validating that serialized value. WHATWG serialization percent-encodes spaces and non-ASCII characters, so an input shorter than 2,048 UTF-16 code units can be accepted but expand beyond 2,048 characters in url.href; the renderer then rejects that returned value in dashboardPathFromDeepLink.
  • Minimum fix: Serialize the candidate first, reject it if the canonical representation exceeds the limit, and validate that dashboardPathFromDeepLink(candidate) succeeds and produces the same dashboard path before returning it. Cover an under-limit input whose canonical serialization expands beyond the limit.

Suggestions

These are optional follow-ups and are not sent to /fix.

No suggestions.

Score

The implementation is otherwise focused, well-tested, and supported by passing current checks, but the explicit canonical validation/output invariant still has a reproducible edge-case failure.

Score: 6/10


🤖 Review Details

  • Model: GPT-5.6 Sol
  • Time: 2m 22s
  • Tokens: 46,399 (41,522 in / 4,877 out)
  • Cost: $0.31

View Task


💡 Next step: Comment /fix to address F# merge blockers only.
F# IDs increment across review comments and remain permanent, so selectors such as /fix F3 F5 stay unambiguous across cycles. Suggestions require a separate ordinary follow-up request.

🤖 Review by ProPR

@integry integry added the AI label Aug 29, 2026
@integry

integry commented Aug 29, 2026

Copy link
Copy Markdown
Owner Author

/fix F1

Fix only the exact canonical serialization invariant on shared runtime head 48f8581. Serialize the candidate before return; reject if the canonical href exceeds 2,048 code units or contains prohibited controls; require dashboardPathFromDeepLink(canonicalCandidate) to succeed and equal the dashboard path validated from the input. Never validate one representation and deliver another. Add desktop and renderer regressions for a raw under-limit open path containing enough spaces/non-ASCII characters that WHATWG percent-serialization expands beyond 2,048, plus a near-boundary canonical value that remains valid. Preserve every encoded-delimiter/traversal/auth rejection and normal tasks query/hash behavior. Run desktop/UI focused tests, typechecks, package/smoke, full suite, and git diff --check; publish the exact branch head.

@propr-dev

propr-dev Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Applied the requested follow-up changes in commit d99f006

Addressed 1 AI review comment (IDs: 5464761315)

Summary of Changes

Implemented F1 only on base head 48f85811addf4bccfd390961e8dcb68971e6a0e6.

Changes:

  • security.ts now checks the serialized canonical candidate’s length/controls and requires its dashboard path to equal the validated input path before returning it.
  • Added desktop and renderer regressions for percent-expansion beyond 2,048 and an exactly 2,048-character valid canonical link.
  • Existing traversal, encoded-delimiter, auth/pairing, query, and hash tests remain passing.

Verification:

  • Desktop tests: 28 passed.
  • Focused renderer tests: 5 passed.
  • Desktop and UI typechecks: passed.
  • Production package: passed.
  • git diff --check: passed.
  • Packaged smoke: environment-blocked because no usable Chromium sandbox helper is installed/configured.
  • Full suite: reached file 155/320 before blocking on unavailable Redis (127.0.0.1:6379); neither Redis nor Docker is installed.

Only the three scoped files are modified. Per instruction, I did not commit or push; the system must publish the resulting branch head.


🤖 Implementation Details

  • Model: GPT-5.6 Sol (high)
  • Requested By: @integry
  • Time: 10m 24s
  • Tokens: 1,883,225 (1,873,630 in / 9,595 out)
  • Cost: $1.68
  • Subscription usage: Weekly +1%

Undo ChangesView Task Execution


💡 ProPR Slash Commands
Command Description Example
/merge Merge target branch into this PR and resolve conflicts /merge
/review Request an AI code review /review or /review claude-sonnet
/fix Implement fixes for issues found by /review /fix or /fix address the null check issue
/switch Change the AI model for this PR /switch claude-opus
/use Override the model for a single follow-up run /use claude-sonnet
/ultrafix Loop review→fix cycles until score goal is met /ultrafix or /ultrafix goal=8 max=10

🤖 Generated by ProPR
Processing comment ID: 5464771154✓

@integry integry removed the AI label Aug 29, 2026
…8dcb68971e6a0e6`.

Implemented F1 only on base head `48f85811addf4bccfd390961e8dcb68971e6a0e6`.

Changes:

- [security.ts](/tmp/git-processor/worktrees/integry/propr/pr-1979-followup-2026-08-29T20-41-29/apps/desktop/src/security.ts) now checks the serialized canonical candidate’s length/controls and requires its dashboard path to equal the validated input path before returning it.
- Added desktop and renderer regressions for percent-expansion beyond 2,048 and an exactly 2,048-character valid canonical link.
- Existing traversal, encoded-delimiter, auth/pairing, query, and hash tests remain passing.

Verification:

- Desktop tests: 28 passed.
- Focused renderer tests: 5 passed.
- Desktop and UI typechecks: passed.
- Production package: passed.
- `git diff --check`: passed.
- Packaged smoke: environment-blocked because no usable Chromium sandbox helper is installed/configured.
- Full suite: reached file 155/320 before blocking on unavailable Redis (`127.0.0.1:6379`); neither Redis nor Docker is installed.

Only the three scoped files are modified. Per instruction, I did not commit or push; the system must publish the resulting branch head.

PR: #1979
Comment by: @integry (ID: 5464771154)
Model: gpt-5.6-sol
@integry integry added the AI label Aug 29, 2026
@integry

integry commented Aug 29, 2026

Copy link
Copy Markdown
Owner Author

/review

Final review of exact runtime head d99f006. Verify canonical serialization is length/control checked and revalidated before delivery; raw and canonical dashboard paths must be equal. Reproduce 673-space candidate length 2046 accepted and 674-space candidate length 2049 rejected. Re-audit encoded delimiter/traversal, login/pairing/reserved/external/protocol-relative/control rejection, normal task query/hash navigation, startup buffering, renderer isolation, deterministic tests, and test-only VAPID padding. No code changes. Report blockers and score only after current-head checks.

@propr-dev

propr-dev Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

AI Code Review Complete requested by @integry

Posted 1 review:

View Task Details

@propr-dev

propr-dev Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

🔍 AI Code Review — codex:gpt-5.6-sol

Overall Evaluation

This PR is merge-ready within scope, conditional only on the pending full test suite completing successfully. The authoritative current-head status has no failures, eight passing checks, and one pending check.

Canonical equivalence enforcednormalizeDeepLink validates the raw dashboard path, serializes url.href, checks the canonical length and controls, then re-extracts the path and requires exact equality before delivery. The specified 673-space candidate serializes to 2,046 code units and is accepted; adding one %20 produces 2,049 and is rejected.

Deep-link security preserved — Recursive decoding and path-scope validation reject encoded traversal/delimiters, controls, external and protocol-relative targets, login/pairing routes, and reserved parameters while preserving normal /tasks query/hash navigation.

Runtime boundaries are well covered — Startup links remain buffered until dashboard readiness, renderer navigation receives only validated paths, packaged CSS/layout checks are deterministic, and VAPID scalar padding is correctly right-aligned to 32 bytes within test-only code.

Merge blockers

No merge blockers.

Suggestions

These are optional follow-ups and are not sent to /fix.

No suggestions.

Score

The implementation satisfies the requested runtime and security invariants with focused regression coverage and no known current-head failure. The remaining full-suite check is still in progress but does not identify a code blocker.

Score: 9/10


🤖 Review Details

  • Model: GPT-5.6 Sol
  • Time: 2m 48s
  • Tokens: 49,774 (43,353 in / 6,421 out)
  • Cost: $0.36

View Task


💡 Next step: Comment /fix to address F# merge blockers only.
F# IDs increment across review comments and remain permanent, so selectors such as /fix F3 F5 stay unambiguous across cycles. Suggestions require a separate ordinary follow-up request.

🤖 Review by ProPR

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