FUZZ-SCSHA-001
- Minimal template / frontmatter
n/a (Python FFI call, no template)- Input
sc_sha.calculate_composition_hash({'schema': 'sc-sha/manifest/v1', 'nodes': [{'source': {'kind': 'local_path', 'value': 'a'}, 'sha256': 'not-hex'}], 'edges': []})- Expected
- docs/error-code-registry.md lines 55-57 documents SC_SHA_INVALID_DIGEST as the stable machine-readable code for 'a manifest digest is not exactly 64 hexadecimal characters', sourced from TemplateSha256::from_hex(). bindings/sc-sha-python/src/lib.rs's own parse_digest() (added in QA round R1/QM-004) delegates directly to that same TemplateSha256::from_hex(), so its ShaError::InvalidDigestHex.code() == "SC_SHA_INVALID_DIGEST" is the correct, already-defined stable code for this condition.
- Observed
- Raised sc_sha.ScShaError with .code == "SC_SHA_INVALID_MANIFEST" and .message == "sha256 must contain exactly 64 hexadecimal characters". Any Python caller branching on error.code per the documented registry contract (e.g. to retry, log a metric, or surface a specific UI message for a corrupt digest) receives the generic manifest-shape code instead, indistinguishable from a missing/wrong-typed field.
- Requirement / ADR
- docs/error-code-registry.md rows for SC_SHA_INVALID_DIGEST (line 56) and ADR-0018 (sc-sha hash ownership) SHA-R-series stable-error-code requirements; also directly regresses the intent of QM-004's fix (delegate parse_digest to sc_sha::TemplateSha256::from_hex so the adapter stops duplicating hex-decode logic) by discarding the resulting error code at the call site.
- Requirement / ADR follow-up
- No new ADR/requirement needed -- this is a contract the repo already documents and already fixed the duplication for; the mapping in bindings/sc-sha-python/src/lib.rs simply needs to stop overwriting it.
- Root cause
- bindings/sc-sha-python/src/lib.rs::parse_digest (lines 99-106) maps sc_sha::ShaError to a bare &'static str message and drops error.code() entirely. Its sole caller, parse_nodes (line 133-134), then wraps that message with a hardcoded "SC_SHA_INVALID_MANIFEST" code: `parse_digest(&string_field(py, node, "sha256")?).map_err(|message| error(py, "SC_SHA_INVALID_MANIFEST", message))?;`. This is inconsistent with calculate_hash_py/calculate_composition_hash_py, which both correctly forward `e.code()` from ShaError/CompositionError to the Python exception.
- Recommended fix
- Change parse_digest to return Result<TemplateSha256, sc_sha::ShaError> (preserving the typed error), and have parse_nodes call `error(py, e.code(), e.to_string())` the same way calculate_hash_py does, instead of hardcoding SC_SHA_INVALID_MANIFEST. Add a regression test in bindings/sc-sha-python/tests/test_compatibility.py asserting error.code == "SC_SHA_INVALID_DIGEST" for an invalid-hex sha256 field inside a manifest node.