Skip to content

feat(kernel): register the real PhotoCameraAdapter as adapterType "photo" (G-8b, fail-loud, no mock fallback) - #332

Draft
LamaSu wants to merge 3 commits into
masterfrom
fix/photo-camera-adapter-registration
Draft

feat(kernel): register the real PhotoCameraAdapter as adapterType "photo" (G-8b, fail-loud, no mock fallback)#332
LamaSu wants to merge 3 commits into
masterfrom
fix/photo-camera-adapter-registration

Conversation

@LamaSu

@LamaSu LamaSu commented Sep 8, 2026

Copy link
Copy Markdown
Owner

Closes memo sub-blocker G-8b (PhotoCameraAdapter exported but not registered by the standard factory). Ledger items: G-8 / MS-11 / LO-SE-1 precondition. Bounded implementer run on the DGX Spark (photo-camera-reg-implementer-1), branch off lamasu/master 7a86491.

What changes (packages/kernel only, +601/-1, no new deps)

  • AdapterType gains "photo"; registerCameraAdapter("photo", buildPhotoCamera) next to the other built-ins.
  • buildPhotoCamera constructs the real PhotoCameraAdapter with a hash-only PhotoCaptureService (the routes/photo-verification.ts:18 precedent; the factory is sync and cannot receive the async evidence storage). Gemini is opt-in only (cfg.gemini: true + key, or an injected compare() service); gemini:true with no key throws rather than silently degrading while stamping gemini-2.0-flash provenance.
  • Misconfiguration throws a clear error naming the device; no mock fallback (repo unknownAdapterTypeError contract). mockMode:true still yields the mock camera.

Negative control (memo: a missing photo must fail loud, never fabricate) packages/kernel/src/__tests__/photo-camera-registration.test.ts:102-112: the factory-built adapter with no bytes pushed rejects captureSnapshot() with No image bytes available and emits zero evidence events.

Verification

  • Agent: pnpm --filter @pcc/kernel test 38 files / 875 passed; tsc --noEmit clean. 12 new tests.
  • Steward re-run on the Spark worktree at 9a24656: photo-camera-registration + adapter-factory + adapter-honesty = 87/87 passed (519 ms).

Honest gaps (report in ai/lane-runs/photo-camera-reg/REPORT.md)

  1. The adapter is push-fed; nothing on the job path calls setNextCapture() yet, so a tier-2 job with a photo camera fails closed at the before-snapshot (correct, but not usable end-to-end). Hook candidates: job-runner.ts:121-127 (load-bearing), routes/photo-verification.ts:75-92 (POST /api/photo/upload already has the bytes).
  2. Job-nonce binding of the photo (memo CP-0 measurement (c)) is NOT done: the hash binds device/kernel only; a photo for job A replays into job B.
  3. pcc-node registers detected cameras as adapterType: "camera" (unregistered) and has no upload path.
  4. packages/spec has a divergent AdapterType union and no photo entry in ADAPTER_DEFAULT_MANIFESTS (one-line data additions, left for composition/spec owners).

Reviewers: sensors 7a438686, gateway 0600b204. Steward 57c2a412 graded. Operator merges (no lane merges to master).

🤖 Generated with Claude Code

https://claude.ai/code/session_012PxrWRej6UAvZepXkpxJPi

LamaSu and others added 3 commits September 8, 2026 15:53
…rType "photo"

PhotoCameraAdapter was exported but constructed nowhere: a kernel config could
not name the real photo-evidence camera, so operators had only "mock". Adds
"photo" to the AdapterType union and a buildPhotoCamera factory registered
alongside the other camera built-ins.

- Storage-less PhotoCaptureService (hash-derived `photo:sha256:...` ref), the
  same choice routes/photo-verification.ts makes. createEvidenceStorage() is
  async while CameraAdapterFactory is sync, and the service's byte-upload path
  would label a hash-derived id `storacha://` — misstated provenance.
- Gemini is opt-in only: `config.gemini: true` (key from config.geminiApiKey or
  GEMINI_API_KEY) or an injected service. A keyless service silently degrades to
  local pHash+SSIM while the adapter still stamps model "gemini-2.0-flash" on
  the emitted evidence, so a missing key throws rather than downgrades.
- Misconfiguration throws a clear error naming the device; no mock fallback.
  globalMockMode=true still yields MockCameraAdapter (existing contract).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…l-loud capture

12 tests in photo-camera-registration.test.ts:
- "photo" is registered; createCameraAdapter returns a real PhotoCameraAdapter
  (not MockCameraAdapter), with source.deviceType "camera" and the device id.
- NEGATIVE CONTROL: factory-built adapter with no bytes pushed rejects
  captureSnapshot() with /No image bytes available/ and emits ZERO events, so
  nothing fabricated can enter a signed bundle. A consumed capture is not reused.
- Push-fed happy path: setNextCapture(bytes) -> captureSnapshot() returns an
  imageHash equal to node:crypto SHA-256 of those exact bytes, plus a
  hash-derived storageRef; distinct bytes yield distinct hashes.
- createAdaptersFromConfig: mockMode:false -> one PhotoCameraAdapter;
  mockMode:true -> MockCameraAdapter (global-mock contract preserved).
- Gemini: an injected service is actually used by runInspection; gemini:true
  with no key throws; a malformed gemini/geminiApiKey throws, never mocks.

Also extends the built-ins assertion in adapter-factory.test.ts and the
supported-adapterType list in kernel-config.test.ts with "photo".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…gistration

Outcome, design note, files changed with line ranges, raw test + typecheck
tails, the quoted negative-control assertion, and honest gaps: no job-path
setNextCapture caller yet (hook site named at job-runner.ts:121-127), no
job-nonce binding (CP-0 (c)), no pcc-node equivalent, and the untouched
packages/spec adapterType/manifest divergence.

ai/ is gitignored; force-added so the report travels with the branch bundle,
matching ai/lane-runs/jobrunner-safety/REPORT.md in history.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant