Skip to content

Register private Storage receipts and authorize media delivery in Convex - #33

Merged
kvz merged 28 commits into
mainfrom
dam-private
Sep 25, 2026
Merged

kvz merged 28 commits into
mainfrom
dam-private

Conversation

@kvz

@kvz kvz commented Sep 25, 2026 •

Copy link
Copy Markdown
Member

Why

The wedding album is the second real consumer of Transloadit Storage and @transloadit/viewer, after the website. Guest uploads need durable, private originals and receipts that the app can authorize. Assembly result URLs provide neither. This PR is the Convex half of the plan agreed with the DAM/Viewer side: receipts only in reactive queries, and a single request-authorized delivery route.

What

Component (@transloadit/convex, minor)

  • Completed Assemblies register canonical Storage receipts once per Workspace, asset and version, from verified webhooks or authoritative refreshes (storageWorkspace / TRANSLOADIT_WORKSPACE).
    • Extraction uses the shared extractStoredAssemblyResults from @transloadit/zod/v3/storageResults, so a malformed or cross-Workspace receipt fails the whole update.
    • Status, results and receipts persist in one transaction.
  • Reads come from local data only, with no Storage calls or signing:
    • listStoredAssets uses convex-helpers' indexed paginator and its React usePaginatedQuery. Inclusive endCursor ranges and split signals keep loaded pages stable. Old/malformed or cross-album cursors reset; non-finite limits are rejected and finite reads stay bounded to 1,000 rows.
    • getStoredAsset returns one exact version.
  • Deletion ledger:
    • Expiry follows each asset's newest version and scans with a cursor.
    • Hidden assets leave reads immediately.
    • Failures stay pending and retryable.
    • Completed deletions become tombstones, so late notifications cannot resurrect them.
  • makeTransloaditAPI exposes no receipt reads.

Example

  • Server-created uploads records choose each upload's prefix: convex-demo/<deployment>/<album>/<upload>/. Photo originals go through /transloadit/store with a ThumbHash. With Storage enabled, photos get no public R2 renditions.
  • media:list shows only receipts bound to a matching upload. media:forDelivery is the single authorization for preview, original and download: it checks the live session, the current invitation, the album and the exact version.
  • Hosted albums use the official Convex Auth Next.js integration (proxy.ts, httpOnly cookies). /api/transloadit/media is Viewer's createStorageRoute with explicit server-only Smart CDN credentials. The gallery renders @transloadit/viewer/react Image with stable cursor pages and opt-in receipt ThumbHash blur. Preview metadata is authorized before reaching the browser; transparent images and letterboxed fullscreen images stay blur-free. The viewer offers original downloads.
  • scripts/cleanup-demo.ts:
    • dry runs cover Convex, Storage and R2
    • expiry works through the ledger, with album scope and backend-reported prefixes
    • Storage is never touched outside the deployment's demo prefix, checked against the asset's current location
    • a reference is released only after Storage confirms the deletion

Scope and limitations

  • Community plan watermark. API2 currently exempts only /transloadit/store Steps that use :original directly from its upload watermark. This album stores a photo filter of :original, so on the Community plan the stored photos are watermarked. The API2 fix is deferred to transloadit/api2#9253, with no workaround here. The byte-identity results below come from the open-test-prod test workspace and say nothing about Community-plan uploads.
  • Video stays on public R2 URLs, so the album is not private end to end.
  • Published dependencies. Viewer 0.0.3 (alpha) and Zod 5.0.1 are exact registry dependencies, released through SDK #518 and Changesets #519. The three preview tarballs, provenance files and QA file: overrides are removed. The publishability guard and Yarn age gate remain; only these two same-day exact versions are exempt. The component now requires Convex 1.43+, matching the helper's peer requirement; a minimum-version consumer was verified.
  • Session migration. Guests with sessions from the earlier browser-only provider enter once more.
  • Activation order. Configure the delivery key on the Next.js host before enabling TRANSLOADIT_WORKSPACE.
  • Scheduling. The daily Storage expiry is not scheduled yet.

Evidence

Tests. Final yarn check passes: 178 tests in 26 files, with no type errors. Red-first regressions cover:

  • delayed replays after deletion (tombstones)
  • cursor ties within one registration instant, a 1200-row walk, stable endCursor pages and split pages
  • fresh-version expiry and scans past skipped assets
  • albumless ledger entries, receipts-only legacy results and atomic persistence of a completed status without results
  • cleanup: never releasing an unconfirmed deletion, current-location checks, orphan adoption, dry-run parity

Current verification — September 25. Final head f253b0b0724a519a61d1cd99ef006aa00272a051 is fully green in CI, including Node 24/26, package/example builds, lint, official component-codegen drift, browser tests and deployment QA. The production deploy was skipped. Final council findings and the lead's boundary finding were reproduced red-first and fixed:

  • Configured Storage/R2 backends now remain untouched when their cleanup skip flags are set.
  • Client deletion listings accept an omitted album, matching the component.
  • NaN/infinite pagination limits cannot bypass the 1,000-row ceiling.
  • Production deployment also waits for the new codegen-drift check.

A local harness renders the real gallery, published Viewer and pagination hook with synthetic data: desktop/mobile blur-to-image loading, transparency, 24→30 pagination, filtered empty page→six photos, empty/error states, fullscreen and Escape/focus all pass. It is not a new production Storage canary. The evidence-based user test found the new error alert's low contrast, now fixed; pre-existing muted-caption contrast remains a follow-up. The loaded count is intentionally not an exact total, and the isolated empty-page harness is not the full page chrome.

The paginator substitution removes 115 net maintained runtime/config lines including its non-finite guard, while adding regression coverage. Its client pagination bundle grows by about 843 bytes gzip and server bundle by about 4.4 KB gzip; the benefit is less bespoke pagination code, not smaller bundles. Living notes record measurements, review reconciliation, evidence and release facts. No new production canary, credentials or deployment were performed in this follow-up.

Earlier production canary on the open-test-prod test workspace, synthetic assets only, no signed URLs recorded (prior implementation evidence, not a fresh canary on f253b0b):

  • store with ThumbHash; native list/get and receipt sync
  • signed avif/webp/jpg previews and exact-byte originals via the least-privilege smart_cdn:sign key
  • tampered parameters → 400; an unknown version → 404
  • renames and overwrites keep pinned delivery
  • soft delete: 404 on reads; cached URLs live until their enforced expiry

Browser spike with Chromium, the local production build and an isolated Convex preview, 80 synthetic photos uploaded through the real Uppy → Assembly → verified-webhook path:

  • Cookies: __convexAuthJWT and __convexAuthRefreshToken are httpOnly, SameSite=Lax and persistent.
  • Payload: media:list is 15.1 KB for 24 receipts and 50.3 KB for 80 (~630 B per receipt, no URLs); HTTP p50 is about 110 ms.
  • Paging: the gallery shows 24 first, then pages to 80. A live upload with every page loaded gives 81 unique cards with all earlier cards kept.
  • Delivery: 80 route redirects (307) → 80 CDN 200s. Route p50 is 116 ms and p95 809 ms (local server → Convex cloud).
  • Grant TTL: 256 s for previews and 255 s for originals and downloads, within the route's 5-minute default. Every redirect is private, no-store.
  • Download: attachment; filename="spike-81.jpg" with exact bytes. This is test-workspace evidence only: it does not cover Community-plan filtered uploads, see api2#9253.
  • Denials: no cookie, wrong version, unknown asset, off-ladder width, unknown or duplicate parameter and invalid action all → 404. A copied cookie → 404 once auth:signOut completes.
  • Long-open album: an original issued before a 330 s wait → 400 AUTH_EXPIRED. Fresh route redirects keep working.
  • Mobile: the fullscreen viewer decodes.
  • WebKit (independent macOS pass by the DAM/Viewer owner on c2a7f0a): gallery, fullscreen and next; mobile centre offset 0 px; the full filename is readable; spike-77 downloads with the same SHA-256; keyboard activation and Escape return focus; 0 auth cookies after a completed logout, then media HEAD → 404 private, no-store. There were no console or page errors; the only cancelled fetches were caused by the native download.

Viewer remount (fixed in c19ac54). The existing Motion design unmounts the thumbnails' shared boundary when a photo opens. With sizes="auto,…", the remounted thumbnails picked 1200 px: 24 extra no-store authorizations per toggle.

  • Thumbnails now use explicit layout sizes.
  • Opening the viewer requests only the viewer rendition on desktop (1920/DPR 1) and laptop (1280/DPR 2), and closing requests nothing. On mobile (390/DPR 3) the viewer reuses the loaded candidate.
  • A jsdom regression test keeps thumbnail sizes/srcset identical across opening and closing the lightbox.

Mobile viewer layout (fixed in c2a7f0a). The viewer's <picture> is display: contents, so its <source> elements became extra grid rows.

  • At 390×844, after all finite animations finished, the photo's centre offset went from 142.88 px to −0.01 px, and the grid now holds only the image.
  • The narrow toolbar now wraps, so the photo name is no longer truncated. Desktop is unchanged.

Council follow-ups (c1c280a, 5ffce0e).

  • A reset refuses to run while R2 or Storage is unconfigured unless explicitly skipped. Even when skipped, results that still reference R2 media are kept and the run is reported incomplete.
  • A partial R2 batch deletion counts as a failure.
  • Summaries read one page per transaction and count each asset once.
  • A configured Workspace without a unique namespace, as on local or self-hosted deployments, fails closed instead of falling back to public R2.
  • A failed gallery page is dropped and shown as an error, not left in place as a stale page.
  • The release workflow refuses local dependency specs.

Canary cleanup.

  • Scope: the isolated preview and its Storage prefix only. R2 was explicitly skipped: this spike wrote no R2 media.
  • Result: 81 photos hidden and deleted in Storage, then tombstoned; 81 results and 8 Assemblies purged; retryNeeded: false.
  • Afterwards: 0 objects under the spike prefix and 0 visible receipts. The test server is stopped.

Independent review. Browser pass by the DAM/Viewer owner:

  • 24→48→72→81 unique cards
  • mobile and desktop viewer, keyboard and focus
  • exact-byte original download
  • upload cancel, EN/NL
  • media HEAD → 404 private, no-store after a completed logout

Not in this PR

  • Convex merge/release and production activation: awaiting Kevin's decision because merging main automatically deploys the demo. The SDK dependencies themselves are already released.
  • The API2 Community-plan watermark fix.
  • The TRANSLOADIT_WORKSPACE repository variable and production delivery credentials.

🤖 Generated with Claude Code

The wedding album is the second real consumer of Transloadit Storage and Viewer. Guest uploads
need durable, private originals with receipts the app can authorize, which Assembly result URLs
cannot provide.

- Component: register verified Storage receipts from completed Assemblies once per Workspace,
  asset and version, with Assembly provenance. A malformed or cross-Workspace receipt fails the
  whole status update. Paginated local reads, exact-version lookup and a deletion ledger that hides
  assets before Storage deletion and keeps failures retryable.
- Example: server-created upload records choose each upload's Storage prefix; photo originals go
  to Storage with a ThumbHash when TRANSLOADIT_WORKSPACE is set. media:forDelivery is the single
  authorization query for preview, original and download. Hosted albums keep Convex Auth tokens in
  httpOnly cookies through the official Next.js integration, so server routes can authorize.
- Cleanup: dry runs cover Convex, Storage and R2; expiry hides first, deletes bytes, then drops
  references; nothing outside the deployment's demo prefix is ever deleted.

The Viewer delivery route and gallery switch follow with the Viewer alpha. Video stays on R2.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vercel

vercel Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
convex Ready Ready Preview Sep 25, 2026 4:31pm UTC

Request Review

kvz and others added 2 commits September 25, 2026 14:39
- Use the shared extractStoredAssemblyResults from @transloadit/zod/v3/storageResults instead of
  the provisional selector.
- Add /api/transloadit/media with Viewer's createStorageRoute: explicit server-only Smart CDN
  credentials, one Convex authorization per request through the guest's cookie, uniform 404s.
- The gallery renders Storage photos with @transloadit/viewer/react Image, pages through
  media:list and offers original downloads. With Storage enabled, photos no longer get public R2
  renditions; video keeps R2.
- Completed deletions leave tombstones so a delayed notification or refresh cannot register a
  deleted asset again (red-first regression).
- Vendor the unpublished SDK preview (source SHA in manifest) so CI can verify the integration;
  the QA template resolves those file: specs. Replace with published versions before merging.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Council review of the demo cleanup:
- The deletion ledger is album-scoped and paginated, so failures never block later assets.
- An entry is completed only after Storage deletes the asset or reports it gone. Entries whose
  receipt or current location lies outside this deployment's demo prefix stay hidden and pending
  with a reason, instead of being released or deleted.
- The backend reports the prefix it assigns to uploads, so cleanup cannot derive a different one.
- Without Storage access nothing is hidden; dry runs count every page and disclose truncation.
- Document that guests from the browser-only session provider enter once more.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A real Convex deployment rejects `.paginate()` inside components ("only supported in the
  app"); convex-test does not. The gallery now reads a bounded, newest-first window that "Show
  more" grows, which also keeps a reactive gallery free of gaps when new photos arrive. The
  deletion ledger pages with an explicit (deletionRequestedAt, _creationTime) index cursor.
- Vendor SDK commit 4b904e5 (clean committed artifacts, source hash in the manifest), which fixes
  preview grant lifetimes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- listStoredAssets pages with an explicit (createdAt, _creationTime) index cursor, including an
  inclusive endCursor; ties within one registration instant and 1200-row walks are tested. The
  gallery freezes loaded pages with end cursors, so live uploads never shift photos between pages.
- Status, results and receipts persist in one transaction; completed Assemblies without results
  still record their status; albumless receipts can enter the deletion ledger; Assembly listings
  skip hidden versions before their limit.
- wedding:listGallery returns only the R2 renditions it renders, never Storage receipts or their
  ThumbHash; private photos keep their Assembly provenance for data-assembly-id.
- Document the Community-plan watermark scope of filtered originals and the activation order for
  Storage and delivery credentials.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kvz and others added 2 commits September 25, 2026 15:02
- requestStoredAssetDeletion keeps an asset whose newest visible version is not yet expired, so an
  overwrite after the cutoff never loses its fresh version to expiry (red-first test).
- Expiry scanning continues from a cursor, so skipped fresh assets cannot stall later eligible
  ones; the cleanup script follows it.
- Document in the README recipe that filtered originals are watermarked on the Community plan.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Council review of the integrated branch:
- A reset records unregistered Storage objects as hidden ledger entries before deleting them, so
  a delayed notification finds a tombstone instead of registering deleted media.
- A frozen gallery page that outgrows the 1000-row limit reports a split cursor; the client splits
  it into two contiguous frozen pages instead of skipping rows.
- Dry runs use the same selection as expiry (distinct assets, newest version across albums) through
  a read-only previewStoredAssetExpiry query.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Opening or closing the viewer remounts thumbnails (the Motion boundary is removed while a photo
is shared with the viewer). With `sizes="auto, ..."` the remounted images picked a larger
candidate, so each toggle re-authorized and re-rendered every visible photo at 1200px. Thumbnails
now use explicit sizes derived from the justified row layout, so a remount selects the same URL.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The viewer's picture is `display: contents`, so its <source> elements (displayed by default)
became extra grid rows and pushed the photo about 143px below centre on a 390px screen. Sources
are now hidden from layout; browsers still select candidates from them. On narrow screens the
viewer toolbar wraps, so the actions no longer truncate the photo name.

Browser proof at 390x844 after all finite animations: image centre offset 142.88px -> -0.01px,
grid items source,source,img -> img, name truncated -> readable; desktop unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Resets refuse to run while R2 or Storage is unconfigured unless skipped explicitly
  (--skip-r2, --skip-storage), so references are never purged while media stays reachable.
- Cleanup summaries read one page per transaction and dry runs count each expired asset once
  across pages, matching the real deletion.
- Local and self-hosted deployments need TRANSLOADIT_STORAGE_NAMESPACE before using Storage, so
  developers sharing a Workspace never write to or clean up one another's prefix.
- The gallery drops a page whose query fails and shows a load error instead of stale photos.
- The release workflow refuses to version or publish while runtime dependencies use local
  specs, such as the vendored SDK preview.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ences

- A configured TRANSLOADIT_WORKSPACE without a unique namespace now fails with an actionable
  error instead of silently choosing public R2; only an unset Workspace keeps the R2 pipeline.
- --skip-r2 no longer purges Convex results that still reference R2 media (or when a truncated
  count cannot rule that out); the run reports them as kept and incomplete.
- An S3 DeleteObjects response with per-key Errors is a failed batch, so results are never
  forgotten after a partial R2 deletion.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kvz and others added 6 commits September 25, 2026 17:21
Upload binding or missing geometry can leave a page without listable photos while later
pages still have them. The empty state hid the only way forward.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The committed component bindings had been hand-edited because
`convex codegen --component-dir` needs a root convex.json and a backend.
A root convex.json now points the CLI at the example app, and
example/convex/tsconfig.json extends the example config so the
tsconfig that `convex init` would otherwise write cannot hide the
paths mapping to src/. `yarn codegen` runs the official command and
ignores CONVEX_DEPLOY_KEY from .env. CI regenerates against a
throwaway anonymous local backend (no credentials) and fails on drift.

The official output differs from the hand-edited file only in
indentation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Thumbnails opt into Viewer's placeholder="blur" with an explicit cover
fit; the letterboxed fullscreen photo passes contain and no placeholder,
so the stylesheet cannot change what Viewer's guard sees. No load
handlers: loaded opaque pixels cover the inline background.

A ThumbHash is preview pixels, so media:list and media:forDelivery now
return it only for versions an admitted guest may preview.

Vendors the Viewer preview from the uncommitted SDK follow-up
(sha256 d93bc693...441e4), recorded in the vendor manifest.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The runtime is identical to the previous preview pack; only its README and reference docs changed.
The manifest records the SDK commit and the tarball hash.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replace the hand-rolled index positions and the example's useStablePages with
convex-helpers' paginator and its React usePaginatedQuery. Pages now follow
Convex's cursor protocol: pinned loaded pages keep their range under live
inserts and deletes, and pageStatus/splitCursor split pages that outgrow the
read limit.

The component keeps its guard rails: cursors from another album, an earlier
release or garbage are refused as InvalidCursor so the hook restarts, page
sizes stay clamped, clients may only lower maximumRowsRead, and a pinned page
that hits the read limit keeps its end so splitting never drops rows. The
expiry scan keeps its explicit mid-page positions.

Non-cursor failures now reach a small error boundary that shows the load
failure instead of stale photos. erasableSyntaxOnly moves to
tsconfig.node.json, which covers only what Node runs by stripping types,
because convex-helpers ships TypeScript sources. The convex peer floor rises
to ^1.43.0, the paginator's own requirement.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Viewer blur background now covers named crops instead of stretching, and
the Storage extractor keeps Assembly errors when ok is null. Both remain
unpublished previews until the SDK release lands.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kvz and others added 4 commits September 25, 2026 18:05
…loor

The README and changeset now describe listStoredAssets' Convex cursor
protocol instead of a growing window, show storedAssets in the data
model, and say plainly what a tombstone keeps (the receipt minus its
ThumbHash, provenance, album/user/upload linkage) and that nothing
purges it. A separate changeset calls out the convex ^1.43.0 peer,
which the tested convex-helpers 0.1.124 paginator declares; a consumer
pinned to convex 1.43.0 typechecks and passes the component tests.

CONTRIBUTING records the helper behavior keepLoadedRange repairs and
the regression test that guards it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Checklist, privacy boundary, measured pagination tradeoffs, guard parity
and the remaining release and merge gates for PR #33.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pin @transloadit/zod 5.0.1 and @transloadit/viewer 0.0.3 exactly from npm and
drop the vendored SDK packs, their resolutions, the QA template's file:
overrides and the tarball Git attributes. Utils resolves to the unchanged 4.9.0.

The registry tarballs carry the same runtime JavaScript as the vendored
e521c00 packs (Zod differs only in .d.ts member order). The Yarn age gate stays
on with exact exceptions for the two same-day releases, and the release guard
still refuses local dependency specs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Capture the node-sdk merges, the published versions, the transient npm
staging race (404 metadata, 409 retry, rerun without a bump) and what the
registry swap changed, since the SDK finish checklist predates the release.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kvz and others added 6 commits September 25, 2026 18:29
Convex numbers include NaN, which passes Math.min and Math.max. With an
end cursor the paginator then had no read limit at all: one query returned
all 1055 rows past the 1000-row ceiling. Both list paths now refuse NaN and
infinite page limits; finite clamping is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
--skip-storage and --skip-r2 only relaxed the missing-configuration error.
With both backends configured, the flags still hid receipts, deleted
Storage assets and R2 objects, and purged results.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The component and the other ledger calls already treat the album as
optional.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The muted grey this PR introduced for the alert measured 4.28:1 on the page
background, below 4.5:1 for 13px text. The existing error colour measures
7.20:1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ving notes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kvz
kvz marked this pull request as ready for review September 25, 2026 16:36
@kvz
kvz merged commit 6775ac9 into main Sep 25, 2026
12 checks passed
@kvz

kvz commented Sep 25, 2026

Copy link
Copy Markdown
Member Author

Merge and production verification — 2026-09-25

Kevin explicitly approved merging the green PR and following its automatic production deployment.

  • Squash-merged with --admin at 17:32:00 UTC as 6775ac93ade1787753d80530229f4d7f1aaee0d3. The approved head was f253b0b0724a519a61d1cd99ef006aa00272a051, contained the latest main, had no unresolved review threads, and had green required checks.
  • Main CI 36167722330 passed, including codegen drift, Node 24/26 tests, package/example builds, browser tests, and Deploy and verify production example.
  • The automatic workflow deployed the Convex backend, then verified a new Vercel Production deployment for the exact merge SHA: deployment 6666544532, Vercel build. The live gallery browser verification passed at 17:36:42 UTC.
  • This completes the exact-head CI / merge-decision items recorded in docs/prompts/2026-09-25-dx-cleanup.md. No additional credentials, Storage activation flags, or production settings were changed outside the approved automatic workflow. This deployment result does not claim that the previously separate Storage activation prerequisites are now configured.

Separate release automation blocker

Release run 36167722300 failed while Changesets tried to update the existing changeset-release/main branch: Cannot force-push to this branch. The applicable organization ruleset is 17287233 (non_fast_forward, plus required signatures). There is no open Version Packages PR; the retained branch is from an earlier release.

The deployment is successful, but this run did not prepare/publish a new Convex npm version. No protections were changed, release branches deleted, or force-push workaround attempted. Release automation needs a separate scoped decision/fix.

@kvz

kvz commented Sep 25, 2026

Copy link
Copy Markdown
Member Author

Release automation unblocked — 2026-09-25

With Kevin’s explicit approval, the organization policy is now split:

The replacement force-push rule was created and verified before removing that rule from the original combined ruleset, so other branches had no protection gap. Effective-rule readback confirms unchanged main/feature protections in Convex, Node SDK, Content and API2. Uppy’s main/release rules and its two separately inspected rulesets are unchanged.

Release run 36167722300, attempt 2 is green and created Version Packages #34. Its commit 4830676a94bc84b13d5a4ce1e1aa7196e19a85c9 is GitHub-verified and directly based on main 6775ac93ade1787753d80530229f4d7f1aaee0d3. The diff is only the 0.3.0 version, changelog and consumed changesets. No release branch was deleted, no signing requirement weakened, and no npm publication occurred in this retry.

The full before/after snapshots and restore payload are saved in /tmp/transloadit-rulesets.1QQa0W/. To roll back safely, restore rule 17287233 to its original combined policy first, verify effective protection, then disable the redundant new rule. PR #34 tracks validation and the separate publication decision.

@kvz kvz mentioned this pull request Sep 25, 2026

This branch was successfully deployed

1 active deployment
Preview — f253b0b0 Deployed Sep 25, 2026 by vercel[bot]
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