feat(upload): print the canonical upload link, not a short link to resolve - #1169
Open
TylerJang27 wants to merge 2 commits into
Open
feat(upload): print the canonical upload link, not a short link to resolve#1169TylerJang27 wants to merge 2 commits into
TylerJang27 wants to merge 2 commits into
Conversation
…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>
|
Codecov Report✅ All modified and coverable lines are covered by tests. 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. 🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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'sbundle_meta_created_atfrom that id and redirects to the canonical…/collections/{shortId}/uploads/{bundleMetaKey}.This prints the canonical URL directly. The CLI already has both halves:
CreateBundleUploadResponsereturnstest_collection_bundle_meta_created_atnext to the id, and both land in the bundle asTestCollectionProps. So there is nothing to look up.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 withrepo_idunconstrained 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
bundle_meta_id-ordered projection ontest_collection_uploadmakes the resolve a point read; cheapest to add before the table fills up).bundleMetaKeyencoding — a frozen cross-repo contract withencodeBundleMetaKeyin trunk2. That is the same class of coupling that caused thebundle_meta_iddivergence in trunk-io/trunk2#5451. Mitigating:bundleMetaKeyis 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.Implementation notes
#[derive(Serialize)]struct, not aformat!, so field order and whitespace matchJSON.stringify({ id, createdAt })byte for byte and an id containing a quote cannot break the payload.api/src/urls.rsgenerated from trunk2'sencodeBundleMetaKey; the CLI integration test's expectation is generated the same way.base64andchronobecome direct deps ofapi— both were already in the lock, so the lock diff is two lines.url_for_uploadreturnsanyhow::Resultnow, 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.873Zand…11:16:48.873-07:00must produce the same key), and an unparseable-timestamp rejection — 5 passedtrunk-analytics-cli --test upload: 53 passed, including the three link assertionscargo clippy --all-targetsclean;cargo fmtclean for every file this touches (the one pre-existing diff incontext/src/junit/parser.rsis on the base branch too)