Benchtop Tool — capture and submit a lab witness against an energy summit

GFTCL-LION-ENERGY-LEDGER-001 / benchtop tool. The operator-facing or lab-facing workflow that lets an experimenter run an energy-summit protocol locally, lock the pre-registration at the moment the run starts, and submit a witness package to the cell. Primary surface: headless CLI (M8BenchtopWitness) for lab-side / scripted use. Same benchtop_sessions substrate whether the witness is captured from terminal or HTTP membrane.

The integrity property the tool exists to enforce

A lab cannot retro-fit its pre-registration to its result. The substrate trigger trig_benchtop_preregistration_immutable refuses any UPDATE that touches preregistration_sha256, preregistration_at_iso, started_at_iso, or session_hash after a session is started. The pre-registration is locked at the call instant of startSession(…), and from there it is the ground truth the operator's verdict is graded against.

This is the same discipline the rest of the cell runs under (immutable witness columns on alert_queue / energy_ledger_receipts / summit_states), now applied to the lab-side capture surface. The benchtop tool structurally cannot let a lab cheat the pre-registration; the SQL layer refuses.

Operator surface

Surface When to use How
M8BenchtopWitness CLI Lab-side; headless; scriptable; CI/CD integration swift run M8BenchtopWitness <cmd> ...
HTTP membrane Remote lab submission POST /energy/summits/{slug}/witness (see Energy-Ledger-Breaker-Replication)

All paths read and write the same benchtop_sessions table via BenchtopSessionStore. There is no second source of truth.

CLI workflow

# 1) List open summits
swift run M8BenchtopWitness list

# 2) Inspect a summit's pre-registered fields
swift run M8BenchtopWitness show --summit benchtop_divergence

# 3) Start a session (PRE-REGISTRATION LOCKED AT THIS INSTANT)
swift run M8BenchtopWitness start \
  --summit benchtop_divergence \
  --lab-identity "MIT-Plasma-Lab-2026" \
  --apparatus-id "rev-A-serial-0042" \
  --notes "blinded by independent witness Dr. X"
# → prints session_id (e.g., bts-B1E4AE91-...)

# 4) Record measured values (JSON file matching the predicted-values names)
swift run M8BenchtopWitness measure \
  --session-id bts-... \
  --measured ./measured.json

# 5) Mark ready to submit
swift run M8BenchtopWitness ready --session-id bts-...

# 6a) EITHER export the witness JSON for offline submission
swift run M8BenchtopWitness export --session-id bts-... > witness.json

# 6b) OR submit directly to the local endpoint
swift run M8BenchtopWitness submit \
  --session-id bts-... \
  --endpoint http://127.0.0.1:8423 \
  --membrane-token "$ENERGY_LAB_TOKEN"

# 7) Inspect a session at any time
swift run M8BenchtopWitness show --session-id bts-...

A measured.json file looks like:

[
  {"name": "thermal_suppression_pct",     "measured": 12.4, "instrument": "FLIR-T1020"},
  {"name": "clock_offset_slope",           "measured": 0.47, "instrument": "Microsemi-5071A"},
  {"name": "controls_match_classical",     "measured": true, "instrument": "calorimetry-baseline"}
]

The name field must match an entry in the summit's predicted_values_json (visible via show --summit <slug>). Free-form fields beyond the predicted set are allowed and preserved in the submission.

HTTP membrane workflow

After witness produces a ready package, submit to the cell:

curl -X POST "https://<cell-host>/energy/summits/benchtop_divergence/witness" \
  -H "Authorization: Bearer <membrane-token>" \
  -H "Content-Type: application/json" \
  -d @witness.json

On 202 Accepted, the session flips to submitted (frozen by trigger). Pre-registered prediction, success criterion, and null-result clause are visible via show --summit <slug> before start.

Session lifecycle

   startSession()              updateMeasured()
        │                              │
        ▼                              ▼
┌───────────────┐  ────────►  ┌────────────────┐
│  in_progress  │  ──────────►│ ready_to_submit │
└───────┬───────┘             └────────┬────────┘
        │                              │
        │   POST /energy/...witness    │
        │   202 accepted               │
        ▼                              ▼
┌───────────────┐             ┌────────────────┐
│   abandoned   │             │    submitted   │  ← terminal, FROZEN by trigger
└───────────────┘             └────────────────┘
                                       │
                                       │ on operator seal (sealed_confirm | sealed_refute)
                                       ▼
                              ┌────────────────┐
                              │  summit status │
                              │  transitions   │
                              └────────────────┘

A submitted session row is frozen by trig_benchtop_terminal_frozen — no further UPDATEs accepted. The corresponding lab_submissions.submission_id links the two records; the operator's subsequent seal of the submission drives the summit_states.status transition (inReplicationclosedConfirmed | closedRefuted).

Substrate schema (V120)

CREATE TABLE benchtop_sessions (
  session_id                TEXT PRIMARY KEY,
  summit_id                 TEXT NOT NULL REFERENCES summit_states(summit_id),
  lab_identity              TEXT NOT NULL,
  apparatus_id              TEXT NOT NULL DEFAULT '',
  apparatus_calibration_json TEXT NOT NULL DEFAULT '{}',
  measured_values_json      TEXT NOT NULL DEFAULT '[]',
  preregistration_sha256    TEXT NOT NULL,              -- IMMUTABLE
  preregistration_at_iso    TEXT NOT NULL,              -- IMMUTABLE
  status                    TEXT NOT NULL DEFAULT 'in_progress',
  started_at_iso            TEXT NOT NULL,              -- IMMUTABLE
  last_updated_at_iso       TEXT NOT NULL,
  submitted_submission_id   TEXT,
  session_hash              TEXT NOT NULL,              -- IMMUTABLE
  notes                     TEXT NOT NULL DEFAULT '',
  timestamp_iso             TEXT NOT NULL,
  CHECK (status IN ('in_progress','ready_to_submit','submitted','abandoned'))
);

Two triggers enforce the discipline:

-- Pre-registration anchor + start time + session hash are immutable
CREATE TRIGGER trig_benchtop_preregistration_immutable BEFORE UPDATE ON benchtop_sessions
  ... RAISE(ABORT, 'benchtop_sessions pre-registration anchor is immutable');

-- A submitted session is fully frozen
CREATE TRIGGER trig_benchtop_terminal_frozen BEFORE UPDATE ON benchtop_sessions
  WHEN OLD.status = 'submitted' BEGIN
    RAISE(ABORT, 'benchtop_sessions terminal state submitted — row is frozen');
  END;

Verification (this commit)

The CLI has been exercised end-to-end against the live substrate:

1. ✓ list enumerates the three energy summits.

2. ✓ show --summit benchtop_divergence prints the pre-registered predicted-values schema.

3. ✓ start locks the pre-registration anchor and prints the session_id + sha + timestamp.

4. ✓ measure persists measured values from a JSON file.

5. ✓ ready transitions the session to ready_to_submit.

6. ✓ export prints the witness submission JSON to stdout (verified to include lab_identity, apparatus_id, measured_values, preregistration_sha256, preregistration_at_iso, session_hash, blinding_attestation).

7. ✓ Retro-fit attempt REFUSED: UPDATE benchtop_sessions SET preregistration_sha256='tampered' produces Error: benchtop_sessions pre-registration anchor is immutable (19) — the trigger fires.

The SwiftUI sheet compiles clean and is wired to EnergyDomainPanel's summit cards via .sheet(item: $benchtopSummit).

Constitutional posture

What this tool is NOT

Next moves (named)

© 2026 Richard Gillespie. All rights reserved. USPTO patent applications 19/460,960 and 19/096,071.

Federation cosignature: pending operator signing host (v26). Witness (sha256 of rendered body): c295e6eac8e917a5e87624824b06372a28fad6033b77d3113f6c8583d82fa116. This page serves with a substrate-honest pending-signature notice until the operator's Franklin signer cosigns it.