Skip to content

Cross-repo origin: skip foreign-repo anchors instead of failing the gate #155

Description

@Connorrmcd6

Summary

Add an optional origin field so a hub that travels across repositories (shared/vendored/published docs) doesn't throw false UNRESOLVED divergences in a repo where its anchored code doesn't exist. Borrowed from fiberplane/drift's origin = "github:org/repo" binding field: when a hub's declared origin isn't the current repo, surf check skips its anchors instead of failing the gate.

This is the lightweight, non-registry version of cross-repo support. It does not reopen the "Cross-repo registry" deferral in the proposal (§9.3, polyrepo-at-scale) - it's a single string field plus a skip rule, no resolver, no network, no catalog.

Why

A hub anchored to src/auth/refresh.ts > rotateRefreshToken is meaningful only in the repo that contains that file. If the same hub is copied/published into another repo (an OKF bundle shared across services, a docs monorepo, a vendored spec), every anchor resolves to a missing file. Today that path becomes DivergenceKind::Unresolvable ("UNRESOLVED, run surf lint") in surf-cli/src/check.rs (read_site fails at surf-cli/src/workspace.rs:141), which blocks the gate with false positives. origin lets the foreign repo cleanly skip anchors it can't own, while the home repo still governs them.

Bonus: origin is a provenance breadcrumb - it records where a hub's governance natively lives, which pairs well with OKF portability.

Design

1. Declaring the current repo's identity

Core stays pure/deterministic (no network, no git - see surf-core/src/config.rs docs). Add to surf.toml:

origin = "github:Connorrmcd6/surface"
  • New optional field origin: Option<String> on Config (surf-core/src/config.rs:11). deny_unknown_fields is on, so adding the field is the safe way to accept it.
  • Optional CLI fallback (I/O, lives in surf-cli): if surf.toml omits origin, derive from git remote get-url origin and normalize to host:owner/repo. Keep this out of core. If neither is present, the feature is inert (every hub is treated as native, today's behavior).

2. Declaring a hub's home

Add optional origin: Option<String> to Frontmatter (surf-core/src/hub.rs:22), sibling to refs/covers. Hub-level (applies to all the hub's anchors) is the right granularity - hubs travel as whole docs. Serialize with skip_serializing_if = "Option::is_none" so existing hubs stay byte-identical.

3. The skip rule

In check_workspace (surf-cli/src/check.rs:57), before a hub's anchors are resolved:

  • If hub.origin is Some(o) and workspace origin is Some(w) and o != w (normalized compare) -> skip all anchors in that hub. Do not emit Unresolvable; do not count toward the gate.
  • Report skips on stderr as advisory (surf check: skipped N anchor(s) from hub X (foreign origin "..." != "...")), matching how --files/v1 nudges already print to stderr so JSON stdout stays clean.
  • If either side is None, behave exactly as today (native).
  • If o == w, check normally - origin is inert metadata in the home repo.

Normalization: strip scheme variance (github:, git@github.com:, https://github.com/, trailing .git) to a canonical host/owner/repo before compare. Small pure helper in core with unit tests.

4. JSON envelope

Add an advisory skipped list (hub, origin, reason) to the --format json report (surf-core/src/report.rs, CheckReport) so downstream layers see what was skipped rather than silently absent. Bump the envelope version note in docs/reference/how-it-works.md#the-json-seam if the shape changes.

Acceptance criteria

  • origin parses on both Config (surf.toml) and Frontmatter (hub), optional, and round-trips byte-identically when absent.
  • Origin normalization helper handles github:owner/repo, git@github.com:owner/repo.git, and https://github.com/owner/repo to one canonical form (unit-tested).
  • surf check: a hub whose origin != workspace origin skips its anchors, exits 0 (no false UNRESOLVED), and prints an advisory stderr line.
  • A hub whose origin == workspace origin (or absent on either side) is checked exactly as today - covered by a regression test.
  • Skipped anchors appear in the JSON report's advisory skipped list, not as divergences.
  • surf lint does not flag foreign-origin anchors as unresolvable.
  • Docs: docs/reference/configuration.md (the origin key), docs/guides/authoring-hubs.md (hub origin), and a short "docs that travel" note; clarify origin is orthogonal to refs (hub->hub composition) and is not the deferred cross-repo registry.

Affected code

  • surf-core/src/config.rs - Config.origin.
  • surf-core/src/hub.rs - Frontmatter.origin.
  • New pure helper (normalize + compare) in core + lib.rs re-export.
  • surf-cli/src/check.rs - skip rule in check_workspace; surf-cli/src/workspace.rs - optional git-remote fallback for workspace origin.
  • surf-core/src/report.rs - advisory skipped in CheckReport.
  • docs/reference/configuration.md, docs/guides/authoring-hubs.md, docs/reference/how-it-works.md.

Open questions

  1. Per-claim override. Hub-level is the MVP. Do we ever need per-claim origin (a hub anchored partly into a vendored dep)? Defer unless a real case appears.
  2. Silent skip vs explicit opt-in. Should a foreign hub with no matching code be skipped silently, or require origin to be present to avoid an accidental green? Recommend: skip only when origin is explicitly declared and mismatched; a missing file with no origin stays Unresolvable (fail closed) - so origin is an intentional "this doc is foreign here" signal, never an accidental mute.

Metadata

Metadata

Assignees

No one assigned

    Labels

    dxDeveloper experience / CLI ergonomicsenhancementNew feature or request

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions