From 56297ddabd3a010a6f2c6098d75fcab8ce6a0b81 Mon Sep 17 00:00:00 2001 From: globalmysterysnailrevolution Date: Thu, 24 Sep 2026 16:20:26 -0700 Subject: [PATCH 1/2] feat(spec): default emitter manifests for the opentrons and hamilton adapters The kernel's adapter factory registers opentrons and hamilton, but spec shipped no default emitter manifest for either, so auto-onboarding wrote free-text tier-0 evidence for liquid handlers and the provenance planner saw them supply nothing (the lab vertical capped at tier 0). Both get the kernel receipt core and nothing more, because that is what the code emits: - the kernel's EvidenceEmitter signs every job-step bundle and marks mock/simulated events fabricated (kernel/src/evidence-emitter.ts), which is the basis of receipt.kernel_signed, ident.registered_key and confirm.execution_mode for any kernel adapter; - the real Opentrons path emits protocol_uploaded and run_action; Hamilton emits execution_started/completed/failed; - neither feeds LogCaptureService (only printer-log-adapter does), so no machine.execution_log is claimed, and neither emits telemetry. PyLabRobot is not a kernel AdapterType, so it gets no entry. A kernel test now fails if any registered machine adapter lacks a default manifest (it fails on master with [hamilton, opentrons]). The spec tests' adapter lists gain both, including tier-1 eligibility by default. Tests: spec 797/797, tsc clean; kernel coverage + adapter-factory 50/50. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01PkeiQMnVePeBGU9svhREFy --- .../adapter-manifest-coverage.test.ts | 26 +++++++++++++++++++ .../src/__tests__/emitter-manifest.test.ts | 4 +-- .../spec/src/evidence/adapter-manifests.ts | 22 +++++++++++++--- 3 files changed, 47 insertions(+), 5 deletions(-) create mode 100644 packages/kernel/src/__tests__/adapter-manifest-coverage.test.ts diff --git a/packages/kernel/src/__tests__/adapter-manifest-coverage.test.ts b/packages/kernel/src/__tests__/adapter-manifest-coverage.test.ts new file mode 100644 index 000000000..cf121308e --- /dev/null +++ b/packages/kernel/src/__tests__/adapter-manifest-coverage.test.ts @@ -0,0 +1,26 @@ +/** + * Every machine adapter the kernel can build has a default evidence-emitter + * manifest in @pcc/spec. Without one, auto-onboarding writes free-text tier-0 + * evidence for the device and the provenance planner sees it supply nothing, + * which is how the liquid handlers were capped at tier 0. + */ + +import { describe, it, expect } from "vitest"; +import { ADAPTER_DEFAULT_MANIFESTS } from "@pcc/spec"; +import { listRegisteredMachineAdapters } from "../adapter-factory.js"; + +describe("adapter default manifests cover the kernel's machine adapters", () => { + it("has a default manifest for every registered machine adapter", () => { + const registered = listRegisteredMachineAdapters(); + expect(registered.length).toBeGreaterThan(0); + const missing = registered.filter((type) => !(type in ADAPTER_DEFAULT_MANIFESTS)); + expect(missing).toEqual([]); + }); + + it("includes the liquid handlers", () => { + for (const type of ["opentrons", "hamilton"]) { + expect(listRegisteredMachineAdapters()).toContain(type); + expect(ADAPTER_DEFAULT_MANIFESTS[type]?.subject).toEqual({ kind: "adapter", ref: type }); + } + }); +}); diff --git a/packages/spec/src/__tests__/emitter-manifest.test.ts b/packages/spec/src/__tests__/emitter-manifest.test.ts index 265a9c7d2..0e5074e4c 100644 --- a/packages/spec/src/__tests__/emitter-manifest.test.ts +++ b/packages/spec/src/__tests__/emitter-manifest.test.ts @@ -136,7 +136,7 @@ describe("buildEvidenceTiersFromEmits — the digital-receipt core is tier-1 eli describe("adapter default manifests", () => { it("ships a manifest for every kernel adapter family", () => { - for (const t of ["octoprint", "ipp", "opcua", "sila", "modbus", "generic-http", "mock"]) { + for (const t of ["octoprint", "ipp", "opcua", "sila", "modbus", "opentrons", "hamilton", "generic-http", "mock"]) { expect(getAdapterManifest(t)).toBeDefined(); expect(EvidenceEmitterManifestSchema.safeParse(getAdapterManifest(t)).success).toBe(true); } @@ -152,7 +152,7 @@ describe("adapter default manifests", () => { }); it("every digital adapter is eligible-by-default at tier 1", () => { - for (const t of ["octoprint", "ipp", "opcua", "sila", "modbus", "generic-http"]) { + for (const t of ["octoprint", "ipp", "opcua", "sila", "modbus", "opentrons", "hamilton", "generic-http"]) { const { evidence, structured } = buildAdapterEvidence(t); expect(structured).toBe(true); const report = computeCsdEligibility({ url: `pcc://adapter/${t}`, evidence }); diff --git a/packages/spec/src/evidence/adapter-manifests.ts b/packages/spec/src/evidence/adapter-manifests.ts index a12116497..2b37d5df5 100644 --- a/packages/spec/src/evidence/adapter-manifests.ts +++ b/packages/spec/src/evidence/adapter-manifests.ts @@ -1,9 +1,11 @@ /** * Adapter + device-role DEFAULT emitter manifests — the supply-side DATA. * - * One entry per adapter family present in packages/kernel/src/adapters/ - * (octoprint / ipp / opcua / sila / modbus / generic-http / mock) and per - * evidence-only device ROLE (camera / sensor). Each declares, in the bounded + * One entry per machine adapter the kernel's factory registers + * (packages/kernel/src/adapter-factory.ts: octoprint / ipp / opcua / sila / + * modbus / opentrons / hamilton / generic-http / mock; a kernel test keeps the + * two in step) and per evidence-only device ROLE (camera / sensor). + * PyLabRobot is not a kernel AdapterType, so it has no entry here. Each declares, in the bounded * vocabulary, which primitives that adapter/role can EMIT. Auto-discovery reads * these to write structured `evidence.tierN.primitives[]` instead of free-text * `required[]`, turning an onboarded device from tier-0-capped into @@ -91,6 +93,20 @@ export const ADAPTER_DEFAULT_MANIFESTS: Readonly Date: Thu, 24 Sep 2026 16:37:12 -0700 Subject: [PATCH 2/2] fix(spec): opentrons claims only the tier-0 floor until its events map to the vocabulary (evidence review) Evidence's review of #418 (#3251): hamilton approved; opentrons changes requested. The kernel Opentrons adapter's real path emits only non-vocabulary types (protocol_uploaded, run_action, run_*) and no real completion event, so the receipt core over-claims. Its default manifest is now decl.self_attested only (tier 0), keeping the kernel coverage test honest, until evidence maps its events (offered in #3251). A spec test pins opentrons at tier 0; hamilton keeps the receipt core. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01PkeiQMnVePeBGU9svhREFy --- .../src/__tests__/emitter-manifest.test.ts | 10 +++++++- .../spec/src/evidence/adapter-manifests.ts | 25 ++++++++++--------- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/packages/spec/src/__tests__/emitter-manifest.test.ts b/packages/spec/src/__tests__/emitter-manifest.test.ts index 0e5074e4c..0e0361285 100644 --- a/packages/spec/src/__tests__/emitter-manifest.test.ts +++ b/packages/spec/src/__tests__/emitter-manifest.test.ts @@ -152,7 +152,7 @@ describe("adapter default manifests", () => { }); it("every digital adapter is eligible-by-default at tier 1", () => { - for (const t of ["octoprint", "ipp", "opcua", "sila", "modbus", "opentrons", "hamilton", "generic-http"]) { + for (const t of ["octoprint", "ipp", "opcua", "sila", "modbus", "hamilton", "generic-http"]) { const { evidence, structured } = buildAdapterEvidence(t); expect(structured).toBe(true); const report = computeCsdEligibility({ url: `pcc://adapter/${t}`, evidence }); @@ -173,6 +173,14 @@ describe("adapter default manifests", () => { expect(computeCsdEligibility({ url: "pcc://cam", evidence }).eligibleTier).toBe(1); }); + it("opentrons claims only the tier-0 floor until its events map to the vocabulary", () => { + // Its real run path emits protocol_uploaded / run_action / run_* and no + // real completion event (evidence review of #418). + const { evidence, structured } = buildAdapterEvidence("opentrons"); + expect(structured).toBe(true); + expect(computeCsdEligibility({ url: "pcc://opentrons", evidence }).eligibleTier).toBe(0); + }); + it("mock is honestly capped at tier 0 (cannot attest a real execution)", () => { const { evidence } = buildAdapterEvidence("mock"); expect(computeCsdEligibility({ url: "pcc://mock", evidence }).eligibleTier).toBe(0); diff --git a/packages/spec/src/evidence/adapter-manifests.ts b/packages/spec/src/evidence/adapter-manifests.ts index 2b37d5df5..e1d94c5bb 100644 --- a/packages/spec/src/evidence/adapter-manifests.ts +++ b/packages/spec/src/evidence/adapter-manifests.ts @@ -93,19 +93,20 @@ export const ADAPTER_DEFAULT_MANIFESTS: Readonly