{
 "tool": "eclipse-blackbox-mcp",
 "version": "0.1.0",
 "bench_date": "2026-10-25",
 "bench_version": "0.1.0",
 "environment": "local",
 "qualifier": "local",
 "verdict": "CERTIFIED(local)",
 "checks": [
  {
   "id": "B1",
   "name": "credential shape: agent credential has NO read/verify/status/export path; owner credential cannot append",
   "verdict": "pass",
   "evidence": "agent credential on record_read / record_verify_chain / record_status / record_export_pack: all refused OWNER_CREDENTIAL_REQUIRED \u2014 the recorded agent has no read, verify, status, or export path; owner credential on record_append refused APPEND_REQUIRES_AGENT_CREDENTIAL (history is written by submissions and the recorder itself, or not at all); missing credential -> AUTH_REQUIRED on both surfaces"
  },
  {
   "id": "B2",
   "name": "auth is not an oracle: unknown id == wrong secret, byte-identical; revoked credential appends nothing",
   "verdict": "pass",
   "evidence": "unknown agent id, wrong secret for a real agent id, and wrong secret for the owner produce BYTE-IDENTICAL refusals ('AUTH_INVALID: credential not recognized') \u2014 the auth surface is not an oracle; the revoked agent's credential is refused CREDENTIAL_REVOKED and the record count is unchanged"
  },
  {
   "id": "B3",
   "name": "cross-agent append refused by name",
   "verdict": "pass",
   "evidence": "agent A's credential naming agent B (or 'owner') as agent_id: AGENT_ID_MISMATCH both times, zero records written, agent B has no records \u2014 a submission credential appends for its own agent_id only"
  },
  {
   "id": "B4",
   "name": "grade inflation: submitter-supplied grade refused in every mode; import dressed with chain fields stays E-CLAIMED",
   "verdict": "pass",
   "evidence": "evidence_grade AND grade fields refused GRADE_NOT_ASSIGNABLE in all three modes (live / imported / agent_chained) \u2014 6/6; an imported record carrying agent chain fields still lands E-CLAIMED (dressing does not upgrade it); an unknown submission mode ('LIVE') refuses SUBMISSION_UNKNOWN \u2014 grades come from proof state only"
  },
  {
   "id": "B5",
   "name": "agent chain: valid formula + WRONG anchor refused; fork/restart refused; bad hash refused",
   "verdict": "pass",
   "evidence": "genesis-anchored chained record lands E-CHAINED; a submission whose agent_hash recomputes PERFECTLY under the published formula but over a fabricated prev (wrong anchor) refuses AGENT_CHAIN_ANCHOR_MISMATCH \u2014 the formula alone buys nothing; a GENESIS restart (fork) refuses the same way; right anchor + non-recomputing hash -> AGENT_CHAIN_INVALID; missing chain fields -> AGENT_CHAIN_FIELDS_REQUIRED; the correct continuation then lands"
  },
  {
   "id": "B6",
   "name": "deletion narrative: gap + claimed-only + conflict naming both seqs, winner unpicked",
   "verdict": "pass",
   "evidence": "bench deletion narrative: live seqs 1-3, skip to 7 -> the recorder's own signed INGEST_GAP event (missing 4-6) lands at recorder seq 7, BEFORE the seq-7 record at seq 10; imports for 4 and 5 land E-CLAIMED and coverage lists claimed_only == [4, 5] while seq 6 stays gap-only (in neither set \u2014 shown via the gap event); the contradicting claim for seq 2 produces exactly one conflict naming BOTH recorder seqs (submitted 5, claimed 11) and differing_fields [action, inputs_hash], with no winner/verdict/authoritative key \u2014 the recorder picks no winner; the live record's stored bytes are byte-identical after the claims"
  },
  {
   "id": "B7",
   "name": "exported-pack tamper battery: edit, middle delete, wrong-key forge, tail truncation (honest detection claim)",
   "verdict": "pass",
   "evidence": "exported pack verifies standalone offline (exit 0); editing record seq 2's action -> exit 1 with a 'hash' failure NAMING seq 2; deleting the middle record (seq 3) -> exit 1 with sequence/chain_link failures naming seq 4; a record re-signed with a fresh WRONG key (hash left self-consistent) -> record-level 'signature' failure naming its seq and the pack rejected; tail truncation stated honestly and confirmed exactly as claimed: a tail-truncated pack fails its head/pack checks, while a tail-truncated STORE verifies as a valid shorter chain \u2014 detectable only against the exported pack head, whose hash the truncated head no longer matches. The product claims no more than that (README + manifest not_tested), so the limitation is disclosed, not hidden"
  },
  {
   "id": "B8",
   "name": "recorder-field smuggling: canonical names refused; camelCase/nested variants take no effect",
   "verdict": "pass",
   "evidence": "all 12 canonical recorder fields (seq, ts, recorder_ts, hash, prev_hash, signature, signature_b64, signer, actor, kind, event_type, recorder_notes) refused RECORDER_FIELD_REFUSED, zero records written; camelCase/variant keys (recorderTs, prevHash, seqNumber, evidenceGrade, Grade) are NOT honored \u2014 the append lands with recorder-assigned seq/prev_hash/grade and none of the variant keys present at record level; recorder names nested inside detail stay inside detail (agent data, labeled) and move nothing; chain still valid"
  },
  {
   "id": "B9",
   "name": "backdated/future claimed_ts never moves recorder time or order",
   "verdict": "pass",
   "evidence": "claimed_ts backdated to 1999 and postdated to 2031 are both stored verbatim AS CLAIMS, while the recorder timestamps stay inside the bench window and the sequence advances by exactly 1 \u2014 a submitted timestamp moves neither recorder time nor recorder order"
  },
  {
   "id": "B10",
   "name": "mandate absence renders NO_MANDATE_RECORDED on read surfaces; pack stays verbatim",
   "verdict": "pass",
   "evidence": "record_read renders a missing mandate as NO_MANDATE_RECORDED (agent records AND recorder events) and a present ref verbatim ('wallet-mandate-bench-7'); the export pack ships the record VERBATIM (mandate_ref stays null) with its content_notice \u2014 ruled consistent, not a miss: the pack is the hash-closed evidence artifact (\u00a74.1/Codex pack pattern), so the \u00a74.3 render rule binds rendered surfaces, and rewriting the pack to render would break its own verification"
  },
  {
   "id": "B11",
   "name": "\u00a72.1 lint planted in new carriers: quarantined at render, stored bytes untouched, chain valid",
   "verdict": "pass",
   "evidence": "banned classes planted in three NEW carriers (action text, a detail note, an imported record's action): record_read quarantines every plant in place with lint_flags naming safety_verdict + root_cause_conclusion + insurability_verdict + legal_conclusion; none of the planted phrases survive anywhere in the read output; the STORE bytes still contain all four phrases verbatim and the chain verifies \u2014 quarantine is a render, never a rewrite"
  },
  {
   "id": "B12",
   "name": "key-shaped input refused + receipted; bare 64-hex narrowing confirmed and ruled on",
   "verdict": "pass",
   "evidence": "extended key in detail, a 12-word seed-phrase shape as the action, and self-labeled 'private_key = ...' text in a detail note: all refused KEY_MATERIAL_REFUSED, each receipted as an AUTH_VIOLATION recorder event (patterns extended_key / seed_phrase_shape / key-labeled recorded; the probe VALUES appear nowhere in the store or any output); a bare 64-hex digest as content is accepted, confirming the documented narrowing behavior \u2014 ruled SOUND for this product in the card notes (digests are its native content; every labeled/structured key shape is still caught)"
  },
  {
   "id": "B13",
   "name": "surface is exactly five verbs; denylist enumerated incl. bench additions",
   "verdict": "pass",
   "evidence": "tools/list is exactly the five verbs; 34 forbidden names (the builder's 22 \u2014 record_update/delete/redact/truncate/regrade/import_as_signed/suppress_gap/rotate_key/admin_append et al. \u2014 plus bench additions record_wipe, credential_list, credential_export, provision_agent, store_dump) each refused by direct call with -32602 \u2014 no verb alters history and no credential management surface exists on the wire"
  },
  {
   "id": "N1",
   "name": "N-check: the recorded agent cannot read, grade, or rewrite its own record; the recorder receipts its own gaps",
   "verdict": "pass",
   "evidence": "N-CHECK \u2014 novel mechanism: the recorder is the author of the record's metadata AND of its own gaps: seq/time/hash/grade are recorder-assigned and recorder-signed under a key the agent never holds, the agent's credential is append-only with no read path, and a submission the agent never made becomes a signed recorder-authored event. Distinguishing test, run here: the agent appends seq 1 then seq 4 \u2014 the store contains an INGEST_GAP event with actor 'recorder' and grade E-SIGNED that the agent never submitted; the same agent then cannot read the record back (OWNER_CREDENTIAL_REQUIRED) and cannot assign its grade (GRADE_NOT_ASSIGNABLE). A logging/observability incumbent \u2014 agent-readable by design, storing submitter metadata as given, recording only what it is sent \u2014 fails all three limbs"
  },
  {
   "id": "U1",
   "name": "auth enforced (a real credential layer exists and is tested)",
   "verdict": "pass",
   "evidence": "unlike the stdio products where U1 is na, Blackbox HAS a credential layer and it is enforced: all five tools refuse a missing credential (AUTH_REQUIRED), a blank credential (AUTH_REQUIRED), and a malformed one (AUTH_INVALID) before any tool logic runs"
  },
  {
   "id": "U2",
   "name": "schema valid",
   "verdict": "pass",
   "evidence": "tools/list: all 5 tools carry a name and an object inputSchema; every schema declares the credential property; record_append requires exactly {agent_id, action}"
  },
  {
   "id": "U3",
   "name": "no network surface (source-level)",
   "verdict": "pass",
   "evidence": "source scan of server.py + blackbox_engine.py + recorder_sig.py: zero network/process call sites (no urllib/socket/requests/http.client/urlopen/subprocess) \u2014 local stdio only"
  },
  {
   "id": "U4",
   "name": "no secret leak",
   "verdict": "pass",
   "evidence": "no credential material anywhere it shouldn't be: all four bench token VALUES are absent from every tool output, the store file, the exported packs, and the credential registry itself (digests only); the recorder private key (d_hex) appears only in recorder-key.json \u2014 never in outputs, store, or packs (packs carry the public key); secret-pattern scan over all outputs clean"
  },
  {
   "id": "U5",
   "name": "sane errors",
   "verdict": "pass",
   "evidence": "malformed inputs refuse by name or as schema errors (agent_seq str -> AGENT_SEQ_FORMAT; detail str -> DETAIL_NOT_OBJECT; missing action -> -32602 schema; unknown agent filter -> empty result); read filters are type-safe || EXTENDED (run 3): guard accepts int-shaped strings + integral floats on read filters, refuses bools and non-integral floats by name; retention_class sibling CLOSED on the wire: list/dict/int/bool all refuse RETENTION_CLASS_UNKNOWN (isinstance-first), no -32603 unhashable escape; claimed_ts non-string refused CLAIMED_TS_FORMAT (run-2 observation closed); agent_seq stays strict-int by name (bool + integral float refused AGENT_SEQ_FORMAT); schema-enumerated wire fuzz: 114 wrong-type probes across all 5 tools (every declared param incl. enum-typed submission + retention_class, credential fields, hash fields, export/verify args); 20 probes were accepted without error \u2014 all verified benign semantics: chain-hash fields are inert in live mode (a wrong-typed agent_hash is not stored; in chained mode the same values refuse AGENT_CHAIN_INVALID / AGENT_CHAIN_ANCHOR_MISMATCH by name) and read filters treat unknown values as data (empty result, the U5 unknown-agent behavior)"
  },
  {
   "id": "U6",
   "name": "process health",
   "verdict": "pass",
   "evidence": "ping answers; initialize latency 51 ms recorded at setup"
  },
  {
   "id": "U7",
   "name": "audit completeness (counts + chain + self-receipts)",
   "verdict": "pass",
   "evidence": "appends return strictly increasing recorder seqs; an authenticated key-material refusal grows the chain by exactly one AUTH_VIOLATION self-receipt while two unauthenticated bad-credential attempts grow it by ZERO (strangers don't get to grow the chain); status, verify and the store agree on the count (43 records), head.seq == records, every record's signature checked, chain valid end to end"
  },
  {
   "id": "U8",
   "name": "bench isolation",
   "verdict": "pass",
   "evidence": "workspace product state byte-identical before/after bench"
  }
 ],
 "notes": [
  "Local scope: v0.1 is a local single-store recorder \u2014 all probes ran against bench-owned stores/keys in /tmp via the ECLIPSE_BLACKBOX_* env overrides; the product's own data/, keys/ and exports/ were never pointed at (U8 byte-identical).",
  "Hosted multi-tenant recorder (owner gate G-C3-1) and any OS-level isolation boundary are NOT tested, per the manifest not_tested: this build isolates by recorder-held key + credential scoping + an append-only engine with no mutation code path. The README's isolation statement was checked for honesty against the build and matches it (four layers claimed: key + write-reach + ordering real, failure isolation partial \u2014 harness queue not shipped).",
  "Tail-truncation ruling (B7): the product claims store-file tail truncation is detectable ONLY against a previously exported pack head (README + manifest not_tested state this plainly). The bench confirmed the claim is exactly true and not overstated: a tail-truncated store verifies as a valid shorter chain, and its head differs from the exported pack head \u2014 the documented comparison is the detection. A pack whose records are tail-truncated fails its own pack hash / head checks. No stronger claim is made anywhere in the product's surfaces, so there is nothing to fail.",
  "Bare 64-hex narrowing ruling (B12): SOUND for this product. Digests are Blackbox's native content (inputs_hash / outputs_hash / agent chain hashes are 64-hex by design and validated as hashes by name), and a bare unlabeled hex string carries no self-identifying signal a pattern could use \u2014 the same refusal Fathom applies is impossible here without refusing the product's own content class. The net still catches every labeled or structured key shape (extended keys, seed-phrase shapes, key-labeled text) in every field the bench probed. Residual risk, recorded: a bare raw private key pasted into a free-text field (detail / action) would be stored as content \u2014 unlabeled, indistinguishable from a digest, and the spec's hash-fields-by-name exclusion is the stated boundary. Observation, not a defect: no assigned claim is violated.",
  "Offline-forgery note (spec \u00a76.4 'receipted as violation'): a forged-signature record planted in an exported pack is rejected at verify NAMING its seq (B7). The build receipts violations only for authenticated API misuse (key-material refusals are AUTH_VIOLATION recorder events, B12); a store/pack-file forgery is detected at verification, not receipted \u2014 verify is read-only in this build and no surface claims otherwise.",
  "Run 1 PARTIAL card retained append-only: its B5/B7 untestables were a bench-suite defect (loading the engine by path without the product dir on sys.path, so its recorder_sig import failed), its B6 untestable was a suite defect (comparing a RENDERED read copy against raw store bytes), its U1 untestable was a suite defect (record_append with empty args correctly hits the transport schema check before auth), and its U5 'untestable' was the genuine product defect reported as an assertion instead of a fail return. Suite fixed; run 2 is the standing card. No product code was touched between runs.",
  "Run-2 minor observation CLOSED in run 3: a non-string claimed_ts (integer, list) now refuses CLAIMED_TS_FORMAT by name on the wire; a string claim is still stored verbatim and never used for ordering (recorder time governs).",
  "Run 3 (re-bench after the U5 fix): the builder's fix was verified first \u2014 own suite 47/47 (40 original + 7 new WireTypeFuzzTests enumerating typed params from the live tools/list schemas) and smoke both replicated exactly. The run-2 defect shapes (from_seq='abc', to_seq=[3], limit='x') now refuse READ_FILTER_FORMAT by name at raw JSON-RPC level, zero -32603 anywhere in the run.",
  "Sibling verification (builder-claimed, bench-confirmed on the wire): retention_class was membership-tested against the RETENTION_CLASSES dict unguarded \u2014 a list value raised an unhashable TypeError, the same -32603 escape. It is now isinstance-first under its existing RETENTION_CLASS_UNKNOWN refusal; list/dict/int/bool values all refuse by name. Bench fuzz beyond the builder's suite (schema-enumerated, every param of all 5 tools incl. credential fields, enum-typed submission/retention_class, hash fields, export/verify args) found no further member of the class. Chained and imported modes were probed separately with wrong-typed chain fields: agent_hash as int/list/dict/float/bool refuses AGENT_CHAIN_INVALID, agent_prev_hash likewise refuses AGENT_CHAIN_ANCHOR_MISMATCH \u2014 named refusals, no -32603; and a wrong-typed agent_hash sent in live mode is inert (not stored on the record).",
  "HOUSE-REFERENCE RULING (U5 fix, candidate reference for the raw-coercion class): coerce_int_param is APPROVED as the house reference for wave-2 builds, to be lifted as-is for int-typed wire parameters. Its accepted-shape set is right: real ints, int-shaped strings ('42', '-3'), and integral floats (2.0) \u2014 exactly what JSON callers legitimately send \u2014 and its refusal set is right: bools are excluded FIRST (True is not a sequence number; isinstance(True, int) is the trap that makes bare coercion lie), non-integral floats, non-numeric strings, lists and dicts all refuse BY NAME with the caller's refusal token. Two disciplines ride with it: (1) isinstance-before-membership for every enum/dict membership test on wire values (the retention_class sibling), and (2) claim-data params may stay stricter (agent_seq refuses strings and integral floats by name, AGENT_SEQ_FORMAT) \u2014 strictness is fine, silence is not: every wrong type must land on a named refusal, never a coercion and never a -32603. Spot regressions: B4 (grade inflation) and B6 (deletion narrative) re-ran clean \u2014 the guards moved nothing."
 ],
 "history": "append-only; prior runs retained in results/"
}