Skip to content

Traces written before ResourceState marshaled names cannot be decoded; the store's history is unreadable #758

Open

Description

@David-Noble-at-work

Feature: #458 — Schema generation and document contracts

Traces written before ResourceState began marshaling a name cannot be decoded, so the store's history is
unreadable and writ status reports nothing about what is deployed.

Measured, on a live machine

$ writ status -o json | jq '.health'
{
  "runs": 0,
  "findings": [
    "trace .../20260809T071528....yaml cannot be read: op.LoadTrace: yaml decode:
     op.ResourceState: unknown state \"1\"",
    ... two more
  ]
}

runs: 0 and entries: null follow directly: nothing folded, so nothing is known about deployed files.

Cause

The stored document holds the integer:

state: 1

ResourceState's decoder accepts names only (pkg/op/resource_state.go:118-122):

case "pending":
case "active":
case "gone":

So every trace written before the change to name-marshaling is undecodable. Three exist on this machine,
dated 2026-08-09.

This may not be a code defect

The failure is handled the way it should be: Health.Findings reports each unreadable trace by path and
reason, and the command still succeeds. That is the designed behavior for a store whose contents disagree
with itself, and the message names the cause precisely.

Under the governing principle -- greenfield, no legacy, no backward compatibility -- accepting the old form
would be the bug. So the likely resolution is discard: the three files are from a retired format and
should be removed, not migrated.

Decided 2026-08-31

Discard is the ruling, and a trace gains a version so a stale document reads as stale.

The scheme already exists in the tree and is applied to one document of three. A graph carries it:

kind: com.noblefactor.DevLore.Graph
schema_version: 1

A trace opens straight into graph_checksum, and the run index carries nothing per line. So the work is
not to invent versioning -- it is to apply GraphSchemaVersion's scheme (graph.go:41, a uint32) to the
two documents that lack it.

With a version on a trace, the loader reads it first: absent or below the supported floor means written by
a retired format
, and a decode failure at the current version means damaged. That is the distinction
this issue was filed for, and it needs no legacy-decoding path -- a retired document is still refused, with
an honest reason instead of unknown state "1".

The three files on the affected machine are the operator's to delete. Nothing here migrates them.

The versioning policy this sits under

Recorded in docs/architecture/10-command-line-interface.md §13. An application and the documents it writes
carry separate versions -- the application from git describe, a document as a uint32 -- because the
application version changes on every commit and would otherwise claim a new format between identical ones.
On the release of a major application version the store version is bumped with it, as policy rather than as
an invariant: the two remain independently versionable, a format bumps whenever it actually changes, and the
policy is revisited regularly.

Remaining work

  • schema_version widens from uint32 to a declared semver string. The value tracks the
    application's for now -- a reader attributing "1.0.0" to a release needs no lookup table -- but it is
    a literal rather than a computed value, so the two diverge the day a format changes and the
    application does not, by editing one constant. An integer forecloses that: it cannot express 1.2.0,
    so an additive format change would have to become a major bump or go unrecorded.
    This changes a serialized field's type, so it moves the canonical form and every stored checksum. It
    belongs with the serialization work, not with a CLI branch.
  • A trace carries kind: com.noblefactor.DevLore.Trace and schema_version, as a graph does.
  • The run index carries a version. Per line rather than as a header, so it stays self-describing and
    survives the truncation TestIndexTornLastLine already covers.
  • LoadTrace reports a retired version distinctly from a decode failure, and writ status surfaces the
    two differently -- only damage deserves alarm.

Related

The visible symptom overlaps #756: on that machine entries is null and runs is 0, so writ status
answers nothing useful. #756 covers the missing-index case; this covers the unreadable-document case.

Metadata

Metadata

Labels

Area:ArchitectureSystem architecture and designEpic:SerializationSerialization and the single codecThread:WritOriginCross-cutting: writ records and verifies where a deployment came frombugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions