Skip to content

spec lint: surface abstract staleness (the signal is already fetched and discarded) #335

Description

@shadowbrush

docs/plans/spec-command-group.md deferred this as "Abstract-staleness lint deferred — abstractOriginHash is selected (so a future pass / get can use it), but reliable staleness needs a server-provided signal; not enforced in v1."

That signal exists now, and the CLI already fetches it and throws it away.

What prompted this

While updating cor:urn:010:01 (URN grammar) I found its body saying:

Emission status — v2 emitted (the flip has landed, #697)

and its abstract ending:

The platform still EMITS v1 until the #697 migration flips emission to v2.

spec lint cor:urn:010:01 reported ✓ 1 spec(s) OK. Meanwhile hadron_get_node on the same node printed Source: abstract-stale — the MCP layer acts on the signal, the CLI doesn't.

That matters more than an ordinary doc lag: the abstract is the RAG retrieval surface. An agent asking what URN form the platform emits got back an authoritative-sounding "still EMITS v1" from the law corpus. Wrong answers of that shape propagate — this is a plausible upstream source of the v1 spellings that were still in CLAUDE.md until #333.

The signal is already on the wire

abstractOriginHash is selected by both GetNode and NodeBatch (internal/api/queries/nodes.graphql:122 and :172), so every spec get / spec lint / node get already pays for it. It is then dropped: it appears in no DTO, so hadron node get --json doesn't expose it and nothing can act on it.

Schema contract:

Spec 032 — fingerprint of the content value at the time abstract was authored. SHA-256 of plaintext content, truncated to 8 hex chars. Compared at read time against computeContentHash(node.content) to detect staleness; when the two values differ AND abstractOriginHash is non-null, the abstract may not reflect current content.

Verified computable client-side — no new query, no server change:

$ hadron api 'query($r:ID!){ node(ref:$r){ loc abstractOriginHash content } }' -F r=…cor:urn:010:01
server hash:          11a308dc
sha256(content)[:8]:  11a308dc   → match

Scale: 176 of 264 spec nodes (67%) are currently stale

Swept hadronmemory.com::specs per module and computed the hash for every node:

spec nodes scanned:    264
  with an abstract:    264
    ABSTRACT-STALE:    176
    abstract, no hash:   1   (authored pre-spec-032; undetectable)
  no abstract:           0

Cross-checked against the server's own verdict rather than trusting my arithmetic — hadron_get_node reports Source: abstract-stale on cor:dmo:050:10, which the sweep flagged. Agreement.

Severity: warning, not error

Stale ≠ wrong. The hash only says the body moved since the abstract was authored; plenty of those 176 abstracts are still accurate. At 67% an error-level rule would make spec lint --all permanently red and --strict unusable — the same reasoning that made preflight's route-label-phrasing a warning in #328.

Proposed:

  • rule abstract-stale, warning, message naming both hashes so it's checkable by hand
  • fires only when abstractOriginHash is non-null and differs (a null hash is pre-spec-032, not a finding)
  • silent when the node has no abstract — abstract-present already covers that

Implementation notes

  • Add Content and AbstractOriginHash to the spec lint model; the rule is pure and unit-testable against a fixed (content, hash) pair.
  • Consider exposing abstractOriginHash in node get --json too, so it's checkable without dropping to hadron api.
  • A whole-corpus sweep must batch per module: nodeBatch(locPrefix:"cor") is rejected outright with "prefix matches more than the 200-node cap. Narrow the prefix; the batch read does not silently truncate." spec lint --all already pages via scanAllNodes, so this mostly matters for anyone writing a one-off sweep.

Out of scope

Fixing the 176. This issue is about detection; the corpus owner decides which abstracts genuinely need rewriting. cor:urn:010:01 is already fixed (#333 follow-up) and now reports clean.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions