Files
suaveplan/openspec/changes/exif-completion/tasks.md
2026-05-27 23:29:53 +01:00

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.md lines 37, 38, 43; that's the failure mode this checklist guards against.

0. Pre-flight (non-negotiable)

  • 0.1 Confirm clean working tree (git status reports nothing) and no in-flight worktrees with uncommitted work (git worktree list then git status in 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.md matches find packages -maxdepth 3 -name package.json count. 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 dedicated enterprise/exif.md exists)
  • 1.2 Re-read openspec/archive/2026-04-19-enterprise-exif/tasks.md and specs/exif/spec.md to confirm what was claimed but not delivered
  • 1.3 Read openspec/AGENTS.md Sections 0a (parity), 13 (verification), 14 (archival), 14a (affected dependents)
  • 1.4 Draft proposal.md from openspec/templates/proposal.md; the Why section explicitly cites the archive discrepancy (lines 37, 38, 43 of the archived tasks.md)
  • 1.5 Fill in ## Source mapping table — every requirement cites a line range OR appears in the "no source line" table with justification
  • 1.6 Draft specs/exif/spec.md from openspec/templates/spec-delta.md covering all six ADDED Requirements
  • 1.7 Every requirement in the spec delta carries a **Verified by:** line naming concrete it(...) titles
  • 1.8 openspec change validate exif-completion exits with no errors

2. MakerNotes registry + decoders

  • 2.1 Define MakerNoteDecoder interface in packages/image/exif/src/makernotes/types.ts
  • 2.2 Port the dispatcher makernotes/dispatch/dispatch.ts (L1-L78) into packages/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 stub
    • fujifilm/fujifilm.ts (L1-L130)
    • kodak/kodak.ts (L1-L6) — vendor-detection stub
    • leica/leica.ts (L1-L6) — vendor-detection stub
    • minolta/minolta.ts (L1-L6) — vendor-detection stub
    • nikon/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 stub
    • samsung/samsung.ts (L1-L9)
    • sigma/sigma.ts (L1-L6) — vendor-detection stub
    • sony/sony.ts (L1-L14)
    • generic/generic.ts (L1-L13)
  • 2.4 decodeMakerNotes(buffer, byteOrder, makeTag, modelTag?) dispatcher — case-insensitive Make-prefix match, falls through to generic
  • 2.5 Add EXIF.MAKERNOTE_VENDOR_UNKNOWN, EXIF.MAKERNOTE_DECRYPT_FAILED, EXIF.MAKERNOTE_INVALID_HEADER to error-codes.ts
  • 2.6 Sibling .md for each implementation file (≥200 words, basic + advanced examples)

3. Vendor ciphers

  • 3.1 Port decryptNikonMakerNote from makernotes/nikon-cipher/nikon-cipher.ts (L1-L84) into packages/image/exif/src/makernotes/nikon-cipher/nikon-cipher.ts
  • 3.2 Port decryptSonyMakerNote from makernotes/sony-cipher/sony-cipher.ts (L1-L29) into packages/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 parseBplist from makernotes/apple-bplist/apple-bplist.ts (L1-L170) into packages/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_RANGE to error-codes.ts
  • 4.4 Wire parseBplist into 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) into packages/image/exif/src/icc/{header,read,write,validate,convert,tag-table}/
  • 5.2 Define IccProfile, IccHeader, IccTagEntry, IccProfileClass, IccColorSpace, IccRenderingIntent, IccTagSignature types in src/icc/types.ts
  • 5.3 Public API: parseIccProfile, writeIccProfile, validateIccProfile, plus convert helpers classifyProfile, 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_FAILED to error-codes.ts
  • 5.5 Wire ICC into readMetadata: when an ICC profile segment is present in JPEG (APP2 / ICC_PROFILE\0) or PNG (iCCP chunk), populate metadata.icc
  • 5.6 Add includeIcc?: boolean to ReadOptions (default true)
  • 5.7 Sibling .md for each implementation file

6. GPS module

  • 6.1 Port gps/convert/convert.ts (L1-L41) into packages/image/exif/src/gps/convert/convert.ts
  • 6.2 Port gps/timestamp/timestamp.ts (L1-L38) into packages/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_RANGE to error-codes.ts
  • 6.5 Sibling .md

7. Public surface

  • 7.1 package.json exports map gains ./makernotes, ./icc, ./gps, ./bplist subpaths
  • 7.2 Root index.ts re-exports decodeMakerNotes, parseIccProfile/writeIccProfile/validateIccProfile, GPS helpers, and parseBplist
  • 7.3 ReadOptions gains includeMakerNote?: boolean and includeIcc?: boolean (both default true)
  • 7.4 ImageMetadata gains optional icc?: IccProfile
  • 7.5 readMetadata decodes MakerNote when Make tag present; falls through to raw bytes when includeMakerNote: 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 — MakerNoteDecoder interface pinned for every vendor; MAKERNOTE_VENDOR_UNKNOWN raised in strict mode
  • 8.3 src/makernotes/<vendor>/<vendor>.test.ts for 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_RANGE paths
  • 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, ./bplist resolve
  • 8.17 Every it(...) carries { timeout: 5000 }

9. Documentation

  • 9.1 Sibling .md for every new implementation .ts (≥200 words, basic + advanced examples, full API reference)
  • 9.2 README links every co-located .md under ## Modules
  • 9.3 Update CLAUDE.md exif package 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, no error findings
  • 10.5 bunx turbo build --filter=@suaveplan/exif — green

11. Pre-archive 7-row checklist (AGENTS.md Section 13)

  • 11.1 Generics threaded; expectTypeOf assertions on MakerNoteData, IccProfile, GpsCoordinate, BplistValue discriminated 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: true vs false; includeIcc: true vs false)
  • 11.5 OTel histograms emitted for exif.makernote.decode_ms, exif.icc.parse_ms, exif.gps.convert_ms, asserted with a real in-memory MeterProvider
  • 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's proposal.md) pointing at this change as the actual delivery of §7.1, §7.2, §8.1
  • 12.4 Refresh MANIFEST.md if the package's exports list materially changed
  • 12.5 Update STATUS.md if 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/ has binary/, core/, exif/, format/, ifd/, iptc/, jpeg/, xmp/, errors/, types.ts. No makernotes/, no icc/, no gps/, no bplist/.
  • 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 in errors/exif-error.ts.

Missing pieces:

  1. makernotes/ directory with dispatcher + 18 vendor decoders + Nikon/Sony cipher pair + Apple bplist parser.
  2. icc/ module (six files: header, read, write, validate, convert, tag-table) wired into readMetadata for JPEG APP2 and PNG iCCP.
  3. gps/ module (DMS↔decimal conversion + timestamp parsing) plus ReadOptions flag 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).