Skip to content

[1957 by GPT-5.6 Sol] Add cross-platform desktop packaging, updates, and release CI - #1972

Open
propr-dev[bot] wants to merge 23 commits into
1952-epic-electron-runtime-appfrom
1957/gpt-5.6-sol-add-cross-platform-deskto-20260829-1735-0gu
Open

[1957 by GPT-5.6 Sol] Add cross-platform desktop packaging, updates, and release CI#1972
propr-dev[bot] wants to merge 23 commits into
1952-epic-electron-runtime-appfrom
1957/gpt-5.6-sol-add-cross-platform-deskto-20260829-1735-0gu

Conversation

@propr-dev

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

Copy link
Copy Markdown
Contributor

AI Implementation Summary

Closes #1957

Branch: 1957/gpt-5.6-sol-add-cross-platform-deskto-20260829-1735-0gu
Commits: ✅ Changes committed (42d1fb9)


AI Processing Completed

Execution Details:

Summary:
Implemented GitHub issue #1957.

Key changes:

  • Added native x64/arm64 release matrix for Linux, macOS, and Windows in desktop-release-guard.yml.
  • Added DEB, RPM, ZIP, DMG, and Squirrel Windows packaging with independent desktop-v* version propagation.
  • Added stable artifact naming, SHA-256 checksums, release manifests, matrix-completeness validation, and GitHub release publication.
  • Added opt-in Ed25519-signed update manifests with HTTPS, architecture, and OS signing-identity verification. Unsigned developer builds keep updates disabled.
  • Added macOS signing/notarization and Windows Authenticode CI plumbing, including Squirrel startup handling.
  • Documented all required secrets, variables, update-feed publishing, and unsigned local workflows in README.md.
  • Added focused release, update-security, artifact-integrity, and Squirrel tests.

Verification passed:

npm run desktop:test          # 34 tests
npm run desktop:typecheck
npm run desktop:audit
npm run desktop:package
npm run desktop:smoke:inspect

Also verified PROPR_DESKTOP_VERSION=1.2.3 reaches the packaged ASAR metadata. Full GUI smoke testing was unavailable because this host lacks Xvfb; native arm64, macOS, Windows, signing, and notarization are delegated to their corresponding CI runners. Local DEB/RPM/ZIP maker execution was limited by missing zip, fakeroot, and RPM tools; the workflow explicitly installs them.

Detailed Logs:

  • Conversation: 134 messages
  • Session: 01a04e97-baf8-70b2-82fd-9145ce4811f0

Log files stored at:

  • conversation: /tmp/claude-logs/issue-1957-2026-08-29T17-56-59-171Z-conversation.json
  • output: /tmp/claude-logs/issue-1957-2026-08-29T17-56-59-171Z-output.txt
Latest Conversation Messages

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


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

…updates, and

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

Implementation completed successfully.
@integry

integry commented Aug 29, 2026

Copy link
Copy Markdown
Owner

/merge

Merge the current runtime epic base into this packaging branch before review. Preserve the new native packaging/update workflows and all runtime integration fixes, including Windows /@fs/ handling, shared+client desktop preparation, IPv6 loopback validation, and App.tsx lint repair. Resolve conflicts without reverting hardened fuses, audits, or renderer isolation.

@propr-dev

propr-dev Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor Author

🔀 Resolved merge conflicts from 1952-epic-electron-runtime-app into 1957/gpt-5.6-sol-add-cross-platform-deskto-20260829-1735-0gu in commit 78b2803

Conflicting Files

  • .github/workflows/desktop-release-guard.yml
  • apps/desktop/package.json
  • apps/desktop/vite.renderer.config.ts

Resolution Summary

Resolved all three content conflicts and removed every active conflict-marker line. No commit was created.

Conflict resolutions

  • desktop-release-guard.yml

    • Conflict: The PR introduced cross-platform packaging, native Linux tools, signing, and architecture-aware smoke tests; 1952 added clean-checkout packaging before typechecking and an Ubuntu-specific sandbox setup.
    • Resolution: Added the clean-checkout packaging assertions before the PR’s existing matrix typecheck/test flow, retained native Linux tool installation, and kept the PR’s later architecture-aware sandbox launch. The assertions explicitly use Bash for Windows-runner compatibility.
    • Why: This preserves 1952’s clean-build guarantee without duplicating checks or replacing the PR’s cross-platform sandbox logic with an x64-only Ubuntu step.
  • package.json

    • Conflict: The PR expanded desktop tests to include release-script tests, while 1952 added the renderer/client preparation hooks for typechecking and packaging.
    • Resolution: Kept the expanded test glob and added prepackage, alongside the merged prepare:renderer and pretypecheck hooks.
    • Why: The changes are complementary: packaging now builds both workspace dependencies, while PR release tests remain covered.
  • vite.renderer.config.ts

    • Conflict: The PR added independent desktop-version resolution; 1952 added cross-platform Vite filesystem URL normalization.
    • Resolution: Retained both imports and both behaviors.
    • Why: Version injection and filesystem URL portability solve independent requirements and are fully compatible.

Validation passed:

  • Repository-wide active conflict-marker scan
  • git diff --check
  • JSON and YAML parsing
  • Desktop and renderer typechecks
  • Desktop tests: 36/36 passed

One environment issue remains: git add could not update the linked worktree index because its metadata is root-owned:

fatal: Unable to create '.../index.lock': Permission denied

Therefore Git still reports the three files as UU despite their resolved working-tree contents. The privileged automation must stage them before committing.


🤖 Resolution Details

  • Model: gpt-5.6-sol
  • Time: 4m 31s

View Task Execution


System-triggered merge conflict resolution

…7/gpt-5.6-sol-add-cross-platform-deskto-20260829-1735-0gu

Automatically resolved merge conflicts after merging target branch into PR branch.
Resolved conflicts in:
- .github/workflows/desktop-release-guard.yml
- apps/desktop/package.json
- apps/desktop/vite.renderer.config.ts

PR: #1972
Model: gpt-5.6-sol
@integry

integry commented Aug 29, 2026

Copy link
Copy Markdown
Owner

/review

Review the synchronized packaging head 78b2803 as a release/security gate. Focus on signed-update trust and fail-closed behavior, canonical manifest verification and anti-downgrade handling, platform/architecture/URL validation, artifact/checksum completeness, native x64/arm64 matrix correctness, Squirrel startup, macOS signing/notarization, workflow trigger/permission/secret safety, and the semantic conflict resolution in .github/workflows/desktop-release-guard.yml. Treat any path traversal, shell injection, untrusted-code secret exposure, unsigned production update path, non-native artifact masquerading, or incomplete release publication as a blocker. Native CI is running; do not infer success from stale pre-sync runs.

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

Copy link
Copy Markdown
Owner

!propr Remove only the five trailing blank lines at EOF currently reported by git diff --check in apps/desktop/scripts/make-dmg.mjs, apps/desktop/src/release-config.test.ts, apps/desktop/src/release-config.ts, apps/desktop/src/signed-updates.ts, and apps/desktop/src/squirrel-events.ts. Preserve behavior and all synchronized runtime changes. Then run git diff --check, desktop typecheck, and desktop tests. This is an independent formatting gate found while the security/release review continues.

@integry

integry commented Aug 29, 2026

Copy link
Copy Markdown
Owner

!propr Fix the native Linux packaging blocker shown by both synchronized matrix jobs (run 33267474593): MakerDeb searches apps/desktop/out/propr-desktop-linux-{x64,arm64}/@propr/desktop, but Forge packages the executable as propr-desktop. Configure the DEB maker—and RPM maker wherever it derives the same default—so its bin/executable contract explicitly matches packagerConfig.executableName (propr-desktop) instead of the scoped npm workspace name. Add a focused config regression that prevents either Linux maker from resolving @propr/desktop. Preserve the real native x64/arm64 matrix. Then run desktop typecheck/tests, make DEB+RPM+ZIP on Linux, inspect with dpkg/rpm/unzip, stage all three artifacts, git diff --check, and let the full matrix rerun. This is a release blocker, not runner noise.

@propr-dev

propr-dev Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor Author

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

Overall Evaluation

This PR builds a thoughtful cross-platform packaging pipeline, but it needs significant security corrections before merge. The current head’s 16 native checks are still pending, and the code review identifies blockers independent of those results.

Fail-closed build configuration — Updates default to disabled and enabling them requires HTTPS metadata, an Ed25519 public key, a signing identity, and an explicit code-signed build flag.

Strong release completeness checks — The six-target native matrix verifies runner architecture, rejects missing or duplicate artifacts, recomputes fragment integrity, and produces deterministic artifact names and SHA-256 metadata.

Careful Squirrel startup handling — Install, update, uninstall, obsolete, first-run, and unrelated arguments are separated without suppressing normal startup.

Merge blockers

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

F1: 🔴 PR code receives the update private key

  • Required behavior: Pull-request code must never receive release-signing secrets; untrusted-code secret exposure is explicitly a blocker.
  • Evidence: .github/workflows/desktop-release-guard.yml:353 — the finalize job runs for pull_request events and unconditionally places PROPR_DESKTOP_UPDATE_PRIVATE_KEY in the environment of the PR-controlled release-artifacts.mjs program.
  • Minimum fix: Run PR finalization without the private key. Move manifest signing into a separate job that executes only for a trusted tag or protected publish dispatch, checks out trusted tagged code, and preferably uses an approval-protected environment.

F2: 🔴 Signed metadata does not authenticate updates

  • Required behavior: A verified update manifest must cryptographically bind the selected platform, architecture, version, native feed metadata, and downloaded update content before native installation begins.
  • Evidence: apps/desktop/src/signed-updates.ts:150 — after verifying only the top-level manifest, the code passes a mutable feed URL directly to autoUpdater; it never authenticates the fetched RELEASES/JSON feed or its NUPKG/ZIP contents against the signed artifact hashes.
  • Minimum fix: Commit target-specific feed metadata and update-content hashes/version to the signed payload, then verify the exact native feed and downloadable content before permitting installation. Do not enable the updater through a mutable URL unless equivalent cryptographic content binding is enforced.

F3: 🔴 Signing identity check is only metadata

  • Required behavior: The signing identity pinned in an installed build must be checked against the actual OS signer of the released update, not merely another string supplied by the manifest signer.
  • Evidence: apps/desktop/src/signed-updates.ts:146 — the identity gate compares feed.signingIdentity with config.signingIdentity; neither value is derived from the update artifact’s Authenticode certificate or macOS signing authority.
  • Minimum fix: Derive and validate the actual Windows certificate subject and macOS Team ID/signing authority before signing release metadata, bind the validated artifacts as described in F2, and ensure the platform updater enforces the same identity before installation. CI must also compare the signed binaries’ identities with the configured pins, rather than checking only generic signature validity.

F4: 🔴 Query URLs produce the wrong signature URL

  • Required behavior: Every manifest URL accepted by the new HTTPS validator must resolve its documented detached .sig companion correctly.
  • Evidence: apps/desktop/src/signed-updates.ts:136 — query strings are accepted, but ${manifestUrl}.sig appends the suffix after the query; https://host/desktop-release.json?channel=stable becomes ...?channel=stable.sig instead of addressing desktop-release.json.sig.
  • Minimum fix: Either reject manifest URLs containing a query or construct the signature URL by appending .sig to the URL pathname while handling the query explicitly; add coverage for this accepted boundary case.

Suggestions

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

S1: 🟢 Inspect produced binary architectures

The workflow verifies the runner’s Node architecture and supplies the matching Forge --arch, which is reasonable evidence of native packaging. Inspecting PE, Mach-O, ELF, and packaged application payload headers would additionally catch a maker regression or mislabeled bootstrap executable. This is defense in depth rather than a blocker because the current jobs build natively and reject mismatched runners.

S2: 🟢 Validate Squirrel metadata internally

stageArtifacts confirms that RELEASES references the renamed NUPKG but does not validate its embedded checksum and size against that package; the test fixture even uses a placeholder checksum. Checking those fields would catch malformed Squirrel output earlier. It is optional because the maker generates both files together, renaming does not alter the NUPKG bytes, and the final release layer independently computes SHA-256 hashes.

S3: 🟢 Tighten manifest canonicality

Consider enforcing safe-integer version components, canonical RFC 3339 timestamps, and an explicit policy for unknown or duplicate JSON fields. Exact-byte Ed25519 verification already prevents post-signing mutation, so this is parser hardening rather than a prerequisite for merge.

Score

The native matrix, staging logic, and default-off configuration are strong, but exposing the update signing key to PR-controlled code and failing to authenticate the actual update feed/content are critical release-gate defects. Pending native checks must also complete successfully after these fixes.

Score: 4/10


🤖 Review Details

  • Model: GPT-5.6 Sol
  • Time: 6m 3s
  • Tokens: 85,698 (78,764 in / 6,934 out)
  • Cost: $0.29

View Task


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

🤖 Review by ProPR

@propr-dev

propr-dev Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor Author

Applied the requested follow-up changes in commit a9dc441

Processed 2 comments:

  • Comment 1 by @integry (ID: 5464034777✓)
  • Comment 2 by @integry (ID: 5464059305✓)

Summary of Changes

Implemented the PR follow-ups.

  • DEB and RPM makers now explicitly use bin: 'propr-desktop' in forge.config.ts.
  • Added a real Forge-config regression in release-config.test.ts, verifying both makers match packagerConfig.executableName and never resolve @propr/desktop.
  • Removed only the requested five EOF blank lines.
  • Preserved the native x64/arm64 workflow matrix.

Verification passed:

  • Desktop typecheck
  • Desktop tests: 37/37
  • Native Linux x64 DEB, RPM, and ZIP creation
  • dpkg-deb, rpm, and unzip inspection
  • DEB/RPM both contain /usr/bin/propr-desktop -> ../lib/propr-desktop/propr-desktop
  • All three artifacts staged with checksums successfully
  • Working-tree and PR-base git diff --check

The temporary staging output was removed afterward to avoid committing ~300 MB of generated binaries; artifacts remain reproducible under the ignored apps/desktop/out/make directory. No commit was created.


🤖 Implementation Details

  • Model: GPT-5.6 Sol (high)
  • Requested By: @integry
  • Time: 10m 1s
  • Tokens: 2,095,318 (2,078,169 in / 17,149 out)
  • Cost: $2.15

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 IDs: 5464034777✓, 5464059305✓

@integry

integry commented Aug 29, 2026

Copy link
Copy Markdown
Owner

/fix F1 F2 F3 F4

Implement all four as release blockers together with the already assigned Linux maker and diff-check fixes. For F1, PR jobs must never expose PROPR_DESKTOP_UPDATE_PRIVATE_KEY to PR-controlled checkout/code: PR finalization may generate unsigned validation metadata, while signing runs only in a separate trusted tag/protected publish job checking out the immutable desktop-v<version> tag (use a release environment boundary) and fails closed when signed updates are configured. For F2/F3, do not treat a manifest-provided identity string or mutable feed URL as authentication. Bind the exact target/version/feed metadata and downloadable bytes (URL, size, SHA-256) in the Ed25519-signed payload; verify exact feed and artifact bytes before any native installation path. macOS must verify the produced app’s actual designated signer/Team ID against the build pin; Windows CI/runtime must bind the actual Authenticode signer and package bytes. If Electron autoUpdater cannot consume preverified immutable/local bytes without re-fetch/TOCTOU, keep the channel safely check-only and do not call setFeedURL/checkForUpdates until a cryptographically bound installation path exists. For F4, reject manifest query strings (and test it) so the documented pathname .sig companion is unambiguous. Add tampered-feed, tampered-artifact, signer-mismatch, wrong target/version/arch, PR-secret absence, and trusted-release-signing tests. Preserve default-off unsigned developer packages and do not weaken sandbox/fuses. Re-run the full native matrix after the head settles.

Implemented the PR follow-ups.

- DEB and RPM makers now explicitly use `bin: 'propr-desktop'` in [forge.config.ts](/tmp/git-processor/worktrees/integry/propr/pr-1972-followup-2026-08-29T18-16-06/apps/desktop/forge.config.ts).
- Added a real Forge-config regression in [release-config.test.ts](/tmp/git-processor/worktrees/integry/propr/pr-1972-followup-2026-08-29T18-16-06/apps/desktop/src/release-config.test.ts), verifying both makers match `packagerConfig.executableName` and never resolve `@propr/desktop`.
- Removed only the requested five EOF blank lines.
- Preserved the native x64/arm64 workflow matrix.

Verification passed:

- Desktop typecheck
- Desktop tests: 37/37
- Native Linux x64 DEB, RPM, and ZIP creation
- `dpkg-deb`, `rpm`, and `unzip` inspection
- DEB/RPM both contain `/usr/bin/propr-desktop -> ../lib/propr-desktop/propr-desktop`
- All three artifacts staged with checksums successfully
- Working-tree and PR-base `git diff --check`

The temporary staging output was removed afterward to avoid committing ~300 MB of generated binaries; artifacts remain reproducible under the ignored `apps/desktop/out/make` directory. No commit was created.

PR: #1972
Comment by: @integry (ID: 5464034777)
Comment by: @integry (ID: 5464059305)
Model: gpt-5.6-sol
@propr-dev

propr-dev Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor Author

Applied the requested follow-up changes in commit 7ba9e9b

Addressed 1 AI review comment (IDs: 5464059975)

Summary of Changes

Implemented F1–F4 without committing.

Key changes:

  • PR finalization is unsigned and never receives the update private key.
  • Signing moved behind the desktop-release environment, restricted to trusted tag/publish events and pinned tag SHA.
  • Signed schema now binds manifest URL, target, version, feed bytes, artifact URL/size/SHA-256, and actual native signer evidence.
  • macOS Team ID/designated requirement and Windows Authenticode subjects are verified from produced/downloaded packages.
  • Runtime is safely check-only; Electron autoUpdater is never initialized.
  • Manifest query strings are rejected.
  • Added all requested security regression tests.

Verification passed:

  • npm run desktop:test — 45 tests
  • npm run desktop:typecheck
  • npm run desktop:package
  • npm run desktop:smoke:inspect
  • git diff --check

The local host lacks fakeroot, rpm, and zip, so native DEB/RPM/ZIP creation and macOS/Windows verification remain for the six-runner CI matrix after the changes are committed.


🤖 Implementation Details

  • Model: GPT-5.6 Sol (high)
  • Requested By: @integry
  • Time: 15m 43s
  • Tokens: 3,326,016 (3,289,324 in / 36,692 out)
  • Cost: $3.32
  • 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: 5464067090✓

Implemented F1–F4 without committing.

Key changes:

- PR finalization is unsigned and never receives the update private key.
- Signing moved behind the `desktop-release` environment, restricted to trusted tag/publish events and pinned tag SHA.
- Signed schema now binds manifest URL, target, version, feed bytes, artifact URL/size/SHA-256, and actual native signer evidence.
- macOS Team ID/designated requirement and Windows Authenticode subjects are verified from produced/downloaded packages.
- Runtime is safely check-only; Electron `autoUpdater` is never initialized.
- Manifest query strings are rejected.
- Added all requested security regression tests.

Verification passed:

- `npm run desktop:test` — 45 tests
- `npm run desktop:typecheck`
- `npm run desktop:package`
- `npm run desktop:smoke:inspect`
- `git diff --check`

The local host lacks `fakeroot`, `rpm`, and `zip`, so native DEB/RPM/ZIP creation and macOS/Windows verification remain for the six-runner CI matrix after the changes are committed.

PR: #1972
Comment by: @integry (ID: 5464067090)
Model: gpt-5.6-sol
@integry

integry commented Aug 29, 2026

Copy link
Copy Markdown
Owner

!propr Harden the runtime download boundary before final review. main.ts currently calls response.arrayBuffer() for manifests, signatures, feeds, and entire update packages with no response-size ceiling or timeout, so a compromised/misconfigured update host can exhaust the Electron main process before the signed size/SHA-256 check runs. Add an abort timeout and streaming byte limits: small fixed caps for manifest/signature/feed, and for the artifact enforce the signed expected size while streaming (plus a defensible global maximum); abort immediately if Content-Length or received bytes exceed the limit. Avoid holding multiple package-sized copies in memory if practical, clean temporary files on every failure, and test oversized headers, chunked overflow, timeout/abort, undersize, and exact-size success. Preserve the check-only/no-autoUpdater design and the existing exact hash/native signer verification. Also make redirect behavior explicit: either reject cross-origin redirects or validate the final HTTPS URL against the signed URL policy without weakening byte authentication. Re-run desktop security tests and the native matrix; do not merge yet because this branch still needs the final runtime/CSS base sync.

@propr-dev

propr-dev Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor Author

Applied the requested follow-up changes in commit eef7396

Summary of Changes

Implemented the runtime download hardening without committing or merging.

Key changes:

  • Added streaming limits: 512 KiB manifest, 1 KiB signature, 1 MiB feed, and 1 GiB artifact maximum.
  • Added 30-second metadata and 10-minute artifact abort timeouts.
  • Rejects oversized Content-Length, chunked overflow, signed-size mismatches, and cross-origin final redirects.
  • Streams artifacts directly to private temporary files with incremental SHA-256 verification.
  • Cleans temporary files/directories on download, hash, and signer failures.
  • Preserved check-only behavior, Ed25519 verification, exact feed/artifact hashes, and native signer validation.

Files changed:

Validation passed:

  • Desktop typecheck
  • All 52 desktop tests
  • Runtime and packaging audits: 0 vulnerabilities
  • Linux x64 native packaging and fuse/executable inspection
  • git diff --check

The full six-target native matrix cannot run locally on this Linux x64 host; it must rerun in CI after the follow-up is committed and pushed.


🤖 Implementation Details

  • Model: GPT-5.6 Sol (high)
  • Requested By: @integry
  • Time: 9m 36s
  • Tokens: 1,577,453 (1,557,935 in / 19,518 out)
  • Cost: $1.80
  • Subscription usage: Weekly +1%

Undo ChangesView Task Execution


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

🤖 Generated by ProPR
Processing comment ID: 5464201037✓

@github-actions

Copy link
Copy Markdown

Full Test Suite Results

Validation failed during: Full test suite.

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

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


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


[1/325] apps/desktop/scripts/release-artifacts.test.mjs
TAP version 13
# Subtest: desktop release artifacts
    # Subtest: stages named artifacts and finalizes unsigned validation metadata
    ok 1 - stages named artifacts and finalizes unsigned validation metadata
      ---
      duration_ms: 52.558049
      type: 'test'
      ...
    # Subtest: fails closed when trusted update signing configuration is incomplete
    ok 2 - fails closed when trusted update signing configuration is incomplete
      ---
      duration_ms: 67.560869
      type: 'test'
      ...
    # Subtest: signs cryptographically bound feeds only in the trusted release phase
    ok 3 - signs cryptographically bound feeds only in the trusted release phase
      ---
      duration_ms: 42.499181
      type: 'test'
      ...
    # Subtest: refuses to sign when artifact bytes changed after unsigned finalization
    ok 4 - refuses to sign when artifact bytes changed after unsigned finalization
      ---
      duration_ms: 28.6199
      type: 'test'
      ...
    1..4
ok 1 - desktop release artifacts
  ---
  duration_ms: 192.443575
  type: 'suite'
  ...
1..1
# tests 4
# suites 1
# pass 4
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 414.985512

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

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

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

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

[6/325] apps/desktop/src/release-config.test.ts
TAP version 13
# Subtest: desktop release configuration
    # Subtest: keeps Linux maker executables aligned with the packaged executable
    ok 1 - keeps Linux maker executables aligned with the packaged executable
      ---
      duration_ms: 633.032525
      type: 'test'
      ...
    # Subtest: propagates an explicit independent desktop version
    ok 2 - propagates an explicit independent desktop version
      ---
      duration_ms: 0.599453
      type: 'test'
      ...
    # Subtest: keeps updates disabled unless they are explicitly enabled
    ok 3 - keeps updates disabled unless they are explicitly enabled
      ---
      duration_ms: 0.139241
      type: 'test'
      ...
    # Subtest: requires a signed build and a complete trusted update configuration
    ok 4 - requires a signed build and a complete trusted update configuration
      ---
      duration_ms: 0.819194
      type: 'test'
      ...
    # Subtest: rejects partially configured signing groups
    ok 5 - rejects partially configured signing groups
      ---
      duration_ms: 0.179666
      type: 'test'
      ...
    1..5
ok 1 - desktop release configuration
  ---
  duration_ms: 636.012073
  type: 'suite'
  ...
1..1
# tests 5
# suites 1
# pass 5
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 896.135331

[7/325] apps/desktop/src/release-workflow.test.ts
TAP version 13
# Subtest: desktop trusted release workflow
    # Subtest: never exposes the update private key to pull-request finalization
    ok 1 - never exposes the update private key to pull-request finalization
      ---
      duration_ms: 1.022255
      type: 'test'
      ...
    # Subtest: signs only behind the release environment from the immutable desktop tag
    ok 2 - signs only behind the release environment from the immutable desktop tag
      ---
      duration_ms: 0.37946
      type: 'test'
      ...
    1..2
ok 1 - desktop trusted release workflow
  ---
  duration_ms: 2.650843
  type: 'suite'
  ...
1..1
# tests 2
# suites 1
# pass 2
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 243.808006

[8/325] apps/desktop/src/security.test.ts
TAP version 13
# Subtest: desktop URL security
    # Subtest: only accepts HTTPS and loopback HTTP API endpoints
    ok 1 - only accepts HTTPS and loopback HTTP API endpoints
      ---
      duration_ms: 1.173547
      type: 'test'
      ...
    # Subtest: denies unsafe external browser schemes and credential-bearing URLs
    ok 2 - denies unsafe external browser schemes and credential-bearing URLs
      ---
      duration_ms: 0.199734
      type: 'test'
      ...
    # Subtest: requires an exact loopback development origin
    ok 3 - requires an exact loopback development origin
      ---
      duration_ms: 0.288681
      type: 'test'
      ...
    # Subtest: retains IPC trust for hash-routed packaged renderer URLs only
    ok 4 - retains IPC trust for hash-routed packaged renderer URLs only
      ---
      duration_ms: 0.297307
      type: 'test'
      ...
    # Subtest: allowlists custom protocol actions and extracts them from argv
    ok 5 - allowlists custom protocol actions and extracts them from argv
      ---
      duration_ms: 0.322183
      type: 'test'
      ...
    # Subtest: publishes a restrictive production policy
    ok 6 - publishes a restrictive production policy
      ---
      duration_ms: 0.313346
      type: 'test'
      ...
    # Subtest: relaxes inline scripts only while Vite serves the development renderer
    ok 7 - relaxes inline scripts only while Vite serves the development renderer
      ---
      duration_ms: 0.29387
      type: 'test'
      ...
    1..7
ok 1 - desktop URL security
  ---
  duration_ms: 4.206266
  type: 'suite'
  ...
1..1
# tests 7
# suites 1
# pass 7
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 268.68745

[9/325] apps/desktop/src/signed-updates.test.ts
TAP version 13
# Subtest: signed desktop updates
    # Subtest: verifies the exact published manifest bytes
    ok 1 - verifies the exact published manifest bytes
      ---
      duration_ms: 4.308056
      type: 'test'
      ...
    # Subtest: checks exact feed, artifact, and native signer without invoking Electron autoUpdater
    ok 2 - checks exact feed, artifact, and native signer without invoking Electron autoUpdater
      ---
      duration_ms: 1.387708
      type: 'test'
      ...
    # Subtest: rejects tampered native feed bytes
    ok 3 - rejects tampered native feed bytes
      ---
      duration_ms: 0.949668
      type: 'test'
      ...
    # Subtest: rejects tampered artifact bytes before native signer inspection
    ok 4 - rejects tampered artifact bytes before native signer inspection
      ---
      duration_ms: 0.89731
      type: 'test'
      ...
    # Subtest: rejects the actual native signer when it differs from the signed build pin
    ok 5 - rejects the actual native signer when it differs from the signed build pin
      ---
      duration_ms: 0.82236
      type: 'test'
      ...
    # Subtest: rejects wrong target, version, and architecture bindings
    ok 6 - rejects wrong target, version, and architecture bindings
      ---
      duration_ms: 1.827722
      type: 'test'
      ...
    # Subtest: rejects manifest query strings before resolving the pathname .sig companion
    ok 7 - rejects manifest query strings before resolving the pathname .sig companion
      ---
      duration_ms: 0.277871
      type: 'test'
      ...
    # Subtest: does not fetch update bytes for current or unsupported builds
    ok 8 - does not fetch update bytes for current or unsupported builds
      ---
      duration_ms: 0.868746
      type: 'test'
      ...
    1..8
ok 1 - signed desktop updates
  ---
  duration_ms: 12.739674
  type: 'suite'
  ...
1..1
# tests 8
# suites 1
# pass 8
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 268.124142

[10/325] apps/desktop/src/squirrel-events.test.ts
TAP version 13
# Subtest: Squirrel.Windows startup events
    # Subtest: creates shortcuts and schedules a clean exit after install
    ok 1 - creates shortcuts and schedules a clean exit after install
      ---
      duration_ms: 1.398348
      type: 'test'
      ...
    # Subtest: does not consume first-run or unrelated arguments
    ok 2 - does not consume first-run or unrelated arguments
      ---
      duration_ms: 0.249818
      type: 'test'
      ...
    1..2
ok 1 - Squirrel.Windows startup events
  ---
  duration_ms: 2.775256
  type: 'suite'
  ...
1..1
# tests 2
# suites 1
# pass 2
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 240.120164

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

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

[13/325] packages/api/test/agentLoginRoutes.test.ts
TAP version 13
# Subtest: agent login session manager
    # Subtest: builds an allowlisted Docker login container without forwarding host secrets
    ok 1 - builds an allowlisted Docker login container without forwarding host secrets
      ---
      duration_ms: 4.019637
      type: 'test'
      ...
    # Subtest: maps a ProPR-managed account to the managed host root and marks its container ownership as safe to normalize
    ok 2 - maps a ProPR-managed account to the managed host root and marks its container ownership as safe to normalize
      ---
      duration_ms: 1.332255
      type: 'test'
      ...
    # Subtest: creates a ProPR-managed credential directory before starting Docker
    ok 3 - creates a ProPR-managed credential directory before starting Docker
      ---
      duration_ms: 2.368545
      type: 'test'
      ...
    # Subtest: fails clearly instead of resolving a default home path inside a containerized API
    ok 4 - fails clearly instead of resolving a default home path inside a containerized API
      ---
      duration_ms: 0.657491
      type: 'test'
      ...
    # Subtest: rejects unsafe credential roots and option-like image names
    ok 5 - rejects unsafe credential roots and option-like image names
      ---
      duration_ms: 0.463598
      type: 'test'
      ...
    # Subtest: streams sanitized output, accepts input, and records successful completion
    ok 6 - streams sanitized output, accepts input, and records successful completion
      ---
      duration_ms: 1.757591
      type: 'test'
      ...
    # Subtest: prevents concurrent logins that write the same credential directory
    ok 7 - prevents concurrent logins that write the same credential directory
      ---
      duration_ms: 0.679162
      type: 'test'
      ...
    # Subtest: pulls a missing agent image before creating the login container
    ok 8 - pulls a missing agent image before creating the login container
      ---
      duration_ms: 0.698588
      type: 'test'
      ...
    # Subtest: renews the session deadline when the user sends input
    ok 9 - renews the session deadline when the user sends input
      ---
      duration_ms: 0.673702
      type: 'test'
      ...
    # Subtest: sweeps only orphaned login containers from the current stack scope
    ok 10 - sweeps only orphaned login containers from the current stack scope
      ---
      duration_ms: 0.559357
      type: 'test'
      ...
    1..10
ok 1 - agent login session manager
  ---
  duration_ms: 14.763413
  type: 'suite'
  ...
# Subtest: agent login routes
    # Subtest: starts and returns only the requesting user login session
    ok 1 - starts and returns only the requesting user login session
      ---
      duration_ms: 0.850392
      type: 'test'
      ...
    # Subtest: accepts the same agent alias on start and follow-up calls, including while disabled
    ok 2 - accepts the same agent alias on start and follow-up calls, including while disabled
      ---
      duration_ms: 0.605063
      type: 'test'
      ...
    # Subtest: rejects interactive login for an unsupported agent type
    ok 3 - rejects interactive login for an unsupported agent type
      ---
      duration_ms: 0.483035
      type: 'test'
      ...
    1..3
ok 2 - agent login routes
  ---
  duration_ms: 2.083581
  type: 'suite'
  ...
# [2026-08-29 18:44:16.921 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-KeZodH/013-agentLoginRoutes.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 18:44:16.933 +0000] �[32mINFO�[39m: �[36mSQLite database connection closed�[39m
# [2026-08-29 18:44:16.933 +0000] �[32mINFO�[39m: �[36mShutting down queue...�[39m
# [2026-08-29 18:44:16.933 +0000] �[32mINFO�[39m: �[36mQueue was never initialized, nothing to shutdown�[39m
1..2
# tests 13
# suites 2
# pass 13
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 2328.572288

[14/325] packages/api/test/agentRuntimeRoutes.test.ts
TAP version 13
# Subtest: agent runtime package routes
    # Subtest: queues one validated package profile for the unified agent image
    ok 1 - queues one validated package profile for the unified agent image
      ---
      duration_ms: 4.371245
      type: 'test'
      ...
    # Subtest: persists a failed state when queue submission fails
    ok 2 - persists a failed state when queue submission fails
      ---
      duration_ms: 0.911918
      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.771194
      type: 'test'
      ...
    # Subtest: times out slow runtime package validation before queueing
    ok 4 - times out slow runtime package validation before queueing
      ---
      duration_ms: 5.593273
      type: 'test'
      ...
    # Subtest: returns package suggestions from configured runtimes
    ok 5 - returns package suggestions from configured runtimes
      ---
      duration_ms: 0.910465
      type: 'test'
      ...
    # Subtest: enforces PROPR_ADMIN_USERS when configured
    ok 6 - enforces PROPR_ADMIN_USERS when configured
      ---
      duration_ms: 0.538838
      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.38439
      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.444693
      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.505366
      type: 'test'
      ...
    # Subtest: does not warm the package catalog when a member is rejected
    ok 10 - does not warm the package catalog when a member is rejected
      ---
      duration_ms: 3.109512
      type: 'test'
      ...
    # Subtest: resolves the runtime build queue lazily when queueing
    ok 11 - resolves the runtime build queue lazily when queueing
      ---
      duration_ms: 0.838611
      type: 'test'
      ...
    # Subtest: reports apply load failures through the route response
    ok 12 - reports apply load failures through the route response
      ---
      duration_ms: 0.453589
      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.490148
      type: 'test'
      ...
    # Subtest: rejects runtime verification without runtime-management permission
    ok 14 - rejects runtime verification without runtime-management permission
      ---
      duration_ms: 0.27242
      type: 'test'
      ...
    1..14
ok 1 - agent runtime package routes
  ---
  duration_ms: 21.360557
  type: 'suite'
  ...
# [2026-08-29 18:44:19.019 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-KeZodH/014-agentRuntimeRoutes.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 18:44:19.037 +0000] �[32mINFO�[39m: �[36mSQLite database connection closed�[39m
1..1
# tests 14
# suites 1
# pass 14
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 2104.551817

[15/325] packages/api/test/attachmentUploadCleanup.test.ts
TAP version 13
# Subtest: upload handler removes Multer files rejected before attachment processing
ok 1 - upload handler removes Multer files rejected before attachment processing
  ---
  duration_ms: 8.156903
  type: 'test'
  ...
# Subtest: attachment processing removes temporary and final files when persistence fails
ok 2 - attachment processing removes temporary and final files when persistence fails
  ---
  duration_ms: 5.851797
  type: 'test'
  ...
# Subtest: temporary cleanup refuses paths outside its configured root
ok 3 - temporary cleanup refuses paths outside its configured root
  ---
  duration_ms: 0.683369
  type: 'test'
  ...
# Subtest: temporary cleanup rejects dot-segment paths that resolve above its root
ok 4 - temporary cleanup rejects dot-segment paths that resolve above its root
  ---
  duration_ms: 0.503062
  type: 'test'
  ...
# Subtest: attachment processing rejects path-like draft IDs and still cleans its temp file
ok 5 - attachment processing rejects path-like draft IDs and still cleans its temp file
  ---
  duration_ms: 1.77836
  type: 'test'
  ...
# [2026-08-29 18:44:21.260 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-KeZodH/015-attachmentUploadCleanup.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 18:44:21.277 +0000] �[32mINFO�[39m: �[36mSQLite database connection closed�[39m
1..5
# tests 5
# suites 0
# pass 5
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 2110.660157

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

[17/325] packages/api/test/authRedirect.test.ts
TAP version 13
# Subtest: auth redirect allowlist treats FRONTEND_URL as exact host only
ok 1 - auth redirect allowlist treats FRONTEND_URL as exact host only
  ---
  duration_ms: 45.439055
  type: 'test'
  ...
# Subtest: proxy mode auth redirect fallback returns the hosted UI origin
ok 2 - proxy mode auth redirect fallback returns the hosted UI origin
  ---
  duration_ms: 13.49976
  type: 'test'
  ...
# Subtest: auth redirect allowlist permits subdomains only for explicit wildcard-style hosts
ok 3 - auth redirect allowlist permits subdomains only for explicit wildcard-style hosts
  ---
  duration_ms: 5.993044
  type: 'test'
  ...
# Subtest: auth redirect allowlist permits leading-dot COOKIE_DOMAIN subdomains
ok 4 - auth redirect allowlist permits leading-dot COOKIE_DOMAIN subdomains
  ---
  duration_ms: 4.108548
  type: 'test'
  ...
# Subtest: auth redirect allowlist permits exact additional hosts without permitting their subdomains
ok 5 - auth redirect allowlist permits exact additional hosts without permitting their subdomains
  ---
  duration_ms: 6.367184
  type: 'test'
  ...
# Subtest: auth redirect allowlist permits wildcard entries with protocol prefixes
ok 6 - auth redirect allowlist permits wildcard entries with protocol prefixes
  ---
  duration_ms: 5.944664
  type: 'test'
  ...
# Subtest: auth redirect allowlist rejects invalid URLs and non-http protocols
ok 7 - auth redirect allowlist rejects invalid URLs and non-http protocols
  ---
  duration_ms: 6.182849
  type: 'test'
  ...
# Subtest: auth redirect allowlist only permits cleartext HTTP for localhost
ok 8 - auth redirect allowlist only permits cleartext HTTP for localhost
  ---
  duration_ms: 5.857501
  type: 'test'
  ...
# Subtest: auth redirect allowlist permits configured local IP literals
ok 9 - auth redirect allowlist permits configured local IP literals
  ---
  duration_ms: 5.409262
  type: 'test'
  ...
# Subtest: auth redirect allowlist ignores malformed additional host entries
ok 10 - auth redirect allowlist ignores malformed additional host entries
  ---
  duration_ms: 5.13588
  type: 'test'
  ...
# [2026-08-29 18:44:26.880 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-KeZodH/017-authRedirect.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 18:44:26.975 +0000] �[32mINFO�[39m: �[36mSQLite database connection closed�[39m
1..10
# tests 10
# suites 0
# pass 10
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 2792.760667

[18/325] packages/api/test/configAgentValidation.antigravity.test.ts
TAP version 13
# Subtest: agent config validation accepts antigravity and rejects gemini for new configs
ok 1 - agent config validation accepts antigravity and rejects gemini for new configs
  ---
  duration_ms: 1540.462156
  type: 'test'
  ...
# [2026-08-29 18:44:29.359 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-KeZodH/018-configAgentValidation.antigravity.test.ts/propr.test.sqlite"
#     environment: "test"
1..1
# tests 1
# suites 0
# pass 1
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 2115.054435

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

[20/325] packages/api/test/configRoutesIndexingSummarization.test.ts
TAP version 13
# [2026-08-29 18:44:31.965 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-KeZodH/020-configRoutesIndexingSummarization.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 18:44:31.996 +0000] �[32mINFO�[39m: �[36mRunning database migrations...�[39m
# Subtest: postSummarizationSettings trims model-specific aliases before saving
ok 1 - postSummarizationSettings trims model-specific aliases before saving
  ---
  duration_ms: 1866.09137
  type: 'test'
  ...
# Subtest: postSummarizationSettings rejects enabled summarization without a primary alias
ok 2 - postSummarizationSettings rejects enabled summarization without a primary alias
  ---
  duration_ms: 3.402541
  type: 'test'
  ...
# [2026-08-29 18:44:32.258 +0000] �[32mINFO�[39m: �[36mDatabase migrations completed successfully�[39m
# [2026-08-29 18:44:32.260 +0000] �[32mINFO�[39m: �[36mSuccessfully loaded agents configuration�[39m
#     agentCount: 2
# [2026-08-29 18:44:32.260 +0000] �[32mINFO�[39m: �[36mSuccessfully loaded summarization settings�[39m
#     summarization: {
#       "enabled": false,
#       "agent_alias": "",
#       "fallback_agent_alias": "",
#       "custom_prompt": ""
#     }
# [2026-08-29 18:44:32.262 +0000] �[32mINFO�[39m: �[36mSuccessfully saved summarization settings�[39m
#     summarization: {
#       "enabled": true,
#       "agent_alias": "codex:gpt-5.5",
#       "fallback_agent_alias": "fallback:gpt-5.4",
#       "custom_prompt": ""
#     }
# [2026-08-29 18:44:32.263 +0000] �[32mINFO�[39m: �[36mSuccessfully loaded summarization settings�[39m
#     summarization: {
#       "enabled": true,
#       "agent_alias": "codex:gpt-5.5",
#       "fallback_agent_alias": "fallback:gpt-5.4",
#       "custom_prompt": ""
#     }
# [2026-08-29 18:44:32.274 +0000] �[32mINFO�[39m: �[36mSQLite database connection closed�[39m
1..2
# tests 2
# suites 0
# pass 2
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 2289.307963

[21/325] packages/api/test/connectAuth.test.ts
TAP version 13
# Subtest: relay tunnel mode uses Connect without local OAuth credentials
ok 1 - relay tunnel mode uses Connect without local OAuth credentials
  ---
  duration_ms: 1.684845
  type: 'test'
  ...
# Subtest: relay tunnel mode preserves Connect for operator-supplied service endpoints
ok 2 - relay tunnel mode preserves Connect for operator-supplied service endpoints
  ---
  duration_ms: 0.336881
  type: 'test'
  ...
# Subtest: local relay mode uses Connect without a per-instance OAuth App
ok 3 - local relay mode uses Connect without a per-instance OAuth App
  ---
  duration_ms: 0.326872
  type: 'test'
  ...
# Subtest: off-tunnel relay inference rejects callbacks outside the exact loopback allowlist
ok 4 - off-tunnel relay inference rejects callbacks outside the exact loopback allowlist
  ---
  duration_ms: 0.455563
  type: 'test'
  ...
# Subtest: off-tunnel custom relay enrollment does not infer hosted Connect auth
ok 5 - off-tunnel custom relay enrollment does not infer hosted Connect auth
  ---
  duration_ms: 0.230492
  type: 'test'
  ...
# Subtest: literal example OAuth placeholders do not enable GitHub web auth
ok 6 - literal example OAuth placeholders do not enable GitHub web auth
  ---
  duration_ms: 0.178605
  type: 'test'
  ...
# Subtest: explicit custom GitHub web auth remains supported
ok 7 - explicit custom GitHub web auth remains supported
  ---
  duration_ms: 0.32988
  type: 'test'
  ...
# Subtest: explicit custom GitHub web auth wins over relay inference off-tunnel
ok 8 - explicit custom GitHub web auth wins over relay inference off-tunnel
  ---
  duration_ms: 1.013357
  type: 'test'
  ...
# Subtest: Connect authorization URL carries the exact callback and CSRF state
ok 9 - Connect authorization URL carries the exact callback and CSRF state
  ---
  duration_ms: 0.503192
  type: 'test'
  ...
# Subtest: redeems a Connect code server-to-server without exposing the relay token in the body
ok 10 - redeems a Connect code server-to-server without exposing the relay token in the body
  ---
  duration_ms: 30.200127
  type: 'test'
  ...
# Subtest: binds the Connect identity username to the validated token owner
ok 11 - binds the Connect identity username to the validated token owner
  ---
  duration_ms: 1.126289
  type: 'test'
  ...
1..11
# tests 11
# suites 0
# pass 11
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 312.393227

[22/325] packages/api/test/connectAuthCallback.test.ts
TAP version 13
# [2026-08-29 18:44:35.214 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-KeZodH/022-connectAuthCallback.test.ts/propr.test.sqlite"
#     environment: "test"
# Subtest: Connect login preserves a validated redirect_to across Passport session regeneration
ok 1 - Connect login preserves a validated redirect_to across Passport session regeneration
  ---
  duration_ms: 3041.025572
  type: 'test'
  ...
# [2026-08-29 18:44:38.252 +0000] �[32mINFO�[39m: �[36mSQLite database connection closed�[39m
1..1
# tests 1
# suites 0
# pass 1
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 5563.352321

[23/325] packages/api/test/corsValidation.test.ts
TAP version 13
# Subtest: CORS allows the hosted UI origin under proxy mode
ok 1 - CORS allows the hosted UI origin under proxy mode
  ---
  duration_ms: 1.10561
  type: 'test'
  ...
# Subtest: CORS rejects unrelated origins under proxy mode
ok 2 - CORS rejects unrelated origins under proxy mode
  ---
  duration_ms: 0.244187
  type: 'test'
  ...
# Subtest: CORS allows requests with no origin
ok 3 - CORS allows requests with no origin
  ---
  duration_ms: 0.13322
  type: 'test'
  ...
# Subtest: CORS allows only the exact packaged desktop renderer custom origin
ok 4 - CORS allows only the exact packaged desktop renderer custom origin
  ---
  duration_ms: 0.206656
  type: 'test'
  ...
# Subtest: CORS allows HTTP(S) loopback origins for development
ok 5 - CORS allows HTTP(S) loopback origins for development
  ---
  duration_ms: 0.243156
  type: 'test'
  ...
# Subtest: CORS rejects unsafe schemes and non-loopback hosts
ok 6 - CORS rejects unsafe schemes and non-loopback hosts
  ---
  duration_ms: 0.171171
  type: 'test'
  ...
# Subtest: CORS allows COOKIE_DOMAIN subdomains for preview environments
ok 7 - CORS allows COOKIE_DOMAIN subdomains for preview environments
  ---
  duration_ms: 0.224791
  type: 'test'
  ...
# Subtest: CORS preserves http COOKIE_DOMAIN preview compatibility
ok 8 - CORS preserves http COOKIE_DOMAIN preview compatibility
  ---
  duration_ms: 0.155581
  type: 'test'
  ...
# Subtest: CORS validator factory throws on an invalid FRONTEND_URL
ok 9 - CORS validator factory throws on an invalid FRONTEND_URL
  ---
  duration_ms: 0.395992
  type: 'test'
  ...
# Subtest: Express CORS boundary is sanitized in development mode
ok 10 - Express CORS boundary is sanitized in development mode
  ---
  duration_ms: 71.165575
  type: 'test'
  ...
# Subtest: Express CORS boundary is sanitized in production mode
ok 11 - Express CORS boundary is sanitized in production mode
  ---
  duration_ms: 27.583819
  type: 'test'
  ...
# Subtest: Socket.IO applies the shared CORS validator to the packaged desktop renderer
ok 12 - Socket.IO applies the shared CORS validator to the packaged desktop renderer
  ---
  duration_ms: 7.936972
  type: 'test'
  ...
1..12
# tests 12
# suites 0
# pass 12
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 565.91015

[24/325] packages/api/test/demoMode.test.ts
TAP version 13
# Subtest: demoModeReadOnlyMiddleware rejects mutating requests in demo mode
ok 1 - demoModeReadOnlyMiddleware rejects mutating requests in demo mode
  ---
  duration_ms: 6.288537
  type: 'test'
  ...
# Subtest: demoModeReadOnlyMiddleware blocks auth metadata mutations
ok 2 - demoModeReadOnlyMiddleware blocks auth metadata mutations
  ---
  duration_ms: 1.979447
  type: 'test'
  ...
# Subtest: isDemoMode accepts common truthy environment values
ok 3 - isDemoMode accepts common truthy environment values
  ---
  duration_ms: 1.125327
  type: 'test'
  ...
# Subtest: configured demo mode keeps auth and middleware on the same startup value
ok 4 - configured demo mode keeps auth and middleware on the same startup value
  ---
  duration_ms: 1.388119
  type: 'test'
  ...
# Subtest: demo Redis facade covers read-only route Redis usage
ok 5 - demo Redis facade covers read-only route Redis usage
  ---
  duration_ms: 3.490647
  type: 'test'
  ...
# Subtest: demo Express GET routes work with the in-memory Redis facade
ok 6 - demo Express GET routes work with the in-memory Redis facade
  ---
  duration_ms: 62.10188
  type: 'test'
  ...
# Subtest: ensureAuthenticated attaches the synthetic demo user
ok 7 - ensureAuthenticated attaches the synthetic demo user
  ---
  duration_ms: 1.631115
  type: 'test'
  ...
# Subtest: ensureAuthenticated ignores bearer auth and attaches the synthetic demo user in demo mode
ok 8 - ensureAuthenticated ignores bearer auth and attaches the synthetic demo user in demo mode
  ---
  duration_ms: 1.309573
  type: 'test'
  ...
# Subtest: demo repository metadata resolves enabled configured repositories
ok 9 - demo repository metadata resolves enabled configured repositories
  ---
  duration_ms: 13.491336
  type: 'test'
  ...
# Subtest: repository config branch validation documents ProPR-supported branch names
ok 10 - repository config branch validation documents ProPR-supported branch names
  ---
  duration_ms: 2.275011
  type: 'test'
  ...
# [2026-08-29 18:44:41.681 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-KeZodH/024-demoMode.test.ts/propr.test.sqlite"
#     environment: "test"
# Subtest: /api/github/repos returns configured and persisted repositories in demo mode
ok 11 - /api/github/repos returns configured and persisted repositories in demo mode
  ---
  duration_ms: 253.708246
  type: 'test'
  ...
# [2026-08-29 18:44:42.022 +0000] �[32mINFO�[39m: �[36mSuccessfully saved monitored repositories�[39m
#     repos: [
#       {
#         "id": "1",
#         "name": "integry/propr",
#         "enabled": true
#       },
#       {
#         "id": "2",
#         "name": "integry/private",
#         "enabled": true
#       },
#       {
#         "id": "3",
#         "name": "integry/disabled",
#         "enabled": false
#       }
#     ]
# [2026-08-29 18:44:42.024 +0000] �[32mINFO�[39m: �[36mSuccessfully loaded all monitored repositories�[39m
#     total_repos: 3
# Subtest: demo repository metadata resolves persisted repositories without configured allowlists
ok 12 - demo repository metadata resolves persisted repositories without configured allowlists
  ---
  duration_ms: 9.935085
  type: 'test'
  ...
# Subtest: demo repository metadata ignores malformed database repository names
ok 13 - demo repository metadata ignores malformed database repository names
  ---
  duration_ms: 1.515779
  type: 'test'
  ...
# [2026-08-29 18:44:42.034 +0000] �[32mINFO�[39m: �[36mSuccessfully loaded all monitored repositories�[39m
#     total_repos: 0
# Subtest: planner demo reads use the curated database without owner or repository allowlists
ok 14 - planner demo reads use the curated database without owner or repository allowlists
  ---
  duration_ms: 7.977139
  type: 'test'
  ...
# [2026-08-29 18:44:42.043 +0000] �[32mINFO�[39m: �[36mSuccessfully saved monitored repositories�[39m
#     repos: [
#       {
#         "id": "1",
#         "name": "integry/propr",
#         "enabled": true
#       },
#       {
#         "id": "2",
#         "name": "integry/private",
#         "enabled": true
#       }
#     ]
# Subtest: repo todo demo reads use the curated database without owner filters
ok 15 - repo todo demo reads use the curated database without owner filters
  ---
  duration_ms: 7.382996
  type: 'test'
  ...
# Subtest: auth demo-mode metadata endpoint reports startup environment value
ok 16 - auth demo-mode metadata endpoint reports startup environment value
  ---
  duration_ms: 13.887227
  type: 'test'
  ...
1..16
# tests 16
# suites 0
# pass 16
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 2708.079373

[25/325] packages/api/test/desktopAuth.test.ts
TAP version 13
# [desktop-auth] {
#   action: 'pairing_started',
#   actorUserId: undefined,
#   pairingId: 'dpr_g_z5yE3stFMs2EhfduKX0w',
#   tokenId: undefined,
#   clientName: 'Work Laptop'
# }
# [desktop-auth] {
#   action: 'pairing_started',
#   actorUserId: undefined,
#   pairingId: 'dpr_lr7hiiR_5rLIvXJEZeicIQ',
#   tokenId: undefined,
#   clientName: 'Windows desktop'
# }
# [desktop-auth] {
#   action: 'pairing_started',
#   actorUserId: undefined,
#   pairingId: 'dpr_laCcJ-0nAbvkjIwsVs6M8w',
#   tokenId: undefined,
#   clientName: 'MacBook Pro'
# }
# [desktop-auth] {
#   action: 'pairing_approved',
#   actorUserId: '101',
#   pairingId: 'dpr_laCcJ-0nAbvkjIwsVs6M8w',
#   tokenId: undefined,
#   clientName: 'MacBook Pro'
# }
# [desktop-auth] {
#   action: 'token_issued',
#   actorUserId: '101',
#   pairingId: 'dpr_laCcJ-0nAbvkjIwsVs6M8w',
#   tokenId: '5e0021a7-7d91-48a9-899f-f375080a7d87',
#   clientName: 'MacBook Pro'
# }
# [desktop-auth] {
#   action: 'pairing_started',
#   actorUserId: undefined,
#   pairingId: 'dpr_BiJU8oO26S7YrfpMCVVizw',
#   tokenId: undefined,
#   clientName: 'Linux workstation'
# }
# [desktop-auth] {
#   action: 'pairing_approved',
#   actorUserId: '101',
#   pairingId: 'dpr_BiJU8oO26S7YrfpMCVVizw',
#   tokenId: undefined,
#   clientName: 'Linux workstation'
# }
# [desktop-auth] {
#   action: 'pairing_started',
#   actorUserId: undefined,
#   pairingId: 'dpr_pjZ0-4hUquUCYwY31-2TRA',
#   tokenId: undefined,
#   clientName: 'Old laptop'
# }
# [desktop-auth] {
#   action: 'pairing_started',
#   actorUserId: undefined,
#   pairingId: 'dpr_7ZdyTG9CUl4J-yWi0m4LhQ',
#   tokenId: undefined,
#   clientName: 'Desktop app'
# }
# [desktop-auth] {
#   action: 'pairing_approved',
#   actorUserId: '101',
#   pairingId: 'dpr_7ZdyTG9CUl4J-yWi0m4LhQ',
#   tokenId: undefined,
#   clientName: 'Desktop app'
# }
# [desktop-auth] {
#   action: 'token_issued',
#   actorUserId: '101',
#   pairingId: 'dpr_7ZdyTG9CUl4J-yWi0m4LhQ',
#   tokenId: 'c9ae6020-bd9c-4da7-8fa0-fe00c53bd8de',
#   clientName: 'Desktop app'
# }
# [desktop-auth] {
#   action: 'token_revoked',
#   actorUserId: '101',
#   pairingId: undefined,
#   tokenId: 'c9ae6020-bd9c-4da7-8fa0-fe00c53bd8de',
#   clientName: undefined
# }
# [desktop-auth] {
#   action: 'pairing_started',
#   actorUserId: undefined,
#   pairingId: 'dpr_XuQ-nFu9gg3AEm_rviOcYQ',
#   tokenId: undefined,
#   clientName: 'Desktop app'
# }
# [desktop-auth] {
#   action: 'pairing_approved',
#   actorUserId: '101',
#   pairingId: 'dpr_XuQ-nFu9gg3AEm_rviOcYQ',
#   tokenId: undefined,
#   clientName: 'Desktop app'
# }
# [desktop-auth] {
#   action: 'token_issued',
#   actorUserId: '101',
#   pairingId: 'dpr_XuQ-nFu9gg3AEm_rviOcYQ',
#   tokenId: '29a3040a-e991-449c-8f1d-84625b9436cc',
#   clientName: 'Desktop app'
# }
# Subtest: desktop browser pairing
    # Subtest: stores only a device-secret hash and builds a fixed trusted approval URL
    ok 1 - stores only a device-secret hash and builds a fixed trusted approval URL
      ---
      duration_ms: 15.596277
      type: 'test'
      ...
    # Subtest: uses the configured API browser entry and preserves only a managed hosted tunnel selector
    ok 2 - uses the configured API browser entry and preserves only a managed hosted tunnel selector
      ---
      duration_ms: 4.162135
      type: 'test'
      ...
    # Subtest: issues an opaque token once, resolves its owner, and never stores plaintext credentials
    ok 3 - issues an opaque token once, resolves its owner, and never stores plaintext credentials
      ---
      duration_ms: 14.021248
      type: 'test'
      ...
    # Subtest: rejects the wrong secret without revealing pairing state
    ok 4 - rejects the wrong secret without revealing pairing state
      ---
      duration_ms: 5.924835
      type: 'test'
      ...
    # Subtest: expires unapproved pairings and cleans retained expired records
    ok 5 - expires unapproved pairings and cleans retained expired records
      ---
      duration_ms: 6.355432
      type: 'test'
      ...
    # Subtest: rejects unsafe names and non-HTTPS approval origins
    ok 6 - rejects unsafe names and non-HTTPS approval origins
      ---
      duration_ms: 2.862361
      type: 'test'
      ...
    1..6
ok 1 - desktop browser pairing
  ---
  duration_ms: 50.094453
  type: 'suite'
  ...
# Subtest: instance token ownership and revocation
    # Subtest: lists safe metadata only and limits revocation to the owner
    ok 1 - lists safe metadata only and limits revocation to the owner
      ---
      duration_ms: 10.362466
      type: 'test'
      ...
    # Subtest: honors optional token expiry
    ok 2 - honors optional token expiry
      ---
      duration_ms: 6.018812
      type: 'test'
      ...
    # Subtest: REST authentication accepts instance tokens while optional GitHub bearer auth is disabled
    ok 3 - REST authentication accepts instance tokens while optional GitHub bearer auth is disabled
      ---
      duration_ms: 3.340797
      type: 'test'
      ...
    1..3
ok 2 - instance token ownership and revocation
  ---
  duration_ms: 20.158541
  type: 'suite'
  ...
# Subtest: pairing approval request protection
    # Subtest: accepts only the exact HTTPS frontend origin
    ok 1 - accepts only the exact HTTPS frontend origin
      ---
      duration_ms: 2.330905
      type: 'test'
      ...
    # Subtest: requires a browser session even when another authentication method supplied the user
    ok 2 - requires a browser session even when another authentication method supplied the user
      ---
      duration_ms: 1.925716
      type: 'test'
      ...
    1..2
ok 3 - pairing approval request protection
  ---
  duration_ms: 4.375514
  type: 'suite'
  ...
# [2026-08-29 18:44:44.369 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-KeZodH/025-desktopAuth.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 18:44:44.438 +0000] �[32mINFO�[39m: �[36mSQLite database connection closed�[39m
1..3
# tests 11
# suites 3
# pass 11
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 2771.897392

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

[27/325] packages/api/test/instanceAuthorization.test.ts
TAP version 13
# Subtest: instance authorization
    # Subtest: defaults unassigned authenticated users to members on a new installation
    ok 1 - defaults unassigned authenticated users to members on a new installation
      ---
      duration_ms: 11.263774
      type: 'test'
      ...
    # Subtest: refuses startup until a bootstrap or durable administrator exists
    ok 2 - refuses startup until a bootstrap or durable administrator exists
      ---
      duration_ms: 4.652173
      type: 'test'
      ...
    # Subtest: grants full admin permissions to PROPR_ADMIN_USERS
    ok 3 - grants full admin permissions to PROPR_ADMIN_USERS
      ---
      duration_ms: 2.492498
      type: 'test'
      ...
    # Subtest: resolves durable roles by numeric GitHub ID after a username change
    ok 4 - resolves durable roles by numeric GitHub ID after a username change
      ---
      duration_ms: 2.736846
      type: 'test'
      ...
    # Subtest: resolves demo users without installation permissions
    ok 5 - resolves demo users without installation permissions
      ---
      duration_ms: 2.120521
      type: 'test'
      ...
    # Subtest: permission middleware rejects members with a stable error code
    ok 6 - permission middleware rejects members with a stable error code
      ---
      duration_ms: 1.979096
      type: 'test'
      ...
    # Subtest: current-user responses expose capabilities without OAuth credentials
    ok 7 - current-user responses expose capabilities without OAuth credentials
      ---
      duration_ms: 2.342978
      type: 'test'
      ...
    1..7
ok 1 - instance authorization
  ---
  duration_ms: 28.85113
  type: 'suite'
  ...
# Subtest: instance member service
    # Subtest: does not let an unassigned user claim the initial administrator role
    ok 1 - does not let an unassigned user claim the initial administrator role
      ---
      duration_ms: 3.339314
      type: 'test'
      ...
    # Subtest: stores a bootstrap administrator against their numeric GitHub ID
    ok 2 - stores a bootstrap administrator against their numeric GitHub ID
      ---
      duration_ms: 6.587978
      type: 'test'
      ...
    # Subtest: adds explicit members and writes an audit entry
    ok 3 - adds explicit members and writes an audit entry
      ---
      duration_ms: 4.180699
      type: 'test'
      ...
    # Subtest: keeps environment authority separate from durable role assignments
    ok 4 - keeps environment authority separate from durable role assignments
      ---
      duration_ms: 2.526882
      type: 'test'
      ...
    # Subtest: prevents removing the last durable administrator
    ok 5 - prevents removing the last durable administrator
      ---
      duration_ms: 10.999877
      type: 'test'
      ...
    # Subtest: allows role changes once another administrator exists
    ok 6 - allows role changes once another administrator exists
      ---
      duration_ms: 5.614584
      type: 'test'
      ...
    1..6
ok 2 - instance member service
  ---
  duration_ms: 33.659593
  type: 'suite'
  ...
# Subtest: instance catalog
    # Subtest: returns operational agent and repository fields without installation internals
    ok 1 - returns operational agent and repository fields without installation internals
      ---
      duration_ms: 2.004364
      type: 'test'
      ...
    # Subtest: projects indexing status only for enabled repository and branch entries
    ok 2 - projects indexing status only for enabled repository and branch entries
      ---
      duration_ms: 1.656622
      type: 'test'
      ...
    1..2
ok 3 - instance catalog
  ---
  duration_ms: 3.773387
  type: 'suite'
  ...
# Subtest: instance admin routes
    # Subtest: resolves a GitHub username and creates one durable member assignment
    ok 1 - resolves a GitHub username and creates one durable member assignment
      ---
      duration_ms: 2.618894
      type: 'test'
      ...
    # Subtest: rejects a non-numeric audit limit with a 400
    ok 2 - rejects a non-numeric audit limit with a 400
      ---
      duration_ms: 1.35643
      type: 'test'
      ...
    # Subtest: rejects GitHub usernames with trailing or consecutive hyphens
    ok 3 - rejects GitHub usernames with trailing or consecutive hyphens
      ---
      duration_ms: 1.654699
      type: 'test'
      ...
    1..3
ok 4 - instance admin routes
  ---
  duration_ms: 5.743085
  type: 'suite'
  ...
# [2026-08-29 18:44:47.918 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-KeZodH/027-instanceAuthorization.test.ts/propr.test.sqlite"
#     environment: "test"
# [2026-08-29 18:44:47.984 +0000] �[32mINFO�[39m: �[36mSQLite database connection closed�[39m
1..4
# tests 18
# suites 4
# pass 18
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 2111.888929

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

[29/325] packages/api/test/liveDetailsAntigravity.test.ts
TAP version 13
# Failed to read GitHub App private key: ENOENT: no such file or directory, open '/tmp/missing-key.pem'
# Ensure GH_PRIVATE_KEY_PATH is set correctly in your .env file and points to a valid private key file.
# Subtest: stored output detection recognizes Antigravity stream JSON
ok 1 - stored output detection recognizes Antigravity stream JSON
  ---
  duration_ms: 2.254712
  type: 'test'
  ...
# Subtest: stored output detection keeps Codex message JSONL classified as Codex
ok 2 - stored output detection keeps Codex message JSONL classified as Codex
  ---
  duration_ms: 1.407997
  type: 'test'
  ...
# Subtest: stored output detection keeps Codex result JSONL classified as Codex
ok 3 - stored output detection keeps Codex result JSONL classified as Codex
  ---
  duration_ms: 0.870861
  type: 'test'
  ...
# Subtest: stored output detection keeps Codex tool result JSONL classified as Codex
ok 4 - stored output detection keeps Codex tool result JSONL classified as Codex
  ---
  duration_ms: 0.207969
  type: 'test'
  ...
# Subtest: stored output detection does not treat generic init JSONL as Antigravity
ok 5 - stored output detection does not treat generic init JSONL as Antigravity
  ---
  duration_ms: 0.331872
  type: 'test'
  ...
# Subtest: stored output detection recognizes Antigravity JSONL from result stats when init is missing
ok 6 - stored output detection recognizes Antigravity JSONL from result stats when init is missing
  ---
  duration_ms: 0.337232
  type: 'test'
  ...
# Subtest: stored output detection keeps generic result token stats classified as Codex
ok 7 - stored output detection keeps generic result token stats classified as Codex
  ---
  duration_ms: 0.202208
  type: 'test'
  ...
# Subtest: stored output detection recognizes truncated Antigravity result JSON
ok 8 - stored output detection recognizes truncated Antigravity result JSON
  ---
  duration_ms: 0.204574
  type: 'test'
  ...
# Subtest: stored output detection recognizes Antigravity message JSON with model metadata
ok 9 - stored output detection recognizes Antigravity message JSON with model metadata
  ---
  duration_ms: 0.362529
  type: 'test'
  ...
# Subtest: stored output parsing renders only Antigravity analysis events through live details
ok 10 - stored output parsing renders only Antigravity analysis events through live details
  ---
  duration_ms: 65.89089
  type: 'test'
  ...
# Subtest: Antigravity 1.1.12 stream text remains visible through live details
ok 11 - Antigravity 1.1.12 stream text remains visible through live details
  ---
  duration_ms: 1.915516
  type: 'test'
  ...
# Subtest: stored output detection and live-details rendering consume Antigravity stream arrays
ok 12 - stored output detection and live-details rendering consume Antigravity stream arrays
  ---
  duration_ms: 1.436841
  type: 'test'
  ...
# Subtest: Antigravity cache-only stream usage remains visible through live details
ok 13 - Antigravity cache-only stream usage remains visible through live details
  ---
  duration_ms: 0.783788
  type: 'test'
  ...
# Subtest: stored output parsing filters Antigravity transcript tool items
ok 14 - stored output parsing filters Antigravity transcript tool items
  ---
  duration_ms: 1.047792
  type: 'test'
  ...
# Subtest: Claude-format Antigravity conversation files render only planner analysis
ok 15 - Claude-format Antigravity conversation files render only planner analysis
  ---
  duration_ms: 1.343466
  type: 'test'
  ...
# Subtest: execution detail fallback filters Antigravity transcript tool rows
ok 16 - execution detail fallback filters Antigravity transcript tool rows
  ---
  duration_ms: 0.898462
  type: 'test'
  ...
# [2026-08-29 18:44:50.577 +0000] �[32mINFO�[39m: �[36mSQLite database connection established successfully�[39m
#     filename: "/tmp/propr-test-suite-KeZodH/029-liveDetailsAntigravity.test.ts/propr.test.sqlite"
#     environment: "test"
1..16
# tests 16
# suites 0
# pass 16
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 2128.085746

[30/325] packages/api/test/liveDetailsCodexDbFallback.test.ts
TAP version 13
# [live-details] jobId: integry-propr-1915-codex, taskId: integry-propr-1915-codex
# [live-details] Trying Redis fallback
# [live-details] stateKey: worker:state:integry-propr-1915-codex, hasData: false
# [live-details] No state data found in Redis
# [live-details] Fetching sessionId from SQLite for taskId: integry-propr-1915-codex
# [live-details] Found sessionId in SQLite: codex-db-session
# [live-details] Using sessionId: c

...(truncated)

View Workflow

…ng or merging.

Implemented the runtime download hardening without committing or merging.

Key changes:

- Added streaming limits: 512 KiB manifest, 1 KiB signature, 1 MiB feed, and 1 GiB artifact maximum.
- Added 30-second metadata and 10-minute artifact abort timeouts.
- Rejects oversized `Content-Length`, chunked overflow, signed-size mismatches, and cross-origin final redirects.
- Streams artifacts directly to private temporary files with incremental SHA-256 verification.
- Cleans temporary files/directories on download, hash, and signer failures.
- Preserved check-only behavior, Ed25519 verification, exact feed/artifact hashes, and native signer validation.

Files changed:

- [main.ts](/tmp/git-processor/worktrees/integry/propr/pr-1972-followup-2026-08-29T18-46-20/apps/desktop/src/main.ts:252)
- [signed-updates.ts](/tmp/git-processor/worktrees/integry/propr/pr-1972-followup-2026-08-29T18-46-20/apps/desktop/src/signed-updates.ts:49)
- [signed-updates.test.ts](/tmp/git-processor/worktrees/integry/propr/pr-1972-followup-2026-08-29T18-46-20/apps/desktop/src/signed-updates.test.ts:264)

Validation passed:

- Desktop typecheck
- All 52 desktop tests
- Runtime and packaging audits: 0 vulnerabilities
- Linux x64 native packaging and fuse/executable inspection
- `git diff --check`

The full six-target native matrix cannot run locally on this Linux x64 host; it must rerun in CI after the follow-up is committed and pushed.

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

propr-dev Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor Author

Applied the requested follow-up changes in commit b36984e

Summary of Changes

Fixed the CI-only shortcut race in DesktopExperience.tsx. The keyboard listener now uses useLayoutEffect, ensuring it is current before the connected UI becomes interactive.

Validation passed:

  • Focused tests: 21/21
  • Full UI suite: 496/496 across 69 files
  • UI typecheck
  • ESLint
  • Whitespace check

No commit was created. The file appears untracked because it originates from the newer target branch; merging that target was blocked by root-owned Git metadata (ORIG_HEAD.lock: Permission denied). Its content differs from the target version by exactly the two intended lines.


🤖 Implementation Details

  • Model: GPT-5.6 Sol (high)
  • Requested By: @github-actions[bot]
  • Time: 11m 4s
  • Tokens: 5,605,749 (5,587,140 in / 18,609 out)
  • Cost: $4.05

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

…/tmp/git-processor/worktrees/integry/propr/pr-1972-followup-2026-08-29T18-56-24/propr-ui/src/desktop/DesktopExperience.tsx). The keyboard listener now uses `useLayoutEffect`, ensuring it is current before the connected UI becomes interactive.

Fixed the CI-only shortcut race in [DesktopExperience.tsx](/tmp/git-processor/worktrees/integry/propr/pr-1972-followup-2026-08-29T18-56-24/propr-ui/src/desktop/DesktopExperience.tsx). The keyboard listener now uses `useLayoutEffect`, ensuring it is current before the connected UI becomes interactive.

Validation passed:

- Focused tests: 21/21
- Full UI suite: 496/496 across 69 files
- UI typecheck
- ESLint
- Whitespace check

No commit was created. The file appears untracked because it originates from the newer target branch; merging that target was blocked by root-owned Git metadata (`ORIG_HEAD.lock: Permission denied`). Its content differs from the target version by exactly the two intended lines.

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

integry commented Aug 29, 2026

Copy link
Copy Markdown
Owner

/merge

Merge the finalized runtime base at d5465a3 into this packaging head. Preserve the signed-update streaming limits, trusted tag-only signing boundary, native signer verification, and CI shortcut-race fix. Resolve any CSS/deep-link/package-lock conflicts in favor of the reviewed runtime behavior plus this PR's packaging delta. Re-run desktop tests/typecheck/package and do not merge; the native six-target matrix and final Ultrafix review follow on the synced head.

@propr-dev

propr-dev Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor Author

🔀 Resolved merge conflicts from 1952-epic-electron-runtime-app into 1957/gpt-5.6-sol-add-cross-platform-deskto-20260829-1735-0gu in commit 872cd99

Conflicting Files

  • apps/desktop/README.md
  • apps/desktop/scripts/smoke-packaged.mjs
  • apps/desktop/vite.renderer.config.ts
  • propr-ui/src/desktop/DesktopExperience.tsx

Resolution Summary

Resolved all four conflicted files; no conflict markers remain anywhere in the worktree.

  • apps/desktop/README.md: Both branches updated packaged-smoke documentation. Combined the PR’s cross-platform inspection/release details with the incoming branch’s 1280×820 layout validation and sandbox checks.

  • smoke-packaged.mjs: The PR added cross-platform executable discovery and --inspect-only; the incoming branch added renderer layout parsing/assertions and Linux-specific messaging. Preserved cross-platform behavior while incorporating all layout assertions and generalized the success message.

  • vite.renderer.config.ts: The PR added release-aware desktop version resolution while the incoming branch added the propr-ui PostCSS root and compiled-CSS validation. Retained both because they serve independent packaging requirements.

  • DesktopExperience.tsx: This was an add/add conflict whose remaining difference was useLayoutEffect versus useEffect for keyboard listener registration. Kept the PR’s useLayoutEffect and matching import to preserve its synchronous registration behavior; the incoming component changes remain compatible.

Validation passed:

  • Repository-wide conflict-marker scan: clean.
  • node --check apps/desktop/scripts/smoke-packaged.mjs: passed.
  • npm run desktop:typecheck: passed for desktop and propr-ui.
  • Focused git diff --check: passed.
  • No commit was created.

One environment limitation remains: Git could not mark the files resolved because the linked worktree index directory is owned by root, causing git add to fail with index.lock: Permission denied. The resolved file contents are saved, but the committing system must stage them with sufficient permissions.


🤖 Resolution Details

  • Model: gpt-5.6-sol
  • Time: 3m 42s

View Task Execution


System-triggered merge conflict resolution

…7/gpt-5.6-sol-add-cross-platform-deskto-20260829-1735-0gu

Automatically resolved merge conflicts after merging target branch into PR branch.
Resolved conflicts in:
- apps/desktop/README.md
- apps/desktop/scripts/smoke-packaged.mjs
- apps/desktop/vite.renderer.config.ts
- propr-ui/src/desktop/DesktopExperience.tsx

PR: #1972
Model: gpt-5.6-sol
@integry

integry commented Aug 29, 2026

Copy link
Copy Markdown
Owner

/merge

Synchronize the final hardened runtime head into this packaging branch now. Preserve the exact canonical deep-link fix, deterministic UI test, and the 32-byte VAPID test-fixture stabilization, together with all signed-update, native packaging, smoke, and release hardening already on this PR. Resolve semantically; do not remove any packaging security checks or native x64/arm64 jobs. Run git diff --check, desktop tests/typecheck, production package, and the full native matrix on the published head.

@propr-dev

propr-dev Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor Author

🔀 Auto-merged 1952-epic-electron-runtime-app into 1957/gpt-5.6-sol-add-cross-platform-deskto-20260829-1735-0gu (clean merge) in commit f94a38d

No conflicts were found — the merge was verified by an AI agent.


🤖 Verification Details

  • Model: gpt-5.6-sol
  • Time: 1m 55s

View Task Execution


System-triggered merge conflict resolution

…into 1957/gpt-5.6-sol-add-cross-platform-deskto-20260829-1735-0gu
@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 partial review covers the supplied diff; the tail of release-artifacts.mjs and other ranges were explicitly omitted. The PR builds a strong cross-platform release pipeline, but it is not ready to merge until the macOS update verifier validates the canonical application rather than an arbitrary signed bundle.

Complete native matrix — Unsigned validation covers Linux, macOS, and Windows on both x64 and arm64, and all six current package checks pass.

Fail-closed release publication — Preflight provenance checks, immutable-tag verification, draft publication, exact asset validation, and tag rechecks substantially reduce release-race and partial-upload risks.

Bounded signed downloads — Update metadata and artifacts are size-limited, timeout-controlled, SHA-256-bound, and verified against an embedded Ed25519 key.

Merge blockers

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

F12: 🔴 macOS verifier accepts decoy bundles

  • Required behavior: Native signing-identity verification must authenticate the canonical application contained in the selected update artifact; it must not accept an unrelated signed bundle as evidence for an unsigned or malicious canonical application.
  • Evidence: apps/desktop/src/signed-updates.ts (verifyNativeUpdateSigner) — after extracting the ZIP, the verifier runs find ... -name '*.app' -print -quit and verifies whichever application directory appears first. A ZIP containing a legitimately signed decoy .app alongside a malicious propr-desktop.app can therefore pass the Team ID and designated-requirement checks.
  • Minimum fix: Verify the exact real directory extracted/propr-desktop.app, reject a symlink or missing canonical bundle, and reject an ambiguous archive layout before invoking codesign. Reusing the canonical DMG/application-layout validation rules would also satisfy this requirement.

Suggestions

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

S1: 🟢 Test Squirrel startup integration

main.ts assigns false to hasSingleInstanceLock for handled Squirrel events, so the later if (!hasSingleInstanceLock) app.quit() exits immediately even though handleSquirrelStartupEvent schedules a delayed exit. The detached maintenance process should survive, making this non-blocking, but an integration test would clarify and preserve the intended exit timing.

S2: 🟢 Cross-check Squirrel SHA-1

verifyFeedReferencesArtifact checks a Windows RELEASES record’s filename and size but not its recorded SHA-1. The signed manifest independently SHA-256-binds both files and production staging validates RELEASES, so this is not an integrity blocker; checking the SHA-1 at runtime would nevertheless reject internally inconsistent feeds before reporting an update as available.

Score

The release architecture and test coverage are unusually thorough, and all current validation checks pass. One localized but meaningful macOS signing-verification bypass remains, so the PR is not yet merge-ready.

Score: 6/10


🤖 Review Details

  • Model: GPT-5.6 Sol
  • Time: 4m 48s
  • Tokens: 88,216 (76,768 in / 11,448 out)
  • Cost: $0.68
  • Review scope: Partial — PR diff files or ranges were unavailable from GitHub or omitted by the configured review context limit.

View Task

Files omitted from review diff

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

  • apps/desktop/scripts/release-architecture.mjs
  • package-lock.json

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

🤖 Review by ProPR

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

integry commented Aug 30, 2026

Copy link
Copy Markdown
Owner

/fix F12

@propr-dev

propr-dev Bot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor Author

Applied the requested follow-up changes in commit f4cfdcd

Addressed 1 AI review comment (IDs: 5466397683)

Summary of Changes

Implemented F12 only.

  • macOS signer verification now targets exactly extracted/propr-desktop.app.
  • Rejects missing or symlinked canonical bundles.
  • Rejects additional top-level payloads, including signed decoy apps, before codesign.
  • Added regressions covering valid, decoy, symlink, and missing layouts.

Changed:

Verification passed:

  • Desktop tests: 118 passed, 1 skipped
  • Desktop typecheck
  • git diff --check

No commit was created.


🤖 Implementation Details

  • Model: GPT-5.6 Sol (high)
  • Requested By: @integry
  • Time: 3m 38s
  • Tokens: 574,915 (569,525 in / 5,390 out)
  • Cost: $0.68
  • Subscription usage: Weekly +1%

Undo ChangesView Task Execution


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

🤖 Generated by ProPR
Processing comment ID: 5466411650✓

@integry integry removed the AI label Aug 30, 2026
Implemented F12 only.

- macOS signer verification now targets exactly `extracted/propr-desktop.app`.
- Rejects missing or symlinked canonical bundles.
- Rejects additional top-level payloads, including signed decoy apps, before `codesign`.
- Added regressions covering valid, decoy, symlink, and missing layouts.

Changed:

- [signed-updates.ts](/tmp/git-processor/worktrees/integry/propr/pr-1972-followup-2026-08-30T03-13-19/apps/desktop/src/signed-updates.ts:444)
- [signed-updates.test.ts](/tmp/git-processor/worktrees/integry/propr/pr-1972-followup-2026-08-30T03-13-19/apps/desktop/src/signed-updates.test.ts:99)

Verification passed:

- Desktop tests: 118 passed, 1 skipped
- Desktop typecheck
- `git diff --check`

No commit was created.

PR: #1972
Comment by: @integry (ID: 5466411650)
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 substantially implements the cross-platform desktop release pipeline, but it needs a small production-path correction before merge. The visible validation checks pass; however, the trusted release preflight is skipped on pull requests, and its GitHub App token lacks the permission needed by the environment-policy checks. This review is partial because the supplied diff explicitly omits ranges.

Complete native matrix — The workflow covers Linux, macOS, and Windows on both x64 and arm64, verifies runner architecture, and stages each target independently.

Strong release integrity controls — Changed code binds artifacts to checksums, architecture evidence, native signing identities, immutable tags, and a verified draft-before-publication flow.

Focused verification — The PR adds substantial release, update-security, packaging-layout, and publication tests, with all 18 applicable current-head checks passing.

Merge blockers

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

F13: 🔴 Preflight token cannot read environments

  • Required behavior: The trusted release workflow must successfully verify the protected desktop-release-preflight and desktop-release environments before exposing signing credentials or publishing a release.
  • Evidence: .github/workflows/desktop-release-guard.yml:274 requests only administration: read and contents: read for the short-lived App token, while apps/desktop/scripts/release-preflight.mjs:169 uses that token to query environment definitions and deployment-branch policies, which require repository Environments read permission.
  • Minimum fix: Grant the App and generated token permission-environments: read, retain the existing read-only permissions, and update the workflow assertion test to require this third permission.

Suggestions

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

S1: 🟢 Cross-check Squirrel SHA-1

signed-updates.ts currently establishes that a signed RELEASES record names the downloaded NUPKG and reports its size, while the release builder separately validates the record’s SHA-1. Comparing the runtime record’s SHA-1 with the downloaded artifact as well would provide useful defense against future feed-generation changes. This is optional because the artifact is already bound by signed SHA-256 metadata and the current channel remains check-only.

Score

The implementation is broad, security-conscious, and well tested, but the missing token permission prevents the newly introduced production release path from completing. The required correction is localized.

Score: 6/10


🤖 Review Details

  • Model: GPT-5.6 Sol
  • Time: 2m 35s
  • Tokens: 82,941 (76,697 in / 6,244 out)
  • Cost: $0.57
  • Review scope: Partial — PR diff files or ranges were unavailable from GitHub or omitted by the configured review context limit.

View Task

Files omitted from review diff

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

  • apps/desktop/scripts/release-architecture.mjs
  • package-lock.json

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

🤖 Review by ProPR

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

integry commented Aug 30, 2026

Copy link
Copy Markdown
Owner

!propr Fix review blocker F13 on exact head f4cfdcd, but use the correct current GitHub App permission. Do not merge or sync runtime. Preserve the F12 canonical macOS bundle fix and every existing release guard.

The trusted preflight token calls GET /repos/{owner}/{repo}/environments/{environment_name} and GET /repos/{owner}/{repo}/environments/{environment_name}/deployment-branch-policies. Current official GitHub REST documentation for both fine-grained installation-token endpoints requires repository Actions: read. Add permission-actions: read to the pinned actions/create-github-app-token step alongside the existing administration:read and contents:read. Do not add a nonexistent or unrelated permission-environments input and do not broaden write permissions.

Update release-workflow.test exact least-privilege assertions so the permitted inputs are precisely actions:read, administration:read, and contents:read, while every write permission and unexpected permission remains rejected. Add a focused fixture proving the preflight token configuration covers both environment API calls, and keep token persistence disabled and output redacted.

Run release-workflow/release-preflight/release-artifacts/signed-updates tests, clean npm ci then all six native unsigned package jobs and aggregate checksum finalization, Validate Changes including actionlint, configured Full Suite, desktop typecheck/package, and git diff --check. Keep the head scoped to F13 and do not claim environment-policy access without the exact Actions read input.

@propr-dev

propr-dev Bot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor Author

Applied the requested follow-up changes in commit fe26f93

Summary of Changes

Implemented F13 on exact head f4cfdcd717be70804f435ef3955bd735085bdc57 without committing, merging, or syncing.

  • Added permission-actions: read beside administration/contents read in desktop-release-guard.yml.
  • Updated exact least-privilege and endpoint fixtures in release-workflow.test.ts.
  • Preserved credential non-persistence, single redacted token consumption, all write-permission rejection, and rejection of permission-environments.
  • Confirmed both endpoints require Actions read in GitHub’s official environment and deployment branch policy documentation.

Passed:

  • Clean npm ci
  • Focused release/preflight/artifact/signed-update tests
  • Desktop tests, typecheck, and package
  • Actionlint 1.7.12
  • Validate Changes’ non-container stages
  • Full Suite: all 327 files plus workspace suites
  • git diff --check

The six native matrix jobs and real aggregate artifact finalization require their Linux ARM64, macOS, and Windows CI runners and could not run on this Linux x64 host. The configured Docker-based actionlint/Redis steps were unavailable; equivalent official binaries were used locally. Only the two intended F13 files are modified.


🤖 Implementation Details

  • Model: GPT-5.6 Sol (high)
  • Requested By: @integry
  • Time: 18m 24s
  • Tokens: 11,343,974 (11,326,540 in / 17,434 out)
  • Cost: $7.18
  • 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: 5466511035✓

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

Implemented F13 on exact head `f4cfdcd717be70804f435ef3955bd735085bdc57` without committing, merging, or syncing.

- Added `permission-actions: read` beside administration/contents read in [desktop-release-guard.yml](/tmp/git-processor/worktrees/integry/propr/pr-1972-followup-2026-08-30T03-38-36/.github/workflows/desktop-release-guard.yml:268).
- Updated exact least-privilege and endpoint fixtures in [release-workflow.test.ts](/tmp/git-processor/worktrees/integry/propr/pr-1972-followup-2026-08-30T03-38-36/apps/desktop/src/release-workflow.test.ts:20).
- Preserved credential non-persistence, single redacted token consumption, all write-permission rejection, and rejection of `permission-environments`.
- Confirmed both endpoints require Actions read in GitHub’s official [environment](https://docs.github.com/en/rest/deployments/environments) and [deployment branch policy](https://docs.github.com/en/rest/deployments/branch-policies) documentation.

Passed:

- Clean `npm ci`
- Focused release/preflight/artifact/signed-update tests
- Desktop tests, typecheck, and package
- Actionlint 1.7.12
- Validate Changes’ non-container stages
- Full Suite: all 327 files plus workspace suites
- `git diff --check`

The six native matrix jobs and real aggregate artifact finalization require their Linux ARM64, macOS, and Windows CI runners and could not run on this Linux x64 host. The configured Docker-based actionlint/Redis steps were unavailable; equivalent official binaries were used locally. Only the two intended F13 files are modified.

PR: #1972
Comment by: @integry (ID: 5466511035)
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 builds a comprehensive cross-platform desktop release pipeline with native packaging, artifact validation, signing, signed update checks, and guarded publication. No merge blocker is evident in the supplied portion, but the diff explicitly omits core ranges of the artifact-processing implementation, so merge readiness cannot be fully confirmed from this partial review.

Complete native matrix — CI validates Linux, macOS, and Windows on both x64 and arm64, and all corresponding current-head validation checks passed.

Fail-closed update verificationsigned-updates.ts binds metadata to Ed25519 signatures, HTTPS origins, bounded downloads, exact hashes, target architecture, and native signer evidence.

Defensive release publicationrelease-publish.mjs uses a draft release, verifies uploaded asset contents, paginates asset listings, and rechecks tag identity before publication.

Merge blockers

No merge blockers.

Suggestions

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

S1: 🟢 Verify Squirrel SHA-1

apps/desktop/src/signed-updates.ts checks that RELEASES contains the expected filename and size but does not compare its SHA-1 record with the downloaded NUPKG. The release staging code already performs that comparison, so adding the runtime check would be optional defense in depth against inconsistent signed metadata.

S2: 🟢 Reduce repeated update downloads

checkForSignedUpdates downloads and verifies the complete update artifact whenever a newer release is detected, then deletes it because installation is intentionally not implemented. Caching verified availability or deferring the full download until installation becomes possible would reduce startup bandwidth and temporary disk use without changing current correctness.

S3: 🟢 Rehearse the protected release path

All unsigned native packaging checks passed, while the signing, notarization, metadata-signing, and publication jobs were appropriately skipped on this pull request. A controlled tag rehearsal before the first production release would help validate environment policies and external credentials that cannot be exercised by PR checks.

Score

The visible implementation is unusually thorough and its current validation matrix is green, but omitted artifact-finalization ranges and unexercised production-only jobs leave a material verification gap.

Score: 7/10


🤖 Review Details

  • Model: GPT-5.6 Sol
  • Time: 4m 27s
  • Tokens: 87,272 (76,981 in / 10,291 out)
  • Cost: $0.65
  • Review scope: Partial — PR diff files or ranges were unavailable from GitHub or omitted by the configured review context limit.

View Task

Files omitted from review diff

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

  • apps/desktop/scripts/release-architecture.mjs
  • package-lock.json

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

🤖 Review by ProPR

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

integry commented Aug 30, 2026

Copy link
Copy Markdown
Owner

/fix S1 S2

@propr-dev

propr-dev Bot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor Author

ℹ️ No authorized review findings were selected.

No files were changed because this /fix command did not select an actionable F# finding. Suggestions require a separate ordinary follow-up request.

View Task Execution


Processing comment ID: 5466666825✓

@integry

integry commented Aug 30, 2026

Copy link
Copy Markdown
Owner

!propr Implement the two actionable blocker-free review suggestions on exact head fe26f93 to reach the 9/10 quality gate. The slash fixer could not select advisory S1/S2. Do not merge or sync runtime. Preserve F12/F13, private snapshot packaging, signer/team/notarization/pin checks, least-privilege release token, and all native gates.

  1. Add runtime defense-in-depth cross-checking for Squirrel RELEASES SHA-1. Strictly parse the selected canonical RELEASES entry with bounded bytes/lines/tokens, exact version and canonical package filename, exact size, and exactly one lowercase or canonicalized 40-hex SHA-1. Reject duplicates, ambiguity, traversal, alternate case/name, malformed or unsupported algorithms. Hash the same held no-follow regular package descriptor already authenticated by the signed SHA-256 update metadata and require both the signed SHA-256 and RELEASES SHA-1/size to match before any handoff to Squirrel. Revalidate held identity/size/hash before use and keep fixed redacted errors. Add adversarial mismatched SHA-1, duplicate entry, wrong filename/version/size, traversal, case collision, swap, and valid Windows Squirrel fixtures. SHA-1 is compatibility binding only, never the trust root.

  2. Avoid downloading the full verified update twice between check-only and apply. After a successful check that already downloaded and fully authenticated the artifact, retain it only in an owner-only bounded cache keyed by exact normalized origin/channel/version/signed manifest digest and artifact SHA-256. Store through O_EXCL/no-follow private temp, fsync/atomic publish, require real owner-only file/dir with nlink 1 and no reparse/symlink, and use a short fixed expiry plus one artifact/strict byte quota. On apply, reopen no-follow, verify complete key, size, SHA-256, platform signer/notarization and Squirrel SHA-1 through held bytes, then consume without network. Any missing, expired, malformed, permission-broad, swapped, partial, or mismatched cache entry is deleted and safely redownloaded/reverified; it must never weaken rollback/version/channel or origin binding. Concurrent checks/applies must serialize, cancellation must clean partials, startup removes stale entries, and logs expose no private path/token.

  3. Test request counts proving check then apply performs one artifact download, while expiry/corruption/origin/channel/version change performs exactly one safe redownload. Test crash/partial cache, symlink/hardlink/reparse, ABA swap, concurrent calls, quota/cleanup, and check-only no-install behavior across Windows/macOS/Linux paths.

Run focused signed-updates/update-manager/Squirrel/release-artifact/workflow tests, clean npm ci, all six native unsigned packaging jobs plus aggregate 16-artifact checksum finalization, desktop typecheck/package, Validate Changes including actionlint, configured Full Suite, and git diff --check. Keep production sign/publish jobs gated to protected release environments.

@propr-dev

propr-dev Bot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor Author

Applied the requested follow-up changes in commit a047008

Summary of Changes

Implemented on exact head fe26f938c7ea8c2ae276f0abbb5079fdede8b18b without committing, merging, syncing, or changing release workflows.

Key changes:

  • Strict bounded Squirrel RELEASES parsing and canonical filename/version/size/SHA-1 validation.
  • SHA-1 and signed SHA-256 computed from the same held no-follow descriptor before signer verification and handoff.
  • Explicit applySignedUpdate path; startup remains check-only.
  • Owner-private, atomic, fsynced, ten-minute single-artifact cache keyed to origin/channel/version/manifest digest/artifact SHA-256.
  • Cache reauthentication, signer/notarization checks, serialization, expiry, corruption and partial cleanup, and safe redownload.
  • Redacted runtime update failures without paths or tokens.
  • Adversarial RELEASES, cache, request-count, concurrency, ABA, link, corruption, cancellation, Windows/macOS/Linux behavior tests.

Changed files:

Passed:

  • Clean npm ci
  • Focused signed-update tests: 37/37
  • Desktop tests: 139 passed, 1 platform skip
  • Desktop and UI typecheck
  • Desktop production package and fuse inspection
  • Release-artifact, Squirrel, and workflow tests
  • Fast unit suite: 278/278
  • Release verification and CLI package validation
  • git diff --check

The six native runner jobs, aggregate native finalization, actionlint container, and Redis-backed Full Suite require CI/Docker/native environments unavailable on this host. All release workflow gates and protected production sign/publish jobs remain untouched.


🤖 Implementation Details

  • Model: GPT-5.6 Sol (high)
  • Requested By: @integry
  • Time: 18m 28s
  • Tokens: 6,329,145 (6,291,720 in / 37,425 out)
  • Cost: $5.06

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

@integry integry removed the AI label Aug 30, 2026
…ede8b18b` without committing, merging, syncing, or changing release workflows.

Implemented on exact head `fe26f938c7ea8c2ae276f0abbb5079fdede8b18b` without committing, merging, syncing, or changing release workflows.

Key changes:

- Strict bounded Squirrel `RELEASES` parsing and canonical filename/version/size/SHA-1 validation.
- SHA-1 and signed SHA-256 computed from the same held no-follow descriptor before signer verification and handoff.
- Explicit `applySignedUpdate` path; startup remains check-only.
- Owner-private, atomic, fsynced, ten-minute single-artifact cache keyed to origin/channel/version/manifest digest/artifact SHA-256.
- Cache reauthentication, signer/notarization checks, serialization, expiry, corruption and partial cleanup, and safe redownload.
- Redacted runtime update failures without paths or tokens.
- Adversarial RELEASES, cache, request-count, concurrency, ABA, link, corruption, cancellation, Windows/macOS/Linux behavior tests.

Changed files:

- [signed-updates.ts](/tmp/git-processor/worktrees/integry/propr/pr-1972-followup-2026-08-30T04-21-41/apps/desktop/src/signed-updates.ts)
- [signed-updates.test.ts](/tmp/git-processor/worktrees/integry/propr/pr-1972-followup-2026-08-30T04-21-41/apps/desktop/src/signed-updates.test.ts)
- [main.ts](/tmp/git-processor/worktrees/integry/propr/pr-1972-followup-2026-08-30T04-21-41/apps/desktop/src/main.ts)

Passed:

- Clean `npm ci`
- Focused signed-update tests: 37/37
- Desktop tests: 139 passed, 1 platform skip
- Desktop and UI typecheck
- Desktop production package and fuse inspection
- Release-artifact, Squirrel, and workflow tests
- Fast unit suite: 278/278
- Release verification and CLI package validation
- `git diff --check`

The six native runner jobs, aggregate native finalization, actionlint container, and Redis-backed Full Suite require CI/Docker/native environments unavailable on this host. All release workflow gates and protected production sign/publish jobs remain untouched.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant