Skip to content

driver/yaml drops a scalar's YAML anchor on save, so every alias to it dangles and the file no longer parses #196

Description

@ajatprabha

Replacing a scalar that carries a YAML anchor drops the anchor and leaves every alias to it dangling, so Dump returns nil and writes a file that Load then refuses.

Found on feat/188-durable-opt-in while probing which of driver/yaml's error branches are reachable through the public seam.
It reproduces identically on main at a3dc8f8; nothing in #187 or #188 touched it.

Reproduce

host: &h localhost
other: *h
type config struct {
	Host string `ferry:"host"`
}

err := ferry.Dump(ctx, config{Host: "example"}, yaml.NewSink(path))
// err == nil

The file afterwards:

host: example
other: *h

And loading it back:

ferry: opening the plane: invalid value: the plane is not a YAML document: yaml: unknown anchor 'h' referenced

Where it comes from

writer.Set replaces the node wholesale:

spelled.HeadComment, spelled.LineComment, spelled.FootComment = at.HeadComment, at.LineComment, at.FootComment
*at = *spelled

Three comment fields are carried across because they are the operator's.
Anchor is not, and it is the operator's too.
spell builds a fresh leaf with an empty Anchor, so the assignment clears it, while other's AliasNode still points at the same *yamlv3.Node and still emits *h.

Set is right that the value, the tag and the style are ferry's.
An anchor is neither: it is the operator's name for a place in their document, exactly like a comment.

Why this is worse than the tag case in #155

#155 is a mapped scalar losing its tag, which is a value that reads back as the wrong kind.
This is a whole document that no longer parses, so it is not a lossy round trip but a save that destroys the file for every reader including ferry.
driver/yaml's entire argument is that a hand-maintained config file survives being loaded and written back, and an anchor is ordinary in a hand-maintained file.

It is also not in doc.go's exception list, which names five things a Dump does not preserve.
None of them is "the file stops being valid YAML".

What needs deciding

  1. Carry the anchor across, beside the three comments, so host: &h example is written and the alias still resolves.
    This is the small fix and it makes the common case correct.
  2. What happens to an alias that ferry cannot keep valid, if such a case exists once (1) is done.
    Worth checking whether a mapped node that is itself an AliasNode behaves, since replacing one turns an alias into an inline value and silently unshares it from its anchor.
  3. Whether either outcome belongs in doc.go's exception list.

Scope

Not #187's and not #188's.
Both were durability tickets and neither goes near Set.
The probes that found this were throwaway and are deleted.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions