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.
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:
- GitHub organization + releases + domain verification (GitHub: verifying a domain for your organization, accessed 2026-10-18)1
- GitHub Container Registry: container images published under your GitHub identity (GitHub: working with the Container registry, accessed 2026-10-18)
OIDC-bound registries:
- npm trusted publishing (OIDC) (npm: trusted publishers, accessed 2026-10-18)2
- npm provenance, generated and viewable (generating provenance statements, viewing package provenance, accessed 2026-10-18)34
- PyPI trusted publishing (OIDC) (PyPI: trusted publishers, accessed 2026-10-18)5
Artifact-native distribution with machine-readable metadata:
- Terraform Registry API (score the API, not the JS UI) (Terraform Registry API)6
- Homebrew JSON API (formulae as JSON) (Homebrew API)7
- Open VSX registry API (Open VSX Swagger)8
- Read the Docs (docs-as-code imported from GitHub) (Read the Docs tutorial)9
- 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):
| Factor | Value | Why |
|---|---|---|
| P | 1 | stable URLs and APIs |
| U | 1 | CI can publish releases |
| B | 5 | DNS-verified domain and org identity (GitHub docs) |
| S | 4 | APIs and predictable artifact pages |
| G | 4 | strong namespaces and governance |
| I | 4 | immutable releases on; history is visible |
| A | 4 | artifact-heavy surface |
| F | 4 | spoofing requires the account plus the domain binding |
| C | 2 | CI-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):
| Factor | Value | Why |
|---|---|---|
| P | 1 | stable HTML; predictable URLs |
| U | 1 | CI-driven when imported from GitHub (Read the Docs tutorial) |
| B | 2 | binding is usually “repo-linked,” not DNS/OIDC-verified |
| S | 3 | structured enough for retrieval; not a registry API |
| G | 3 | governance exists, but it isn’t a supply-chain registry |
| I | 3 | builds are traceable, but not a strict immutable release ledger |
| A | 3 | docs artifacts, not packages |
| F | 2 | easier to spoof than an OIDC/DNS-bound registry loop |
| C | 2 | CI-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:
- Your canonical domain publishes JSON‑LD
OrganizationwithsameAspointing to each anchor (schema.org/sameAs, accessed 2026-10-18)14. - 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. - 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.
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"]
}
Copy-ready external link (rel=“me”)
<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
OrganizationJSON‑LD exists and lists everysameAsanchor. - 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 rateWe 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
- Pick one artifact type you genuinely ship (package, module, container, docs).
- Publish through a trusted CI flow (OIDC where available).
- Make sure there is a trail that can’t silently change (immutable releases, provenance, or pinned digests).
- Add the anchor URL to your canonical
sameAs. - Add a link back to your canonical domain (
rel="me"where supported). - 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
sameAslist - 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)
- 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.
- Bind identity: domain verification, plus OIDC trusted publishing where possible.
- Close the loop: canonical
sameAsand links back from each anchor. - Ship one replayable proof artifact (the audit packet) and point it at a release that can’t silently change.
- 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
On this page