ce-serializer-impl
Implement ADR-031 serializer and persistence APIs with versioned primitives and compatible load behavior.
SKILL.md.claude/skills/ce-serializer-impl/SKILL.md
GitHubCE Serializer Implementation
You are implementing calibrator serialization or explainer state persistence
per ADR-031. The contract is: versioned JSON-safe primitives, fail-fast on
schema incompatibility, and -- as of the schema v3 security hardening --
no pickle bytes anywhere on the load_state() call path. A SHA-256
checksum recorded inside the same artifact proves internal consistency only;
it never proves who produced the artifact, so it must never gate whether
pickle-like bytes get deserialized. If you are extending this area, assume
the artifact is untrusted input and re-read ADR-031's "Trust boundary
(schema v3)" section before writing code.
Load references/serialization_templates.md for full code templates.
Key contracts
Calibrator to_primitive / from_primitive
All built-in calibrators must implement this pair:
to_primitive()returns a dict withschema_versionas the first key and only JSON-safe values (lists/numbers/strings from.tolist()etc.).VennAbers/IntervalRegressoralso stash achecksums.sha256field computed from a pickle of internal state, but that pickle blob is never stored in the primitive and never fed topickle.loads()-- it is a vestigial corruption-detection artifact, not part of the trust boundary. Do not add a calibrator that pickles its restorable payload.from_primitive(payload)validatesschema_versionandcalibrator_type, and reconstructs the object purely from JSON-safefields. RaisesConfigurationErroron any mismatch with a guidance message.- Calibrators that cannot implement a JSON-safe
to_primitive()must not be silently pickled byWrapCalibratedExplainer.save_state(). It raisesSerializationErrorinstead (see_calibrator_to_primitiveinwrap_explainer.py). Restoration (_restore_calibrator_from_primitive) dispatchescalibrator_typeagainst a fixed, explicit set of trusted restorers only -- never a dynamic import of an artifact-provided module path -- and rejects the legacycalibrator_type="python_pickle"value unconditionally, before any base64 decode or checksum check.
Explainer save_state / load_state (schema v3)
- On-disk format: directory artifact with
manifest.jsonplusexplainer_state.json(JSON-safe wrapper/learner-identity/calibration config), optionalcalibrator_primitive.json, optionalpreprocessing_mapping.json. Nowrapper.pklor any other pickle payload is ever written. - Manifest fields include
schema_version(currently3),created_at_utc,artifact_type, andfiles(mappingfilename -> sha256). save_state(path)writes the artifact directory and returnsPath.load_state(path, *, learner=None, preprocessor=None, difficulty_estimator=None, mc=None)validates the manifest and every file's checksum, then reconstructs the wrapper from JSON alone plus the caller-supplied runtime objects (a generic fitted learner/custom preprocessor cannot be safely reconstructed from bytes, so the caller must supply them again; they are validated against persisted identity/shape metadata before use). RaisesIncompatibleStateErrorfor schema/manifest problems (including unconditional rejection of legacy schema v1/v2 artifacts, before any pickle byte is read) andValidationErrorfor missing/incompatible caller-supplied objects.
Round-trip invariant (ADR-031 §4)
After a save/load round-trip, the restored calibrator must produce identical
outputs (np.allclose(ref, restored, atol=1e-9)).
Additional compatibility expectations
- Wrapper object round-trips via direct
pickle.dump/loadandjoblib.dump/load(i.e. the caller pickling the Python object themselves, not going throughsave_state()/load_state()) should remain functional -- that is a different, well-understood trust boundary (the caller already trusts whatever produced the pickle bytes) than the ADR-031 state-artifact format. - Explanation collection objects should remain pickleable.
Out of Scope
- Third-party calibrator schema management - external packages own their schema versions.
Evaluation Checklist
-
to_primitive()returns a dict withschema_versionas the first key. - All values are JSON-safe (no numpy arrays, no non-serialisable objects).
-
from_primitive()validatesschema_version,calibrator_type, and checksum. -
from_primitive()raisesConfigurationErroron any validation failure. - Round-trip test verifies identical predictions (not just no-exception).
-
json.dumps(primitive)test verifies JSON-safety. - Checksum tamper test verifies
from_primitive()rejects corrupted payloads. -
save_state/load_statemanifest includesschema_version,created_at_utc,artifact_type, andfiles. -
load_statevalidates per-file sha256 checksums before deserialising. - Unsupported state schema version raises
IncompatibleStateError. - Legacy (pre-v3) pickle-based schema versions are rejected unconditionally, before any pickle byte is read -- never accepted merely because a checksum matches.
- Unsupported calibrators fail closed (
SerializationError) at save time instead of falling back to pickling. -
load_state()never callspickle.loads/pickle.load/joblib.load/cloudpickle.loadson artifact-provided bytes; an AST-based guard test enforces this. - Wrapper pickle and joblib round-trips are covered in integration tests (direct
pickle.dump/load, not viasave_state/load_state).