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:
- Parse the body for
surf: markers (new module surf-core/src/inline.rs, pure string -> Vec<InlineAnchor { claim, at, span }>).
- 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.
- Stamp
id/hash/verified_* exactly as verify does today.
- 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
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)
- 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.
- 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.)
- 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.
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#Symbolinline refs thatdrift linkauto-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 intoanchors:entries.Prior art: drift embeds
@./src/auth/provider.ts#AuthConfigin the body and stamps provenance into itsdrift.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 theat:path (seehubs/*.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, orrefspropagation. Those keep operating overanchors: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 frontmatterClaim, linked by the existing stableClaim::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:
surf:scheme + the existing anchor grammar (surf-core/src/anchor.rs,parse_anchor) as the target. The>grammar is reused verbatim, sofile > Type > methodand@Npositional selectors work with zero new parsing.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 addsurf sync:surf:markers (new modulesurf-core/src/inline.rs, pure string ->Vec<InlineAnchor { claim, at, span }>).Claim:idif the marker carries one (round-trip), else match on normalizedat:+ claim text.set_anchor_field,set_anchor_hashinsurf-core/src/hub.rs:200) so the human sees a tight diff.id/hash/verified_*exactly asverifydoes today.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/statsare untouched.Acceptance criteria
surf:inline marker in a hub body is parsed into{claim, at}reusingparse_anchor(no new anchor grammar).surf verify(orsurf sync) materializes each inline marker into a frontmatteranchors:Claim, creatingidand stampinghash, via the minimal-diff editors (diff touches only the added/changed lines).set_hash_to_same_value_is_byte_identical.parse_anchoris asurf linterror with the offending span.surf lintwarns when an inline marker and its materialized frontmatter claim have drifted apart (prose orat:changed inline but not re-synced).hub.rstest module).docs/guides/authoring-hubs.md; note the sugar->frontmatter model and that the gate still reads frontmatter only.Affected code
surf-core/src/inline.rs(pure parser) + re-export insurf-core/src/lib.rs.surf-cli/src/verify.rs(or newsurf-cli/src/sync.rs+ wire intosurf-cli/src/main.rs).surf-core/src/hub.rs— reuseset_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)
idback 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.)verifyor a dedicatedsync. Leaningverify --follow-adjacent, but a separatesynckeepsverify's "I re-read it" semantics clean.Explicitly out of scope
Auto-stamping on every agent code change (drift's skill runs
drift linkautomatically). 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.