Skip to content

Inline anchoring: anchor a claim in prose, materialize into frontmatter #154

Description

@Connorrmcd6

Summary

Let authors anchor a claim inline, at the point in the prose where it's made, instead of only in anchors: frontmatter. This is the one genuinely better ergonomic in fiberplane/drift (its @./path#Symbol inline refs that drift link auto-stamps). We should adopt the ergonomic while keeping Surface's model intact: frontmatter stays the single governed store; inline markers are sugar that a command materializes into anchors: entries.

Prior art: drift embeds @./src/auth/provider.ts#AuthConfig in the body and stamps provenance into its drift.lock. Surface's provenance lives in-doc (OKF-native, surf-core/src/hub.rs), so we materialize into frontmatter rather than a lockfile.

Why

Authoring friction is the main adoption tax. Today you write the sentence in the body, then scroll up to frontmatter and hand-write a matching anchors: item with the at: path (see hubs/*.md). For a hub with many claims this is error-prone and detached: the prose and its anchor live in two places. Inline anchoring co-locates the claim text with the symbol it describes, which is exactly where an author is already looking.

Non-goal: this does not change the gate, the verify loop, magnitude, ignore_literals, or refs propagation. Those keep operating over anchors: in frontmatter exactly as today (surf-cli/src/check.rs, surf-cli/src/verify.rs).

Recommended design: inline as sugar, frontmatter as truth

Keep Frontmatter::anchors (surf-core/src/hub.rs:46) as the sole governed store. Add an inline marker in the body that a new sync step reads and materializes into a frontmatter Claim, linked by the existing stable Claim::id (surf-core/src/hub.rs:68).

Inline syntax

A markdown-link-shaped marker so it renders harmlessly in any OKF consumer / Obsidian / GitHub preview:

Refresh rotation is [single-use; reuse triggers global logout](surf:src/auth/refresh.ts > rotateRefreshToken).
  • Link text = the claim prose.
  • surf: scheme + the existing anchor grammar (surf-core/src/anchor.rs, parse_anchor) as the target. The > grammar is reused verbatim, so file > Type > method and @N positional selectors work with zero new parsing.
  • List anchors: allow repeating the scheme, or surf:a.rs > f | a.rs > g, deferred to a follow-up. MVP = one target per inline marker.

The sync step

Extend surf verify (preferred) or add surf sync:

  1. Parse the body for surf: markers (new module surf-core/src/inline.rs, pure string -> Vec<InlineAnchor { claim, at, span }>).
  2. For each marker, find or create the matching frontmatter Claim:
    • Match on id if the marker carries one (round-trip), else match on normalized at: + claim text.
    • Create via the existing minimal-diff editors (set_anchor_field, set_anchor_hash in surf-core/src/hub.rs:200) so the human sees a tight diff.
  3. Stamp id/hash/verified_* exactly as verify does today.
  4. Rewrite the inline marker to carry the assigned id (e.g. ](surf:... #c_01hxyz)) so future syncs are idempotent and a prose edit keeps the same claim identity.

Frontmatter remains authoritative; check/lint/stats are untouched.

Acceptance criteria

  • A surf: inline marker in a hub body is parsed into {claim, at} reusing parse_anchor (no new anchor grammar).
  • surf verify (or surf sync) materializes each inline marker into a frontmatter anchors: Claim, creating id and stamping hash, via the minimal-diff editors (diff touches only the added/changed lines).
  • Re-running with no changes is byte-identical (idempotent), mirroring set_hash_to_same_value_is_byte_identical.
  • An inline marker whose target fails parse_anchor is a surf lint error with the offending span.
  • surf lint warns when an inline marker and its materialized frontmatter claim have drifted apart (prose or at: changed inline but not re-synced).
  • Markers render as plain links in GitHub/Obsidian (no visual breakage) and survive frontmatter round-trip tests (hub.rs test module).
  • Docs: new section in docs/guides/authoring-hubs.md; note the sugar->frontmatter model and that the gate still reads frontmatter only.

Affected code

  • New: surf-core/src/inline.rs (pure parser) + re-export in surf-core/src/lib.rs.
  • surf-cli/src/verify.rs (or new surf-cli/src/sync.rs + wire into surf-cli/src/main.rs).
  • surf-core/src/hub.rs — reuse set_anchor_field/set_anchor_hash; possibly a marker-rewrite helper for the body.
  • docs/guides/authoring-hubs.md, docs/reference/commands.md.

Open design questions (spike first)

  1. Materialize vs first-class. Recommended: materialize to frontmatter (above). Alternative: treat the inline marker as the governed unit and store its hash in a sidecar/lockfile - rejected because it splits provenance out of the doc and breaks the OKF-native model. Confirm before building.
  2. Claim identity on prose edits. Writing id back into the marker is what makes edits stable. Acceptable to mutate the author's prose line? (drift mutates the lockfile, not the prose - this is the one place our model costs an extra write.)
  3. Which command owns it - overload verify or a dedicated sync. Leaning verify --follow-adjacent, but a separate sync keeps verify's "I re-read it" semantics clean.

Explicitly out of scope

Auto-stamping on every agent code change (drift's skill runs drift link automatically). That is antithetical to Surface's human-sign-off thesis - see the "does NOT do" section of the README. Inline anchoring is an authoring ergonomic, not an auto-verify.

Metadata

Metadata

Assignees

No one assigned

    Labels

    dxDeveloper experience / CLI ergonomicsenhancementNew feature or requestresearchOpen question / needs a design spike (proposal §11)

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions