Metadata-Version: 2.4
Name: sm-feed
Version: 0.2.0
Summary: The Verifiable Agent Feed — a signed, hash-chained, cursor-based append-only feed a subscriber can prove is complete and untampered.
Project-URL: Homepage, https://github.com/Sharathvc23/sm-feed
Project-URL: Spec, https://github.com/Sharathvc23/sm-feed/blob/main/SPEC.md
Author-email: StellarMinds <hello@stellarminds.ai>
License-Expression: MIT
License-File: LICENSE
Keywords: ai-agents,did,federation,feed,nanda,transparency-log,verifiable
Classifier: Development Status :: 3 - Alpha
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Typing :: Typed
Requires-Python: >=3.11
Requires-Dist: sm-arp>=0.3.0
Provides-Extra: dev
Requires-Dist: coverage>=7.0; extra == 'dev'
Requires-Dist: jsonschema>=4.0; extra == 'dev'
Requires-Dist: mypy>=1.0; extra == 'dev'
Requires-Dist: pytest>=7.0; extra == 'dev'
Requires-Dist: ruff==0.15.13; extra == 'dev'
Description-Content-Type: text/markdown

# sm-feed

**The Verifiable Agent Feed — a signed, hash-chained, cursor-based append-only feed a subscriber can prove is complete and untampered.**

An agent that publishes an ongoing stream — claims, lifecycle events, aggregate
signals — is served today by an RSS feed or a bespoke webhook. Both ask the
subscriber to *trust the server* for completeness: a feed can silently drop or
reorder an item and no one can tell. `sm-feed` closes that gap. Every entry is
Ed25519-signed and hash-chained, so a subscriber that walks a contiguous run
verifies authenticity, integrity, **and completeness** in one pass. It owns the
*feed* — the wire shape and its verification — and nothing else: the transport,
the storage, and the meaning of the payload are the consumer's. The payload is
opaque (any JSON object with a `type`), so one format carries any agent's stream.

## What this package secures (v0.1)

- **Authenticity** — each entry is Ed25519-signed by the feed's issuer DID.
- **Tamper-evidence** — `entry_hash` content-addresses the entry; any edit to any
  field breaks it and verification returns falsy (never raises on hostile input).
- **Completeness** — entries are hash-chained (`prev_hash`) with contiguous `seq`;
  a dropped or reordered entry breaks the chain from the subscriber's anchor.
- **Head attestation** — a signed head (`{seq, entry_hash}`) lets a subscriber pin
  "the feed as of here" and detect a later rewind. Rewind detection requires
  passing that pinned head back as `expected_head`; the check is inert without it
  (SPEC §5 rule 6).
- **Adversarially tested.** The conformance corpus includes a hostile vector per
  failure path: dropped entry, tampered payload, wrong anchor, forged head.

## What this package does NOT (yet) do

- **Actively surfacing forks across subscribers** — equivocation *detection* from
  two conflicting signed heads ships (`detect_equivocation`, SPEC §7), but the
  gossip transport that brings a peer's head to you, and witness co-signing to
  catch a fork before two heads meet, are the consumer's / a later property.
- **Merkle inclusion proofs** — a deliberate non-goal, not deferred: the chain is
  strictly linear and completeness is proven by walking `prev_hash` (SPEC §8).
- **Push delivery, retry, and subscription registration** — the consumer's
  responsibility; this primitive fixes only the payload and its verification.
- **Detecting a withheld or stale view** — an issuer that freezes one subscriber
  at `seq = 40` while serving others through `seq = 90` has not tampered, rewound,
  or forked, and nothing here catches it. Completeness is a claim about the run
  between two anchors, never about being current (`THREATMODEL.md`).
- **A trusted clock or key custody** — `issued_at` is caller-asserted; key
  generation/rotation/revocation is out of scope. SPEC Appendix A reserves a
  `feed/key-rotation` payload convention, non-normatively.

## Features

- One dependency (`sm-arp`, for the shared Ed25519 / JCS / did:key crypto).
- Pure, wall-clock-free core: the caller supplies timestamps.
- Pull (`GET …/feed?since=<cursor>`) and push (`POST` a page) deliver the same
  object; `verify_page` checks both.
- **Bounded pages** — a publisher may serve a prefix of a long backlog with the
  head running ahead of it, so a transport has a conformant way to cap a response
  body (SPEC §4.1).
- Opaque typed payload — the same wire format serves claims, events, or deltas.
- **Compaction** — a late subscriber adopts a signed state checkpoint as genesis
  and walks forward, instead of replaying from the start (`adopt_checkpoint`).
- **Equivocation proofs** — two conflicting signed heads yield a non-repudiable
  proof that an issuer forked its feed (`detect_equivocation`).

## Installation

```bash
pip install sm-feed
```

The reference implementation is on PyPI; this repository is not yet public. The
published sdist carries the specification, the threat model, and the conformance
corpus, so a second implementation can be built from the package alone.

## Quick start

```python
from sm_arp import Identity
from sm_feed import FeedLog, verify_page

issuer = Identity.generate()
log = FeedLog(issuer)
log.append({"type": "example/claim", "title": "First"}, issued_at="2026-01-01T00:00:00+00:00")
log.append({"type": "example/claim", "title": "Second"}, issued_at="2026-01-01T01:00:00+00:00")

# Serve on pull (GET /feed?since=<cursor>) or POST on push — same object.
# limit= serves a bounded prefix of a long backlog (SPEC §4.1).
page = log.page(cursor=None, generated_at="2026-01-01T02:00:00+00:00")

# Subscriber: verify authenticity + completeness, keep the new cursor.
ok, reason, cursor = verify_page(page, expected_prev_hash=None, expected_head=None)
assert ok, reason

# Persist both, and pass them back on the next pull:
#   cursor["entry_hash"] -> expected_prev_hash
#   cursor["head"]       -> expected_head   (this is the rewind defence — pass it)
# cursor["complete_to_head"] is False while a bounded backlog is still draining.
```

A runnable end-to-end example (publish → verify → detect a dropped entry) is in
[`examples/quick_start.py`](./examples/quick_start.py).

## Reference fixtures

The golden vectors under [`conformance/vectors/`](./conformance/vectors/) are
regenerated by `conformance/_vector_gen.py` from a fixed, non-secret fixture seed
(`"11"×32` — regenerable, never a real key) and replayed by
`tests/test_conformance_vectors.py`. They are the language-agnostic corpus a
second implementation replays to prove interoperability.

## Specification

- [`SPEC.md`](./SPEC.md) — normative wire shape and verification, working draft.
- [`WHITEPAPER.md`](./WHITEPAPER.md) — design rationale and axioms.
- [`THREATMODEL.md`](./THREATMODEL.md) — what it defends, and what it doesn't.
Worked payload profiles (non-normative), each with a runnable example:

- [`capability-announcement`](./docs/profiles/capability-announcement.md) — an
  agent announcing what it can do, and withdrawing it. The first-person case:
  issuer and subject are the same DID.
  ([example](./examples/capability_announcement.py))
- [`registry-changelog`](./docs/profiles/registry-changelog.md) — a registry
  exposing its mutations. The same shape in the third person; a registry that
  subscribes to announcement feeds aggregates them into one of these.
  ([example](./examples/registry_changelog.py))

## Related packages

| Package | Role |
| --- | --- |
| [`sm-arp`](https://github.com/Sharathvc23/sm-arp) | Agency Receipt Protocol — the signed, hash-chained record primitive sm-feed reuses for crypto |
| [`sm-authority`](https://github.com/Sharathvc23/sm-authority) | Common Authority Evidence — establishes who controls a subject |
| [`sm-bridge`](https://github.com/Sharathvc23/sm-bridge) | AgentFacts / registry endpoints — a natural feed publisher and consumer |

## License

[MIT](./LICENSE)

---

*First published: 2026-07-30 | Last modified: 2026-08-02*

*Personal research contributions aligned with [Project NANDA](https://projectnanda.org) standards. [Stellarminds.ai](https://stellarminds.ai)*
