Skip to content

[1956 by GPT-5.6 Sol] Scaffold the secure Electron desktop runtime and reusable Web UI renderer - #1967

Merged
integry merged 11 commits into
1952-epic-electron-runtime-appfrom
1956/gpt-5.6-sol-scaffold-the-secure-elect-20260829-1408-gux
Aug 29, 2026
Merged

[1956 by GPT-5.6 Sol] Scaffold the secure Electron desktop runtime and reusable Web UI renderer#1967
integry merged 11 commits into
1952-epic-electron-runtime-appfrom
1956/gpt-5.6-sol-scaffold-the-secure-elect-20260829-1408-gux

Conversation

@propr-dev

@propr-dev propr-dev Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

AI Implementation Summary

Closes #1956

Branch: 1956/gpt-5.6-sol-scaffold-the-secure-elect-20260829-1408-gux
Commits: ✅ Changes committed (5fc195c)


AI Processing Completed

Execution Details:

Summary:
Implemented the secure Electron desktop scaffold under apps/desktop, including:

  • Electron Forge/Vite workspace and root desktop scripts.
  • Reused propr-ui desktop entry with hash routing and connection placeholder.
  • Sandboxed BrowserWindow, restrictive CSP, denied permissions, navigation/external URL allowlists, and hardened Electron fuses.
  • Typed preload contract for metadata, profiles, encrypted credentials, deep links, and lifecycle placeholders.
  • safeStorage persistence with no plaintext fallback.
  • Single-instance deep-link routing, structured logs, and clean shutdown.
  • 15 focused main/preload/storage security tests.

Verification passed:

npm run build
npm run build -w propr-ui
npm run lint -w propr-ui
npm run desktop:typecheck
npm run desktop:test
npm run desktop:package

The packaged Linux binary’s security fuses were also inspected successfully. Native installer creation could not be completed because this environment lacks zip, fakeroot, dpkg, and rpmbuild. macOS/Windows packaging and GUI launch were not locally exercised.

Detailed Logs:

  • Conversation: 159 messages
  • Session: 01a04ddb-4416-70c1-9510-b140a7409db8

Log files stored at:

  • conversation: /tmp/claude-logs/issue-1956-2026-08-29T14-31-40-455Z-conversation.json
  • output: /tmp/claude-logs/issue-1956-2026-08-29T14-31-40-455Z-output.txt
Latest Conversation Messages

This PR was created automatically by ProPR after processing issue #1956.


💡 Need changes?

Comment on this PR to request refinements — the AI agent monitors comments and will update the implementation based on your feedback. Keep iterating until you're satisfied!

…untime and r

Implemented by ProPR AI using gpt-5.6-sol model.

Implementation completed successfully.
@integry
integry changed the base branch from main to 1952-epic-electron-runtime-app August 29, 2026 14:35
@integry

integry commented Aug 29, 2026

Copy link
Copy Markdown
Owner

!propr Independent review found a packaging supply-chain gap that the green dependency-review check did not surface.

On this PR's clean lockfile:

  • npm audit --omit=dev --audit-level=low reports 0 runtime vulnerabilities.
  • Full npm audit reports 28 development/build-tool vulnerabilities: 1 critical and 24 high, principally vulnerable tar copies under @electron/rebuild / @electron/node-gyp / cacache, high-severity extract-zip under @electron/packager, and tmp under Inquirer. The reported classes include arbitrary file overwrite/path traversal while extracting archives. This matters on release runners even though these packages are not shipped as renderer runtime dependencies.

Please update Electron Forge/packager/rebuild and safe transitive overrides to fixed versions where available, regenerate the lockfile, and rerun desktop typecheck/tests/package plus npm audit. Add a release-CI audit/guard that distinguishes production runtime dependencies from the packaging toolchain and fails on high/critical packaging advisories. If an upstream advisory truly has no fixed compatible release, document the exact constrained exposure and add concrete runner/download-integrity mitigations instead of silently accepting it. Preserve the current secure Electron configuration and do not weaken audit thresholds.

@propr-dev

propr-dev Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor Author

Applied the requested follow-up changes in commit 21d0ff3

Summary of Changes

Implemented the packaging supply-chain follow-up without changing Electron security configuration.

Changes:

  • Upgraded all Electron Forge packages to 8.0.0-alpha.10.
  • Pinned safe overrides:
    • @electron/packager 20.3.0
    • @electron/rebuild 4.2.0
  • Regenerated package-lock.json.
  • Added separate runtime and packaging audit scripts in package.json.
  • Added a lockfile-first desktop release guard that fails:
    • Runtime advisories at low or higher.
    • Desktop packaging advisories at high or critical.
  • Documented the audit policy in apps/desktop/README.md.

Verification passed:

  • Clean npm ci
  • Runtime audit: 0 vulnerabilities
  • Desktop packaging audit: 0 vulnerabilities
  • Full npm audit: 0 vulnerabilities
  • Desktop/UI typecheck
  • 15 desktop tests
  • Linux desktop packaging
  • Workflow YAML validation and git diff --check

The vulnerable extract-zip, Electron node-gyp, and cacache chains are absent. No advisory exception or constrained-exposure documentation was necessary. Changes remain uncommitted as requested.


🤖 Implementation Details

  • Model: GPT-5.6 Sol (high)
  • Requested By: @integry
  • Time: 8m 4s
  • Tokens: 1,817,341 (1,803,286 in / 14,055 out)
  • Cost: $1.80
  • 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: 5463039825✓

@integry

integry commented Aug 29, 2026

Copy link
Copy Markdown
Owner

!propr Additional release-blocking finding from launching the actual Linux package.

Reproduction:

  1. Build @propr/shared.
  2. Run the desktop Forge package command successfully.
  3. Launch apps/desktop/out/ProPR Desktop-linux-x64/propr-desktop --disable-gpu.

Observed: the packaged binary exits immediately with code 133:
FATAL:gin/v8_initializer.cc:675] Error loading V8 startup snapshot file

@electron/fuses read confirms LoadBrowserProcessSpecificV8Snapshot is Enabled. The artifact contains the normal snapshot_blob.bin and v8_context_snapshot.bin, but no browser-process-specific snapshot. Please correct the fuse/package configuration (do not enable that optional fuse unless a valid browser-specific snapshot is generated and shipped), rebuild, and prove the packaged Linux binary reaches renderer-ready without disabling Electron's sandbox. Add an automated packaged-binary launch smoke test so a successful Forge packaging command cannot mask an unlaunchable artifact. Preserve the other hardened fuses.

…anging Electron security configuration.

Implemented the packaging supply-chain follow-up without changing Electron security configuration.

Changes:

- Upgraded all Electron Forge packages to `8.0.0-alpha.10`.
- Pinned safe overrides:
  - `@electron/packager` 20.3.0
  - `@electron/rebuild` 4.2.0
- Regenerated [package-lock.json](/tmp/git-processor/worktrees/integry/propr/pr-1967-followup-2026-08-29T14-44-22/package-lock.json).
- Added separate runtime and packaging audit scripts in [package.json](/tmp/git-processor/worktrees/integry/propr/pr-1967-followup-2026-08-29T14-44-22/package.json).
- Added a lockfile-first [desktop release guard](/tmp/git-processor/worktrees/integry/propr/pr-1967-followup-2026-08-29T14-44-22/.github/workflows/desktop-release-guard.yml) that fails:
  - Runtime advisories at low or higher.
  - Desktop packaging advisories at high or critical.
- Documented the audit policy in [apps/desktop/README.md](/tmp/git-processor/worktrees/integry/propr/pr-1967-followup-2026-08-29T14-44-22/apps/desktop/README.md).

Verification passed:

- Clean `npm ci`
- Runtime audit: 0 vulnerabilities
- Desktop packaging audit: 0 vulnerabilities
- Full `npm audit`: 0 vulnerabilities
- Desktop/UI typecheck
- 15 desktop tests
- Linux desktop packaging
- Workflow YAML validation and `git diff --check`

The vulnerable `extract-zip`, Electron `node-gyp`, and `cacache` chains are absent. No advisory exception or constrained-exposure documentation was necessary. Changes remain uncommitted as requested.

PR: #1967
Comment by: @integry (ID: 5463039825)
Model: gpt-5.6-sol
@propr-dev

propr-dev Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor Author

Applied the requested follow-up changes in commit 3275d74

Summary of Changes

Implemented the release-blocking packaging fix.

  • Disabled only LoadBrowserProcessSpecificV8Snapshot; all hardened fuses remain enforced.
  • Added a packaged Linux smoke test that:
    • Validates every fuse state.
    • Launches without --no-sandbox or --disable-sandbox.
    • Requires Electron’s ready-to-show renderer event.
    • Requires a clean exit.
  • Added the smoke test to the desktop release guard using Xvfb and a correctly configured Chromium setuid sandbox helper.
  • Added desktop:smoke and documented its usage.

Verification passed:

  • Shared package build
  • Desktop/UI typecheck
  • All 15 desktop tests
  • Forge Linux packaging
  • Fuse inspection, including the snapshot fuse disabled
  • Script syntax and git diff --check

The current execution container cannot complete the final launch because it prohibits user namespaces and provides no root/sudo or Xvfb; Electron correctly failed closed instead of disabling its sandbox. The release CI now supplies the required sandbox helper configuration and display server to execute that proof automatically.


🤖 Implementation Details

  • Model: GPT-5.6 Sol (high)
  • Requested By: @integry
  • Time: 8m 29s
  • Tokens: 1,398,781 (1,382,650 in / 16,131 out)
  • Cost: $1.54

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

@integry

integry commented Aug 29, 2026

Copy link
Copy Markdown
Owner

!propr The new Desktop Release Guard currently fails in its Typecheck desktop and renderer step on a clean checkout because @propr/shared/dist has not been built. The log shows TS2307: Cannot find module '@propr/shared' followed by cascading UI type errors. The package step also needs that dist output (independently reproduced before packaging).

Please make desktop validation/package preparation self-contained and explicit: build required workspace renderer dependencies (at minimum @propr/shared) before desktop/UI typecheck and Forge packaging, then keep the new audit steps first so untrusted lifecycle/tooling still does not execute before the lockfile audit. Rerun the release guard through audit, install, typecheck, 15 desktop tests, package, and the requested packaged-binary launch smoke.

Implemented the release-blocking packaging fix.

- Disabled only `LoadBrowserProcessSpecificV8Snapshot`; all hardened fuses remain enforced.
- Added a packaged Linux smoke test that:
  - Validates every fuse state.
  - Launches without `--no-sandbox` or `--disable-sandbox`.
  - Requires Electron’s `ready-to-show` renderer event.
  - Requires a clean exit.
- Added the smoke test to the desktop release guard using Xvfb and a correctly configured Chromium setuid sandbox helper.
- Added `desktop:smoke` and documented its usage.

Verification passed:

- Shared package build
- Desktop/UI typecheck
- All 15 desktop tests
- Forge Linux packaging
- Fuse inspection, including the snapshot fuse disabled
- Script syntax and `git diff --check`

The current execution container cannot complete the final launch because it prohibits user namespaces and provides no root/sudo or Xvfb; Electron correctly failed closed instead of disabling its sandbox. The release CI now supplies the required sandbox helper configuration and display server to execute that proof automatically.

PR: #1967
Comment by: @integry (ID: 5463056792)
Model: gpt-5.6-sol
@propr-dev

propr-dev Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor Author

Applied the requested follow-up changes in commit 10c0cd7

Summary of Changes

Implemented the self-contained desktop preparation:

  • Added desktop:prepare to build @propr/shared.
  • Made desktop:typecheck and desktop:package invoke preparation first.
  • Added packages/shared/** to the release-guard trigger.
  • Documented the behavior.

Files changed: package.json, desktop-release-guard.yml, and README.md.

Verification:

  • Runtime audit: 0 vulnerabilities
  • Packaging audit: 0 vulnerabilities
  • npm ci: passed
  • Typecheck from absent packages/shared/dist: passed and recreated output
  • Desktop tests: 15/15 passed
  • Forge Linux package: passed, independently rebuilding shared first
  • JSON, script syntax, and git diff --check: passed

The packaged launch smoke was attempted but this execution host lacks both sudo and Xvfb and forbids unprivileged user namespaces. The harness verified the fuse configuration, then correctly failed closed because chrome-sandbox could not be made root-owned mode 4755. The CI workflow retains the required sandbox-helper setup and Xvfb launch steps.


🤖 Implementation Details

  • Model: GPT-5.6 Sol (high)
  • Requested By: @integry
  • Time: 6m 12s
  • Tokens: 931,118 (920,995 in / 10,123 out)
  • Cost: $1.00

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

Implemented the self-contained desktop preparation:

- Added `desktop:prepare` to build `@propr/shared`.
- Made `desktop:typecheck` and `desktop:package` invoke preparation first.
- Added `packages/shared/**` to the release-guard trigger.
- Documented the behavior.

Files changed: [package.json](/tmp/git-processor/worktrees/integry/propr/pr-1967-followup-2026-08-29T15-02-01/package.json:75), [desktop-release-guard.yml](/tmp/git-processor/worktrees/integry/propr/pr-1967-followup-2026-08-29T15-02-01/.github/workflows/desktop-release-guard.yml:10), and [README.md](/tmp/git-processor/worktrees/integry/propr/pr-1967-followup-2026-08-29T15-02-01/apps/desktop/README.md:23).

Verification:

- Runtime audit: 0 vulnerabilities
- Packaging audit: 0 vulnerabilities
- `npm ci`: passed
- Typecheck from absent `packages/shared/dist`: passed and recreated output
- Desktop tests: 15/15 passed
- Forge Linux package: passed, independently rebuilding shared first
- JSON, script syntax, and `git diff --check`: passed

The packaged launch smoke was attempted but this execution host lacks both `sudo` and Xvfb and forbids unprivileged user namespaces. The harness verified the fuse configuration, then correctly failed closed because `chrome-sandbox` could not be made root-owned mode `4755`. The CI workflow retains the required sandbox-helper setup and Xvfb launch steps.

PR: #1967
Comment by: @integry (ID: 5463103357)
Model: gpt-5.6-sol
@integry

integry commented Aug 29, 2026

Copy link
Copy Markdown
Owner

!propr The final repaired Linux package now gets past the V8 snapshot, but independently launching the exact artifact exposes another release-blocking main-process failure before any window is created:

A JavaScript error occurred in the main process
ReferenceError: require is not defined in ES module scope
This file is being treated as an ES module because .../resources/app.asar/package.json contains "type": "module".
... at app.asar/.vite/build/main.js:2:19

Inspection confirms .vite/build/main.js and preload.js are CommonJS bundles beginning with "use strict"; const node_path = require("node:path"), while the packaged desktop manifest marks .js as ESM. Please make the Electron main/preload output format and packaged manifest/entry extensions consistent (including the configured main and BrowserWindow preload path), then rerun typecheck, all 15 tests, package, fuse validation, and the sandboxed renderer-ready smoke. The smoke must fail on any main-process uncaught exception and prove that window.proprDesktop is actually exposed before declaring ready.

@propr-dev

propr-dev Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor Author

Applied the requested follow-up changes in commit c95ad7a

Summary of Changes

Implemented the Electron entry-format repair and strengthened release smoke:

  • Main/preload now emit explicit CommonJS .cjs bundles; packaged manifest and preload path match.
  • Packaged renderer uses a restricted app-owned protocol, preserving the disabled file-protocol privilege fuse, as Electron recommends.
  • Renderer-ready is emitted only after verifying window.proprDesktop.
  • Smoke rejects known main-process uncaught-exception markers.

Key changes: package.json, main.ts, smoke-packaged.mjs.

Validation:

  • Typecheck: passed
  • Desktop tests: 15/15 passed
  • Forge package: passed
  • Packaged manifest/bundle inspection: passed
  • Fuse validation: passed, including V8 snapshot fuse disabled
  • Functional packaged launch: reached renderer-ready with "preloadBridgeExposed":true

The exact sandbox smoke was rerun but this container lacks sudo and cannot make chrome-sandbox root-owned mode 4755; Chromium correctly refused to start rather than disabling sandboxing. The existing release workflow performs that ownership setup before running the unchanged smoke command.


🤖 Implementation Details

  • Model: GPT-5.6 Sol (high)
  • Requested By: @integry
  • Time: 17m 41s
  • Tokens: 4,780,855 (4,751,190 in / 29,665 out)
  • Cost: $4.19

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

…ed release smoke:

Implemented the Electron entry-format repair and strengthened release smoke:

- Main/preload now emit explicit CommonJS `.cjs` bundles; packaged manifest and preload path match.
- Packaged renderer uses a restricted app-owned protocol, preserving the disabled file-protocol privilege fuse, as [Electron recommends](https://www.electronjs.org/docs/latest/tutorial/security).
- Renderer-ready is emitted only after verifying `window.proprDesktop`.
- Smoke rejects known main-process uncaught-exception markers.

Key changes: [package.json](/tmp/git-processor/worktrees/integry/propr/pr-1967-followup-2026-08-29T15-14-42/apps/desktop/package.json:11), [main.ts](/tmp/git-processor/worktrees/integry/propr/pr-1967-followup-2026-08-29T15-14-42/apps/desktop/src/main.ts:22), [smoke-packaged.mjs](/tmp/git-processor/worktrees/integry/propr/pr-1967-followup-2026-08-29T15-14-42/apps/desktop/scripts/smoke-packaged.mjs:12).

Validation:

- Typecheck: passed
- Desktop tests: 15/15 passed
- Forge package: passed
- Packaged manifest/bundle inspection: passed
- Fuse validation: passed, including V8 snapshot fuse disabled
- Functional packaged launch: reached renderer-ready with `"preloadBridgeExposed":true`

The exact sandbox smoke was rerun but this container lacks `sudo` and cannot make `chrome-sandbox` root-owned mode `4755`; Chromium correctly refused to start rather than disabling sandboxing. The existing release workflow performs that ownership setup before running the unchanged smoke command.

PR: #1967
Comment by: @integry (ID: 5463184150)
Model: gpt-5.6-sol
@integry

integry commented Aug 29, 2026

Copy link
Copy Markdown
Owner

!propr The repaired artifact independently passes on this host: clean Node 22 install (0 vulnerabilities), both audits 0, typecheck, 15/15 desktop tests, Forge package, hardened fuse validation, and an actual sandboxed launch that emitted desktop.renderer.ready with "preloadBridgeExposed":true. Visual inspection also confirms the packaged window renders.

However the current Desktop Release Guard exposes one final Linux/setuid-sandbox portability blocker. Its smoke log says:
LaunchProcess: failed to execvp: /home/runner/work/propr/propr/apps/desktop/out/ProPR
then zygote exit 127/SIGTRAP. The actual bundle directory is ProPR Desktop-linux-x64; the setuid sandbox launch path is being truncated at the space. Please make the Linux packaged output/product path shell- and sandbox-safe (prefer a stable space-free package/output name such as propr-desktop-linux-x64, while retaining the user-facing product name in app metadata), update smoke/artifact paths without brittle globbing, and rerun the full release guard. Preserve all current security settings. Acceptance is a green CI sandboxed smoke plus the existing renderer-ready/bridge proof.

@propr-dev

propr-dev Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor Author

Applied the requested follow-up changes in commit c706970

Summary of Changes

Implemented the Linux space-free package path while preserving the user-facing product name and all security settings.

Changes:

  • Forge now emits apps/desktop/out/propr-desktop-linux-x64 via forge.config.ts.
  • Smoke launches the exact space-free binary path in smoke-packaged.mjs.
  • CI configures the exact sandbox helper path without globbing in desktop-release-guard.yml.
  • Packaged ASAR metadata still contains "productName":"ProPR Desktop".

Verification passed:

  • Both audits: 0 vulnerabilities
  • Clean npm ci: 0 vulnerabilities
  • Desktop and renderer typecheck
  • Desktop tests: 15/15
  • Forge package
  • Exact artifact and metadata assertions
  • Hardened fuse validation
  • git diff --check

The final local renderer-ready launch could not complete because this container has neither sudo nor xvfb-run; it stopped at Chromium’s expected root/setuid ownership check. Its diagnostic used the complete new space-free path without truncation. The CI workflow retains the required chown root:root, mode 4755, Xvfb, and sandboxed renderer-ready/bridge proof.


🤖 Implementation Details

  • Model: GPT-5.6 Sol (high)
  • Requested By: @integry
  • Time: 7m 54s
  • Tokens: 2,202,533 (2,189,247 in / 13,286 out)
  • Cost: $1.90

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

@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/312] 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.490757
      type: 'test'
      ...
    # Subtest: maps profile and credential operations to fixed channels
    ok 2 - maps profile and credential operations to fixed channels
      ---
      duration_ms: 0.505491
      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.261327
      type: 'test'
      ...
    1..3
ok 1 - desktop preload bridge
  ---
  duration_ms: 3.271014
  type: 'suite'
  ...
1..1
# tests 3
# suites 1
# pass 3
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 244.676008

[2/312] 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.755145
      type: 'test'
      ...
    # Subtest: encrypts credentials before writing app-owned storage
    ok 2 - encrypts credentials before writing app-owned storage
      ---
      duration_ms: 6.456522
      type: 'test'
      ...
    # Subtest: refuses plaintext fallback when encryption is unavailable or basic_text
    ok 3 - refuses plaintext fallback when encryption is unavailable or basic_text
      ---
      duration_ms: 2.284106
      type: 'test'
      ...
    # Subtest: rejects unsafe endpoints and path-like profile identifiers
    ok 4 - rejects unsafe endpoints and path-like profile identifiers
      ---
      duration_ms: 2.30242
      type: 'test'
      ...
    1..4
ok 1 - desktop profile store
  ---
  duration_ms: 26.064763
  type: 'suite'
  ...
1..1
# tests 4
# suites 1
# pass 4
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 275.026321

[3/312] 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.02015
      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.244125
      type: 'test'
      ...
    # Subtest: requires an exact loopback development origin
    ok 3 - requires an exact loopback development origin
      ---
      duration_ms: 0.270224
      type: 'test'
      ...
    # Subtest: only trusts the packaged renderer URL
    ok 4 - only trusts the packaged renderer URL
      ---
      duration_ms: 0.2089
      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.252501
      type: 'test'
      ...
    # Subtest: publishes a restrictive production policy
    ok 6 - publishes a restrictive production policy
      ---
      duration_ms: 0.233124
      type: 'test'
      ...
    1..6
ok 1 - desktop URL security
  ---
  duration_ms: 3.444528
  type: 'suite'
  ...
1..1
# tests 6
# suites 1
# pass 6
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 236.317029

[4/312] 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.636368
      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.187459
      type: 'test'
      ...
    1..2
ok 1 - desktop BrowserWindow security
  ---
  duration_ms: 3.129821
  type: 'suite'
  ...
1..1
# tests 2
# suites 1
# pass 2
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 233.171937

[5/312] 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.015048
      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.57253
      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.224104
      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.693582
      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.452814
      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.724232
      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.686579
      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.731132
      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.638289
      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.527452
      type: 'test'
      ...
    1..10
ok 1 - agent login session manager
  ---
  duration_ms: 13.659329
  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.84241
      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.620746
      type: 'test'
      ...
    # Subtest: rejects interactive login for an unsupported agent type
    ok 3 - rejects interactive login for an unsupported agent type
      ---
      duration_ms: 0.452172
      type: 'test'
      ...
    1..3
ok 2 - agent login routes
  ---
  duration_ms: 2.06125
  type: 'suite'
  ...
# [2026-08-29 15:34:06.701 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-MVPEDU/005-agentLoginRoutes.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 15:34:06.713 +0000] �[32mINFO�[39m: �[36mSQLite database connection closed�[39m
# [2026-08-29 15:34:06.713 +0000] �[32mINFO�[39m: �[36mShutting down queue...�[39m
# [2026-08-29 15:34:06.713 +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 2360.146784

[6/312] 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.334376
      type: 'test'
      ...
    # Subtest: persists a failed state when queue submission fails
    ok 2 - persists a failed state when queue submission fails
      ---
      duration_ms: 0.889147
      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.772258
      type: 'test'
      ...
    # Subtest: times out slow runtime package validation before queueing
    ok 4 - times out slow runtime package validation before queueing
      ---
      duration_ms: 5.819123
      type: 'test'
      ...
    # Subtest: returns package suggestions from configured runtimes
    ok 5 - returns package suggestions from configured runtimes
      ---
      duration_ms: 0.84242
      type: 'test'
      ...
    # Subtest: enforces PROPR_ADMIN_USERS when configured
    ok 6 - enforces PROPR_ADMIN_USERS when configured
      ---
      duration_ms: 0.474164
      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.374879
      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.339493
      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.47743
      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.250687
      type: 'test'
      ...
    # Subtest: resolves the runtime build queue lazily when queueing
    ok 11 - resolves the runtime build queue lazily when queueing
      ---
      duration_ms: 0.829816
      type: 'test'
      ...
    # Subtest: reports apply load failures through the route response
    ok 12 - reports apply load failures through the route response
      ---
      duration_ms: 0.47756
      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.441332
      type: 'test'
      ...
    # Subtest: rejects runtime verification without runtime-management permission
    ok 14 - rejects runtime verification without runtime-management permission
      ---
      duration_ms: 0.292114
      type: 'test'
      ...
    1..14
ok 1 - agent runtime package routes
  ---
  duration_ms: 21.330474
  type: 'suite'
  ...
# [2026-08-29 15:34:08.892 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-MVPEDU/006-agentRuntimeRoutes.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 15:34:08.910 +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 2256.202008

[7/312] 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.603658
  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.478641
  type: 'test'
  ...
# Subtest: temporary cleanup refuses paths outside its configured root
ok 3 - temporary cleanup refuses paths outside its configured root
  ---
  duration_ms: 0.694154
  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.526882
  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.355649
  type: 'test'
  ...
# [2026-08-29 15:34:11.260 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-MVPEDU/007-attachmentUploadCleanup.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 15:34:11.276 +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 2123.171178

[8/312] 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.485291
  type: 'test'
  ...
# Subtest: ensureAuthenticated refreshes an expired GitHub token before continuing
ok 2 - ensureAuthenticated refreshes an expired GitHub token before continuing
  ---
  duration_ms: 5.257376
  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.286137
  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: 9.608999
  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.663877
  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.945342
  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.236885
  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.08408
  type: 'test'
  ...
# [2026-08-29 15:34:14.008 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-MVPEDU/008-authGithubTokens.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 15:34:14.025 +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 2607.883591

[9/312] 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: 44.332067
  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: 8.216481
  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.994577
  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.99805
  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.338711
  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.197638
  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.170598
  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.81739
  type: 'test'
  ...
# Subtest: auth redirect allowlist permits configured local IP literals
ok 9 - auth redirect allowlist permits configured local IP literals
  ---
  duration_ms: 5.432592
  type: 'test'
  ...
# Subtest: auth redirect allowlist ignores malformed additional host entries
ok 10 - auth redirect allowlist ignores malformed additional host entries
  ---
  duration_ms: 4.937139
  type: 'test'
  ...
# [2026-08-29 15:34:16.680 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-MVPEDU/009-authRedirect.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 15:34:16.768 +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 2558.946261

[10/312] 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: 1545.754311
  type: 'test'
  ...
# [2026-08-29 15:34:18.892 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-MVPEDU/010-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 2094.21621

[11/312] 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: 4.860517
  type: 'test'
  ...
# Subtest: API config subscription reloads repository updates
ok 2 - API config subscription reloads repository updates
  ---
  duration_ms: 0.376552
  type: 'test'
  ...
# Subtest: API config subscription ignores unrelated and malformed events
ok 3 - API config subscription ignores unrelated and malformed events
  ---
  duration_ms: 0.268691
  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.433909
  type: 'test'
  ...
1..4
# tests 4
# suites 0
# pass 4
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 257.968083

[12/312] packages/api/test/configRoutesIndexingSummarization.test.ts
TAP version 13
# [2026-08-29 15:34:21.532 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-MVPEDU/012-configRoutesIndexingSummarization.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 15:34:21.561 +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: 1871.017208
  type: 'test'
  ...
# Subtest: postSummarizationSettings rejects enabled summarization without a primary alias
ok 2 - postSummarizationSettings rejects enabled summarization without a primary alias
  ---
  duration_ms: 3.924543
  type: 'test'
  ...
# [2026-08-29 15:34:21.810 +0000] �[32mINFO�[39m: �[36mDatabase migrations completed successfully�[39m
# [2026-08-29 15:34:21.812 +0000] �[32mINFO�[39m: �[36mSuccessfully loaded agents configuration�[39m
#     agentCount: 2
# [2026-08-29 15:34:21.812 +0000] �[32mINFO�[39m: �[36mSuccessfully loaded summarization settings�[39m
#     summarization: {
#       "enabled": false,
#       "agent_alias": "",
#       "fallback_agent_alias": "",
#       "custom_prompt": ""
#     }
# [2026-08-29 15:34:21.814 +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 15:34:21.814 +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 15:34:21.825 +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 2536.756422

[13/312] 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.296095
  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.284881
  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.297124
  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.351816
  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.195995
  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.159958
  type: 'test'
  ...
# Subtest: explicit custom GitHub web auth remains supported
ok 7 - explicit custom GitHub web auth remains supported
  ---
  duration_ms: 0.193771
  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.195113
  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.570824
  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: 28.854787
  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.813826
  type: 'test'
  ...
1..11
# tests 11
# suites 0
# pass 11
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 306.268572

[14/312] packages/api/test/connectAuthCallback.test.ts
TAP version 13
# [2026-08-29 15:34:25.040 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-MVPEDU/014-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: 3045.492577
  type: 'test'
  ...
# [2026-08-29 15:34:28.082 +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 5577.250581

[15/312] 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.079471
  type: 'test'
  ...
# Subtest: CORS rejects unrelated origins under proxy mode
ok 2 - CORS rejects unrelated origins under proxy mode
  ---
  duration_ms: 0.250927
  type: 'test'
  ...
# Subtest: CORS allows requests with no origin
ok 3 - CORS allows requests with no origin
  ---
  duration_ms: 0.144549
  type: 'test'
  ...
# Subtest: CORS allows localhost for development
ok 4 - CORS allows localhost for development
  ---
  duration_ms: 0.156922
  type: 'test'
  ...
# Subtest: CORS rejects non-http(s) localhost schemes
ok 5 - CORS rejects non-http(s) localhost schemes
  ---
  duration_ms: 0.213768
  type: 'test'
  ...
# Subtest: CORS allows COOKIE_DOMAIN subdomains for preview environments
ok 6 - CORS allows COOKIE_DOMAIN subdomains for preview environments
  ---
  duration_ms: 0.172451
  type: 'test'
  ...
# Subtest: CORS preserves http COOKIE_DOMAIN preview compatibility
ok 7 - CORS preserves http COOKIE_DOMAIN preview compatibility
  ---
  duration_ms: 0.196135
  type: 'test'
  ...
# Subtest: CORS validator factory throws on an invalid FRONTEND_URL
ok 8 - CORS validator factory throws on an invalid FRONTEND_URL
  ---
  duration_ms: 1.194846
  type: 'test'
  ...
# Subtest: Express CORS boundary is sanitized in development mode
ok 9 - Express CORS boundary is sanitized in development mode
  ---
  duration_ms: 79.868042
  type: 'test'
  ...
# Subtest: Express CORS boundary is sanitized in production mode
ok 10 - Express CORS boundary is sanitized in production mode
  ---
  duration_ms: 27.126262
  type: 'test'
  ...
1..10
# tests 10
# suites 0
# pass 10
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 472.420997

[16/312] 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.431614
  type: 'test'
  ...
# Subtest: demoModeReadOnlyMiddleware blocks auth metadata mutations
ok 2 - demoModeReadOnlyMiddleware blocks auth metadata mutations
  ---
  duration_ms: 2.056842
  type: 'test'
  ...
# Subtest: isDemoMode accepts common truthy environment values
ok 3 - isDemoMode accepts common truthy environment values
  ---
  duration_ms: 1.123994
  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.365014
  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.571965
  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: 58.705501
  type: 'test'
  ...
# Subtest: ensureAuthenticated attaches the synthetic demo user
ok 7 - ensureAuthenticated attaches the synthetic demo user
  ---
  duration_ms: 1.14324
  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: 0.935834
  type: 'test'
  ...
# Subtest: demo repository metadata resolves enabled configured repositories
ok 9 - demo repository metadata resolves enabled configured repositories
  ---
  duration_ms: 9.732504
  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.533311
  type: 'test'
  ...
# [2026-08-29 15:34:31.490 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-MVPEDU/016-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: 273.588147
  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.51455
  type: 'test'
  ...
# Subtest: demo repository metadata ignores malformed database repository names
ok 13 - demo repository metadata ignores malformed database repository names
  ---
  duration_ms: 1.695489
  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.656298
  type: 'test'
  ...
# 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: 7.174988
  type: 'test'
  ...
# [2026-08-29 15:34:31.843 +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 15:34:31.844 +0000] �[32mINFO�[39m: �[36mSuccessfully loaded all monitored repositories�[39m
#     total_repos: 3
# [2026-08-29 15:34:31.856 +0000] �[32mINFO�[39m: �[36mSuccessfully loaded all monitored repositories�[39m
#     total_repos: 0
# [2026-08-29 15:34:31.865 +0000] �[32mINFO�[39m: �[36mSuccessfully saved monitored repositories�[39m
#     repos: [
#       {
#         "id": "1",
#         "name": "integry/propr",
#         "enabled": true
#       },
#       {
#         "id": "2",
#         "name": "integry/private",
#         "enabled": true
#       }
#     ]
# Subtest: auth demo-mode metadata endpoint reports startup environment value
ok 16 - auth demo-mode metadata endpoint reports startup environment value
  ---
  duration_ms: 8.87718
  type: 'test'
  ...
1..16
# tests 16
# suites 0
# pass 16
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 2816.358164

[17/312] 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.985175
  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.181157
  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.111325
  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: 5.25251
  type: 'test'
  ...
1..4
# tests 4
# suites 0
# pass 4
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 940.504429

[18/312] 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.52856
      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.785787
      type: 'test'
      ...
    # Subtest: grants full admin permissions to PROPR_ADMIN_USERS
    ok 3 - grants full admin permissions to PROPR_ADMIN_USERS
      ---
      duration_ms: 2.501461
      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.701985
      type: 'test'
      ...
    # Subtest: resolves demo users without installation permissions
    ok 5 - resolves demo users without installation permissions
      ---
      duration_ms: 2.175574
      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.052364
      type: 'test'
      ...
    # Subtest: current-user responses expose capabilities without OAuth credentials
    ok 7 - current-user responses expose capabilities without OAuth credentials
      ---
      duration_ms: 2.449384
      type: 'test'
      ...
    1..7
ok 1 - instance authorization
  ---
  duration_ms: 29.440093
  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.400526
      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.122337
      type: 'test'
      ...
    # Subtest: adds explicit members and writes an audit entry
    ok 3 - adds explicit members and writes an audit entry
      ---
      duration_ms: 5.127072
      type: 'test'
      ...
    # Subtest: keeps environment authority separate from durable role assignments
    ok 4 - keeps environment authority separate from durable role assignments
      ---
      duration_ms: 3.044252
      type: 'test'
      ...
    # Subtest: prevents removing the last durable administrator
    ok 5 - prevents removing the last durable administrator
      ---
      duration_ms: 4.566458
      type: 'test'
      ...
    # Subtest: allows role changes once another administrator exists
    ok 6 - allows role changes once another administrator exists
      ---
      duration_ms: 5.781773
      type: 'test'
      ...
    1..6
ok 2 - instance member service
  ---
  duration_ms: 28.483291
  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.021376
      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.707792
      type: 'test'
      ...
    1..2
ok 3 - instance catalog
  ---
  duration_ms: 3.820628
  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.723635
      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.409155
      type: 'test'
      ...
    # Subtest: rejects GitHub usernames with trailing or consecutive hyphens
    ok 3 - rejects GitHub usernames with trailing or consecutive hyphens
      ---
      duration_ms: 1.860476
      type: 'test'
      ...
    1..3
ok 4 - instance admin routes
  ---
  duration_ms: 6.110796
  type: 'suite'
  ...
# [2026-08-29 15:34:34.955 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-MVPEDU/018-instanceAuthorization.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 15:34:35.017 +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 2184.399734

[19/312] 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.127461
  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.219599
  type: 'test'
  ...
# Subtest: an explicit API listen host overrides environment defaults
ok 3 - an explicit API listen host overrides environment defaults
  ---
  duration_ms: 0.168655
  type: 'test'
  ...
1..3
# tests 3
# suites 0
# pass 3
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 247.175208

[20/312] 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.343427
  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.461734
  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.839634
  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.244155
  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.297083
  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.339773
  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.209269
  type: 'test'
  ...
# Subtest: stored output detection recognizes truncated Antigravity result JSON
ok 8 - stored output detection recognizes truncated Antigravity result JSON
  ---
  duration_ms: 0.163305
  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.361974
  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: 71.967827
  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.328719
  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.768054
  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: 1.246172
  type: 'test'
  ...
# Subtest: stored output parsing filters Antigravity transcript tool items
ok 14 - stored output parsing filters Antigravity transcript tool items
  ---
  duration_ms: 1.384039
  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.699947
  type: 'test'
  ...
# Subtest: execution detail fallback filters Antigravity transcript tool rows
ok 16 - execution detail fallback filters Antigravity transcript tool rows
  ---
  duration_ms: 1.148069
  type: 'test'
  ...
# [2026-08-29 15:34:37.674 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-MVPEDU/020-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 2173.513316

[21/312] 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.633018
  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.291672
  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.168183
  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.149979
  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.466087
  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: 20.959128
  type: 'test'
  ...
# [2026-08-29 15:34:39.949 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-MVPEDU/021-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 1932.933186

[22/312] 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: 1600.932391
  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.569142
  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.788051
  type: 'test'
  ...
# Subtest: parseOpenCodeOutputToConversationResult separates structured assistant text parts
ok 4 - parseOpenCodeOutputToConversationResult separates structured assistant text parts
  ---
  duration_ms: 16.925147
  type: 'test'
  ...
# Subtest: detectStoredOutputFormat recognizes whole-document pretty OpenCode JSON
ok 5 - detectStoredOutputFormat recognizes whole-document pretty OpenCode JSON
  ---
  duration_ms: 1.688125
  type: 'test'
  ...
# Subtest: detectStoredOutputFormat recognizes OpenCode assistant parts without session identifiers
ok 6 - detectStoredOutputFormat recognizes OpenCode assistant parts without session identifiers
  ---
  duration_ms: 1.114607
  type: 'test'
  ...
# Subtest: detectStoredOutputFormat recognizes whole-document pretty Claude JSON arrays
ok 7 - detectStoredOutputFormat recognizes whole-document pretty Claude JSON arrays
  ---
  duration_ms: 1.031642
  type: 'test'
  ...
# [2026-08-29 15:34:42.054 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-MVPEDU/022-liveDetailsOpenCodeParser.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 15:34:42.082 +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 2172.490693

[23/312] 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 Function.from (<anonymous>)
#       at cancelActiveTasksForMergedPR (/home/runner/work/propr/propr/packages/api/webhookHandler.ts:236:27)
#       at async TestContext.<anonymous> (/home/runner/work/propr/propr/packages/api/test/mergedPrTaskCancellation.test.ts:94:19)
#       at async Test.run (node:internal/test_runner/test:1054:7)
#       at async Test.processPendingSubtests (node:internal/test_runner/test:744:7)
# }
# [webhook] Task task-a for merged PR acme/widgets\#42 was stopped but the cancellation was not durably recorded; the abort signal remains for the worker to observe
# [webhook] Merged PR acme/widgets\#42: cancelled 2/3 active task(s), 1 failed
# [webhook] Task task-a for merged PR acme/widgets\#42 was no longer active
# [webhook] Merged PR acme/widgets\#42: cancelled 2/3 active task(s), 1 no longer active
# [webhook] Merged PR acme/widgets\#42: cancelled 2/3 active task(s), 1 stopped without a durable record
# [webhook] Event received: pull_request, action: closed, repo: acme/widgets, delivery: delivery-mc22xizn3q
# [webhook] Merged PR acme/widgets\#42: cancelled 3/3 active task(s)
# [webhook] Event received: pull_request, action: closed, repo: acme/widgets, delivery: delivery-ykhb8n4btsi
# [webhook] Event received: pull_request, action: closed, repo: acme/widgets, delivery: delivery-yx9avvi4ai
# [webhook] Merge-triggered task cancellation failed {
#   repository: 'acme/widgets',
#   prNumber: 42,
#   error: Error: database unavailable
#       at Object.getActiveTasksForPR (/home/runner/work/propr/propr/packages/api/test/mergedPrTaskCancellation.test.ts:202:46)
#       at cancelActiveTasksForMergedPR (/home/runner/work/propr/propr/packages/api/webhookHandler.ts:187:50)
#       at handleWebhookRequest (/home/runner/work/propr/propr/packages/api/webhookHandler.ts:333:28)
#       at async TestContext.<anonymous> (/home/runner/work/propr/propr/packages/api/test/mergedPrTaskCancellation.test.ts:207:3)
#       at async Test.run (node:internal/test_runner/test:1054:7)
#       at async Test.processPendingSubtests (node:internal/test_runner/test:744:7)
# }
# Subtest: isMergedPullRequestClose only matches merged PR close events
ok 1 - isMergedPullRequestClose only matches merged PR close events
  ---
  duration_ms: 1.333103
  type: 'test'
  ...
# Subtest: cancelActiveTasksForMergedPR stops every active task and queued job with the merge reason
ok 2 - cancelActiveTasksForMergedPR stops every active task and queued job with the merge reason
  ---
  duration_ms: 2.929778
  type: 'test'
  ...
# Subtest: cancelActiveTasksForMergedPR does nothing when there is no active work
ok 3 - cancelActiveTasksForMergedPR does nothing when there is no active work
  ---
  duration_ms: 0.948557
  type: 'test'
  ...
# Subtest: cancelActiveTasksForMergedPR continues cancelling after a task fails to stop
ok 4 - cancelActiveTasksForMergedPR continues cancelling after a task fails to stop
  ---
  duration_ms: 3.843691
  type: 'test'
  ...
# Subtest: cancelActiveTasksForMergedPR counts no-longer-active tasks as skipped
ok 5 - cancelActiveTasksForMergedPR counts no-longer-active tasks as skipped
  ---
  duration_ms: 0.528445
  type: 'test'
  ...
# Subtest: cancelActiveTasksForMergedPR surfaces stops without a durable cancellation record separately
ok 6 - cancelActiveTasksForMergedPR surfaces stops without a durable cancellation record separately
  ---
  duration_ms: 0.48303
  type: 'test'
  ...
# Subtest: handleWebhookRequest cancels active tasks when a PR is merged
ok 7 - handleWebhookRequest cancels active tasks when a PR is merged
  ---
  duration_ms: 1.927591
  type: 'test'
  ...
# Subtest: handleWebhookRequest does not cancel anything for an unmerged PR close
ok 8 - handleWebhookRequest does not cancel anything for an unmerged PR close
  ---
  duration_ms: 0.607813
  type: 'test'
  ...
# Subtest: handleWebhookRequest still succeeds when the PR task lookup fails entirely
ok 9 - handleWebhookRequest still succeeds when the PR task lookup fails entirely
  ---
  duration_ms: 1.317094
  type: 'test'
  ...
# [2026-08-29 15:34:44.361 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-MVPEDU/023-mergedPrTaskCancellation.test.ts/propr.test.sqlite"
#     environment: "test"
1..9
# tests 9
# suites 0
# pass 9
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 2030.236863

[24/312] packages/api/test/notificationManagementRoutes.test.ts
TAP version 13
# Subtest: notification subscription management routes
    # Subtest: lists safe metadata and revokes an owned opaque subscription ID
    ok 1 - lists safe metadata and revokes an owned opaque subscription ID
      ---
      duration_ms: 3.254273
      type: 'test'
      ...
    # Subtest: sanitizes preference snapshots at both response boundaries
    ok 2 - sanitizes preference snapshots at both response boundaries
      ---
      duration_ms: 0.990055
      type: 'test'
      ...
    # Subtest: warns distinctly and without secrets for invalid VAPID configuration
    ok 3 - warns distinctly and without secrets for invalid VAPID configuration
      ---
      duration_ms: 1.7015
      type: 'test'
      ...
    1..3
ok 1 - notification subscription management routes
  ---
  duration_ms: 7.008477
  type: 'suite'
  ...
# [2026-08-29 15:34:46.535 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-MVPEDU/024-notificationManagementRoutes.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 15:34:46.537 +0000] �[32mINFO�[39m: �[36mSQLite database connection closed�[39m
1..1
# tests 3
# suites 1
# pass 3
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 2162.949693

[25/312] packages/api/test/notificationProjectionService.test.ts
TAP version 13
# Subtest: notification lifecycle projection
    # Subtest: creates exactly one plan-ready event for the draft owner
    ok 1 - creates exactly one plan-ready event for the draft owner
      ---
      duration_ms: 160.28907
      type: 'test'
      ...
    # Subtest: separates implementation, review, and sanitized PR-attention events
    ok 2 - separates implementation, review, and sanitized PR-attention events
      ---
      duration_ms: 154.286818
      type: 'test'
      ...
    # Subtest: ignores stale task transitions and emits one stalled event per unchanged activity
    ok 3 - ignores stale task transitions and emits one stalled event per unchanged activity
      ---
      duration_ms: 120.907705
      type: 'test'
      ...
    # Subtest: projects a task failure once without copying error details
    ok 4 - projects a task failure once without copying error details
      ---
      duration_ms: 121.425509
      type: 'test'
      ...
    # Subtest: does not advertise Open PR when a trusted GitHub URL cannot be constructed
    ok 5 - does not advertise Open PR when a trusted GitHub URL cannot be constructed
      ---
      duration_ms: 122.995694
      type: 'test'
      ...
    # Subtest: advertises follow-up only with the stored repository and issue identity the endpoint requires
    ok 6 - advertises follow-up only with the stored repository and issue identity the endpoint requires
      ---
      duration_ms: 148.296138
      type: 'test'
      ...
    # Subtest: advertises review follow-up only when the endpoint issue is the reviewed PR
    ok 7 - advertises review follow-up only when the endpoint issue is the reviewed PR
      ---
      duration_ms: 123.324948
      type: 'test'
      ...
    # Subtest: restricts indexing failures to administrators
    ok 8 - restricts indexing failures to administrators
      ---
      duration_ms: 131.906405
      type: 'test'
      ...
    # Subtest: deduplicates one unhealthy period and allows a later failure after recovery
    ok 9 - deduplicates one unhealthy period and allows a later failure after recovery
      ---
      duration_ms: 116.66493
      type: 'test'
      ...
    # Subtest: logs and isolates projection persistence failures
    ok 10 - logs and isolates projection persistence failures
      ---
      duration_ms: 101.870479
      type: 'test'
      ...
    1..10
ok 1 - notification lifecycle projection
  ---
  duration_ms: 1304.014889
  type: 'suite'
  ...
# [2026-08-29 15:34:48.847 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-MVPEDU/025-notificationProjectionService.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 15:34:50.148 +0000] �[32mINFO�[39m: �[36mSQLite database connection closed�[39m
1..1
# tests 10
# suites 1
# pass 10
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 4247.543444

[26/312] packages/api/test/notificationRoutes.test.ts
TAP version 13
# Subtest: notification routes
    # Subtest: uses only the authenticated user and clamps list limits
    ok 1 - uses only the authenticated user and clamps list limits
      ---
      duration_ms: 3.144348
      type: 'test'
      ...
    # Subtest: uses the authenticated user for receipt mutations
    ok 2 - uses the authenticated user for receipt mutations
      ---
      duration_ms: 1.382545
      type: 'test'
      ...
    # Subtest: returns 400 for malformed limits, cursors, and history flags
    ok 3 - returns 400 for malformed limits, cursors, and history flags
      ---
      duration_ms: 1.300172
      type: 'test'
      ...
    # Subtest: requires an authenticated identity even when handlers are called directly
    ok 4 - requires an authenticated identity even when handlers are called directly
      ---
      duration_ms: 0.306541
      type: 'test'
      ...
    # Subtest: returns Web Push capability without exposing private VAPID material
    ok 5 - returns Web Push capability without exposing private VAPID material
      ---
      duration_ms: 1.134874
      type: 'test'
      ...
    # Subtest: does not advertise invalid VAPID keys or a disabled dispatcher
    ok 6 - does not advertise invalid VAPID keys or a disabled dispatcher
      ---
      duration_ms: 1.242956
      type: 'test'
      ...
    # Subt

...(truncated)

View Workflow

…ing the user-facing product name and all security settings.

Implemented the Linux space-free package path while preserving the user-facing product name and all security settings.

Changes:

- Forge now emits `apps/desktop/out/propr-desktop-linux-x64` via [forge.config.ts](/tmp/git-processor/worktrees/integry/propr/pr-1967-followup-2026-08-29T15-42-24/apps/desktop/forge.config.ts:11).
- Smoke launches the exact space-free binary path in [smoke-packaged.mjs](/tmp/git-processor/worktrees/integry/propr/pr-1967-followup-2026-08-29T15-42-24/apps/desktop/scripts/smoke-packaged.mjs:20).
- CI configures the exact sandbox helper path without globbing in [desktop-release-guard.yml](/tmp/git-processor/worktrees/integry/propr/pr-1967-followup-2026-08-29T15-42-24/.github/workflows/desktop-release-guard.yml:62).
- Packaged ASAR metadata still contains `"productName":"ProPR Desktop"`.

Verification passed:

- Both audits: 0 vulnerabilities
- Clean `npm ci`: 0 vulnerabilities
- Desktop and renderer typecheck
- Desktop tests: 15/15
- Forge package
- Exact artifact and metadata assertions
- Hardened fuse validation
- `git diff --check`

The final local renderer-ready launch could not complete because this container has neither `sudo` nor `xvfb-run`; it stopped at Chromium’s expected root/setuid ownership check. Its diagnostic used the complete new space-free path without truncation. The CI workflow retains the required `chown root:root`, mode `4755`, Xvfb, and sandboxed renderer-ready/bridge proof.

PR: #1967
Comment by: @integry (ID: 5463320073)
Model: gpt-5.6-sol
@propr-dev

propr-dev Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor Author

Applied the requested follow-up changes in commit d1d4e32

Summary of Changes

Fixed the full-suite failure in test/orchestratorConfig.test.mjs.

The VAPID fixture now pads OpenSSL-generated private scalars to the required 32-byte width, preventing intermittent “malformed” results when a leading zero was omitted.

Validation:

  • Exact suite-runner test: 56/56 passed
  • git diff --check: passed
  • No commit created
  • Only the test fixture file was modified

🤖 Implementation Details

  • Model: GPT-5.6 Sol (high)
  • Requested By: @github-actions[bot]
  • Time: 3m 42s
  • Tokens: 897,229 (892,246 in / 4,983 out)
  • Cost: $0.97

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

…st.mjs](/tmp/git-processor/worktrees/integry/propr/pr-1967-followup-2026-08-29T15-50-52/test/orchestratorConfig.test.mjs:25).

Fixed the full-suite failure in [test/orchestratorConfig.test.mjs](/tmp/git-processor/worktrees/integry/propr/pr-1967-followup-2026-08-29T15-50-52/test/orchestratorConfig.test.mjs:25).

The VAPID fixture now pads OpenSSL-generated private scalars to the required 32-byte width, preventing intermittent “malformed” results when a leading zero was omitted.

Validation:

- Exact suite-runner test: 56/56 passed
- `git diff --check`: passed
- No commit created
- Only the test fixture file was modified

PR: #1967
Comment by: @github-actions[bot] (ID: 5463323066)
Model: gpt-5.6-sol
@integry

integry commented Aug 29, 2026

Copy link
Copy Markdown
Owner

/ultrafix goal=8 max=10

@propr-dev

propr-dev Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor Author

🔄 Ultrafix loop started (goal: 8/10, max cycles: 10)

First action: /review

💡 Tip: Remove the ultrafix label from this PR to stop further ultrafix cycles.

@integry

integry commented Aug 29, 2026

Copy link
Copy Markdown
Owner

/review

@propr-dev

propr-dev Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor Author

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 Author

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

Overall Evaluation

This PR establishes a thoughtful Electron scaffold with a reused web renderer, hardened packaging, encrypted persistence, and useful release verification. It is not ready to merge: three PR-introduced integration defects affect routed IPC, profile switching, and clean-checkout commands.

Strong Electron boundary — The renderer is sandboxed and isolated, permissions default to denied, navigation is constrained, and the frozen preload bridge exposes fixed operations rather than generic IPC or filesystem access.

Secure credential persistence — Credentials use safeStorage, permission-restricted files, validated identifiers, and explicitly refuse Linux’s insecure basic_text fallback.

Meaningful artifact verification — The passing desktop release guard checks audits, compilation, tests, fuse states, and an actual sandboxed packaged launch. The repository’s full suite remains pending.

Merge blockers

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

F1: 🔴 Hash routes invalidate trusted IPC

  • Required behavior: The reused hash-routed renderer must retain access to its narrow IPC contract while remaining on the trusted packaged document.
  • Evidence: apps/desktop/src/security.ts:48 — packaged senders are accepted only when the complete candidateUrl.href exactly equals packagedRendererUrl; after HashRouter navigates to propr-app://renderer/renderer.html#/..., that same document no longer matches and subsequent profile, credential, lifecycle, and external-browser IPC calls are rejected.
  • Minimum fix: Validate the packaged sender by its expected scheme, host, and renderer pathname while ignoring the hash-routing fragment and continuing to reject unexpected query parameters or documents. Add a routed-URL IPC regression test.

F2: 🔴 Reconnecting retains the first API endpoint

  • Required behavior: The active desktop profile must consistently determine the REST and Socket.IO endpoint; switching connections must not leave the shared renderer bound to a previous profile.
  • Evidence: propr-ui/src/desktop.tsx:193 — reconnecting calls loadDashboard(saved) again after the dashboard was merely unmounted at lines 200–201. The dynamic import('./App') reuses its cached module graph, whose runtime configuration and API constants were initialized for the first profile.
  • Minimum fix: Force a fresh renderer/module initialization after changing the active profile—such as reloading after setActive and letting startup load that profile—or refactor every REST and socket consumer to resolve the current configuration dynamically. Verify switching between two distinct URLs.

F3: 🔴 Dev and make skip preparation

  • Required behavior: The newly added desktop development and make scripts must work from a clean checkout, including building required workspace renderer dependencies.
  • Evidence: package.json:74 — desktop:dev invokes Forge directly, and package.json:80 does the same for desktop:make; unlike typecheck and package, neither runs desktop:prepare, although the renderer imports @propr/shared and its ignored dist output is required.
  • Minimum fix: Run desktop:prepare before the root desktop, desktop:dev, and desktop:make paths, including the DEB/RPM entrypoints, or add equivalent workspace pre-scripts.

Suggestions

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

S1: 🟢 Expand packaged smoke coverage

The smoke test validates only Linux while Forge configures Windows and macOS makers and platform-specific fuse paths. A later OS matrix would catch signing, executable-name, and packaging-layout differences; it is optional because the current release guard deliberately targets the Linux artifact.

S2: 🟢 Normalize IPv6 loopback hosts

LOOPBACK_HOSTS includes ::1, but WHATWG URL hostnames for IPv6 literals commonly retain brackets as [::1], so the intended loopback HTTP allowance may reject that form. Normalizing brackets and adding an IPv6 test would improve compatibility without affecting the current documented localhost flow.

Score

The security architecture and artifact guard are strong, but routed IPC, profile switching, and clean-checkout command behavior require correction before merge. The fixes are reasonably localized despite affecting core desktop flows.

Score: 6/10


🤖 Review Details

  • Model: GPT-5.6 Sol
  • Time: 5m 15s
  • Tokens: 61,670 (49,933 in / 11,737 out)
  • Cost: $0.56
  • Review scope: Partial — PR diff files or ranges were unavailable from GitHub or omitted by the configured review context limit.

View Task

Files omitted from review diff

1 file was omitted because patch content was unavailable from GitHub or did not fit the review context budget. Large, binary, generated, and lockfile changes are deprioritized.

  • package-lock.json

💡 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

/fix F1 F2 F3

@propr-dev

propr-dev Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor Author

Applied the requested follow-up changes in commit 9e9cd23

Addressed 1 AI review comment (IDs: 5463451312)

Summary of Changes

Implemented F1–F3 only.

  • security.ts: packaged renderer trust now allows hash routes while rejecting queries, alternate hosts, and documents.
  • desktop.tsx: activating a profile now reloads the renderer for fresh REST/socket module configuration.
  • package.json: dev, make, DEB, and RPM entrypoints prepare @propr/shared.
  • Added routed-IPC and two-endpoint switching regression tests.

Validation passed:

  • Desktop tests: 15/15
  • UI tests: 466/466
  • Desktop and UI typechecks
  • All preparation hooks
  • git diff --check

No commit created.


🤖 Implementation Details

  • Model: GPT-5.6 Sol (high)
  • Requested By: @integry
  • Time: 6m 11s
  • Tokens: 741,193 (731,258 in / 9,935 out)
  • Cost: $0.89
  • 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: 5463457877✓

Implemented F1–F3 only.

- [security.ts](/tmp/git-processor/worktrees/integry/propr/pr-1967-followup-2026-08-29T16-10-43/apps/desktop/src/security.ts): packaged renderer trust now allows hash routes while rejecting queries, alternate hosts, and documents.
- [desktop.tsx](/tmp/git-processor/worktrees/integry/propr/pr-1967-followup-2026-08-29T16-10-43/propr-ui/src/desktop.tsx): activating a profile now reloads the renderer for fresh REST/socket module configuration.
- [package.json](/tmp/git-processor/worktrees/integry/propr/pr-1967-followup-2026-08-29T16-10-43/apps/desktop/package.json): dev, make, DEB, and RPM entrypoints prepare `@propr/shared`.
- Added routed-IPC and two-endpoint switching regression tests.

Validation passed:

- Desktop tests: 15/15
- UI tests: 466/466
- Desktop and UI typechecks
- All preparation hooks
- `git diff --check`

No commit created.

PR: #1967
Comment by: @integry (ID: 5463457877)
Model: gpt-5.6-sol
@integry

integry commented Aug 29, 2026

Copy link
Copy Markdown
Owner

/review

@propr-dev

propr-dev Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor Author

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 Author

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

Overall Evaluation

This PR establishes a thoughtful Electron scaffold with strong process isolation, reusable UI integration, encrypted storage, hardened packaging, and Linux launch validation. It needs minor, localized corrections before merge: deep links can be lost during startup, desktop logout targets the wrong browser session, and concurrent credential writes can collide. The full suite is also still pending; dependency-resolution review is limited because package-lock.json was omitted.

Strong renderer boundary — The BrowserWindow enables context isolation and sandboxing, disables Node integration and webviews, validates IPC senders, and exposes only frozen, fixed-channel preload namespaces.

Verified packaged execution — The release guard audits before installation, builds renderer dependencies, packages the application, configures the Linux sandbox helper, verifies fuse states, and launches the packaged binary through Xvfb.

UI reuse without duplication — The desktop entry dynamically loads the existing dashboard after installing profile-specific runtime configuration and uses hash routing for the packaged custom-protocol origin.

Merge blockers

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

F4: 🔴 Startup deep links can be lost

  • Required behavior: The newly added typed deep-link contract and single-instance routing must reliably deliver validated links to the renderer.
  • Evidence: apps/desktop/src/main.ts:125 and apps/desktop/src/preload-bridge.ts:16did-finish-load sends and immediately clears the pending link, but the preload does not register its IPC listener until React later calls onDeepLink. IPC messages are not queued when no listener exists.
  • Minimum fix: Register the deep-link IPC listener eagerly in the preload and buffer messages until a renderer subscriber exists, or add a renderer-ready/acknowledgment handshake and clear pending links only after delivery. Cover initial launch and second-instance delivery while the renderer is loading.

F5: 🔴 Desktop logout uses the wrong session

  • Required behavior: Reusing the existing dashboard as the desktop renderer must preserve authentication behavior; an internal logout action must clear the Electron session rather than being converted into an external-browser navigation.
  • Evidence: apps/desktop/src/main.ts:116 — Every navigation outside the renderer URL is prevented and passed to shell.openExternal. The reused dashboard’s logout path navigates to the configured API logout URL, so it opens in the system browser, whose cookies are separate from Electron’s session.
  • Minimum fix: Add a desktop-aware logout path that requests the API logout endpoint with Electron renderer credentials and then uses hash-aware UI navigation to reach the login route. Add a test proving logout does not invoke external browsing and clears the active Electron authentication session.

F6: 🔴 Concurrent credential writes collide

  • Required behavior: The new encrypted credential persistence contract must handle valid concurrent IPC operations without spurious failures or inconsistent results.
  • Evidence: apps/desktop/src/profile-store.ts:187 — Every write for a profile uses the same PID-derived temporary filename, while writeCredential and removeCredential bypass the mutation queue used for profile state. Concurrent writes can overwrite or rename the same temporary file, causing one request to fail or persist an unexpected value.
  • Minimum fix: Serialize credential mutations per profile, or use unique temporary files together with defined last-write semantics and coordinated removals. Add concurrent write and write/remove tests.

Suggestions

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

S1: 🟢 Exercise other package targets

The Forge configuration declares Windows and macOS makers and contains platform-specific fuse-path behavior, while the release guard packages and launches only Linux. Adding Windows and macOS packaging jobs would catch platform-specific signing, executable-path, and protocol-registration problems. This is optional for this Linux-validated scaffold rather than a demonstrated current defect.

Score

The core architecture and Linux release validation are strong, but three localized correctness defects affect newly introduced deep-link, authentication, and credential-storage behavior. The pending full suite should also complete successfully after those corrections.

Score: 6/10


🤖 Review Details

  • Model: GPT-5.6 Sol
  • Time: 6m 15s
  • Tokens: 65,462 (51,626 in / 13,836 out)
  • Cost: $0.63
  • Review scope: Partial — PR diff files or ranges were unavailable from GitHub or omitted by the configured review context limit.

View Task

Files omitted from review diff

1 file was omitted because patch content was unavailable from GitHub or did not fit the review context budget. Large, binary, generated, and lockfile changes are deprioritized.

  • package-lock.json

💡 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

/fix F4 F5 F6

@propr-dev

propr-dev Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor Author

Applied the requested follow-up changes in commit 7c39c48

Addressed 1 AI review comment (IDs: 5463544757)

Summary of Changes

Implemented only F4, F5, and F6.

  • F4: Deep-link IPC now registers eagerly in preload and buffers links until React subscribes. Main-process pending links are queued, preserving startup and loading-time second-instance links.
  • F5: Desktop logout now uses the active Electron session with credentials and manual redirects, then navigates internally to #/login?logged_out=true without external browsing.
  • F6: Credential mutations are serialized per profile with deterministic invocation-order semantics, including profile removal.

Verification passed:

  • Desktop tests: 20/20
  • UI tests: 467/467
  • Desktop and UI typechecks
  • Electron Forge package
  • git diff --check

No commit was created.


🤖 Implementation Details

  • Model: GPT-5.6 Sol (high)
  • Requested By: @integry
  • Time: 9m 55s
  • Tokens: 2,921,870 (2,903,795 in / 18,075 out)
  • Cost: $2.53

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

Implemented only F4, F5, and F6.

- F4: Deep-link IPC now registers eagerly in preload and buffers links until React subscribes. Main-process pending links are queued, preserving startup and loading-time second-instance links.
- F5: Desktop logout now uses the active Electron session with credentials and manual redirects, then navigates internally to `#/login?logged_out=true` without external browsing.
- F6: Credential mutations are serialized per profile with deterministic invocation-order semantics, including profile removal.

Verification passed:

- Desktop tests: 20/20
- UI tests: 467/467
- Desktop and UI typechecks
- Electron Forge package
- `git diff --check`

No commit was created.

PR: #1967
Comment by: @integry (ID: 5463548749)
Model: gpt-5.6-sol
@integry

integry commented Aug 29, 2026

Copy link
Copy Markdown
Owner

/review

@propr-dev

propr-dev Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor Author

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 Author

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

Overall Evaluation

This PR establishes a well-structured Electron scaffold, but it needs significant changes before merge. The packaged renderer’s origin is incompatible with the existing API CORS boundary, development mode is blocked by its CSP, and deep links can be lost during window startup. The desktop release check has passed, while the full suite remains pending.

Strong renderer isolationcontextIsolation, sandboxing, disabled Node integration, permission denial, exact IPC channels, and sender-frame validation create a narrow security boundary.

Fail-closed credential storage — Credentials use safeStorage, restrictive file permissions, serialized writes, and refuse plaintext storage when encryption is unavailable or uses Linux basic_text.

Hardened packaging validation — Main/preload output extensions match their CommonJS format, Electron fuses are explicitly verified, and CI launches the packaged Linux binary without disabling its sandbox.

Merge blockers

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

F7: 🔴 Packaged renderer fails API CORS

  • Required behavior: The reused Web UI must operate as a functional desktop renderer; a selected profile must be able to make its existing REST and Socket.IO requests.
  • Evidence: apps/desktop/src/main.ts:22-25 — the packaged dashboard is assigned the origin propr-app://renderer, while the changed desktop entry directs the existing browser API and Socket.IO clients to the selected HTTP(S) endpoint. These credentialed browser requests carry a custom or opaque origin, but the supplied API CORS validator accepts only configured HTTP(S) frontend/domain origins and HTTP(S) loopback origins, so it rejects the desktop renderer.
  • Minimum fix: Establish an explicit desktop-origin request boundary accepted by both REST and Socket.IO—without broadly accepting arbitrary opaque origins—and add an integration check proving the packaged renderer can complete a request to a representative profile endpoint.

F8: 🔴 Development CSP blocks Vite

  • Required behavior: The newly added desktop development command must load the renderer while preserving the restrictive production CSP.
  • Evidence: apps/desktop/renderer.html:8script-src 'self' rejects inline scripts, while apps/desktop/vite.renderer.config.ts:16 enables the React Vite plugin, whose development-mode React Refresh preamble is injected as an inline module without a nonce or hash.
  • Minimum fix: Make the development CSP compatible with Vite’s injected preamble using a nonce/hash or a development-only policy, while retaining the current strict packaged policy. Verify that desktop:dev reaches a rendered UI.

F9: 🔴 Startup deep links can be lost

  • Required behavior: Accepted initial and second-instance protocol links must be delivered reliably to the renderer.
  • Evidence: apps/desktop/src/main.ts:127-130did-finish-load drains pendingDeepLinks, but the global mainWindow is not assigned until line 212. A link arriving after that drain but before assignment sees mainWindow === null, is appended to the queue, and is never drained again.
  • Minimum fix: Drain the queue again immediately after assigning the loaded window, or track the loading window directly so every queued link has a deterministic delivery point. Add a test covering a link received between did-finish-load and global window assignment.

Suggestions

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

S1: 🟢 Use renderer-owned readiness

The smoke test accepts desktop.renderer.ready after the main process merely confirms that the preload bridge exists. A renderer exception or failed React mount could therefore still produce a green result and a blank window. A renderer-emitted ready signal after DesktopRoot mounts would strengthen the release guard, but this is additional verification rather than a demonstrated runtime failure.

S2: 🟢 Complete IPv6 loopback support

security.ts attempts to allow ::1, but WHATWG IPv6 host serialization uses brackets, and neither CSP permits http://[::1]:* or ws://[::1]:*. Normalizing the bracketed form and extending the policy would support IPv6-only local installations; IPv4 and localhost remain available, so this is non-blocking.

S3: 🟢 Add native packaging coverage

The release guard validates only Linux even though the Forge configuration creates Windows and macOS artifacts. Adding periodic native package-and-launch jobs would catch signing, fuse-path, and maker-specific regressions, but the current Linux-focused guard satisfies the demonstrated release path.

Score

The security and packaging foundations are strong, but the packaged dashboard cannot currently cross the API CORS boundary, development rendering is blocked, and deep-link delivery contains a startup race. Those blockers outweigh the passing Linux release guard, with the full suite also still pending.

Score: 5/10


🤖 Review Details

  • Model: GPT-5.6 Sol
  • Time: 8m 59s
  • Tokens: 71,447 (51,468 in / 19,979 out)
  • Cost: $0.80
  • Review scope: Partial — PR diff files or ranges were unavailable from GitHub or omitted by the configured review context limit.

View Task

Files omitted from review diff

1 file was omitted because patch content was unavailable from GitHub or did not fit the review context budget. Large, binary, generated, and lockfile changes are deprioritized.

  • package-lock.json

💡 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

/fix F7 F8 F9

@propr-dev

propr-dev Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor Author

Applied the requested follow-up changes in commit 7a26a6b

Addressed 1 AI review comment (IDs: 5463665170)

Summary of Changes

Implemented only F7, F8, and F9.

  • F7: Added an exact shared propr-app://renderer CORS boundary for REST and Socket.IO, enabled CORS on the registered scheme, and extended the packaged smoke test with a real credentialed renderer request.
  • F8: Added development-only inline-script CSP support and corrected Vite’s shared renderer entry resolution. Packaged output retains strict script-src 'self'.
  • F9: Added deterministic deep-link queue coordination and regression coverage for the post-load/pre-assignment race.

Verification passed:

  • Desktop tests: 22/22
  • API CORS tests: 12/12
  • Desktop, UI, and API typechecks
  • API lint
  • Desktop profile UI test
  • Forge packaging
  • Vite development HTML and renderer module loading

The packaged launch smoke was attempted but this container cannot configure the Chromium helper as root-owned mode 4755 or provide Xvfb. The existing release workflow provisions both before running the enhanced smoke test.


🤖 Implementation Details

  • Model: GPT-5.6 Sol (high)
  • Requested By: @integry
  • Time: 10m 58s
  • Tokens: 3,062,648 (3,039,671 in / 22,977 out)
  • Cost: $2.88

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

Implemented only F7, F8, and F9.

- F7: Added an exact shared `propr-app://renderer` CORS boundary for REST and Socket.IO, enabled CORS on the registered scheme, and extended the packaged smoke test with a real credentialed renderer request.
- F8: Added development-only inline-script CSP support and corrected Vite’s shared renderer entry resolution. Packaged output retains strict `script-src 'self'`.
- F9: Added deterministic deep-link queue coordination and regression coverage for the post-load/pre-assignment race.

Verification passed:

- Desktop tests: 22/22
- API CORS tests: 12/12
- Desktop, UI, and API typechecks
- API lint
- Desktop profile UI test
- Forge packaging
- Vite development HTML and renderer module loading

The packaged launch smoke was attempted but this container cannot configure the Chromium helper as root-owned mode `4755` or provide Xvfb. The existing release workflow provisions both before running the enhanced smoke test.

PR: #1967
Comment by: @integry (ID: 5463673605)
Model: gpt-5.6-sol
@integry

integry commented Aug 29, 2026

Copy link
Copy Markdown
Owner

/review

Independent post-fix checks passed at head 7a26a6b: diff check; desktop + renderer typecheck; 22/22 Electron lifecycle/security/deep-link tests; and 12/12 API CORS tests including exact packaged origin and Socket.IO parity. Please verify F7–F9 end to end, especially that production CSP remains strict, development relaxation is serve-only, the custom-origin exception is exact, and links cannot strand across any window-load ordering.

@propr-dev

propr-dev Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor Author

AI Code Review Complete requested by @integry

Posted 1 review:

View Task Details

@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/314] 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.22715
      type: 'test'
      ...
    1..1
ok 1 - desktop deep-link delivery
  ---
  duration_ms: 2.033916
  type: 'suite'
  ...
1..1
# tests 1
# suites 1
# pass 1
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 235.292946

[2/314] 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: 22.85774
      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.732114
      type: 'test'
      ...
    1..2
ok 1 - desktop session IPC operations
  ---
  duration_ms: 24.957995
  type: 'suite'
  ...
1..1
# tests 2
# suites 1
# pass 2
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 268.399331

[3/314] 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.806734
      type: 'test'
      ...
    # Subtest: maps profile and credential operations to fixed channels
    ok 2 - maps profile and credential operations to fixed channels
      ---
      duration_ms: 0.581517
      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.337859
      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.294925
      type: 'test'
      ...
    1..4
ok 1 - desktop preload bridge
  ---
  duration_ms: 4.242076
  type: 'suite'
  ...
1..1
# tests 4
# suites 1
# pass 4
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 237.28944

[4/314] 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: 12.035254
      type: 'test'
      ...
    # Subtest: encrypts credentials before writing app-owned storage
    ok 2 - encrypts credentials before writing app-owned storage
      ---
      duration_ms: 5.576647
      type: 'test'
      ...
    # Subtest: serializes concurrent credential writes with last-write semantics
    ok 3 - serializes concurrent credential writes with last-write semantics
      ---
      duration_ms: 6.138269
      type: 'test'
      ...
    # Subtest: orders concurrent credential writes and removals by invocation
    ok 4 - orders concurrent credential writes and removals by invocation
      ---
      duration_ms: 7.314687
      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: 3.206685
      type: 'test'
      ...
    # Subtest: rejects unsafe endpoints and path-like profile identifiers
    ok 6 - rejects unsafe endpoints and path-like profile identifiers
      ---
      duration_ms: 1.55205
      type: 'test'
      ...
    1..6
ok 1 - desktop profile store
  ---
  duration_ms: 37.344263
  type: 'suite'
  ...
1..1
# tests 6
# suites 1
# pass 6
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 268.323867

[5/314] 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.167701
      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.2455
      type: 'test'
      ...
    # Subtest: requires an exact loopback development origin
    ok 3 - requires an exact loopback development origin
      ---
      duration_ms: 0.262035
      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.246722
      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.250287
      type: 'test'
      ...
    # Subtest: publishes a restrictive production policy
    ok 6 - publishes a restrictive production policy
      ---
      duration_ms: 0.295035
      type: 'test'
      ...
    # Subtest: relaxes inline scripts only while Vite serves the development renderer
    ok 7 - relaxes inline scripts only while Vite serves the development renderer
      ---
      duration_ms: 0.263508
      type: 'test'
      ...
    1..7
ok 1 - desktop URL security
  ---
  duration_ms: 3.921332
  type: 'suite'
  ...
1..1
# tests 7
# suites 1
# pass 7
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 251.482883

[6/314] 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.275183
      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.145099
      type: 'test'
      ...
    1..2
ok 1 - desktop BrowserWindow security
  ---
  duration_ms: 2.32191
  type: 'suite'
  ...
1..1
# tests 2
# suites 1
# pass 2
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 224.833408

[7/314] 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.791368
      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.275442
      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.212255
      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.647105
      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.43141
      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.707454
      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.646855
      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.670441
      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.617721
      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.53048
      type: 'test'
      ...
    1..10
ok 1 - agent login session manager
  ---
  duration_ms: 12.825646
  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.76963
      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.599804
      type: 'test'
      ...
    # Subtest: rejects interactive login for an unsupported agent type
    ok 3 - rejects interactive login for an unsupported agent type
      ---
      duration_ms: 0.439042
      type: 'test'
      ...
    1..3
ok 2 - agent login routes
  ---
  duration_ms: 1.962268
  type: 'suite'
  ...
# [2026-08-29 17:08:42.433 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-figvl4/007-agentLoginRoutes.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 17:08:42.444 +0000] �[32mINFO�[39m: �[36mSQLite database connection closed�[39m
# [2026-08-29 17:08:42.444 +0000] �[32mINFO�[39m: �[36mShutting down queue...�[39m
# [2026-08-29 17:08:42.444 +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 2271.795119

[8/314] 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.987982
      type: 'test'
      ...
    # Subtest: persists a failed state when queue submission fails
    ok 2 - persists a failed state when queue submission fails
      ---
      duration_ms: 0.868159
      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.787858
      type: 'test'
      ...
    # Subtest: times out slow runtime package validation before queueing
    ok 4 - times out slow runtime package validation before queueing
      ---
      duration_ms: 6.180673
      type: 'test'
      ...
    # Subtest: returns package suggestions from configured runtimes
    ok 5 - returns package suggestions from configured runtimes
      ---
      duration_ms: 0.857493
      type: 'test'
      ...
    # Subtest: enforces PROPR_ADMIN_USERS when configured
    ok 6 - enforces PROPR_ADMIN_USERS when configured
      ---
      duration_ms: 0.283297
      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.305741
      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.348927
      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.472463
      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.17049
      type: 'test'
      ...
    # Subtest: resolves the runtime build queue lazily when queueing
    ok 11 - resolves the runtime build queue lazily when queueing
      ---
      duration_ms: 0.949361
      type: 'test'
      ...
    # Subtest: reports apply load failures through the route response
    ok 12 - reports apply load failures through the route response
      ---
      duration_ms: 0.474656
      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.452783
      type: 'test'
      ...
    # Subtest: rejects runtime verification without runtime-management permission
    ok 14 - rejects runtime verification without runtime-management permission
      ---
      duration_ms: 0.244779
      type: 'test'
      ...
    1..14
ok 1 - agent runtime package routes
  ---
  duration_ms: 20.939442
  type: 'suite'
  ...
# [2026-08-29 17:08:44.407 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-figvl4/008-agentRuntimeRoutes.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 17:08:44.425 +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 1977.969262

[9/314] 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: 6.933753
  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.071115
  type: 'test'
  ...
# Subtest: temporary cleanup refuses paths outside its configured root
ok 3 - temporary cleanup refuses paths outside its configured root
  ---
  duration_ms: 0.683791
  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.509168
  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.809297
  type: 'test'
  ...
# [2026-08-29 17:08:46.587 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-figvl4/009-attachmentUploadCleanup.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 17:08:46.602 +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 2053.879249

[10/314] 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.407309
  type: 'test'
  ...
# Subtest: ensureAuthenticated refreshes an expired GitHub token before continuing
ok 2 - ensureAuthenticated refreshes an expired GitHub token before continuing
  ---
  duration_ms: 5.422204
  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.500421
  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.824809
  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.698152
  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.889971
  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.113048
  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.006597
  type: 'test'
  ...
# [2026-08-29 17:08:49.239 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-figvl4/010-authGithubTokens.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 17:08:49.258 +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 2531.161995

[11/314] 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.156811
  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.674951
  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.701047
  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.742242
  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.152476
  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.845172
  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: 5.955549
  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: 4.946627
  type: 'test'
  ...
# Subtest: auth redirect allowlist permits configured local IP literals
ok 9 - auth redirect allowlist permits configured local IP literals
  ---
  duration_ms: 5.264927
  type: 'test'
  ...
# Subtest: auth redirect allowlist ignores malformed additional host entries
ok 10 - auth redirect allowlist ignores malformed additional host entries
  ---
  duration_ms: 5.014349
  type: 'test'
  ...
# [2026-08-29 17:08:51.821 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-figvl4/011-authRedirect.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 17:08:51.907 +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 2452.420716

[12/314] 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: 1501.672143
  type: 'test'
  ...
# [2026-08-29 17:08:53.998 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-figvl4/012-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 2055.000989

[13/314] 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: 4.773005
  type: 'test'
  ...
# Subtest: API config subscription reloads repository updates
ok 2 - API config subscription reloads repository updates
  ---
  duration_ms: 0.466773
  type: 'test'
  ...
# Subtest: API config subscription ignores unrelated and malformed events
ok 3 - API config subscription ignores unrelated and malformed events
  ---
  duration_ms: 0.292671
  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.430519
  type: 'test'
  ...
1..4
# tests 4
# suites 0
# pass 4
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 246.757129

[14/314] packages/api/test/configRoutesIndexingSummarization.test.ts
TAP version 13
# [2026-08-29 17:08:56.527 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-figvl4/014-configRoutesIndexingSummarization.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 17:08:56.552 +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: 1754.154631
  type: 'test'
  ...
# Subtest: postSummarizationSettings rejects enabled summarization without a primary alias
ok 2 - postSummarizationSettings rejects enabled summarization without a primary alias
  ---
  duration_ms: 3.210569
  type: 'test'
  ...
# [2026-08-29 17:08:56.788 +0000] �[32mINFO�[39m: �[36mDatabase migrations completed successfully�[39m
# [2026-08-29 17:08:56.789 +0000] �[32mINFO�[39m: �[36mSuccessfully loaded agents configuration�[39m
#     agentCount: 2
# [2026-08-29 17:08:56.790 +0000] �[32mINFO�[39m: �[36mSuccessfully loaded summarization settings�[39m
#     summarization: {
#       "enabled": false,
#       "agent_alias": "",
#       "fallback_agent_alias": "",
#       "custom_prompt": ""
#     }
# [2026-08-29 17:08:56.791 +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 17:08:56.792 +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 17:08:56.801 +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 2041.835687

[15/314] 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.391487
  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.237438
  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.276958
  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.337298
  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.15291
  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.128183
  type: 'test'
  ...
# Subtest: explicit custom GitHub web auth remains supported
ok 7 - explicit custom GitHub web auth remains supported
  ---
  duration_ms: 0.170678
  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.170788
  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.526674
  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: 26.19164
  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.803241
  type: 'test'
  ...
1..11
# tests 11
# suites 0
# pass 11
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 293.070685

[16/314] packages/api/test/connectAuthCallback.test.ts
TAP version 13
# [2026-08-29 17:08:59.541 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-figvl4/016-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: 3040.438811
  type: 'test'
  ...
# [2026-08-29 17:09:02.578 +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 5483.518383

[17/314] 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: 0.999767
  type: 'test'
  ...
# Subtest: CORS rejects unrelated origins under proxy mode
ok 2 - CORS rejects unrelated origins under proxy mode
  ---
  duration_ms: 0.234793
  type: 'test'
  ...
# Subtest: CORS allows requests with no origin
ok 3 - CORS allows requests with no origin
  ---
  duration_ms: 0.120492
  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.14597
  type: 'test'
  ...
# Subtest: CORS allows localhost for development
ok 5 - CORS allows localhost for development
  ---
  duration_ms: 0.235345
  type: 'test'
  ...
# Subtest: CORS rejects non-http(s) localhost schemes
ok 6 - CORS rejects non-http(s) localhost schemes
  ---
  duration_ms: 0.137748
  type: 'test'
  ...
# Subtest: CORS allows COOKIE_DOMAIN subdomains for preview environments
ok 7 - CORS allows COOKIE_DOMAIN subdomains for preview environments
  ---
  duration_ms: 0.19192
  type: 'test'
  ...
# Subtest: CORS preserves http COOKIE_DOMAIN preview compatibility
ok 8 - CORS preserves http COOKIE_DOMAIN preview compatibility
  ---
  duration_ms: 0.115925
  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.428086
  type: 'test'
  ...
# Subtest: Express CORS boundary is sanitized in development mode
ok 10 - Express CORS boundary is sanitized in development mode
  ---
  duration_ms: 64.357194
  type: 'test'
  ...
# Subtest: Express CORS boundary is sanitized in production mode
ok 11 - Express CORS boundary is sanitized in production mode
  ---
  duration_ms: 26.105052
  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: 7.301407
  type: 'test'
  ...
1..12
# tests 12
# suites 0
# pass 12
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 556.012321

[18/314] 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: 5.923139
  type: 'test'
  ...
# Subtest: demoModeReadOnlyMiddleware blocks auth metadata mutations
ok 2 - demoModeReadOnlyMiddleware blocks auth metadata mutations
  ---
  duration_ms: 1.933174
  type: 'test'
  ...
# Subtest: isDemoMode accepts common truthy environment values
ok 3 - isDemoMode accepts common truthy environment values
  ---
  duration_ms: 0.990613
  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.533993
  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.408629
  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: 54.999128
  type: 'test'
  ...
# Subtest: ensureAuthenticated attaches the synthetic demo user
ok 7 - ensureAuthenticated attaches the synthetic demo user
  ---
  duration_ms: 1.207039
  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.290355
  type: 'test'
  ...
# Subtest: demo repository metadata resolves enabled configured repositories
ok 9 - demo repository metadata resolves enabled configured repositories
  ---
  duration_ms: 9.600933
  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.088308
  type: 'test'
  ...
# [2026-08-29 17:09:05.913 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-figvl4/018-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: 258.126601
  type: 'test'
  ...
# [2026-08-29 17:09:06.247 +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 17:09:06.249 +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.939423
  type: 'test'
  ...
# Subtest: demo repository metadata ignores malformed database repository names
ok 13 - demo repository metadata ignores malformed database repository names
  ---
  duration_ms: 1.457127
  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: 7.620237
  type: 'test'
  ...
# [2026-08-29 17:09:06.259 +0000] �[32mINFO�[39m: �[36mSuccessfully loaded all monitored repositories�[39m
#     total_repos: 0
# [2026-08-29 17:09:06.268 +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.365796
  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: 14.067616
  type: 'test'
  ...
1..16
# tests 16
# suites 0
# pass 16
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 2618.054067

[19/314] 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.921239
  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.150767
  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: 68.506169
  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: 3.480386
  type: 'test'
  ...
1..4
# tests 4
# suites 0
# pass 4
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 896.344512

[20/314] 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.473885
      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.471089
      type: 'test'
      ...
    # Subtest: grants full admin permissions to PROPR_ADMIN_USERS
    ok 3 - grants full admin permissions to PROPR_ADMIN_USERS
      ---
      duration_ms: 2.365456
      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.544715
      type: 'test'
      ...
    # Subtest: resolves demo users without installation permissions
    ok 5 - resolves demo users without installation permissions
      ---
      duration_ms: 2.062408
      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.96407
      type: 'test'
      ...
    # Subtest: current-user responses expose capabilities without OAuth credentials
    ok 7 - current-user responses expose capabilities without OAuth credentials
      ---
      duration_ms: 2.493358
      type: 'test'
      ...
    1..7
ok 1 - instance authorization
  ---
  duration_ms: 27.574028
  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.021774
      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: 5.702344
      type: 'test'
      ...
    # Subtest: adds explicit members and writes an audit entry
    ok 3 - adds explicit members and writes an audit entry
      ---
      duration_ms: 4.774366
      type: 'test'
      ...
    # Subtest: keeps environment authority separate from durable role assignments
    ok 4 - keeps environment authority separate from durable role assignments
      ---
      duration_ms: 3.100593
      type: 'test'
      ...
    # Subtest: prevents removing the last durable administrator
    ok 5 - prevents removing the last durable administrator
      ---
      duration_ms: 4.286541
      type: 'test'
      ...
    # Subtest: allows role changes once another administrator exists
    ok 6 - allows role changes once another administrator exists
      ---
      duration_ms: 9.230081
      type: 'test'
      ...
    1..6
ok 2 - instance member service
  ---
  duration_ms: 30.504284
  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: 1.971762
      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.638189
      type: 'test'
      ...
    1..2
ok 3 - instance catalog
  ---
  duration_ms: 3.693486
  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.543343
      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.312237
      type: 'test'
      ...
    # Subtest: rejects GitHub usernames with trailing or consecutive hyphens
    ok 3 - rejects GitHub usernames with trailing or consecutive hyphens
      ---
      duration_ms: 1.629787
      type: 'test'
      ...
    1..3
ok 4 - instance admin routes
  ---
  duration_ms: 5.59954
  type: 'suite'
  ...
# [2026-08-29 17:09:09.160 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-figvl4/020-instanceAuthorization.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 17:09:09.222 +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 2047.270806

[21/314] 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.78954
  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.133271
  type: 'test'
  ...
# Subtest: an explicit API listen host overrides environment defaults
ok 3 - an explicit API listen host overrides environment defaults
  ---
  duration_ms: 0.139991
  type: 'test'
  ...
1..3
# tests 3
# suites 0
# pass 3
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 234.289658

[22/314] 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.090851
  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.330915
  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.837772
  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.218519
  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.28521
  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.329877
  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.228304
  type: 'test'
  ...
# Subtest: stored output detection recognizes truncated Antigravity result JSON
ok 8 - stored output detection recognizes truncated Antigravity result JSON
  ---
  duration_ms: 0.159381
  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.34518
  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: 62.089829
  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.807875
  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.386339
  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.969271
  type: 'test'
  ...
# Subtest: stored output parsing filters Antigravity transcript tool items
ok 14 - stored output parsing filters Antigravity transcript tool items
  ---
  duration_ms: 1.113679
  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.358688
  type: 'test'
  ...
# Subtest: execution detail fallback filters Antigravity transcript tool rows
ok 16 - execution detail fallback filters Antigravity transcript tool rows
  ---
  duration_ms: 1.112467
  type: 'test'
  ...
# [2026-08-29 17:09:11.742 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-figvl4/022-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 2029.343211

[23/314] 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.177967
  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.259652
  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.155184
  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.143537
  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.397198
  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.403419
  type: 'test'
  ...
# [2026-08-29 17:09:13.888 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-figvl4/023-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 1826.96299

[24/314] 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: 1440.988726
  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.309765
  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.494993
  type: 'test'
  ...
# Subtest: parseOpenCodeOutputToConversationResult separates structured assistant text parts
ok 4 - parseOpenCodeOutputToConversationResult separates structured assistant text parts
  ---
  duration_ms: 14.858649
  type: 'test'
  ...
# Subtest: detectStoredOutputFormat recognizes whole-document pretty OpenCode JSON
ok 5 - detectStoredOutputFormat recognizes whole-document pretty OpenCode JSON
  ---
  duration_ms: 1.36003
  type: 'test'
  ...
# Subtest: detectStoredOutputFormat recognizes OpenCode assistant parts without session identifiers
ok 6 - detectStoredOutputFormat recognizes OpenCode assistant parts without session identifiers
  ---
  duration_ms: 0.963132
  type: 'test'
  ...
# Subtest: detectStoredOutputFormat recognizes whole-document pretty Claude JSON arrays
ok 7 - detectStoredOutputFormat recognizes whole-document pretty Claude JSON arrays
  ---
  duration_ms: 0.830142
  type: 'test'
  ...
# [2026-08-29 17:09:15.825 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-figvl4/024-liveDetailsOpenCodeParser.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 17:09:15.850 +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 2017.149522

[25/314] 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 Function.from (<anonymous>)
#       at cancelActiveTasksForMergedPR (/home/runner/work/propr/propr/packages/api/webhookHandler.ts:236:27)
#       at async TestContext.<anonymous> (/home/runner/work/propr/propr/packages/api/test/mergedPrTaskCancellation.test.ts:94:19)
#       at async Test.run (node:internal/test_runner/test:1054:7)
#       at async Test.processPendingSubtests (node:internal/test_runner/test:744:7)
# }
# [webhook] Task task-a for merged PR acme/widgets\#42 was stopped but the cancellation was not durably recorded; the abort signal remains for the worker to observe
# [webhook] Merged PR acme/widgets\#42: cancelled 2/3 active task(s), 1 failed
# [webhook] Task task-a for merged PR acme/widgets\#42 was no longer active
# [webhook] Merged PR acme/widgets\#42: cancelled 2/3 active task(s), 1 no longer active
# [webhook] Merged PR acme/widgets\#42: cancelled 2/3 active task(s), 1 stopped without a durable record
# [webhook] Event received: pull_request, action: closed, repo: acme/widgets, delivery: delivery-7nfb83m13gg
# [webhook] Merged PR acme/widgets\#42: cancelled 3/3 active task(s)
# [webhook] Event received: pull_request, action: closed, repo: acme/widgets, delivery: delivery-3y5njnh2wc5
# [webhook] Event received: pull_request, action: closed, repo: acme/widgets, delivery: delivery-tsowdmy4ovq
# [webhook] Merge-triggered task cancellation failed {
#   repository: 'acme/widgets',
#   prNumber: 42,
#   error: Error: database unavailable
#       at Object.getActiveTasksForPR (/home/runner/work/propr/propr/packages/api/test/mergedPrTaskCancellation.test.ts:202:46)
#       at cancelActiveTasksForMergedPR (/home/runner/work/propr/propr/packages/api/webhookHandler.ts:187:50)
#       at handleWebhookRequest (/home/runner/work/propr/propr/packages/api/webhookHandler.ts:333:28)
#       at async TestContext.<anonymous> (/home/runner/work/propr/propr/packages/api/test/mergedPrTaskCancellation.test.ts:207:3)
#       at async Test.run (node:internal/test_runner/test:1054:7)
#       at async Test.processPendingSubtests (node:internal/test_runner/test:744:7)
# }
# Subtest: isMergedPullRequestClose only matches merged PR close events
ok 1 - isMergedPullRequestClose only matches merged PR close events
  ---
  duration_ms: 1.16117
  type: 'test'
  ...
# Subtest: cancelActiveTasksForMergedPR stops every active task and queued job with the merge reason
ok 2 - cancelActiveTasksForMergedPR stops every active task and queued job with the merge reason
  ---
  duration_ms: 2.503824
  type: 'test'
  ...
# Subtest: cancelActiveTasksForMergedPR does nothing when there is no active work
ok 3 - cancelActiveTasksForMergedPR does nothing when there is no active work
  ---
  duration_ms: 0.838083
  type: 'test'
  ...
# Subtest: cancelActiveTasksForMergedPR continues cancelling after a task fails to stop
ok 4 - cancelActiveTasksForMergedPR continues cancelling after a task fails to stop
  ---
  duration_ms: 3.441878
  type: 'test'
  ...
# Subtest: cancelActiveTasksForMergedPR counts no-longer-active tasks as skipped
ok 5 - cancelActiveTasksForMergedPR counts no-longer-active tasks as skipped
  ---
  duration_ms: 0.442226
  type: 'test'
  ...
# Subtest: cancelActiveTasksForMergedPR surfaces stops without a durable cancellation record separately
ok 6 - cancelActiveTasksForMergedPR surfaces stops without a durable cancellation record separately
  ---
  duration_ms: 0.343578
  type: 'test'
  ...
# Subtest: handleWebhookRequest cancels active tasks when a PR is merged
ok 7 - handleWebhookRequest cancels active tasks when a PR is merged
  ---
  duration_ms: 1.695476
  type: 'test'
  ...
# Subtest: handleWebhookRequest does not cancel anything for an unmerged PR close
ok 8 - handleWebhookRequest does not cancel anything for an unmerged PR close
  ---
  duration_ms: 0.278069
  type: 'test'
  ...
# Subtest: handleWebhookRequest still succeeds when the PR task lookup fails entirely
ok 9 - handleWebhookRequest still succeeds when the PR task lookup fails entirely
  ---
  duration_ms: 0.982851
  type: 'test'
  ...
# [2026-08-29 17:09:17.978 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-figvl4/025-mergedPrTaskCancellation.test.ts/propr.test.sqlite"
#     environment: "test"
1..9
# tests 9
# suites 0
# pass 9
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 2008.567238

[26/314] packages/api/test/notificationManagementRoutes.test.ts
TAP version 13
# Subtest: notification subscription management routes
    # Subtest: lists safe metadata and revokes an owned opaque subscription ID
    ok 1 - lists safe metadata and revokes an owned opaque subscription ID
      ---
      duration_ms: 2.866691
      type: 'test'
      ...
    # Subtest: sanitizes preference snapshots at both response boundaries
    ok 2 - sanitizes preference snapshots at both response boundaries
      ---
      duration_ms: 0.874969
      type: 'test'
      ...
    # Subtest: warns distinctly and without secrets for invalid VAPID configuration
    ok 3 - warns distinctly and without secrets for invalid VAPID configuration
      ---
      duration_ms: 1.630208
      type: 'test'
      ...
    1..3
ok 1 - notification subscription management routes
  ---
  duration_ms: 6.3158
  type: 'suite'
  ...
# [2026-08-29 17:09:20.144 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-figvl4/026-notificationManagementRoutes.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 17:09:20.145 +0000] �[32mINFO�[39m: �[36mSQLite database connection closed�[39m
1..1
# tests 3
# suites 1
# pass 3
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 2038.541536

[27/314] packages/api/test/notificationProjectionService.test.ts
TAP version 13
# Subtest: notification lifecycle projection
    # Subtest: creates exactly one plan-ready event for the draft owner
    ok 1 - creates exactly one plan-ready event for the draft owner
      ---
      duration_ms: 148.444609
      type: 'test'
      ...
    # Subtest: separates implementation, review, and sanitized PR-attention events
    ok 2 - separates implementation, review, and sanitized PR-attention events
      ---
      duration_ms: 150.229279
      type: 'test'
      ...
    # Subtest: ignores stale task transitions and emits one stalled event per unchanged activity
    ok 3 - ignores stale task transitions and emits one stalled event per unchanged activity
      ---
      duration_ms: 115.374075
      type: 'test'
      ...
    # Subtest: projects a task failure once without copying error details
    ok 4 - projects a task failure once without copying error details
      ---
      duration_ms: 110.464204
      type: 'test'
      ...
    # Subtest: does not advertise Open PR when a trusted GitHub URL cannot be constructed
    ok 5 - does not advertise Open PR when a trusted GitHub URL cannot be constructed
      ---
      duration_ms: 117.340118
      type: 'test'
      ...
    # Subtest: advertises follow-up only with the stored repository and issue identity the endpoint requires
    ok 6 - advertises follow-up only with the stored repository and issue identity the endpoint requires
      ---
      duration_ms: 123.83015
      type: 'test'
      ...
    # Subtest: advertises review follow-up only when the endpoint

...(truncated)

View Workflow

@propr-dev

propr-dev Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor Author

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

Overall Evaluation

The PR delivers a well-structured, security-conscious Electron scaffold that reuses the existing React UI. It is ready to merge within the reviewed source scope, conditional on the two pending head checks completing successfully.

Strict production CSPrenderer.html retains script-src 'self', while the inline-script relaxation is confined to the Vite plugin’s apply: 'serve' transformation and unpackaged development responses.

Exact desktop-origin handlingpackages/api/corsValidation.ts compares against propr-app://renderer exactly, rejects opaque null and lookalike origins, and supplies the same validator to REST and Socket.IO.

Complete deep-link handoffDeepLinkDelivery flushes at both load completion and window assignment, while the preload eagerly buffers events until the renderer subscribes, covering the previously vulnerable ordering interval.

Merge blockers

No merge blockers.

Suggestions

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

S1: 🟢 Test effective packaged CSP

The current tests verify generated policy strings, while the packaged smoke test verifies launch and bridge exposure. An optional smoke assertion that an injected inline script is actually blocked would protect against future packaging or protocol-header changes that alter the effective policy.

S2: 🟢 Smoke-test WebSocket transport

packages/api/test/corsValidation.test.ts exercises the Socket.IO CORS path using polling, whereas SocketProvider.tsx forces WebSocket transport. The same origin callback applies to both, so this is not a blocker, but a packaged-renderer WebSocket handshake would provide stronger end-to-end regression coverage.

Score

The reviewed source changes satisfy the objective with strong security boundaries and focused tests. The omitted lockfile and two pending authoritative checks keep this just short of an unconditional top-tier assessment.

Score: 8/10


🤖 Review Details

  • Model: GPT-5.6 Sol
  • Time: 6m 7s
  • Tokens: 68,872 (55,547 in / 13,325 out)
  • Cost: $0.62
  • Review scope: Partial — PR diff files or ranges were unavailable from GitHub or omitted by the configured review context limit.

View Task

Files omitted from review diff

1 file was omitted because patch content was unavailable from GitHub or did not fit the review context budget. Large, binary, generated, and lockfile changes are deprioritized.

  • package-lock.json

💡 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

@propr-dev

propr-dev Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor Author

Applied the requested follow-up changes in commit 5b73b8f

Summary of Changes

Fixed the flaky full-suite failure in webPushDispatcher.test.ts.

The fixture now uses a stable historical timestamp, preventing SQLite wall-clock comparisons from intermittently hiding claimable delivery jobs.

Validation passed:

  • Dispatcher suite: 20 consecutive runs, 340/340 tests
  • Notification suites: 42/42 tests
  • API typecheck
  • API lint
  • git diff --check

Only the intended test file changed; no commit was created.


🤖 Implementation Details

  • Model: GPT-5.6 Sol (high)
  • Requested By: @github-actions[bot]
  • Time: 6m 11s
  • Tokens: 1,937,044 (1,930,744 in / 6,300 out)
  • Cost: $1.55

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

…st.ts](/tmp/git-processor/worktrees/integry/propr/pr-1967-followup-2026-08-29T17-17-44/packages/api/test/webPushDispatcher.test.ts:15).

Fixed the flaky full-suite failure in [webPushDispatcher.test.ts](/tmp/git-processor/worktrees/integry/propr/pr-1967-followup-2026-08-29T17-17-44/packages/api/test/webPushDispatcher.test.ts:15).

The fixture now uses a stable historical timestamp, preventing SQLite wall-clock comparisons from intermittently hiding claimable delivery jobs.

Validation passed:

- Dispatcher suite: 20 consecutive runs, 340/340 tests
- Notification suites: 42/42 tests
- API typecheck
- API lint
- `git diff --check`

Only the intended test file changed; no commit was created.

PR: #1967
Comment by: @github-actions[bot] (ID: 5463780771)
Model: gpt-5.6-sol
@integry
integry merged commit 85aee4b into 1952-epic-electron-runtime-app Aug 29, 2026
13 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.

Scaffold the secure Electron desktop runtime and reusable Web UI renderer

1 participant