(starter) The vendored acceptance uploader still PUTs via `@vercel/blob` — the mint has returned an S3 presigned URL since MOTIR-2389, so the first adopter to write an acceptance spec gets a lane that cannot publish
Found while building MOTIR-2690, which removed the acceptance lane's continue-on-error and so had to read the vendored uploader's exit paths end to end. Out of that card's scope (it changes the lane's failure semantics; this changes how the upload is performed), filed rather than absorbed.
scripts/upload-acceptance-video.mjs in the starter is a VENDORED COPY of motir-core's, and its own header says so: "It is not a fork: keep it in sync." It has drifted by three upstream cards.
The breakage
The starter still uploads through the Vercel Blob client SDK:
import { put as putBlob } from '@vercel/blob/client';
// …
await putBlob(targets.video.pathname, fs.readFileSync(artifacts.video), {
access: 'private',
token: targets.video.token, // ← expects a `vercel_blob_client_…` token
contentType: targets.video.contentType,
});
targets.*.token has not been a Vercel client upload token since MOTIR-2389 (done) moved the blob store to S3/Tigris. Verified on motir-core origin/main, lib/blob/uploader.ts:
export async function mintPrivateUploadToken(pathname, opts) {
return getSignedUrl(s3Client(), new PutObjectCommand({ … }), {
expiresIn: opts.ttlSeconds ?? 300,
signableHeaders: new Set(['content-type']),
});
}
So /upload-token now returns a presigned PUT URL, and the consumer is expected to fetch(url, { method: 'PUT', headers: { 'content-type': target.contentType }, body }). Handing that URL to @vercel/blob's put as a token cannot work.
This is LATENT in this repo, not live. The starter owns no tests/e2e/acceptance*.spec.ts, and the lane's paths: filter means it never triggers — so nothing is red today. It goes live for the first PR (ours or an adopter's) that writes an acceptance spec, and the failure mode upstream was Failed to parse URL from <the whole credential>, which names neither side.
The rest of the drift, same file
Re-syncing should bring all of it, not just the PUT:
- MOTIR-2389 —
putSignedArtifact, and dropping the@vercel/blobdependency frompackage.json(^2.6.1, referenced nowhere else once this goes). - MOTIR-2499 —
assertPresignedTarget(turns exactly this failure into a sentence naming which deployment is stale) anddescribeToken(the previous diagnosis printed ~700 characters of a live upload grant into a public job log, twice per recording). - MOTIR-1911 — the artifact-size gate:
assessArtifactSizes/resolveMaxArtifactBytes/assertWithinMintedCap, and the policy that an over-cap trace is dropped while an over-cap video rejects the recording. Absent here, an over-cap artifact fails inside the upload instead, after the mint.
Acceptance criteria
- The vendored
scripts/upload-acceptance-video.mjsPUTs each artifact to the presigned URL the mint returns, sendingcontent-typeexactly astarget.contentType— the header is inside the signature, so a guess from the filename is a signature failure. @vercel/blobis gone from the starter'spackage.jsonand from every import, or a comment records what still needs it.- A mint response that is not a presigned URL fails with a message naming the stale deployment, not with
Failed to parse URL from …. - The artifact-size gate is ported with its two-tier policy: over-cap video rejects the recording, over-cap trace is dropped and the video still publishes.
- No credential is ever echoed into the log.
tests/acceptance-video-uploader.test.tsis re-synced alongside it — it currently mocks@vercel/blob/clientand assertsputBlobMockcall counts, which the port invalidates. The two files are vendored together precisely so a bad sync fails locally.- The result is recognisably identical to motir-core's copy, so the next sync is a diff and not an archaeology exercise.
Context refs
nextjs-prisma-vercel-starter/scripts/upload-acceptance-video.mjs— the vendored copy, and its own keep-in-sync header.nextjs-prisma-vercel-starter/tests/acceptance-video-uploader.test.ts— vendored with it; thevi.mock('@vercel/blob/client')at the top is the thing to replace.motir-core/scripts/upload-acceptance-video.mjs— the source of truth to re-copy from.motir-core/lib/blob/uploader.ts—mintPrivateUploadToken, the presigned-PUT contract and whysignableHeadersis not optional.nextjs-prisma-vercel-starter/docs/acceptance-video.md§ Why the uploader is VENDORED here — the MOTIR-1941 decision this card is the maintenance cost of.