Skip to content

[1961 by GPT-5.6 Sol] Integrate remote instance discovery, browser pairing, and revocation in desktop - #1977

Open
propr-dev[bot] wants to merge 21 commits into
1953-epic-desktop-transport-apifrom
1961/gpt-5.6-sol-integrate-remote-instance-20260829-1840-cv7
Open

[1961 by GPT-5.6 Sol] Integrate remote instance discovery, browser pairing, and revocation in desktop#1977
propr-dev[bot] wants to merge 21 commits into
1953-epic-desktop-transport-apifrom
1961/gpt-5.6-sol-integrate-remote-instance-20260829-1840-cv7

Conversation

@propr-dev

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

Copy link
Copy Markdown
Contributor

AI Implementation Summary

Closes #1961

Branch: 1961/gpt-5.6-sol-integrate-remote-instance-20260829-1840-cv7
Commits: ✅ Changes committed (e720382)


AI Processing Completed

Execution Details:

Summary:
Implemented GitHub issue #1961.

Key changes:

  • Added shared-client desktop discovery and cancellable/expiry-aware browser pairing.
  • Connected Electron’s secure profile/credential bridge to the desktop instance chooser.
  • Added consistent bearer authentication for REST and Socket.IO, including reconnect and revocation handling.
  • Added current-token revocation and secure profile removal.
  • Isolated profiles by omitting cookies for bearer requests and clearing renderer state/instance cookies when switching.
  • Replaced the placeholder Electron renderer with the shared desktop application.
  • Added deterministic protocol and Electron-adapter tests.

Verification passed:

  • npm run typecheck -w @propr/client
  • npm run test -w @propr/client
  • npm run typecheck -w propr-ui
  • npm run test -w propr-ui -- --run src/desktop/electronAdapters.test.ts src/desktop/DesktopExperience.test.tsx src/contexts/SocketProvider.test.tsx
  • npm run typecheck -w @propr/desktop
  • npm run test -w @propr/desktop
  • npx tsc --noEmit -p packages/api/tsconfig.json
  • PROPR_GH_RELAY_URL=https://relay.example.test PROPR_GH_RELAY_TOKEN=test-token npx tsx --test packages/api/test/desktopAuth.test.ts packages/api/test/socketAuthentication.test.ts packages/api/test/statusRoutes.test.ts
  • npm run build -w @propr/api
  • npm run build -w propr-ui
  • npm run package -w @propr/desktop

Linux packaging succeeded. The packaged-app smoke test was not run because xvfb-run is unavailable. Windows/macOS packaging and platform keychain behavior could not be exercised locally.

Detailed Logs:

  • Conversation: 147 messages
  • Session: 01a04ed3-897c-7c03-abac-8e20a5be139e

Log files stored at:

  • conversation: /tmp/claude-logs/issue-1961-2026-08-29T18-58-16-341Z-conversation.json
  • output: /tmp/claude-logs/issue-1961-2026-08-29T18-58-16-341Z-output.txt
Latest Conversation Messages

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


💡 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!

…rowser pairi

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

Implementation completed successfully.
@integry

integry commented Aug 29, 2026

Copy link
Copy Markdown
Owner

/review

Initial Ultrafix review for the remote-instance implementation lane. Review the full PR delta against 1953-epic-desktop-transport-api, especially: URL/redirect validation; device-flow expiry/cancellation/polling; main-process-only token custody; no token/log/localStorage leakage; exact profile isolation across REST, Socket.IO, cookies, restart and switching; revocation/disconnect semantics; renderer IPC sender validation; and preservation of hosted Web UI behavior. This child PR must not target or merge to main. The finalized runtime/CSS epic base will be synced after this 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

@github-actions

Copy link
Copy Markdown

Checks Failed

Validation failed during setup, tests, CLI packaging, linting, or build checks.

View Logs
Build check diagnostics for run 33269653350, attempt 1
Checkout outcome: success

--- GitHub Actions workflow lint ---
docker.io/rhysd/actionlint@sha256:b1934ee5f1c509618f2508e6eb47ee0d3520686341fec936f3b79331f9315667: Pulling from rhysd/actionlint
589002ba0eae: Pulling fs layer
c09af8888d6a: Pulling fs layer
4ccd7d76ca98: Pulling fs layer
b6b4b7b0e257: Pulling fs layer
b6b4b7b0e257: Waiting
4ccd7d76ca98: Verifying Checksum
4ccd7d76ca98: Download complete
c09af8888d6a: Verifying Checksum
c09af8888d6a: Download complete
589002ba0eae: Verifying Checksum
589002ba0eae: Download complete
589002ba0eae: Pull complete
b6b4b7b0e257: Verifying Checksum
b6b4b7b0e257: Download complete
c09af8888d6a: Pull complete
4ccd7d76ca98: Pull complete
b6b4b7b0e257: Pull complete
Digest: sha256:b1934ee5f1c509618f2508e6eb47ee0d3520686341fec936f3b79331f9315667
Status: Downloaded newer image for rhysd/actionlint@sha256:b1934ee5f1c509618f2508e6eb47ee0d3520686341fec936f3b79331f9315667
docker.io/rhysd/actionlint@sha256:b1934ee5f1c509618f2508e6eb47ee0d3520686341fec936f3b79331f9315667

--- Release shell script lint ---

--- Toolchain ---
v22.23.2
10.9.8

--- Dependency installation ---
npm warn deprecated inflight@1.0.6: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
npm warn deprecated glob@7.2.3: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
npm warn deprecated gar@1.0.4: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
npm warn deprecated rimraf@2.6.3: Rimraf versions prior to v4 are no longer supported

added 1136 packages, and audited 1145 packages in 20s

326 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

--- Fast unit tests ---
Running fast unit tests (no Redis/Docker dependencies)...

> propr@0.8.15 test:unit
> NODE_ENV=test npx tsx --experimental-test-module-mocks --test test/minimal.test.ts test/modelName.test.ts test/agentContainerResources.test.ts test/agentDockerfileSupplyChain.test.ts test/daemonEventIntake.test.ts test/databaseMigrationGate.test.ts test/generateContext.test.ts test/githubEventIntakeMode.test.ts test/intakeModePrerequisites.test.ts test/orchestratorMigrationPhase.test.mjs test/validateRoutingUrl.test.ts test/routingWebSocketProtocol.test.ts test/routingWebSocketIntakeService.test.ts test/routingStatusPublisher.test.ts test/releaseValidation.test.mjs test/sessionSecret.test.ts test/testSuiteRunner.test.mjs packages/api/test/connectAuth.test.ts packages/api/test/attachmentUploadCleanup.test.ts packages/api/test/configReloadSubscription.test.ts packages/api/test/dockerCommandSafety.test.ts packages/api/test/listenAddress.test.ts packages/api/test/oauthState.test.ts packages/api/test/requestRateLimits.test.ts packages/api/test/statusRoutes.test.ts packages/api/test/agentRuntimeRoutes.test.ts packages/api/test/instanceAuthorization.test.ts packages/api/test/routeAuthorization.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: 14.478906
      type: 'test'
      ...
    # Subtest: persists a failed state when queue submission fails
    ok 2 - persists a failed state when queue submission fails
      ---
      duration_ms: 3.501318
      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: 3.180715
      type: 'test'
      ...
    # Subtest: times out slow runtime package validation before queueing
    ok 4 - times out slow runtime package validation before queueing
      ---
      duration_ms: 10.644756
      type: 'test'
      ...
    # Subtest: returns package suggestions from configured runtimes
    ok 5 - returns package suggestions from configured runtimes
      ---
      duration_ms: 3.391293
      type: 'test'
      ...
    # Subtest: enforces PROPR_ADMIN_USERS when configured
    ok 6 - enforces PROPR_ADMIN_USERS when configured
      ---
      duration_ms: 0.410442
      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.34726
      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.475263
      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.69607
      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.537176
      type: 'test'
      ...
    # Subtest: resolves the runtime build queue lazily when queueing
    ok 11 - resolves the runtime build queue lazily when queueing
      ---
      duration_ms: 0.933591
      type: 'test'
      ...
    # Subtest: reports apply load failures through the route response
    ok 12 - reports apply load failures through the route response
      ---
      duration_ms: 0.487814
      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.659875
      type: 'test'
      ...
    # Subtest: rejects runtime verification without runtime-management permission
    ok 14 - rejects runtime verification without runtime-management permission
      ---
      duration_ms: 0.348109
      type: 'test'
      ...
    1..14
ok 1 - agent runtime package routes
  ---
  duration_ms: 48.416318
  type: 'suite'
  ...
# [2026-08-29 18:58:57.781 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/home/runner/work/propr/propr/data/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 18:58:57.822 +0000] �[32mINFO�[39m: �[36mSQLite database connection closed�[39m
# Subtest: upload handler removes Multer files rejected before attachment processing
ok 2 - upload handler removes Multer files rejected before attachment processing
  ---
  duration_ms: 11.087313
  type: 'test'
  ...
# [2026-08-29 18:58:57.396 +0000] �[32mINFO�[39m: �[36mCreated data directory for SQLite database�[39m
#     directory: "/home/runner/work/propr/propr/data"
# Subtest: attachment processing removes temporary and final files when persistence fails
ok 3 - attachment processing removes temporary and final files when persistence fails
  ---
  duration_ms: 10.306117
  type: 'test'
  ...
# [2026-08-29 18:58:57.763 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/home/runner/work/propr/propr/data/propr.test.sqlite"
#     environment: "test"
# Subtest: temporary cleanup refuses paths outside its configured root
ok 4 - temporary cleanup refuses paths outside its configured root
  ---
  duration_ms: 0.726722
  type: 'test'
  ...
# Subtest: temporary cleanup rejects dot-segment paths that resolve above its root
ok 5 - temporary cleanup rejects dot-segment paths that resolve above its root
  ---
  duration_ms: 1.721543
  type: 'test'
  ...
# Subtest: attachment processing rejects path-like draft IDs and still cleans its temp file
ok 6 - attachment processing rejects path-like draft IDs and still cleans its temp file
  ---
  duration_ms: 2.635448
  type: 'test'
  ...
# [2026-08-29 18:58:57.789 +0000] �[32mINFO�[39m: �[36mSQLite database connection closed�[39m
# Subtest: API config subscription serializes the startup reload with settings updates
ok 7 - API config subscription serializes the startup reload with settings updates
  ---
  duration_ms: 6.65021
  type: 'test'
  ...
# Subtest: API config subscription reloads repository updates
ok 8 - API config subscription reloads repository updates
  ---
  duration_ms: 0.521885
  type: 'test'
  ...
# Subtest: API config subscription ignores unrelated and malformed events
ok 9 - API config subscription ignores unrelated and malformed events
  ---
  duration_ms: 0.366865
  type: 'test'
  ...
# Subtest: a failed API settings reload does not block the next notification
ok 10 - a failed API settings reload does not block the next notification
  ---
  duration_ms: 0.457748
  type: 'test'
  ...
# Subtest: relay tunnel mode uses Connect without local OAuth credentials
ok 11 - relay tunnel mode uses Connect without local OAuth credentials
  ---
  duration_ms: 1.473956
  type: 'test'
  ...
# Subtest: relay tunnel mode preserves Connect for operator-supplied service endpoints
ok 12 - relay tunnel mode preserves Connect for operator-supplied service endpoints
  ---
  duration_ms: 0.292887
  type: 'test'
  ...
# Subtest: local relay mode uses Connect without a per-instance OAuth App
ok 13 - local relay mode uses Connect without a per-instance OAuth App
  ---
  duration_ms: 0.317384
  type: 'test'
  ...
# Subtest: off-tunnel relay inference rejects callbacks outside the exact loopback allowlist
ok 14 - off-tunnel relay inference rejects callbacks outside the exact loopback allowlist
  ---
  duration_ms: 0.379029
  type: 'test'
  ...
# Subtest: off-tunnel custom relay enrollment does not infer hosted Connect auth
ok 15 - off-tunnel custom relay enrollment does not infer hosted Connect auth
  ---
  duration_ms: 0.1653
  type: 'test'
  ...
# Subtest: literal example OAuth placeholders do not enable GitHub web auth
ok 16 - literal example OAuth placeholders do not enable GitHub web auth
  ---
  duration_ms: 0.160723
  type: 'test'
  ...
# Subtest: explicit custom GitHub web auth remains supported
ok 17 - explicit custom GitHub web auth remains supported
  ---
  duration_ms: 0.18304
  type: 'test'
  ...
# Subtest: explicit custom GitHub web auth wins over relay inference off-tunnel
ok 18 - explicit custom GitHub web auth wins over relay inference off-tunnel
  ---
  duration_ms: 0.170557
  type: 'test'
  ...
# Subtest: Connect authorization URL carries the exact callback and CSRF state
ok 19 - Connect authorization URL carries the exact callback and CSRF state
  ---
  duration_ms: 0.629817
  type: 'test'
  ...
# Subtest: redeems a Connect code server-to-server without exposing the relay token in the body
ok 20 - redeems a Connect code server-to-server without exposing the relay token in the body
  ---
  duration_ms: 23.93771
  type: 'test'
  ...
# Subtest: binds the Connect identity username to the validated token owner
ok 21 - binds the Connect identity username to the validated token owner
  ---
  duration_ms: 0.643007
  type: 'test'
  ...
# Subtest: accepts Docker IDs and ProPR-generated container names
ok 22 - accepts Docker IDs and ProPR-generated container names
  ---
  duration_ms: 1.000279
  type: 'test'
  ...
# Subtest: rejects container references that could be parsed as options or shell syntax
ok 23 - rejects container references that could be parsed as options or shell syntax
  ---
  duration_ms: 0.709025
  type: 'test'
  ...
# Subtest: production subprocess call sites do not invoke a command shell
ok 24 - production subprocess call sites do not invoke a command shell
  ---
  duration_ms: 139.477079
  type: 'test'
  ...
# Subtest: no-shell guard rejects aliases and enabled shell options
ok 25 - no-shell guard rejects aliases and enabled shell options
  ---
  duration_ms: 3.832024
  type: 'test'
  ...
# 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: 14.545917
      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: 13.56497
      type: 'test'
      ...
    # Subtest: grants full admin permissions to PROPR_ADMIN_USERS
    ok 3 - grants full admin permissions to PROPR_ADMIN_USERS
      ---
      duration_ms: 3.889411
      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: 3.865123
      type: 'test'
      ...
    # Subtest: resolves demo users without installation permissions
    ok 5 - resolves demo users without installation permissions
      ---
      duration_ms: 3.109632
      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.861719
      type: 'test'
      ...
    # Subtest: current-user responses expose capabilities without OAuth credentials
    ok 7 - current-user responses expose capabilities without OAuth credentials
      ---
      duration_ms: 4.915241
      type: 'test'
      ...
    1..7
ok 26 - instance authorization
  ---
  duration_ms: 48.307705
  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: 4.500351
      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: 18.258687
      type: 'test'
      ...
    # Subtest: adds explicit members and writes an audit entry
    ok 3 - adds explicit members and writes an audit entry
      ---
      duration_ms: 17.598601
      type: 'test'
      ...
    # Subtest: keeps environment authority separate from durable role assignments
    ok 4 - keeps environment authority separate from durable role assignments
      ---
      duration_ms: 7.498935
      type: 'test'
      ...
    # Subtest: prevents removing the last durable administrator
    ok 5 - prevents removing the last durable administrator
      ---
      duration_ms: 8.576262
      type: 'test'
      ...
    # Subtest: allows role changes once another administrator exists
    ok 6 - allows role changes once another administrator exists
      ---
      duration_ms: 11.026931
      type: 'test'
      ...
    1..6
ok 27 - instance member service
  ---
  duration_ms: 67.954962
  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.930805
      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: 2.679067
      type: 'test'
      ...
    1..2
ok 28 - instance catalog
  ---
  duration_ms: 5.74516
  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: 4.236221
      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: 2.108036
      type: 'test'
      ...
    # Subtest: rejects GitHub usernames with trailing or consecutive hyphens
    ok 3 - rejects GitHub usernames with trailing or consecutive hyphens
      ---
      duration_ms: 3.876162
      type: 'test'
      ...
    1..3
ok 29 - instance admin routes
  ---
  duration_ms: 10.396827
  type: 'suite'
  ...
# [2026-08-29 18:59:00.065 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/home/runner/work/propr/propr/data/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 18:59:00.190 +0000] �[32mINFO�[39m: �[36mSQLite database connection closed�[39m
# Subtest: direct API execution binds to loopback by default
ok 30 - direct API execution binds to loopback by default
  ---
  duration_ms: 1.126976
  type: 'test'
  ...
# Subtest: containerized API execution remains reachable through its published port
ok 31 - containerized API execution remains reachable through its published port
  ---
  duration_ms: 0.220718
  type: 'test'
  ...
# Subtest: an explicit API listen host overrides environment defaults
ok 32 - an explicit API listen host overrides environment defaults
  ---
  duration_ms: 0.181172
  type: 'test'
  ...
# Subtest: GitHub OAuth authorization stores and sends a random state nonce
ok 33 - GitHub OAuth authorization stores and sends a random state nonce
  ---
  duration_ms: 3.403449
  type: 'test'
  ...
# Subtest: GitHub OAuth callback rejects a mismatched state before token exchange
ok 34 - GitHub OAuth callback rejects a mismatched state before token exchange
  ---
  duration_ms: 4.764626
  type: 'test'
  ...
# [2026-08-29 18:59:01.241 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/home/runner/work/propr/propr/data/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 18:59:01.242 +0000] �[32mINFO�[39m: �[36mSQLite database connection closed�[39m
# Subtest: returns a standard 429 response after the configured quota
ok 35 - returns a standard 429 response after the configured quota
  ---
  duration_ms: 94.546741
  type: 'test'
  ...
# Subtest: does not charge CORS preflight requests against the quota
ok 36 - does not charge CORS preflight requests against the quota
  ---
  duration_ms: 13.320805
  type: 'test'
  ...
# Subtest: route-level webhook limiting preserves alternate-case raw bodies and rejects excess requests before parsing
ok 37 - route-level webhook limiting preserves alternate-case raw bodies and rejects excess requests before parsing
  ---
  duration_ms: 15.820721
  type: 'test'
  ...
# Subtest: does not let an unconfigured private peer rotate quota buckets with X-Forwarded-For
ok 38 - does not let an unconfigured private peer rotate quota buckets with X-Forwarded-For
  ---
  duration_ms: 13.333796
  type: 'test'
  ...
# Subtest: tunnel trust does not let an unrelated private peer rotate quota buckets
ok 39 - tunnel trust does not let an unrelated private peer rotate quota buckets
  ---
  duration_ms: 14.172016
  type: 'test'
  ...
# Subtest: trusted TLS proxy preserves per-client quotas and secure session cookies
ok 40 - trusted TLS proxy preserves per-client quotas and secure session cookies
  ---
  duration_ms: 18.701273
  type: 'test'
  ...
# Subtest: resolves secure defaults and explicit positive-integer overrides
ok 41 - resolves secure defaults and explicit positive-integer overrides
  ---
  duration_ms: 0.396344
  type: 'test'
  ...
# Subtest: rejects invalid overrides instead of silently disabling protection
ok 42 - rejects invalid overrides instead of silently disabling protection
  ---
  duration_ms: 0.449306
  type: 'test'
  ...
# Subtest: assembled instance permission routes
    # Subtest: captures Express 5 named wildcard parameters as path segments
    ok 1 - captures Express 5 named wildcard parameters as path segments
      ---
      duration_ms: 57.529743
      type: 'test'
      ...
# [2026-08-29 18:59:01.801 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/home/runner/work/propr/propr/data/propr.test.sqlite"
#     environment: "test"
    # Subtest: members can read only the sanitized catalog endpoints
    ok 2 - members can read only the sanitized catalog endpoints
      ---
      duration_ms: 75.906657
      type: 'test'
      ...
    # Subtest: administrators pass every management guard in the route matrix
    ok 3 - administrators pass every management guard in the route matrix
      ---
      duration_ms: 31.597553
      type: 'test'
      ...
    1..3
ok 43 - assembled instance permission routes
  ---
  duration_ms: 166.305284
  type: 'suite'
  ...
# [2026-08-29 18:59:01.965 +0000] �[32mINFO�[39m: �[36mSQLite database connection closed�[39m
# Error loading summarization warnings: SqliteError: select * from `system_configs` where `key` = 'summarization_runtime_state' limit 1 - no such table: system_configs
#     at Database.prepare (/home/runner/work/propr/propr/node_modules/better-sqlite3/lib/methods/wrappers.js:5:21)
#     at Client_BetterSQLite3._query (/home/runner/work/propr/propr/node_modules/knex/lib/dialects/better-sqlite3/index.js:35:34)
#     at executeQuery (/home/runner/work/propr/propr/node_modules/knex/lib/execution/internal/query-executioner.js:37:17)
#     at Client_BetterSQLite3.query (/home/runner/work/propr/propr/node_modules/knex/lib/client.js:154:12)
#     at Runner.query (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:141:36)
#     at ensureConnectionCallback (/home/runner/work/propr/propr/node_modules/knex/lib/execution/internal/ensure-connection-callback.js:13:17)
#     at Runner.ensureConnection (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:318:20)
#     at async Runner.run (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:30:19)
#     at async getConfigWithClient (/home/runner/work/propr/propr/packages/core/src/config/configStore.ts:17:24)
#     at async loadSummarizationRuntimeState (/home/runner/work/propr/propr/packages/core/src/config/configManagerSummarization.ts:101:19) {
#   code: 'SQLITE_ERROR'
# }
# Subtest: /api/status omits disabled configured agents
ok 44 - /api/status omits disabled configured agents
  ---
  duration_ms: 2043.857216
  type: 'test'
  ...
# Subtest: /api/compatibility returns public version contract metadata
ok 45 - /api/compatibility returns public version contract metadata
  ---
  duration_ms: 4.617307
  type: 'test'
  ...
# Subtest: /api/desktop/discovery adds only the stable product name to compatibility metadata
ok 46 - /api/desktop/discovery adds only the stable product name to compatibility metadata
  ---
  duration_ms: 4.551634
  type: 'test'
  ...
# Error loading summarization warnings: SqliteError: select * from `system_configs` where `key` = 'summarization_runtime_state' limit 1 - no such table: system_configs
#     at Database.prepare (/home/runner/work/propr/propr/node_modules/better-sqlite3/lib/methods/wrappers.js:5:21)
#     at Client_BetterSQLite3._query (/home/runner/work/propr/propr/node_modules/knex/lib/dialects/better-sqlite3/index.js:35:34)
#     at executeQuery (/home/runner/work/propr/propr/node_modules/knex/lib/execution/internal/query-executioner.js:37:17)
#     at Client_BetterSQLite3.query (/home/runner/work/propr/propr/node_modules/knex/lib/client.js:154:12)
#     at Runner.query (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:141:36)
#     at ensureConnectionCallback (/home/runner/work/propr/propr/node_modules/knex/lib/execution/internal/ensure-connection-callback.js:13:17)
#     at Runner.ensureConnection (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:318:20)
#     at async Runner.run (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:30:19)
#     at async getConfigWithClient (/home/runner/work/propr/propr/packages/core/src/config/configStore.ts:17:24)
#     at async loadSummarizationRuntimeState (/home/runner/work/propr/propr/packages/core/src/config/configManagerSummarization.ts:101:19) {
#   code: 'SQLITE_ERROR'
# }
# Subtest: /api/status returns default Claude fallback when no agents are configured
ok 47 - /api/status returns default Claude fallback when no agents are configured
  ---
  duration_ms: 5.584269
  type: 'test'
  ...
# Error loading summarization warnings: SqliteError: select * from `system_configs` where `key` = 'summarization_runtime_state' limit 1 - no such table: system_configs
#     at Database.prepare (/home/runner/work/propr/propr/node_modules/better-sqlite3/lib/methods/wrappers.js:5:21)
#     at Client_BetterSQLite3._query (/home/runner/work/propr/propr/node_modules/knex/lib/dialects/better-sqlite3/index.js:35:34)
#     at executeQuery (/home/runner/work/propr/propr/node_modules/knex/lib/execution/internal/query-executioner.js:37:17)
#     at Client_BetterSQLite3.query (/home/runner/work/propr/propr/node_modules/knex/lib/client.js:154:12)
#     at Runner.query (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:141:36)
#     at ensureConnectionCallback (/home/runner/work/propr/propr/node_modules/knex/lib/execution/internal/ensure-connection-callback.js:13:17)
#     at Runner.ensureConnection (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:318:20)
#     at async Runner.run (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:30:19)
#     at async getConfigWithClient (/home/runner/work/propr/propr/packages/core/src/config/configStore.ts:17:24)
#     at async loadSummarizationRuntimeState (/home/runner/work/propr/propr/packages/core/src/config/configManagerSummarization.ts:101:19) {
#   code: 'SQLITE_ERROR'
# }
# [notifications] Failed to project system health snapshot: Error: notification persistence unavailable
#     at projectSystemSnapshot (/home/runner/work/propr/propr/packages/api/test/statusRoutes.test.ts:254:13)
#     at Object.getStatus (/home/runner/work/propr/propr/packages/api/routes/statusRoutes.ts:192:14)
#     at async readStatus (/home/runner/work/propr/propr/packages/api/test/statusRoutes.test.ts:162:3)
#     at async TestContext.<anonymous> (/home/runner/work/propr/propr/packages/api/test/statusRoutes.test.ts:251:16)
#     at async Test.run (node:internal/test_runner/test:1054:7)
#     at async Test.processPendingSubtests (node:internal/test_runner/test:744:7)
# Subtest: /api/status isolates system notification projection failures
ok 48 - /api/status isolates system notification projection failures
  ---
  duration_ms: 10.127576
  type: 'test'
  ...
# Subtest: /api/status surfaces unified agent image outages
ok 49 - /api/status surfaces unified agent image outages
  ---
  duration_ms: 1.609776
  type: 'test'
  ...
# Subtest: /api/status includes warnings field in demo mode
ok 50 - /api/status includes warnings field in demo mode
  ---
  duration_ms: 1.112732
  type: 'test'
  ...
# Error loading summarization warnings: SqliteError: select * from `system_configs` where `key` = 'summarization_runtime_state' limit 1 - no such table: system_configs
#     at Database.prepare (/home/runner/work/propr/propr/node_modules/better-sqlite3/lib/methods/wrappers.js:5:21)
#     at Client_BetterSQLite3._query (/home/runner/work/propr/propr/node_modules/knex/lib/dialects/better-sqlite3/index.js:35:34)
#     at executeQuery (/home/runner/work/propr/propr/node_modules/knex/lib/execution/internal/query-executioner.js:37:17)
#     at Client_BetterSQLite3.query (/home/runner/work/propr/propr/node_modules/knex/lib/client.js:154:12)
#     at Runner.query (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:141:36)
#     at ensureConnectionCallback (/home/runner/work/propr/propr/node_modules/knex/lib/execution/internal/ensure-connection-callback.js:13:17)
#     at Runner.ensureConnection (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:318:20)
#     at async Runner.run (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:30:19)
#     at async getConfigWithClient (/home/runner/work/propr/propr/packages/core/src/config/configStore.ts:17:24)
#     at async loadSummarizationRuntimeState (/home/runner/work/propr/propr/packages/core/src/config/configManagerSummarization.ts:101:19) {
#   code: 'SQLITE_ERROR'
# }
# Error loading summarization warnings: SqliteError: select * from `system_configs` where `key` = 'summarization_runtime_state' limit 1 - no such table: system_configs
#     at Database.prepare (/home/runner/work/propr/propr/node_modules/better-sqlite3/lib/methods/wrappers.js:5:21)
#     at Client_BetterSQLite3._query (/home/runner/work/propr/propr/node_modules/knex/lib/dialects/better-sqlite3/index.js:35:34)
#     at executeQuery (/home/runner/work/propr/propr/node_modules/knex/lib/execution/internal/query-executioner.js:37:17)
#     at Client_BetterSQLite3.query (/home/runner/work/propr/propr/node_modules/knex/lib/client.js:154:12)
#     at Runner.query (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:141:36)
#     at ensureConnectionCallback (/home/runner/work/propr/propr/node_modules/knex/lib/execution/internal/ensure-connection-callback.js:13:17)
#     at Runner.ensureConnection (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:318:20)
#     at async Runner.run (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:30:19)
#     at async getConfigWithClient (/home/runner/work/propr/propr/packages/core/src/config/configStore.ts:17:24)
#     at async loadSummarizationRuntimeState (/home/runner/work/propr/propr/packages/core/src/config/configManagerSummarization.ts:101:19) {
#   code: 'SQLITE_ERROR'
# }
# Subtest: /api/status caches agent health checks briefly
ok 51 - /api/status caches agent health checks briefly
  ---
  duration_ms: 10.63618
  type: 'test'
  ...
# Error loading summarization warnings: SqliteError: select * from `system_configs` where `key` = 'summarization_runtime_state' limit 1 - no such table: system_configs
#     at Database.prepare (/home/runner/work/propr/propr/node_modules/better-sqlite3/lib/methods/wrappers.js:5:21)
#     at Client_BetterSQLite3._query (/home/runner/work/propr/propr/node_modules/knex/lib/dialects/better-sqlite3/index.js:35:34)
#     at executeQuery (/home/runner/work/propr/propr/node_modules/knex/lib/execution/internal/query-executioner.js:37:17)
#     at Client_BetterSQLite3.query (/home/runner/work/propr/propr/node_modules/knex/lib/client.js:154:12)
#     at Runner.query (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:141:36)
#     at ensureConnectionCallback (/home/runner/work/propr/propr/node_modules/knex/lib/execution/internal/ensure-connection-callback.js:13:17)
#     at Runner.ensureConnection (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:318:20)
#     at async Runner.run (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:30:19)
#     at async getConfigWithClient (/home/runner/work/propr/propr/packages/core/src/config/configStore.ts:17:24)
#     at async loadSummarizationRuntimeState (/home/runner/work/propr/propr/packages/core/src/config/configManagerSummarization.ts:101:19) {
#   code: 'SQLITE_ERROR'
# }
# Subtest: /api/status reports resolved auth mode and event intake mode
ok 52 - /api/status reports resolved auth mode and event intake mode
  ---
  duration_ms: 5.551161
  type: 'test'
  ...
# Error loading summarization warnings: SqliteError: select * from `system_configs` where `key` = 'summarization_runtime_state' limit 1 - no such table: system_configs
#     at Database.prepare (/home/runner/work/propr/propr/node_modules/better-sqlite3/lib/methods/wrappers.js:5:21)
#     at Client_BetterSQLite3._query (/home/runner/work/propr/propr/node_modules/knex/lib/dialects/better-sqlite3/index.js:35:34)
#     at executeQuery (/home/runner/work/propr/propr/node_modules/knex/lib/execution/internal/query-executioner.js:37:17)
#     at Client_BetterSQLite3.query (/home/runner/work/propr/propr/node_modules/knex/lib/client.js:154:12)
#     at Runner.query (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:141:36)
#     at ensureConnectionCallback (/home/runner/work/propr/propr/node_modules/knex/lib/execution/internal/ensure-connection-callback.js:13:17)
#     at Runner.ensureConnection (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:318:20)
#     at async Runner.run (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:30:19)
#     at async getConfigWithClient (/home/runner/work/propr/propr/packages/core/src/config/configStore.ts:17:24)
#     at async loadSummarizationRuntimeState (/home/runner/work/propr/propr/packages/core/src/config/configManagerSummarization.ts:101:19) {
#   code: 'SQLITE_ERROR'
# }
# Subtest: /api/status reports connected intake status when routing state is live
ok 53 - /api/status reports connected intake status when routing state is live
  ---
  duration_ms: 5.087511
  type: 'test'
  ...
# Error loading summarization warnings: SqliteError: select * from `system_configs` where `key` = 'summarization_runtime_state' limit 1 - no such table: system_configs
#     at Database.prepare (/home/runner/work/propr/propr/node_modules/better-sqlite3/lib/methods/wrappers.js:5:21)
#     at Client_BetterSQLite3._query (/home/runner/work/propr/propr/node_modules/knex/lib/dialects/better-sqlite3/index.js:35:34)
#     at executeQuery (/home/runner/work/propr/propr/node_modules/knex/lib/execution/internal/query-executioner.js:37:17)
#     at Client_BetterSQLite3.query (/home/runner/work/propr/propr/node_modules/knex/lib/client.js:154:12)
#     at Runner.query (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:141:36)
#     at ensureConnectionCallback (/home/runner/work/propr/propr/node_modules/knex/lib/execution/internal/ensure-connection-callback.js:13:17)
#     at Runner.ensureConnection (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:318:20)
#     at async Runner.run (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:30:19)
#     at async getConfigWithClient (/home/runner/work/propr/propr/packages/core/src/config/configStore.ts:17:24)
#     at async loadSummarizationRuntimeState (/home/runner/work/propr/propr/packages/core/src/config/configManagerSummarization.ts:101:19) {
#   code: 'SQLITE_ERROR'
# }
# Error loading summarization warnings: SqliteError: select * from `system_configs` where `key` = 'summarization_runtime_state' limit 1 - no such table: system_configs
#     at Database.prepare (/home/runner/work/propr/propr/node_modules/better-sqlite3/lib/methods/wrappers.js:5:21)
#     at Client_BetterSQLite3._query (/home/runner/work/propr/propr/node_modules/knex/lib/dialects/better-sqlite3/index.js:35:34)
#     at executeQuery (/home/runner/work/propr/propr/node_modules/knex/lib/execution/internal/query-executioner.js:37:17)
#     at Client_BetterSQLite3.query (/home/runner/work/propr/propr/node_modules/knex/lib/client.js:154:12)
#     at Runner.query (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:141:36)
#     at ensureConnectionCallback (/home/runner/work/propr/propr/node_modules/knex/lib/execution/internal/ensure-connection-callback.js:13:17)
#     at Runner.ensureConnection (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:318:20)
#     at async Runner.run (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:30:19)
#     at async getConfigWithClient (/home/runner/work/propr/propr/packages/core/src/config/configStore.ts:17:24)
#     at async loadSummarizationRuntimeState (/home/runner/work/propr/propr/packages/core/src/config/configManagerSummarization.ts:101:19) {
#   code: 'SQLITE_ERROR'
# }
# Error loading summarization warnings: SqliteError: select * from `system_configs` where `key` = 'summarization_runtime_state' limit 1 - no such table: system_configs
#     at Database.prepare (/home/runner/work/propr/propr/node_modules/better-sqlite3/lib/methods/wrappers.js:5:21)
#     at Client_BetterSQLite3._query (/home/runner/work/propr/propr/node_modules/knex/lib/dialects/better-sqlite3/index.js:35:34)
#     at executeQuery (/home/runner/work/propr/propr/node_modules/knex/lib/execution/internal/query-executioner.js:37:17)
#     at Client_BetterSQLite3.query (/home/runner/work/propr/propr/node_modules/knex/lib/client.js:154:12)
#     at Runner.query (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:141:36)
#     at ensureConnectionCallback (/home/runner/work/propr/propr/node_modules/knex/lib/execution/internal/ensure-connection-callback.js:13:17)
#     at Runner.ensureConnection (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:318:20)
#     at async Runner.run (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:30:19)
#     at async getConfigWithClient (/home/runner/work/propr/propr/packages/core/src/config/configStore.ts:17:24)
#     at async loadSummarizationRuntimeState (/home/runner/work/propr/propr/packages/core/src/config/configManagerSummarization.ts:101:19) {
#   code: 'SQLITE_ERROR'
# }
# Subtest: /api/status reports disconnected intake status when routing state is down
ok 54 - /api/status reports disconnected intake status when routing state is down
  ---
  duration_ms: 5.376704
  type: 'test'
  ...
# Subtest: /api/status reports active intake status for polling when the daemon is running
ok 55 - /api/status reports active intake status for polling when the daemon is running
  ---
  duration_ms: 4.450875
  type: 'test'
  ...
# Error loading summarization warnings: SqliteError: select * from `system_configs` where `key` = 'summarization_runtime_state' limit 1 - no such table: system_configs
#     at Database.prepare (/home/runner/work/propr/propr/node_modules/better-sqlite3/lib/methods/wrappers.js:5:21)
#     at Client_BetterSQLite3._query (/home/runner/work/propr/propr/node_modules/knex/lib/dialects/better-sqlite3/index.js:35:34)
#     at executeQuery (/home/runner/work/propr/propr/node_modules/knex/lib/execution/internal/query-executioner.js:37:17)
#     at Client_BetterSQLite3.query (/home/runner/work/propr/propr/node_modules/knex/lib/client.js:154:12)
#     at Runner.query (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:141:36)
#     at ensureConnectionCallback (/home/runner/work/propr/propr/node_modules/knex/lib/execution/internal/ensure-connection-callback.js:13:17)
#     at Runner.ensureConnection (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:318:20)
#     at async Runner.run (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:30:19)
#     at async getConfigWithClient (/home/runner/work/propr/propr/packages/core/src/config/configStore.ts:17:24)
#     at async loadSummarizationRuntimeState (/home/runner/work/propr/propr/packages/core/src/config/configManagerSummarization.ts:101:19) {
#   code: 'SQLITE_ERROR'
# }
# Error loading summarization warnings: SqliteError: select * from `system_configs` where `key` = 'summarization_runtime_state' limit 1 - no such table: system_configs
#     at Database.prepare (/home/runner/work/propr/propr/node_modules/better-sqlite3/lib/methods/wrappers.js:5:21)
#     at Client_BetterSQLite3._query (/home/runner/work/propr/propr/node_modules/knex/lib/dialects/better-sqlite3/index.js:35:34)
#     at executeQuery (/home/runner/work/propr/propr/node_modules/knex/lib/execution/internal/query-executioner.js:37:17)
#     at Client_BetterSQLite3.query (/home/runner/work/propr/propr/node_modules/knex/lib/client.js:154:12)
#     at Runner.query (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:141:36)
#     at ensureConnectionCallback (/home/runner/work/propr/propr/node_modules/knex/lib/execution/internal/ensure-connection-callback.js:13:17)
#     at Runner.ensureConnection (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:318:20)
#     at async Runner.run (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:30:19)
#     at async getConfigWithClient (/home/runner/work/propr/propr/packages/core/src/config/configStore.ts:17:24)
#     at async loadSummarizationRuntimeState (/home/runner/work/propr/propr/packages/core/src/config/configManagerSummarization.ts:101:19) {
#   code: 'SQLITE_ERROR'
# }
# Error loading summarization warnings: SqliteError: select * from `system_configs` where `key` = 'summarization_runtime_state' limit 1 - no such table: system_configs
#     at Database.prepare (/home/runner/work/propr/propr/node_modules/better-sqlite3/lib/methods/wrappers.js:5:21)
#     at Client_BetterSQLite3._query (/home/runner/work/propr/propr/node_modules/knex/lib/dialects/better-sqlite3/index.js:35:34)
#     at executeQuery (/home/runner/work/propr/propr/node_modules/knex/lib/execution/internal/query-executioner.js:37:17)
#     at Client_BetterSQLite3.query (/home/runner/work/propr/propr/node_modules/knex/lib/client.js:154:12)
#     at Runner.query (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:141:36)
#     at ensureConnectionCallback (/home/runner/work/propr/propr/node_modules/knex/lib/execution/internal/ensure-connection-callback.js:13:17)
#     at Runner.ensureConnection (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:318:20)
#     at async Runner.run (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:30:19)
#     at async getConfigWithClient (/home/runner/work/propr/propr/packages/core/src/config/configStore.ts:17:24)
#     at async loadSummarizationRuntimeState (/home/runner/work/propr/propr/packages/core/src/config/configManagerSummarization.ts:101:19) {
#   code: 'SQLITE_ERROR'
# }
# Error loading summarization warnings: SqliteError: select * from `system_configs` where `key` = 'summarization_runtime_state' limit 1 - no such table: system_configs
#     at Database.prepare (/home/runner/work/propr/propr/node_modules/better-sqlite3/lib/methods/wrappers.js:5:21)
#     at Client_BetterSQLite3._query (/home/runner/work/propr/propr/node_modules/knex/lib/dialects/better-sqlite3/index.js:35:34)
#     at executeQuery (/home/runner/work/propr/propr/node_modules/knex/lib/execution/internal/query-executioner.js:37:17)
#     at Client_BetterSQLite3.query (/home/runner/work/propr/propr/node_modules/knex/lib/client.js:154:12)
#     at Runner.query (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:141:36)
#     at ensureConnectionCallback (/home/runner/work/propr/propr/node_modules/knex/lib/execution/internal/ensure-connection-callback.js:13:17)
#     at Runner.ensureConnection (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:318:20)
#     at async Runner.run (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:30:19)
#     at async getConfigWithClient (/home/runner/work/propr/propr/packages/core/src/config/configStore.ts:17:24)
#     at async loadSummarizationRuntimeState (/home/runner/work/propr/propr/packages/core/src/config/configManagerSummarization.ts:101:19) {
#   code: 'SQLITE_ERROR'
# }
# Error loading summarization warnings: SqliteError: select * from `system_configs` where `key` = 'summarization_runtime_state' limit 1 - no such table: system_configs
#     at Database.prepare (/home/runner/work/propr/propr/node_modules/better-sqlite3/lib/methods/wrappers.js:5:21)
#     at Client_BetterSQLite3._query (/home/runner/work/propr/propr/node_modules/knex/lib/dialects/better-sqlite3/index.js:35:34)
#     at executeQuery (/home/runner/work/propr/propr/node_modules/knex/lib/execution/internal/query-executioner.js:37:17)
#     at Client_BetterSQLite3.query (/home/runner/work/propr/propr/node_modules/knex/lib/client.js:154:12)
#     at Runner.query (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:141:36)
#     at ensureConnectionCallback (/home/runner/work/propr/propr/node_modules/knex/lib/execution/internal/ensure-connection-callback.js:13:17)
#     at Runner.ensureConnection (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:318:20)
#     at async Runner.run (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:30:19)
#     at async getConfigWithClient (/home/runner/work/propr/propr/packages/core/src/config/configStore.ts:17:24)
#     at async loadSummarizationRuntimeState (/home/runner/work/propr/propr/packages/core/src/config/configManagerSummarization.ts:101:19) {
#   code: 'SQLITE_ERROR'
# }
# Error loading summarization warnings: SqliteError: select * from `system_configs` where `key` = 'summarization_runtime_state' limit 1 - no such table: system_configs
#     at Database.prepare (/home/runner/work/propr/propr/node_modules/better-sqlite3/lib/methods/wrappers.js:5:21)
#     at Client_BetterSQLite3._query (/home/runner/work/propr/propr/node_modules/knex/lib/dialects/better-sqlite3/index.js:35:34)
#     at executeQuery (/home/runner/work/propr/propr/node_modules/knex/lib/execution/internal/query-executioner.js:37:17)
#     at Client_BetterSQLite3.query (/home/runner/work/propr/propr/node_modules/knex/lib/client.js:154:12)
#     at Runner.query (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:141:36)
#     at ensureConnectionCallback (/home/runner/work/propr/propr/node_modules/knex/lib/execution/internal/ensure-connection-callback.js:13:17)
#     at Runner.ensureConnection (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:318:20)
#     at async Runner.run (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:30:19)
#     at async getConfigWithClient (/home/runner/work/propr/propr/packages/core/src/config/configStore.ts:17:24)
#     at async loadSummarizationRuntimeState (/home/runner/work/propr/propr/packages/core/src/config/configManagerSummarization.ts:101:19) {
#   code: 'SQLITE_ERROR'
# }
# Subtest: /api/status reports disconnected intake status for polling when the daemon is stopped
ok 56 - /api/status reports disconnected intake status for polling when the daemon is stopped
  ---
  duration_ms: 3.886689
  type: 'test'
  ...
# Subtest: /api/status includes routing state published by the daemon
ok 57 - /api/status includes routing state published by the daemon
  ---
  duration_ms: 1.724547
  type: 'test'
  ...
# Subtest: /api/status exposes only validated UI-safe Connect account fields
ok 58 - /api/status exposes only validated UI-safe Connect account fields
  ---
  duration_ms: 3.473166
  type: 'test'
  ...
# Error loading summarization warnings: SqliteError: select * from `system_configs` where `key` = 'summarization_runtime_state' limit 1 - no such table: system_configs
#     at Database.prepare (/home/runner/work/propr/propr/node_modules/better-sqlite3/lib/methods/wrappers.js:5:21)
#     at Client_BetterSQLite3._query (/home/runner/work/propr/propr/node_modules/knex/lib/dialects/better-sqlite3/index.js:35:34)
#     at executeQuery (/home/runner/work/propr/propr/node_modules/knex/lib/execution/internal/query-executioner.js:37:17)
#     at Client_BetterSQLite3.query (/home/runner/work/propr/propr/node_modules/knex/lib/client.js:154:12)
#     at Runner.query (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:141:36)
#     at ensureConnectionCallback (/home/runner/work/propr/propr/node_modules/knex/lib/execution/internal/ensure-connection-callback.js:13:17)
#     at Runner.ensureConnection (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:318:20)
#     at async Runner.run (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:30:19)
#     at async getConfigWithClient (/home/runner/work/propr/propr/packages/core/src/config/configStore.ts:17:24)
#     at async loadSummarizationRuntimeState (/home/runner/work/propr/propr/packages/core/src/config/configManagerSummarization.ts:101:19) {
#   code: 'SQLITE_ERROR'
# }
# Error loading summarization warnings: SqliteError: select * from `system_configs` where `key` = 'summarization_runtime_state' limit 1 - no such table: system_configs
#     at Database.prepare (/home/runner/work/propr/propr/node_modules/better-sqlite3/lib/methods/wrappers.js:5:21)
#     at Client_BetterSQLite3._query (/home/runner/work/propr/propr/node_modules/knex/lib/dialects/better-sqlite3/index.js:35:34)
#     at executeQuery (/home/runner/work/propr/propr/node_modules/knex/lib/execution/internal/query-executioner.js:37:17)
#     at Client_BetterSQLite3.query (/home/runner/work/propr/propr/node_modules/knex/lib/client.js:154:12)
#     at Runner.query (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:141:36)
#     at ensureConnectionCallback (/home/runner/work/propr/propr/node_modules/knex/lib/execution/internal/ensure-connection-callback.js:13:17)
#     at Runner.ensureConnection (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:318:20)
#     at async Runner.run (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:30:19)
#     at async getConfigWithClient (/home/runner/work/propr/propr/packages/core/src/config/configStore.ts:17:24)
#     at async loadSummarizationRuntimeState (/home/runner/work/propr/propr/packages/core/src/config/configManagerSummarization.ts:101:19) {
#   code: 'SQLITE_ERROR'
# }
# Error loading summarization warnings: SqliteError: select * from `system_configs` where `key` = 'summarization_runtime_state' limit 1 - no such table: system_configs
#     at Database.prepare (/home/runner/work/propr/propr/node_modules/better-sqlite3/lib/methods/wrappers.js:5:21)
#     at Client_BetterSQLite3._query (/home/runner/work/propr/propr/node_modules/knex/lib/dialects/better-sqlite3/index.js:35:34)
#     at executeQuery (/home/runner/work/propr/propr/node_modules/knex/lib/execution/internal/query-executioner.js:37:17)
#     at Client_BetterSQLite3.query (/home/runner/work/propr/propr/node_modules/knex/lib/client.js:154:12)
#     at Runner.query (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:141:36)
#     at ensureConnectionCallback (/home/runner/work/propr/propr/node_modules/knex/lib/execution/internal/ensure-connection-callback.js:13:17)
#     at Runner.ensureConnection (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:318:20)
#     at async Runner.run (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:30:19)
#     at async getConfigWithClient (/home/runner/work/propr/propr/packages/core/src/config/configStore.ts:17:24)
#     at async loadSummarizationRuntimeState (/home/runner/work/propr/propr/packages/core/src/config/configManagerSummarization.ts:101:19) {
#   code: 'SQLITE_ERROR'
# }
# Error loading summarization warnings: SqliteError: select * from `system_configs` where `key` = 'summarization_runtime_state' limit 1 - no such table: system_configs
#     at Database.prepare (/home/runner/work/propr/propr/node_modules/better-sqlite3/lib/methods/wrappers.js:5:21)
#     at Client_BetterSQLite3._query (/home/runner/work/propr/propr/node_modules/knex/lib/dialects/better-sqlite3/index.js:35:34)
#     at executeQuery (/home/runner/work/propr/propr/node_modules/knex/lib/execution/internal/query-executioner.js:37:17)
#     at Client_BetterSQLite3.query (/home/runner/work/propr/propr/node_modules/knex/lib/client.js:154:12)
#     at Runner.query (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:141:36)
#     at ensureConnectionCallback (/home/runner/work/propr/propr/node_modules/knex/lib/execution/internal/ensure-connection-callback.js:13:17)
#     at Runner.ensureConnection (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:318:20)
#     at async Runner.run (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:30:19)
#     at async getConfigWithClient (/home/runner/work/propr/propr/packages/core/src/config/configStore.ts:17:24)
#     at async loadSummarizationRuntimeState (/home/runner/work/propr/propr/packages/core/src/config/configManagerSummarization.ts:101:19) {
#   code: 'SQLITE_ERROR'
# }
# Error loading summarization warnings: SqliteError: select * from `system_configs` where `key` = 'summarization_runtime_state' limit 1 - no such table: system_configs
#     at Database.prepare (/home/runner/work/propr/propr/node_modules/better-sqlite3/lib/methods/wrappers.js:5:21)
#     at Client_BetterSQLite3._query (/home/runner/work/propr/propr/node_modules/knex/lib/dialects/better-sqlite3/index.js:35:34)
#     at executeQuery (/home/runner/work/propr/propr/node_modules/knex/lib/execution/internal/query-executioner.js:37:17)
#     at Client_BetterSQLite3.query (/home/runner/work/propr/propr/node_modules/knex/lib/client.js:154:12)
#     at Runner.query (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:141:36)
#     at ensureConnectionCallback (/home/runner/work/propr/propr/node_modules/knex/lib/execution/internal/ensure-connection-callback.js:13:17)
#     at Runner.ensureConnection (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:318:20)
#     at async Runner.run (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:30:19)
#     at async getConfigWithClient (/home/runner/work/propr/propr/packages/core/src/config/configStore.ts:17:24)
#     at async loadSummarizationRuntimeState (/home/runner/work/propr/propr/packages/core/src/config/configManagerSummarization.ts:101:19) {
#   code: 'SQLITE_ERROR'
# }
# Error loading summarization warnings: SqliteError: select * from `system_configs` where `key` = 'summarization_runtime_state' limit 1 - no such table: system_configs
#     at Database.prepare (/home/runner/work/propr/propr/node_modules/better-sqlite3/lib/methods/wrappers.js:5:21)
#     at Client_BetterSQLite3._query (/home/runner/work/propr/propr/node_modules/knex/lib/dialects/better-sqlite3/index.js:35:34)
#     at executeQuery (/home/runner/work/propr/propr/node_modules/knex/lib/execution/internal/query-executioner.js:37:17)
#     at Client_BetterSQLite3.query (/home/runner/work/propr/propr/node_modules/knex/lib/client.js:154:12)
#     at Runner.query (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:141:36)
#     at ensureConnectionCallback (/home/runner/work/propr/propr/node_modules/knex/lib/execution/internal/ensure-connection-callback.js:13:17)
#     at Runner.ensureConnection (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:318:20)
#     at async Runner.run (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:30:19)
#     at async getConfigWithClient (/home/runner/work/propr/propr/packages/core/src/config/configStore.ts:17:24)
#     at async loadSummarizationRuntimeState (/home/runner/work/propr/propr/packages/core/src/config/configManagerSummarization.ts:101:19) {
#   code: 'SQLITE_ERROR'
# }
# Error loading summarization warnings: SqliteError: select * from `system_configs` where `key` = 'summarization_runtime_state' limit 1 - no such table: system_configs
#     at Database.prepare (/home/runner/work/propr/propr/node_modules/better-sqlite3/lib/methods/wrappers.js:5:21)
#     at Client_BetterSQLite3._query (/home/runner/work/propr/propr/node_modules/knex/lib/dialects/better-sqlite3/index.js:35:34)
#     at executeQuery (/home/runner/work/propr/propr/node_modules/knex/lib/execution/internal/query-executioner.js:37:17)
#     at Client_BetterSQLite3.query (/home/runner/work/propr/propr/node_modules/knex/lib/client.js:154:12)
#     at Runner.query (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:141:36)
#     at ensureConnectionCallback (/home/runner/work/propr/propr/node_modules/knex/lib/execution/internal/ensure-connection-callback.js:13:17)
#     at Runner.ensureConnection (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:318:20)
#     at async Runner.run (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:30:19)
#     at async getConfigWithClient (/home/runner/work/propr/propr/packages/core/src/config/configStore.ts:17:24)
#     at async loadSummarizationRuntimeState (/home/runner/work/propr/propr/packages/core/src/config/configManagerSummarization.ts:101:19) {
#   code: 'SQLITE_ERROR'
# }
# Error loading summarization warnings: SqliteError: select * from `system_configs` where `key` = 'summarization_runtime_state' limit 1 - no such table: system_configs
#     at Database.prepare (/home/runner/work/propr/propr/node_modules/better-sqlite3/lib/methods/wrappers.js:5:21)
#     at Client_BetterSQLite3._query (/home/runner/work/propr/propr/node_modules/knex/lib/dialects/better-sqlite3/index.js:35:34)
#     at executeQuery (/home/runner/work/propr/propr/node_modules/knex/lib/execution/internal/query-executioner.js:37:17)
#     at Client_BetterSQLite3.query (/home/runner/work/propr/propr/node_modules/knex/lib/client.js:154:12)
#     at Runner.query (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:141:36)
#     at ensureConnectionCallback (/home/runner/work/propr/propr/node_modules/knex/lib/execution/internal/ensure-connection-callback.js:13:17)
#     at Runner.ensureConnection (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:318:20)
#     at async Runner.run (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:30:19)
#     at async getConfigWithClient (/home/runner/work/propr/propr/packages/core/src/config/configStore.ts:17:24)
#     at async loadSummarizationRuntimeState (/home/runner/work/propr/propr/packages/core/src/config/configManagerSummarization.ts:101:19) {
#   code: 'SQLITE_ERROR'
# }
# Error loading summarization warnings: SqliteError: select * from `system_configs` where `key` = 'summarization_runtime_state' limit 1 - no such table: system_configs
#     at Database.prepare (/home/runner/work/propr/propr/node_modules/better-sqlite3/lib/methods/wrappers.js:5:21)
#     at Client_BetterSQLite3._query (/home/runner/work/propr/propr/node_modules/knex/lib/dialects/better-sqlite3/index.js:35:34)
#     at executeQuery (/home/runner/work/propr/propr/node_modules/knex/lib/execution/internal/query-executioner.js:37:17)
#     at Client_BetterSQLite3.query (/home/runner/work/propr/propr/node_modules/knex/lib/client.js:154:12)
#     at Runner.query (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:141:36)
#     at ensureConnectionCallback (/home/runner/work/propr/propr/node_modules/knex/lib/execution/internal/ensure-connection-callback.js:13:17)
#     at Runner.ensureConnection (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:318:20)
#     at async Runner.run (/home/runner/work/propr/propr/node_modules/knex/lib/execution/runner.js:30:19)
#     at async getConfigWithClient (/home/runner/work/propr/propr/packages/core/src/config/configStore.ts:17:24)
#     at async loadSummarizationRuntimeState (/home/runner/work/propr/propr/packages/core/src/config/configManagerSummarization.ts:101:19) {
#   code: 'SQLITE_ERROR'
# }
# Subtest: /api/status rejects impossible account dates and preserves valid leap-day instants
ok 59 - /api/status rejects impossible account dates and preserves valid leap-day instants
  ---
  duration_ms: 5.981645
  type: 'test'
  ...
# Subtest: /api/status drops malformed or disconnected Connect account state without assuming Community
ok 60 - /api/status drops malformed or disconnected Connect account state without assuming Community
  ---
  duration_ms: 2.597788
  type: 'test'
  ...
# Subtest: /api/status does not expose Co

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

This PR adds remote-instance discovery, browser pairing, bearer-authenticated REST/Socket.IO transport, secure-profile integration, and token revocation. It needs significant rework before merge because plaintext tokens cross into the renderer, pairing expiry cannot interrupt a stalled poll, and Socket.IO authorization changes can incorrectly delete valid credentials. The two current checks are still pending, and the PR must remain based on 1953-epic-desktop-transport-api, not main.

REST profile isolation — Bearer authentication replaces any supplied authorization header and forces credentials: 'omit', preventing cookie identity from overriding a named profile.

Scoped self-revocation/api/desktop/tokens/current requires instance-token authentication and revokes the exact token identified by the authentication middleware.

IPC sender checks remain centralized — The newly changed profile-switch and removal handlers still run through the existing trusted-renderer validation wrapper.

Merge blockers

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

F1: 🔴 Tokens enter the renderer

  • Required behavior: Desktop instance tokens must remain in main-process custody and must not be exposed to renderer JavaScript.
  • Evidence: propr-ui/src/desktop/electronAdapters.ts:48 reads and returns the plaintext credential through the renderer-visible bridge; the same renderer also receives pairing.token and passes it back through bridge.credentials.write around line 253.
  • Minimum fix: Move pairing completion, token persistence, token lookup, REST authorization injection, and Socket.IO authentication injection behind a main-process API. The renderer should receive only success/status results and must never receive a credential value.

F2: 🔴 Pairing can outlive expiry

  • Required behavior: Expiry-aware pairing must terminate at its advertised deadline, including while a poll request or wait is in progress.
  • Evidence: packages/client/src/desktopPairing.ts:150 starts each poll with only the caller’s cancellation signal and no timeout or deadline signal; a server that accepts the request but never responds leaves the flow pending indefinitely past expiresAt.
  • Minimum fix: Create an expiry deadline signal for the entire completion flow, clamp sleeps to the remaining lifetime, and abort an in-flight poll when the deadline arrives, translating that abort to PAIRING_EXPIRED unless the caller explicitly cancelled.

F3: 🔴 Authorization changes erase valid tokens

  • Required behavior: Revocation handling must remove credentials only when the instance token is actually invalid or expired; ordinary authorization changes or transient revalidation failures must not destroy a valid pairing.
  • Evidence: propr-ui/src/contexts/SocketProvider.tsx:52 converts every authentication:error code into a desktop-access-invalid event, while propr-ui/src/desktop/DesktopExperience.tsx:280 ignores the code and unconditionally calls clearCredentials. The server emits this event for AUTHORIZATION_CHANGED, USER_NOT_WHITELISTED, and generic authentication failures as well as token revocation.
  • Minimum fix: Preserve and classify the error code. Delete the stored credential only for definitive token-invalidity codes such as INVALID_INSTANCE_TOKEN; handle authorization changes by re-probing or reconnecting without clearing the token, and treat transient failures as retryable.

Suggestions

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

S1: 🟢 Exercise real profile switching

Add a packaged Electron integration test covering two profiles on the same API origin across switching and restart, including REST identity, Socket.IO identity, cookies, renderer storage, and an in-flight request. The unit coverage is useful, but it does not currently validate the complete isolation journey; this is optional verification rather than a separate code defect.

S2: 🟢 Tighten protocol value bounds

Consider bounding polling intervals and requiring completed-token expiresAt values to be valid timestamps. The current parser accepts arbitrarily large or fractional intervals and any nonempty expiry string. The present server emits controlled values, so this is defense-in-depth for untrusted or future-compatible instances.

S3: 🟢 Advertise the approval origin

A future protocol revision could advertise the instance’s canonical approval origin and let the client bind approvalUrl to it, rather than accepting any credential-free HTTPS URL. Existing scheme and user-info checks prevent dangerous URL classes, while an explicit origin contract would further reduce phishing opportunities without assuming that the API and hosted UI share an origin.

Score

The implementation has strong transport and server-side revocation foundations, but the renderer token exposure violates a central security boundary and the two lifecycle defects make pairing and credential handling unreliable. These blockers prevent merge regardless of the pending checks.

Score: 4/10


🤖 Review Details

  • Model: GPT-5.6 Sol
  • Time: 3m 52s
  • Tokens: 71,108 (61,778 in / 9,330 out)
  • Cost: $0.54

View Task


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

🤖 Review by ProPR

@propr-dev

propr-dev Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor Author

Applied the requested follow-up changes in commit 5330dcc

Summary of Changes

Fixed the PR build blocker in DesktopExperience.tsx.

The formatting-only change reduces the file to the 400-line ESLint limit without changing behavior.

Validated successfully:

  • UI lint with zero warnings
  • UI typecheck
  • 23 desktop tests
  • Production UI build
  • git diff --check

No commit was created.


🤖 Implementation Details

  • Model: GPT-5.6 Sol (high)
  • Requested By: @github-actions[bot]
  • Time: 4m 24s
  • Tokens: 976,005 (969,818 in / 6,187 out)
  • Cost: $1.24
  • 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: 5464276486✓

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

[2/321] apps/desktop/src/ipc.test.ts
TAP version 13
# Subtest: desktop session IPC operations
    # Subtest: logs out through the active Electron session with credentials and without following redirects
    ok 1 - logs out through the active Electron session with credentials and without following redirects
      ---
      duration_ms: 24.030868
      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.707637
      type: 'test'
      ...
    # Subtest: clears only cookies for normalized profile origins when profiles switch
    ok 3 - clears only cookies for normalized profile origins when profiles switch
      ---
      duration_ms: 0.562425
      type: 'test'
      ...
    1..3
ok 1 - desktop session IPC operations
  ---
  duration_ms: 26.512052
  type: 'suite'
  ...
1..1
# tests 3
# suites 1
# pass 3
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 276.660003

[3/321] 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.533306
      type: 'test'
      ...
    # Subtest: maps profile and credential operations to fixed channels
    ok 2 - maps profile and credential operations to fixed channels
      ---
      duration_ms: 0.540213
      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.317154
      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.269205
      type: 'test'
      ...
    1..4
ok 1 - desktop preload bridge
  ---
  duration_ms: 3.850503
  type: 'suite'
  ...
1..1
# tests 4
# suites 1
# pass 4
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 249.666814

[4/321] 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: 14.959792
      type: 'test'
      ...
    # Subtest: encrypts credentials before writing app-owned storage
    ok 2 - encrypts credentials before writing app-owned storage
      ---
      duration_ms: 8.406276
      type: 'test'
      ...
    # Subtest: serializes concurrent credential writes with last-write semantics
    ok 3 - serializes concurrent credential writes with last-write semantics
      ---
      duration_ms: 6.949406
      type: 'test'
      ...
    # Subtest: orders concurrent credential writes and removals by invocation
    ok 4 - orders concurrent credential writes and removals by invocation
      ---
      duration_ms: 7.406223
      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: 5.475472
      type: 'test'
      ...
    # Subtest: rejects unsafe endpoints and path-like profile identifiers
    ok 6 - rejects unsafe endpoints and path-like profile identifiers
      ---
      duration_ms: 8.828521
      type: 'test'
      ...
    1..6
ok 1 - desktop profile store
  ---
  duration_ms: 53.980025
  type: 'suite'
  ...
1..1
# tests 6
# suites 1
# pass 6
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 315.192241

[5/321] 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.181176
      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.274715
      type: 'test'
      ...
    # Subtest: requires an exact loopback development origin
    ok 3 - requires an exact loopback development origin
      ---
      duration_ms: 0.263344
      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.29854
      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.289633
      type: 'test'
      ...
    # Subtest: publishes a restrictive production policy
    ok 6 - publishes a restrictive production policy
      ---
      duration_ms: 0.28788
      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.270718
      type: 'test'
      ...
    1..7
ok 1 - desktop URL security
  ---
  duration_ms: 4.047513
  type: 'suite'
  ...
1..1
# tests 7
# suites 1
# pass 7
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 255.193327

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

[7/321] 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.237361
      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.14915
      type: 'test'
      ...
    1..2
ok 1 - desktop BrowserWindow security
  ---
  duration_ms: 2.4737
  type: 'suite'
  ...
1..1
# tests 2
# suites 1
# pass 2
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 245.573872

[8/321] 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.006179
      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.30631
      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.242987
      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.633598
      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.489368
      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.723252
      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.667271
      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.709461
      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.663975
      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.523171
      type: 'test'
      ...
    1..10
ok 1 - agent login session manager
  ---
  duration_ms: 13.341223
  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.825769
      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.637135
      type: 'test'
      ...
    # Subtest: rejects interactive login for an unsupported agent type
    ok 3 - rejects interactive login for an unsupported agent type
      ---
      duration_ms: 0.510607
      type: 'test'
      ...
    1..3
ok 2 - agent login routes
  ---
  duration_ms: 2.146907
  type: 'suite'
  ...
# [2026-08-29 18:59:47.081 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-FjbhiQ/008-agentLoginRoutes.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 18:59:47.092 +0000] �[32mINFO�[39m: �[36mSQLite database connection closed�[39m
# [2026-08-29 18:59:47.092 +0000] �[32mINFO�[39m: �[36mShutting down queue...�[39m
# [2026-08-29 18:59:47.092 +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 2324.555215

[9/321] 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.378182
      type: 'test'
      ...
    # Subtest: persists a failed state when queue submission fails
    ok 2 - persists a failed state when queue submission fails
      ---
      duration_ms: 0.9012
      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.776957
      type: 'test'
      ...
    # Subtest: times out slow runtime package validation before queueing
    ok 4 - times out slow runtime package validation before queueing
      ---
      duration_ms: 5.629358
      type: 'test'
      ...
    # Subtest: returns package suggestions from configured runtimes
    ok 5 - returns package suggestions from configured runtimes
      ---
      duration_ms: 0.811241
      type: 'test'
      ...
    # Subtest: enforces PROPR_ADMIN_USERS when configured
    ok 6 - enforces PROPR_ADMIN_USERS when configured
      ---
      duration_ms: 0.490991
      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.377297
      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.488256
      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.557867
      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: 6.583758
      type: 'test'
      ...
    # Subtest: resolves the runtime build queue lazily when queueing
    ok 11 - resolves the runtime build queue lazily when queueing
      ---
      duration_ms: 0.67167
      type: 'test'
      ...
    # Subtest: reports apply load failures through the route response
    ok 12 - reports apply load failures through the route response
      ---
      duration_ms: 0.358562
      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.439985
      type: 'test'
      ...
    # Subtest: rejects runtime verification without runtime-management permission
    ok 14 - rejects runtime verification without runtime-management permission
      ---
      duration_ms: 0.272932
      type: 'test'
      ...
    1..14
ok 1 - agent runtime package routes
  ---
  duration_ms: 24.461539
  type: 'suite'
  ...
# [2026-08-29 18:59:49.189 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-FjbhiQ/009-agentRuntimeRoutes.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 18:59:49.211 +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 2099.611633

[10/321] 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: 8.226389
  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.784179
  type: 'test'
  ...
# Subtest: temporary cleanup refuses paths outside its configured root
ok 3 - temporary cleanup refuses paths outside its configured root
  ---
  duration_ms: 0.659967
  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.524584
  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.63376
  type: 'test'
  ...
# [2026-08-29 18:59:51.439 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-FjbhiQ/010-attachmentUploadCleanup.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 18:59:51.457 +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 2124.911345

[11/321] 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.528893
  type: 'test'
  ...
# Subtest: ensureAuthenticated refreshes an expired GitHub token before continuing
ok 2 - ensureAuthenticated refreshes an expired GitHub token before continuing
  ---
  duration_ms: 5.351898
  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.226451
  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: 11.047972
  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.761007
  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.850155
  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.230377
  type: 'test'
  ...
# Subtest: GitHub route auth error emits TOKEN_REFRESHED after a successful refresh
ok 8 - GitHub route auth error emits TOKEN_REFRESHED after a successful refresh
  ---
  duration_ms: 0.949992
  type: 'test'
  ...
# [2026-08-29 18:59:54.179 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-FjbhiQ/011-authGithubTokens.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 18:59:54.198 +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 2596.389258

[12/321] 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.466201
  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.683551
  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.826953
  type: 'test'
  ...
# Subtest: auth redirect allowlist permits leading-dot COOKIE_DOMAIN subdomains
ok 4 - auth redirect allowlist permits leading-dot COOKIE_DOMAIN subdomains
  ---
  duration_ms: 4.050487
  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.301909
  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.130779
  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.19002
  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.157553
  type: 'test'
  ...
# Subtest: auth redirect allowlist permits configured local IP literals
ok 9 - auth redirect allowlist permits configured local IP literals
  ---
  duration_ms: 5.450534
  type: 'test'
  ...
# Subtest: auth redirect allowlist ignores malformed additional host entries
ok 10 - auth redirect allowlist ignores malformed additional host entries
  ---
  duration_ms: 5.194102
  type: 'test'
  ...
# [2026-08-29 18:59:56.870 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-FjbhiQ/012-authRedirect.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 18:59:56.957 +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 2577.891485

[13/321] 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: 1577.614422
  type: 'test'
  ...
# [2026-08-29 18:59:59.129 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-FjbhiQ/013-configAgentValidation.antigravity.test.ts/propr.test.sqlite"
#     environment: "test"
1..1
# tests 1
# suites 0
# pass 1
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 2162.421071

[14/321] 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.511312
  type: 'test'
  ...
# Subtest: API config subscription reloads repository updates
ok 2 - API config subscription reloads repository updates
  ---
  duration_ms: 0.457458
  type: 'test'
  ...
# Subtest: API config subscription ignores unrelated and malformed events
ok 3 - API config subscription ignores unrelated and malformed events
  ---
  duration_ms: 0.324428
  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.407324
  type: 'test'
  ...
1..4
# tests 4
# suites 0
# pass 4
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 248.44154

[15/321] packages/api/test/configRoutesIndexingSummarization.test.ts
TAP version 13
# [2026-08-29 19:00:01.748 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-FjbhiQ/015-configRoutesIndexingSummarization.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 19:00:01.779 +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: 1838.39302
  type: 'test'
  ...
# Subtest: postSummarizationSettings rejects enabled summarization without a primary alias
ok 2 - postSummarizationSettings rejects enabled summarization without a primary alias
  ---
  duration_ms: 3.507599
  type: 'test'
  ...
# [2026-08-29 19:00:02.034 +0000] �[32mINFO�[39m: �[36mDatabase migrations completed successfully�[39m
# [2026-08-29 19:00:02.035 +0000] �[32mINFO�[39m: �[36mSuccessfully loaded agents configuration�[39m
#     agentCount: 2
# [2026-08-29 19:00:02.036 +0000] �[32mINFO�[39m: �[36mSuccessfully loaded summarization settings�[39m
#     summarization: {
#       "enabled": false,
#       "agent_alias": "",
#       "fallback_agent_alias": "",
#       "custom_prompt": ""
#     }
# [2026-08-29 19:00:02.037 +0000] �[32mINFO�[39m: �[36mSuccessfully saved summarization settings�[39m
#     summarization: {
#       "enabled": true,
#       "agent_alias": "codex:gpt-5.5",
#       "fallback_agent_alias": "fallback:gpt-5.4",
#       "custom_prompt": ""
#     }
# [2026-08-29 19:00:02.038 +0000] �[32mINFO�[39m: �[36mSuccessfully loaded summarization settings�[39m
#     summarization: {
#       "enabled": true,
#       "agent_alias": "codex:gpt-5.5",
#       "fallback_agent_alias": "fallback:gpt-5.4",
#       "custom_prompt": ""
#     }
# [2026-08-29 19:00:02.048 +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 2267.692203

[16/321] 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.508059
  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.257703
  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.377752
  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.365646
  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.208811
  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.219592
  type: 'test'
  ...
# Subtest: explicit custom GitHub web auth remains supported
ok 7 - explicit custom GitHub web auth remains supported
  ---
  duration_ms: 0.226776
  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.21835
  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.676179
  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: 29.905051
  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: 1.289739
  type: 'test'
  ...
1..11
# tests 11
# suites 0
# pass 11
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 322.56134

[17/321] packages/api/test/connectAuthCallback.test.ts
TAP version 13
# [2026-08-29 19:00:05.062 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-FjbhiQ/017-connectAuthCallback.test.ts/propr.test.sqlite"
#     environment: "test"
# Subtest: Connect login preserves a validated redirect_to across Passport session regeneration
ok 1 - Connect login preserves a validated redirect_to across Passport session regeneration
  ---
  duration_ms: 3040.407701
  type: 'test'
  ...
# [2026-08-29 19:00:08.099 +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 5606.120233

[18/321] 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.072572
  type: 'test'
  ...
# Subtest: CORS rejects unrelated origins under proxy mode
ok 2 - CORS rejects unrelated origins under proxy mode
  ---
  duration_ms: 0.237926
  type: 'test'
  ...
# Subtest: CORS allows requests with no origin
ok 3 - CORS allows requests with no origin
  ---
  duration_ms: 0.14388
  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.166022
  type: 'test'
  ...
# Subtest: CORS allows HTTP(S) loopback origins for development
ok 5 - CORS allows HTTP(S) loopback origins for development
  ---
  duration_ms: 0.261751
  type: 'test'
  ...
# Subtest: CORS rejects unsafe schemes and non-loopback hosts
ok 6 - CORS rejects unsafe schemes and non-loopback hosts
  ---
  duration_ms: 0.170099
  type: 'test'
  ...
# Subtest: CORS allows COOKIE_DOMAIN subdomains for preview environments
ok 7 - CORS allows COOKIE_DOMAIN subdomains for preview environments
  ---
  duration_ms: 0.2082
  type: 'test'
  ...
# Subtest: CORS preserves http COOKIE_DOMAIN preview compatibility
ok 8 - CORS preserves http COOKIE_DOMAIN preview compatibility
  ---
  duration_ms: 0.131457
  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.435036
  type: 'test'
  ...
# Subtest: Express CORS boundary is sanitized in development mode
ok 10 - Express CORS boundary is sanitized in development mode
  ---
  duration_ms: 74.776403
  type: 'test'
  ...
# Subtest: Express CORS boundary is sanitized in production mode
ok 11 - Express CORS boundary is sanitized in production mode
  ---
  duration_ms: 29.593386
  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.351008
  type: 'test'
  ...
1..12
# tests 12
# suites 0
# pass 12
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 574.25907

[19/321] 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.447751
  type: 'test'
  ...
# Subtest: demoModeReadOnlyMiddleware blocks auth metadata mutations
ok 2 - demoModeReadOnlyMiddleware blocks auth metadata mutations
  ---
  duration_ms: 2.083637
  type: 'test'
  ...
# Subtest: isDemoMode accepts common truthy environment values
ok 3 - isDemoMode accepts common truthy environment values
  ---
  duration_ms: 1.148744
  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.410154
  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.565537
  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: 57.081453
  type: 'test'
  ...
# Subtest: ensureAuthenticated attaches the synthetic demo user
ok 7 - ensureAuthenticated attaches the synthetic demo user
  ---
  duration_ms: 1.35979
  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.377003
  type: 'test'
  ...
# Subtest: demo repository metadata resolves enabled configured repositories
ok 9 - demo repository metadata resolves enabled configured repositories
  ---
  duration_ms: 9.401441
  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.34626
  type: 'test'
  ...
# [2026-08-29 19:00:11.609 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-FjbhiQ/019-demoMode.test.ts/propr.test.sqlite"
#     environment: "test"
# Subtest: /api/github/repos returns configured and persisted repositories in demo mode
ok 11 - /api/github/repos returns configured and persisted repositories in demo mode
  ---
  duration_ms: 257.283131
  type: 'test'
  ...
# [2026-08-29 19:00:11.944 +0000] �[32mINFO�[39m: �[36mSuccessfully saved monitored repositories�[39m
#     repos: [
#       {
#         "id": "1",
#         "name": "integry/propr",
#         "enabled": true
#       },
#       {
#         "id": "2",
#         "name": "integry/private",
#         "enabled": true
#       },
#       {
#         "id": "3",
#         "name": "integry/disabled",
#         "enabled": false
#       }
#     ]
# [2026-08-29 19:00:11.945 +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.468095
  type: 'test'
  ...
# Subtest: demo repository metadata ignores malformed database repository names
ok 13 - demo repository metadata ignores malformed database repository names
  ---
  duration_ms: 2.313448
  type: 'test'
  ...
# [2026-08-29 19:00:11.956 +0000] �[32mINFO�[39m: �[36mSuccessfully loaded all monitored repositories�[39m
#     total_repos: 0
# Subtest: planner demo reads use the curated database without owner or repository allowlists
ok 14 - planner demo reads use the curated database without owner or repository allowlists
  ---
  duration_ms: 10.132972
  type: 'test'
  ...
# [2026-08-29 19:00:11.967 +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: 9.154216
  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: 9.536934
  type: 'test'
  ...
1..16
# tests 16
# suites 0
# pass 16
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 2810.084285

[20/321] packages/api/test/desktopAuth.test.ts
TAP version 13
# [desktop-auth] {
#   action: 'pairing_started',
#   actorUserId: undefined,
#   pairingId: 'dpr_Rvj4VlileMtOU1ElyPh-TA',
#   tokenId: undefined,
#   clientName: 'Work Laptop'
# }
# [desktop-auth] {
#   action: 'pairing_started',
#   actorUserId: undefined,
#   pairingId: 'dpr_RuAH19sxDPI5IXR13n_iEQ',
#   tokenId: undefined,
#   clientName: 'Windows desktop'
# }
# [desktop-auth] {
#   action: 'pairing_started',
#   actorUserId: undefined,
#   pairingId: 'dpr_dTyNvfTv7Lvq9r6kxX9mKQ',
#   tokenId: undefined,
#   clientName: 'MacBook Pro'
# }
# [desktop-auth] {
#   action: 'pairing_approved',
#   actorUserId: '101',
#   pairingId: 'dpr_dTyNvfTv7Lvq9r6kxX9mKQ',
#   tokenId: undefined,
#   clientName: 'MacBook Pro'
# }
# [desktop-auth] {
#   action: 'token_issued',
#   actorUserId: '101',
#   pairingId: 'dpr_dTyNvfTv7Lvq9r6kxX9mKQ',
#   tokenId: '0c7af318-0a09-465b-9aa3-14763ef9c06e',
#   clientName: 'MacBook Pro'
# }
# [desktop-auth] {
#   action: 'pairing_started',
#   actorUserId: undefined,
#   pairingId: 'dpr_GZXnWMrH-LUg2xU0L-yI1w',
#   tokenId: undefined,
#   clientName: 'Linux workstation'
# }
# [desktop-auth] {
#   action: 'pairing_approved',
#   actorUserId: '101',
#   pairingId: 'dpr_GZXnWMrH-LUg2xU0L-yI1w',
#   tokenId: undefined,
#   clientName: 'Linux workstation'
# }
# [desktop-auth] {
#   action: 'pairing_started',
#   actorUserId: undefined,
#   pairingId: 'dpr_9LioXw3p7CazoaIuqGy_xw',
#   tokenId: undefined,
#   clientName: 'Old laptop'
# }
# [desktop-auth] {
#   action: 'pairing_started',
#   actorUserId: undefined,
#   pairingId: 'dpr_a8kRmTGg-2uOqUYDwaHkGg',
#   tokenId: undefined,
#   clientName: 'Desktop app'
# }
# [desktop-auth] {
#   action: 'pairing_approved',
#   actorUserId: '101',
#   pairingId: 'dpr_a8kRmTGg-2uOqUYDwaHkGg',
#   tokenId: undefined,
#   clientName: 'Desktop app'
# }
# [desktop-auth] {
#   action: 'token_issued',
#   actorUserId: '101',
#   pairingId: 'dpr_a8kRmTGg-2uOqUYDwaHkGg',
#   tokenId: 'a282482c-2a50-4417-8922-d7dcc3aa8883',
#   clientName: 'Desktop app'
# }
# [desktop-auth] {
#   action: 'token_revoked',
#   actorUserId: '101',
#   pairingId: undefined,
#   tokenId: 'a282482c-2a50-4417-8922-d7dcc3aa8883',
#   clientName: undefined
# }
# [desktop-auth] {
#   action: 'pairing_started',
#   actorUserId: undefined,
#   pairingId: 'dpr_tR4_ZGI7pOE7EIBb0a8Ahw',
#   tokenId: undefined,
#   clientName: 'Desktop app'
# }
# [desktop-auth] {
#   action: 'pairing_approved',
#   actorUserId: '101',
#   pairingId: 'dpr_tR4_ZGI7pOE7EIBb0a8Ahw',
#   tokenId: undefined,
#   clientName: 'Desktop app'
# }
# [desktop-auth] {
#   action: 'token_issued',
#   actorUserId: '101',
#   pairingId: 'dpr_tR4_ZGI7pOE7EIBb0a8Ahw',
#   tokenId: '844cfefd-4d5a-49d4-9fd4-715523a60c9e',
#   clientName: 'Desktop app'
# }
# [desktop-auth] {
#   action: 'pairing_started',
#   actorUserId: undefined,
#   pairingId: 'dpr_or78eM0zoZNq2tTQvKguDg',
#   tokenId: undefined,
#   clientName: 'Desktop app'
# }
# [desktop-auth] {
#   action: 'pairing_approved',
#   actorUserId: '101',
#   pairingId: 'dpr_or78eM0zoZNq2tTQvKguDg',
#   tokenId: undefined,
#   clientName: 'Desktop app'
# }
# [desktop-auth] {
#   action: 'token_issued',
#   actorUserId: '101',
#   pairingId: 'dpr_or78eM0zoZNq2tTQvKguDg',
#   tokenId: 'e6cb7ffc-47c3-476e-8b43-a113195bf57a',
#   clientName: 'Desktop app'
# }
# [desktop-auth] {
#   action: 'token_revoked',
#   actorUserId: '101',
#   pairingId: undefined,
#   tokenId: 'e6cb7ffc-47c3-476e-8b43-a113195bf57a',
#   clientName: undefined
# }
# Subtest: desktop browser pairing
    # Subtest: stores only a device-secret hash and builds a fixed trusted approval URL
    ok 1 - stores only a device-secret hash and builds a fixed trusted approval URL
      ---
      duration_ms: 15.84843
      type: 'test'
      ...
    # Subtest: uses the configured API browser entry and preserves only a managed hosted tunnel selector
    ok 2 - uses the configured API browser entry and preserves only a managed hosted tunnel selector
      ---
      duration_ms: 4.332334
      type: 'test'
      ...
    # Subtest: issues an opaque token once, resolves its owner, and never stores plaintext credentials
    ok 3 - issues an opaque token once, resolves its owner, and never stores plaintext credentials
      ---
      duration_ms: 14.966466
      type: 'test'
      ...
    # Subtest: rejects the wrong secret without revealing pairing state
    ok 4 - rejects the wrong secret without revealing pairing state
      ---
      duration_ms: 7.047266
      type: 'test'
      ...
    # Subtest: expires unapproved pairings and cleans retained expired records
    ok 5 - expires unapproved pairings and cleans retained expired records
      ---
      duration_ms: 6.881244
      type: 'test'
      ...
    # Subtest: rejects unsafe names and non-HTTPS approval origins
    ok 6 - rejects unsafe names and non-HTTPS approval origins
      ---
      duration_ms: 3.644254
      type: 'test'
      ...
    1..6
ok 1 - desktop browser pairing
  ---
  duration_ms: 53.971071
  type: 'suite'
  ...
# Subtest: instance token ownership and revocation
    # Subtest: lists safe metadata only and limits revocation to the owner
    ok 1 - lists safe metadata only and limits revocation to the owner
      ---
      duration_ms: 11.770063
      type: 'test'
      ...
    # Subtest: honors optional token expiry
    ok 2 - honors optional token expiry
      ---
      duration_ms: 6.855226
      type: 'test'
      ...
    # Subtest: lets a desktop revoke only the instance token authenticating its request
    ok 3 - lets a desktop revoke only the instance token authenticating its request
      ---
      duration_ms: 11.471126
      type: 'test'
      ...
    # Subtest: REST authentication accepts instance tokens while optional GitHub bearer auth is disabled
    ok 4 - REST authentication accepts instance tokens while optional GitHub bearer auth is disabled
      ---
      duration_ms: 2.365075
      type: 'test'
      ...
    1..4
ok 2 - instance token ownership and revocation
  ---
  duration_ms: 32.930408
  type: 'suite'
  ...
# Subtest: pairing approval request protection
    # Subtest: accepts only the exact HTTPS frontend origin
    ok 1 - accepts only the exact HTTPS frontend origin
      ---
      duration_ms: 1.892419
      type: 'test'
      ...
    # Subtest: requires a browser session even when another authentication method supplied the user
    ok 2 - requires a browser session even when another authentication method supplied the user
      ---
      duration_ms: 2.627517
      type: 'test'
      ...
    1..2
ok 3 - pairing approval request protection
  ---
  duration_ms: 4.617028
  type: 'suite'
  ...
# [2026-08-29 19:00:14.356 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-FjbhiQ/020-desktopAuth.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 19:00:14.442 +0000] �[32mINFO�[39m: �[36mSQLite database connection closed�[39m
1..3
# tests 12
# suites 3
# pass 12
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 2804.051559

[21/321] 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.958688
  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.153117
  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: 82.92843
  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.020394
  type: 'test'
  ...
1..4
# tests 4
# suites 0
# pass 4
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 919.506584

[22/321] 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.589479
      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.78253
      type: 'test'
      ...
    # Subtest: grants full admin permissions to PROPR_ADMIN_USERS
    ok 3 - grants full admin permissions to PROPR_ADMIN_USERS
      ---
      duration_ms: 2.776658
      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.730932
      type: 'test'
      ...
    # Subtest: resolves demo users without installation permissions
    ok 5 - resolves demo users without installation permissions
      ---
      duration_ms: 2.155192
      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.088658
      type: 'test'
      ...
    # Subtest: current-user responses expose capabilities without OAuth credentials
    ok 7 - current-user responses expose capabilities without OAuth credentials
      ---
      duration_ms: 1.820415
      type: 'test'
      ...
    1..7
ok 1 - instance authorization
  ---
  duration_ms: 29.155308
  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.501878
      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.86108
      type: 'test'
      ...
    # Subtest: adds explicit members and writes an audit entry
    ok 3 - adds explicit members and writes an audit entry
      ---
      duration_ms: 5.182651
      type: 'test'
      ...
    # Subtest: keeps environment authority separate from durable role assignments
    ok 4 - keeps environment authority separate from durable role assignments
      ---
      duration_ms: 2.984318
      type: 'test'
      ...
    # Subtest: prevents removing the last durable administrator
    ok 5 - prevents removing the last durable administrator
      ---
      duration_ms: 4.316897
      type: 'test'
      ...
    # Subtest: allows role changes once another administrator exists
    ok 6 - allows role changes once another administrator exists
      ---
      duration_ms: 4.993677
      type: 'test'
      ...
    1..6
ok 2 - instance member service
  ---
  duration_ms: 28.334318
  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.15933
      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.651999
      type: 'test'
      ...
    1..2
ok 3 - instance catalog
  ---
  duration_ms: 3.926535
  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.683183
      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.387152
      type: 'test'
      ...
    # Subtest: rejects GitHub usernames with trailing or consecutive hyphens
    ok 3 - rejects GitHub usernames with trailing or consecutive hyphens
      ---
      duration_ms: 1.510002
      type: 'test'
      ...
    1..3
ok 4 - instance admin routes
  ---
  duration_ms: 5.696395
  type: 'suite'
  ...
# [2026-08-29 19:00:17.971 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-FjbhiQ/022-instanceAuthorization.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 19:00:18.032 +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 2177.169815

[23/321] 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.022357
  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.158838
  type: 'test'
  ...
# Subtest: an explicit API listen host overrides environment defaults
ok 3 - an explicit API listen host overrides environment defaults
  ---
  duration_ms: 0.194936
  type: 'test'
  ...
1..3
# tests 3
# suites 0
# pass 3
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 248.34648

[24/321] 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.302499
  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.448788
  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.850315
  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.231895
  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.292308
  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.33591
  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.239189
  type: 'test'
  ...
# Subtest: stored output detection recognizes truncated Antigravity result JSON
ok 8 - stored output detection recognizes truncated Antigravity result JSON
  ---
  duration_ms: 0.167033
  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.361688
  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: 77.970931
  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.996075
  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.451132
  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.729488
  type: 'test'
  ...
# Subtest: stored output parsing filters Antigravity transcript tool items
ok 14 - stored output parsing filters Antigravity transcript tool items
  ---
  duration_ms: 1.035923
  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.331508
  type: 'test'
  ...
# Subtest: execution detail fallback filters Antigravity transcript tool rows
ok 16 - execution detail fallback filters Antigravity transcript tool rows
  ---
  duration_ms: 1.122987
  type: 'test'
  ...
# [2026-08-29 19:00:20.687 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-FjbhiQ/024-liveDetailsAntigravity.test.ts/propr.test.sqlite"
#     environment: "test"
1..16
# tests 16
# suites 0
# pass 16
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 2262.264828

[25/321] 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.438005
  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.259817
  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.16544
  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.156053
  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.424416
  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: 22.501678
  type: 'test'
  ...
# [2026-08-29 19:00:23.038 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-FjbhiQ/025-liveDetailsCodexDbFallback.test.ts/propr.test.sqlite"
#     environment: "test"
1..6
# tests 6
# suites 0
# pass 6
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 1927.745022

[26/321] 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: 1639.700319
  type: 'test'
  ...
# Subtest: detectStoredOutputFormat does not classify bare conversation_id JSON as Claude
ok 2 - detectStoredOutputFormat does not classify bare conversation_id JSON as Claude
  ---
  duration_ms: 3.979675
  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.82295
  type: 'test'
  ...
# Subtest: parseOpenCodeOutputToConversationResult separates structured assistant text parts
ok 4 - parseOpenCodeOutputToConversationResult separates structured assistant text parts
  ---
  duration_ms: 15.824523
  type: 'test'
  ...
# Subtest: detectStoredOutputFormat recognizes whole-document pretty OpenCode JSON
ok 5 - detectStoredOutputFormat recognizes whole-document pretty OpenCode JSON
  ---
  duration_ms: 1.484304
  type: 'test'
  ...
# Subtest: detectStoredOutputFormat recognizes OpenCode assistant parts without session identifiers
ok 6 - detectStoredOutputFormat recognizes OpenCode assistant parts without session identifiers
  ---
  duration_ms: 1.19888
  type: 'test'
  ...
# Subtest: detectStoredOutputFormat recognizes whole-document pretty Claude JSON arrays
ok 7 - detectStoredOutputFormat recognizes whole-document pretty Claude JSON arrays
  ---
  duration_ms: 0.821692
  type: 'test'
  ...
# [2026-08-29 19:00:25.170 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-FjbhiQ/026-liveDetailsOpenCodeParser.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 19:00:2

...(truncated)

View Workflow

…git-processor/worktrees/integry/propr/pr-1977-followup-2026-08-29T19-06-53/propr-ui/src/desktop/DesktopExperience.tsx:23).

Fixed the PR build blocker in [DesktopExperience.tsx](/tmp/git-processor/worktrees/integry/propr/pr-1977-followup-2026-08-29T19-06-53/propr-ui/src/desktop/DesktopExperience.tsx:23).

The formatting-only change reduces the file to the 400-line ESLint limit without changing behavior.

Validated successfully:

- UI lint with zero warnings
- UI typecheck
- 23 desktop tests
- Production UI build
- `git diff --check`

No commit was created.

PR: #1977
Comment by: @github-actions[bot] (ID: 5464276486)
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 2adff9a

Summary of Changes

Fixed the full-suite failure in browserAdapters.test.ts by making the HTTP error assertion case-insensitive.

Validation passed:

  • Targeted test: 5/5
  • Full UI suite: 498/498
  • UI typecheck
  • git diff --check

Only that test file changed; no commit was created.


🤖 Implementation Details

  • Model: GPT-5.6 Sol (high)
  • Requested By: @github-actions[bot]
  • Time: 2m 42s
  • Tokens: 567,320 (564,872 in / 2,448 out)
  • Cost: $0.61

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

…tmp/git-processor/worktrees/integry/propr/pr-1977-followup-2026-08-29T19-11-47/propr-ui/src/desktop/browserAdapters.test.ts:33) by making the HTTP error assertion case-insensitive.

Fixed the full-suite failure in [browserAdapters.test.ts](/tmp/git-processor/worktrees/integry/propr/pr-1977-followup-2026-08-29T19-11-47/propr-ui/src/desktop/browserAdapters.test.ts:33) by making the HTTP error assertion case-insensitive.

Validation passed:

- Targeted test: 5/5
- Full UI suite: 498/498
- UI typecheck
- `git diff --check`

Only that test file changed; no commit was created.

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

integry commented Aug 29, 2026

Copy link
Copy Markdown
Owner

/fix F1 F2 F3

Implement these as one coherent main-process credential architecture, including the independent security findings below.

  1. Keep the bearer entirely in Electron main-process custody. Pairing start/poll/completion, secure persistence, REST Authorization injection, Socket.IO auth injection, revocation, and invalidation all stay behind validated IPC; renderer receives status/result objects only. Preserve credentials: omit/no-cookie identity for bearer traffic.
  2. Bind every credential to both profile ID and normalized origin. Before any authenticated probe/request/socket connection, compare the proposed profile origin with the stored binding. On URL edit, never send the old-origin bearer to the new origin: best-effort revoke against the old origin, clear it, and require pairing. Add an A-to-attacker-origin regression proving no Authorization leaves.
  3. Scope access-invalid events to immutable profile ID plus connection generation. A late 401/socket error from profile A must not clear or deactivate newly active B. Only definitive token-invalid/expired codes clear that matching credential; authorization changes re-probe/reconnect, transient errors remain retryable. Test delayed A failures after B connects.
  4. Thread AbortSignal and a deadline through both pairing start and every poll. Bound individual request timeouts, abort a hung request at expiry, clamp sleeps/intervals to safe integer ranges, validate expiresAt, and distinguish caller cancellation from expiry. Test cancel during start, hung poll, huge/fractional interval, and deadline expiry.
  5. Close completion/cancellation races: immediately before credential write and activation, verify the signal and current profile generation. If cancelled/deleted/switched, do not persist or activate; best-effort revoke a transient completed token. Test completion concurrent with delete/switch.
  6. Require approvalUrl to match the configured normalized instance origin, unless a narrow documented trusted-origin contract exists; reject credentialed, cross-origin, unsafe, and malformed approval URLs.
  7. Keep token revocation scoped to the authenticated current token and clear cookies/local/session state when removing or changing profiles, without reintroducing cookie authentication for bearer profiles.

Re-run client/API/UI/desktop tests, add adversarial cases above, package the Electron app, and request a fresh /review afterward. Stay based on 1953-epic-desktop-transport-api; do not merge to main.

@propr-dev

propr-dev Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor Author

Applied the requested follow-up changes in commit d75cda9

Addressed 1 AI review comment (IDs: 5464297778)

Summary of Changes

Implemented F1–F3 as a coherent main-process credential architecture.

Key outcomes:

  • Bearers never enter renderer JavaScript or credential IPC. Pairing, persistence, probing, revocation, REST/Socket.IO injection, and invalidation live in credential-service.ts.
  • Credentials are encrypted and bound to profile ID plus normalized origin. URL changes revoke against the old origin and never forward Authorization to the new origin.
  • Invalidation is scoped by profile ID and connection generation. Only definitive token-invalid codes clear credentials; authorization changes reconnect/re-probe, while transient errors remain retryable.
  • Pairing start and polling now have cancellation, bounded timeouts, expiry deadlines, safe interval clamping, strict approval-origin validation, and completion race protection.
  • Added adversarial regressions for A→attacker, delayed A failures after B, hung/cancelled pairing, malformed timing/URLs, and completion racing with deletion or switching.

Verification passed:

  • Client: 17 tests
  • Electron desktop: 30 tests
  • UI: 502 tests
  • API desktop auth/socket/status: 47 tests
  • Client, desktop, UI, and API typechecks
  • UI lint
  • API and UI production builds
  • Linux Electron packaging: propr-desktop-linux-x64

The branch remains based on 1953-epic-desktop-transport-api; no commit or merge was created. I could not post the requested /review because this environment has no writable GitHub connector. The remaining handoff is to comment /review on PR #1977.


🤖 Implementation Details

  • Model: GPT-5.6 Sol (high)
  • Requested By: @integry
  • Time: 25m 0s
  • Tokens: 13,402,038 (13,352,154 in / 49,884 out)
  • Cost: $9.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: 5464340590✓

…hitecture.

Implemented F1–F3 as a coherent main-process credential architecture.

Key outcomes:

- Bearers never enter renderer JavaScript or credential IPC. Pairing, persistence, probing, revocation, REST/Socket.IO injection, and invalidation live in [credential-service.ts](/tmp/git-processor/worktrees/integry/propr/pr-1977-followup-2026-08-29T19-16-21/apps/desktop/src/credential-service.ts).
- Credentials are encrypted and bound to profile ID plus normalized origin. URL changes revoke against the old origin and never forward Authorization to the new origin.
- Invalidation is scoped by profile ID and connection generation. Only definitive token-invalid codes clear credentials; authorization changes reconnect/re-probe, while transient errors remain retryable.
- Pairing start and polling now have cancellation, bounded timeouts, expiry deadlines, safe interval clamping, strict approval-origin validation, and completion race protection.
- Added adversarial regressions for A→attacker, delayed A failures after B, hung/cancelled pairing, malformed timing/URLs, and completion racing with deletion or switching.

Verification passed:

- Client: 17 tests
- Electron desktop: 30 tests
- UI: 502 tests
- API desktop auth/socket/status: 47 tests
- Client, desktop, UI, and API typechecks
- UI lint
- API and UI production builds
- Linux Electron packaging: [propr-desktop-linux-x64](/tmp/git-processor/worktrees/integry/propr/pr-1977-followup-2026-08-29T19-16-21/apps/desktop/out/propr-desktop-linux-x64)

The branch remains based on `1953-epic-desktop-transport-api`; no commit or merge was created. I could not post the requested `/review` because this environment has no writable GitHub connector. The remaining handoff is to comment `/review` on PR #1977.

PR: #1977
Comment by: @integry (ID: 5464340590)
Model: gpt-5.6-sol
@integry

integry commented Aug 29, 2026

Copy link
Copy Markdown
Owner

!propr Tighten the remaining untrusted timing and cookie boundaries before review.

  1. Pairing currently accepts any positive fractional interval and an arbitrarily far-future expiresAt. A malicious instance can force 1 ms polling or a 24-day timer (MAX_TIMER_DELAY_MS) even though the server contract is a 10-minute flow. Require a bounded integer poll interval (for example 1..60 seconds) on start and every pending response, and require the advertised deadline to be future-valid but no more than a defensible protocol maximum (for example 30 minutes). Reject, do not merely schedule, fractional/sub-second, huge, NaN/infinite, past, and far-future values. Keep sleeps clamped to the remaining deadline and abort in-flight start/poll at caller cancellation/deadline. Update tests to use injected sleep/time rather than a production 0.0001 interval.
  2. Make the packaged renderer no-cookie rule explicit for all renderer-originated HTTP(S)/WS(S) traffic, including inactive/mismatched profile origins: strip Cookie and renderer Authorization before deciding whether to inject the active profile bearer. Continue preserving main-process bearer requests through the unforgeable internal marker, and strip Set-Cookie so a remote instance cannot plant a parallel cookie identity in the desktop session. Add active, inactive, same-origin multi-profile, and main-process regression cases.
  3. Preserve main-process-only token custody, origin binding, generation-scoped invalidation, exact approval origin, transient completion revocation, and current-token revocation. Re-run client/desktop/UI/API tests and production package. Do not request final review yet; the finalized runtime/deep-link base will be merged into this PR afterward.

@propr-dev

propr-dev Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor Author

Applied the requested follow-up changes in commit 56116bb

Summary of Changes

Implemented the requested follow-up without committing or requesting review.

Key changes:

  • Enforced integer polling intervals of 1–60 seconds on start and every pending response.
  • Rejected invalid, expired, or over-30-minute pairing deadlines before scheduling or approval.
  • Preserved deadline-clamped sleeps and start/poll cancellation behavior.
  • Stripped renderer Cookie and Authorization headers across HTTP(S)/WS(S), including inactive and mismatched origins.
  • Stripped remote Set-Cookie headers while preserving marked main-process bearer requests.
  • Added active, inactive, same-origin multi-profile, forged-marker, WebSocket, cancellation, and deadline regressions.

Updated desktopPairing.ts, client.ts, and credential-service.ts.

Validation passed:

  • Client: 20 tests
  • Desktop: 31 tests
  • UI: 503 tests across 70 files
  • API desktop-auth/Socket.IO/status: 47 tests
  • Client, desktop, UI, and API typechecks
  • API build
  • Linux x64 production Electron package
  • git diff --check

🤖 Implementation Details

  • Model: GPT-5.6 Sol (high)
  • Requested By: @integry
  • Time: 8m 50s
  • Tokens: 2,687,980 (2,671,133 in / 16,847 out)
  • Cost: $2.36
  • 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: 5464477994✓

@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/322] apps/desktop/src/credential-service.test.ts
TAP version 13
# node:internal/modules/run_main:123
#     triggerUncaughtException(
#     ^
# Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/home/runner/work/propr/propr/node_modules/@propr/client/dist/index.js' imported from /home/runner/work/propr/propr/apps/desktop/src/credential-service.ts
#     at finalizeResolution (node:internal/modules/esm/resolve:275:11)
#     at moduleResolve (node:internal/modules/esm/resolve:861:10)
#     at defaultResolve (node:internal/modules/esm/resolve:985:11)
#     at nextResolve (node:internal/modules/esm/hooks:748:28)
#     at resolveBase (file:///home/runner/work/propr/propr/node_modules/tsx/dist/esm/index.mjs?1788032580652:2:3744)
#     at resolveDirectory (file:///home/runner/work/propr/propr/node_modules/tsx/dist/esm/index.mjs?1788032580652:2:4243)
#     at resolveTsPaths (file:///home/runner/work/propr/propr/node_modules/tsx/dist/esm/index.mjs?1788032580652:2:4984)
#     at async resolve (file:///home/runner/work/propr/propr/node_modules/tsx/dist/esm/index.mjs?1788032580652:2:5355)
#     at async nextResolve (node:internal/modules/esm/hooks:748:22)
#     at async Hooks.resolve (node:internal/modules/esm/hooks:240:24) {
#   code: 'ERR_MODULE_NOT_FOUND',
#   url: 'file:///home/runner/work/propr/propr/node_modules/@propr/client/dist/index.js'
# }
# Node.js v22.23.2
# Subtest: /home/runner/work/propr/propr/apps/desktop/src/credential-service.test.ts
not ok 1 - /home/runner/work/propr/propr/apps/desktop/src/credential-service.test.ts
  ---
  duration_ms: 250.570314
  type: 'test'
  location: '/home/runner/work/propr/propr/apps/desktop/src/credential-service.test.ts:1:1'
  failureType: 'testCodeFailure'
  exitCode: 1
  signal: ~
  error: 'test failed'
  code: 'ERR_TEST_FAILURE'
  ...
1..1
# tests 1
# suites 0
# pass 0
# fail 1
# cancelled 0
# skipped 0
# todo 0
# duration_ms 260.951145

[2/322] 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.242766
      type: 'test'
      ...
    1..1
ok 1 - desktop deep-link delivery
  ---
  duration_ms: 2.038296
  type: 'suite'
  ...
1..1
# tests 1
# suites 1
# pass 1
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 235.872567

[3/322] 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: 26.704211
      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.84839
      type: 'test'
      ...
    # Subtest: clears browser identity and origin storage for normalized profile origins when profiles switch
    ok 3 - clears browser identity and origin storage for normalized profile origins when profiles switch
      ---
      duration_ms: 0.631934
      type: 'test'
      ...
    1..3
ok 1 - desktop session IPC operations
  ---
  duration_ms: 29.527713
  type: 'suite'
  ...
1..1
# tests 3
# suites 1
# pass 3
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 288.760799

[4/322] 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.713086
      type: 'test'
      ...
    # Subtest: maps profile and main-process authentication operations to fixed channels
    ok 2 - maps profile and main-process authentication operations to fixed channels
      ---
      duration_ms: 0.588609
      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.341235
      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.300353
      type: 'test'
      ...
    1..4
ok 1 - desktop preload bridge
  ---
  duration_ms: 4.174642
  type: 'suite'
  ...
1..1
# tests 4
# suites 1
# pass 4
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 248.427275

[5/322] 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.47796
      type: 'test'
      ...
    # Subtest: encrypts credentials before writing app-owned storage
    ok 2 - encrypts credentials before writing app-owned storage
      ---
      duration_ms: 5.792179
      type: 'test'
      ...
    # Subtest: serializes concurrent credential writes with last-write semantics
    ok 3 - serializes concurrent credential writes with last-write semantics
      ---
      duration_ms: 5.117244
      type: 'test'
      ...
    # Subtest: orders concurrent credential writes and removals by invocation
    ok 4 - orders concurrent credential writes and removals by invocation
      ---
      duration_ms: 6.403826
      type: 'test'
      ...
    # Subtest: refuses plaintext fallback when encryption is unavailable or basic_text
    ok 5 - refuses plaintext fallback when encryption is unavailable or basic_text
      ---
      duration_ms: 1.954391
      type: 'test'
      ...
    # Subtest: rejects unsafe endpoints and path-like profile identifiers
    ok 6 - rejects unsafe endpoints and path-like profile identifiers
      ---
      duration_ms: 6.025326
      type: 'test'
      ...
    1..6
ok 1 - desktop profile store
  ---
  duration_ms: 40.243359
  type: 'suite'
  ...
1..1
# tests 6
# suites 1
# pass 6
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 281.266642

[6/322] 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.011507
      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.189256
      type: 'test'
      ...
    # Subtest: requires an exact loopback development origin
    ok 3 - requires an exact loopback development origin
      ---
      duration_ms: 0.341655
      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.387575
      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.324349
      type: 'test'
      ...
    # Subtest: publishes a restrictive production policy
    ok 6 - publishes a restrictive production policy
      ---
      duration_ms: 0.353904
      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.268215
      type: 'test'
      ...
    1..7
ok 1 - desktop URL security
  ---
  duration_ms: 3.933037
  type: 'suite'
  ...
1..1
# tests 7
# suites 1
# pass 7
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 237.883764

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

[8/322] 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.710573
      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.142274
      type: 'test'
      ...
    1..2
ok 1 - desktop BrowserWindow security
  ---
  duration_ms: 2.819807
  type: 'suite'
  ...
1..1
# tests 2
# suites 1
# pass 2
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 236.568744

[9/322] 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.82878
      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.243887
      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.209365
      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.64212
      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.428978
      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.628779
      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.64218
      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.651313
      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.645524
      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.526475
      type: 'test'
      ...
    1..10
ok 1 - agent login session manager
  ---
  duration_ms: 12.73894
  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.784794
      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.573246
      type: 'test'
      ...
    # Subtest: rejects interactive login for an unsupported agent type
    ok 3 - rejects interactive login for an unsupported agent type
      ---
      duration_ms: 0.462338
      type: 'test'
      ...
    1..3
ok 2 - agent login routes
  ---
  duration_ms: 1.955122
  type: 'suite'
  ...
# [2026-08-29 19:43:05.711 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-TsZkPU/009-agentLoginRoutes.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 19:43:05.722 +0000] �[32mINFO�[39m: �[36mSQLite database connection closed�[39m
# [2026-08-29 19:43:05.722 +0000] �[32mINFO�[39m: �[36mShutting down queue...�[39m
# [2026-08-29 19:43:05.722 +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 2241.801161

[10/322] 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.025297
      type: 'test'
      ...
    # Subtest: persists a failed state when queue submission fails
    ok 2 - persists a failed state when queue submission fails
      ---
      duration_ms: 0.898587
      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.802382
      type: 'test'
      ...
    # Subtest: times out slow runtime package validation before queueing
    ok 4 - times out slow runtime package validation before queueing
      ---
      duration_ms: 6.276702
      type: 'test'
      ...
    # Subtest: returns package suggestions from configured runtimes
    ok 5 - returns package suggestions from configured runtimes
      ---
      duration_ms: 0.73502
      type: 'test'
      ...
    # Subtest: enforces PROPR_ADMIN_USERS when configured
    ok 6 - enforces PROPR_ADMIN_USERS when configured
      ---
      duration_ms: 0.253753
      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.288726
      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.41069
      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.507657
      type: 'test'
      ...
    # Subtest: does not warm the package catalog when a member is rejected
    ok 10 - does not warm the package catalog when a member is rejected
      ---
      duration_ms: 2.306162
      type: 'test'
      ...
    # Subtest: resolves the runtime build queue lazily when queueing
    ok 11 - resolves the runtime build queue lazily when queueing
      ---
      duration_ms: 0.795942
      type: 'test'
      ...
    # Subtest: reports apply load failures through the route response
    ok 12 - reports apply load failures through the route response
      ---
      duration_ms: 0.459804
      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.491121
      type: 'test'
      ...
    # Subtest: rejects runtime verification without runtime-management permission
    ok 14 - rejects runtime verification without runtime-management permission
      ---
      duration_ms: 0.244368
      type: 'test'
      ...
    1..14
ok 1 - agent runtime package routes
  ---
  duration_ms: 20.038745
  type: 'suite'
  ...
# [2026-08-29 19:43:07.733 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-TsZkPU/010-agentRuntimeRoutes.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 19:43:07.750 +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 2019.163891

[11/322] 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.000699
  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.076925
  type: 'test'
  ...
# Subtest: temporary cleanup refuses paths outside its configured root
ok 3 - temporary cleanup refuses paths outside its configured root
  ---
  duration_ms: 0.666356
  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.546846
  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.86162
  type: 'test'
  ...
# [2026-08-29 19:43:09.852 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-TsZkPU/011-attachmentUploadCleanup.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 19:43:09.867 +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 1990.723093

[12/322] 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.465406
  type: 'test'
  ...
# Subtest: ensureAuthenticated refreshes an expired GitHub token before continuing
ok 2 - ensureAuthenticated refreshes an expired GitHub token before continuing
  ---
  duration_ms: 5.536068
  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.515398
  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.865955
  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.62253
  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.868792
  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.121703
  type: 'test'
  ...
# Subtest: GitHub route auth error emits TOKEN_REFRESHED after a successful refresh
ok 8 - GitHub route auth error emits TOKEN_REFRESHED after a successful refresh
  ---
  duration_ms: 0.995433
  type: 'test'
  ...
# [2026-08-29 19:43:12.505 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-TsZkPU/012-authGithubTokens.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 19:43:12.524 +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 2499.287901

[13/322] 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: 46.17272
  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.943501
  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.92201
  type: 'test'
  ...
# Subtest: auth redirect allowlist permits leading-dot COOKIE_DOMAIN subdomains
ok 4 - auth redirect allowlist permits leading-dot COOKIE_DOMAIN subdomains
  ---
  duration_ms: 4.004545
  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.311688
  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.989982
  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.367091
  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.111576
  type: 'test'
  ...
# Subtest: auth redirect allowlist permits configured local IP literals
ok 9 - auth redirect allowlist permits configured local IP literals
  ---
  duration_ms: 5.222524
  type: 'test'
  ...
# Subtest: auth redirect allowlist ignores malformed additional host entries
ok 10 - auth redirect allowlist ignores malformed additional host entries
  ---
  duration_ms: 5.143234
  type: 'test'
  ...
# [2026-08-29 19:43:15.050 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-TsZkPU/013-authRedirect.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 19:43:15.139 +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 2420.090257

[14/322] 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: 1483.055939
  type: 'test'
  ...
# [2026-08-29 19:43:17.205 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-TsZkPU/014-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 2045.06711

[15/322] packages/api/test/configReloadSubscription.test.ts
TAP version 13
# Subtest: API config subscription serializes the startup reload with settings updates
ok 1 - API config subscription serializes the startup reload with settings updates
  ---
  duration_ms: 3.819386
  type: 'test'
  ...
# Subtest: API config subscription reloads repository updates
ok 2 - API config subscription reloads repository updates
  ---
  duration_ms: 0.367405
  type: 'test'
  ...
# Subtest: API config subscription ignores unrelated and malformed events
ok 3 - API config subscription ignores unrelated and malformed events
  ---
  duration_ms: 0.259181
  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.311631
  type: 'test'
  ...
1..4
# tests 4
# suites 0
# pass 4
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 239.993272

[16/322] packages/api/test/configRoutesIndexingSummarization.test.ts
TAP version 13
# [2026-08-29 19:43:19.763 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-TsZkPU/016-configRoutesIndexingSummarization.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 19:43:19.789 +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: 1777.192565
  type: 'test'
  ...
# Subtest: postSummarizationSettings rejects enabled summarization without a primary alias
ok 2 - postSummarizationSettings rejects enabled summarization without a primary alias
  ---
  duration_ms: 4.248203
  type: 'test'
  ...
# [2026-08-29 19:43:20.030 +0000] �[32mINFO�[39m: �[36mDatabase migrations completed successfully�[39m
# [2026-08-29 19:43:20.032 +0000] �[32mINFO�[39m: �[36mSuccessfully loaded agents configuration�[39m
#     agentCount: 2
# [2026-08-29 19:43:20.033 +0000] �[32mINFO�[39m: �[36mSuccessfully loaded summarization settings�[39m
#     summarization: {
#       "enabled": false,
#       "agent_alias": "",
#       "fallback_agent_alias": "",
#       "custom_prompt": ""
#     }
# [2026-08-29 19:43:20.034 +0000] �[32mINFO�[39m: �[36mSuccessfully saved summarization settings�[39m
#     summarization: {
#       "enabled": true,
#       "agent_alias": "codex:gpt-5.5",
#       "fallback_agent_alias": "fallback:gpt-5.4",
#       "custom_prompt": ""
#     }
# [2026-08-29 19:43:20.035 +0000] �[32mINFO�[39m: �[36mSuccessfully loaded summarization settings�[39m
#     summarization: {
#       "enabled": true,
#       "agent_alias": "codex:gpt-5.5",
#       "fallback_agent_alias": "fallback:gpt-5.4",
#       "custom_prompt": ""
#     }
# [2026-08-29 19:43:20.048 +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 2097.757033

[17/322] 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.18541
  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.232581
  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.267163
  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.359803
  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.161553
  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.135184
  type: 'test'
  ...
# Subtest: explicit custom GitHub web auth remains supported
ok 7 - explicit custom GitHub web auth remains supported
  ---
  duration_ms: 0.156487
  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.995914
  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.544752
  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: 25.631702
  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.915532
  type: 'test'
  ...
1..11
# tests 11
# suites 0
# pass 11
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 329.864669

[18/322] packages/api/test/connectAuthCallback.test.ts
TAP version 13
# [2026-08-29 19:43:22.856 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-TsZkPU/018-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: 3041.534949
  type: 'test'
  ...
# [2026-08-29 19:43:25.894 +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 5458.029389

[19/322] 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.990526
  type: 'test'
  ...
# Subtest: CORS rejects unrelated origins under proxy mode
ok 2 - CORS rejects unrelated origins under proxy mode
  ---
  duration_ms: 0.237008
  type: 'test'
  ...
# Subtest: CORS allows requests with no origin
ok 3 - CORS allows requests with no origin
  ---
  duration_ms: 0.11924
  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.148174
  type: 'test'
  ...
# Subtest: CORS allows HTTP(S) loopback origins for development
ok 5 - CORS allows HTTP(S) loopback origins for development
  ---
  duration_ms: 0.256157
  type: 'test'
  ...
# Subtest: CORS rejects unsafe schemes and non-loopback hosts
ok 6 - CORS rejects unsafe schemes and non-loopback hosts
  ---
  duration_ms: 0.159481
  type: 'test'
  ...
# Subtest: CORS allows COOKIE_DOMAIN subdomains for preview environments
ok 7 - CORS allows COOKIE_DOMAIN subdomains for preview environments
  ---
  duration_ms: 0.196556
  type: 'test'
  ...
# Subtest: CORS preserves http COOKIE_DOMAIN preview compatibility
ok 8 - CORS preserves http COOKIE_DOMAIN preview compatibility
  ---
  duration_ms: 0.116496
  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.401045
  type: 'test'
  ...
# Subtest: Express CORS boundary is sanitized in development mode
ok 10 - Express CORS boundary is sanitized in development mode
  ---
  duration_ms: 71.162772
  type: 'test'
  ...
# Subtest: Express CORS boundary is sanitized in production mode
ok 11 - Express CORS boundary is sanitized in production mode
  ---
  duration_ms: 25.128592
  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.71793
  type: 'test'
  ...
1..12
# tests 12
# suites 0
# pass 12
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 570.435781

[20/322] 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.017343
  type: 'test'
  ...
# Subtest: demoModeReadOnlyMiddleware blocks auth metadata mutations
ok 2 - demoModeReadOnlyMiddleware blocks auth metadata mutations
  ---
  duration_ms: 4.475235
  type: 'test'
  ...
# Subtest: isDemoMode accepts common truthy environment values
ok 3 - isDemoMode accepts common truthy environment values
  ---
  duration_ms: 1.017165
  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.272251
  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.467174
  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.037981
  type: 'test'
  ...
# Subtest: ensureAuthenticated attaches the synthetic demo user
ok 7 - ensureAuthenticated attaches the synthetic demo user
  ---
  duration_ms: 1.493695
  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.329307
  type: 'test'
  ...
# Subtest: demo repository metadata resolves enabled configured repositories
ok 9 - demo repository metadata resolves enabled configured repositories
  ---
  duration_ms: 9.713998
  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: 5.05491
  type: 'test'
  ...
# [2026-08-29 19:43:29.279 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-TsZkPU/020-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: 256.197847
  type: 'test'
  ...
# [2026-08-29 19:43:29.615 +0000] �[32mINFO�[39m: �[36mSuccessfully saved monitored repositories�[39m
#     repos: [
#       {
#         "id": "1",
#         "name": "integry/propr",
#         "enabled": true
#       },
#       {
#         "id": "2",
#         "name": "integry/private",
#         "enabled": true
#       },
#       {
#         "id": "3",
#         "name": "integry/disabled",
#         "enabled": false
#       }
#     ]
# [2026-08-29 19:43:29.617 +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: 10.655926
  type: 'test'
  ...
# Subtest: demo repository metadata ignores malformed database repository names
ok 13 - demo repository metadata ignores malformed database repository names
  ---
  duration_ms: 1.819767
  type: 'test'
  ...
# [2026-08-29 19:43:29.628 +0000] �[32mINFO�[39m: �[36mSuccessfully loaded all monitored repositories�[39m
#     total_repos: 0
# Subtest: planner demo reads use the curated database without owner or repository allowlists
ok 14 - planner demo reads use the curated database without owner or repository allowlists
  ---
  duration_ms: 16.813336
  type: 'test'
  ...
# [2026-08-29 19:43:29.646 +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.61827
  type: 'test'
  ...
# Subtest: auth demo-mode metadata endpoint reports startup environment value
ok 16 - auth demo-mode metadata endpoint reports startup environment value
  ---
  duration_ms: 7.195602
  type: 'test'
  ...
1..16
# tests 16
# suites 0
# pass 16
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 2693.474064

[21/322] packages/api/test/desktopAuth.test.ts
TAP version 13
# [desktop-auth] {
#   action: 'pairing_started',
#   actorUserId: undefined,
#   pairingId: 'dpr_mW1aVYO1Q2miXGQMmMGDGA',
#   tokenId: undefined,
#   clientName: 'Work Laptop'
# }
# [desktop-auth] {
#   action: 'pairing_started',
#   actorUserId: undefined,
#   pairingId: 'dpr_cprmMW0bp4k5KhqiaA34ew',
#   tokenId: undefined,
#   clientName: 'Windows desktop'
# }
# [desktop-auth] {
#   action: 'pairing_started',
#   actorUserId: undefined,
#   pairingId: 'dpr_OgHvKLmzMvO2lsM3UHhj5g',
#   tokenId: undefined,
#   clientName: 'MacBook Pro'
# }
# [desktop-auth] {
#   action: 'pairing_approved',
#   actorUserId: '101',
#   pairingId: 'dpr_OgHvKLmzMvO2lsM3UHhj5g',
#   tokenId: undefined,
#   clientName: 'MacBook Pro'
# }
# [desktop-auth] {
#   action: 'token_issued',
#   actorUserId: '101',
#   pairingId: 'dpr_OgHvKLmzMvO2lsM3UHhj5g',
#   tokenId: '0286edc9-dbe6-479a-addb-e3fa3e2b6fc0',
#   clientName: 'MacBook Pro'
# }
# [desktop-auth] {
#   action: 'pairing_started',
#   actorUserId: undefined,
#   pairingId: 'dpr_9CdOrufnn23maz4JEcCRlg',
#   tokenId: undefined,
#   clientName: 'Linux workstation'
# }
# [desktop-auth] {
#   action: 'pairing_approved',
#   actorUserId: '101',
#   pairingId: 'dpr_9CdOrufnn23maz4JEcCRlg',
#   tokenId: undefined,
#   clientName: 'Linux workstation'
# }
# [desktop-auth] {
#   action: 'pairing_started',
#   actorUserId: undefined,
#   pairingId: 'dpr_p3YUhr1bjBHtKmkaAwgaRg',
#   tokenId: undefined,
#   clientName: 'Old laptop'
# }
# [desktop-auth] {
#   action: 'pairing_started',
#   actorUserId: undefined,
#   pairingId: 'dpr_s6wK7MmX_qsPNPqu6IdyJQ',
#   tokenId: undefined,
#   clientName: 'Desktop app'
# }
# [desktop-auth] {
#   action: 'pairing_approved',
#   actorUserId: '101',
#   pairingId: 'dpr_s6wK7MmX_qsPNPqu6IdyJQ',
#   tokenId: undefined,
#   clientName: 'Desktop app'
# }
# [desktop-auth] {
#   action: 'token_issued',
#   actorUserId: '101',
#   pairingId: 'dpr_s6wK7MmX_qsPNPqu6IdyJQ',
#   tokenId: '129b9786-dd5f-493f-9c00-f4c8ac67bff8',
#   clientName: 'Desktop app'
# }
# [desktop-auth] {
#   action: 'token_revoked',
#   actorUserId: '101',
#   pairingId: undefined,
#   tokenId: '129b9786-dd5f-493f-9c00-f4c8ac67bff8',
#   clientName: undefined
# }
# [desktop-auth] {
#   action: 'pairing_started',
#   actorUserId: undefined,
#   pairingId: 'dpr_6xFT61VLAicNIMgKmkoDhA',
#   tokenId: undefined,
#   clientName: 'Desktop app'
# }
# [desktop-auth] {
#   action: 'pairing_approved',
#   actorUserId: '101',
#   pairingId: 'dpr_6xFT61VLAicNIMgKmkoDhA',
#   tokenId: undefined,
#   clientName: 'Desktop app'
# }
# [desktop-auth] {
#   action: 'token_issued',
#   actorUserId: '101',
#   pairingId: 'dpr_6xFT61VLAicNIMgKmkoDhA',
#   tokenId: '8f11a912-7723-4378-90c4-baec421c2740',
#   clientName: 'Desktop app'
# }
# [desktop-auth] {
#   action: 'pairing_started',
#   actorUserId: undefined,
#   pairingId: 'dpr_MfvX0Y48n0rxO2KBW4bj6w',
#   tokenId: undefined,
#   clientName: 'Desktop app'
# }
# [desktop-auth] {
#   action: 'pairing_approved',
#   actorUserId: '101',
#   pairingId: 'dpr_MfvX0Y48n0rxO2KBW4bj6w',
#   tokenId: undefined,
#   clientName: 'Desktop app'
# }
# [desktop-auth] {
#   action: 'token_issued',
#   actorUserId: '101',
#   pairingId: 'dpr_MfvX0Y48n0rxO2KBW4bj6w',
#   tokenId: '8825c9a2-d51c-44a1-bea3-90bb87c93aa0',
#   clientName: 'Desktop app'
# }
# [desktop-auth] {
#   action: 'token_revoked',
#   actorUserId: '101',
#   pairingId: undefined,
#   tokenId: '8825c9a2-d51c-44a1-bea3-90bb87c93aa0',
#   clientName: undefined
# }
# Subtest: desktop browser pairing
    # Subtest: stores only a device-secret hash and builds a fixed trusted approval URL
    ok 1 - stores only a device-secret hash and builds a fixed trusted approval URL
      ---
      duration_ms: 15.267366
      type: 'test'
      ...
    # Subtest: uses the configured API browser entry and preserves only a managed hosted tunnel selector
    ok 2 - uses the configured API browser entry and preserves only a managed hosted tunnel selector
      ---
      duration_ms: 4.100229
      type: 'test'
      ...
    # Subtest: issues an opaque token once, resolves its owner, and never stores plaintext credentials
    ok 3 - issues an opaque token once, resolves its owner, and never stores plaintext credentials
      ---
      duration_ms: 13.748443
      type: 'test'
      ...
    # Subtest: rejects the wrong secret without revealing pairing state
    ok 4 - rejects the wrong secret without revealing pairing state
      ---
      duration_ms: 5.752413
      type: 'test'
      ...
    # Subtest: expires unapproved pairings and cleans retained expired records
    ok 5 - expires unapproved pairings and cleans retained expired records
      ---
      duration_ms: 5.530318
      type: 'test'
      ...
    # Subtest: rejects unsafe names and non-HTTPS approval origins
    ok 6 - rejects unsafe names and non-HTTPS approval origins
      ---
      duration_ms: 3.694537
      type: 'test'
      ...
    1..6
ok 1 - desktop browser pairing
  ---
  duration_ms: 49.335963
  type: 'suite'
  ...
# Subtest: instance token ownership and revocation
    # Subtest: lists safe metadata only and limits revocation to the owner
    ok 1 - lists safe metadata only and limits revocation to the owner
      ---
      duration_ms: 11.59283
      type: 'test'
      ...
    # Subtest: honors optional token expiry
    ok 2 - honors optional token expiry
      ---
      duration_ms: 6.720374
      type: 'test'
      ...
    # Subtest: lets a desktop revoke only the instance token authenticating its request
    ok 3 - lets a desktop revoke only the instance token authenticating its request
      ---
      duration_ms: 7.118645
      type: 'test'
      ...
    # Subtest: REST authentication accepts instance tokens while optional GitHub bearer auth is disabled
    ok 4 - REST authentication accepts instance tokens while optional GitHub bearer auth is disabled
      ---
      duration_ms: 2.347013
      type: 'test'
      ...
    1..4
ok 2 - instance token ownership and revocation
  ---
  duration_ms: 28.231736
  type: 'suite'
  ...
# Subtest: pairing approval request protection
    # Subtest: accepts only the exact HTTPS frontend origin
    ok 1 - accepts only the exact HTTPS frontend origin
      ---
      duration_ms: 1.842972
      type: 'test'
      ...
    # Subtest: requires a browser session even when another authentication method supplied the user
    ok 2 - requires a browser session even when another authentication method supplied the user
      ---
      duration_ms: 1.830523
      type: 'test'
      ...
    1..2
ok 3 - pairing approval request protection
  ---
  duration_ms: 3.771553
  type: 'suite'
  ...
# [2026-08-29 19:43:31.923 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-TsZkPU/021-desktopAuth.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 19:43:31.999 +0000] �[32mINFO�[39m: �[36mSQLite database connection closed�[39m
1..3
# tests 12
# suites 3
# pass 12
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 2829.754999

[22/322] 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.889052
  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.151549
  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: 72.084854
  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: 8.308843
  type: 'test'
  ...
1..4
# tests 4
# suites 0
# pass 4
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 916.160573

[23/322] 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.787333
      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.615106
      type: 'test'
      ...
    # Subtest: grants full admin permissions to PROPR_ADMIN_USERS
    ok 3 - grants full admin permissions to PROPR_ADMIN_USERS
      ---
      duration_ms: 2.413423
      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.624602
      type: 'test'
      ...
    # Subtest: resolves demo users without installation permissions
    ok 5 - resolves demo users without installation permissions
      ---
      duration_ms: 2.243967
      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.964115
      type: 'test'
      ...
    # Subtest: current-user responses expose capabilities without OAuth credentials
    ok 7 - current-user responses expose capabilities without OAuth credentials
      ---
      duration_ms: 2.303187
      type: 'test'
      ...
    1..7
ok 1 - instance authorization
  ---
  duration_ms: 28.116262
  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.212569
      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.436636
      type: 'test'
      ...
    # Subtest: adds explicit members and writes an audit entry
    ok 3 - adds explicit members and writes an audit entry
      ---
      duration_ms: 4.190185
      type: 'test'
      ...
    # Subtest: keeps environment authority separate from durable role assignments
    ok 4 - keeps environment authority separate from durable role assignments
      ---
      duration_ms: 6.155071
      type: 'test'
      ...
    # Subtest: prevents removing the last durable administrator
    ok 5 - prevents removing the last durable administrator
      ---
      duration_ms: 4.071717
      type: 'test'
      ...
    # Subtest: allows role changes once another administrator exists
    ok 6 - allows role changes once another administrator exists
      ---
      duration_ms: 5.218507
      type: 'test'
      ...
    1..6
ok 2 - instance member service
  ---
  duration_ms: 29.74461
  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.950475
      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.580496
      type: 'test'
      ...
    1..2
ok 3 - instance catalog
  ---
  duration_ms: 3.61736
  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.536058
      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.356107
      type: 'test'
      ...
    # Subtest: rejects GitHub usernames with trailing or consecutive hyphens
    ok 3 - rejects GitHub usernames with trailing or consecutive hyphens
      ---
      duration_ms: 1.646896
      type: 'test'
      ...
    1..3
ok 4 - instance admin routes
  ---
  duration_ms: 5.660936
  type: 'suite'
  ...
# [2026-08-29 19:43:35.617 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-TsZkPU/023-instanceAuthorization.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 19:43:35.678 +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 2125.81091

[24/322] 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.838851
  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.129015
  type: 'test'
  ...
# Subtest: an explicit API listen host overrides environment defaults
ok 3 - an explicit API listen host overrides environment defaults
  ---
  duration_ms: 0.153452
  type: 'test'
  ...
1..3
# tests 3
# suites 0
# pass 3
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 239.082248

[25/322] 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.105859
  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.362967
  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.848731
  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.192301
  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.288686
  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.326263
  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.187653
  type: 'test'
  ...
# Subtest: stored output detection recognizes truncated Antigravity result JSON
ok 8 - stored output detection recognizes truncated Antigravity result JSON
  ---
  duration_ms: 0.160843
  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.35102
  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: 70.97234
  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.820278
  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.246912
  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.038207
  type: 'test'
  ...
# Subtest: stored output parsing filters Antigravity transcript tool items
ok 14 - stored output parsing filters Antigravity transcript tool items
  ---
  duration_ms: 1.594867
  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.292522
  type: 'test'
  ...
# Subtest: execution detail fallback filters Antigravity transcript tool rows
ok 16 - execution detail fallback filters Antigravity transcript tool rows
  ---
  duration_ms: 0.878747
  type: 'test'
  ...
# [2026-08-29 19:43:38.236 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-TsZkPU/025-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 2069.177962

[26/322] 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.282114
  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.276848
  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.164919
  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.133471
  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.400405
  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.229496
  type: 'test'
  ...
# [2026-08-29 19:43:40.349 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-TsZkPU/026-liveDetailsCodexDbFallback.test.ts/propr.test.sqlite"
#     environment: "test"
1..6
# tests 6
# suites 0
# pass 6
# fa

...(truncated)

View Workflow

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

integry commented Aug 29, 2026

Copy link
Copy Markdown
Owner

!propr Also fix the exact clean full-suite dependency failure on current remote-auth head. Run 33271529121 failed immediately in apps/desktop/src/credential-service.test.ts with ERR_MODULE_NOT_FOUND for node_modules/@propr/client/dist/index.js. The desktop tests import the new runtime client dependency before that workspace has been built in the serial full-suite path. Make desktop test/pretest preparation self-contained by building required workspace dependencies in order (at minimum @propr/shared then @propr/client), while avoiding redundant recursion in existing desktop typecheck/package hooks. Add or adjust the workflow/package-script regression so a clean npm ci followed by the authoritative full-suite order cannot use stale/missing dist. Preserve the active pairing timing/cookie hardening task and rerun the exact desktop/full-suite entry points; do not treat this as a product-code failure.

…equesting review.

Implemented the requested follow-up without committing or requesting review.

Key changes:

- Enforced integer polling intervals of 1–60 seconds on start and every pending response.
- Rejected invalid, expired, or over-30-minute pairing deadlines before scheduling or approval.
- Preserved deadline-clamped sleeps and start/poll cancellation behavior.
- Stripped renderer Cookie and Authorization headers across HTTP(S)/WS(S), including inactive and mismatched origins.
- Stripped remote Set-Cookie headers while preserving marked main-process bearer requests.
- Added active, inactive, same-origin multi-profile, forged-marker, WebSocket, cancellation, and deadline regressions.

Updated [desktopPairing.ts](/tmp/git-processor/worktrees/integry/propr/pr-1977-followup-2026-08-29T19-45-47/packages/client/src/desktopPairing.ts), [client.ts](/tmp/git-processor/worktrees/integry/propr/pr-1977-followup-2026-08-29T19-45-47/packages/client/src/client.ts), and [credential-service.ts](/tmp/git-processor/worktrees/integry/propr/pr-1977-followup-2026-08-29T19-45-47/apps/desktop/src/credential-service.ts).

Validation passed:

- Client: 20 tests
- Desktop: 31 tests
- UI: 503 tests across 70 files
- API desktop-auth/Socket.IO/status: 47 tests
- Client, desktop, UI, and API typechecks
- API build
- Linux x64 production Electron package
- `git diff --check`

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

integry commented Aug 29, 2026

Copy link
Copy Markdown
Owner

!propr Fix the exact clean full-suite dependency failure on current hardened head 56116bb. Run 33271529121 failed immediately in apps/desktop/src/credential-service.test.ts with ERR_MODULE_NOT_FOUND for node_modules/@propr/client/dist/index.js. The desktop tests import the new runtime client dependency before that workspace has been built in the serial full-suite path. Make desktop test/pretest preparation self-contained by building required workspace dependencies in order (at minimum @propr/shared then @propr/client), while avoiding redundant recursion in existing desktop typecheck/package hooks. Add or adjust the package-script/workflow regression so a clean npm ci followed by the authoritative full-suite order cannot use stale/missing dist. Preserve all credential, timing, cookie, API, and UI behavior. Re-run the exact clean desktop/full-suite entry points and package; do not request review until the runtime base sync.

@propr-dev

propr-dev Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor Author

Applied the requested follow-up changes in commit 6cba577

Summary of Changes

Implemented the clean dependency fix without product-behavior changes.

Validation:

  • Clean npm ci: passed
  • Clean npm run desktop:test: 31/31 passed
  • Clean npm run desktop:package: passed
  • npm run desktop:typecheck: passed
  • Docs setup/typecheck/build: passed
  • Package-script regression: 7/7 passed
  • Serial full-suite entry passed the formerly failing credential test immediately. The remainder could not complete because this environment lacks Docker/Redis; the run was stopped after reaching Redis-dependent tests.
  • git diff --check: passed

No commit or review request was created.


🤖 Implementation Details

  • Model: GPT-5.6 Sol (high)
  • Requested By: @integry
  • Time: 14m 21s
  • Tokens: 3,748,169 (3,734,718 in / 13,451 out)
  • Cost: $2.79
  • 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: 5464563805✓

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

integry commented Aug 30, 2026

Copy link
Copy Markdown
Owner

!propr Fix the remaining exact-head remote transaction durability blockers on 0a2cbd3. Do not merge or sync the transport base. Preserve the now-correct opaque identity epoch, renderer clearing semantics, encrypted pending-revocation journal and retry behavior, and shared cancellation/profile-switch publish gate.

  1. Provide actual native Windows durability proof. The completion report says Windows-native tests were skipped on Linux, and the exact-head CI currently has no Windows job. Add a required windows-latest x64 job that executes real child-process termination/restart tests at every journal, credential, pointer/mirror, revocation, and atomic publication boundary. It must exercise the production Windows implementation, not mocked fsync or Linux-only semantics, and prove every restart yields complete usable A or complete usable B, never a missing or revoked active credential. Include reordered visibility, pointer/mirror rollback or corruption, missing slot, commit-before-revoke, crash during revoke, remote success before local cleanup, offline-to-online retry, and cancellation/switch barriers before publication. Make fixture scenario counts explicit so skipped cases fail CI.

  2. Never report journal durability after a failed or unavailable flush. In profile-store.ts writeJournal currently calls the durable barrier after an O_SYNC write while handle.sync failures are swallowed. A sync failure cannot be treated as success. Use a Windows-supported write-through replace or append protocol whose exact syscall/Node contract is verified by the native job, and fail closed before onDurable, cleanup, revocation, or B activation if any required data or metadata barrier fails. If directory flush is unsupported, the transaction layout must remain recoverable without assuming it succeeded. Add injected and real native failures for write, file flush, replace/rename, metadata persistence, and post-write verification.

  3. Recover from the authoritative valid journal before trusting the mutable profiles.json mirror. Current recover parses and validates profiles.json before applying a valid journal, so a torn or malformed mirror can block recovery even when the self-contained encrypted journal is intact. Read and authenticate the bounded journal first, select the newest complete valid generation using lossless monotonic identifiers, reconstruct B or A from it, then repair the mirror. Treat mirror corruption as recoverable only when the journal independently authenticates all required state; otherwise fail closed with a fixed redacted error. Add crash fixtures for empty, truncated, malformed, stale, future-generation, and attacker-modified mirrors paired with valid and invalid journals, plus alternate-slot rollback.

  4. Re-audit durability ordering end to end: pending revocation must be durably part of B before A becomes ineligible; B activation and renderer scope publication must occur only after authoritative recovery state is persistent; terminal revoke cleanup must itself be journaled before unlinking A. Keep network, timeout, and 5xx retry material encrypted and bounded, and keep only B authorizing during every restart.

Run focused profile-store/credential-service/electronAdapters pairing and race suites, the new actual Windows process-crash matrix, Linux process-crash parity, IPC/apiClient/SocketProvider, UI lint/typecheck/build, desktop typecheck/package, clean npm ci then Validate Changes, configured Full Suite, actionlint, and git diff --check. Do not claim native proof unless the required Windows job executes every named scenario and passes.

@propr-dev

propr-dev Bot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor Author

Applied the requested follow-up changes in commit 553071d

Summary of Changes

Implemented the durability follow-up on exact head 0a2cbd3f… without merging or syncing the transport base.

Key changes:

  • Added prepared → flush → verify → O_SYNC commit-marker journal publication. Flush/verification failures now fail before durable callbacks or B publication.
  • Encrypted and authenticated self-contained journals with bounded size and lossless string/BigInt generations.
  • Recovery now authenticates journals before reading or repairing profiles.json.
  • Added mirror corruption, alternate-slot rollback, missing-slot, injected I/O failure, revocation, cancellation, and crash matrices.
  • Added required x64 windows-latest durability job in desktop-release-guard.yml, with explicit scenario-count enforcement and zero permitted skips.

Validation passed:

  • Clean npm ci
  • Desktop tests: 87/87
  • Linux native durability command: 67/67, 0 skipped
  • Client tests: 24/24
  • UI transport tests: 39/39
  • Fast validation tests: 279/279
  • Desktop/UI typechecks, UI lint/build, root lint/build
  • Linux desktop package
  • Release metadata and git diff --check

Native Windows success is not claimed: the hosted job must execute after push. Local actionlint and the Redis-backed configured Full Suite could not run because this environment has no Docker or Redis; both remain covered by CI.


🤖 Implementation Details

  • Model: GPT-5.6 Sol (high)
  • Requested By: @integry
  • Time: 22m 5s
  • Tokens: 11,385,396 (11,346,694 in / 38,702 out)
  • Cost: $7.84
  • 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: 5466169482✓

@integry integry removed the AI label Aug 30, 2026
…f…` without merging or syncing the transport base.

Implemented the durability follow-up on exact head `0a2cbd3f…` without merging or syncing the transport base.

Key changes:

- Added prepared → flush → verify → `O_SYNC` commit-marker journal publication. Flush/verification failures now fail before durable callbacks or B publication.
- Encrypted and authenticated self-contained journals with bounded size and lossless string/`BigInt` generations.
- Recovery now authenticates journals before reading or repairing `profiles.json`.
- Added mirror corruption, alternate-slot rollback, missing-slot, injected I/O failure, revocation, cancellation, and crash matrices.
- Added required x64 `windows-latest` durability job in [desktop-release-guard.yml](/home/node/workspace/.github/workflows/desktop-release-guard.yml:26), with explicit scenario-count enforcement and zero permitted skips.

Validation passed:

- Clean `npm ci`
- Desktop tests: 87/87
- Linux native durability command: 67/67, 0 skipped
- Client tests: 24/24
- UI transport tests: 39/39
- Fast validation tests: 279/279
- Desktop/UI typechecks, UI lint/build, root lint/build
- Linux desktop package
- Release metadata and `git diff --check`

Native Windows success is not claimed: the hosted job must execute after push. Local `actionlint` and the Redis-backed configured Full Suite could not run because this environment has no Docker or Redis; both remain covered by CI.

PR: #1977
Comment by: @integry (ID: 5466169482)
Model: gpt-5.6-sol
@integry integry added the AI label Aug 30, 2026
@integry

integry commented Aug 30, 2026

Copy link
Copy Markdown
Owner

!propr Fix the exact-head native Windows durability failures on 553071d. Do not merge or sync the transport base. Preserve opaque credential identity epochs, renderer isolation, encrypted pending revocations, journal-first malformed-mirror recovery, and the shared cancellation/profile-switch gate.

  1. Make the production persistence barriers actually work on Windows. Required job 99195246923 runs 68 tests with 0 skipped but 66 fail immediately with EPERM operation not permitted fsync at profile-store.ts #fsyncFile, because it opens the path read-only and calls FileHandle.sync. Do not skip the tests, ignore EPERM, or merely catch it. Use a documented Windows-supported writable/write-through handle or a small native Windows replacement primitive with the minimum access needed. Every required file-data and replacement/metadata barrier must either succeed and be verified or abort before B authority. Keep Linux/macOS behavior correct. Add focused native proof for each production helper and require all 68 named Windows durability scenarios to execute beyond setup and pass.

  2. Never publish a committed journal marker before all required verification. The current journal path writes durable C before post-write verification, then tries to roll back to P if verification fails. A crash or rollback-write failure can leave C authoritative even though verification failed. Use a monotonic prepare-then-commit protocol: write encrypted P to an inactive/self-contained slot, flush with the supported primitive, reopen through an independent handle, authenticate and verify exact bytes/schema/generation/credential references, then and only then durably publish C or an atomic commit selector. Recovery must ignore every P record. Once C is authoritative it must never need rollback. Inject termination and failures after every P write/flush/close/reopen/verify and every C write/replace/flush; every restart must choose complete usable A or fully verified B, never unverified B.

  3. Keep mirror repair subordinate to the authenticated journal. With a valid C journal, missing/truncated/malformed/schema-invalid/stale profiles.json is repaired only after journal recovery; permission and real I/O failures remain fixed redacted fail-closed errors. With no valid C, invalid mirrors must not be laundered. Exercise these cases in the actual Windows runner as well as Linux parity.

  4. Make native CI evidence explicit. The Windows job must fail if any required scenario is skipped or exits during setup, report expected/executed counts by category, and cover real child termination/restart, reordered visibility, missing/corrupt slots, pointer or mirror rollback, revocation crash/offline retry/terminal cleanup, and cancellation/switch before commit. Run actual windows-latest x64 production code plus desktop test/typecheck/package, Linux transaction parity, clean npm ci then Validate Changes, configured Full Suite, actionlint, and git diff --check. Do not claim the native proof until the full scenario matrix passes.

@propr-dev

propr-dev Bot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor Author

Applied the requested follow-up changes in commit 207fbc5

Summary of Changes

Implemented the exact-head durability follow-up without syncing the transport base.

Key changes:

  • Replaced read-only fsync handles with minimum-access O_WRONLY handles plus FileHandle.sync(), matching Node 22’s documented flush API. Node.js filesystem documentation
  • Reworked journals to durable P → close → independent reopen/authenticate/exact-verify → C → writable flush. Recovery never selects P, and C is never rolled back.
  • Preserved verified B without revoking it if C becomes observable but its final flush reports failure.
  • Enforced journal-first mirror repair with fixed redacted errors for real I/O failures.
  • Added strict CI accounting for all 68 tests and barrier, transaction, visibility, mirror, revocation, and cancellation categories.
  • Expanded Windows CI to run native durability, desktop tests/typecheck, and x64 packaging; added Linux transaction parity.

Verification passed:

  • Clean npm ci
  • Linux durability parity: 68/68 passed, 0 skipped
  • Desktop tests: 87/87
  • Fast unit tests: 279/279
  • Desktop and renderer typecheck
  • Linux x64 desktop package
  • Release metadata validation
  • git diff --check

Windows-native proof, actionlint, and the Redis-backed Full Suite remain for CI; this host is Linux and lacks Docker/Redis, so I am not claiming native Windows completion.


🤖 Implementation Details

  • Model: GPT-5.6 Sol (high)
  • Requested By: @integry
  • Time: 25m 57s
  • Tokens: 11,026,888 (10,983,388 in / 43,500 out)
  • Cost: $7.95
  • 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: 5466293129✓

@integry integry removed the AI label Aug 30, 2026
…cing the transport base.

Implemented the exact-head durability follow-up without syncing the transport base.

Key changes:

- Replaced read-only fsync handles with minimum-access `O_WRONLY` handles plus `FileHandle.sync()`, matching Node 22’s documented flush API. [Node.js filesystem documentation](https://nodejs.org/docs/latest-v22.x/api/fs.html)
- Reworked journals to durable `P` → close → independent reopen/authenticate/exact-verify → `C` → writable flush. Recovery never selects `P`, and `C` is never rolled back.
- Preserved verified B without revoking it if C becomes observable but its final flush reports failure.
- Enforced journal-first mirror repair with fixed redacted errors for real I/O failures.
- Added strict CI accounting for all 68 tests and barrier, transaction, visibility, mirror, revocation, and cancellation categories.
- Expanded Windows CI to run native durability, desktop tests/typecheck, and x64 packaging; added Linux transaction parity.

Verification passed:

- Clean `npm ci`
- Linux durability parity: 68/68 passed, 0 skipped
- Desktop tests: 87/87
- Fast unit tests: 279/279
- Desktop and renderer typecheck
- Linux x64 desktop package
- Release metadata validation
- `git diff --check`

Windows-native proof, actionlint, and the Redis-backed Full Suite remain for CI; this host is Linux and lacks Docker/Redis, so I am not claiming native Windows completion.

PR: #1977
Comment by: @integry (ID: 5466293129)
Model: gpt-5.6-sol
@integry integry added the AI label Aug 30, 2026
@integry

integry commented Aug 30, 2026

Copy link
Copy Markdown
Owner

!propr Fix the two remaining exact-head transaction gaps on 207fbc5. Do not merge or sync the transport base. Preserve the now-passing 68/68 native Windows durability matrix, Windows-supported barriers, journal-first mirror repair, identity epoch, pending revocation ordering, and cancellation/profile-switch gate.

  1. Make first-ever bootstrap and legacy migration crash-safe at every prepared boundary. Current empty-store recovery synthesizes v1 then writes state; if the process dies after the first P journal write/fsync/close/reopen/verification but before the first C, profiles.json is absent and only P remains. Next recovery sees mirrorMissing plus sawPreparedJournal and fails permanently. The same class must be covered for first v1/v2-to-v3 migration. Either durably establish an authenticated canonical empty A committed journal before any fallible mutation/migration, or define a narrowly authenticated prepared-bootstrap record that recovery can deterministically resolve back to complete usable empty/legacy A without ever activating staged B. An arbitrary P from an interrupted A-to-B commit must still never become authoritative. Add real child-process kills at every bootstrap and migration P/C/write/flush/close/reopen/verify/selector boundary on Windows and Linux, then restart repeatedly. Require canonical usable A or complete B as appropriate, never a bricked store or staged B.

  2. Keep the verified prepared bytes handle-bound through commit. The current path closes the descriptor after verification and reopens the pathname before writing C, with no identity/hash binding; a same-user ABA replacement can cause a different unverified record to receive the commit marker. Prefer verifying the prepared record and publishing the C marker or selector through the same held writable/write-through descriptor, then sync and re-read/authenticate that same handle. If a reopen is unavoidable, compare lossless native volume/file identity, size, generation, authenticated ciphertext/hash, mode/owner/link count, and no-reparse state before any commit write. Add deterministic swaps between prepared verification and C publication, including same-size/generation attacker records and path restoration ABA; attacker bytes must never become committed and failure must preserve A.

  3. Extend native evidence rather than reducing it. Keep the existing Windows 68/68 categories and add explicit bootstrap/migration and verified-handle-swap category counts with zero skips. Exercise production child processes on windows-latest x64 and Linux parity. Rerun desktop tests/typecheck/package, profile-store/credential-service/electronAdapters/IPC suites, clean npm ci then Validate Changes, configured Full Suite, actionlint, and git diff --check. Do not claim completion until all original 68 plus every new bootstrap/migration/swap scenario executes past setup and passes.

@propr-dev

propr-dev Bot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor Author

Applied the requested follow-up changes in commit 339110d

Summary of Changes

Implemented the two exact-head durability fixes without touching the transport base.

  • Recovery now accepts only an authenticated generation-1 canonical empty prepared bootstrap, reconstructs empty A, and retries publication. Other prepared records remain non-authoritative.
  • Prepared verification, C publication, flush, re-authentication, and close remain bound to one writable handle. Native identity/metadata checks reject pathname replacement before commit.
  • Added real child-kill coverage across bootstrap, v1 migration, and v2 migration boundaries, plus same-size attacker swaps and path-restoration ABA.
  • Native accounting now enforces 70 top-level tests: the original 68 plus two new categories.

Verification passed:

  • Linux native durability: 70/70, zero skips
  • Transaction boundaries: 18/18
  • Bootstrap/migration: 39/39 Linux; Windows expects 36/36 supported boundaries
  • Verified-handle swaps: 2/2
  • Final Full Suite: 322/322 entries
  • Electron adapters: 9/9; IPC: 3/3
  • Clean npm ci, Validate Changes sequence, desktop typecheck/package
  • actionlint and git diff --check (official actionlint release)
  • Full Suite used checksum-verified local Redis because Docker is unavailable (official Redis hashes)

Windows-native execution cannot run on this Linux host; the existing required windows-latest x64 job now enforces the expanded zero-skip counts. No commit was created.


🤖 Implementation Details

  • Model: GPT-5.6 Sol (high)
  • Requested By: @integry
  • Time: 39m 30s
  • Tokens: 17,544,907 (17,513,739 in / 31,168 out)
  • Cost: $10.79
  • Subscription usage: Weekly +2%

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

@integry integry removed the AI label Aug 30, 2026
…ching the transport base.

Implemented the two exact-head durability fixes without touching the transport base.

- Recovery now accepts only an authenticated generation-1 canonical empty prepared bootstrap, reconstructs empty A, and retries publication. Other prepared records remain non-authoritative.
- Prepared verification, `C` publication, flush, re-authentication, and close remain bound to one writable handle. Native identity/metadata checks reject pathname replacement before commit.
- Added real child-kill coverage across bootstrap, v1 migration, and v2 migration boundaries, plus same-size attacker swaps and path-restoration ABA.
- Native accounting now enforces 70 top-level tests: the original 68 plus two new categories.

Verification passed:

- Linux native durability: 70/70, zero skips
- Transaction boundaries: 18/18
- Bootstrap/migration: 39/39 Linux; Windows expects 36/36 supported boundaries
- Verified-handle swaps: 2/2
- Final Full Suite: 322/322 entries
- Electron adapters: 9/9; IPC: 3/3
- Clean `npm ci`, Validate Changes sequence, desktop typecheck/package
- `actionlint` and `git diff --check` ([official actionlint release](https://github.com/rhysd/actionlint/releases))
- Full Suite used checksum-verified local Redis because Docker is unavailable ([official Redis hashes](https://github.com/redis/redis-hashes))

Windows-native execution cannot run on this Linux host; the existing required `windows-latest` x64 job now enforces the expanded zero-skip counts. No commit was created.

PR: #1977
Comment by: @integry (ID: 5466441009)
Model: gpt-5.6-sol
@integry integry added the AI label Aug 30, 2026
@integry

integry commented Aug 30, 2026

Copy link
Copy Markdown
Owner

!propr Fix the three broad-audit blockers on exact head 339110d. Do not merge or sync transport. Preserve the passing 70/70 Windows durability matrix, bootstrap/migration recovery, same-handle journal commit, identity epochs, request stripping, exact origin/ticket/scope binding, and profile/socket isolation.

  1. Journal revocation durably for every credential that leaves active authority, not only A-to-B re-pair. saveProfile origin edits and removeProfile currently detach/delete the active slot, then best-effort revoke in memory; network/5xx/crash loses the only retry token. Failed/cancelled transient pairing also best-effort revokes a newly issued token and then deletes it. Add one atomic store transition that removes a credential from activation while moving its encrypted token plus opaque exact binding into bounded pendingRevocations before returning. Use it for profile deletion, origin change/replacement, rollback after failed B publication, cancelled/stale pair after issuance, and any cleanup path. Startup and before mutations retry the durable record idempotently. A later profile with the same display/profile ID or origin must have a distinct credential generation and can never be revoked by old material. Only after terminal remote confirmation is durably recorded may the old slot be unlinked. Add child-process crashes before/after detach journal, during revoke, after remote success before local cleanup, network/5xx/offline then restart/online, repeated restart, same-ID replacement, and transient-B failure tests.

  2. Treat only definitive endpoint-bound responses as terminal revocation. Current code clears retry material for any bare 401 or 404, including empty/HTML route-missing responses. Keep 2xx terminal. For 401/404 require strict bounded JSON from the exact revocation endpoint with a documented stable code such as TOKEN_NOT_FOUND, INSTANCE_TOKEN_REVOKED, or INSTANCE_TOKEN_EXPIRED and schema/version binding sufficient to prove the addressed token is no longer active. If the server endpoint does not emit this contract, add it consistently to API route/types/client. Generic auth 401, proxy or route 404, HTML, empty, malformed, wrong content type/code/version, redirect, timeout, network and 5xx remain retryable with encrypted material intact and fixed redacted diagnostics. Add exact positive and negative tests.

  3. Centralize one loopback origin rule across renderer, shared client, Electron main, profile store, Connect reconciliation, REST and Socket.IO. The UI currently accepts localhost subdomains and all 127/8 while main accepts only localhost, 127.0.0.1 and ::1, causing post-IPC rejection. Create a shared canonical origin validator used everywhere. Permit HTTPS generally; permit HTTP only for exact loopback forms intentionally supported: localhost and its standards-defined subdomains, IPv4 127.0.0.0/8, and exact bracketed ::1 after URL canonicalization. Reject credentials, non-default path/query/fragment for origins, encoded host tricks, trailing-dot ambiguity unless explicitly canonicalized, IPv4-mapped or alternate numeric forms not deliberately supported, wildcard lookalikes, and non-loopback HTTP. Add a parity table executed against every call site plus save/pair/activate/apiClient/SocketProvider end-to-end tests. Preserve exact origin binding so canonicalization cannot broaden credential scope.

  4. Re-run all profile-store/credential-service child crash/recovery and revocation suites, actual Windows native 70 plus new detach/transient categories with zero skips, renderer same-origin re-pair and multi-profile isolation, IPC/security/baseUrl/apiClient/SocketProvider, desktop/UI typecheck/build/package, clean npm ci then Validate Changes with actionlint, configured Full Suite, and git diff --check. Do not review or merge until the broader audit passes.

@propr-dev

propr-dev Bot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor Author

Applied the requested follow-up changes in commit b3f331b

Summary of Changes

Implemented the three broad-audit fixes on exact head 339110d9d9aadb36a1e452d527cbb06a7016ec84.

  • All credential detachments now atomically move encrypted material and generation-bound metadata into bounded durable revocation records.
  • Revocation cleanup requires exact endpoint-bound terminal responses; malformed, redirected, generic 401/404, network, timeout, and 5xx responses retain retry material.
  • One shared canonical origin validator now covers client, renderer, Electron, store, Connect, REST, Socket.IO, CORS, and session handling.
  • Added detach/revocation crash recovery, transient pairing, same-ID replacement, strict response-contract, and origin parity coverage.

Validation:

  • Clean npm ci: passed, zero vulnerabilities.
  • Native durability matrix: 91/91, zero skips.
  • Configured Full Suite: 321 test files plus UI workspace passed; UI 525/525.
  • Final focused desktop/API/UI tests passed.
  • Typechecks, lint, API/UI builds, desktop package, browser smoke, notification suites, actionlint, and git diff --check: passed.
  • Actual Windows execution remains for the configured Windows CI runner; the native runner now expects 91/91 there.

No commit, merge, PR, or transport sync was performed.


🤖 Implementation Details

  • Model: GPT-5.6 Sol (high)
  • Requested By: @integry
  • Time: 71m 11s
  • Tokens: 47,554,736 (47,462,344 in / 92,392 out)
  • Cost: $29.74
  • Subscription usage: Weekly +4%

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

@integry integry removed the AI label Aug 30, 2026
…10d9d9aadb36a1e452d527cbb06a7016ec84`.

Implemented the three broad-audit fixes on exact head `339110d9d9aadb36a1e452d527cbb06a7016ec84`.

- All credential detachments now atomically move encrypted material and generation-bound metadata into bounded durable revocation records.
- Revocation cleanup requires exact endpoint-bound terminal responses; malformed, redirected, generic 401/404, network, timeout, and 5xx responses retain retry material.
- One shared canonical origin validator now covers client, renderer, Electron, store, Connect, REST, Socket.IO, CORS, and session handling.
- Added detach/revocation crash recovery, transient pairing, same-ID replacement, strict response-contract, and origin parity coverage.

Validation:

- Clean `npm ci`: passed, zero vulnerabilities.
- Native durability matrix: **91/91**, zero skips.
- Configured Full Suite: 321 test files plus UI workspace passed; UI **525/525**.
- Final focused desktop/API/UI tests passed.
- Typechecks, lint, API/UI builds, desktop package, browser smoke, notification suites, actionlint, and `git diff --check`: passed.
- Actual Windows execution remains for the configured Windows CI runner; the native runner now expects 91/91 there.

No commit, merge, PR, or transport sync was performed.

PR: #1977
Comment by: @integry (ID: 5466946794)
Model: gpt-5.6-sol
@integry integry added the AI label Aug 30, 2026
@integry

integry commented Aug 30, 2026

Copy link
Copy Markdown
Owner

!propr Fix the remaining exact-head remote retry lifecycle and bounded-response blockers on b3f331b. Do not merge or sync transport. Preserve encrypted pending revocation for delete/edit/transient paths, strict endpoint-bound terminal codes, credential generations, shared canonical loopback parity, exact scope binding, and the now-passing Windows native 91/91 matrix.

  1. Track and drain every background revocation retry. Desktop runtime job 99220129984 passes 110/111 but the same-profile reprobe test teardown fails ENOTEMPTY because a fire-and-forget retry continues touching the profile directory after teardown. Replace untracked void promises with a bounded task group owned by CredentialService/ProfileStore lifecycle. Deduplicate retries by immutable revocation generation, serialize them with mutations, propagate AbortSignal, and expose an awaitIdle or close/dispose method that prevents new work, aborts network waits, awaits local journal cleanup/fs operations, and resolves only when no task can touch state. App shutdown/profile test teardown must call it. A retry failure remains durably journaled and yields only fixed diagnostics; shutdown must not silently discard it. Add deterministic barriers proving dispose during fetch/body/journal-write/retry leaves consistent encrypted material and no post-dispose file/network activity.

  2. Read terminal 401/404 JSON with an independent byte and time bound while streaming. isEndpointBoundTerminalRevocation currently calls response.clone().text and checks 2 KiB only after allocation; ProprClient clears its 8-second abort timer when headers arrive, so chunked or stalled bodies can hang startup and grow unbounded. Implement a streaming reader that accepts at most 2048 bytes and a short fixed body deadline, cancels the reader/network on overflow, timeout, abort, malformed UTF-8, extra chunks or premature error, and never calls unbounded text/json helpers. Retain pending revocation for every such failure. Keep strict content type, schema version and exact terminal code binding. Add chunked 2048/2049, no Content-Length, deceptive Content-Length, one-byte slowloris, headers-then-stall, abort, malformed UTF-8/JSON, HTML/empty generic 401/404 and valid terminal tests.

  3. Bound startup independently. initialize may retry pending revocations but must never wait beyond fixed connect/header/body/overall deadlines per bounded record and a fixed aggregate startup budget. Remaining records stay encrypted for later retry, only B authorizes, and UI startup receives a fixed degraded/retry status rather than hanging. Test multiple stalled records, cancellation and later online recovery.

  4. Re-run the exact desktop runtime test repeatedly to prove no ENOTEMPTY/open-handle leak, all profile-store/credential-service crash/recovery/revocation suites, actual Windows native 91 plus new lifecycle/body scenarios with zero skips, origin parity tables, renderer/API/socket isolation, desktop/UI typecheck/build/package, clean npm ci then Validate Changes/actionlint, configured Full Suite, and git diff --check. Do not review or merge until package audit and Full Suite are green on the exact head.

@propr-dev

propr-dev Bot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor Author

Applied the requested follow-up changes in commit 6fc8e8f

Summary of Changes

Implemented the follow-up changes on base head b3f331bb21dd1bb578a16f19510a048d57cd1a56 without committing, merging, or syncing transport.

Key changes:

  • Added tracked, generation-deduplicated revocation retry lifecycle with awaitIdle()/dispose().
  • Disposal prevents new work, aborts network/body waits, and drains ProfileStore operations.
  • Integrated credential disposal into app shutdown and all credential-service test teardown.
  • Replaced unbounded terminal-body parsing with fatal UTF-8 streaming capped at 2048 bytes and fixed header/body/record/aggregate deadlines.
  • Added bounded startup with fixed degraded/retry status and later recovery.
  • Added 12 lifecycle, streaming-body, cancellation, journal-barrier, and startup-budget scenarios.
  • Expanded the native durability guard from 91 to 103 tests.

Validation passed:

  • Clean npm ci: 0 vulnerabilities.
  • Full Suite: all 321 non-live files plus native workspace suite passed in 598.3s.
  • Native durability: 103/103, zero skips.
  • Desktop runtime: 123/123.
  • Credential suite: 67/67; same-profile reprobe repeated 10 additional times.
  • Renderer isolation: 47/47.
  • API/socket tests: 49/49.
  • Desktop/UI typechecks and UI build.
  • Linux x64 desktop packaging.
  • Runtime and packaging audits: 0 vulnerabilities.
  • Checksum-verified actionlint: passed.
  • git diff --check: passed.

Actual Windows-native execution cannot run on this Linux host. The required windows-latest workflow is configured to run the expanded 103-test matrix after the system commits these changes; review/merge should remain blocked until that exact-head CI job is green.


🤖 Implementation Details

  • Model: GPT-5.6 Sol (high)
  • Requested By: @integry
  • Time: 53m 14s
  • Tokens: 23,145,299 (23,098,916 in / 46,383 out)
  • Cost: $15.87
  • Subscription usage: Weekly +3%

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

@integry integry removed the AI label Aug 30, 2026
…d1bb578a16f19510a048d57cd1a56` without committing, merging, or syncing transport.

Implemented the follow-up changes on base head `b3f331bb21dd1bb578a16f19510a048d57cd1a56` without committing, merging, or syncing transport.

Key changes:

- Added tracked, generation-deduplicated revocation retry lifecycle with `awaitIdle()`/`dispose()`.
- Disposal prevents new work, aborts network/body waits, and drains ProfileStore operations.
- Integrated credential disposal into app shutdown and all credential-service test teardown.
- Replaced unbounded terminal-body parsing with fatal UTF-8 streaming capped at 2048 bytes and fixed header/body/record/aggregate deadlines.
- Added bounded startup with fixed degraded/retry status and later recovery.
- Added 12 lifecycle, streaming-body, cancellation, journal-barrier, and startup-budget scenarios.
- Expanded the native durability guard from 91 to 103 tests.

Validation passed:

- Clean `npm ci`: 0 vulnerabilities.
- Full Suite: all 321 non-live files plus native workspace suite passed in 598.3s.
- Native durability: 103/103, zero skips.
- Desktop runtime: 123/123.
- Credential suite: 67/67; same-profile reprobe repeated 10 additional times.
- Renderer isolation: 47/47.
- API/socket tests: 49/49.
- Desktop/UI typechecks and UI build.
- Linux x64 desktop packaging.
- Runtime and packaging audits: 0 vulnerabilities.
- Checksum-verified actionlint: passed.
- `git diff --check`: passed.

Actual Windows-native execution cannot run on this Linux host. The required `windows-latest` workflow is configured to run the expanded 103-test matrix after the system commits these changes; review/merge should remain blocked until that exact-head CI job is green.

PR: #1977
Comment by: @integry (ID: 5467269590)
Model: gpt-5.6-sol
@integry integry added the AI label Aug 30, 2026
@integry

integry commented Aug 30, 2026

Copy link
Copy Markdown
Owner

!propr Fix the final two exact-head remote lifecycle blockers on 6fc8e8f. Resolve and use the full current SHA before editing. Do not merge or sync transport. Preserve bounded streaming terminal-body parsing, aggregate startup budget/coalesced retry worker, encrypted detach/edit/transient revocation journal, strict terminal codes, origin parity, identity epochs, same-handle profile transactions, and the expanded native durability matrix.

  1. Make server token issuance provisional until the desktop has durably journaled recovery material. pollPairing currently creates and returns a normal non-expiring instance token before the client can record it; a process kill in that delivery interval leaves an indefinite active token with no local revocation handle. Add a two-phase pairing protocol and persistent server state. Poll may create or return one short-lived provisional credential bound to exact pairing, instance, requested origin/scope and opaque credential generation; provisional credentials must be rejected by every API and Socket.IO auth path. Return a one-time activation ticket and fixed expiry. Before activation, desktop durably writes the encrypted credential plus pending-revocation/rollback record. It then calls an exact no-redirect activation endpoint; server atomically validates unexpired pairing/ticket/binding, marks that credential active once, consumes the pairing/ticket, and returns a bounded activation receipt. Only after the receipt and durable local B commit may renderer scope publish. Crash before local journal leaves only an unusable server provisional that expires; crash after journal but before activation retains revocation/expiry material; crash after server activation but before B commit causes startup retry to revoke and A remains active. Add cleanup for abandoned provisional rows and database migration/rollback tests. No plaintext token in logs or durable non-OS-backed storage.

  2. Make pairing poll/activation idempotent and replay-safe. Repeated poll before activation returns the same provisional binding without minting extras; activation ticket is one-use, exact origin/scope/generation bound, short-lived and not accepted as bearer auth. Duplicate activation returns the same safe receipt only for the exact committed transaction; wrong instance/profile/origin/scope/generation, expired/cancelled/consumed pairing, redirect or replay fails. Cancellation revokes/expires provisional server-side and client retains local retry until definitive confirmation. Test network loss after activation response, server commit before response, duplicate poll/activate, concurrent devices and database restart.

  3. Put every public credential and profile operation under one closable task gate. dispose currently tracks only the revocation worker and pairing controllers; pre-existing save/probe/activate/pair catches and direct IPC ProfileStore reads can enqueue journal/file/network work after it captured awaitIdle. Wrap initialize/list/get/save/remove/probe/activate/pair/cancel/retry and every cleanup/catch continuation in an operation task group that rejects new work after close, owns AbortControllers, waits for all descendants, then drains ProfileStore. No task may spawn untracked work after its parent settles. Route profilesList and list-before-activate through the gated service or make ProfileStore explicitly closable under the same gate.

  4. Unregister or fail closed every desktop IPC handler during shutdown before draining. Keep disposer functions for profile, pairing, session and setup handlers; before-quit first prevents new invokes, removes handlers, aborts credential operations, awaits CredentialService.dispose plus ProfileStore/lifecycle idle, then destroys session/window. A late invoke returns a fixed closing error and touches no file/network. Add deterministic barriers for dispose racing every public method, pair catch after issuance, direct list/activate IPC, revocation journal write, response body and server activation; assert no post-dispose events and no ENOTEMPTY/open handle.

  5. Rerun server pairing/token authentication/migration tests, desktop credential/profile/IPC crash/recovery and shutdown suites, actual Windows native durability with provisional/delivery/dispose categories and zero skips, remote REST/Socket.IO auth/isolation, UI/desktop typecheck/build/package, clean npm ci then Validate Changes/actionlint, configured Full Suite, and git diff --check. Do not review or merge until provisional credentials are proven unusable and all foreground/background work drains before dispose resolves.

@propr-dev

propr-dev Bot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor Author

Applied the requested follow-up changes in commit 5d9d48b

Summary of Changes

Implemented on exact HEAD 6fc8e8fe244c6e3c5fd71f9257c162b9eaa4835f without merging, syncing, or committing.

Key changes:

  • Added persistent two-phase pairing with expiring provisional credentials, exact binding, one-time activation tickets, idempotent activation receipts, cancellation, cleanup, and migration rollback.
  • Provisional credentials are rejected by REST and Socket.IO authentication.
  • Desktop now durably journals encrypted rollback/revocation material before server activation and publishes renderer scope only after activation and local commit.
  • Added a closeable operation gate covering credential/profile work and descendants.
  • IPC and session handlers fail closed before shutdown draining; late invokes return DESKTOP_CLOSING.
  • Expanded native durability coverage with provisional, delivery, and disposal categories.

Validation:

  • Two-phase server tests: 16/16
  • Native durability: 104/104, zero skips
  • Fast unit tests: 279/279
  • Migration and IPC lifecycle tests: passed
  • Client, API, desktop, REST/Socket.IO, UI typecheck/build, desktop package, release verification, and CLI package: passed
  • Clean npm ci: passed
  • git diff --check: passed
  • Full suite: 323/324 runs; only llmMetrics.test.ts exceeded its existing 180-second timeout
  • Windows-native execution and actionlint could not run locally because this Linux environment has neither Docker nor an actionlint binary. No workflow files were modified.

🤖 Implementation Details

  • Model: GPT-5.6 Sol (high)
  • Requested By: @integry
  • Time: 48m 12s
  • Tokens: 33,209,109 (33,152,181 in / 56,928 out)
  • Cost: $20.79
  • Subscription usage: Weekly +4%

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

@integry integry removed the AI label Aug 30, 2026
@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/322] apps/desktop/src/credential-service.test.ts
TAP version 13
# Subtest: main-process desktop credential service
    # Subtest: injects the active bearer only for its bound profile origin and strips renderer identity
    ok 1 - injects the active bearer only for its bound profile origin and strips renderer identity
      ---
      duration_ms: 90.440383
      type: 'test'
      ...
    # Subtest: uses only the active bearer when profiles share an origin and never a cookie identity
    ok 2 - uses only the active bearer when profiles share an origin and never a cookie identity
      ---
      duration_ms: 47.088567
      type: 'test'
      ...
    # Subtest: detaches profile B credential A without sending any bearer request to A or minting a ticket
    ok 3 - detaches profile B credential A without sending any bearer request to A or minting a ticket
      ---
      duration_ms: 44.319147
      type: 'test'
      ...
    # Subtest: does not mint a ticket when a delayed B probe observes credential replacement with origin A
    ok 4 - does not mint a ticket when a delayed B probe observes credential replacement with origin A
      ---
      duration_ms: 37.393444
      type: 'test'
      ...
    # Subtest: atomically rejects a ticket when delayed activation races with profile B credential A
    ok 5 - atomically rejects a ticket when delayed activation races with profile B credential A
      ---
      duration_ms: 38.192043
      type: 'test'
      ...
    # Subtest: keeps a slow successful same-origin A probe status-only after fast B activates
    ok 6 - keeps a slow successful same-origin A probe status-only after fast B activates
      ---
      duration_ms: 52.405249
      type: 'test'
      ...
    # Subtest: keeps A active while B is only probed and if B selection persistence fails
    ok 7 - keeps A active while B is only probed and if B selection persistence fails
      ---
      duration_ms: 56.064328
      type: 'test'
      ...
    # Subtest: keeps B active during a direct same-origin A probe and rejects replayed activation tickets
    ok 8 - keeps B active during a direct same-origin A probe and rejects replayed activation tickets
      ---
      duration_ms: 57.628526
      type: 'test'
      ...
    # Subtest: rejects activation after candidate removal, selection drift, or exact credential replacement
    ok 9 - rejects activation after candidate removal, selection drift, or exact credential replacement
      ---
      duration_ms: 190.348903
      type: 'test'
      ...
    # Subtest: binds REST and Socket.IO work to one fresh scope and rejects stale or malformed markers
    ok 10 - binds REST and Socket.IO work to one fresh scope and rejects stale or malformed markers
      ---
      duration_ms: 61.037799
      type: 'test'
      ...
    # Subtest: passes through a realistic packaged-origin CORS preflight without renderer identity or bearer injection
    ok 11 - passes through a realistic packaged-origin CORS preflight without renderer identity or bearer injection
      ---
      duration_ms: 0.476166
      type: 'test'
      ...
    # Subtest: rotates scope on every same-profile reprobe and rejects a cold reconnect from the old activation
    ok 12 - rotates scope on every same-profile reprobe and rejects a cold reconnect from the old activation
      ---
      duration_ms: 42.740178
      type: 'test'
      ...
    # Subtest: never sends an A-origin bearer after the profile URL is edited to an attacker origin
    ok 13 - never sends an A-origin bearer after the profile URL is edited to an attacker origin
      ---
      duration_ms: 19.580327
      type: 'test'
      ...
    # Subtest: preserves a re-paired credential and current connection after a stale definitive probe response
    ok 14 - preserves a re-paired credential and current connection after a stale definitive probe response
      ---
      duration_ms: 69.342042
      type: 'test'
      ...
    # Subtest: preserves a replacement credential at a changed origin after a stale definitive probe response
    ok 15 - preserves a replacement credential at a changed origin after a stale definitive probe response
      ---
      duration_ms: 64.44824
      type: 'test'
      ...
    # Subtest: preserves the active profile and credential when an origin edit fails during browser-launch
    ok 16 - preserves the active profile and credential when an origin edit fails during browser-launch
      ---
      duration_ms: 41.413485
      type: 'test'
      ...
    # Subtest: preserves the active profile and credential when an origin edit fails during cancellation
    ok 17 - preserves the active profile and credential when an origin edit fails during cancellation
      ---
      duration_ms: 48.367325
      type: 'test'
      ...
    # Subtest: preserves the active profile and credential when an origin edit fails during expiry
    ok 18 - preserves the active profile and credential when an origin edit fails during expiry
      ---
      duration_ms: 41.282899
      type: 'test'
      ...
    # Subtest: preserves the active profile and credential when an origin edit fails during polling
    ok 19 - preserves the active profile and credential when an origin edit fails during polling
      ---
      duration_ms: 43.505796
      type: 'test'
      ...
    # Subtest: preserves the active profile and credential when an origin edit fails during secure-storage
    ok 20 - preserves the active profile and credential when an origin edit fails during secure-storage
      ---
      duration_ms: 46.661888
      type: 'test'
      ...
    # Subtest: commits an edited profile and replacement credential before revoking the old token
    ok 21 - commits an edited profile and replacement credential before revoking the old token
      ---
      duration_ms: 46.319151
      type: 'test'
      ...
    # Subtest: retries an encrypted pending A revocation across failure, restart, remote success, and local cleanup failure
    ok 22 - retries an encrypted pending A revocation across failure, restart, remote success, and local cleanup failure
      ---
      duration_ms: 147.234597
      type: 'test'
      ...
# NATIVE_SCENARIO revocation-crash
    # Subtest: recovers B and retries idempotently after a real process crash during-revoke
    ok 23 - recovers B and retries idempotently after a real process crash during-revoke
      ---
      duration_ms: 393.684417
      type: 'test'
      ...
# NATIVE_SCENARIO revocation-crash
    # Subtest: recovers B and retries idempotently after a real process crash after-remote-success
    ok 24 - recovers B and retries idempotently after a real process crash after-remote-success
      ---
      duration_ms: 385.919662
      type: 'test'
      ...
    # Subtest: cleans durable retry material only for endpoint-bound terminal 204 success
    ok 25 - cleans durable retry material only for endpoint-bound terminal 204 success
      ---
      duration_ms: 31.844223
      type: 'test'
      ...
    # Subtest: cleans durable retry material only for endpoint-bound terminal 404 TOKEN_NOT_FOUND
    ok 26 - cleans durable retry material only for endpoint-bound terminal 404 TOKEN_NOT_FOUND
      ---
      duration_ms: 40.301296
      type: 'test'
      ...
    # Subtest: cleans durable retry material only for endpoint-bound terminal 401 INSTANCE_TOKEN_REVOKED
    ok 27 - cleans durable retry material only for endpoint-bound terminal 401 INSTANCE_TOKEN_REVOKED
      ---
      duration_ms: 30.761419
      type: 'test'
      ...
    # Subtest: cleans durable retry material only for endpoint-bound terminal 401 INSTANCE_TOKEN_EXPIRED
    ok 28 - cleans durable retry material only for endpoint-bound terminal 401 INSTANCE_TOKEN_EXPIRED
      ---
      duration_ms: 28.880766
      type: 'test'
      ...
    # Subtest: retains encrypted retry material for empty 401
    ok 29 - retains encrypted retry material for empty 401
      ---
      duration_ms: 29.396286
      type: 'test'
      ...
    # Subtest: retains encrypted retry material for empty 404
    ok 30 - retains encrypted retry material for empty 404
      ---
      duration_ms: 21.998308
      type: 'test'
      ...
    # Subtest: retains encrypted retry material for HTML route 404
    ok 31 - retains encrypted retry material for HTML route 404
      ---
      duration_ms: 21.657153
      type: 'test'
      ...
    # Subtest: retains encrypted retry material for malformed JSON
    ok 32 - retains encrypted retry material for malformed JSON
      ---
      duration_ms: 24.515162
      type: 'test'
      ...
    # Subtest: retains encrypted retry material for wrong content type
    ok 33 - retains encrypted retry material for wrong content type
      ---
      duration_ms: 24.685842
      type: 'test'
      ...
    # Subtest: retains encrypted retry material for wrong schema version
    ok 34 - retains encrypted retry material for wrong schema version
      ---
      duration_ms: 31.99304
      type: 'test'
      ...
    # Subtest: retains encrypted retry material for wrong credential generation
    ok 35 - retains encrypted retry material for wrong credential generation
      ---
      duration_ms: 22.805602
      type: 'test'
      ...
    # Subtest: retains encrypted retry material for unknown terminal code
    ok 36 - retains encrypted retry material for unknown terminal code
      ---
      duration_ms: 24.060136
      type: 'test'
      ...
    # Subtest: retains encrypted retry material for status/code mismatch
    ok 37 - retains encrypted retry material for status/code mismatch
      ---
      duration_ms: 22.511302
      type: 'test'
      ...
    # Subtest: retains encrypted retry material for redirect
    ok 38 - retains encrypted retry material for redirect
      ---
      duration_ms: 35.44736
      type: 'test'
      ...
    # Subtest: retains encrypted retry material for redirected 204
    ok 39 - retains encrypted retry material for redirected 204
      ---
      duration_ms: 23.709783
      type: 'test'
      ...
    # Subtest: retains encrypted retry material for wrong endpoint 204
    ok 40 - retains encrypted retry material for wrong endpoint 204
      ---
      duration_ms: 22.958689
      type: 'test'
      ...
    # Subtest: retains encrypted retry material for server failure
    ok 41 - retains encrypted retry material for server failure
      ---
      duration_ms: 21.799159
      type: 'test'
      ...
    # Subtest: retains encrypted retry material for oversized JSON
    ok 42 - retains encrypted retry material for oversized JSON
      ---
      duration_ms: 29.481198
      type: 'test'
      ...
    # Subtest: accepts encrypted retry material for chunked 2048-byte terminal JSON
    ok 43 - accepts encrypted retry material for chunked 2048-byte terminal JSON
      ---
      duration_ms: 28.044978
      type: 'test'
      ...
    # Subtest: retains encrypted retry material for chunked 2049-byte terminal JSON
    ok 44 - retains encrypted retry material for chunked 2049-byte terminal JSON
      ---
      duration_ms: 25.149355
      type: 'test'
      ...
    # Subtest: accepts encrypted retry material for terminal JSON without Content-Length
    ok 45 - accepts encrypted retry material for terminal JSON without Content-Length
      ---
      duration_ms: 24.47558
      type: 'test'
      ...
    # Subtest: retains encrypted retry material for deceptive short Content-Length
    ok 46 - retains encrypted retry material for deceptive short Content-Length
      ---
      duration_ms: 28.85512
      type: 'test'
      ...
    # Subtest: retains encrypted retry material for extra chunk after declared Content-Length
    ok 47 - retains encrypted retry material for extra chunk after declared Content-Length
      ---
      duration_ms: 21.972607
      type: 'test'
      ...
    # Subtest: retains encrypted retry material for malformed UTF-8
    ok 48 - retains encrypted retry material for malformed UTF-8
      ---
      duration_ms: 22.688795
      type: 'test'
      ...
    # Subtest: retains encrypted retry material for premature body error
    ok 49 - retains encrypted retry material for premature body error
      ---
      duration_ms: 21.624607
      type: 'test'
      ...
    # Subtest: bounds a one-byte slowloris body and retains its encrypted retry material
    ok 50 - bounds a one-byte slowloris body and retains its encrypted retry material
      ---
      duration_ms: 47.300837
      type: 'test'
      ...
    # Subtest: dispose aborts a stalled header fetch, deduplicates its generation, and leaves no later activity
    ok 51 - dispose aborts a stalled header fetch, deduplicates its generation, and leaves no later activity
      ---
      duration_ms: 43.679905
      type: 'test'
      ...
    # Subtest: dispose cancels a headers-then-stall body and retains exact encrypted material
    ok 52 - dispose cancels a headers-then-stall body and retains exact encrypted material
      ---
      duration_ms: 42.737689
      type: 'test'
      ...
    # Subtest: dispose waits for terminal journal cleanup and no file operation runs afterward
    ok 53 - dispose waits for terminal journal cleanup and no file operation runs afterward
      ---
      duration_ms: 62.094455
      type: 'test'
      ...
    # Subtest: bounds aggregate startup across stalled records and recovers all encrypted records later
    ok 54 - bounds aggregate startup across stalled records and recovers all encrypted records later
      ---
      duration_ms: 255.390248
      type: 'test'
      ...
# NATIVE_SCENARIO transient-revocation
    # Subtest: retries a crash-left provisional pairing credential on startup
    ok 55 - retries a crash-left provisional pairing credential on startup
      ---
      duration_ms: 30.712758
      type: 'test'
      ...
    # Subtest: ignores delayed A invalidation after B connects and preserves tokens for authorization/transient codes
    ok 56 - ignores delayed A invalidation after B connects and preserves tokens for authorization/transient codes
      ---
      duration_ms: 85.155587
      type: 'test'
      ...
    # Subtest: preserves a replacement written while an old transient token revocation is pending
    ok 57 - preserves a replacement written while an old transient token revocation is pending
      ---
      duration_ms: 49.170115
      type: 'test'
      ...
# NATIVE_SCENARIO transient-revocation
    # Subtest: keeps an exactly persisted cancelled pairing token pending when revocation fails
    ok 58 - keeps an exactly persisted cancelled pairing token pending when revocation fails
      ---
      duration_ms: 29.935611
      type: 'test'
      ...
    # Subtest: detaches a removed profile locally before deferred revoke and preserves a later replacement
    ok 59 - detaches a removed profile locally before deferred revoke and preserves a later replacement
      ---
      duration_ms: 54.447434
      type: 'test'
      ...
    # Subtest: never lets a delayed A-to-B revoke overwrite a later C save, pairing, selection, or credential
    ok 60 - never lets a delayed A-to-B revoke overwrite a later C save, pairing, selection, or credential
      ---
      duration_ms: 72.579093
      type: 'test'
      ...
    # Subtest: returns connection-changed and preserves a re-paired credential for an old ready invalidation
    ok 61 - returns connection-changed and preserves a re-paired credential for an old ready invalidation
      ---
      duration_ms: 50.521587
      type: 'test'
      ...
# NATIVE_SCENARIO transient-revocation
    # Subtest: revokes a transient completion instead of persisting when pairing races with delete
    ok 62 - revokes a transient completion instead of persisting when pairing races with delete
      ---
      duration_ms: 43.278677
      type: 'test'
      ...
# NATIVE_SCENARIO transient-revocation
    # Subtest: revokes a transient completion instead of persisting when pairing races with switch
    ok 63 - revokes a transient completion instead of persisting when pairing races with switch
      ---
      duration_ms: 40.200045
      type: 'test'
      ...
# NATIVE_SCENARIO cancellation-switch
    # Subtest: keeps durable A when cancel linearizes at paired state-written before publish
    ok 64 - keeps durable A when cancel linearizes at paired state-written before publish
      ---
      duration_ms: 58.655282
      type: 'test'
      ...
# NATIVE_SCENARIO cancellation-switch
    # Subtest: keeps durable A when switch linearizes at paired state-written before publish
    ok 65 - keeps durable A when switch linearizes at paired state-written before publish
      ---
      duration_ms: 56.396702
      type: 'test'
      ...
# NATIVE_SCENARIO cancellation-switch
    # Subtest: keeps durable A when cancel linearizes at paired state-fsynced before publish
    ok 66 - keeps durable A when cancel linearizes at paired state-fsynced before publish
      ---
      duration_ms: 53.162439
      type: 'test'
      ...
# NATIVE_SCENARIO cancellation-switch
    # Subtest: keeps durable A when switch linearizes at paired state-fsynced before publish
    ok 67 - keeps durable A when switch linearizes at paired state-fsynced before publish
      ---
      duration_ms: 57.747492
      type: 'test'
      ...
    1..67
ok 1 - main-process desktop credential service
  ---
  duration_ms: 3866.150581
  type: 'suite'
  ...
1..1
# tests 67
# suites 1
# pass 67
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 4330.097504

[2/322] 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.719644
      type: 'test'
      ...
    1..1
ok 1 - desktop deep-link delivery
  ---
  duration_ms: 2.75568
  type: 'suite'
  ...
1..1
# tests 1
# suites 1
# pass 1
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 234.977137

[3/322] 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: 18.467043
      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.961952
      type: 'test'
      ...
    # Subtest: clears browser identity and origin storage for normalized profile origins when profiles switch
    ok 3 - clears browser identity and origin storage for normalized profile origins when profiles switch
      ---
      duration_ms: 0.848563
      type: 'test'
      ...
    1..3
ok 1 - desktop session IPC operations
  ---
  duration_ms: 21.31668
  type: 'suite'
  ...
1..1
# tests 3
# suites 1
# pass 3
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 268.128202

[4/322] 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.550507
      type: 'test'
      ...
    # Subtest: maps profile and main-process authentication operations to fixed channels
    ok 2 - maps profile and main-process authentication operations to fixed channels
      ---
      duration_ms: 0.505802
      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.363671
      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.324396
      type: 'test'
      ...
    1..4
ok 1 - desktop preload bridge
  ---
  duration_ms: 3.906732
  type: 'suite'
  ...
1..1
# tests 4
# suites 1
# pass 4
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 238.698727

[5/322] apps/desktop/src/profile-store.test.ts
TAP version 13
# Subtest: desktop profile store
    # Subtest: matches the shared canonical origin parity table at the persistence boundary
    ok 1 - matches the shared canonical origin parity table at the persistence boundary
      ---
      duration_ms: 93.823105
      type: 'test'
      ...
    # Subtest: persists validated profiles and active selection
    ok 2 - persists validated profiles and active selection
      ---
      duration_ms: 20.80543
      type: 'test'
      ...
    # Subtest: encrypts credentials before writing app-owned storage
    ok 3 - encrypts credentials before writing app-owned storage
      ---
      duration_ms: 20.872537
      type: 'test'
      ...
    # Subtest: atomically refuses activation when the credential origin differs from the profile origin
    ok 4 - atomically refuses activation when the credential origin differs from the profile origin
      ---
      duration_ms: 29.762609
      type: 'test'
      ...
    # Subtest: serializes concurrent credential writes with last-write semantics
    ok 5 - serializes concurrent credential writes with last-write semantics
      ---
      duration_ms: 18.52415
      type: 'test'
      ...
    # Subtest: orders concurrent credential writes and removals by invocation
    ok 6 - orders concurrent credential writes and removals by invocation
      ---
      duration_ms: 30.770388
      type: 'test'
      ...
    # Subtest: serializes concurrent paired replacements without mixing profile and credential generations
    ok 7 - serializes concurrent paired replacements without mixing profile and credential generations
      ---
      duration_ms: 31.279738
      type: 'test'
      ...
    # Subtest: commits encrypted pending revocation material atomically with B and unlinks A only after durable completion
    ok 8 - commits encrypted pending revocation material atomically with B and unlinks A only after durable completion
      ---
      duration_ms: 39.118116
      type: 'test'
      ...
    # Subtest: migrates legacy fixed credentials through the atomic state pointer and removes the old slot
    ok 9 - migrates legacy fixed credentials through the atomic state pointer and removes the old slot
      ---
      duration_ms: 8.755578
      type: 'test'
      ...
    # Subtest: migrates the exact-head numeric unsealed journal only when its valid mirror matches exactly
    ok 10 - migrates the exact-head numeric unsealed journal only when its valid mirror matches exactly
      ---
      duration_ms: 6.679764
      type: 'test'
      ...
    # Subtest: settles conditional credential removal and profile removal in the former lock-order interleaving
    ok 11 - settles conditional credential removal and profile removal in the former lock-order interleaving
      ---
      duration_ms: 31.535225
      type: 'test'
      ...
    # Subtest: refuses plaintext fallback when encryption is unavailable or basic_text
    ok 12 - refuses plaintext fallback when encryption is unavailable or basic_text
      ---
      duration_ms: 1.178699
      type: 'test'
      ...
    # Subtest: rejects unsafe endpoints and path-like profile identifiers
    ok 13 - rejects unsafe endpoints and path-like profile identifiers
      ---
      duration_ms: 13.244847
      type: 'test'
      ...
    # Subtest: removes an active profile despite a corrupt-json credential failure
    ok 14 - removes an active profile despite a corrupt-json credential failure
      ---
      duration_ms: 30.969777
      type: 'test'
      ...
    # Subtest: removes an active profile despite a decrypt credential failure
    ok 15 - removes an active profile despite a decrypt credential failure
      ---
      duration_ms: 27.080055
      type: 'test'
      ...
    # Subtest: preserves the complete profile and credential when state publication fails before commit
    ok 16 - preserves the complete profile and credential when state publication fails before commit
      ---
      duration_ms: 27.438876
      type: 'test'
      ...
    # Subtest: keeps A authoritative across every injected pre-commit paired replacement failure
    ok 17 - keeps A authoritative across every injected pre-commit paired replacement failure
      ---
      duration_ms: 125.524447
      type: 'test'
      ...
# NATIVE_CATEGORY barriers expected=13 executed=13
    # Subtest: fails closed before C and preserves fully verified B when the C flush fails
    ok 18 - fails closed before C and preserves fully verified B when the C flush fails
      ---
      duration_ms: 380.799396
      type: 'test'
      ...
    # Subtest: treats mirror replace and directory-flush failures after the journal commit as recoverable mirror failures
    ok 19 - treats mirror replace and directory-flush failures after the journal commit as recoverable mirror failures
      ---
      duration_ms: 53.543858
      type: 'test'
      ...
# NATIVE_CATEGORY transaction-boundaries expected=18 executed=18
    # Subtest: recovers real process crashes as complete A before the pointer commit and complete B after it
    ok 20 - recovers real process crashes as complete A before the pointer commit and complete B after it
      ---
      duration_ms: 4394.280816
      type: 'test'
      ...
# NATIVE_SCENARIO detach-crash
# NATIVE_SCENARIO detach-crash
# NATIVE_SCENARIO detach-crash
# NATIVE_SCENARIO detach-crash
# NATIVE_SCENARIO detach-crash
# NATIVE_SCENARIO detach-crash
# NATIVE_SCENARIO detach-crash
# NATIVE_SCENARIO detach-crash
# NATIVE_SCENARIO detach-crash
# NATIVE_SCENARIO detach-crash
# NATIVE_SCENARIO detach-crash
# NATIVE_SCENARIO detach-crash
# NATIVE_SCENARIO detach-crash
    # Subtest: recovers profile deletion crashes as active A or detached pending A at the journal commit
    ok 21 - recovers profile deletion crashes as active A or detached pending A at the journal commit
      ---
      duration_ms: 3167.530488
      type: 'test'
      ...
# NATIVE_CATEGORY bootstrap-migration expected=39 executed=39
    # Subtest: recovers every first bootstrap and v1/v2 migration child-process kill without activating prepared B
    ok 22 - recovers every first bootstrap and v1/v2 migration child-process kill without activating prepared B
      ---
      duration_ms: 8590.903417
      type: 'test'
      ...
# NATIVE_CATEGORY verified-handle-swap expected=2 executed=2
    # Subtest: binds verified prepared bytes to one handle across same-size swaps and path-restoration ABA
    ok 23 - binds verified prepared bytes to one handle across same-size swaps and path-restoration ABA
      ---
      duration_ms: 58.093863
      type: 'test'
      ...
    # Subtest: recovers a pointer-rollback durability view as complete A or complete B
    ok 24 - recovers a pointer-rollback durability view as complete A or complete B
      ---
      duration_ms: 31.201023
      type: 'test'
      ...
    # Subtest: recovers a missing-target durability view as complete A or complete B
    ok 25 - recovers a missing-target durability view as complete A or complete B
      ---
      duration_ms: 29.949965
      type: 'test'
      ...
    # Subtest: recovers a state-before-journal durability view as complete A or complete B
    ok 26 - recovers a state-before-journal durability view as complete A or complete B
      ---
      duration_ms: 29.867484
      type: 'test'
      ...
# NATIVE_SCENARIO mirror-repair
    # Subtest: recovers the authoritative encrypted journal before a missing mirror
    ok 27 - recovers the authoritative encrypted journal before a missing mirror
      ---
      duration_ms: 35.487003
      type: 'test'
      ...
# NATIVE_SCENARIO mirror-repair
    # Subtest: recovers the authoritative encrypted journal before a truncated mirror
    ok 28 - recovers the authoritative encrypted journal before a truncated mirror
      ---
      duration_ms: 28.906221
      type: 'test'
      ...
# NATIVE_SCENARIO mirror-repair
    # Subtest: recovers the authoritative encrypted journal before a malformed mirror
    ok 29 - recovers the authoritative encrypted journal before a malformed mirror
      ---
      duration_ms: 32.467056
      type: 'test'
      ...
# NATIVE_SCENARIO mirror-repair
    # Subtest: recovers the authoritative encrypted journal before a stale mirror
    ok 30 - recovers the authoritative encrypted journal before a stale mirror
      ---
      duration_ms: 33.054157
      type: 'test'
      ...
# NATIVE_SCENARIO mirror-repair
    # Subtest: recovers the authoritative encrypted journal before a schema-invalid mirror
    ok 31 - recovers the authoritative encrypted journal before a schema-invalid mirror
      ---
      duration_ms: 30.009201
      type: 'test'
      ...
# NATIVE_SCENARIO mirror-repair
    # Subtest: recovers the authoritative encrypted journal before a attacker-modified mirror
    ok 32 - recovers the authoritative encrypted journal before a attacker-modified mirror
      ---
      duration_ms: 33.923842
      type: 'test'
      ...
    # Subtest: fails with one fixed redacted error when neither mirror nor journal authenticates
    ok 33 - fails with one fixed redacted error when neither mirror nor journal authenticates
      ---
      duration_ms: 24.042889
      type: 'test'
      ...
    # Subtest: selects a lossless newest valid generation and survives alternate-slot rollback
    ok 34 - selects a lossless newest valid generation and survives alternate-slot rollback
      ---
      duration_ms: 29.296299
      type: 'test'
      ...
# NATIVE_CATEGORY reordered-visibility expected=11 executed=11
    # Subtest: runs every native child-termination visibility fixture with an explicit scenario count
    ok 35 - runs every native child-termination visibility fixture with an explicit scenario count
      ---
      duration_ms: 2692.993982
      type: 'test'
      ...
    # Subtest: removes an orphan credential before allowing same-ID recreation
    ok 36 - removes an orphan credential before allowing same-ID recreation
      ---
      duration_ms: 21.347846
      type: 'test'
      ...
    1..36
ok 1 - desktop profile store
  ---
  duration_ms: 20258.551203
  type: 'suite'
  ...
1..1
# tests 36
# suites 1
# pass 36
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 20549.775761

[6/322] apps/desktop/src/security.test.ts
TAP version 13
# Subtest: desktop URL security
    # Subtest: matches the shared canonical origin parity table
    ok 1 - matches the shared canonical origin parity table
      ---
      duration_ms: 2.337137
      type: 'test'
      ...
    # Subtest: only accepts HTTPS and loopback HTTP API endpoints
    ok 2 - only accepts HTTPS and loopback HTTP API endpoints
      ---
      duration_ms: 0.450282
      type: 'test'
      ...
    # Subtest: denies unsafe external browser schemes and credential-bearing URLs
    ok 3 - denies unsafe external browser schemes and credential-bearing URLs
      ---
      duration_ms: 0.289443
      type: 'test'
      ...
    # Subtest: requires an exact loopback development origin
    ok 4 - requires an exact loopback development origin
      ---
      duration_ms: 0.385083
      type: 'test'
      ...
    # Subtest: retains IPC trust for hash-routed packaged renderer URLs only
    ok 5 - retains IPC trust for hash-routed packaged renderer URLs only
      ---
      duration_ms: 0.276909
      type: 'test'
      ...
    # Subtest: allowlists custom protocol actions and extracts them from argv
    ok 6 - allowlists custom protocol actions and extracts them from argv
      ---
      duration_ms: 0.268675
      type: 'test'
      ...
    # Subtest: publishes a restrictive production policy
    ok 7 - publishes a restrictive production policy
      ---
      duration_ms: 0.439232
      type: 'test'
      ...
    # Subtest: relaxes inline scripts only while Vite serves the development renderer
    ok 8 - relaxes inline scripts only while Vite serves the development renderer
      ---
      duration_ms: 0.293819
      type: 'test'
      ...
    1..8
ok 1 - desktop URL security
  ---
  duration_ms: 6.184436
  type: 'suite'
  ...
1..1
# tests 8
# suites 1
# pass 8
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 266.906998

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

[8/322] 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.685043
      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.198421
      type: 'test'
      ...
    1..2
ok 1 - desktop BrowserWindow security
  ---
  duration_ms: 3.130615
  type: 'suite'
  ...
1..1
# tests 2
# suites 1
# pass 2
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 251.362829

[9/322] 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.051359
      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.289183
      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.132726
      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.577988
      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.404467
      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.621343
      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.610858
      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.689851
      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.595164
      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.460729
      type: 'test'
      ...
    1..10
ok 1 - agent login session manager
  ---
  duration_ms: 12.774195
  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.735898
      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.546704
      type: 'test'
      ...
    # Subtest: rejects interactive login for an unsupported agent type
    ok 3 - rejects interactive login for an unsupported agent type
      ---
      duration_ms: 0.413926
      type: 'test'
      ...
    1..3
ok 2 - agent login routes
  ---
  duration_ms: 1.825077
  type: 'suite'
  ...
# [2026-08-30 07:54:27.443 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-nivV4Y/009-agentLoginRoutes.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-30 07:54:27.453 +0000] �[32mINFO�[39m: �[36mSQLite database connection closed�[39m
# [2026-08-30 07:54:27.453 +0000] �[32mINFO�[39m: �[36mShutting down queue...�[39m
# [2026-08-30 07:54:27.453 +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 2181.736038

[10/322] 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.912893
      type: 'test'
      ...
    # Subtest: persists a failed state when queue submission fails
    ok 2 - persists a failed state when queue submission fails
      ---
      duration_ms: 0.920583
      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.676345
      type: 'test'
      ...
    # Subtest: times out slow runtime package validation before queueing
    ok 4 - times out slow runtime package validation before queueing
      ---
      duration_ms: 7.882209
      type: 'test'
      ...
    # Subtest: returns package suggestions from configured runtimes
    ok 5 - returns package suggestions from configured runtimes
      ---
      duration_ms: 0.760565
      type: 'test'
      ...
    # Subtest: enforces PROPR_ADMIN_USERS when configured
    ok 6 - enforces PROPR_ADMIN_USERS when configured
      ---
      duration_ms: 0.397146
      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.359947
      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.358954
      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.430222
      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.104387
      type: 'test'
      ...
    # Subtest: resolves the runtime build queue lazily when queueing
    ok 11 - resolves the runtime build queue lazily when queueing
      ---
      duration_ms: 0.801074
      type: 'test'
      ...
    # Subtest: reports apply load failures through the route response
    ok 12 - reports apply load failures through the route response
      ---
      duration_ms: 0.28158
      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.422013
      type: 'test'
      ...
    # Subtest: rejects runtime verification without runtime-management permission
    ok 14 - rejects runtime verification without runtime-management permission
      ---
      duration_ms: 0.243492
      type: 'test'
      ...
    1..14
ok 1 - agent runtime package routes
  ---
  duration_ms: 22.128085
  type: 'suite'
  ...
# [2026-08-30 07:54:29.410 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-nivV4Y/010-agentRuntimeRoutes.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-30 07:54:29.430 +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 1813.488089

[11/322] 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.796018
  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: 4.643914
  type: 'test'
  ...
# Subtest: temporary cleanup refuses paths outside its configured root
ok 3 - temporary cleanup refuses paths outside its configured root
  ---
  duration_ms: 0.562552
  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.461939
  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.335612
  type: 'test'
  ...
# [2026-08-30 07:54:31.437 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-nivV4Y/011-attachmentUploadCleanup.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-30 07:54:31.452 +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 1902.475353

[12/322] 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.184336
  type: 'test'
  ...
# Subtest: ensureAuthenticated refreshes an expired GitHub token before continuing
ok 2 - ensureAuthenticated refreshes an expired GitHub token before continuing
  ---
  duration_ms: 4.728977
  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.176639
  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: 11.076795
  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.625992
  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.831725
  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.000823
  type: 'test'
  ...
# Subtest: GitHub route auth error emits TOKEN_REFRESHED after a successful refresh
ok 8 - GitHub route auth error emits TOKEN_REFRESHED after a successful refresh
  ---
  duration_ms: 0.935937
  type: 'test'
  ...
# [2026-08-30 07:54:33.838 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-nivV4Y/012-authGithubTokens.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-30 07:54:33.856 +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 2526.787451

[13/322] 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.174419
  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.037986
  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.713179
  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: 7.550281
  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.268369
  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.745431
  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.323157
  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: 6.109782
  type: 'test'
  ...
# Subtest: auth redirect allowlist permits configured local IP literals
ok 9 - auth redirect allowlist permits configured local IP literals
  ---
  duration_ms: 5.553462
  type: 'test'
  ...
# Subtest: auth redirect allowlist ignores malformed additional host entries
ok 10 - auth redirect allowlist ignores malformed additional host entries
  ---
  duration_ms: 4.998375
  type: 'test'
  ...
# [2026-08-30 07:54:36.452 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-nivV4Y/013-authRedirect.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-30 07:54:36.543 +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 2594.477888

[14/322] 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: 1443.565441
  type: 'test'
  ...
# [2026-08-30 07:54:38.914 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-nivV4Y/014-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 1997.910037

[15/322] packages/api/test/configReloadSubscription.test.ts
TAP version 13
# Subtest: API config subscription serializes the startup reload with settings updates
ok 1 - API config subscription serializes the startup reload with settings updates
  ---
  duration_ms: 5.166493
  type: 'test'
  ...
# Subtest: API config subscription reloads repository updates
ok 2 - API config subscription reloads repository updates
  ---
  duration_ms: 0.492145
  type: 'test'
  ...
# Subtest: API config subscription ignores unrelated and malformed events
ok 3 - API config subscription ignores unrelated and malformed events
  ---
  duration_ms: 0.347277
  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.424011
  type: 'test'
  ...
1..4
# tests 4
# suites 0
# pass 4
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 244.073445

[16/322] packages/api/test/configRoutesIndexingSummarization.test.ts
TAP version 13
# [2026-08-30 07:54:41.289 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-nivV4Y/016-configRoutesIndexingSummarization.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-30 07:54:41.320 +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: 1666.834686
  type: 'test'
  ...
# Subtest: postSummarizationSettings rejects enabled summarization without a primary alias
ok 2 - postSummarizationSettings rejects enabled summarization without a primary alias
  ---
  duration_ms: 3.613453
  type: 'test'
  ...
# [2026-08-30 07:54:41.590 +0000] �[32mINFO�[39m: �[36mDatabase migrations completed successfully�[39m
# [2026-08-30 07:54:41.592 +0000] �[32mINFO�[39m: �[36mSuccessfully loaded agents configuration�[39m
#     agentCount: 2
# [2026-08-30 07:54:41.592 +0000] �[32mINFO�[39m: �[36mSuccessfully loaded summarization settings�[39m
#     summarization: {
#       "enabled": false,
#       "agent_alias": "",
#       "fallback_agent_alias": "",
#       "custom_prompt": ""
#     }
# [2026-08-30 07:54:41.594 +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-30 07:54:41.594 +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-30 07:54:41.607 +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 2018.218921

[17/322] 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.385362
  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.248246
  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.352266
  type: 'test'
  ...
# Subtest: off-tunnel relay inference uses the shared canonical loopback rule
ok 4 - off-tunnel relay inference uses the shared canonical loopback rule
  ---
  duration_ms: 1.274864
  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.149589
  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.114245
  type: 'test'
  ...
# Subtest: explicit custom GitHub web auth remains supported
ok 7 - explicit custom GitHub web auth remains supported
  ---
  duration_ms: 0.148665
  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.150237
  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.517166
  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: 25.382296
  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.971894
  type: 'test'
  ...
1..11
# tests 11
# suites 0
# pass 11
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 299.347454

[18/322] packages/api/test/connectAuthCallback.test.ts
TAP version 13
# [2026-08-30 07:54:44.204 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-nivV4Y/018-connectAuthCallback.test.ts/propr.test.sqlite"
#     environment: "test"
# Subtest: Connect login preserves a validated redirect_to across Passport session regeneration
ok 1 - Connect login preserves a validated redirect_to across Passport session regeneration
  ---
  duration_ms: 3039.818367
  type: 'test'
  ...
# [2026-08-30 07:54:47.241 +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 5397.933177

[19/322] 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.215496
  type: 'test'
  ...
# Subtest: CORS rejects unrelated origins under proxy mode
ok 2 - CORS rejects unrelated origins under proxy mode
  ---
  duration_ms: 0.397054
  type: 'test'
  ...
# Subtest: CORS allows requests with no origin
ok 3 - CORS allows requests with no origin
  ---
  duration_ms: 0.130932
  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.156143
  type: 'test'
  ...
# Subtest: CORS allows HTTP(S) loopback origins for development
ok 5 - CORS allows HTTP(S) loopback origins for development
  ---
  duration_ms: 0.526111
  type: 'test'
  ...
# Subtest: CORS rejects unsafe schemes and non-loopback hosts
ok 6 - CORS rejects unsafe schemes and non-loopback hosts
  ---
  duration_ms: 0.208532
  type: 'test'
  ...
# Subtest: CORS allows COOKIE_DOMAIN subdomains for preview environments
ok 7 - CORS allows COOKIE_DOMAIN subdomains for preview environments
  ---
  duration_ms: 0.192966
  type: 'test'
  ...
# Subtest: CORS preserves http COOKIE_DOMAIN preview compatibility
ok 8 - CORS preserves http COOKIE_DOMAIN preview compatibility
  ---
  duration_ms: 0.127991
  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.347144
  type: 'test'
  ...
# Subtest: Express CORS boundary is sanitized in development mode
ok 10 - Express CORS boundary is sanitized in development mode
  ---
  duration_ms: 74.037809
  type: 'test'
  ...
# Subtest: Express CORS boundary is sanitized in production mode
ok 11 - Express CORS boundary is sanitized in production mode
  ---
  duration_ms: 32.331797
  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.069275
  type: 'test'
  ...
1..12
# tests 12
# suites 0
# pass 12
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 538.550142

[20/322] 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.610811
  type: 'test'
  ...
# Subtest: demoModeReadOnlyMiddleware blocks auth metadata mutations
ok 2 - demoModeReadOnlyMiddleware blocks auth metadata mutations
  ---
  duration_ms: 1.85077
  type: 'test'
  ...
# Subtest: isDemoMode accepts common truthy environment values
ok 3 - isDemoMode accepts common truthy environment values
  ---
  duration_ms: 0.924939
  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.297055
  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.144683
  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: 51.077741
  type: 'test'
  ...
# Subtest: ensureAuthenticated attaches the synthetic demo user
ok 7 - ensureAuthenticated attaches the synthetic demo user
  ---
  duration_ms: 0.991979
  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
 

...(truncated)

View Workflow

…eaa4835f` without merging, syncing, or committing.

Implemented on exact HEAD `6fc8e8fe244c6e3c5fd71f9257c162b9eaa4835f` without merging, syncing, or committing.

Key changes:

- Added persistent two-phase pairing with expiring provisional credentials, exact binding, one-time activation tickets, idempotent activation receipts, cancellation, cleanup, and migration rollback.
- Provisional credentials are rejected by REST and Socket.IO authentication.
- Desktop now durably journals encrypted rollback/revocation material before server activation and publishes renderer scope only after activation and local commit.
- Added a closeable operation gate covering credential/profile work and descendants.
- IPC and session handlers fail closed before shutdown draining; late invokes return `DESKTOP_CLOSING`.
- Expanded native durability coverage with provisional, delivery, and disposal categories.

Validation:

- Two-phase server tests: 16/16
- Native durability: 104/104, zero skips
- Fast unit tests: 279/279
- Migration and IPC lifecycle tests: passed
- Client, API, desktop, REST/Socket.IO, UI typecheck/build, desktop package, release verification, and CLI package: passed
- Clean `npm ci`: passed
- `git diff --check`: passed
- Full suite: 323/324 runs; only `llmMetrics.test.ts` exceeded its existing 180-second timeout
- Windows-native execution and actionlint could not run locally because this Linux environment has neither Docker nor an actionlint binary. No workflow files were modified.

PR: #1977
Comment by: @integry (ID: 5467503249)
Model: gpt-5.6-sol
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant