Skip to content

sync(desktop): propagate finalized runtime into UX epic - #1980

Merged
integry merged 5 commits into
1951-epic-desktop-experience-uxsfrom
1952-epic-electron-runtime-app
Aug 29, 2026
Merged

sync(desktop): propagate finalized runtime into UX epic#1980
integry merged 5 commits into
1951-epic-desktop-experience-uxsfrom
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 UX 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 #1978 the exact reviewed runtime/CSS/deep-link foundation. It must merge only into 1951-epic-desktop-experience-uxs, 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 1951-epic-desktop-experience-uxs without dropping UX/setup-engine/desktop chooser 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

The PR cleanly integrates the Electron runtime foundation, including deterministic Tailwind compilation, packaged-renderer layout validation, and buffered dashboard deep links. It needs one localized security correction before merge and must remain targeted to 1951-epic-desktop-experience-uxs.

Strong build validation — The renderer build fails when CSS is absent, Tailwind directives remain, or representative desktop/dashboard selectors are missing.

Meaningful packaged smoke coverage — The smoke test verifies real 1280×820 layout bounds alongside sandbox, preload bridge, and profile API proofs.

Well-covered deep-link lifecycle — Tests exercise startup buffering, post-load delivery, malformed inputs, reserved parameters, and restricted routes.

Merge blockers

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

F1: 🔴 Encoded delimiter bypasses route restrictions

  • Required behavior: Accepted propr://open links must not navigate to security-sensitive /login or /desktop/pairing routes, including through encoded path normalization.
  • Evidence: apps/desktop/src/security.ts:54 — restrictions are evaluated against decodedUrl, but line 62 returns components from the differently parsed url. For example, /tasks%23/%2e%2e/login is interpreted by decodedUrl as /tasks with a fragment, while url.pathname normalizes to /login, allowing the prohibited route to be returned.
  • Minimum fix: Apply restricted-route and reserved-parameter checks to the canonical URL representation actually returned and navigated, as well as the fully decoded interpretation, or derive the returned path from one consistently validated representation. Add regression coverage combining an encoded # or ? delimiter with encoded dot segments.

Suggestions

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

No suggestions.

Score

The integration is otherwise focused and well tested, with no reported current-head failures, but the new deep-link validator has a reproducible restricted-route bypass that must be corrected before merge.

Score: 6/10


🤖 Review Details

  • Model: GPT-5.6 Sol
  • Time: 2m 34s
  • Tokens: 40,876 (35,357 in / 5,519 out)
  • Cost: $0.30

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 commented Aug 29, 2026

Copy link
Copy Markdown
Owner Author

/fix F1

Fix only the canonical deep-link discrepancy on the shared runtime head. The exact path /tasks%23/%2e%2e/login (and analogous encoded ? delimiter variants) must never validate one decoded representation and return/navigate a different prohibited representation. Derive the returned route from one canonical representation, or validate every representation actually returned, so /login, /login/*, /desktop/pairing, reserved auth/hosted-flow parameters, protocol-relative/external paths, traversal, encoded controls, and malformed/repeated encodings all fail closed. Add regressions for encoded # and ? delimiters combined with single/double-encoded dot segments, plus normal /tasks query/hash routing. Preserve startup buffering and the existing 2,048-byte/decoding bounds. Run desktop security tests, focused UI navigation tests, typechecks, production package, and sandboxed smoke. This source branch also feeds transport sync #1979 and packaging #1972; make no unrelated changes and do not merge to main.

@propr-dev

propr-dev Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Applied the requested follow-up changes in commit 87a6e62

Addressed 1 AI review comment (IDs: 5464449613)

Summary of Changes

Implemented F1 only.

  • Deep links now validate every bounded decoding layer and return components from the final validated canonical URL.
  • Encoded #/? delimiters cannot hide single/double-encoded traversal into /login.
  • Added desktop and UI regressions, including normal /tasks query/hash navigation.
  • Startup buffering and the 2,048-byte/decoding bounds remain unchanged.

Changed:

Verification:

  • Desktop tests: 27 passed
  • Focused UI navigation: 4 passed
  • Desktop and UI typechecks: passed
  • Production package: passed
  • Sandboxed smoke: blocked before launch because this agent image lacks the Debian xvfb-run/Xvfb runtime package

No commit or unrelated changes were made.


🤖 Implementation Details

  • Model: GPT-5.6 Sol (high)
  • Requested By: @integry
  • Time: 9m 3s
  • Tokens: 1,786,934 (1,771,360 in / 15,574 out)
  • Cost: $1.73

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: 5464459471✓

…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 commented Aug 29, 2026

Copy link
Copy Markdown
Owner Author

!propr The reported F1 repair commit 87a6e62 did not reach the PR branch because the concurrent CI-only commit 6fe54e8 won the shared-head update. GitHub still reports exact head 6fe54e8, and the encoded-delimiter bypass remains there. Reapply the already-developed canonical deep-link repair and its tests on top of current live head 6fe54e8, preserving the CI test change. Verify the published PR head actually contains the fix for /tasks%23/%2e%2e/login and encoded ? variants before reporting completion. Run the desktop/UI tests and typechecks, package/smoke, and make no unrelated changes.

@propr-dev

propr-dev Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Applied the requested follow-up changes in commit c524a9e

Summary of Changes

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

Changed only:

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.


🤖 Implementation Details

  • Model: GPT-5.6 Sol (high)
  • Requested By: @integry
  • Time: 5m 46s
  • Tokens: 646,666 (636,581 in / 10,085 out)
  • Cost: $0.90
  • 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: 5464512567✓

…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
@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.252015
      type: 'test'
      ...
    1..1
ok 1 - desktop deep-link delivery
  ---
  duration_ms: 1.995305
  type: 'suite'
  ...
1..1
# tests 1
# suites 1
# pass 1
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 229.920339

[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.661591
      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.728027
      type: 'test'
      ...
    1..2
ok 1 - desktop session IPC operations
  ---
  duration_ms: 26.574182
  type: 'suite'
  ...
1..1
# tests 2
# suites 1
# pass 2
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 266.551106

[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.76674
      type: 'test'
      ...
    # Subtest: maps profile and credential operations to fixed channels
    ok 2 - maps profile and credential operations to fixed channels
      ---
      duration_ms: 0.573123
      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.346001
      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.297768
      type: 'test'
      ...
    1..4
ok 1 - desktop preload bridge
  ---
  duration_ms: 4.211914
  type: 'suite'
  ...
1..1
# tests 4
# suites 1
# pass 4
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 239.199574

[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: 13.270205
      type: 'test'
      ...
    # Subtest: encrypts credentials before writing app-owned storage
    ok 2 - encrypts credentials before writing app-owned storage
      ---
      duration_ms: 6.071233
      type: 'test'
      ...
    # Subtest: serializes concurrent credential writes with last-write semantics
    ok 3 - serializes concurrent credential writes with last-write semantics
      ---
      duration_ms: 4.84057
      type: 'test'
      ...
    # Subtest: orders concurrent credential writes and removals by invocation
    ok 4 - orders concurrent credential writes and removals by invocation
      ---
      duration_ms: 6.1688
      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: 1.767182
      type: 'test'
      ...
    # Subtest: rejects unsafe endpoints and path-like profile identifiers
    ok 6 - rejects unsafe endpoints and path-like profile identifiers
      ---
      duration_ms: 3.826372
      type: 'test'
      ...
    1..6
ok 1 - desktop profile store
  ---
  duration_ms: 37.594869
  type: 'suite'
  ...
1..1
# tests 6
# suites 1
# pass 6
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 275.807266

[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.423845
      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.269917
      type: 'test'
      ...
    # Subtest: requires an exact loopback development origin
    ok 3 - requires an exact loopback development origin
      ---
      duration_ms: 0.282355
      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.330618
      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.389417
      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.759634
      type: 'test'
      ...
    # Subtest: rejects malformed and unsafe open deep-link paths
    ok 7 - rejects malformed and unsafe open deep-link paths
      ---
      duration_ms: 1.188029
      type: 'test'
      ...
    # Subtest: publishes a restrictive production policy
    ok 8 - publishes a restrictive production policy
      ---
      duration_ms: 0.422917
      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.39175
      type: 'test'
      ...
    1..9
ok 1 - desktop URL security
  ---
  duration_ms: 7.232683
  type: 'suite'
  ...
1..1
# tests 9
# suites 1
# pass 9
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 252.980993

[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.721156
      type: 'test'
      ...
    # Subtest: normalizes a Windows drive-letter path and separators
    ok 2 - normalizes a Windows drive-letter path and separators
      ---
      duration_ms: 0.153321
      type: 'test'
      ...
    1..2
ok 1 - Vite filesystem renderer URLs
  ---
  duration_ms: 1.77326
  type: 'suite'
  ...
1..1
# tests 2
# suites 1
# pass 2
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 241.428103

[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.742324
      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.171879
      type: 'test'
      ...
    1..2
ok 1 - desktop BrowserWindow security
  ---
  duration_ms: 3.020529
  type: 'suite'
  ...
1..1
# tests 2
# suites 1
# pass 2
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 241.759281

[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: 2.827929
      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.272987
      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.248747
      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.611651
      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.448706
      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.628933
      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.628557
      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.695327
      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.641526
      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.489697
      type: 'test'
      ...
    1..10
ok 1 - agent login session manager
  ---
  duration_ms: 12.717373
  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.764201
      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.594025
      type: 'test'
      ...
    # Subtest: rejects interactive login for an unsupported agent type
    ok 3 - rejects interactive login for an unsupported agent type
      ---
      duration_ms: 0.441675
      type: 'test'
      ...
    1..3
ok 2 - agent login routes
  ---
  duration_ms: 1.95197
  type: 'suite'
  ...
# [2026-08-29 19:49:33.133 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-sO6K9Y/008-agentLoginRoutes.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 19:49:33.144 +0000] �[32mINFO�[39m: �[36mSQLite database connection closed�[39m
# [2026-08-29 19:49:33.145 +0000] �[32mINFO�[39m: �[36mShutting down queue...�[39m
# [2026-08-29 19:49:33.145 +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 2191.484948

[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: 3.982528
      type: 'test'
      ...
    # Subtest: persists a failed state when queue submission fails
    ok 2 - persists a failed state when queue submission fails
      ---
      duration_ms: 0.8893
      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.792674
      type: 'test'
      ...
    # Subtest: times out slow runtime package validation before queueing
    ok 4 - times out slow runtime package validation before queueing
      ---
      duration_ms: 6.884819
      type: 'test'
      ...
    # Subtest: returns package suggestions from configured runtimes
    ok 5 - returns package suggestions from configured runtimes
      ---
      duration_ms: 0.95653
      type: 'test'
      ...
    # Subtest: enforces PROPR_ADMIN_USERS when configured
    ok 6 - enforces PROPR_ADMIN_USERS when configured
      ---
      duration_ms: 0.45846
      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.255845
      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.367033
      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.490749
      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: 3.535254
      type: 'test'
      ...
    # Subtest: resolves the runtime build queue lazily when queueing
    ok 11 - resolves the runtime build queue lazily when queueing
      ---
      duration_ms: 0.970993
      type: 'test'
      ...
    # Subtest: reports apply load failures through the route response
    ok 12 - reports apply load failures through the route response
      ---
      duration_ms: 0.309155
      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.401124
      type: 'test'
      ...
    # Subtest: rejects runtime verification without runtime-management permission
    ok 14 - rejects runtime verification without runtime-management permission
      ---
      duration_ms: 0.299872
      type: 'test'
      ...
    1..14
ok 1 - agent runtime package routes
  ---
  duration_ms: 22.254937
  type: 'suite'
  ...
# [2026-08-29 19:49:35.120 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-sO6K9Y/009-agentRuntimeRoutes.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 19:49:35.140 +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 1989.750669

[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.638695
  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.462412
  type: 'test'
  ...
# Subtest: temporary cleanup refuses paths outside its configured root
ok 3 - temporary cleanup refuses paths outside its configured root
  ---
  duration_ms: 0.698051
  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.553513
  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: 1.761172
  type: 'test'
  ...
# [2026-08-29 19:49:37.225 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-sO6K9Y/010-attachmentUploadCleanup.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 19:49:37.241 +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 1980.220496

[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.295968
  type: 'test'
  ...
# Subtest: ensureAuthenticated refreshes an expired GitHub token before continuing
ok 2 - ensureAuthenticated refreshes an expired GitHub token before continuing
  ---
  duration_ms: 5.511299
  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.503284
  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.860125
  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.669058
  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.939465
  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.105675
  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: 0.999255
  type: 'test'
  ...
# [2026-08-29 19:49:39.857 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-sO6K9Y/011-authGithubTokens.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 19:49:39.875 +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 2740.567176

[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: 42.753642
  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.456039
  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.666928
  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: 3.877679
  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.065755
  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: 5.957972
  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.096411
  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.508816
  type: 'test'
  ...
# Subtest: auth redirect allowlist permits configured local IP literals
ok 9 - auth redirect allowlist permits configured local IP literals
  ---
  duration_ms: 5.408354
  type: 'test'
  ...
# Subtest: auth redirect allowlist ignores malformed additional host entries
ok 10 - auth redirect allowlist ignores malformed additional host entries
  ---
  duration_ms: 5.143766
  type: 'test'
  ...
# [2026-08-29 19:49:42.665 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-sO6K9Y/012-authRedirect.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 19:49:42.750 +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 2431.955193

[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: 1493.885159
  type: 'test'
  ...
# [2026-08-29 19:49:44.822 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-sO6K9Y/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 2042.363256

[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: 3.780804
  type: 'test'
  ...
# Subtest: API config subscription reloads repository updates
ok 2 - API config subscription reloads repository updates
  ---
  duration_ms: 0.323617
  type: 'test'
  ...
# Subtest: API config subscription ignores unrelated and malformed events
ok 3 - API config subscription ignores unrelated and malformed events
  ---
  duration_ms: 0.31211
  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.308294
  type: 'test'
  ...
1..4
# tests 4
# suites 0
# pass 4
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 245.226904

[15/320] packages/api/test/configRoutesIndexingSummarization.test.ts
TAP version 13
# [2026-08-29 19:49:47.312 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-sO6K9Y/015-configRoutesIndexingSummarization.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 19:49:47.341 +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: 1717.179638
  type: 'test'
  ...
# Subtest: postSummarizationSettings rejects enabled summarization without a primary alias
ok 2 - postSummarizationSettings rejects enabled summarization without a primary alias
  ---
  duration_ms: 3.197435
  type: 'test'
  ...
# [2026-08-29 19:49:47.573 +0000] �[32mINFO�[39m: �[36mDatabase migrations completed successfully�[39m
# [2026-08-29 19:49:47.574 +0000] �[32mINFO�[39m: �[36mSuccessfully loaded agents configuration�[39m
#     agentCount: 2
# [2026-08-29 19:49:47.575 +0000] �[32mINFO�[39m: �[36mSuccessfully loaded summarization settings�[39m
#     summarization: {
#       "enabled": false,
#       "agent_alias": "",
#       "fallback_agent_alias": "",
#       "custom_prompt": ""
#     }
# [2026-08-29 19:49:47.576 +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:49:47.577 +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:49:47.587 +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 2027.48326

[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.139726
  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.235936
  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.261053
  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.334384
  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.226511
  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.236617
  type: 'test'
  ...
# Subtest: explicit custom GitHub web auth remains supported
ok 7 - explicit custom GitHub web auth remains supported
  ---
  duration_ms: 0.314153
  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.175404
  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.579743
  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.480235
  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.925564
  type: 'test'
  ...
1..11
# tests 11
# suites 0
# pass 11
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 291.65524

[17/320] packages/api/test/connectAuthCallback.test.ts
TAP version 13
# [2026-08-29 19:49:50.316 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-sO6K9Y/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: 3039.350692
  type: 'test'
  ...
# [2026-08-29 19:49:53.352 +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 5447.50221

[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.027127
  type: 'test'
  ...
# Subtest: CORS rejects unrelated origins under proxy mode
ok 2 - CORS rejects unrelated origins under proxy mode
  ---
  duration_ms: 0.230237
  type: 'test'
  ...
# Subtest: CORS allows requests with no origin
ok 3 - CORS allows requests with no origin
  ---
  duration_ms: 0.119299
  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.176105
  type: 'test'
  ...
# Subtest: CORS allows HTTP(S) loopback origins for development
ok 5 - CORS allows HTTP(S) loopback origins for development
  ---
  duration_ms: 0.22574
  type: 'test'
  ...
# Subtest: CORS rejects unsafe schemes and non-loopback hosts
ok 6 - CORS rejects unsafe schemes and non-loopback hosts
  ---
  duration_ms: 0.147563
  type: 'test'
  ...
# Subtest: CORS allows COOKIE_DOMAIN subdomains for preview environments
ok 7 - CORS allows COOKIE_DOMAIN subdomains for preview environments
  ---
  duration_ms: 0.194082
  type: 'test'
  ...
# Subtest: CORS preserves http COOKIE_DOMAIN preview compatibility
ok 8 - CORS preserves http COOKIE_DOMAIN preview compatibility
  ---
  duration_ms: 0.115464
  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.413813
  type: 'test'
  ...
# Subtest: Express CORS boundary is sanitized in development mode
ok 10 - Express CORS boundary is sanitized in development mode
  ---
  duration_ms: 65.320141
  type: 'test'
  ...
# Subtest: Express CORS boundary is sanitized in production mode
ok 11 - Express CORS boundary is sanitized in production mode
  ---
  duration_ms: 27.099972
  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: 6.904117
  type: 'test'
  ...
1..12
# tests 12
# suites 0
# pass 12
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 564.386168

[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.081679
  type: 'test'
  ...
# Subtest: demoModeReadOnlyMiddleware blocks auth metadata mutations
ok 2 - demoModeReadOnlyMiddleware blocks auth metadata mutations
  ---
  duration_ms: 1.872961
  type: 'test'
  ...
# Subtest: isDemoMode accepts common truthy environment values
ok 3 - isDemoMode accepts common truthy environment values
  ---
  duration_ms: 1.007868
  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.573359
  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.290365
  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: 52.532062
  type: 'test'
  ...
# Subtest: ensureAuthenticated attaches the synthetic demo user
ok 7 - ensureAuthenticated attaches the synthetic demo user
  ---
  duration_ms: 1.21541
  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.274589
  type: 'test'
  ...
# Subtest: demo repository metadata resolves enabled configured repositories
ok 9 - demo repository metadata resolves enabled configured repositories
  ---
  duration_ms: 9.440928
  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.204299
  type: 'test'
  ...
# [2026-08-29 19:49:56.711 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-sO6K9Y/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: 246.402719
  type: 'test'
  ...
# [2026-08-29 19:49:57.029 +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:49:57.031 +0000] �[32mINFO�[39m: �[36mSuccessfully loaded all monitored repositories�[39m
#     total_repos: 3
# Subtest: demo repository metadata resolves persisted repositories without configured allowlists
ok 12 - demo repository metadata resolves persisted repositories without configured allowlists
  ---
  duration_ms: 9.517503
  type: 'test'
  ...
# Subtest: demo repository metadata ignores malformed database repository names
ok 13 - demo repository metadata ignores malformed database repository names
  ---
  duration_ms: 1.489483
  type: 'test'
  ...
# [2026-08-29 19:49:57.042 +0000] �[32mINFO�[39m: �[36mSuccessfully loaded all monitored repositories�[39m
#     total_repos: 0
# 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: 7.701659
  type: 'test'
  ...
# [2026-08-29 19:49:57.050 +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: 6.37452
  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: 6.842014
  type: 'test'
  ...
1..16
# tests 16
# suites 0
# pass 16
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 2617.858062

[20/320] packages/api/test/desktopAuth.test.ts
TAP version 13
# [desktop-auth] {
#   action: 'pairing_started',
#   actorUserId: undefined,
#   pairingId: 'dpr_pxO7Y0T_LHDF1erISYjmag',
#   tokenId: undefined,
#   clientName: 'Work Laptop'
# }
# [desktop-auth] {
#   action: 'pairing_started',
#   actorUserId: undefined,
#   pairingId: 'dpr_B_aLsqJTcqOy9ePd9tLkNA',
#   tokenId: undefined,
#   clientName: 'Windows desktop'
# }
# [desktop-auth] {
#   action: 'pairing_started',
#   actorUserId: undefined,
#   pairingId: 'dpr_QCjjCKqg339rN-RI6II8-g',
#   tokenId: undefined,
#   clientName: 'MacBook Pro'
# }
# [desktop-auth] {
#   action: 'pairing_approved',
#   actorUserId: '101',
#   pairingId: 'dpr_QCjjCKqg339rN-RI6II8-g',
#   tokenId: undefined,
#   clientName: 'MacBook Pro'
# }
# [desktop-auth] {
#   action: 'token_issued',
#   actorUserId: '101',
#   pairingId: 'dpr_QCjjCKqg339rN-RI6II8-g',
#   tokenId: 'b0ae2ec1-2d1e-4e97-92a9-1afbb2383a15',
#   clientName: 'MacBook Pro'
# }
# [desktop-auth] {
#   action: 'pairing_started',
#   actorUserId: undefined,
#   pairingId: 'dpr_3ATKqIzMyMXBb-PVioueNw',
#   tokenId: undefined,
#   clientName: 'Linux workstation'
# }
# [desktop-auth] {
#   action: 'pairing_approved',
#   actorUserId: '101',
#   pairingId: 'dpr_3ATKqIzMyMXBb-PVioueNw',
#   tokenId: undefined,
#   clientName: 'Linux workstation'
# }
# [desktop-auth] {
#   action: 'pairing_started',
#   actorUserId: undefined,
#   pairingId: 'dpr_Ou8pvfEOWi4vdz-qgbKE4w',
#   tokenId: undefined,
#   clientName: 'Old laptop'
# }
# [desktop-auth] {
#   action: 'pairing_started',
#   actorUserId: undefined,
#   pairingId: 'dpr_E5wjwwn0DY0Jrw3STu1Tkw',
#   tokenId: undefined,
#   clientName: 'Desktop app'
# }
# [desktop-auth] {
#   action: 'pairing_approved',
#   actorUserId: '101',
#   pairingId: 'dpr_E5wjwwn0DY0Jrw3STu1Tkw',
#   tokenId: undefined,
#   clientName: 'Desktop app'
# }
# [desktop-auth] {
#   action: 'token_issued',
#   actorUserId: '101',
#   pairingId: 'dpr_E5wjwwn0DY0Jrw3STu1Tkw',
#   tokenId: '4e201f75-1841-471b-987d-c656d287280d',
#   clientName: 'Desktop app'
# }
# [desktop-auth] {
#   action: 'token_revoked',
#   actorUserId: '101',
#   pairingId: undefined,
#   tokenId: '4e201f75-1841-471b-987d-c656d287280d',
#   clientName: undefined
# }
# [desktop-auth] {
#   action: 'pairing_started',
#   actorUserId: undefined,
#   pairingId: 'dpr_lrq9lhytT4SuF40pbOKb5A',
#   tokenId: undefined,
#   clientName: 'Desktop app'
# }
# [desktop-auth] {
#   action: 'pairing_approved',
#   actorUserId: '101',
#   pairingId: 'dpr_lrq9lhytT4SuF40pbOKb5A',
#   tokenId: undefined,
#   clientName: 'Desktop app'
# }
# [desktop-auth] {
#   action: 'token_issued',
#   actorUserId: '101',
#   pairingId: 'dpr_lrq9lhytT4SuF40pbOKb5A',
#   tokenId: 'e75af4af-5cc4-4ba0-9c4d-5d0b93db9cd1',
#   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: 14.688801
      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: 4.191272
      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: 13.739571
      type: 'test'
      ...
    # Subtest: rejects the wrong secret without revealing pairing state
    ok 4 - rejects the wrong secret without revealing pairing state
      ---
      duration_ms: 5.001543
      type: 'test'
      ...
    # Subtest: expires unapproved pairings and cleans retained expired records
    ok 5 - expires unapproved pairings and cleans retained expired records
      ---
      duration_ms: 5.603979
      type: 'test'
      ...
    # Subtest: rejects unsafe names and non-HTTPS approval origins
    ok 6 - rejects unsafe names and non-HTTPS approval origins
      ---
      duration_ms: 3.029262
      type: 'test'
      ...
    1..6
ok 1 - desktop browser pairing
  ---
  duration_ms: 47.33098
  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: 10.363147
      type: 'test'
      ...
    # Subtest: honors optional token expiry
    ok 2 - honors optional token expiry
      ---
      duration_ms: 9.195438
      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.156374
      type: 'test'
      ...
    1..3
ok 2 - instance token ownership and revocation
  ---
  duration_ms: 23.136273
  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.23082
      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: 1.807361
      type: 'test'
      ...
    1..2
ok 3 - pairing approval request protection
  ---
  duration_ms: 4.143701
  type: 'suite'
  ...
# [2026-08-29 19:49:59.338 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-sO6K9Y/020-desktopAuth.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 19:49:59.407 +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 2724.615476

[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.899074
  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.147032
  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: 69.915412
  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: 7.389669
  type: 'test'
  ...
1..4
# tests 4
# suites 0
# pass 4
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 914.775856

[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: 10.596076
      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.51491
      type: 'test'
      ...
    # Subtest: grants full admin permissions to PROPR_ADMIN_USERS
    ok 3 - grants full admin permissions to PROPR_ADMIN_USERS
      ---
      duration_ms: 2.488568
      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.535057
      type: 'test'
      ...
    # Subtest: resolves demo users without installation permissions
    ok 5 - resolves demo users without installation permissions
      ---
      duration_ms: 2.039141
      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: 1.980152
      type: 'test'
      ...
    # Subtest: current-user responses expose capabilities without OAuth credentials
    ok 7 - current-user responses expose capabilities without OAuth credentials
      ---
      duration_ms: 2.562679
      type: 'test'
      ...
    1..7
ok 1 - instance authorization
  ---
  duration_ms: 27.947606
  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.080559
      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.250572
      type: 'test'
      ...
    # Subtest: adds explicit members and writes an audit entry
    ok 3 - adds explicit members and writes an audit entry
      ---
      duration_ms: 4.054716
      type: 'test'
      ...
    # Subtest: keeps environment authority separate from durable role assignments
    ok 4 - keeps environment authority separate from durable role assignments
      ---
      duration_ms: 2.452363
      type: 'test'
      ...
    # Subtest: prevents removing the last durable administrator
    ok 5 - prevents removing the last durable administrator
      ---
      duration_ms: 4.233215
      type: 'test'
      ...
    # Subtest: allows role changes once another administrator exists
    ok 6 - allows role changes once another administrator exists
      ---
      duration_ms: 5.393862
      type: 'test'
      ...
    1..6
ok 2 - instance member service
  ---
  duration_ms: 25.89186
  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: 7.514997
      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.65402
      type: 'test'
      ...
    1..2
ok 3 - instance catalog
  ---
  duration_ms: 9.260747
  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.575269
      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.349762
      type: 'test'
      ...
    # Subtest: rejects GitHub usernames with trailing or consecutive hyphens
    ok 3 - rejects GitHub usernames with trailing or consecutive hyphens
      ---
      duration_ms: 1.652048
      type: 'test'
      ...
    1..3
ok 4 - instance admin routes
  ---
  duration_ms: 5.691191
  type: 'suite'
  ...
# [2026-08-29 19:50:02.841 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-sO6K9Y/022-instanceAuthorization.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 19:50:02.905 +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 2057.287361

[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: 0.826044
  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.167142
  type: 'test'
  ...
# Subtest: an explicit API listen host overrides environment defaults
ok 3 - an explicit API listen host overrides environment defaults
  ---
  duration_ms: 0.134984
  type: 'test'
  ...
1..3
# tests 3
# suites 0
# pass 3
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 234.538307

[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: 5.269645
  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.427149
  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.813875
  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.224408
  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.290136
  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.325581
  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.184227
  type: 'test'
  ...
# Subtest: stored output detection recognizes truncated Antigravity result JSON
ok 8 - stored output detection recognizes truncated Antigravity result JSON
  ---
  duration_ms: 0.157968
  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.352711
  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: 65.485205
  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: 2.394726
  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.460489
  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.911152
  type: 'test'
  ...
# Subtest: stored output parsing filters Antigravity transcript tool items
ok 14 - stored output parsing filters Antigravity transcript tool items
  ---
  duration_ms: 1.082691
  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.490305
  type: 'test'
  ...
# Subtest: execution detail fallback filters Antigravity transcript tool rows
ok 16 - execution detail fallback filters Antigravity transcript tool rows
  ---
  duration_ms: 1.084083
  type: 'test'
  ...
# [2026-08-29 19:50:05.407 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-sO6K9Y/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 2023.132323

[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.155218
  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.25268
  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.17912
  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.144748
  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.400263
  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: 17.894046
  type: 'test'
  ...
# [2026-08-29 19:50:07.546 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-sO6K9Y/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 1811.610777

[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: 1447.714239
  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: 3.402254
  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.564185
  type: 'test'
  ...
# Subtest: parseOpenCodeOutputToConversationResult separates structured assistant text parts
ok 4 - parseOpenCodeOutputToConversationResult separates structured assistant text parts
  ---
  duration_ms: 16.199325
  type: 'test'
  ...
# Subtest: detectStoredOutputFormat recognizes whole-document pretty OpenCode JSON
ok 5 - detectStoredOutputFormat recognizes whole-document pretty OpenCode JSON
  ---
  duration_ms: 1.555112
  type: 'test'
  ...
# Subtest: detectStoredOutputFormat recognizes OpenCode assistant parts without session identifiers
ok 6 - detectStoredOutputFormat recognizes OpenCode assistant parts without session identifiers
  ---
  duration_ms: 1.004713
  type: 'test'
  ...
# Subtest: detectStoredOutputFormat recognizes whole-document pretty Claude JSON arrays
ok 7 - detectStoredOutputFormat recognizes whole-document pretty Claude JSON arrays
  ---
  duration_ms: 0.907626
  type: 'test'
  ...
# [2026-08-29 19:50:09.471 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-sO6K9Y/026-liveDetailsOpenCodeParser.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 19:50:09.497 +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 2002.933085

[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)
#       at Fu

...(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 48f8581

Summary of Changes

Fixed the full-suite flake in notificationManagementRoutes.test.ts.

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.


🤖 Implementation Details

  • Model: GPT-5.6 Sol (high)
  • Requested By: @github-actions[bot]
  • Time: 5m 40s
  • Tokens: 1,879,666 (1,870,615 in / 9,051 out)
  • Cost: $1.64

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: 5464573988✓

@integry integry removed the AI label Aug 29, 2026
….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\nRe-review exact shared runtime head 48f8581 after F1. Confirm /tasks%23/%2e%2e/login and encoded ? variants are rejected in every API/UI path, normal /tasks query/hash navigation remains intact, canonical output is the representation actually validated, the UI timing test is deterministic, and the VAPID fixture padding is test-only and correct. Treat any bypass or 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-1980-followup-2026-08-29T20-07-02

Comment ID: 5464613934✓
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

Retry the exact-head review after the transient missing-worktree infrastructure failure. Review SHA 48f8581 only. Confirm the canonical encoded-delimiter/traversal bypass is closed while normal /tasks query/hash navigation works, every returned representation was validated, deterministic UI coverage remains, and the VAPID padding is safe test-only scope. No code changes; report findings 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

SHA 48f85811addf4bccfd390961e8dcb68971e6a0e6 is ready to merge within scope, subject to the two pending full-suite checks completing successfully.

Canonical bypass closedsecurity.ts tracks and validates the fully decoded original path scope, rejecting encoded #/? delimiters combined with single- or double-encoded traversal before returning a route.

Navigation and UI coverage preserved — Normal /tasks?status=open#recent navigation remains intact, startup delivery stays buffered, and the packaged smoke test waits for concrete DOM/layout readiness before measuring rendered controls.

VAPID padding safely scoped — The fixed-width left-padding occurs only in packages/api/test/notificationManagementRoutes.test.ts and correctly produces a 32-byte P-256 test scalar.

Merge blockers

No merge blockers.

Suggestions

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

No suggestions.

Score

The changed security path returns the final canonical representation that passed validation, targeted regressions cover the reported bypass in both API and UI paths, and no current-head check is failing. The remaining checks are pending rather than failed.

Score: 9/10


🤖 Review Details

  • Model: GPT-5.6 Sol
  • Time: 1m 28s
  • Tokens: 44,419 (41,057 in / 3,362 out)
  • Cost: $0.26

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 merged commit 2611e5f into 1951-epic-desktop-experience-uxs Aug 29, 2026
73 of 74 checks passed
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