sm-resolver — the corroboration kernel
A tiny, dependency-free kernel for detecting when independent sources disagree about the same subject. Point it at two or more sources, ask them the same question, and it reports any disagreement. It is the machinery under cross-registry / cross-method divergence detection, factored out so any layer can reuse it.
It is the reference implementation of the IETF Internet-Draft Multi-Source
Corroboration for AI Agent Discovery.
-01 is the published revision; -02 is prepared and adds the claim-outcome
vocabulary this release implements.
Requires Python 3.11 or later, and has no runtime dependencies — adopting it commits you to nothing beyond the standard library.
Five pieces, and only the resolvers know a wire format:
View— the contract a claim implements:comparable() → {field: value}. Those fields are what gets compared; aNonevalue never participates.Resolver[T]— a per-source adapter: a canonical id →(Status, View). It hides one source's format (an HTTP GET, a DID resolve, a DNS lookup) and MUST NOT raise — an unreachable source iserror(no claim), never a falseabsent. It MAY expose avantage— an observation context,class:valuefrom a controlled vocabulary (SPEC.md §3a) — andresolve_with_outcome, returning why a resolve ended as it did (timeout,unreachable,refused,unparseable,unverifiable). Both are optional; a resolver that exposes neither behaves exactly as before.Claim— one source's answer from one vantage in one sweep, carrying the status, the view, and the outcome (SPEC.md §2a). An outcome records why a resolve ended as it did and never affects which claims are compared.diff_claims— the pure diff: a sweep's claims →Findinglist. It emitsomission(present on one source, positively absent on another),source_equivocation(one source's vantages disagree with each other), and one finding per view field whose values disagree across sources. It never learns its layer.Corroborator— resolve every(source, vantage), diff, apply confirmation, and return oneSweepResultper subject: a verdict (AGREE/DIVERGENT/INSUFFICIENT), the claims, and the findings. Fewer than two decisive claims →INSUFFICIENT.
Install
pip install sm-resolver # zero runtime dependencies
Use
Supply a view (its fields are what you compare) and a thin resolver per source:
import asyncio
from collections.abc import Mapping
from dataclasses import dataclass
from sm_resolver import Corroborator, Status
@dataclass(frozen=True)
class RecordView: # your layer's view
endpoint: str | None = None
def comparable(self) -> Mapping[str, str | None]:
return {"endpoint": self.endpoint}
class MyResolver: # your thin per-source adapter
def __init__(self, label): self.label = label
async def resolve(self, agent_id) -> tuple[Status, RecordView | None]:
... # query this source; normalize to RecordView
results = asyncio.run(
Corroborator([MyResolver("a"), MyResolver("b")]).check(["agent-1"])
)
for r in results:
print(r.agent_id, r.verdict) # agent-1 DIVERGENT
for f in r.findings:
print(" ", f.kind, f.confirmation, f.detail)
# endpoint suspected {'field': 'endpoint', 'values': {'a': 'https://real', 'b': 'https://evil'}}
Long-running? Pass on_finding= — it fires once per distinct finding across the
corroborator's lifetime — and staleness_window_s= to promote a re-observed
finding from suspected to confirmed.
Who uses it
sm-divergence builds the
reference layers on this kernel: a discovery layer (registries — NEST, the
NANDA Index) and an identity layer (DID methods — did:key, did:web,
Universal Resolver), each supplying its own view and thin resolvers. Capability
and evidence layers are the same shape.
Design rules
- A timeout is not a claim. An unreachable or erroring source is excluded, never counted as an omission.
- Only present, comparable values disagree. A
Nonefield contributes nothing — an unverifiable value is not a disagreement. - The diff stays pure and generic — deterministic, no I/O, never raises, never learns its layer. All I/O lives in the resolvers.
Develop
make ci-local # uv: sync → ruff → format → mypy --strict → pytest
License
First published: 2026-07-04 | Last modified: 2026-08-28
Published by Stellarminds.ai under the MIT licence. Aligned with Project NANDA standards.
Metadata
Release files for sm-resolver 0.6.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| sm_resolver-0.6.0.tar.gz | 59.0 kB | With provenance | Details for sm_resolver-0.6.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| sm_resolver-0.6.0-py3-none-any.whl | Python 3 | none | any | Details for sm_resolver-0.6.0-py3-none-any.whl |
Total release size: 76.4 kB
Release files / sm_resolver-0.6.0.tar.gz
| Download URL | sm_resolver-0.6.0.tar.gz |
|---|---|
| Size | 59.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
c97952e7c5fe0fca7556b0b8ca9b093833f70b0c721a6781218c9a8eb8d52a46
|
|
BLAKE2b-256 checksum How to use checksums |
8a41c678a127ab922fa57a5a76eee2be8ad57b62edba7d4ff3b93f9801edba4a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 29, 2026.
Transparency logRelease files / sm_resolver-0.6.0-py3-none-any.whl
| Download URL | sm_resolver-0.6.0-py3-none-any.whl |
|---|---|
| Size | 17.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
bc304c9707c80e67868422eb8842c5a250275c8e96b5a96814e311ee37a074a6
|
|
BLAKE2b-256 checksum How to use checksums |
662821f6e9ab107de2c2dc98f2372f47e6292baa4386eb0be351434d94d6aa4c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 29, 2026.
Transparency logSecurity for sm-resolver 0.6.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for sm_resolver-0.6.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 29, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.