sm-resolver 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,28 @@
1
+ name: ci
2
+
3
+ on:
4
+ push:
5
+ branches: [main, master]
6
+ pull_request:
7
+
8
+ jobs:
9
+ ci-local:
10
+ runs-on: ubuntu-latest
11
+ strategy:
12
+ matrix:
13
+ python-version: ["3.11", "3.12"]
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ - uses: astral-sh/setup-uv@v5
17
+ - name: Pin Python
18
+ run: uv python pin ${{ matrix.python-version }}
19
+ # The same gate as `make ci-local` — green locally == green here.
20
+ - run: uv sync --extra dev
21
+ - run: uv run ruff check .
22
+ - run: uv run ruff format --check .
23
+ - run: uv run mypy sm_resolver
24
+ - run: uv run pytest -v
25
+ - name: Examples run
26
+ run: |
27
+ uv run python examples/corroborate_sources.py
28
+ uv run python examples/custom_layer.py
@@ -0,0 +1,48 @@
1
+ # Publish sm-resolver to PyPI on a version tag, via PyPI Trusted Publishing (OIDC).
2
+ #
3
+ # No API token anywhere: PyPI is told (once, in its web UI) to trust THIS repo +
4
+ # THIS workflow file, and the `id-token: write` permission below lets the job
5
+ # mint a short-lived OIDC token PyPI accepts. See PUBLISHING.md for the one-time
6
+ # setup and the exact values to type into PyPI's "Add a publisher" form.
7
+ #
8
+ # Fires only when a tag like `v0.1.0` is pushed — never on normal commits.
9
+ name: release
10
+
11
+ on:
12
+ push:
13
+ tags:
14
+ - "v*"
15
+
16
+ permissions:
17
+ contents: read
18
+
19
+ jobs:
20
+ publish:
21
+ name: Build + publish to PyPI
22
+ runs-on: ubuntu-latest
23
+ permissions:
24
+ # A job-level permissions block REPLACES the workflow-level one (it does not
25
+ # merge), so contents:read must be restated here or actions/checkout 404s on
26
+ # its own repo. Both lines are required.
27
+ contents: read # for actions/checkout
28
+ id-token: write # REQUIRED for Trusted Publishing — this is what replaces the token.
29
+ steps:
30
+ - uses: actions/checkout@v4
31
+
32
+ - uses: actions/setup-python@v5
33
+ with:
34
+ python-version: "3.12"
35
+
36
+ - name: Build sdist + wheel
37
+ run: |
38
+ python -m pip install --upgrade build
39
+ python -m build
40
+
41
+ - name: Check the built distributions
42
+ run: |
43
+ python -m pip install --upgrade twine
44
+ python -m twine check dist/*
45
+
46
+ - name: Publish to PyPI
47
+ # No `with: password:` — the action uses the OIDC token automatically.
48
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,14 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ .pytest_cache/
5
+ .mypy_cache/
6
+ .ruff_cache/
7
+ .venv/
8
+ dist/
9
+ build/
10
+ uv.lock.tmp
11
+
12
+ # Development & reconnaissance notes — design scoping, competitive research,
13
+ # decision records. Kept local, never published.
14
+ dev/
@@ -0,0 +1,23 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/) and this project adheres to
5
+ [Semantic Versioning](https://semver.org/).
6
+
7
+ ## [0.1.0] — 2026-07-04
8
+
9
+ Initial release — the corroboration kernel, extracted from `sm-divergence` once a
10
+ second layer (identity) joined the first (discovery) as a consumer.
11
+
12
+ ### Added
13
+ - **`View`** — the comparable-claim contract (`comparable() → named fields`).
14
+ - **`Resolver[T]`** — the per-source adapter protocol + `Status`.
15
+ - **`diff_views`** — the pure, generic diff (`omission` + per-field divergence),
16
+ with the uniform `{field, values}` finding detail.
17
+ - **`Corroborator`** — resolve-all + diff + dedupe-emit orchestration.
18
+ - **`Finding`** — the finding type with a stable dedup fingerprint.
19
+
20
+ Zero runtime dependencies. `make ci-local` gate (ruff / format / mypy --strict /
21
+ pytest + runnable examples) on Python 3.11 and 3.12. Full `sm-*` doc set —
22
+ README, WHITEPAPER, SPEC, GOVERNANCE, CONTRIBUTING, PUBLISHING — plus
23
+ `examples/` and a PyPI trusted-publishing `release.yml`.
@@ -0,0 +1,24 @@
1
+ # Contributing to sm-resolver
2
+
3
+ Contributions are accepted under the Developer Certificate of Origin (DCO)
4
+ sign-off model. Add `Signed-off-by: Your Name <you@example.com>` to every commit
5
+ (`git commit -s`).
6
+
7
+ ## Change process
8
+
9
+ 1. Spec-affecting changes open a PR updating the **spec (`SPEC.md`), the tests,
10
+ and the code together**. The `tests/` suite is the authoritative behavioural
11
+ specification of the diff.
12
+ 2. The gate is `make ci-local` (uv: `ruff` → `ruff format` → `mypy --strict` →
13
+ `pytest`). CI runs the same on Python 3.11 and 3.12. Push only when green.
14
+
15
+ ## House rules
16
+
17
+ - **A timeout is not a claim.** An unreachable or erroring source MUST be
18
+ excluded from the diff, never counted as an omission. A change here needs an
19
+ RFC-style PR to `SPEC.md` first.
20
+ - **The diff stays pure and generic.** `diff_views` is deterministic, does no
21
+ I/O, never raises, and never learns its layer. All I/O lives in resolvers.
22
+ - **Zero runtime dependencies.** The kernel is stdlib-only; keep it that way.
23
+ - **No expansion of the classification (SPEC §2), the view contract (SPEC §3), or
24
+ the diff (SPEC §4) without an RFC-style PR to `SPEC.md` first.**
@@ -0,0 +1,22 @@
1
+ # Governance
2
+
3
+ ## Scope
4
+
5
+ | In scope | Out of scope |
6
+ | --- | --- |
7
+ | the claim classification (`present`/`absent`/`error`), the view contract (`comparable()`), the pure diff (`omission` + per-field), and the `Corroborator` orchestration | any specific source format or transport (a consumer's resolver owns that); the view type's fields (the consumer defines them); signing / verification (a consumer's resolver applies it before returning a view); a transparency log; and remediation policy on a finding |
8
+
9
+ The kernel owns one thing — answering *"do these sources disagree about this
10
+ subject, and how?"* — for any view type and any layer. Anything outside the table
11
+ belongs to a consumer (e.g. `sm-divergence`) or the caller.
12
+
13
+ ## Versioning
14
+
15
+ Semantic Versioning 2.0.0. The claim classification (SPEC §2), the view contract
16
+ (SPEC §3), and the diff (SPEC §4) are frozen within a major; a change requires an
17
+ RFC-style PR to `SPEC.md` before code.
18
+
19
+ ## Conformance
20
+
21
+ An implementation is conforming iff it reproduces the finding set in SPEC §6. The
22
+ diff is pure and deterministic by construction, so conformance is mechanical.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 stellarminds.ai
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,10 @@
1
+ .PHONY: ci-local
2
+ # One-command pre-push gate — the same steps CI runs, hard-failing on first red.
3
+ ci-local:
4
+ uv sync --extra dev
5
+ uv run ruff check .
6
+ uv run ruff format --check .
7
+ uv run mypy sm_resolver
8
+ uv run pytest -v
9
+ uv run python examples/corroborate_sources.py
10
+ uv run python examples/custom_layer.py
@@ -0,0 +1,111 @@
1
+ Metadata-Version: 2.4
2
+ Name: sm-resolver
3
+ Version: 0.1.0
4
+ Summary: The source-agnostic corroboration kernel — Resolver[T], the View contract, a pure diff, and the Corroborator. Zero runtime dependencies.
5
+ Project-URL: Homepage, https://github.com/Sharathvc23/sm-resolver
6
+ Project-URL: Spec, https://github.com/Sharathvc23/sm-resolver/blob/main/SPEC.md
7
+ Project-URL: Whitepaper, https://github.com/Sharathvc23/sm-resolver/blob/main/WHITEPAPER.md
8
+ Author: Sharath (Stellarminds)
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Keywords: accountability,ai-agents,corroboration,divergence,nanda,resolver
12
+ Requires-Python: >=3.11
13
+ Provides-Extra: dev
14
+ Requires-Dist: mypy>=1.10; extra == 'dev'
15
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
16
+ Requires-Dist: pytest>=8; extra == 'dev'
17
+ Requires-Dist: ruff>=0.6; extra == 'dev'
18
+ Description-Content-Type: text/markdown
19
+
20
+ # sm-resolver — the corroboration kernel
21
+
22
+ **A tiny, dependency-free kernel for detecting when independent sources disagree
23
+ about the same subject.** Point it at two or more sources, ask them the same
24
+ question, and it reports any disagreement. It is the machinery under
25
+ cross-registry / cross-method divergence detection, factored out so any layer can
26
+ reuse it.
27
+
28
+ Four pieces, and only the resolvers know a wire format:
29
+
30
+ - **`View`** — the contract a claim implements: `comparable() → {field: value}`.
31
+ Those fields are what gets compared; a `None` value never participates.
32
+ - **`Resolver[T]`** — a per-source adapter: a canonical id → `(Status, View)`.
33
+ It hides one source's format (an HTTP GET, a DID resolve, a DNS lookup) and
34
+ MUST NOT raise — an unreachable source is `error` (no claim), never a false
35
+ `absent`.
36
+ - **`diff_views`** — the pure diff: per-source claims → `Finding` list. It emits
37
+ `omission` (present on one source, positively absent on another) and one
38
+ finding per view field whose values disagree. It never learns its layer.
39
+ - **`Corroborator`** — resolve every source, diff, and emit new findings once
40
+ (deduped). Fewer than two sources → no-op.
41
+
42
+ ## Install
43
+
44
+ ```bash
45
+ pip install sm-resolver # zero runtime dependencies
46
+ ```
47
+
48
+ ## Use
49
+
50
+ Supply a view (its fields are what you compare) and a thin resolver per source:
51
+
52
+ ```python
53
+ import asyncio
54
+ from collections.abc import Mapping
55
+ from dataclasses import dataclass
56
+ from sm_resolver import Corroborator, Status
57
+
58
+ @dataclass(frozen=True)
59
+ class RecordView: # your layer's view
60
+ endpoint: str | None = None
61
+ def comparable(self) -> Mapping[str, str | None]:
62
+ return {"endpoint": self.endpoint}
63
+
64
+ class MyResolver: # your thin per-source adapter
65
+ def __init__(self, label): self.label = label
66
+ async def resolve(self, agent_id) -> tuple[Status, RecordView | None]:
67
+ ... # query this source; normalize to RecordView
68
+
69
+ findings = asyncio.run(
70
+ Corroborator([MyResolver("a"), MyResolver("b")]).check(["agent-1"])
71
+ )
72
+ for f in findings:
73
+ print(f.kind, f.agent_id, f.detail)
74
+ # endpoint agent-1 {'field': 'endpoint', 'values': {'a': 'https://real', 'b': 'https://evil'}}
75
+ ```
76
+
77
+ Long-running? Pass `on_finding=` — it fires once per distinct finding across the
78
+ corroborator's lifetime.
79
+
80
+ ## Who uses it
81
+
82
+ [`sm-divergence`](https://github.com/Sharathvc23/sm-divergence) builds the
83
+ reference layers on this kernel: a **discovery** layer (registries — NEST, the
84
+ NANDA Index) and an **identity** layer (DID methods — `did:key`, `did:web`,
85
+ Universal Resolver), each supplying its own view and thin resolvers. Capability
86
+ and evidence layers are the same shape.
87
+
88
+ ## Design rules
89
+
90
+ - **A timeout is not a claim.** An unreachable or erroring source is excluded,
91
+ never counted as an omission.
92
+ - **Only present, comparable values disagree.** A `None` field contributes
93
+ nothing — an unverifiable value is not a disagreement.
94
+ - **The diff stays pure and generic** — deterministic, no I/O, never raises,
95
+ never learns its layer. All I/O lives in the resolvers.
96
+
97
+ ## Develop
98
+
99
+ ```bash
100
+ make ci-local # uv: sync → ruff → format → mypy --strict → pytest
101
+ ```
102
+
103
+ ## License
104
+
105
+ [MIT](LICENSE)
106
+
107
+ ---
108
+
109
+ *First published: 2026-07-04 | Last modified: 2026-07-04*
110
+
111
+ *Personal research contributions aligned with [Project NANDA](https://projectnanda.org) standards. [Stellarminds.ai](https://stellarminds.ai)*
@@ -0,0 +1,42 @@
1
+ # Publishing sm-resolver to PyPI
2
+
3
+ `sm-resolver` publishes via **PyPI Trusted Publishing** — no API tokens. You tell
4
+ PyPI once that this repo's `release.yml` workflow may publish; after that, pushing
5
+ a version tag builds and uploads automatically.
6
+
7
+ ## One-time setup (≈5 minutes — this is the part only you can do)
8
+
9
+ 1. Sign in to PyPI (the same account that publishes the rest of the `sm-*` family).
10
+ 2. Go to **https://pypi.org/manage/account/publishing/** → "Add a pending
11
+ publisher" (use *pending*, because the `sm-resolver` project doesn't exist on
12
+ PyPI yet — the first publish creates it).
13
+ 3. Fill the form with **exactly** these values:
14
+
15
+ | Field | Value |
16
+ |-------|-------|
17
+ | PyPI Project Name | `sm-resolver` |
18
+ | Owner | `Sharathvc23` |
19
+ | Repository name | `sm-resolver` |
20
+ | **Workflow name** | `release.yml` ← just the filename |
21
+ | Environment name | *(leave blank)* |
22
+
23
+ That's it. No secret is created or stored anywhere.
24
+
25
+ ## Releasing (every time, after setup)
26
+
27
+ ```bash
28
+ # bump `version` in pyproject.toml + update CHANGELOG, commit, then:
29
+ git tag v0.1.0
30
+ git push origin v0.1.0
31
+ ```
32
+
33
+ The `release` workflow builds the sdist + wheel, runs `twine check`, and uploads
34
+ to PyPI over OIDC. Watch it under the repo's **Actions** tab. Within a minute,
35
+ `pip install sm-resolver` works for everyone.
36
+
37
+ ## Notes
38
+
39
+ - The tag (`v0.1.0`) and `version` in `pyproject.toml` must match.
40
+ - Dry run anytime, no upload: `python -m build && python -m twine check dist/*`.
41
+ - Making this public also unblocks `sm-divergence`'s CI, which depends on this
42
+ package (a private dependency otherwise needs a PAT to clone in Actions).
@@ -0,0 +1,92 @@
1
+ # sm-resolver — the corroboration kernel
2
+
3
+ **A tiny, dependency-free kernel for detecting when independent sources disagree
4
+ about the same subject.** Point it at two or more sources, ask them the same
5
+ question, and it reports any disagreement. It is the machinery under
6
+ cross-registry / cross-method divergence detection, factored out so any layer can
7
+ reuse it.
8
+
9
+ Four pieces, and only the resolvers know a wire format:
10
+
11
+ - **`View`** — the contract a claim implements: `comparable() → {field: value}`.
12
+ Those fields are what gets compared; a `None` value never participates.
13
+ - **`Resolver[T]`** — a per-source adapter: a canonical id → `(Status, View)`.
14
+ It hides one source's format (an HTTP GET, a DID resolve, a DNS lookup) and
15
+ MUST NOT raise — an unreachable source is `error` (no claim), never a false
16
+ `absent`.
17
+ - **`diff_views`** — the pure diff: per-source claims → `Finding` list. It emits
18
+ `omission` (present on one source, positively absent on another) and one
19
+ finding per view field whose values disagree. It never learns its layer.
20
+ - **`Corroborator`** — resolve every source, diff, and emit new findings once
21
+ (deduped). Fewer than two sources → no-op.
22
+
23
+ ## Install
24
+
25
+ ```bash
26
+ pip install sm-resolver # zero runtime dependencies
27
+ ```
28
+
29
+ ## Use
30
+
31
+ Supply a view (its fields are what you compare) and a thin resolver per source:
32
+
33
+ ```python
34
+ import asyncio
35
+ from collections.abc import Mapping
36
+ from dataclasses import dataclass
37
+ from sm_resolver import Corroborator, Status
38
+
39
+ @dataclass(frozen=True)
40
+ class RecordView: # your layer's view
41
+ endpoint: str | None = None
42
+ def comparable(self) -> Mapping[str, str | None]:
43
+ return {"endpoint": self.endpoint}
44
+
45
+ class MyResolver: # your thin per-source adapter
46
+ def __init__(self, label): self.label = label
47
+ async def resolve(self, agent_id) -> tuple[Status, RecordView | None]:
48
+ ... # query this source; normalize to RecordView
49
+
50
+ findings = asyncio.run(
51
+ Corroborator([MyResolver("a"), MyResolver("b")]).check(["agent-1"])
52
+ )
53
+ for f in findings:
54
+ print(f.kind, f.agent_id, f.detail)
55
+ # endpoint agent-1 {'field': 'endpoint', 'values': {'a': 'https://real', 'b': 'https://evil'}}
56
+ ```
57
+
58
+ Long-running? Pass `on_finding=` — it fires once per distinct finding across the
59
+ corroborator's lifetime.
60
+
61
+ ## Who uses it
62
+
63
+ [`sm-divergence`](https://github.com/Sharathvc23/sm-divergence) builds the
64
+ reference layers on this kernel: a **discovery** layer (registries — NEST, the
65
+ NANDA Index) and an **identity** layer (DID methods — `did:key`, `did:web`,
66
+ Universal Resolver), each supplying its own view and thin resolvers. Capability
67
+ and evidence layers are the same shape.
68
+
69
+ ## Design rules
70
+
71
+ - **A timeout is not a claim.** An unreachable or erroring source is excluded,
72
+ never counted as an omission.
73
+ - **Only present, comparable values disagree.** A `None` field contributes
74
+ nothing — an unverifiable value is not a disagreement.
75
+ - **The diff stays pure and generic** — deterministic, no I/O, never raises,
76
+ never learns its layer. All I/O lives in the resolvers.
77
+
78
+ ## Develop
79
+
80
+ ```bash
81
+ make ci-local # uv: sync → ruff → format → mypy --strict → pytest
82
+ ```
83
+
84
+ ## License
85
+
86
+ [MIT](LICENSE)
87
+
88
+ ---
89
+
90
+ *First published: 2026-07-04 | Last modified: 2026-07-04*
91
+
92
+ *Personal research contributions aligned with [Project NANDA](https://projectnanda.org) standards. [Stellarminds.ai](https://stellarminds.ai)*
@@ -0,0 +1,66 @@
1
+ # Corroboration Kernel — Procedure
2
+
3
+ **Spec version:** `resolver/0.1-draft`
4
+ **Status:** DRAFT / Working Draft
5
+ **Last edited:** 2026-07-04
6
+
7
+ The keywords MUST, MUST NOT, SHOULD, SHOULD NOT, MAY are to be interpreted as in RFC 2119.
8
+
9
+ ---
10
+
11
+ ## 1. Introduction
12
+
13
+ Given several **sources** that each answer "what do you have for subject *X*?",
14
+ the kernel classifies each source's claim, reduces a present claim to a
15
+ comparable **view**, and diffs the views into findings. It is source-, format-,
16
+ and layer-agnostic; a consumer supplies a view type and a resolver per source.
17
+
18
+ ## 2. Claim classification
19
+
20
+ For a source *S* and subject *A*, a resolver MUST resolve exactly one of:
21
+
22
+ - **`present`** — *S* served a claim, reduced to a view (§3).
23
+ - **`absent`** — *S* positively asserts *A* is unknown.
24
+ - **`error`** — any other outcome (unreachable, timeout, unparseable, or a claim
25
+ the resolver cannot map to a view).
26
+
27
+ An `error` claim MUST be excluded from the diff entirely. It is NOT `absent`: **a
28
+ source that failed to answer has asserted nothing, and MUST NOT be treated as
29
+ claiming the subject is absent.** A resolver MUST NOT raise; a failure is `error`.
30
+
31
+ ## 3. The view contract
32
+
33
+ A `present` claim MUST be reduced to a **view**: an object exposing
34
+ `comparable() → Mapping[field_name, str | None]`. The keys are the fields that
35
+ participate in the diff; a value of `None` never participates (an unverifiable or
36
+ missing field is not a disagreement). The view type is the consumer's choice.
37
+
38
+ ## 4. The diff
39
+
40
+ Input is `views[source][subject]`: a view (present), `null` (absent), or *absent
41
+ from the mapping* (the `error` case — no claim). Per subject:
42
+
43
+ - **`omission`** — emit iff present on ≥1 source AND absent on ≥1 other (both
44
+ positive claims). Detail: `{present_on, missing_from}` (sorted source lists).
45
+ - **field divergence** — for each field name in any present view's
46
+ `comparable()`, collect the non-`None` values keyed by source; emit a finding
47
+ of `kind = <field name>` iff more than one distinct value appears. Detail:
48
+ `{"field": <name>, "values": {source: value}}`.
49
+
50
+ The diff MUST be pure: deterministic, no I/O, no raising. A subject MAY produce
51
+ multiple findings.
52
+
53
+ ## 5. Corroborator
54
+
55
+ A `Corroborator` takes two or more resolvers, resolves each subject against each,
56
+ builds the `views` mapping (excluding `error` claims), and applies §4. Fewer than
57
+ two sources → no-op. It SHOULD deduplicate emitted findings by a stable
58
+ fingerprint over `{kind, agent_id, detail}`.
59
+
60
+ ## 6. Conformance
61
+
62
+ A conforming implementation MUST reproduce, for a fixed set of per-source claim
63
+ inputs, the §4 finding set: agreement → none; present + absent → `omission`;
64
+ distinct field values → a finding of that field's kind; single/identical value or
65
+ one-value-beside-`None` → none; `error`/unreachable → excluded (no `omission`).
66
+ The diff is pure and deterministic, so conformance is mechanical.
@@ -0,0 +1,118 @@
1
+ # sm-resolver: A Corroboration Kernel for the Internet of AI Agents
2
+
3
+ *Personal research contribution by [Stellarminds.ai](https://stellarminds.ai), aligned with [Project NANDA](https://projectnanda.org) standards.*
4
+
5
+ ---
6
+
7
+ ## Abstract
8
+
9
+ Accountability for a federated system where agents reach each other through
10
+ middlemen — registries, name services, DID methods, catalogs — reduces to one
11
+ question asked over and over: *do independent sources agree about the same
12
+ subject, and if not, how do they differ?* This paper describes the minimal,
13
+ source-agnostic machinery that answers it: a per-source **resolver** that
14
+ normalizes any format to one comparable **view**, and a pure **diff** that turns
15
+ the views from all sources into findings. `sm-resolver` is that machinery and
16
+ nothing else — a dependency-free kernel other packages build layers on.
17
+
18
+ ## 1. Problem
19
+
20
+ A middleman can lie: it can tamper with what it serves, omit a subject it holds,
21
+ or equivocate between clients. Signing an artifact defeats tampering, but no
22
+ signature proves what a source *withheld* or whether it showed everyone the same
23
+ thing. The robust defense is corroboration — ask two or more sources and treat
24
+ disagreement as a signal — and it recurs at every layer: a registry about an
25
+ agent's endpoint, a DID method about its key, a catalog about its capabilities.
26
+ The comparison logic is identical each time; only the wire format differs. Left
27
+ un-factored, every layer reimplements the same diff and the same "a timeout is
28
+ not an absence" subtleties, and drifts.
29
+
30
+ ## 2. The corroboration primitive
31
+
32
+ The operation factors into three pieces, and only the first knows a format:
33
+
34
+ - **Resolve** — a per-source *resolver* performs that source's query, applies its
35
+ native verification, and returns a *view*. It is the only format-aware piece;
36
+ a thin adapter lives here.
37
+ - **View** — the comparable reduction of a claim to named string fields
38
+ (`comparable() → {field: value}`). A `None` value never participates.
39
+ - **Diff** — a pure function over the views from all sources. It emits `omission`
40
+ (present on one, positively absent on another) and one finding per view field
41
+ whose values disagree. It never learns what layer it serves.
42
+
43
+ Because the diff operates on normalized views, it is untouched by format
44
+ heterogeneity — the same code compares a registry claim and a DID-document claim
45
+ once each is a view. A `Corroborator` wires it together: resolve every source,
46
+ diff, emit new findings once.
47
+
48
+ ## 3. Why it is a separate package
49
+
50
+ The kernel is small (three types and one pure function) but it is the part every
51
+ accountability layer shares, so it earns its own package the moment a second
52
+ layer consumes it. Extracting it keeps each layer a *thin adapter* — a view type
53
+ and per-source resolvers — with no generic machinery of its own, and lets a layer
54
+ depend on the kernel without dragging any sibling's wire code along. The kernel
55
+ carries **zero runtime dependencies**, so adopting it commits a consumer to
56
+ nothing but the standard library.
57
+
58
+ ## 4. Design axioms
59
+
60
+ Consequences of the threat model, not preferences.
61
+
62
+ 1. **Corroborate, don't trust.** One artifact proves its own contents, not that
63
+ the source served it honestly or to everyone. The minimum unit of
64
+ accountability is two independent sources; with one, the kernel is a no-op
65
+ rather than false assurance.
66
+ 2. **A timeout is not a claim.** An unreachable source has asserted nothing and
67
+ is excluded — never counted as an omission — so a transient fault cannot
68
+ manufacture a false finding.
69
+ 3. **Only present, comparable values disagree.** A `None` field contributes
70
+ nothing; an unverifiable value is not a disagreement and cannot be laundered
71
+ into one that discredits an honest source.
72
+ 4. **Detect; do not remediate.** The kernel reports *that* sources disagree and
73
+ *how*. What to do about it is caller policy.
74
+
75
+ ## 5. Composition
76
+
77
+ `sm-resolver` is the base; layers supply a view and resolvers:
78
+
79
+ ```
80
+ sm-resolver (View · Resolver[T] · diff · Corroborator)
81
+ ▲ ▲ ▲
82
+ discovery layer identity layer capability / evidence
83
+ RecordView + DidView + (a view + resolvers)
84
+ registry resolvers DID-method resolvers
85
+ ```
86
+
87
+ [`sm-divergence`](https://github.com/Sharathvc23/sm-divergence) is the reference
88
+ consumer: a discovery layer (registries — NEST, the NANDA Index) and an identity
89
+ layer (DID methods — `did:key`, `did:web`, a Universal Resolver), each a thin set
90
+ of resolvers over this kernel.
91
+
92
+ ## 6. NANDA alignment
93
+
94
+ Project NANDA's discovery is multi-source by design — a lean Index delegating to
95
+ a quilt of registries. Corroboration is only *possible* because of that
96
+ decentralization: the redundancy built for resilience doubles as an integrity
97
+ check. `sm-resolver` is the mechanism that turns that latent property into an
98
+ active one, underpinning **accountable discovery** across the DNS and CA pillars.
99
+
100
+ ## 7. Future work
101
+
102
+ - A quorum reducer over findings (*k*-of-*n* agreement) for more than two sources.
103
+ - Durable per-source divergence history (source reputation).
104
+ - A normative wire schema for a shareable `Finding` (cross-tool interchange).
105
+
106
+ ## 8. Related packages
107
+
108
+ | Package | Role |
109
+ | --- | --- |
110
+ | [`sm-divergence`](https://github.com/Sharathvc23/sm-divergence) | The reference discovery + identity layers built on this kernel. |
111
+ | [`sm-bridge`](https://github.com/Sharathvc23/sm-bridge) | NANDA-compatible registry endpoints — a source a resolver corroborates across. |
112
+ | [`sm-conformance`](https://github.com/Sharathvc23/sm-conformance) | The shared Ed25519 / JCS signing convention a resolver's verification uses. |
113
+
114
+ ---
115
+
116
+ ## License
117
+
118
+ [MIT](LICENSE).
File without changes