Skip to content

A graph with a number in an any slot cannot be loaded: checksum mismatch #734

Closed

Description

@David-Noble-at-work

Regression introduced by: #713 (merged 2026-08-27T17:50:06Z, d5b1375)
Fixed by: #712's envelope work — same PR, but tracked here because the breakage is independent of it.

A graph holding a number in an any slot cannot be loaded. LoadGraph rejects it as tampered.

Measured

An any slot holding a string or a bool reloads cleanly. One holding an integer or a float does not:

LoadGraph(json): op.LoadGraph: checksum mismatch:
  document   "sha256:0d223c4d2bcc047dc3d8334ef1afc2333e91d716543286a3a8c769bfc800aea8",
  recomputed "sha256:c38dcb7cf1d39b566dbceb75ca41f3e1942cb3740982b00db95a62d9a242b92b"

Covered by TestLoadGraph_AGraphWithAValueInAnAnySlotLoads (pkg/op/any_slot_types_test.go), which isolates
the failure by value kind.

Mechanism

#713 replaced json.Unmarshal(data, &p) with a UseNumber decoder so a serialized number could be read
against the declared type of the parameter it fills. An any parameter has no declared type, so
tryParseSerializedNumber matches no case and convertDirect returns the value untouched: the slot reloads
holding a json.Number.

The canonical form a checksum covers is computed from the RELOADED values via yaml.Marshal
(pkg/op/graph.go:850). json.Number is a named string type, so yaml renders it as a quoted scalar where a
float64 or int64 renders as a bare number. The recomputed checksum therefore differs from the stored one
and the document is refused.

Why it did not happen before #713

With plain json.Unmarshal, a number in an any slot came back as float64, and yaml.Marshal(float64(42))
emits 42 — byte-identical to what the original int64(42) or float64(42) produced. The checksum matched
precisely because the type loss was invisible to it. #713 did not create the type loss (#712 filed that, as a
silent defect); it converted a silent loss into a hard failure.

Severity

Higher than #712's P2. #712 describes a value arriving with the wrong type; this refuses the document
outright, and it is live on develop.

Not separately fixable

The fix is #712's envelope: the document records the type, the decoder resolves it to a concrete Go value, and
the canonical form reconciles. There is no smaller repair that does not guess — resolving a json.Number by
literal syntax, or falling back to float64, both infer at read time what the writer knew at write time.
Reverting UseNumber would regress #711.

Acceptance

  • A graph with an integer in an any slot loads
  • A graph with a float in an any slot loads
  • Strings and bools in any slots keep loading
  • The reloaded graph's recomputed checksum matches the stored one, from json and from yaml

Epic: #443 · Feature: #458

Activity

  1. David-Noble-at-work commented on Sep 8, 2026

    @David-Noble-at-work
    ContributorAuthor

    Fixed by #858 (a5b2fea), phase 2 of the #712 plan: an enveloped value is resolved at the decoder, so a graph with an integer or a float in an any slot loads and its recomputed checksum matches the stored one from JSON and from YAML. Pins: TestLoadGraph_AGraphWithAValueInAnAnySlotLoads, TestLoadGraph_JSONDocumentVerifies, TestLoadGraph_YAMLDocumentVerifies. The PR body's "Closes #712, #734, #735" closed only the first and last; closing by hand.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Area:ArchitectureSystem architecture and designEpic:SerializationSerialization and the single codecPriority:P1This cycleSeverity:HighA supported path is broken with no workaroundbugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions