Skip to content

fix(platform): enforce GitHub authority in the schema, require platform 0.23.0 in the CLI - #356

Merged
Brooooooklyn merged 5 commits into
mainfrom
fix/github-authority-constraints
Oct 4, 2026
Merged

Brooooooklyn merged 5 commits into
mainfrom
fix/github-authority-constraints

Conversation

@Brooooooklyn

@Brooooooklyn Brooooooklyn commented Oct 1, 2026 •

Copy link
Copy Markdown
Member

Why

#355 (released in v0.22.0) moved the data to the current GitHub authority model and removed the code for the old shapes. This PR makes the schema enforce that model, so those shapes can no longer come back.

Migration 0057

cleanup (idempotent; 0 rows expected after 0052)
  one statement: DELETE connections without owner/admin authority RETURNING project_id
                 → cancel those projects' active builds   (same lock order as runtime: connection → builds)
  UPDATE github_installations SET user_id = NULL WHERE account_type = 'Organization'
DDL
  github_connections.repository_id          NOT NULL
  github_connections.authorized_by_user_id  NOT NULL
  authorized_by_user_id FK                  ON DELETE SET NULL → NO ACTION

FK: NO ACTION. deleteUser is the only code that deletes a users row, and it removes every connection the user authorized first (cancelling builds). With CASCADE, a connection that escaped that cleanup would disappear without cancelling its builds. With NO ACTION, that case fails loudly.

Code

  • Upsert fix: upsertGitHubInstallation clears user_id when the incoming account type is Organization (a personal account converted into an organization).
  • Removed: runtime null handling that the NOT NULL columns make dead: nullable repositoryId types, the null-repository fail-closed branches, and the ?? 'unknown' authorizer fallback.

Removed compatibility paths

Each path was checked against current code and every rollback target (0.20.3 – 0.24.0). No writer uses it.

Path Change
Membership without a GitHub account id Join codes and memberships take a string account id. /github/join/complete rejects a join code without one. The column stays nullable: the schema-compat job runs the deployed runtime's tests, and they still insert rows without it.
Singular buildId in webhook responses Removed. Responses return buildIds. The ingress relay passes the body through, and GitHub ignores it.
run_attempt null handling The OIDC exchange returns 401 without run_attempt. A workflow_run event without it is ignored. The build column stays nullable, because container builds have no attempt.
Deploy tokens without build or credential The auth middleware returns 401. Every build token is minted with build and lives at most 65 minutes. CI credential tokens carry credential.
Queued build without a branch The scheduler cancels it ("github connection config changed") and does not fall back to the connection branch.

Kept on purpose: the claim-time instance-type fill-in. Without it, one queued row without an instance type would block the build queue.

CLI minimum platform version

feat(void): the void CLI requires platform 0.23.0 or newer (MIN_PLATFORM_VERSION in packages/void/src/cli/client.ts).

PlatformClient.request()
  └─ once per process and platform URL: GET /.well-known/void-platform (no account token)
       0.23.0 or newer          → pass
       older than 0.23.0        → stop: "… runs version X, but this CLI requires 0.23.0 or later.
                                   Ask its administrator to upgrade the platform runtime with `void platform upgrade`."
       404 (HTML or JSON)       → stop: the platform predates discovery, so it predates the minimum
       "hosted"                 → pass (a hosted deploy that does not set PLATFORM_VERSION yet)
       other version string     → pass (custom or development build)
       network error, timeout,
       503, Access login page,
       invalid document         → the real request runs and reports its own error
  every outcome is kept for the rest of the process, so polling commands probe once
  • Hosted API and email gateway deploys now set PLATFORM_VERSION from packages/platform/package.json (platform/scripts/platform-version.ts). Release order: deploy hosted prod, then publish npm (platform/meta/operations.md).
  • Login and void connect skip the check, so users can still sign in to an older platform. Platform upgrade, status, rollback, domain activation, and operator commands do not use PlatformClient, so they keep working on older runtimes.
  • Removed CLI fallbacks for older platforms:
    • deploy streams without an x-void-deployment-id receipt (server change a98803f, in 0.23.0). A 200 deploy response without a receipt now fails with "Platform returned no deployment receipt."
    • user build-log pages without logsComplete (server change c37e951, in 0.23.0). null still means "cannot prove completion".
    • workflow path echo checks
    • optional cname_target on domain status
    • missing id on whoami
    • missing project role on read paths
    • "upgrade the platform runtime" on a 404 from project choices and resolve
    • unused repository_id on GitHub connections
  • Checked at v0.23.0 and kept:
    • Heartbeats have no seq, and heartbeat status plus the observation page heartbeat came in 21d4ec4, after 0.23.0. These stay optional. recoveryAt is sent only while a lease exists.
    • Operator build-log logsComplete === undefined: operator commands skip the version check, and servers before 0.20.0 omit it.
    • void platform domain handling of a 404 from runtimes before 0.22.0 (a management command).
  • 0.23.0 runtimes always answer a CLI deploy with the receipt: the packaged runtime has deploymentExecution: 1, so the installer binds DEPLOYMENTS_QUEUE, and every hosted wrangler environment binds it too. The inline stream without a receipt runs only when that binding is missing.
  • Docs: the self-hosting guide, platform maintenance, and the Sandboxes guide name the minimum.

Compatibility edges

Version Schema Edge Reason
0.10.14 (6) – removed inserts connections without authorized_by_user_id
0.20.1 – removed writes a NULL authorizer for personal installs
0.20.3 – 0.21.9 089431e2, 408364e6, 9a9ae667, ac70deb5 kept always writes both fields; deleteUser cleans connections first
0.22.0 (2) bb024ebd (0053), bef2a1cf (0055) kept same
0.23.0 3e3458ba (0056) added same code paths; without it a 0.23.0 install cannot upgrade to a runtime with 0057
0.24.0 3e3458ba (0056) added same, and no writer changed from 0.23.0

The digests match the runtimes published to npm (@void/platform 0.23.0 and 0.24.0). Self-hosted installs on 0.10.14 or 0.20.1 must upgrade through a release from 0.20.3 to 0.24.0 first.

Not in this PR

  • A CHECK (Organization ⇒ user_id IS NULL) waits for a later release. The rollback targets 0.20.3–0.24.0 still run the old upsert, which keeps user_id on a User→Organization conversion and would violate the CHECK. It can land once every rollback target contains the upsert fix above.
  • github_installation_members.github_account_id NOT NULL waits for the same reason: the deployed runtime's tests still insert rows without it.

Test deadlock on PostgreSQL

test(platform): both failed v0.24.0 publish runs stopped in platform-ci / schema-compat on deploy-direct-r2.test.ts with 40P01 deadlock detected.

test A: POST /deploy (JSON) → checks status only, never reads the NDJSON body
        the deploy keeps running detached (routes/deploy.ts `void pipeline`)
          tx: SELECT projects FOR UPDATE  → UPDATE deployments
test B: beforeEach clearTables (one transaction)
          delete from deployments          → delete from projects
opposite lock order → 40P01 (PGlite has one connection, so only real Postgres shows it)
  • The integration setup now reads every unread deploy NDJSON body after each test, so the deploy finishes before the next cleanup. Receipt streams only poll, so they are cancelled instead.
  • A single TRUNCATE still deadlocked in a local reproduction, and changing only the cleanup would still let the leaked deploy write into the next test.
  • New Postgres-only regression unread-deploy-cleanup.test.ts holds a deploy at its project lock and leaves it unread. Before the fix it failed 3/3 with 40P01; after, it passed 3/3. It is skipped on PGlite.
  • Running the deployed (84d60bc) copy of deploy-direct-r2.test.ts on Postgres: 4/6 runs deadlock with the old setup, 0/8 with the new one.
  • Main's runner already avoids this in schema-compat: d5ddae4 drains the stream in main's copy of the test, and the runner patches the older deployed copy by checksum. That is why v0.24.0+retry.2 published. This change fixes the harness itself, so a test that forgets to read the body cannot deadlock.

Also

test(platform): the Sandbox drain audit test read operator_events without ORDER BY. Each audit row is inserted and then updated, so on a reused Postgres table its physical position can change. The test now reads by creation time. Production already sorts these rows.

Verification

  • vp run check ✓, vp lint ✓, vp run lint:platform ✓, vp fmt --check ✓
  • vp test run: 678 files (1 skipped: the Postgres-only regression on PGlite), 12732 passed, 27 skipped.
  • vp run test --base origin/main: selected all 678 files; 12732 passed, 27 skipped.
  • Schema-compat simulated locally with main's runner (one database copy per file): baseline = current Prod deployment 9ef5d5b5 (platform 0.24.0), migrated + seeded, then this branch's migrations + fixture verify, then the baseline's API integration tests on Postgres in UTC: 99/99 files, 2344 tests passed.
  • Both platform API integration projects together on one real Postgres database: 3 runs, 0 deadlocks (2347 tests per run). The few failures there (api-health "relation already exists", platform-email-bootstrap:125) also happen on plain main in the same setup; plain main also hit the operator-operations Sandbox drain read that the second commit fixes.
  • Built CLI, no saved login: hosted api.void.cloud passes the check (then 401 for a bogus token); a fake 0.22.0 platform stops with the minimum-version error and gets no API call; a fake 0.23.0 platform passes.
  • Migration test seeds stray rows (including a NULL authorizer from a real user delete under SET NULL) and checks the cleanup and the constraints. The installer test upgrades 0.23.0 and 0.24.0 databases through 0057 and refuses 0.22.0 at that schema.
  • Fresh DB through the drizzle migrator: 51 migrations, both columns NOT NULL, FK NO ACTION. A second db:generate reports no changes.

🤖 Generated with Claude Code

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-03T09:17:39.311007Z 921455e New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

🚀 Deploying Preview to Cloudflare 🚀

Preview URL: https://fix-github-authority-constraints-void-cloud.voidzero-docs.workers.dev (commit 921455e)

This URL reflects your latest Preview deployment

Preview Deployments by commit

Status Deployment URL Commit Updated (UTC) See this deployment's details
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://ee7c4025-void-cloud.voidzero-docs.workers.dev 921455e 2026-10-03T09:14:33.507Z Visit the dashboard ↗
  • Build: Failed ❌

View logs ↗
200c2a1 2026-10-03T03:21:26.102Z View logs ↗
  • Build: Failed ❌

View logs ↗
fb553bd 2026-10-02T17:06:03.357Z View logs ↗
  • Build: Failed ❌

View logs ↗
496f11f 2026-10-02T16:57:54.088Z View logs ↗
  • Build: Failed ❌

View logs ↗
174470c 2026-10-02T16:37:06.547Z View logs ↗
  • Build: Failed ❌

View logs ↗
065a3f2 2026-10-02T13:52:17.786Z View logs ↗
  • Build: Failed ❌

View logs ↗
690f41b 2026-10-02T05:20:50.073Z View logs ↗
  • Build: Failed ❌

View logs ↗
eb902f9 2026-10-02T05:14:00.222Z View logs ↗
  • Build: Failed ❌

View logs ↗
1485a5e 2026-10-02T03:59:24.737Z View logs ↗
  • Build: Failed ❌

View logs ↗
cd5c125 2026-10-01T16:46:36.787Z View logs ↗

View all previews: View all previews ↗

@Brooooooklyn
Brooooooklyn force-pushed the fix/github-authority-constraints branch 2 times, most recently from cd5c125 to 1485a5e Compare October 2, 2026 03:57
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

1 similar comment
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@Brooooooklyn
Brooooooklyn force-pushed the fix/github-authority-constraints branch from eb902f9 to 690f41b Compare October 2, 2026 05:19
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@Brooooooklyn
Brooooooklyn force-pushed the fix/github-authority-constraints branch from 690f41b to 065a3f2 Compare October 2, 2026 13:50
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@Brooooooklyn
Brooooooklyn force-pushed the fix/github-authority-constraints branch from 065a3f2 to 174470c Compare October 2, 2026 16:35
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@Brooooooklyn Brooooooklyn changed the title fix(platform): enforce GitHub connection authority in the schema fix(platform): enforce GitHub authority in the schema and drop old compatibility paths Oct 2, 2026
@Brooooooklyn
Brooooooklyn force-pushed the fix/github-authority-constraints branch from 174470c to 496f11f Compare October 2, 2026 16:56
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@Brooooooklyn
Brooooooklyn force-pushed the fix/github-authority-constraints branch from 496f11f to fb553bd Compare October 2, 2026 17:05
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@Brooooooklyn
Brooooooklyn force-pushed the fix/github-authority-constraints branch from fb553bd to 200c2a1 Compare October 3, 2026 03:19
Brooooooklyn and others added 5 commits October 3, 2026 16:38
GitHub connections now require an authorizer and a repository id, and a user who still authorizes a connection can no longer be deleted silently. The migration first removes connections that lack authority and cancels their active builds, so it applies cleanly to older data, and runtime null handling for these columns is removed. A personal installation converted into an organization now drops its owner. Upgrades from 0.10.14 and 0.20.1 must now pass through a later release; 0.23.0 and 0.24.0 installations can upgrade directly.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The drain audit test took the last row of an unordered operator_events read. Each audit row is inserted and then updated with its outcome, so on a reused PostgreSQL table its physical position can differ from request order. Read the rows by creation time instead.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Installation memberships and join codes always carry the GitHub account that proved them. Webhook responses return only `buildIds`. GitHub Actions OIDC tokens and workflow_run events must carry `run_attempt`, which GitHub always sends. Deploy tokens must be bound to a build or a CI credential. The build scheduler cancels a queued build without a branch instead of using the connection branch.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The CLI checks the platform version from public discovery once per process and stops with an upgrade message on a platform older than 0.23.0, or on one that answers discovery with 404. Other unusable discovery answers let the request run. Login still works on older platforms. Hosted deploys now report their release version. Fallbacks for responses that older platforms sent are removed, including deploy streams without a deployment receipt and build log pages without logsComplete.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A JSON deploy keeps writing to the database until its NDJSON body is read. A test that checked only the status left that deploy running into the next test, where its project-then-deployment row locks deadlocked with the deployment-then-project deletes in clearTables on PostgreSQL. The integration setup now reads every unread deploy stream after each test, and cancels receipt streams, which only poll. A PostgreSQL-only regression test leaves a deploy unread while it holds the project lock.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Brooooooklyn
Brooooooklyn force-pushed the fix/github-authority-constraints branch from 200c2a1 to 921455e Compare October 3, 2026 09:13
@Brooooooklyn Brooooooklyn changed the title fix(platform): enforce GitHub authority in the schema and drop old compatibility paths fix(platform): enforce GitHub authority in the schema, require platform 0.23.0 in the CLI Oct 3, 2026
@Brooooooklyn
Brooooooklyn merged commit 68a9f32 into main Oct 4, 2026
20 checks passed
@Brooooooklyn
Brooooooklyn deleted the fix/github-authority-constraints branch October 4, 2026 04:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant