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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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