Skip to content
ARGBE.TECH

Article in English · Deutsch

GEO series, 17 min readBy argbe.tech

Your Website Is Not Evidence: Build Authority on Supply Chains

Your own site can’t corroborate itself. Corroboration Ops is a proposed playbook for binding your identity to artifact registries, publishing proof trails others can check, and running drift checks so your public identity stays consistent.

Your website is a brochure.

A verifier needs a supply chain.

Your domain is the one surface you fully control, which is exactly why it can’t corroborate itself. Anyone checking who you are and what you ship (a buyer’s engineer, a procurement team, an agent acting for either) learns more from third-party records you can’t silently edit than from your own pages.

That’s the premise of this article, and it’s a proposal. We haven’t measured how any particular AI system weighs these records. The goal is narrower and testable: make the check possible and cheap, then keep it passing.

This article uses Argbe.tech as the worked example, including the checks it currently fails.

Reality Drift: The Hidden Failure Mode of “Topical Authority”

Reality drift is when the external web disagrees with your canonical truth, so readers hedge, misattribute, or route around you.

The earlier parts of this series (the Verifiable Truth Protocol and Fan-Out) make your truth internally consistent and cheap to ingest.

Corroboration Ops is the missing layer: external truth acceptance.

A careful verifier looks for failure-resistance:

  • Can I verify identity without trusting a single surface?
  • Can I replay a trail of artifacts that shows this org actually ships?
  • Is that trail kept current, and would anyone notice if it drifted?

If the answers are no, the rational move is to treat your claims as unverified.

Your Website Is Not Evidence

None of this means you lie. It means a verifier can’t tell apart:

  • “true claim” vs “marketing claim” vs “stale claim”

…without corroboration.

So corroboration has to come from platforms that are hard to impersonate and cheap to keep current.

This is the core reframe:

Authority is an identity graph plus an artifact trail, not a content volume game.

The Supply-Chain Authority Stack (Proposed)

These are not “profiles.” They’re supply-chain surfaces.

Your domain
JSON-LD sameAs

GitHub org
+ releases

Registries
npm, PyPI

Docs, archive
RTD, Zenodo

Provenance
build origin

In words: your domain lists its anchors; a GitHub organization with releases is the root; package registries and documentation or archive services hang off it; registries that support provenance tie each package back to the build that produced it.

Ten anchors to choose from (pick what matches what you ship)

Root trust:

  1. GitHub organization + releases + domain verification (GitHub: verifying a domain for your organization, accessed 2026-10-18)1
  2. GitHub Container Registry: container images published under your GitHub identity (GitHub: working with the Container registry, accessed 2026-10-18)

OIDC-bound registries:

  1. npm trusted publishing (OIDC) (npm: trusted publishers, accessed 2026-10-18)2
  2. npm provenance, generated and viewable (generating provenance statements, viewing package provenance, accessed 2026-10-18)34
  3. PyPI trusted publishing (OIDC) (PyPI: trusted publishers, accessed 2026-10-18)5

Artifact-native distribution with machine-readable metadata:

  1. Terraform Registry API (score the API, not the JS UI) (Terraform Registry API)6
  2. Homebrew JSON API (formulae as JSON) (Homebrew API)7
  3. Open VSX registry API (Open VSX Swagger)8
  4. Read the Docs (docs-as-code imported from GitHub) (Read the Docs tutorial)9
  5. Zenodo (GitHub releases archived with a DOI) (Zenodo developers, all accessed 2026-10-18)10

You’re not trying to be everywhere. You’re trying to be corroboratable.

Here’s the contrast that keeps you from drifting into “community talk”:

Community platforms create opinions. Registries create supply-chain facts: versioned artifacts, provenance metadata, and verifiable bindings.

npm is a good example of how far this already goes: when you publish with trusted publishing from GitHub Actions or GitLab CI/CD, npm generates provenance attestations by default for public packages from public repositories (npm: trusted publishers, accessed 2026-10-18).

The Scoring Model (So This Doesn’t Become Platform Bingo)

Without a model, teams turn this into vibes: “we should be active on X.”

The model below is ours, and it is a proposal. The factors are the properties we think make an anchor hard to fake and cheap to maintain. The weights are judgment, not values fitted to data; change them if your threat model differs, but write them down so the scores stay comparable.

Hard gates (fail = score zero)

  • P (Parsable): stable HTML or a documented API/JSON endpoint exists.
  • U (Self-serve update): the owner or CI can update it without editorial delays.

If the systems you care about can’t fetch an anchor (blocked by robots.txt, a bot wall, or rendered only in client JavaScript with no stable endpoint), score P as zero for that purpose. Crawler access is documented per operator: OpenAI, for example, says sites that disallow OAI-SearchBot won’t appear in ChatGPT search answers (OpenAI: overview of OpenAI crawlers, accessed 2026-10-18)11, and Common Crawl documents the robots.txt rule that blocks CCBot (Common Crawl: CCBot, accessed 2026-10-18)12.

General rule: if the API is the anchor, score the API, not the UI. JavaScript-heavy frontends change; documented endpoints are what a script can fetch and parse reliably.

Rate each factor from one to five

  • B Binding strength: OIDC trusted publishing, DNS verification, verified publisher programs
  • S Structured access: predictable metadata pages and APIs (not screenshots)
  • G Governance: namespaces, anti-spam, verification programs
  • I Immutability: published versions can’t be silently overwritten
  • A Artifact density: the platform is fundamentally artifacts, not bios
  • F Cost-to-fake: difficulty of spoofing the whole loop (account, proofs, domain binding)

Rate update friction C the same way, from one (CI-driven) to five (manual bureaucracy).

Compute the score

Core       = (0.25B + 0.15S + 0.15G + 0.15I + 0.15A + 0.15F) / 5
Efficiency = (6 - C) / 5
Score      = 100 * P * U * Core * Efficiency        (0 to 100)

Binding strength gets the largest weight because it’s the factor that ties the anchor to you rather than to someone using your name. The rest are weighted equally because we had no basis to rank them.

Worked example: GitHub org + releases

GitHub is the easiest root anchor because it has:

  • org-level verification (domain binding via a DNS TXT record)
  • artifact history (releases, tags, commit graph)
  • an API and predictable URLs

One caveat matters for the immutability score. GitHub releases are editable by default: a release’s tag can be moved and its assets replaced. Immutable releases lock the tag and assets after publication and generate a release attestation, but you have to turn them on (GitHub: immutable releases, accessed 2026-10-18)13.

Example scoring for an org with a verified domain and immutable releases (our judgment):

FactorValueWhy
P1stable URLs and APIs
U1CI can publish releases
B5DNS-verified domain and org identity (GitHub docs)
S4APIs and predictable artifact pages
G4strong namespaces and governance
I4immutable releases on; history is visible
A4artifact-heavy surface
F4spoofing requires the account plus the domain binding
C2CI-driven once set up
Core       = (0.25×5 + 0.15×(4+4+4+4+4)) / 5 = 0.85
Efficiency = (6 − 2) / 5                       = 0.8
Score      = 100 × 1 × 1 × 0.85 × 0.8           = 68

Same org, releases left mutable (I = 2):           Score = 63.2
This site’s org today: unverified (B = 3), mutable releases (I = 2):   Score = 55.2

The last line is our own anchor scored honestly: the GitHub org isn’t domain-verified and its releases aren’t immutable. The IdentityCI run below shows both.

This is why “we have a blog” is weak, but “we ship artifacts on governed registries” is strong.

Worked example: Read the Docs

Read the Docs is valuable because it’s docs-as-code tied back to GitHub, but its binding is usually weaker than a registry with OIDC or DNS verification. That’s not a criticism; it’s the model doing its job.

Example scoring (illustrative):

FactorValueWhy
P1stable HTML; predictable URLs
U1CI-driven when imported from GitHub (Read the Docs tutorial)
B2binding is usually “repo-linked,” not DNS/OIDC-verified
S3structured enough for retrieval; not a registry API
G3governance exists, but it isn’t a supply-chain registry
I3builds are traceable, but not a strict immutable release ledger
A3docs artifacts, not packages
F2easier to spoof than an OIDC/DNS-bound registry loop
C2CI-driven once configured
Core  = (0.25×2 + 0.15×(3+3+3+3+2)) / 5 = 0.52
Score = 100 × 1 × 1 × 0.52 × 0.8        = 41.6

Closed-Loop Identity (How You Defeat Impersonation)

Corroboration is not a list of outbound links.

It’s a loop:

  1. Your canonical domain publishes JSON‑LD Organization with sameAs pointing to each anchor (schema.org/sameAs, accessed 2026-10-18)14.
  2. External anchors point back to the canonical domain: best-effort through homepage or repository fields, and rel="me" where you control the HTML (microformats: rel-me, accessed 2026-10-18)15.
  3. At least one platform verifies the domain through a DNS TXT record, binding the loop to real control of DNS. The IETF has a draft on exactly this practice, still an Internet-Draft as of its latest revision (draft-ietf-dnsop-domain-verification-techniques, accessed 2026-10-18)16.

links back

links back

Your domain
sameAs

GitHub org
verified

npm package
trusted publisher

In words: your domain names its anchors, and each anchor names your domain back. The verification (DNS for GitHub, OIDC for npm) is what stops a lookalike from completing the same loop.

Practical rule:

  • Use rel="me" where you control HTML (docs sites, GitHub Pages, your own properties).
  • Where you don’t, use the platform’s homepage or repository fields as the link back, and rely on DNS or OIDC as the binding proof.

Copy-ready canonical JSON-LD (sameAs)

This is this site’s actual sameAs list:

{
	"@context": "https://schema.org",
	"@type": "Organization",
	"name": "Argbe.tech",
	"url": "https://argbe.tech",
	"sameAs": ["https://x.com/argbeknwn", "https://github.com/argbe-tech"]
}
<a href="https://argbe.tech" rel="me">Canonical</a>

If you do only one thing after reading this: implement the loop.

Without it, nothing distinguishes your profile from a lookalike namespace someone else registers.

DriftOps: IdentityCI (Truth Maintenance Is the New Authority)

Anchors rot.

Links break. Platforms change markup. Bot rules change. Someone forgets to update a description after a pivot.

So you treat identity like you treat uptime: measure it.

IdentityCI is a scheduled job that checks a small drift surface and stores the result as an artifact. GitHub Actions can run it on a cron schedule (GitHub: events that trigger workflows, accessed 2026-10-18)17.

What to validate (minimum)

  • Canonical Organization JSON‑LD exists and lists every sameAs anchor.
  • Each anchor resolves and still maps back to the canonical domain.
  • The domain binding still holds (for GitHub: the org is verified).
  • The release your audit packet names is the latest one, and it can’t have changed since (immutable, or pinned by commit SHA and asset digest).
  • Provenance exists for your latest packages, where your registry supports it.

A working IdentityCI script

// identity-ci.mjs — Node 18+, no dependencies. Run on a schedule; exit 1 on any FAIL.
// Edit CONFIG for your organization.
const CONFIG = {
	canonical: 'https://argbe.tech',
	githubOrg: 'argbe-tech',
	releasesRepo: 'argbe-tech/releases',
	auditPacket: 'https://argbe.tech/audit/argbe_tech.json'
}

const results = []
const record = (status, check, detail) => results.push({ status, check, detail })
const gh = async (path) =>
	(
		await fetch(`https://api.github.com${path}`, {
			headers: { accept: 'application/vnd.github+json' }
		})
	).json()

// 1. Canonical Organization JSON-LD lists sameAs anchors
const html = await (await fetch(CONFIG.canonical)).text()
const nodes = [...html.matchAll(/<script type="application\/ld\+json">([\s\S]*?)<\/script>/g)]
	.map((m) => JSON.parse(m[1]))
	.flatMap((doc) => doc['@graph'] ?? [doc])
const org = nodes.find((n) => [].concat(n['@type']).includes('Organization'))
const sameAs = org?.sameAs ?? []
record(sameAs.length ? 'PASS' : 'FAIL', 'Organization JSON-LD has sameAs', JSON.stringify(sameAs))

// 2. Each anchor resolves
for (const url of sameAs) {
	const res = await fetch(url, { redirect: 'follow' })
	record(res.ok ? 'PASS' : 'WARN', `anchor resolves: ${url}`, `HTTP ${res.status}`)
}

// 3. GitHub org points back to the canonical domain and is domain-verified
const ghOrg = await gh(`/orgs/${CONFIG.githubOrg}`)
const backlink = (ghOrg.blog ?? '').replace(/\/$/, '')
record(
	backlink === CONFIG.canonical ? 'PASS' : 'FAIL',
	'GitHub org website field points back',
	backlink
)
record(ghOrg.is_verified ? 'PASS' : 'FAIL', 'GitHub org domain verified', String(ghOrg.is_verified))

// 4. Latest release is immutable (tag and assets locked)
const [latest] = await gh(`/repos/${CONFIG.releasesRepo}/releases?per_page=1`)
record(
	latest?.immutable ? 'PASS' : 'FAIL',
	`latest release immutable: ${latest?.tag_name}`,
	String(latest?.immutable)
)

// 5. Audit packet names the latest release
const packet = await (await fetch(CONFIG.auditPacket)).json()
const tag = packet?.target?.release_tag
record(
	tag === latest?.tag_name ? 'PASS' : 'FAIL',
	'audit packet matches latest release',
	`${tag} vs ${latest?.tag_name}`
)

for (const r of results) console.log(`${r.status} ${r.check} (${r.detail})`)
const failures = results.filter((r) => r.status === 'FAIL').length
console.log(`${failures} failure(s)`)
process.exitCode = failures ? 1 : 0

Run against this site on 2026-10-18:

$ node identity-ci.mjs
PASS Organization JSON-LD has sameAs (["https://x.com/argbeknwn","https://github.com/argbe-tech"])
PASS anchor resolves: https://x.com/argbeknwn (HTTP 200)
PASS anchor resolves: https://github.com/argbe-tech (HTTP 200)
PASS GitHub org website field points back (https://argbe.tech)
FAIL GitHub org domain verified (false)
FAIL latest release immutable: v0.130.1 (false)
PASS audit packet matches latest release (v0.130.1 vs v0.130.1)
2 failure(s)

Two real failures, both on our side: the org’s domain isn’t verified, and the release the audit packet points to could still be edited. Until they’re fixed, our own loop is self-asserted at the root.

To schedule it, add a workflow. shell: bash matters here: GitHub runs an explicit bash shell with pipefail, so a failing check still fails the job even though its output is piped to tee (GitHub: workflow syntax, accessed 2026-10-18).

name: identity-ci
on:
  schedule:
    - cron: '0 6 * * 1' # Mondays, 06:00 UTC
  workflow_dispatch:
jobs:
  identity:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
      - name: Run IdentityCI
        shell: bash
        run: node scripts/identity-ci.mjs | tee identity-ci.log
      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: identity-ci-log
          path: identity-ci.log

Pinning releases you can’t make immutable

If immutable releases aren’t available to you, pin the release in your audit packet instead: record the tag’s commit SHA and each asset’s SHA-256 digest, and let IdentityCI compare them on every run. GitHub exposes a digest for each uploaded release asset through its releases API (GitHub changelog: releases now expose digests for release assets, accessed 2026-10-18)18. Add these checks to the script above:

// Assumes the packet records target.commit_sha and target.asset_digests ({ name: 'sha256:…' }).
const ref = await gh(`/repos/${CONFIG.releasesRepo}/git/ref/tags/${packet.target.release_tag}`)
record(
	ref.object?.sha === packet.target.commit_sha ? 'PASS' : 'FAIL',
	'release tag still points at the recorded commit',
	ref.object?.sha
)
for (const asset of latest?.assets ?? []) {
	const pinned = packet.target.asset_digests?.[asset.name]
	record(
		asset.digest === pinned ? 'PASS' : 'FAIL',
		`asset digest unchanged: ${asset.name}`,
		asset.digest
	)
}

For an annotated tag, the ref points at a tag object rather than the commit; dereference it or record the tag object’s SHA instead.

Limits of IdentityCI

  • “Resolves” is not “points back.” A JavaScript-heavy profile page (X, LinkedIn) can return a page without exposing a link back to your domain in its HTML; check those by hand, or accept them as weaker anchors.
  • Unauthenticated GitHub API calls are rate-limited; give the job a token in CI.
  • A passing run shows your anchors are consistent and bound to your domain. It doesn’t show your claims are true or that any AI system uses these anchors.

The rule that keeps this cheap

Don’t build a scraping empire.

Score anchors up front for stable endpoints. If an anchor forces brittle DOM scraping, it’s not a good anchor.

Proof Primitive: The Audit Packet (Agent-Credible, Not Marketing)

Corroboration Ops needs a proof object that a verifier can replay.

Keep it boring:

  • versioned schema
  • stable canonical URL
  • pointers to artifacts that can’t silently change (an immutable release, a container digest, build logs)

If you already run a golden record and Fan-Out, your audit packet is just an export. Be precise about what it does: it points to where someone can check that this org ships and that these claims map to canonical URLs. It doesn’t prove either by itself; it’s self-published, like the rest of your domain.

Minimal packet shape (copy-ready)

Publish a small JSON object at a stable endpoint. This is the shape this site publishes at /audit/argbe_tech.json, rendered from the golden record:

{
	"generated_at": "2026-10-20",
	"canonical_url": "https://argbe.tech/audit/argbe_tech.json",
	"target": {
		"project_id": "project.argbe_tech_website",
		"org": "Argbe.tech",
		"canonical_domain": "https://argbe.tech",
		"version": "0.130.1",
		"release_tag": "v0.130.1"
	},
	"evidence": [
		{
			"type": "github_release",
			"url": "https://github.com/argbe-tech/releases/releases/tag/v0.130.1",
			"relationship": "release_archive"
		},
		{
			"type": "golden_record",
			"url": "https://argbe.tech/entity.json",
			"relationship": "canonical_truth"
		},
		{
			"type": "change_feed",
			"url": "https://argbe.tech/changes.json",
			"relationship": "version_history"
		}
	],
	"provenance": {
		"golden_record_url": "https://argbe.tech/entity.json",
		"change_feed_url": "https://argbe.tech/changes.json",
		"releases_repo": "https://github.com/argbe-tech/releases"
	}
}

What makes it replayable is the release link: a verifier can fetch the release the packet names and compare it with the golden record. What would make it trustworthy is the part this site hasn’t done yet: an immutable release, or the commit SHA and asset digests recorded in the packet.

Example of a visible claim you can anchor (real values from the golden record):

Our pricing model is a Fixed weekly rate.

Fixed weekly rate

We serve DACH + United States.

["DACH","United States"]

These are still self-published claims. What the packet adds is a stable, versioned place to check them and a link to the release they shipped in.

Implementation Playbooks (Do This, Not “Be More Active Online”)

Playbook A — Ship one corroborated artifact

  1. Pick one artifact type you genuinely ship (package, module, container, docs).
  2. Publish through a trusted CI flow (OIDC where available).
  3. Make sure there is a trail that can’t silently change (immutable releases, provenance, or pinned digests).
  4. Add the anchor URL to your canonical sameAs.
  5. Add a link back to your canonical domain (rel="me" where supported).
  6. Add the anchor to IdentityCI.

Playbook B — Pivot without losing reality

When positioning or the offer changes, the failure mode isn’t the copy.

It’s drift between:

  • canonical site
  • registries and descriptions
  • docs surfaces
  • what the provenance trail says you ship

The fix is procedural:

  • update the golden record (so your internal truth stays consistent)
  • update the canonical JSON‑LD sameAs list
  • update the anchor descriptions that matter (GitHub, one registry, one docs surface)
  • run IdentityCI and treat failures like broken builds

The Checklist (Start in One Day)

  1. Pick three anchors you can actually maintain. Three is our rule of thumb: enough to form a loop, few enough that drift doesn’t become debt.
  2. Bind identity: domain verification, plus OIDC trusted publishing where possible.
  3. Close the loop: canonical sameAs and links back from each anchor.
  4. Ship one replayable proof artifact (the audit packet) and point it at a release that can’t silently change.
  5. Turn on IdentityCI weekly and keep its logs as artifacts.

If you do this, you stop “trying to look authoritative.”

You become cheap to verify.

How This Was Checked

The IdentityCI output, the GitHub organization and release status, and the audit packet shape come from running the script and API calls shown against argbe.tech and the argbe-tech GitHub organization on 2026-10-18. Platform behavior (GitHub verification and immutable releases, npm and PyPI trusted publishing, crawler documentation) was checked against the linked documentation the same day. The scoring model, its weights, and the premise that verifiers prefer corroborated anchors are proposals; none was measured against AI citation outcomes.

Evidence Locker

Post tags

  • geo
  • generative engine optimization
  • ai agents
  • agentic readiness
  • supply chain security
  • provenance
  • oidc
  • identity
  • drift
  • verification
ShareXLinkedIn