finish phase 4: drop @atproto/api dependency, lex-only errors, delete toLex and legacy validators

oxlint-suppressions.json gains a baseline entry for webpack.config.js: the
file was never previously staged/linted (lint script covers src+modules
only), and editing it exposed its pre-existing violations to lint-staged.
All suppressed counts are pre-existing; this change only removes a line.
This commit is contained in:
Samuel Newman
2026-07-17 13:28:29 +03:00
parent ca5b8b6346
commit 8ee62d67bd
20 changed files with 80 additions and 265 deletions
+11 -24
View File
@@ -7,22 +7,20 @@
jest.unmock('multiformats/cid')
jest.unmock('multiformats/hashes/hasher')
import {BlobRef} from '@atproto/api'
import {CID} from 'multiformats/cid'
import {computeCid} from '#/lib/api/computeCid'
import {type app, type com} from '#/lexicons'
/*
* Golden-CID regression test for the composer post pipeline (design section F).
* Golden-CID regression test for the composer post pipeline.
*
* `computeCid` hashes a post record in the client so a thread's later posts can
* reference earlier posts by CID before the server assigns them. The hash is
* byte-sensitive: any drift in how records (especially blobs) are serialized to
* DAG-CBOR silently produces the wrong CID and breaks reply chains with NO type
* error. These golden values were captured from the PRE-migration `computeCid`
* (the `instanceof BlobRef` path) and MUST remain byte-identical after the guard
* is changed to the structural `isBlobRef` shape check.
* error. These golden values MUST remain byte-identical - they gate the
* structural `isBlobRef` shape check.
*
* The blob CID below is a fixed, deterministic CIDv1/raw/sha256 used purely as a
* stable fixture - it is not derived from any real upload.
@@ -30,10 +28,7 @@ import {type app, type com} from '#/lexicons'
const BLOB_CID = 'bafkreieq5jui4j25lacwomsqgjeswwl3y5zcdrresptwgmfylxo2depppq'
/**
* Build a post record with an image embed whose blob is the given value. Used to
* prove that a `@atproto/api` `BlobRef` class instance (the shape the not-yet
* -migrated video path still yields) and a plain-JSON lex blob (the shape lex
* `uploadBlob` returns) hash to the SAME CID.
* Build a post record with an image embed whose blob is the given value.
*/
function postWithImageBlob(blob: unknown): app.bsky.feed.post.Main {
return {
@@ -65,22 +60,14 @@ describe('computeCid', () => {
)
})
it('case 2: record whose embed carries a BlobRef class instance', async () => {
const blob = new BlobRef(CID.parse(BLOB_CID), 'image/jpeg', 12345)
expect(await computeCid(postWithImageBlob(blob))).toBe(
'bafyreiem7g6vja66nebr7he4fshfnlyndyldbvle2n265oixscmepjcbii',
)
})
it('case 2b: a plain-JSON blob object hashes identically to the class instance', async () => {
it('case 2: record whose embed carries a plain-JSON lex blob', async () => {
/*
* This is the post-migration shape: lex `uploadBlob` returns a plain object
* `{$type: 'blob', ref, mimeType, size}` (with `ref` a parsed CID), not a
* `BlobRef` class instance. The structural `isBlobRef` guard must treat it
* exactly like the class instance so the CID is unchanged. Under the
* pre-change `instanceof` code this case already matches because the plain
* object walks through `prepareForHashing` unchanged and DAG-CBOR encodes
* its CID `ref` the same way `.ipld()` does.
* The blob shape lex `uploadBlob` returns: a plain object
* `{$type: 'blob', ref, mimeType, size}` with `ref` a parsed CID. The
* structural `isBlobRef` guard passes it through `prepareForHashing`
* untouched and DAG-CBOR encodes its CID `ref` as a CID link. The golden
* CID below is the byte-identical value the pre-migration `BlobRef` class
* instance produced via `.ipld()`.
*/
const blob = {
$type: 'blob' as const,
+3 -32
View File
@@ -46,10 +46,9 @@ export async function computeCid(
}
/**
* True for a plain-JSON lexicon blob, the shape lex `uploadBlob` now returns
* (`{$type: 'blob', ref, mimeType, size}` with `ref` a parsed CID). Replaces
* the old `instanceof BlobRef` check, since lex blobs are plain objects, not
* class instances (design section F).
* True for a plain-JSON lexicon blob, the shape lex `uploadBlob` returns
* (`{$type: 'blob', ref, mimeType, size}` with `ref` a parsed CID). Lex blobs
* are plain objects, not class instances.
*/
function isBlobRef(v: unknown): boolean {
if (v == null || typeof v !== 'object') return false
@@ -57,24 +56,6 @@ function isBlobRef(v: unknown): boolean {
return o.$type === 'blob' && 'ref' in o && 'mimeType' in o
}
/**
* True for a legacy `@atproto/api` `BlobRef` class instance. During the
* migration the video embed path still yields these (its blob comes from the
* not-yet-migrated `app.bsky.video.getJobStatus` bridge call), so we must keep
* handling them here even though the composer's own uploads are now plain lex
* blobs. A class instance is duck-typed by its `ipld()` method plus the
* `ref`/`mimeType` fields; it has NO `$type` and a non-plain prototype, so it
* would otherwise slip past both `isBlobRef` and `isPlainObject` and be encoded
* wrong - silently breaking video reply-chain CIDs.
*/
function isBlobRefInstance(
v: unknown,
): v is {ipld: () => unknown; ref: unknown; mimeType: unknown} {
if (v == null || typeof v !== 'object') return false
const o = v as Record<string, unknown>
return typeof o.ipld === 'function' && 'ref' in o && 'mimeType' in o
}
// Returns a transformed version of the object for use in DAG-CBOR.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function prepareForHashing(v: any): any {
@@ -87,16 +68,6 @@ function prepareForHashing(v: any): any {
return v
}
/*
* A legacy `BlobRef` class instance must be converted via `ipld()` to the
* plain `{$type, ref, mimeType, size}` object; encoding the instance directly
* would emit its internal `original` field and omit `$type`, producing the
* wrong CID. `ipld()` returns exactly what `isBlobRef` accepts above.
*/
if (isBlobRefInstance(v)) {
return v.ipld()
}
// Walk through arrays
if (Array.isArray(v)) {
let pure = true
+3 -4
View File
@@ -402,10 +402,9 @@ async function resolveMedia(
return {
$type: 'app.bsky.embed.video',
/*
* The video blob is a legacy `@atproto/api` BlobRef from the not-yet
* -migrated video pipeline (getJobStatus, in composer state/video). Its
* structural shape matches the lexicon blob field; the CID hasher handles
* both class instances and plain lex blobs (see computeCid).
* The video blob is a plain lex blob from the video pipeline
* (getJobStatus, in composer state/video). Its structural shape matches
* the lexicon blob field and the CID hasher (see computeCid).
*/
video: videoDraft.pendingPublish.blobRef,
alt: videoDraft.altText || undefined,