Skip to content

feat(upload): print the canonical upload link, not a short link to resolve - #1169

Open
TylerJang27 wants to merge 2 commits into
tyler/collection-upload-short-linkfrom
tyler/collection-upload-canonical-link
Open

feat(upload): print the canonical upload link, not a short link to resolve#1169
TylerJang27 wants to merge 2 commits into
tyler/collection-upload-short-linkfrom
tyler/collection-upload-canonical-link

Conversation

@TylerJang27

@TylerJang27 TylerJang27 commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator
Screenshot 2026-08-26 at 5 45 31 PM

Replaces the need for https://github.com/trunk-io/trunk2/pull/5440

What this changes

#1168 prints …/collections/{shortId}/u/{bundleMetaId}, and trunk2#5440 adds a page that resolves the upload's bundle_meta_created_at from that id and redirects to the canonical …/collections/{shortId}/uploads/{bundleMetaKey}.

This prints the canonical URL directly. The CLI already has both halves: CreateBundleUploadResponse returns test_collection_bundle_meta_created_at next to the id, and both land in the bundle as TestCollectionProps. So there is nothing to look up.

before  105 chars  …/collections/tc_a1b2c3d4/u/82c6a6e5-f8ea-4d93-9a26-b8ab6ff8f6bc
after   170 chars  …/collections/tc_a1b2c3d4/uploads/eyJpZCI6IjgyYzZhNmU1LWY4ZWEtNGQ5My05YTI2LWI4YWI2ZmY4ZjZiYyIsImNyZWF0ZWRBdCI6MTc3ODQxNjQ5NjAwMH0

Why it might be worth the longer URL

The resolve the short link requires cannot use test_collection_upload's primary key. That key leads with (test_collection_id, repo_id, bundle_meta_created_at, …); a link carries no repo, so with repo_id unconstrained the index resolves only the leading column and every granule in the collection is a bloom-filter candidate. The table's 45-day TTL also means the query's own 45-day bound prunes almost nothing beyond what the TTL already did.

It measures at one granule (8,192 rows) on staging only because staging collections fit in one granule — that is a floor, not a scaling result. At the volumes prod is heading for, it becomes a multi-MB skip-index read per click.

Why it might not be

  • The longer URL is the thing the short-link design exists to avoid. If a short, pasteable link is the product requirement, this is the wrong trade and the fix belongs server-side (a bundle_meta_id-ordered projection on test_collection_upload makes the resolve a point read; cheapest to add before the table fills up).
  • It makes the CLI a second producer of the bundleMetaKey encoding — a frozen cross-repo contract with encodeBundleMetaKey in trunk2. That is the same class of coupling that caused the bundle_meta_id divergence in trunk-io/trunk2#5451. Mitigating: bundleMetaKey is already a permanent public URL contract the webapp emits, so it is frozen either way, and unlike a hash its drift is visible — it is base64 of a two-field JSON object.
  • The redirect has a benefit of its own: it leaves the canonical URL in the address bar, so a refresh, bookmark, or forwarded link never re-pays the resolve. This form gets that too (it is the canonical URL), so this is only an argument against rendering the short link in place — noting it for completeness.

Implementation notes

  • The key is built from a #[derive(Serialize)] struct, not a format!, so field order and whitespace match JSON.stringify({ id, createdAt }) byte for byte and an id containing a quote cannot break the payload.
  • Golden vector in api/src/urls.rs generated from trunk2's encodeBundleMetaKey; the CLI integration test's expectation is generated the same way.
  • base64 and chrono become direct deps of api — both were already in the lock, so the lock diff is two lines.
  • url_for_upload returns anyhow::Result now, since parsing the timestamp is a second failure mode. The caller already discarded errors with .ok(), so a malformed timestamp degrades to printing no link rather than a broken one.

Testing

  • api: golden vector, offset-independence (…18:16:48.873Z and …11:16:48.873-07:00 must produce the same key), and an unparseable-timestamp rejection — 5 passed
  • trunk-analytics-cli --test upload: 53 passed, including the three link assertions
  • Full workspace: 54 test targets, 0 failures. cargo clippy --all-targets clean; cargo fmt clean for every file this touches (the one pre-existing diff in context/src/junit/parser.rs is on the base branch too)

TylerJang27 and others added 2 commits August 26, 2026 21:19
…solve

Alternative to the short-link form in #1168, for comparison — see trunk-io/trunk2#5440.

The short link carries only the bundle_meta id, so the webapp has to recover
`bundle_meta_created_at` before it can address the upload, then redirect. That
lookup cannot use `test_collection_upload`'s primary key: it leads with
`(test_collection_id, repo_id, bundle_meta_created_at, …)` and a link carries no
repo, so with `repo_id` unconstrained the index resolves only the leading column
and every granule in the collection becomes a bloom-filter candidate. The 45-day
TTL means the query's own 45-day bound prunes almost nothing on top of that. It
measures as one granule on staging today only because staging collections fit in
one granule.

The CLI already has both halves — `CreateBundleUploadResponse` returns
`test_collection_bundle_meta_created_at` alongside the id, and both land in the
bundle — so it can emit the webapp's canonical `uploads/{bundleMetaKey}` URL
directly and skip the lookup and the redirect entirely.

Costs, stated plainly:

- The printed URL grows from 105 to 170 characters (+62%), which is the tradeoff
  the short-link design was chosen to avoid.
- It makes the CLI a second producer of the `bundleMetaKey` encoding, a frozen
  contract with `encodeBundleMetaKey` in trunk2. Pinned here by a golden vector
  generated from that function. Mitigating: `bundleMetaKey` is already a permanent
  public URL contract the webapp emits, and unlike a hash its drift is visible —
  it is base64 of a two-field JSON object.

Field order and whitespace match `JSON.stringify({ id, createdAt })` byte for
byte via a `Serialize` struct rather than a `format!`, so the golden test is
meaningful and an id containing a quote cannot break the payload. The webapp's
decoder parses JSON and is order-insensitive, so a drift would still resolve —
it would just stop matching the string the webapp emits.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The golden vector now uses a case verified three ways: against a base64 of the
literal JSON, against trunk2's own `encodeBundleMetaKey`, and by decoding the
Rust output back (byte-identical, field order and no-whitespace included).
Confirmed load-bearing — swapping the struct's field order fails it.

The three url_for_upload cases shared five identical arguments; they go through
one `upload_url(created_at)` helper so each test shows only what it varies.
Comments trimmed to the non-obvious parts.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@trunk-staging-io

trunk-staging-io Bot commented Aug 26, 2026

Copy link
Copy Markdown

Static BadgeStatic BadgeStatic Badge

Failed Test Failure Summary Logs
pending_quarantine_test should be quarantined when run with variant A test marked as pending was expected to fail but unexpectedly passed. Logs ↗︎
variant_quarantine_test should be quarantined when run with variant A test expected the sum of 2 + 2 to be 5, but it was actually 4, indicating a failing assertion. Logs ↗︎

View Full Report ↗︎Docs

@codecov-commenter

codecov-commenter commented Aug 26, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 83.37%. Comparing base (ca5d99b) to head (1bb9913).

Additional details and impacted files
@@                          Coverage Diff                           @@
##           tyler/collection-upload-short-link    #1169      +/-   ##
======================================================================
+ Coverage                               83.36%   83.37%   +0.01%     
======================================================================
  Files                                      72       72              
  Lines                                   16359    16375      +16     
======================================================================
+ Hits                                    13637    13653      +16     
  Misses                                   2722     2722              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@trunk-io

trunk-io Bot commented Aug 26, 2026

Copy link
Copy Markdown

Static BadgeStatic BadgeStatic Badge

View Full Report ↗︎Docs

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

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants