12 KiB
12 KiB
Tasks — EXIF completion (MakerNotes / ICC / GPS / Apple bplist / vendor ciphers)
Every checkbox MUST reflect work actually done in this change. Do not retroactively tick boxes for work that was claimed but never landed. The MakerNotes / ICC / GPS gaps closed here were prematurely ticked in
openspec/archive/2026-04-19-enterprise-exif/tasks.mdlines 37, 38, 43; that's the failure mode this checklist guards against.
0. Pre-flight (non-negotiable)
- 0.1 Confirm clean working tree (
git statusreports nothing) and no in-flight worktrees with uncommitted work (git worktree listthengit statusin each) - 0.2 If any worktrees exist under
.claude/worktrees/, account for each one: still-active agent, work-pending-merge, or stale-and-prune. Stale worktrees MUST be removed (git worktree remove) before opening a new one — code that has been lost twice on main is the failure mode this guards against. - 0.3 Confirm the package set in
MANIFEST.mdmatchesfind packages -maxdepth 3 -name package.jsoncount. Drift here means a prior change archived without regenerating the manifest.
1. Spec authoring
- 1.1 Read
../suaveplan/packages/universal/exif/src/{makernotes,icc,gps}end-to-end (no dedicatedenterprise/exif.mdexists) - 1.2 Re-read
openspec/archive/2026-04-19-enterprise-exif/tasks.mdandspecs/exif/spec.mdto confirm what was claimed but not delivered - 1.3 Read
openspec/AGENTS.mdSections 0a (parity), 13 (verification), 14 (archival), 14a (affected dependents) - 1.4 Draft
proposal.mdfromopenspec/templates/proposal.md; the Why section explicitly cites the archive discrepancy (lines 37, 38, 43 of the archivedtasks.md) - 1.5 Fill in
## Source mappingtable — every requirement cites a line range OR appears in the "no source line" table with justification - 1.6 Draft
specs/exif/spec.mdfromopenspec/templates/spec-delta.mdcovering all six ADDED Requirements - 1.7 Every requirement in the spec delta carries a
**Verified by:**line naming concreteit(...)titles - 1.8
openspec change validate exif-completionexits with no errors
2. MakerNotes registry + decoders
- 2.1 Define
MakerNoteDecoderinterface inpackages/image/exif/src/makernotes/types.ts - 2.2 Port the dispatcher
makernotes/dispatch/dispatch.ts(L1-L78) intopackages/image/exif/src/makernotes/dispatch/dispatch.ts - 2.3 Port the 18 vendor decoders (one file each):
apple/apple.ts(L1-L126)canon/canon.ts(L1-L139)casio/casio.ts(L1-L6) — vendor-detection stubfujifilm/fujifilm.ts(L1-L130)kodak/kodak.ts(L1-L6) — vendor-detection stubleica/leica.ts(L1-L6) — vendor-detection stubminolta/minolta.ts(L1-L6) — vendor-detection stubnikon/nikon.ts(L1-L155)olympus/olympus.ts(L1-L129)panasonic/panasonic.ts(L1-L133)pentax/pentax.ts(L1-L140)ricoh/ricoh.ts(L1-L6) — vendor-detection stubsamsung/samsung.ts(L1-L9)sigma/sigma.ts(L1-L6) — vendor-detection stubsony/sony.ts(L1-L14)generic/generic.ts(L1-L13)
- 2.4
decodeMakerNotes(buffer, byteOrder, makeTag, modelTag?)dispatcher — case-insensitive Make-prefix match, falls through togeneric - 2.5 Add
EXIF.MAKERNOTE_VENDOR_UNKNOWN,EXIF.MAKERNOTE_DECRYPT_FAILED,EXIF.MAKERNOTE_INVALID_HEADERtoerror-codes.ts - 2.6 Sibling
.mdfor each implementation file (≥200 words, basic + advanced examples)
3. Vendor ciphers
- 3.1 Port
decryptNikonMakerNotefrommakernotes/nikon-cipher/nikon-cipher.ts(L1-L84) intopackages/image/exif/src/makernotes/nikon-cipher/nikon-cipher.ts - 3.2 Port
decryptSonyMakerNotefrommakernotes/sony-cipher/sony-cipher.ts(L1-L29) intopackages/image/exif/src/makernotes/sony-cipher/sony-cipher.ts - 3.3 Wire ciphers into the Nikon and Sony decoders; both decoders accept an injectable cipher function (default = the bundled one) for testability
- 3.4 KAT vector tests against legacy implementation
- 3.5 Sibling
.md
4. Apple bplist parser
- 4.1 Port
parseBplistfrommakernotes/apple-bplist/apple-bplist.ts(L1-L170) intopackages/image/exif/src/makernotes/apple-bplist/apple-bplist.ts - 4.2 Define typed value tree:
BplistValue = { type: 'dict' | 'array' | 'string' | 'integer' | 'real' | 'boolean' | 'date' | 'data' | 'uid' | 'null'; value: … } - 4.3 Add
EXIF.BPLIST_VERSION_UNSUPPORTED,EXIF.BPLIST_TRAILER_INVALID,EXIF.BPLIST_OBJECT_REF_OUT_OF_RANGEtoerror-codes.ts - 4.4 Wire
parseBplistinto the Apple decoder for HEIC + iPhone-shot JPEG - 4.5 Sibling
.md
5. ICC profile module
- 5.1 Port
icc/header/header.ts(L1-L121),icc/read/read.ts(L1-L165),icc/write/write.ts(L1-L187),icc/validate/validate.ts(L1-L37),icc/convert/convert.ts(L1-L51),icc/tag-table/tag-table.ts(L1-L63) intopackages/image/exif/src/icc/{header,read,write,validate,convert,tag-table}/ - 5.2 Define
IccProfile,IccHeader,IccTagEntry,IccProfileClass,IccColorSpace,IccRenderingIntent,IccTagSignaturetypes insrc/icc/types.ts - 5.3 Public API:
parseIccProfile,writeIccProfile,validateIccProfile, plus convert helpersclassifyProfile,isSRGB,isAdobeRGB,isDisplayP3,profileDescription - 5.4 Add
EXIF.ICC_HEADER_TOO_SHORT,EXIF.ICC_PROFILE_TOO_SHORT,EXIF.ICC_TAG_OFFSET_OUT_OF_RANGE,EXIF.ICC_INVALID_SIGNATURE,EXIF.ICC_VALIDATION_FAILEDtoerror-codes.ts - 5.5 Wire ICC into
readMetadata: when an ICC profile segment is present in JPEG (APP2 / ICC_PROFILE\0) or PNG (iCCPchunk), populatemetadata.icc - 5.6 Add
includeIcc?: booleantoReadOptions(defaulttrue) - 5.7 Sibling
.mdfor each implementation file
6. GPS module
- 6.1 Port
gps/convert/convert.ts(L1-L41) intopackages/image/exif/src/gps/convert/convert.ts - 6.2 Port
gps/timestamp/timestamp.ts(L1-L38) intopackages/image/exif/src/gps/timestamp/timestamp.ts - 6.3 Public API:
gpsCoordinatesToDecimal,decimalToGpsCoordinates,parseGpsTimestamp,gpsAltitudeToMeters - 6.4 Add
EXIF.GPS_INVALID_REF,EXIF.GPS_OUT_OF_RANGEtoerror-codes.ts - 6.5 Sibling
.md
7. Public surface
- 7.1
package.jsonexportsmap gains./makernotes,./icc,./gps,./bplistsubpaths - 7.2 Root
index.tsre-exportsdecodeMakerNotes,parseIccProfile/writeIccProfile/validateIccProfile, GPS helpers, andparseBplist - 7.3
ReadOptionsgainsincludeMakerNote?: booleanandincludeIcc?: boolean(both defaulttrue) - 7.4
ImageMetadatagains optionalicc?: IccProfile - 7.5
readMetadatadecodes MakerNote whenMaketag present; falls through to raw bytes whenincludeMakerNote: false(back-compat)
8. Tests
- 8.1
src/makernotes/dispatch/dispatch.test.ts— vendor selection by Make tag; fall-through to generic - 8.2
src/makernotes/dispatch/dispatch.spec-conformance.test.ts—MakerNoteDecoderinterface pinned for every vendor;MAKERNOTE_VENDOR_UNKNOWNraised in strict mode - 8.3
src/makernotes/<vendor>/<vendor>.test.tsfor all 18 vendors — happy path against fixture - 8.4
src/makernotes/nikon-cipher/nikon-cipher.test.ts— KAT vectors - 8.5
src/makernotes/sony-cipher/sony-cipher.test.ts— KAT vectors - 8.6
src/makernotes/apple-bplist/apple-bplist.test.ts— bplist00 v0/v1 fixtures - 8.7
src/makernotes/apple-bplist/apple-bplist.spec-conformance.test.ts—BPLIST_VERSION_UNSUPPORTED,BPLIST_TRAILER_INVALID,BPLIST_OBJECT_REF_OUT_OF_RANGEpaths - 8.8
src/icc/read/read.test.ts— sRGB and Display-P3 fixtures - 8.9
src/icc/write/write.test.ts— round-trip byte-equal - 8.10
src/icc/validate/validate.test.ts— valid + 3 truncation variants - 8.11
src/icc/convert/convert.test.ts—classifyProfile,isSRGB,isAdobeRGB,isDisplayP3 - 8.12
src/icc/header/header.spec-conformance.test.ts—ICC_HEADER_TOO_SHORT - 8.13
src/gps/convert/convert.test.ts— round-trip decimal ↔ DMS within 1e-9; altitude positive vs negative ref - 8.14
src/gps/convert/convert.spec-conformance.test.ts—GPS_INVALID_REF,GPS_OUT_OF_RANGE - 8.15
src/gps/timestamp/timestamp.test.ts— date+time → Date; midnight/midday boundaries - 8.16
tests/dep-graph.test.ts— subpath exports./makernotes,./icc,./gps,./bplistresolve - 8.17 Every
it(...)carries{ timeout: 5000 }
9. Documentation
- 9.1 Sibling
.mdfor every new implementation.ts(≥200 words, basic + advanced examples, full API reference) - 9.2 README links every co-located
.mdunder## Modules - 9.3 Update
CLAUDE.mdexifpackage listing to reflect the restored surface (MakerNotes for 18 vendors, ICC read/write/validate/convert, GPS, Apple bplist, Nikon + Sony ciphers)
10. Pre-archive verification (the 5-gate triple)
- 10.1
bun run check— Biome 0/0/0 across the monorepo - 10.2
bunx turbo lint typecheck test --filter=@suaveplan/exif— green - 10.3
bunx turbo test:coverage --filter=@suaveplan/exif— 100/100/100/100 - 10.4
bun scripts/openspec-conformance.ts @suaveplan/exif— exit 0, noerrorfindings - 10.5
bunx turbo build --filter=@suaveplan/exif— green
11. Pre-archive 7-row checklist (AGENTS.md Section 13)
- 11.1 Generics threaded;
expectTypeOfassertions onMakerNoteData,IccProfile,GpsCoordinate,BplistValuediscriminated unions - 11.2 Zod schemas wired at every declared boundary (
MakerNoteData,IccProfile, GPS inputs,ReadOptions) - 11.3 Every
EXIF.*code added in §2.5 / 3 / 4.3 / 5.4 / 6.4 thrown by at least one production path; no orphans - 11.4 Strict-mode behaviors tested both on and off (e.g.
includeMakerNote: truevsfalse;includeIcc: truevsfalse) - 11.5 OTel histograms emitted for
exif.makernote.decode_ms,exif.icc.parse_ms,exif.gps.convert_ms, asserted with a real in-memoryMeterProvider - 11.6 Lifecycle dedup tested: same image decoded twice produces identical output (deterministic; ciphers seeded from input only, no module-scope state)
- 11.7 No retroactive tick — every box above reflects work done in this change
12. Archive
- 12.1 Fold spec delta into
openspec/specs/exif/spec.md(creating any missing requirements; existing requirements claimed in 2026-04-19 archive but never delivered are now backed by code) - 12.2 Move
openspec/changes/exif-completion/→openspec/archive/<yyyy-mm-dd>-exif-completion/ - 12.3 Add a note to
openspec/archive/2026-04-19-enterprise-exif/tasks.md(or to the archive'sproposal.md) pointing at this change as the actual delivery of §7.1, §7.2, §8.1 - 12.4 Refresh
MANIFEST.mdif the package's exports list materially changed - 12.5 Update
STATUS.mdif the package was previously listed as Pending
Audit Note (2026-05-02)
Audit verdict: DEFERRED — 0/78 tasks evidenced in code.
Evidence reviewed:
packages/image/exif/src/hasbinary/,core/,exif/,format/,ifd/,iptc/,jpeg/,xmp/,errors/,types.ts. Nomakernotes/, noicc/, nogps/, nobplist/.- The 18 vendor MakerNote decoders, the dispatcher, the Nikon + Sony ciphers, the Apple bplist parser, the ICC profile module (header/read/write/validate/convert/tag-table), and the GPS module are all absent.
- None of the new
EXIF.*error codes (MAKERNOTE_, BPLIST_, ICC_, GPS_) appear inerrors/exif-error.ts.
Missing pieces:
makernotes/directory with dispatcher + 18 vendor decoders + Nikon/Sony cipher pair + Apple bplist parser.icc/module (six files: header, read, write, validate, convert, tag-table) wired intoreadMetadatafor JPEGAPP2and PNGiCCP.gps/module (DMS↔decimal conversion + timestamp parsing) plusReadOptionsflag additions and the corresponding error codes.
Decision: leave change open. The MakerNotes block alone is 18+ vendor files with KAT cipher tests, plus the ICC and GPS modules — substantial implementation work flagged by the prior audit (entries 37/38/43 of 2026-04-19-enterprise-exif/tasks.md were ticked but never delivered).