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:
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user