Skip to content

[1985 by GPT-5.6 Sol] Support ProPR Connect tunnel endpoints seamlessly in desktop - #1988

Open
propr-dev[bot] wants to merge 7 commits into
1953-epic-desktop-transport-apifrom
1985/gpt-5.6-sol-support-propr-connect-tun-20260829-2108-al6
Open

[1985 by GPT-5.6 Sol] Support ProPR Connect tunnel endpoints seamlessly in desktop#1988
propr-dev[bot] wants to merge 7 commits into
1953-epic-desktop-transport-apifrom
1985/gpt-5.6-sol-support-propr-connect-tun-20260829-2108-al6

Conversation

@propr-dev

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

Copy link
Copy Markdown
Contributor

AI Implementation Summary

Closes #1985

Branch: 1985/gpt-5.6-sol-support-propr-connect-tun-20260829-2108-al6
Commits: ✅ Changes committed (67e2d2f)


AI Processing Completed

Execution Details:

Summary:
Implemented the integration-safe ProPR Connect support for issue #1985.

  • Added strict canonical Connect endpoint parsing and classification, rejecting credentials, ports, paths, queries, fragments, encoded hosts, Unicode/IDNA ambiguity, and suffix lookalikes.
  • Added API-returned approval URL validation for hosted pairing and exact same-origin /browser fallback.
  • Ensured REST, /api/status, and Socket.IO use the Connect origin directly.
  • Hardened API tunnel-selector generation.
  • Added “Verified ProPR Connect endpoint” paste/add-profile UX.
  • Preserved ordinary HTTPS and loopback endpoints.
  • Updated launcher mirror validation and comprehensive deterministic tests.

Because #1961’s DesktopCredentialService and profile-generation stack are not present on this base, no parallel credential or pairing stack was introduced, as required by the issue.

All affected typechecks and targeted suites pass: 94 tests across client, Electron, UI, API, and launcher contracts.

Detailed Logs:

  • Conversation: 89 messages
  • Session: 01a04f5b-78cc-7962-98c0-b755289c34c9

Log files stored at:

  • conversation: /tmp/claude-logs/issue-1985-2026-08-29T21-21-02-780Z-conversation.json
  • output: /tmp/claude-logs/issue-1985-2026-08-29T21-21-02-780Z-output.txt
Latest Conversation Messages

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


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

… seamlessly

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

Implementation completed successfully.
@propr-dev

propr-dev Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor Author

/ultrafix
Triggered automatically by Planner execution settings.

@propr-dev propr-dev Bot added the ultrafix label Aug 29, 2026
@propr-dev

propr-dev Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor Author

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

First action: /review

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

@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 33275848861, 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
c09af8888d6a: Verifying Checksum
c09af8888d6a: Download complete
589002ba0eae: Verifying Checksum
589002ba0eae: Download complete
4ccd7d76ca98: Verifying Checksum
4ccd7d76ca98: Download complete
589002ba0eae: Pull complete
c09af8888d6a: Pull complete
b6b4b7b0e257: Verifying Checksum
b6b4b7b0e257: Download 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 24s

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: 5.281986
      type: 'test'
      ...
    # Subtest: persists a failed state when queue submission fails
    ok 2 - persists a failed state when queue submission fails
      ---
      duration_ms: 1.152204
      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.943193
      type: 'test'
      ...
    # Subtest: times out slow runtime package validation before queueing
    ok 4 - times out slow runtime package validation before queueing
      ---
      duration_ms: 10.189765
      type: 'test'
      ...
    # Subtest: returns package suggestions from configured runtimes
    ok 5 - returns package suggestions from configured runtimes
      ---
      duration_ms: 1.023604
      type: 'test'
      ...
    # Subtest: enforces PROPR_ADMIN_USERS when configured
    ok 6 - enforces PROPR_ADMIN_USERS when configured
      ---
      duration_ms: 0.496368
      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.519591
      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.441396
      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.568563
      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.748659
      type: 'test'
      ...
    # Subtest: resolves the runtime build queue lazily when queueing
    ok 11 - resolves the runtime build queue lazily when queueing
      ---
      duration_ms: 0.871439
      type: 'test'
      ...
    # Subtest: reports apply load failures through the route response
    ok 12 - reports apply load failures through the route response
      ---
      duration_ms: 0.616884
      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.832246
      type: 'test'
      ...
    # Subtest: rejects runtime verification without runtime-management permission
    ok 14 - rejects runtime verification without runtime-management permission
      ---
      duration_ms: 0.489365
      type: 'test'
      ...
    1..14
ok 1 - agent runtime package routes
  ---
  duration_ms: 28.392608
  type: 'suite'
  ...
# [2026-08-29 21:21:45.688 +0000] �[32mINFO�[39m: �[36mCreated data directory for SQLite database�[39m
#     directory: "/home/runner/work/propr/propr/data"
# [2026-08-29 21:21:46.166 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/home/runner/work/propr/propr/data/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 21:21:46.191 +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.458323
  type: 'test'
  ...
# 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: 9.139969
  type: 'test'
  ...
# Subtest: temporary cleanup refuses paths outside its configured root
ok 4 - temporary cleanup refuses paths outside its configured root
  ---
  duration_ms: 0.910061
  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: 0.701131
  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.582679
  type: 'test'
  ...
# [2026-08-29 21:21:46.202 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/home/runner/work/propr/propr/data/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 21:21:46.225 +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: 7.223302
  type: 'test'
  ...
# Subtest: API config subscription reloads repository updates
ok 8 - API config subscription reloads repository updates
  ---
  duration_ms: 0.639957
  type: 'test'
  ...
# Subtest: API config subscription ignores unrelated and malformed events
ok 9 - API config subscription ignores unrelated and malformed events
  ---
  duration_ms: 0.434672
  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.469577
  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: 2.13455
  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.371615
  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.453477
  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.542725
  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.248825
  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.249256
  type: 'test'
  ...
# Subtest: explicit custom GitHub web auth remains supported
ok 17 - explicit custom GitHub web auth remains supported
  ---
  duration_ms: 0.320509
  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.281196
  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.836704
  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: 49.380753
  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: 4.040833
  type: 'test'
  ...
# Subtest: accepts Docker IDs and ProPR-generated container names
ok 22 - accepts Docker IDs and ProPR-generated container names
  ---
  duration_ms: 1.362417
  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.229159
  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: 222.071169
  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: 6.113811
  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: 33.782743
      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: 8.103822
      type: 'test'
      ...
    # Subtest: grants full admin permissions to PROPR_ADMIN_USERS
    ok 3 - grants full admin permissions to PROPR_ADMIN_USERS
      ---
      duration_ms: 4.931681
      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: 7.620558
      type: 'test'
      ...
    # Subtest: resolves demo users without installation permissions
    ok 5 - resolves demo users without installation permissions
      ---
      duration_ms: 9.779531
      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: 3.214871
      type: 'test'
      ...
    # Subtest: current-user responses expose capabilities without OAuth credentials
    ok 7 - current-user responses expose capabilities without OAuth credentials
      ---
      duration_ms: 2.628905
      type: 'test'
      ...
    1..7
ok 26 - instance authorization
  ---
  duration_ms: 73.317264
  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: 5.036266
      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: 11.797834
      type: 'test'
      ...
    # Subtest: adds explicit members and writes an audit entry
    ok 3 - adds explicit members and writes an audit entry
      ---
      duration_ms: 16.324321
      type: 'test'
      ...
    # Subtest: keeps environment authority separate from durable role assignments
    ok 4 - keeps environment authority separate from durable role assignments
      ---
      duration_ms: 6.042137
      type: 'test'
      ...
    # Subtest: prevents removing the last durable administrator
    ok 5 - prevents removing the last durable administrator
      ---
      duration_ms: 6.935858
      type: 'test'
      ...
    # Subtest: allows role changes once another administrator exists
    ok 6 - allows role changes once another administrator exists
      ---
      duration_ms: 7.780137
      type: 'test'
      ...
    1..6
ok 27 - instance member service
  ---
  duration_ms: 54.566478
  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: 3.923155
      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.593198
      type: 'test'
      ...
    1..2
ok 28 - instance catalog
  ---
  duration_ms: 6.660683
  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: 3.724664
      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: 13.774574
      type: 'test'
      ...
    # Subtest: rejects GitHub usernames with trailing or consecutive hyphens
    ok 3 - rejects GitHub usernames with trailing or consecutive hyphens
      ---
      duration_ms: 2.313185
      type: 'test'
      ...
    1..3
ok 29 - instance admin routes
  ---
  duration_ms: 20.036592
  type: 'suite'
  ...
# [2026-08-29 21:21:48.369 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/home/runner/work/propr/propr/data/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 21:21:48.508 +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: 3.011571
  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.251931
  type: 'test'
  ...
# Subtest: an explicit API listen host overrides environment defaults
ok 32 - an explicit API listen host overrides environment defaults
  ---
  duration_ms: 0.188432
  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.719083
  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: 1.867422
  type: 'test'
  ...
# [2026-08-29 21:21:51.225 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/home/runner/work/propr/propr/data/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 21:21:51.226 +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: 125.353584
  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: 26.119412
  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: 29.872842
  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: 27.846954
  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: 18.046561
  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: 19.703199
  type: 'test'
  ...
# Subtest: resolves secure defaults and explicit positive-integer overrides
ok 41 - resolves secure defaults and explicit positive-integer overrides
  ---
  duration_ms: 0.644105
  type: 'test'
  ...
# Subtest: rejects invalid overrides instead of silently disabling protection
ok 42 - rejects invalid overrides instead of silently disabling protection
  ---
  duration_ms: 0.731076
  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: 33.633564
      type: 'test'
      ...
# [2026-08-29 21:21:51.464 +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: 48.330952
      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: 32.634448
      type: 'test'
      ...
    1..3
ok 43 - assembled instance permission routes
  ---
  duration_ms: 115.829827
  type: 'suite'
  ...
# [2026-08-29 21:21:51.582 +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: 2568.37136
  type: 'test'
  ...
# Subtest: /api/compatibility returns public version contract metadata
ok 45 - /api/compatibility returns public version contract metadata
  ---
  duration_ms: 3.986219
  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: 1.966525
  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: 11.556957
  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'
# }
# [2026-08-29 21:21:52.175 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/home/runner/work/propr/propr/data/propr.test.sqlite"
#     environment: "test"
# [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)
# 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'
# }
# [2026-08-29 21:21:52.178 +0000] �[31mERROR�[39m: �[36mFailed to load config from DB�[39m
#     key: "summarization_runtime_state"
#     error: "select * from `system_configs` where `key` = 'summarization_runtime_state' limit 1 - no such table: system_configs"
# [2026-08-29 21:21:52.202 +0000] �[31mERROR�[39m: �[36mFailed to load config from DB�[39m
#     key: "summarization_runtime_state"
#     error: "select * from `system_configs` where `key` = 'summarization_runtime_state' limit 1 - no such table: system_configs"
# Subtest: /api/status isolates system notification projection failures
ok 48 - /api/status isolates system notification projection failures
  ---
  duration_ms: 9.294987
  type: 'test'
  ...
# Subtest: /api/status surfaces unified agent image outages
ok 49 - /api/status surfaces unified agent image outages
  ---
  duration_ms: 1.30538
  type: 'test'
  ...
# Subtest: /api/status includes warnings field in demo mode
ok 50 - /api/status includes warnings field in demo mode
  ---
  duration_ms: 0.962789
  type: 'test'
  ...
# Subtest: /api/status caches agent health checks briefly
ok 51 - /api/status caches agent health checks briefly
  ---
  duration_ms: 4.659917
  type: 'test'
  ...
# 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: 2.474414
  type: 'test'
  ...
# 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: 2.783411
  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'
# }
# [2026-08-29 21:21:52.205 +0000] �[31mERROR�[39m: �[36mFailed to load config from DB�[39m
#     key: "summarization_runtime_state"
#     error: "select * from `system_configs` where `key` = 'summarization_runtime_state' limit 1 - no such table: system_configs"
# [2026-08-29 21:21:52.217 +0000] �[31mERROR�[39m: �[36mFailed to load config from DB�[39m
#     key: "summarization_runtime_state"
#     error: "select * from `system_configs` where `key` = 'summarization_runtime_state' limit 1 - no such table: system_configs"
# [2026-08-29 21:21:52.218 +0000] �[31mERROR�[39m: �[36mFailed to load config from DB�[39m
#     key: "summarization_runtime_state"
#     error: "select * from `system_configs` where `key` = 'summarization_runtime_state' limit 1 - no such table: system_configs"
# [2026-08-29 21:21:52.221 +0000] �[31mERROR�[39m: �[36mFailed to load config from DB�[39m
#     key: "summarization_runtime_state"
#     error: "select * from `system_configs` where `key` = 'summarization_runtime_state' limit 1 - no such table: system_configs"
# [2026-08-29 21:21:52.224 +0000] �[31mERROR�[39m: �[36mFailed to load config from DB�[39m
#     key: "summarization_runtime_state"
#     error: "select * from `system_configs` where `key` = 'summarization_runtime_state' limit 1 - no such table: system_configs"
# 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: 4.73639
  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: 9.575803
  type: 'test'
  ...
# 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: 12.347305
  type: 'test'
  ...
# [2026-08-29 21:21:52.229 +0000] �[31mERROR�[39m: �[36mFailed to load config from DB�[39m
#     key: "summarization_runtime_state"
#     error: "select * from `system_configs` where `key` = 'summarization_runtime_state' limit 1 - no such table: system_configs"
# [2026-08-29 21:21:52.236 +0000] �[31mERROR�[39m: �[36mFailed to load config from DB�[39m
#     key: "summarization_runtime_state"
#     error: "select * from `system_configs` where `key` = 'summarization_runtime_state' limit 1 - no such table: system_configs"
# [2026-08-29 21:21:52.251 +0000] �[31mERROR�[39m: �[36mFailed to load config from DB�[39m
#     key: "summarization_runtime_state"
#     error: "select * from `system_configs` where `key` = 'summarization_runtime_state' limit 1 - no such table: system_configs"
# 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 includes routing state published by the daemon
ok 57 - /api/status includes routing state published by the daemon
  ---
  duration_ms: 11.670445
  type: 'test'
  ...
# [2026-08-29 21:21:52.263 +0000] �[31mERROR�[39m: �[36mFailed to load config from DB�[39m
#     key: "summarization_runtime_state"
#     error: "select * from `system_configs` where `key` = 'summarization_runtime_state' limit 1 - no such table: system_configs"
# 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 exposes only validated UI-safe Connect account fields
ok 58 - /api/status exposes only validated UI-safe Connect account fields
  ---
  duration_ms: 6.080552
  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'
# }
# [2026-08-29 21:21:52.269 +0000] �[31mERROR�[39m: �[36mFailed to load config from DB�[39m
#     key: "summarization_runtime_state"
#     error: "select * from `system_configs` where `key` = 'summarization_runtime_state' limit 1 - no such table: system_configs"
# [2026-08-29 21:21:52.274 +0000] �[31mERROR�[39m: �[36mFailed to load config from DB�[39m
#     key: "summarization_runtime_state"
#     error: "select * from `system_configs` where `key` = 'summarization_runtime_state' limit 1 - no such table: system_configs"
# [2026-08-29 21:21:52.276 +0000] �[31mERROR�[39m: �[36mFailed to load config from DB�[39m
#     key: "summarization_runtime_state"
#     error: "select * from `system_configs` where `key` = 'summarization_runtime_state' limit 1 - no such table: system_configs"
# 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'
# }
# [2026-08-29 21:21:52.279 +0000] �[31mERROR�[39m: �[36mFailed to load config from DB�[39m
#     key: "summarization_runtime_state"
#     error: "select * from `system_configs` where `key` = 'summarization_runtime_state' limit 1 - no such table: system_configs"
# 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: 11.323241
  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'
# }
# [2026-08-29 21:21:52.281 +0000] �[31mERROR�[39m: �[36mFailed to load config from DB�[39m
#     key: "summarization_runtime_state"
#     error: "select * from `system_configs` where `key` = 'summarization_runtime_state' limit 1 - no such table: system_configs"
# 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: 9.553602
  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

...(truncated)

View Workflow

@propr-dev

propr-dev Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor Author

Applied the requested follow-up changes in commit 572a133

Summary of Changes

Fixed PR #1988’s build blockers:

Verified:

  • Failing CI regression: 66/66 passed
  • Focused UI/desktop tests: 88/88 passed
  • Client tests: 17/17 passed
  • UI typecheck, lint, and production build passed
  • Notification regression stage passed
  • Browser smoke tests: 4/4 passed
  • CLI package verification passed
  • git diff --check passed

No commit was created.


🤖 Implementation Details

  • Model: GPT-5.6 Sol (high)
  • Requested By: @github-actions[bot]
  • Time: 8m 14s
  • Tokens: 2,920,983 (2,912,128 in / 8,855 out)
  • Cost: $2.30

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

@integry integry removed the AI label Aug 29, 2026
Fixed PR #1988’s build blockers:

- Restored correct URL warning classification in [runtimeConfig.ts](/tmp/git-processor/worktrees/integry/propr/pr-1988-followup-2026-08-29T21-22-36/propr-ui/src/config/runtimeConfig.ts:100).
- Compacted the Connect verification markup in [DesktopExperience.tsx](/tmp/git-processor/worktrees/integry/propr/pr-1988-followup-2026-08-29T21-22-36/propr-ui/src/desktop/DesktopExperience.tsx:96) to satisfy the 400-line lint limit.

Verified:

- Failing CI regression: 66/66 passed
- Focused UI/desktop tests: 88/88 passed
- Client tests: 17/17 passed
- UI typecheck, lint, and production build passed
- Notification regression stage passed
- Browser smoke tests: 4/4 passed
- CLI package verification passed
- `git diff --check` passed

No commit was created.

PR: #1988
Comment by: @github-actions[bot] (ID: 5464959244)
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

/review

Review exact Connect desktop head 572a133 against transport epic base as a pre-reconciliation security and UX gate. Audit strict bare managed tunnel origin parsing: HTTPS t-.propr.dev only, with no IDNA lookalike, userinfo, port, path, query, fragment, credential, or relay-token crossover. Verify one exact origin for compatibility, pairing, REST, and Socket.IO; API-returned same-origin browser entry versus hosted app.propr.dev approval redirect; explicit confirmation with no silent profile overwrite; stale tunnel and restart warnings; main-renderer secret boundaries; and preservation of arbitrary remote and localhost flows. Check async switch and unmount races, same-host reconnect, origin-change fresh pairing, URL or log or DOM leakage, accessibility, and bounded errors. This head intentionally predates secret-free CLI discovery and runtime deep-link reconciliation: report those as integration requirements, while distinguishing exact-head defects. No code changes. Give blocker IDs and a score.

@propr-dev

propr-dev Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor Author

ℹ️ Analyzed the follow-up request by @integry

Analysis Summary

Gate result: fail — 4 blockers. Score: 4/10.

Reviewed exact head 572a1333ce74c2a0cb56aeabeb5429a6b072b0b6 against transport-epic base 7c8010f3bdf09b0e08a25bb25c76a351d8cc53ca.

Exact-head blockers

  • B1 — CONNECT-ORIGIN-01 — P1 security: forbidden tunnel authorities are re-canonicalized and accepted.
    In runtimeConfig.ts:122, strict validation is followed by a fallback that parses https://${raw} and reconstructs the endpoint from url.hostname. This discards userinfo, ports, and encoded spelling. Consequently these selectors resolve to https://t-abc123.propr.dev:

    • user:secret@t-abc123.propr.dev
    • t-abc123.propr.dev:443
    • t-abc123.propr.dev:8443
    • t-%61bc123.propr.dev

    The fallback defeats the new raw-authority checks and then persists the resulting tunnel as trusted flow state.

  • B2 — CONNECT-PAIR-02 — P1 security: pairing downgrades a noncanonical managed host to an ordinary remote origin.
    desktopPairing.ts:67 validates a generic bare HTTPS origin before checking whether it is Connect. Thus https://t-instance123.propr.dev:8443 is accepted with a same-origin browser entry. Direct reproduction returned:

    https://t-instance123.propr.dev:8443/api/desktop/pairings/<id>/browser
    

    The API repeats this inconsistency: desktopAuthService.ts:152 accepts the managed hostname with a port and desktopAuthService.ts:210 publishes it as the browser entry. Reserved t-*.propr.dev hosts must fail closed whenever the exact Connect parser rejects them.

  • B3 — CONNECT-LEAK-03 — P1 security/robustness: malformed configuration is reflected verbatim and then can fail during module initialization.
    runtimeConfig.ts:311 and runtimeConfig.ts:354 interpolate the complete configured URL into console and user-facing diagnostics. A URL containing user:password@… or ?token=… therefore leaks those values to logs/DOM. The input and resulting error are also unbounded. Afterwards API_BASE_URL is initialized eagerly through apiClient.ts:13, where strict normalization can throw before the intended bounded connection screen renders.

  • B4 — CONNECT-STALE-04 — P1 UX: managed tunnel failure has no stale/restart recovery state.
    The Connect-specific UX is only a positive “Verified” badge in desktop.tsx:116. Failures are collapsed into the generic offline message in browserAdapters.ts:98. There is no managed-endpoint warning explaining that the tunnel may be stale after restart, nor an action to rediscover/re-enter the endpoint and pair again.

Expected reconciliation requirements

These are not counted as exact-head defects:

  • IR1 — Secret-free CLI discovery: merge the forthcoming discovery contract without tunnel/relay/device/access secrets in argv, stdout, URLs, profile metadata, logs, or DOM. Discovered profiles require explicit confirmation and must not overwrite an existing profile silently.
  • IR2 — Runtime deep-link reconciliation: main must parse and bind a bounded, structured deep link to one exact origin. The current generic link is forwarded wholesale to the renderer in preload-bridge.ts:16.
  • IR3 — Main-process secret custody: pairing bootstrap, device-secret polling, access-token storage, REST auth, and Socket.IO auth must remain in main. Do not use the inherited plaintext credential bridge at preload-bridge.ts:48 for Connect tokens.
  • IR4 — Profile identity semantics: same-origin reconnect may reuse the credential; any origin change must create a fresh pairing identity and must not retain credentials keyed by the old profile ID. Current generic profile replacement preserves the ID and credential file in profile-store.ts:124.
  • IR5 — Approval URL consumption: the new approval validator is currently exported and tested but unused by a desktop pairing controller. Reconciliation must validate the API-returned URL immediately before main opens it.

Passed audit areas

The shared client keeps compatibility, REST, and Socket.IO on one normalized origin; arbitrary HTTPS remotes and loopback HTTP remain supported. Connection-attempt generation and serialized profile mutations cover the tested switch/unmount races. Confirmation controls and the new status announcement are accessible, and pairing approval remains explicit.

Focused verification passed:

  • Client: 17/17
  • Desktop: 24/24
  • UI: 90/90
  • API pairing/auth: 13/13
  • Launcher drift: 10/10

No tracked files were changed.

No code changes were necessary based on the current state of the branch.


🤖 Analysis Details

  • Model: GPT-5.6 Sol (high)
  • Time: 10m 20s
  • Tokens: 4,998,672 (4,976,562 in / 22,110 out)
  • Cost: $4.06

View 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: 5465088388✓

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

integry commented Aug 29, 2026

Copy link
Copy Markdown
Owner

/review

Exact-head review only for 572a133. Use the detailed audit scope in comment 5465088388. Distinguish current-head blockers from the expected later discovery and runtime deep-link reconciliation. Do not modify code. Report blocker IDs and score.

@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

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

propr-dev Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor Author

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

Overall Evaluation

The PR provides a strong, well-tested trust boundary for ProPR Connect origins, but needs one localized pairing correction before merge.

Strict origin verification — Raw-authority comparison prevents default-port, encoded-host, credential, Unicode/IDNA, path, query, fragment, and suffix-lookalike inputs from acquiring Connect trust.

Consistent transport routing — REST, compatibility/status requests, and Socket.IO retain the exact normalized Connect origin and fixed transport paths.

Bound approval contracts — The shared validator accepts only the matching hosted approval URL or exact same-origin browser route, without permitting secret-bearing parameters. All current-head checks pass.

Merge blockers

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

F1: 🔴 Noncanonical Connect configuration starts broken pairing

  • Required behavior: Pairing must use one exact canonical Connect origin, reject explicit ports, and fail with a bounded error rather than initiating a flow whose hosted redirect loses the tunnel selector.
  • Evidence: packages/api/desktopAuthService.ts:244getFrontendApprovalUrl() now withholds the selector when strict parsing rejects https://t-instance123.propr.dev:443, while startPairing() still accepts that value after WHATWG removes the default port. packages/api/test/desktopAuth.test.ts:102 demonstrates that pairing starts successfully before producing the selector-free hosted URL.
  • Minimum fix: Detect Connect-looking but noncanonical API_PUBLIC_URL spellings before creating a pairing and reject them with a bounded configuration error. Add a redirect-chain or startPairing() test requiring rejection for explicit-default-port and encoded-host variants.

Suggestions

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

S1: 🟢 Complete runtime reconciliation

During the intentionally later discovery and deep-link integration, route every API-returned approval URL through normalizeDesktopPairingApprovalUrl, keep device secrets in the main process, cancel stale probes or polls on profile changes and unmount, force fresh pairing after an origin change, and surface stale-tunnel or restart guidance. These are integration requirements for the later stack, not blockers for this explicitly pre-reconciliation head.

Score

The Connect parser, transport behavior, approval validator, UX indicator, and tests are otherwise strong, and all authoritative checks pass. The inconsistent explicit-port pairing path is localized but must be corrected before merge.

Score: 6/10


🤖 Review Details

  • Model: GPT-5.6 Sol
  • Time: 3m 48s
  • Tokens: 52,212 (49,207 in / 3,005 out)
  • Cost: $0.13

View Task


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

🤖 Review by ProPR

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

integry commented Aug 29, 2026

Copy link
Copy Markdown
Owner

/fix F1

Fix formal F1 plus all four independently reproduced exact-head blockers on 572a133. Do not merge or base-sync yet.

  1. Remove the runtimeConfig fallback that reconstructs trust from URL.hostname and discards forbidden raw authority. A bare selector may become trusted Connect only if its complete bounded ASCII raw spelling matches the canonical t-.propr.dev host grammar, optionally with the explicitly supported trailing slash normalization. Reject userinfo, colon or port including 443, percent encoding, Unicode or IDNA, backslash, path, query, fragment, whitespace, trailing dot, alternate case ambiguity, and lookalike suffix before persistence. Regress the exact user:secret@host, :443, :8443 and t-%61bc123 cases.

  2. Reserved managed-host names must never downgrade to generic remote behavior. In shared desktop pairing and API startPairing, detect any URL whose parsed or raw authority attempts the reserved t-*.propr.dev namespace. If the exact Connect parser rejects it for port, encoding, path, userinfo, IDNA, alternate spelling, or other noncanonical form, reject before creating pairing with one bounded configuration error. Apply the same rule to API_PUBLIC_URL before WHATWG normalization erases explicit default ports. Test explicit 443, encoded host and every raw-authority case through startPairing and redirect chain; no selector-free hosted approval and no same-origin downgrade. Preserve valid arbitrary HTTPS remotes and loopback HTTP.

  3. Never reflect a raw configured URL, authority, query, credential, path, or unbounded input in console, DOM, error, profile metadata, or analytics. Diagnostics use fixed bounded reason codes and generic text only. Invalid runtime config must not throw during eager API_BASE_URL module initialization; preserve a bounded disconnected or invalid-config screen and delay API construction until a validated endpoint exists. Sentinel tests cover userinfo password, query token, long input and malformed URL across logs, DOM and thrown errors.

  4. Add the exact-head stale Connect recovery state. When a verified managed endpoint becomes unreachable or pairing fails with managed configuration or restart symptoms, show bounded guidance that the tunnel or stack configuration may be stale, with explicit actions to retry and re-enter or rediscover the endpoint and pair again. Do not silently overwrite a profile, leak the old URL, or present generic remote wording. Keep status announcements and controls accessible. The later CLI discovery and propr deep-link integration will wire the rediscover action; expose a safe callback or state now without inventing token handling.

Keep the exact origin for compatibility, pairing, REST and Socket.IO; validate every API-returned approval URL immediately before main opens it once reconciliation adds the controller; tokens and device secrets remain main-only; origin changes require a fresh identity. Run Connect parsing, shared pairing, API auth, client, runtimeConfig, desktop UX, accessibility, launcher drift, typechecks, full suite, package guard and git diff --check. Preserve current green arbitrary remote, localhost, strict approval and same-host semantics.

@propr-dev

propr-dev Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor Author

Applied the requested follow-up changes in commit 0b7c296

Addressed 1 AI review comment (IDs: 5465165681)

Summary of Changes

Implemented F1 only.

  • Noncanonical reserved Connect URLs—including explicit :443 and percent-encoded hosts—now fail before pairing creation.
  • Returns bounded PAIRING_CONFIGURATION_INVALID / HTTP 503 without reflecting configured input.
  • Valid Connect, arbitrary HTTPS remotes, lookalikes, and loopback behavior remain unchanged.
  • Added regressions confirming no pairing row is persisted.

Changed desktopAuthService.ts and desktopAuth.test.ts.

Verification passed:

  • 67 Connect/client/API tests
  • 24 desktop tests
  • 96 runtime-config/desktop UX tests
  • 10 launcher drift tests
  • Affected typechecks and API lint
  • CLI release-package guard
  • git diff --check

The full 321-file suite reached 176 files before hanging because Redis is unavailable at 127.0.0.1:6379; it was stopped after repeated connection failures. No merge, base sync, commit, or PR creation was performed.


🤖 Implementation Details

  • Model: GPT-5.6 Sol (high)
  • Requested By: @integry
  • Time: 12m 29s
  • Tokens: 4,004,757 (3,992,878 in / 11,879 out)
  • Cost: $2.97
  • 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: 5465187212✓

@integry integry removed the AI label Aug 29, 2026
Implemented F1 only.

- Noncanonical reserved Connect URLs—including explicit `:443` and percent-encoded hosts—now fail before pairing creation.
- Returns bounded `PAIRING_CONFIGURATION_INVALID` / HTTP 503 without reflecting configured input.
- Valid Connect, arbitrary HTTPS remotes, lookalikes, and loopback behavior remain unchanged.
- Added regressions confirming no pairing row is persisted.

Changed [desktopAuthService.ts](/tmp/git-processor/worktrees/integry/propr/pr-1988-followup-2026-08-29T22-14-16/packages/api/desktopAuthService.ts:152) and [desktopAuth.test.ts](/tmp/git-processor/worktrees/integry/propr/pr-1988-followup-2026-08-29T22-14-16/packages/api/test/desktopAuth.test.ts:101).

Verification passed:

- 67 Connect/client/API tests
- 24 desktop tests
- 96 runtime-config/desktop UX tests
- 10 launcher drift tests
- Affected typechecks and API lint
- CLI release-package guard
- `git diff --check`

The full 321-file suite reached 176 files before hanging because Redis is unavailable at `127.0.0.1:6379`; it was stopped after repeated connection failures. No merge, base sync, commit, or PR creation was performed.

PR: #1988
Comment by: @integry (ID: 5465187212)
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 Implement the remaining independently reproduced blockers on exact head 0b7c296. Formal F1 API rejection is complete; do not redo or weaken it.

  1. runtimeConfig must not reconstruct Connect trust from parsed hostname. Remove the fallback that accepts bare user:secret@t-abc123.propr.dev, t-abc123.propr.dev:443 or :8443, and t-%61bc123.propr.dev by discarding userinfo, port or encoding. Accept scheme-less shorthand only when the complete bounded ASCII raw input itself is the exact canonical t-.propr.dev host form, apart from documented redundant trailing slash trimming. Reject credentials, colon, percent, Unicode or IDNA, backslash, path, query, fragment, whitespace, trailing dot and suffix lookalikes before storing flow state. Add all exact reproductions.

  2. shared desktopPairing must not downgrade a Connect-looking reserved host rejected by the strict parser into an ordinary remote origin. Reject every noncanonical attempt at the reserved t-*.propr.dev namespace before the generic HTTPS path. Cover explicit ports, encoding, userinfo and alternate spellings; preserve unrelated HTTPS remotes, lookalikes outside the reserved namespace and loopback HTTP.

  3. Bound and redact malformed configuration end to end. Never place raw configured URLs, authority, query credentials, private paths or unbounded input in console, DOM, errors, profiles or analytics. Use fixed codes and generic bounded messages. Invalid runtime config must not throw during eager API_BASE_URL module initialization; render a safe invalid-configuration connection screen and construct the client only after validation. Sentinel tests cover userinfo password, query token, oversized value and malformed URL in logs, DOM and exceptions.

  4. Add a managed-tunnel stale or restart recovery state, not just the positive Verified badge. On verified Connect endpoint offline, configuration failure or pairing failure, show bounded managed-specific guidance plus accessible Retry and Re-enter or Rediscover actions. Do not display the raw endpoint or silently overwrite a profile. Expose a safe callback or state seam for the later CLI-discovery integration; do not invent renderer token handling.

Run runtimeConfig and hosted-flow tests, shared pairing/client/API tests, desktop UX and accessibility tests, typechecks, lint, full suite, package guard and git diff --check. Preserve completed API F1, exact origin transport, arbitrary remote and localhost behavior, approval validation, and main-only token custody. No base sync or merge.

@propr-dev

propr-dev Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor Author

Applied the requested follow-up changes in commit 68abb05

Summary of Changes

Implemented the remaining blockers on exact head 0b7c2961ac0e83b082c4c75d8ac8894cd947aeb1.

Key changes:

  • Strict raw Connect shorthand validation; credentials, ports, encoding, Unicode, paths, whitespace, alternate casing, trailing dots, and lookalikes are rejected before flow persistence.
  • Reserved t-*.propr.dev attempts can no longer downgrade to generic HTTPS pairing/client behavior.
  • Malformed runtime configuration is bounded and redacted. The API client is constructed only after successful validation, otherwise a safe configuration screen renders.
  • Desktop logs, IPC errors, DOM messages, and profile presentation avoid raw endpoints and failure details.
  • Added managed-tunnel stale/restart recovery with Retry, Re-enter, Rediscover, and explicit confirmation before replacing a profile.
  • Added a secret-free optional rediscovery adapter seam.
  • Formal F1 API files were not modified.

Validation passed:

  • Client/shared pairing: 20 tests
  • API desktop-auth/status: 39 tests
  • Desktop/Electron: 24 tests
  • Full UI: 507 tests across 70 files
  • Root, client, UI, and desktop typechecks
  • Root and UI lint
  • CLI package guard
  • git diff --check

The repository-wide runner reached 175/321 files, then hung on Redis-dependent tests because neither Redis nor Docker is available. A resumed run encountered the same environment blocker at llmMetrics.test.ts. All affected and independently runnable suites pass.

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


🤖 Implementation Details

  • Model: GPT-5.6 Sol (high)
  • Requested By: @integry
  • Time: 33m 39s
  • Tokens: 23,012,043 (22,960,907 in / 51,136 out)
  • Cost: $14.46
  • 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: 5465256401✓

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

Implemented the remaining blockers on exact head `0b7c2961ac0e83b082c4c75d8ac8894cd947aeb1`.

Key changes:

- Strict raw Connect shorthand validation; credentials, ports, encoding, Unicode, paths, whitespace, alternate casing, trailing dots, and lookalikes are rejected before flow persistence.
- Reserved `t-*.propr.dev` attempts can no longer downgrade to generic HTTPS pairing/client behavior.
- Malformed runtime configuration is bounded and redacted. The API client is constructed only after successful validation, otherwise a safe configuration screen renders.
- Desktop logs, IPC errors, DOM messages, and profile presentation avoid raw endpoints and failure details.
- Added managed-tunnel stale/restart recovery with Retry, Re-enter, Rediscover, and explicit confirmation before replacing a profile.
- Added a secret-free optional rediscovery adapter seam.
- Formal F1 API files were not modified.

Validation passed:

- Client/shared pairing: 20 tests
- API desktop-auth/status: 39 tests
- Desktop/Electron: 24 tests
- Full UI: 507 tests across 70 files
- Root, client, UI, and desktop typechecks
- Root and UI lint
- CLI package guard
- `git diff --check`

The repository-wide runner reached 175/321 files, then hung on Redis-dependent tests because neither Redis nor Docker is available. A resumed run encountered the same environment blocker at `llmMetrics.test.ts`. All affected and independently runnable suites pass.

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

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

integry commented Aug 29, 2026

Copy link
Copy Markdown
Owner

!propr Fix the exact-head Connect blockers on 68abb05. Do not merge or sync the transport base; #1961 auth and #1987 CLI discovery will be reconciled later.

  1. Make the trusted Connect spelling genuinely canonical everywhere. parseProprConnectEndpoint and all launcher/client/hosted shorthand mirrors must accept only the exact raw ASCII https://t-<canonical-lowercase-id>.propr.dev origin: no leading/trailing whitespace, trailing slash or repeated slash, credentials, port including :443, query, fragment, escapes, Unicode/IDNA, mixed case, or path. Scheme-less hosted tunnel= accepts only exact lowercase t-<id>.propr.dev; do not strip slashes or rebuild an unsafe spelling before validation. Preserve ordinary validated HTTPS remotes, but ensure every malformed or alternate spelling under the reserved Connect namespace—including nested/lookalike *.t-<id>.propr.dev / x.t-<id>.propr.dev attempts—cannot downgrade to generic remote behavior. Keep shared, client, API, Electron, hosted UI, and launcher tests in drift-locked agreement.

  2. Remove renderer/network-discovery authority from managed recovery. If managedTunnelRecovery is absent, do not fall back to adapters.discovery and select the first Connect-looking profile. Rediscovery must use only the optional trusted main-process adapter seam keyed by the existing opaque profile id; otherwise show a generic unavailable result. Require a returned candidate to be explicitly bound to that same profile id/current attempt and to contain a strict canonical Connect endpoint before displaying confirmation. A mismatched id, null, malformed candidate, adapter absence, or validation failure must leave the saved profile untouched and show a safe actionable unavailable message—never silently return.

  3. Make confirmation meaningful without leaking endpoint, token, path, or raw error details: identify the saved connection by its bounded safe display label and state that a replacement endpoint was discovered for that connection. Do not reveal the old/new host. Preserve explicit confirmation before save/connect, fresh-generation semantics, stale attempt fencing, and Retry/Re-enter behavior. Add deterministic regressions for unrelated first discovery candidates, absent adapter, null/malformed/mismatched results, cancellation, concurrent stale rediscovery, and confirmed exact bound replacement.

  4. Fix Validate Changes without weakening lint. Current exact failure is six UI warnings: App.tsx max-lines; runtimeConfig.ts complexity and max-lines; desktop.tsx max-depth; DesktopExperience.tsx and its test max-lines. Refactor/split below configured thresholds while preserving behavior and tests. The rerun Electron package guard is now green.

Run shared/client/API/Electron/UI/launcher Connect suites, UI lint/typecheck/build, desktop typecheck/package, configured full suite, and git diff --check. Preserve bounded/redacted input and errors, lazy API construction, profile mutation serialization, approval URL binding, existing explicit-port pairing selector, and all non-Connect remote/loopback support.

@propr-dev

propr-dev Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor Author

Applied the requested follow-up changes in commit cdb9f41

Summary of Changes

Implemented the exact-head Connect follow-up without merging or syncing the transport base.

  • Enforced byte-for-byte canonical Connect origins across shared, client, API, Electron, hosted UI, CLI, and launcher paths. Reserved nested/lookalike hosts cannot fall through as generic remotes.
  • Removed renderer discovery fallback from managed recovery. Trusted rediscovery now requires current-attempt fencing, matching opaque profile ID, strict endpoint validation, and explicit confirmation.
  • Added bounded/redacted confirmation copy and deterministic recovery regressions.
  • Split the affected UI modules to eliminate all six lint warnings without changing lint configuration.

Verification:

  • Connect suites: passed
  • UI lint, typecheck, build: passed
  • Desktop typecheck and Electron package: passed
  • git diff --check: passed
  • Full suite: 320/321 runs passed; only test/llmMetrics.test.ts timed out waiting for Redis at 127.0.0.1:6379, which is unavailable in this environment.

No commit was created.


🤖 Implementation Details

  • Model: GPT-5.6 Sol (high)
  • Requested By: @integry
  • Time: 35m 0s
  • Tokens: 25,417,292 (25,368,553 in / 48,739 out)
  • Cost: $16.00
  • 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: 5465471435✓

@integry integry removed the AI label Aug 29, 2026
…g or syncing the transport base.

Implemented the exact-head Connect follow-up without merging or syncing the transport base.

- Enforced byte-for-byte canonical Connect origins across shared, client, API, Electron, hosted UI, CLI, and launcher paths. Reserved nested/lookalike hosts cannot fall through as generic remotes.
- Removed renderer discovery fallback from managed recovery. Trusted rediscovery now requires current-attempt fencing, matching opaque profile ID, strict endpoint validation, and explicit confirmation.
- Added bounded/redacted confirmation copy and deterministic recovery regressions.
- Split the affected UI modules to eliminate all six lint warnings without changing lint configuration.

Verification:

- Connect suites: passed
- UI lint, typecheck, build: passed
- Desktop typecheck and Electron package: passed
- `git diff --check`: passed
- Full suite: 320/321 runs passed; only `test/llmMetrics.test.ts` timed out waiting for Redis at `127.0.0.1:6379`, which is unavailable in this environment.

No commit was created.

PR: #1988
Comment by: @integry (ID: 5465471435)
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

/review

@propr-dev

propr-dev Bot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor Author

AI Code Review Complete requested by @integry

Posted 1 review:

View Task Details

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

propr-dev Bot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor Author

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

Overall Evaluation

The PR adds strong, centralized Connect endpoint classification, safer desktop error handling, recovery UX, and direct-origin transport routing. However, two localized validation inconsistencies remain, so it is not ready to merge yet. The full test suite is also still pending.

Strict Connect classificationparseProprConnectEndpoint validates the raw lowercase ASCII origin before URL normalization, while reserved-host attempts cannot silently fall through to trusted Connect behavior.

Transport behavior is well covered — Added tests verify that REST, status, and Socket.IO traffic use the Connect origin and expected paths directly.

Merge blockers

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

F2: 🔴 Hosted gating does not reach API clients

  • Required behavior: The hosted UI must not make API clients available until an exact Connect endpoint has been selected; missing or invalid hosted configuration must produce a blocked configuration state.
  • Evidence: propr-ui/src/config/runtimeConfig.ts:368getRuntimeApiBaseUrlState() returns issue: null whenever generic normalizeApiBaseUrl succeeds, including no configured endpoint or a non-Connect remote such as https://custom.example.com. propr-ui/src/api/apiClient.ts:14 consequently constructs a usable client, while propr-ui/src/App.tsx:381 applies the stricter hosted check only at the rendering layer.
  • Minimum fix: Make getRuntimeApiBaseUrlState() return the corresponding hosted connection issue for missing stacks and noncanonical hosted configurations before apiClient initializes a client, while retaining the explicit OAuth-completion exemption.

F3: 🔴 Approval validator accepts rejected encodings

  • Required behavior: An API-returned hosted approval URL accepted by the new validator must satisfy the exact canonical query contract consumed by the hosted UI.
  • Evidence: packages/shared/src/desktopPairing.ts:98 — approval parameters are validated only through decoded URLSearchParams names and values, so forms such as tunnel=t%2Dinstance123.propr.dev or %74unnel=... are accepted. propr-ui/src/config/hostedTunnelConfig.ts:38 explicitly rejects those same spellings because the raw component must equal the decoded value.
  • Minimum fix: Validate the approval URL’s raw query spelling as well as its decoded values, rejecting encoded parameter names or encoded Connect host values that hostedTunnelQueryApiBaseUrl will not accept, and add a cross-contract regression test.

Suggestions

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

No suggestions.

Score

The core implementation is thoughtful and extensively tested, but the two new trust-boundary inconsistencies must be corrected before merge. The remaining full-suite check is pending rather than failing.

Score: 6/10


🤖 Review Details

  • Model: GPT-5.6 Sol
  • Time: 5m 41s
  • Tokens: 88,192 (75,106 in / 13,086 out)
  • Cost: $0.77

View Task


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

🤖 Review by ProPR

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

integry commented Aug 30, 2026

Copy link
Copy Markdown
Owner

/fix F2 F3

@propr-dev

propr-dev Bot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor Author

Applied the requested follow-up changes in commit cb6a954

Addressed 1 AI review comment (IDs: 5465678429)

Summary of Changes

Implemented only F2 and F3.

  • Hosted API client creation is now blocked for missing or non-Connect configurations, while OAuth completion remains exempt: runtimeConfig.ts.
  • Approval URLs now require canonical raw query names and values, rejecting encoded names/hosts: desktopPairing.ts.
  • Added API-client gating and cross-contract encoding regressions.

Verification passed:

  • Shared build
  • Client tests: 21 passed
  • UI tests: 66 passed
  • UI typecheck and targeted lint
  • git diff --check

🤖 Implementation Details

  • Model: GPT-5.6 Sol (high)
  • Requested By: @integry
  • Time: 5m 29s
  • Tokens: 1,204,756 (1,195,592 in / 9,164 out)
  • Cost: $1.27

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

@integry integry removed the AI label Aug 30, 2026
Implemented only F2 and F3.

- Hosted API client creation is now blocked for missing or non-Connect configurations, while OAuth completion remains exempt: [runtimeConfig.ts](/tmp/git-processor/worktrees/integry/propr/pr-1988-followup-2026-08-30T00-13-37/propr-ui/src/config/runtimeConfig.ts:350).
- Approval URLs now require canonical raw query names and values, rejecting encoded names/hosts: [desktopPairing.ts](/tmp/git-processor/worktrees/integry/propr/pr-1988-followup-2026-08-30T00-13-37/packages/shared/src/desktopPairing.ts:57).
- Added API-client gating and cross-contract encoding regressions.

Verification passed:

- Shared build
- Client tests: 21 passed
- UI tests: 66 passed
- UI typecheck and targeted lint
- `git diff --check`

PR: #1988
Comment by: @integry (ID: 5465683890)
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

/review

@propr-dev

propr-dev Bot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor Author

AI Code Review Complete requested by @integry

Posted 1 review:

View Task Details

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

propr-dev Bot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor Author

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

Overall Evaluation

This PR adds centralized ProPR Connect endpoint classification, approval-URL validation, hosted-tunnel state handling, and desktop recovery UX. The implementation is well tested and thoughtfully defensive, but three localized correctness gaps mean it needs minor changes before merge.

Centralized endpoint trust policy — Connect parsing is shared across the client, desktop, API, CLI, and launcher, with extensive coverage for encoded hosts, ports, nested hosts, and Unicode lookalikes.

Approval URLs are tightly boundnormalizeDesktopPairingApprovalUrl restricts browser navigation to the matching API fallback route or the canonical hosted approval contract.

Invalid configuration fails safely — The UI avoids constructing an API client and renders bounded diagnostics without exposing injected credentials or private URL components.

Merge blockers

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

F4: 🔴 Oversized Connect URL bypasses canonical validation

  • Required behavior: Reserved ProPR Connect endpoints must use the exact canonical spelling and respect the configured maximum URL length before being trusted.
  • Evidence: packages/api/desktopAuthService.ts:154 — publicApiBase calls helpers that both return false for values exceeding MAX_PROPR_API_BASE_URL_LENGTH, but it does not independently reject such values before new URL(raw). A canonical Connect origin padded past 2048 characters with leading or trailing ASCII whitespace is therefore normalized and accepted.
  • Minimum fix: Reject raw.length > MAX_PROPR_API_BASE_URL_LENGTH in publicApiBase with PAIRING_CONFIGURATION_INVALID before parsing or reserved-host classification, and add an oversized whitespace-padded Connect test.

F5: 🔴 Encoded tunnel name falls through

  • Required behavior: A noncanonical hosted tunnel selector must be rejected rather than ignored in favor of a stored or injected endpoint, which could connect the page to a different stack than the URL appears to select.
  • Evidence: propr-ui/src/config/hostedTunnelConfig.ts:45 — hostedTunnelQueryApiBaseUrl rejects an encoded parameter name such as %74unnel, but hasHostedTunnelQueryParameter recognizes only the literal raw tunnel spelling. Consequently, runtime resolution can fall through to valid session storage or runtime configuration.
  • Minimum fix: Make hasHostedTunnelQueryParameter detect any decoded parameter whose name is tunnel, while continuing to accept only the canonical raw spelling in hostedTunnelQueryApiBaseUrl; test the case with valid stored/configured fallback state.

F6: 🔴 Help failure reported as pairing failure

  • Required behavior: Redacting error details must preserve an accurate, operation-specific recovery message.
  • Evidence: propr-ui/src/desktop/DesktopExperience.tsx:233 — every failed blocked action on a Connect profile is converted to “ProPR Connect pairing could not be completed,” including the externalBrowser.open action used by “Open connection help.”
  • Minimum fix: Use the pairing-specific message only for authentication failures; retain a bounded “could not open connection help” message for help-launch failures.

Suggestions

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

No suggestions.

Score

The overall design and test coverage are strong, and all current checks pass, but the canonical endpoint and selector fall-through defects affect security-sensitive routing boundaries. All three blockers appear locally correctable without redesign.

Score: 6/10


🤖 Review Details

  • Model: GPT-5.6 Sol
  • Time: 3m 35s
  • Tokens: 82,810 (74,755 in / 8,055 out)
  • Cost: $0.57

View Task


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

🤖 Review by ProPR

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

integry commented Aug 30, 2026

Copy link
Copy Markdown
Owner

/fix F4 F5 F6

@propr-dev

propr-dev Bot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor Author

Applied the requested follow-up changes in commit 0aa02e0

Addressed 1 AI review comment (IDs: 5465779216)

Summary of Changes

Implemented only F4, F5, and F6:

  • Added the 2048-character guard before parsing API_PUBLIC_URL in desktopAuthService.ts.
  • Detects decoded tunnel parameter names such as %74unnel, blocking stored/configured fallback in hostedTunnelConfig.ts.
  • Connect pairing errors now apply only to authentication; help-launch failures retain the bounded help-specific message in DesktopExperience.tsx.
  • Added focused regressions for all three findings.

Verification passed:

  • API pairing suite: 8 tests
  • Focused UI suites: 82 tests
  • Desktop experience suite: 22 tests
  • API and UI typechecks
  • API and UI lint
  • git diff --check

No commit was created.


🤖 Implementation Details

  • Model: GPT-5.6 Sol (high)
  • Requested By: @integry
  • Time: 5m 58s
  • Tokens: 1,180,186 (1,170,260 in / 9,926 out)
  • Cost: $1.21
  • 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: 5465785343✓

@integry integry removed the AI label Aug 30, 2026
Implemented only F4, F5, and F6:

- Added the 2048-character guard before parsing `API_PUBLIC_URL` in [desktopAuthService.ts](/tmp/git-processor/worktrees/integry/propr/pr-1988-followup-2026-08-30T00-37-29/packages/api/desktopAuthService.ts:156).
- Detects decoded `tunnel` parameter names such as `%74unnel`, blocking stored/configured fallback in [hostedTunnelConfig.ts](/tmp/git-processor/worktrees/integry/propr/pr-1988-followup-2026-08-30T00-37-29/propr-ui/src/config/hostedTunnelConfig.ts:42).
- Connect pairing errors now apply only to authentication; help-launch failures retain the bounded help-specific message in [DesktopExperience.tsx](/tmp/git-processor/worktrees/integry/propr/pr-1988-followup-2026-08-30T00-37-29/propr-ui/src/desktop/DesktopExperience.tsx:221).
- Added focused regressions for all three findings.

Verification passed:

- API pairing suite: 8 tests
- Focused UI suites: 82 tests
- Desktop experience suite: 22 tests
- API and UI typechecks
- API and UI lint
- `git diff --check`

No commit was created.

PR: #1988
Comment by: @integry (ID: 5465785343)
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

/review

@propr-dev

propr-dev Bot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor Author

AI Code Review Complete requested by @integry

Posted 1 review:

View Task Details

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

propr-dev Bot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor Author

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

Overall Evaluation

This PR is ready to merge. It consistently establishes a strict Connect trust boundary while preserving ordinary HTTPS and loopback behavior.

Canonical endpoint verification — Shared, client, desktop, CLI, and launcher paths reject noncanonical reserved-host spellings before they can fall back to ordinary remote handling.

Approval URL binding — Pairing approvals are constrained to the expected hosted contract or the exact API-origin /browser route, with pairing IDs and query parameters validated.

Strong regression coverage — Tests exercise encoded authorities, ports, nested hosts, Unicode lookalikes, stale recovery races, and secret-bearing failures; all current checks pass.

Merge blockers

No merge blockers.

Suggestions

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

S1: 🟢 Wire the future pairing consumer

When the credential/profile-generation stack referenced in the PR context becomes available, wire normalizeDesktopPairingApprovalUrl from packages/shared/src/desktopPairing.ts directly into its external-browser boundary. The validator and contract tests are appropriate now, and adding a parallel pairing implementation is intentionally outside this PR.

Score

The implementation is cohesive, security-conscious, thoroughly tested, and merge-ready with no verified regressions or failing checks.

Score: 9/10


🤖 Review Details

  • Model: GPT-5.6 Sol
  • Time: 6m 0s
  • Tokens: 88,190 (74,760 in / 13,430 out)
  • Cost: $0.73

View Task


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

🤖 Review by ProPR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant