From 22e73e2b125c7a2a06dd3d0cd503aeb98e667c0b Mon Sep 17 00:00:00 2001 From: Gio Della-Libera Date: Wed, 15 Jul 2026 07:15:55 -0700 Subject: [PATCH 01/11] docs(rfc-0009): align feed v1 specifications --- rfcs/0009/clawhub-account-feeds-v1-spec.md | 395 +++++++--------- rfcs/0009/hosted-feed-v1-spec.md | 509 ++++++++------------- rfcs/0009/signed-feed-trust-v1-spec.md | 398 ++++++++-------- 3 files changed, 566 insertions(+), 736 deletions(-) diff --git a/rfcs/0009/clawhub-account-feeds-v1-spec.md b/rfcs/0009/clawhub-account-feeds-v1-spec.md index 014cb44b..fdfee8f0 100644 --- a/rfcs/0009/clawhub-account-feeds-v1-spec.md +++ b/rfcs/0009/clawhub-account-feeds-v1-spec.md @@ -1,290 +1,251 @@ # ClawHub Account Feeds v1 Addendum Specification -This document is the implementer-facing account-feed addendum for RFC 0009 -hosted feeds. It builds on the core feed specification and the signed-feed trust -addendum. +This document defines the ClawHub account and publisher discovery-feed contract +associated with RFC 0009. Status: draft addendum, tied to RFC 0009. +The key words MUST, MUST NOT, SHOULD, SHOULD NOT, and MAY are normative. + ## Scope This addendum defines: -- ClawHub account and publisher feed identity; -- account-feed metadata; -- account feed entries for plugins and skills; -- the boundary between following and install authority; -- user-facing account feed discovery states; -- downstream registry consumption of ClawHub account-feed facts; -- diagnostics and provenance requirements. - -This addendum does not define: - -- the core feed document shape; -- signed envelope mechanics; -- package artifact trust; -- security scanning implementation; -- self-service claim workflows; -- organization-admin approval workflows; -- notification delivery protocols beyond feed/follow semantics. - -## Model +- stable account and publisher feed identity; +- the strict account-feed document and entry shapes; +- bounded pagination and refresh semantics; +- the boundary between following, discovery, trust, and installation; +- downstream registry provenance and diagnostics. -ClawHub can publish feeds for stable account or publisher identities. These -feeds let OpenClaw and downstream registries discover the packages associated -with a publisher without treating account following as a security or install -grant. +It does not define an installable catalog entry, package candidate, signed +account-feed payload type, notification transport, organization approval, +artifact trust, or security-scanning implementation. -An account feed is still a normal hosted feed. It should use the core v1 feed -shape for entries and the signed-feed trust addendum for authenticity. The -account-feed addendum defines the feed identity and metadata conventions around -that core payload. +## Relationship To Hosted Catalog Feeds -## Identity +An account feed is a ClawHub discovery projection. It is not the Hosted Feed v1 +install catalog defined by `hosted-feed-v1-spec.md`. -Account feeds must use stable ClawHub identity, not mutable display names. +Account-feed entries identify public skills and plugins associated with an +account or publisher. A client that wants to install one of those entries MUST +resolve it through an accepted install catalog or another explicit package +resolver and MUST still apply source, integrity, scan, approval, and runtime +policy checks. -Recommended feed ids: - -```text -clawhub-account: -clawhub-publisher: -clawhub-org: -``` +This distinction allows users to follow people and organizations without +turning social discovery into install authority. -Display handles, names, avatars, bios, and URLs are metadata. They may change -without changing feed identity. +## Wire Contract -## Feed Metadata +The normative v1 schema is the generated `AccountFeedSchema` in the ClawHub +`clawhub-schema` package. The schema is strict. Conforming publishers MUST NOT +add unknown fields to a v1 document or entry. -Account feeds should carry bounded account metadata in `metadata`. +## Feed Document ```json { "schemaVersion": 1, - "id": "clawhub-publisher:openclaw", + "feedId": "clawhub.publisher.publishers:01JEXAMPLE", + "scope": "publisher", + "accountId": "users:01JEXAMPLE", + "publisherId": "publishers:01JEXAMPLE", + "handle": "openclaw", + "displayName": "OpenClaw", "generatedAt": "2026-07-15T00:00:00.000Z", "sequence": 17, - "expiresAt": "2026-07-22T00:00:00.000Z", - "metadata": { - "kind": "clawhubPublisherFeed", - "publisherId": "openclaw", - "handle": "openclaw", - "displayName": "OpenClaw", - "profileUrl": "https://clawhub.ai/creators/openclaw", - "officialState": "official" - }, - "entries": [] + "entries": [], + "nextCursor": null } ``` -Recommended metadata fields: - | Field | Type | Required | Semantics | | --- | --- | --- | --- | -| `kind` | string | Yes | `clawhubAccountFeed`, `clawhubPublisherFeed`, or `clawhubOrganizationFeed`. | -| `accountId` | string | Conditional | Stable account id for account feeds. | -| `publisherId` | string | Conditional | Stable publisher id for publisher feeds. | -| `organizationId` | string | Conditional | Stable organization id for organization feeds. | -| `handle` | string | Optional | Current display handle. | -| `displayName` | string | Optional | Current display name. | -| `profileUrl` | string | Optional | Human-facing ClawHub profile URL. | -| `claimState` | string | Optional | Publisher claim state when known. | -| `officialState` | string | Optional | ClawHub official-state fact when known. | -| `restrictionState` | string | Optional | Suspended, restricted, or normal state when known. | +| `schemaVersion` | integer | Yes | MUST be `1`. | +| `feedId` | string | Yes | Stable feed identity. | +| `scope` | string | Yes | `account` or `publisher`. | +| `accountId` | string or null | Yes | Stable account id when applicable. | +| `publisherId` | string or null | Yes | Stable publisher id when applicable. | +| `handle` | string or null | Yes | Current mutable public handle. | +| `displayName` | string | Yes | Current public display name. | +| `generatedAt` | RFC 3339 string | Yes | Projection generation time. | +| `sequence` | non-negative safe integer | Yes | Monotonic revision for this `feedId`. | +| `entries` | array | Yes | Ordered public discovery entries. | +| `nextCursor` | string or null | Yes | Opaque continuation cursor. | + +An account-scoped feed MUST have a non-null `accountId`. A publisher-scoped +feed MUST have a non-null `publisherId`. The other identity may be present when +ClawHub has a public relationship between them. + +Feed ids use stable opaque ids, not mutable handles: + +```text +clawhub.account. +clawhub.publisher. +``` -Metadata fields are facts from ClawHub. They do not by themselves grant install -authority. +Changing a handle or display name MUST NOT change `feedId`. -## Entry Requirements +## Entry Object -Entries in account feeds must use the core feed entry shape. Account-feed -entries should preserve the account or publisher provenance in bounded metadata: +Every account-feed entry has exactly these fields: + +| Field | Type | Required | Semantics | +| --- | --- | --- | --- | +| `kind` | string | Yes | `skill` or `plugin`. | +| `id` | string | Yes | Stable ClawHub object or package identity. | +| `name` | string | Yes | Current package name or slug. | +| `displayName` | string | Yes | User-facing title. | +| `summary` | string or null | Yes | Bounded public summary. | +| `url` | string | Yes | Absolute HTTPS URL or origin-relative URL-reference for the canonical public ClawHub page. | +| `updatedAt` | number | Yes | Finite, non-negative Unix epoch time in milliseconds used for ordering. | + +Example: ```json { - "type": "plugin", - "id": "acpx", - "title": "ACP-X", - "version": "1.2.3", - "state": "available", - "publisher": { - "id": "openclaw", - "trust": "official" - }, - "metadata": { - "clawhub": { - "publisherId": "openclaw", - "packageId": "acpx", - "packageUrl": "https://clawhub.ai/packages/acpx" - } - }, - "install": { - "candidates": [ - { - "sourceRef": "public-clawhub", - "package": "acpx", - "version": "1.2.3", - "integrity": "sha512-..." - } - ] - } + "kind": "plugin", + "id": "packages:01JEXAMPLE", + "name": "@openclaw/acpx", + "displayName": "ACP-X", + "summary": "ACP integration for OpenClaw.", + "url": "https://clawhub.ai/plugins/@openclaw/acpx", + "updatedAt": 1784073600000 } ``` -Account feeds may include plugins, skills, or both. Clients must not route a -skill through the plugin installer merely because both appear in one account -feed. - -## Following Semantics - -Following a ClawHub account, publisher, or organization is a discovery and -notification preference. It means: +An entry intentionally has no install candidate, artifact URL, integrity claim, +trust label, approval state, or scan result. Those facts have separate owners. -- show the followed identity in the user's followed-publisher list; -- optionally include matching entries in discovery filters; -- optionally notify the user when the publisher adds or updates packages; -- preserve provenance so the user can see why an entry appeared. +Clients MUST resolve an origin-relative `url` against the origin of the account +feed request, not against an unrelated configured catalog origin. Publishers +MUST order entries by descending `updatedAt` and MUST use a stable identity +tie-breaker when timestamps are equal. -Following does not mean: +## Pagination And Sequence -- the publisher is official; -- the package is OpenClaw-reviewed; -- the package passed security scanning; -- the package is locally approved; -- the package is organization-approved; -- the package is installable; -- the package bypasses source-profile, artifact-integrity, or policy checks. +Account feeds are bounded projections. `nextCursor` is opaque and MUST NOT be +constructed or interpreted by clients. A client follows `nextCursor` until it +is null or until a local page/entry bound is reached. -Clients should label followed-account results as followed or from a followed -publisher, not as trusted or approved. +The sequence identifies the logical feed revision, not an individual page. +Every page for one coherent revision SHOULD report the same `feedId`, +`generatedAt`, and `sequence`. A publisher MUST NOT derive sequence from the +newest visible entry timestamp because deletion or demotion could make it +decrease. -## Claim, Official, And Scan State +Cursor errors SHOULD return a bounded client error rather than silently +restarting at the first page. ClawHub MUST bound page size, scan work, response +size, and cursor lifetime. -ClawHub may expose publisher facts such as claim state, official state, -restriction state, scan state, or registry review state. These facts should be -explicit fields or metadata; clients and downstream registries must not infer -them from following state. +## Public API -Recommended separation: +The initial ClawHub routes are: -| Fact | Meaning | -| --- | --- | -| `claimState` | Whether the publisher identity is claimed or claimable. | -| `officialState` | Whether ClawHub marks the publisher as official. | -| `restrictionState` | Whether the publisher is restricted, suspended, or normal. | -| `scanState` | Whether a package has scan metadata, if ClawHub exposes it. | -| `registryReviewState` | Whether a downstream registry has reflected review state. | -| `followState` | Whether the current user follows the publisher. | +```text +GET /api/v1/accounts/{accountId} +GET /api/v1/accounts/{accountId}/feed +GET /api/v1/publishers/{publisherId} +GET /api/v1/publishers/{publisherId}/feed +``` -Each fact has a separate source of authority. Clients should show labels and -diagnostics that preserve the distinction. +Detail routes expose public identity and profile facts. Feed routes expose the +strict discovery projection above. APIs SHOULD distinguish missing, non-public, +restricted, suspended, and temporarily unavailable identities without exposing +private moderation evidence. -## Follow Privacy And Abuse Controls +## Following Semantics -Follow state is user-specific preference data. ClawHub should treat follower -lists as private by default unless a user or organization explicitly publishes a -public list. Account-feed publication must not expose who follows an account. +Following is a reversible user preference. It MAY: -Follow and unfollow operations should be reversible, idempotent, and bounded. -Implementations should rate-limit follow changes, suppress self-follow -notifications, and provide a way to mute or disable notifications without -unfollowing the publisher. +- include the identity in a followed-publisher list; +- include matching entries in discovery filters; +- produce bounded update notifications; +- explain why an entry appeared. -Blocked, suspended, deleted, or private publishers should not silently remain in -normal followed-discovery results. Clients should show an actionable status such -as unavailable, restricted, or private rather than treating the feed as a -successful empty feed. +Following MUST NOT mean that: -## Downstream Registry Consumption +- the publisher is official, reviewed, or trusted; +- an entry passed security scanning; +- an entry is locally or organization approved; +- an entry is installable; +- source, integrity, or runtime policy checks may be bypassed. -Downstream registries can consume ClawHub account feeds as source input. They -may subset, block, pin, scan, or re-publish account-feed entries into their own -effective feeds. +UI and APIs MUST use follow/following language rather than trust or approval +language. -When a downstream registry emits an effective feed, it should preserve bounded -provenance: +## Privacy And Abuse Controls -- ClawHub feed id; -- ClawHub feed sequence or checksum; -- ClawHub publisher id; -- ClawHub package id; -- selected version or revision; -- downstream policy state. +Follower lists are private by default. Publishing an account feed MUST NOT +reveal who follows it. -Downstream registries should not treat account following as approval. They must -run their own review, scan, allow-list, block-list, or organization policy before -calling an entry approved. +Follow and unfollow operations SHOULD be authenticated, reversible, idempotent, +rate-limited, and separately mutable from notification preferences. Systems +SHOULD suppress self-follow notifications and make blocked, deleted, private, +or suspended identities explicit rather than representing them as successful +empty feeds. -## Discovery And UI Requirements +## Trust And Signing Boundary -Clients and ClawHub surfaces should make account-feed state explainable: +The signed-feed trust v1 addendum currently defines only the catalog payload +type `openclaw.official-external-plugin-catalog-feed.v1`. It MUST NOT be reused +for account-feed bytes. -- profile pages should show package entries without implying install approval; -- follow buttons should say follow or following, not trust; -- official labels should be separate from scan or review labels; -- unavailable, blocked, or restricted publishers should have actionable status; -- discovery results should say when an entry appears because of a followed - publisher. +Signed account feeds require a separately specified payload type, expected +identity binding, test vectors, and client implementation. Until that lands, +HTTPS transport and ClawHub origin identify the API source, but account-feed +content still grants discovery only and never install authority. -Empty or restricted account feeds should be explicit. A missing account feed, -private feed, deleted publisher, or blocked publisher should not silently look -like an empty successful feed. +## Downstream Registry Consumption -## API Shape +A downstream registry MAY consume account feeds as discovery input, then +resolve selected entries through an install catalog and apply its own review, +scan, allow-list, block-list, and organization policy. -The exact ClawHub HTTP routes can evolve, but account feed APIs should preserve -these capabilities: +Derived records SHOULD preserve: -```text -GET /v1/feeds/accounts/{accountId} -GET /v1/feeds/publishers/{publisherId} -GET /v1/feeds/organizations/{organizationId} -POST /v1/publisher-follows -DELETE /v1/publisher-follows/{publisherId} -GET /v1/publisher-follows -``` +- account `feedId`, sequence, and cursor or page checksum; +- account and publisher ids when present; +- entry kind and stable id; +- the separately resolved catalog feed id, sequence, package version, source + reference, and integrity value; +- downstream policy state and decision time. -Feed routes should return hosted feed payloads or signed envelopes. Follow -routes are user-specific application APIs and should not be confused with the -feed payload itself. +Following state MUST NOT be translated into approval. -## Diagnostics And Audit +## Diagnostics -Clients and ClawHub should emit bounded diagnostics for: +ClawHub and clients SHOULD distinguish: -- account-feed fetch result; -- account-feed verification result; -- missing, private, deleted, restricted, or blocked publisher state; -- follow and unfollow actions; -- discovery results sourced from followed publishers; -- downstream registry import of account-feed entries. +- malformed account feed; +- missing, private, restricted, suspended, or deleted identity; +- invalid or expired cursor; +- bounded-scan or page-limit exhaustion; +- follow and unfollow outcomes; +- discovery results included because of a follow; +- catalog resolution failure for a discovered entry. -Diagnostics must not include raw tokens, private profile data, unbounded URLs, -prompt contents, tool arguments, package payload bytes, or raw user identifiers -unless the local deployment explicitly permits them. +Diagnostics MUST NOT expose tokens, private profile data, moderation evidence, +unbounded URLs, package payload bytes, or private follower identities. -## Publisher Checklist +## ClawHub Conformance -ClawHub account-feed publication is compatible with this addendum when it: +A v1 account-feed publisher: -- uses stable account, publisher, or organization ids; -- keeps mutable display fields in metadata; -- signs account feeds through the ClawHub platform feed-signing path; -- preserves entry provenance for publisher and package identity; -- separates follow, official, scan, registry review, and install state; -- makes empty, private, restricted, and blocked feed states distinguishable. +- emits the strict schema above using stable opaque identity; +- keeps mutable presentation values separate from feed identity; +- paginates deterministically with monotonic revision semantics; +- exposes public discovery facts only; +- keeps following separate from official, scan, approval, and install state; +- distinguishes unavailable identity states from successful empty feeds. -## Client Checklist +## Client Conformance -An OpenClaw client is compatible with this addendum when it: +A v1 client: -- treats account feeds as discovery input, not install authority; -- verifies ClawHub-hosted account feeds according to the signed-feed trust - addendum when configured; -- preserves followed-publisher provenance in discovery results; -- keeps source-profile and artifact-integrity checks on the install path; -- displays follow state separately from official, scan, review, or approval - state; -- produces bounded diagnostics for account-feed failures and followed-publisher - discovery. +- treats account entries as discovery references only; +- preserves account and publisher provenance; +- uses opaque bounded pagination; +- resolves installation through a separate accepted catalog path; +- displays follow state separately from trust, scan, review, and approval; +- provides bounded diagnostics for feed, follow, and catalog-resolution errors. diff --git a/rfcs/0009/hosted-feed-v1-spec.md b/rfcs/0009/hosted-feed-v1-spec.md index 98243fd4..9489a806 100644 --- a/rfcs/0009/hosted-feed-v1-spec.md +++ b/rfcs/0009/hosted-feed-v1-spec.md @@ -1,198 +1,114 @@ # Hosted Feed v1 Core Specification -This document is the implementer-facing core specification for RFC 0009, +This document defines the interoperable v1 catalog feed contract for RFC 0009, Hosted Feeds for Plugins and Skills. The RFC explains the motivation and -rollout plan. This file defines the v1 feed document, entry, source-reference, -candidate, refresh, fallback, and validation contract that feed publishers and -OpenClaw clients can build against. +rollout plan; this sidecar defines the wire shape publishers and clients use. Status: draft, tied to RFC 0009. -## Scope - -This core specification covers the feed contract from the accepted RFC: - -- feed document shape; -- plugin and skill entry shape; -- source references through locally configured source profiles; -- npm, ClawHub, and Git install candidates; -- entry states; -- refresh, cache, and bundled fallback behavior; -- client validation and rejection behavior; -- basic publisher and client conformance checklists. - -This core specification intentionally does not define: - -- signed feed envelopes; -- publisher public-key configuration; -- ClawHub account or publisher feeds; -- account following, notifications, or profile feeds; -- enterprise feed composition semantics beyond final entry state precedence; -- tenant-admin policy, Microsoft MOS3, or other enterprise registry behavior; -- runtime tool policy or connector/agent activation. - -Those belong in separate addendum specs once the corresponding RFC addenda are -accepted. - -## What This Enables Today - -The core v1 spec is enough for a company, registry, or community publisher to -start hosting an OpenClaw-compatible plugin and skill catalog. - -A publisher can: - -- generate a v1 JSON feed; -- host it on an internal or public HTTPS endpoint; -- point entries at package artifacts through `sourceRef` values; -- use local OpenClaw source profiles to resolve those `sourceRef` values to - npm, ClawHub-compatible, Git, or private artifact sources; -- mark entries as `available`, `recommended`, `disabled`, `blocked`, or - `deprecated`; -- publish a final effective feed generated by its own policy, review, or - registry system, as long as the emitted feed follows this core shape. +The key words MUST, MUST NOT, SHOULD, SHOULD NOT, and MAY are normative. -An OpenClaw client can then cache, inspect, search, and install from that feed -using the configured source profiles and existing plugin or skill install paths. - -The core spec does not require the publisher to expose how its internal policy -system produced the feed. For example, a company can combine internal review, -private package metadata, and local approval rules in its own registry, then -emit the final OpenClaw-compatible feed as the client-facing catalog. Later -addendum specs can standardize signed trust, account feeds, following, -enterprise composition, audit, and key management without changing the basic -feed contract. - -## Design Lineage - -Hosted feeds use the same broad pattern as APT, Homebrew formula/cask metadata, -npm package metadata, and other package ecosystems: - -- catalog metadata is separate from artifact storage; -- clients cache catalog state and can fall back when the network is unavailable; -- entries carry stable identity, selected version, source, integrity, and state; -- package or artifact installation remains a separate client/runtime action. - -OpenClaw adds one important constraint in the core v1 contract: feed entries -select locally configured source profiles by `sourceRef`. Feeds do not smuggle -registry domains, credentials, SSH material, or client trust roots into clients. - -## Terminology - -- **Feed**: a deterministic JSON catalog document. -- **Entry**: one plugin or skill record inside a feed. -- **Source profile**: local client configuration that maps a `sourceRef` to npm, - ClawHub, Git, or another approved artifact source. -- **Bundled fallback**: a feed shipped with OpenClaw for offline and recovery - behavior. -- **Snapshot**: a locally accepted and cached feed payload plus metadata such as - sequence, expiry, checksum, and HTTP validators. - -## Media Type +## Scope -Publishers should use this content type where possible: +This specification defines: -```text -application/vnd.openclaw.catalog-feed+json;v=1 -``` +- the catalog feed document and entry shapes; +- plugin and skill install candidates; +- locally configured source profiles; +- entry state, refresh, validation, and fallback behavior; +- publisher and client conformance requirements. -Clients must not rely only on the HTTP content type. They must validate the JSON -shape and schema version before accepting a feed. +It does not define signed envelopes, account following, account-feed discovery, +organization approval, artifact signing, malware scanning, or runtime policy. +Those are separate contracts. -## Compatibility And Evolution +## Wire Contract -Feed v1 uses a conservative compatibility model: +The normative v1 publisher schema is the generated `CatalogFeedSchema` in the +ClawHub `clawhub-schema` package. The matching OpenClaw consumer is the hosted +official external plugin catalog parser. If prose or examples in this document +disagree with those shipped artifacts, that disagreement is a specification bug +and must be corrected before adding a second interpretation. -- clients must reject unsupported major `schemaVersion` values; -- publishers may add optional fields without changing `schemaVersion`; -- clients must ignore unknown optional fields unless local policy says to fail - closed; -- new required fields require a new schema version; -- enum expansions must be treated as unsupported by old clients until the - client has an explicit fallback rule; -- publishers should keep removed or renamed fields readable for at least one - transition release when practical. +Publishers SHOULD serve unsigned feeds as `application/json`. Signed transport +uses the payload type defined by the signed-feed trust addendum. Clients MUST +validate the body and MUST NOT infer compatibility from the HTTP content type. -The compatibility goal is that a publisher can add metadata without breaking old -clients, while old clients do not accidentally treat a new required governance -or install meaning as safe. +The v1 publisher schema is strict. Conforming publishers MUST NOT add fields +that are not defined here. A client MAY retain unknown fields for forward +compatibility, but MUST NOT assign install, trust, approval, or policy meaning +to them. ## Feed Document -A v1 feed document is a JSON object with these top-level fields: - | Field | Type | Required | Semantics | | --- | --- | --- | --- | -| `schemaVersion` | integer | Yes | Must be `1` for this specification. | -| `id` | string | Yes | Stable feed identity, for example `clawhub-public`. | -| `generatedAt` | RFC 3339 string | Yes | Publisher generation timestamp. | -| `sequence` | integer | Yes | Monotonic publisher sequence for this feed id. | -| `expiresAt` | RFC 3339 string | Recommended | Expiry after which clients should report stale state. | -| `entries` | array | Yes | Feed entries. | -| `metadata` | object | Optional | Human-readable or publisher-specific metadata. | - -Feed JSON should be deterministic at generation time. Field order is not a -verification primitive in this core spec. - -Publishers should keep feed payloads bounded. OpenClaw clients should enforce -local limits for payload byte size, entry count, metadata object size, string -length, and nested object depth before accepting a feed. Exact limits are -implementation policy, but an unbounded feed must not block the gateway event -loop, exhaust memory, or make search and install paths perform request-time -network polling. - -Minimal feed: +| `schemaVersion` | integer | Yes | MUST be `1`. | +| `id` | string | Yes | Stable feed identity. | +| `generatedAt` | RFC 3339 string | Yes | Publication generation time. | +| `sequence` | non-negative safe integer | Yes | Monotonically increasing for this `id`. | +| `expiresAt` | RFC 3339 string | Yes | MUST be later than `generatedAt`. | +| `description` | string | No | Bounded human-readable feed description. | +| `entries` | array | Yes | Catalog entries. | + +Example: ```json { "schemaVersion": 1, - "id": "clawhub-public", + "id": "clawhub-official", "generatedAt": "2026-07-15T00:00:00.000Z", "sequence": 42, "expiresAt": "2026-07-22T00:00:00.000Z", + "description": "Official OpenClaw plugins published on ClawHub.", "entries": [] } ``` +Publishers MUST increment `sequence` when publishing a new accepted revision. +Deleting or demoting an entry MUST NOT make the sequence decrease. Republished +bytes at the same sequence MUST be identical. + ## Entry Object -Every entry must have stable identity, type, display metadata, selected version -or revision when available, state, publisher, and install candidates when the -entry is installable. +Every v1 entry has exactly these fields: | Field | Type | Required | Semantics | | --- | --- | --- | --- | -| `type` | string | Yes | `plugin` or `skill` for v1. Clients must ignore or reject unsupported types according to local policy. | -| `id` | string | Yes | Stable entry identity. Must not be a mutable display slug. | -| `title` | string | Yes | User-facing display title. | -| `version` | string | Type-specific | Selected package version when the artifact model has one. Skills may use `revision` instead. | -| `revision` | string | Optional | Immutable source revision for Git-backed or non-semver skills. | -| `state` | string | Yes | One of the v1 entry states below. | -| `publisher` | object | Yes | Publisher id and trust label. | -| `install` | object | Required for installable entries | Candidate artifact selectors. | -| `metadata` | object | Optional | Tags, summaries, icons, links, or publisher-specific metadata. | +| `type` | string | Yes | `plugin` or `skill`. | +| `id` | string | Yes | Stable entry identity. | +| `title` | string | Yes | User-facing title. | +| `version` | string | Yes | Exact package version or immutable source revision. | +| `state` | string | Yes | One of the states below. | +| `publisher` | object | Yes | Publisher identity and catalog trust label. | +| `install` | object | Yes | One or more artifact candidates. | + +The v1 entry schema does not define arbitrary entry `metadata`, `revision`, or +`description` fields. A future schema version may add bounded presentation or +provenance fields. ### Entry States | State | Meaning | | --- | --- | -| `available` | Entry can be shown and installed when source and policy checks pass. | -| `recommended` | Entry can be highlighted; it is still subject to source and policy checks. | -| `disabled` | Entry is known but intentionally unavailable from this feed. | -| `blocked` | Entry is explicitly denied by this feed. | -| `deprecated` | Entry may remain visible for migration but should not be selected for new installs. | +| `available` | May be offered when source and policy checks pass. | +| `recommended` | May be highlighted; no additional install authority. | +| `disabled` | Known but intentionally unavailable from this feed. | +| `blocked` | Explicitly denied by this feed. | +| `deprecated` | Retained for migration but not preferred for new installs. | -State precedence for final effective entry state should be: +When a publisher composes multiple inputs, the final state precedence SHOULD be: ```text blocked > disabled > deprecated > recommended > available ``` -The core spec defines only the final state emitted in the feed. How a publisher -or enterprise registry computes that state is outside this spec. +The feed communicates final state only. It does not standardize the policy or +review process that produced that state. ## Publisher Object -Publisher trust is intentionally narrow in v1. +The publisher object has exactly two fields: ```json { @@ -201,77 +117,68 @@ Publisher trust is intentionally narrow in v1. } ``` -`trust` may be: +`trust` MUST be `official` or `community`. It describes the publisher's catalog +classification. It does not assert package safety, organization approval, +artifact integrity, runtime permission, or account-follow state. -- `official` -- `unofficial` +## Install Candidates -Feeds must not invent broad "safe" labels that imply package security, -enterprise approval, or runtime permission. Those are separate policy layers. +`install.candidates` MUST contain at least one candidate. Every candidate has: -## Install Candidates +| Field | Type | Required | Semantics | +| --- | --- | --- | --- | +| `sourceRef` | string | Yes | Name of a locally configured source profile. | +| `package` | string | Yes | Package name or stable package coordinate. | +| `version` | string | Yes | Exact version or immutable revision. | +| `integrity` | string | Yes | Artifact or selected-content integrity value. | +| `github` | object | No | Immutable GitHub source details. | + +Candidates MUST NOT contain credentials, source base URLs, bearer tokens, SSH +material, or trust roots. -An installable entry has an `install.candidates` array. Each candidate names a -local source profile through `sourceRef`. +### Package Candidate ```json { - "install": { - "candidates": [ - { - "sourceRef": "public-npm", - "package": "@openclaw/acpx", - "version": "1.2.3", - "integrity": "sha512-..." - } - ] - } + "sourceRef": "public-clawhub", + "package": "@openclaw/acpx", + "version": "1.2.3", + "integrity": "sha256:0123456789abcdef" } ``` -Candidate fields vary by source type. +### GitHub Candidate -### npm Candidate +A GitHub candidate retains the common candidate fields and adds a strict nested +`github` object: -| Field | Required | Semantics | -| --- | --- | --- | -| `sourceRef` | Yes | Local source profile with `type: "npm"`. | -| `package` | Yes | npm package name. | -| `version` | Yes | Exact selected package version. | -| `integrity` | Recommended | npm integrity string or equivalent artifact integrity. | - -### ClawHub Candidate - -| Field | Required | Semantics | -| --- | --- | --- | -| `sourceRef` | Yes | Local source profile with `type: "clawhub"`. | -| `package` | Yes | ClawHub package id or package coordinate. | -| `version` | Yes | Exact selected version. | -| `integrity` | Recommended | Artifact integrity when available. | - -### Git Candidate - -| Field | Required | Semantics | -| --- | --- | --- | -| `sourceRef` | Yes | Local source profile with `type: "git"`. | -| `repo` | Yes | Repository path relative to the source profile. | -| `commit` | Yes | Full immutable commit hash. Branches and tags are not allowed. | -| `path` | Optional | Path inside the repository, such as `skills/review/SKILL.md`. | -| `sha256` | Recommended | Content hash for the selected path or package payload. | +```json +{ + "sourceRef": "public-github", + "package": "@openclaw/review-pr", + "version": "8d6b6f7f0d9d4d1e2e6d3d1234567890abcdef12", + "integrity": "sha256:0123456789abcdef", + "github": { + "repo": "openclaw/skills", + "path": "skills/review-pr", + "commit": "8d6b6f7f0d9d4d1e2e6d3d1234567890abcdef12", + "contentHash": "0123456789abcdef" + } +} +``` -A feed candidate must not include registry credentials, bearer tokens, SSH -private keys, npm auth tokens, source base URLs, or trust roots. Unknown -`sourceRef` values make the candidate invalid. If all candidates are invalid, -the entry is not installable. +`github.repo`, `github.path`, `github.commit`, and `github.contentHash` are all +required when `github` is present. `commit` MUST be immutable; branch names, +tags, and shortened commit hashes are invalid. -## Examples +## Complete Entry Examples -### Plugin From npm +Plugin: ```json { "type": "plugin", - "id": "acpx", + "id": "@openclaw/acpx", "title": "ACP-X", "version": "1.2.3", "state": "recommended", @@ -282,172 +189,132 @@ the entry is not installable. "install": { "candidates": [ { - "sourceRef": "public-npm", + "sourceRef": "public-clawhub", "package": "@openclaw/acpx", "version": "1.2.3", - "integrity": "sha512-..." + "integrity": "sha256:0123456789abcdef" } ] } } ``` -### Skill From Git +Community skill from GitHub: ```json { "type": "skill", - "id": "review-pr", + "id": "@example/review-pr", "title": "Review Pull Request", - "revision": "8d6b6f7f0d9d4d1e2e6d3d1234567890abcdef12", + "version": "8d6b6f7f0d9d4d1e2e6d3d1234567890abcdef12", "state": "available", "publisher": { - "id": "openclaw", - "trust": "official" + "id": "example", + "trust": "community" }, "install": { "candidates": [ { "sourceRef": "public-github", - "repo": "openclaw/skills", - "commit": "8d6b6f7f0d9d4d1e2e6d3d1234567890abcdef12", - "path": "skills/review-pr/SKILL.md", - "sha256": "..." + "package": "@example/review-pr", + "version": "8d6b6f7f0d9d4d1e2e6d3d1234567890abcdef12", + "integrity": "sha256:0123456789abcdef", + "github": { + "repo": "example/skills", + "path": "skills/review-pr", + "commit": "8d6b6f7f0d9d4d1e2e6d3d1234567890abcdef12", + "contentHash": "0123456789abcdef" + } } ] } } ``` -### Blocked Entry - -```json -{ - "type": "plugin", - "id": "risky-plugin", - "title": "Risky Plugin", - "version": "1.0.0", - "state": "blocked", - "publisher": { - "id": "example", - "trust": "unofficial" - }, - "metadata": { - "reason": "Blocked by feed policy" - } -} -``` - ## Source Profile Configuration -Source profiles are local configuration. A feed entry names `sourceRef`; the -client maps that name to the actual source endpoint and credentials. +Feed and source profiles are local OpenClaw configuration under +`marketplaces`, not data supplied by a feed: ```json { - "catalog": { + "marketplaces": { "feeds": { - "clawhub-public": { - "url": "https://clawhub.ai/v1/feeds/plugins", - "refresh": { - "onStartup": "if-stale", - "interval": "6h", - "jitter": "10m", - "timeout": "10s", - "maxStale": "7d" - } + "partner-catalog": { + "url": "https://packages.example.com/openclaw/feed.json" } }, "sources": { - "public-npm": { - "type": "npm", - "registry": "https://registry.npmjs.org/" + "partner-packages": { + "type": "npm" }, - "public-github": { - "type": "git", - "baseUrl": "https://github.com/" + "partner-github": { + "type": "git" } } } } ``` -Configuration errors should be precise and actionable. For example, if a -candidate references an unknown `sourceRef`, diagnostics should name the feed -id, entry id, and source ref without printing credentials, raw bearer tokens, -local private paths, or unbounded URLs. +Source profile `type` MUST be `npm`, `clawhub`, or `git`. Endpoint and +credential selection are resolver concerns and are not fields in the v1 feed +wire contract. Unknown `sourceRef` values make that candidate unusable. If an +entry has no usable candidate, the client MUST NOT offer it for installation. ## Refresh And Fallback -Clients should: - -1. Load the bundled fallback feed. -2. Start feed refresh after the gateway/runtime is ready. -3. Fetch remote feeds on the configured lifecycle schedule, not on every search - or install request. -4. Use conditional HTTP requests with `ETag` or `Last-Modified` when available. -5. Validate transport response limits, schema version, entries, source refs, and - candidate fields before accepting a new snapshot. -6. Compute a local `sha256` over the accepted payload bytes. -7. Store accepted snapshots atomically with sequence, expiry, payload checksum, - and HTTP validators. -8. Use the latest accepted snapshot during transient failures. -9. Report stale state when a snapshot exceeds `maxStale`. -10. Fall back to the bundled feed when no usable snapshot exists. - -Refresh runs on a lifecycle schedule. It must not happen synchronously inside -ordinary search, onboarding, or install rendering. Search and install should -consume the current accepted snapshot plus local source profile state. This -keeps feed governance from adding unpredictable latency to the gateway runtime -or user-facing marketplace views. - -## Client Rejection Rules - -Clients must reject or ignore unsafe feed content before it affects install or -search authority. At minimum, clients must reject: - -- unsupported `schemaVersion`; -- missing or malformed top-level feed identity; -- duplicate entry ids inside a feed; -- entries with unsupported `type` when local policy says fail closed; -- install candidates with unknown `sourceRef`; -- Git candidates that use branches, tags, or shortened commit hashes; -- feed documents that contain credentials, source base URLs, or trust roots in - entry candidates; -- malformed payloads or payloads that exceed local size and shape limits. - -Rejections should be diagnosable. The diagnostics record should identify the -feed profile, feed id when available, sequence when available, failure category, -and whether the client used hosted, cached, local, or bundled content. It must -not include secrets, raw credential-bearing URLs, prompt contents, tool -arguments, local private paths, or full package payload bytes. - -## Publisher Checklist - -A feed publisher is compatible with v1 core when it: - -- emits `schemaVersion: 1`; -- uses stable feed and entry ids; -- uses only supported entry states; -- keeps artifact credentials, source endpoints, and trust roots out of the feed; -- emits exact package versions, immutable Git commits, and integrity metadata - where available; -- publishes examples and negative test payloads for client compatibility. - -## Client Checklist - -An OpenClaw client is compatible with v1 core when it: - -- validates feed shape before accepting entries; -- keeps source profile credentials local; -- rejects unknown source refs and unsafe Git selectors; -- caches accepted snapshots atomically; -- preserves bundled fallback behavior; -- treats search, install, package integrity, and runtime policy as separate - decisions; -- emits bounded diagnostics for fetch, validation, fallback, and rejection - outcomes; -- preserves feed id, sequence or payload checksum, entry id, and source ref in +Clients SHOULD: + +1. Load a bundled fallback when one exists. +2. Refresh after runtime startup or through an explicit refresh command. +3. Use conditional HTTP requests with `ETag` or `Last-Modified`. +4. Bound URL, response size, read time, entry count, and string sizes. +5. Validate the complete feed before accepting a new snapshot. +6. Store accepted payload bytes and metadata atomically. +7. Reject signed rollback attempts with a lower sequence. +8. Use the last accepted snapshot during transient failures. +9. Report expired or stale content without silently granting new authority. + +Search and install rendering MUST consume accepted local state rather than +performing an unbounded request-time refresh. + +## Rejection And Diagnostics + +Clients MUST reject a feed before it affects install or search authority when: + +- `schemaVersion` is unsupported; +- feed identity, timestamps, sequence, entries, or candidates are malformed; +- duplicate entry identities create ambiguity; +- no candidate resolves to a configured `sourceRef`; +- an immutable selector or required integrity value is missing; +- configured transport, payload, or shape limits are exceeded; +- signed verification required by the profile fails. + +Diagnostics SHOULD identify the local profile, payload feed id when available, +sequence, failure category, and fallback source. They MUST NOT expose secrets, +credential-bearing URLs, local private paths, or package payload bytes. + +## Publisher Conformance + +A v1 publisher: + +- emits the strict schema above with stable feed and entry identities; +- increments sequence monotonically; +- emits exact versions and required integrity values; +- keeps source endpoints, credentials, and trust roots out of entries; +- publishes positive and negative compatibility fixtures; +- serves stored publication bytes rather than rebuilding a different document + for every read. + +## Client Conformance + +A v1 client: + +- validates shape before accepting entries; +- keeps source and trust configuration local; +- separates discovery, install resolution, artifact integrity, and runtime + policy; +- stores accepted snapshots atomically and preserves last-known-good fallback; +- preserves feed id, sequence, entry id, source ref, version, and integrity in install provenance; -- reports blocked, disabled, deprecated, stale, and configuration-error states - with actionable messages. +- provides bounded refresh, verification, rejection, and fallback diagnostics. diff --git a/rfcs/0009/signed-feed-trust-v1-spec.md b/rfcs/0009/signed-feed-trust-v1-spec.md index 8a214cf9..898c6665 100644 --- a/rfcs/0009/signed-feed-trust-v1-spec.md +++ b/rfcs/0009/signed-feed-trust-v1-spec.md @@ -1,67 +1,62 @@ # Signed Feed Trust v1 Addendum Specification -This document is the implementer-facing trust addendum for RFC 0009 hosted -feeds. It builds on `hosted-feed-v1-spec.md`, which defines the core feed -document, entry, source-reference, refresh, fallback, and validation contract. +This document defines signed transport and trust-anchor behavior for Hosted Feed +v1. It builds on `hosted-feed-v1-spec.md`. Status: draft addendum, tied to RFC 0009. +The key words MUST, MUST NOT, SHOULD, SHOULD NOT, and MAY are normative. + ## Scope This addendum defines: -- signed feed envelopes; -- payload media type binding; -- Ed25519 signature verification; -- configured and bundled publisher public-key trust; -- fail-closed signed feed behavior; -- key id and threshold rules; -- ClawHub platform feed-signing bootstrap; -- optional signed key-rotation documents; -- diagnostics requirements for feed verification failures. - -This addendum does not define: - -- package artifact signing or package malware scanning; -- runtime tool policy; -- account following or notifications; -- organization-admin approval workflows; -- a remote public-key bootstrap endpoint; -- reuse of OpenClaw release-signing identities as feed-signing identities. +- the signed feed envelope; +- exact DSSE pre-authentication encoding; +- Ed25519 verification and signature thresholds; +- local and bundled publisher public-key trust; +- expected feed-identity binding; +- fail-closed refresh, snapshot, and key-rotation behavior; +- bounded verification diagnostics. + +It does not define package signing, malware scanning, runtime policy, account +following, organization approval, a remote trust bootstrap endpoint, or reuse +of release-signing identities as feed-signing identities. ## Trust Model -A signed feed proves that the exact feed payload bytes were signed by a -publisher key already trusted by the client or deployment. It does not prove -that package code is safe, reviewed, organization-approved, or installable. +A valid signature proves that trusted key material signed the exact decoded feed +payload bytes for the declared payload type. It does not prove package safety, +organization approval, artifact integrity, or runtime permission. + +An initial trust anchor MUST arrive through one of these channels: -Trust anchors come from one of these channels: +- a public key bundled with OpenClaw for a built-in platform feed; +- operator-managed local configuration; +- another authenticated software or configuration distribution channel. -- a public key bundled with OpenClaw for the default ClawHub platform - feed-signing identity; -- operator-managed local configuration for private, third-party, or development - feeds; -- a signed key-rotation document that chains from an already trusted key. +A feed endpoint MUST NOT bootstrap its own trust by advertising a key from an +ordinary endpoint such as `/v1/feeds/public-key`. Compromise of that endpoint +would otherwise compromise both content and trust. -A feed host must not bootstrap its own initial trust by serving a key from an -ordinary endpoint such as `/public-key`. Such endpoints may be informational for -operators, but clients must not trust them unless the key is already bundled, -locally configured, or delivered through a signed rotation chain. +Publisher private keys MUST remain in publisher-owned secret storage. Feed keys +MUST be distinct from OpenClaw release, package, TLS, account, and platform +signing identities even when the same secret-management system operates them. ## Signed Envelope -Signed feeds are served as an envelope containing the exact UTF-8 JSON feed -payload bytes encoded as base64url and one or more signatures. +The v1 envelope has this exact shape: ```json { - "type": "openclaw.signed-envelope.v1", - "payloadType": "application/vnd.openclaw.catalog-feed+json;v=1", - "payload": "base64url(exact UTF-8 feed JSON bytes)", + "schemaVersion": 1, + "payloadType": "openclaw.official-external-plugin-catalog-feed.v1", + "payload": "eyJzY2hlbWFWZXJzaW9uIjoxLC4uLn0", "signatures": [ { - "keyid": "clawhub-feed-2026-q3", - "sig": "base64:..." + "keyId": "clawhub-feed-2026-q3", + "algorithm": "ed25519", + "signature": "base64url-signature" } ] } @@ -71,76 +66,67 @@ Envelope fields: | Field | Type | Required | Semantics | | --- | --- | --- | --- | -| `type` | string | Yes | Must be `openclaw.signed-envelope.v1`. | -| `payloadType` | string | Yes | Must identify the signed payload media type. | -| `payload` | string | Yes | Base64url encoded exact payload bytes. | -| `signatures` | array | Yes | One or more signatures over the envelope pre-authentication encoding. | +| `schemaVersion` | integer | Yes | MUST be `1`. | +| `payloadType` | string | Yes | MUST be `openclaw.official-external-plugin-catalog-feed.v1`. | +| `payload` | string | Yes | Base64 or unpadded base64url encoding of the exact UTF-8 feed bytes. | +| `signatures` | array | Yes | Between 1 and 16 signature records. | Signature fields: | Field | Type | Required | Semantics | | --- | --- | --- | --- | -| `keyid` | string | Yes | Publisher key id selected from trusted keys. | -| `sig` | string | Yes | Base64 encoded signature bytes. | +| `keyId` | string | Yes | Non-empty configured publisher key id. | +| `algorithm` | string | Yes | MUST be `ed25519`. | +| `signature` | string | Yes | Base64url Ed25519 signature over the DSSE PAE bytes. | -Clients must verify the envelope before decoding and accepting the payload as a -feed. Signing exact payload bytes avoids a second JSON canonicalization contract -for feed documents. +Duplicate `keyId` values make the envelope invalid. Unknown fields MUST NOT be +assigned trust meaning. -## Signature Algorithm +## DSSE Pre-Authentication Encoding -The initial signature algorithm is Ed25519. +Signatures MUST be calculated over the DSSE v1 pre-authentication encoding of +the decoded payload bytes, not over the base64 text in the envelope. -OpenClaw's verifier uses DSSE-style pre-authentication encoding for signed -payloads. Publishers should use the OpenClaw verifier, test vectors, or a -compatibility harness before treating a signature as compatible. +For UTF-8 `payloadType` bytes `T` and decoded payload bytes `P`, construct: -## Verification Rules - -Clients must: +```text +PAE("DSSEv1", T, P) = + "DSSEv1" || SP || decimal(byte_length(T)) || SP || T || + SP || decimal(byte_length(P)) || SP || P +``` -- reject unsupported envelope `type`; -- reject unsupported `payloadType`; -- reject malformed base64/base64url fields; -- reject empty `signatures`; -- bound the maximum signature count; -- reject duplicate `keyid` values in one envelope; -- resolve `keyid` only against locally trusted keys for the feed profile; -- require the configured threshold to be met by distinct trusted keys; -- reject signed feeds when only part of required key configuration is present; -- decode and validate the feed payload only after signature verification; -- compute and persist a local payload checksum after verification. +For the required payload type this is equivalent to: -`verification.mode: "signed"` fails closed. A failure to fetch keys, malformed -local key config, missing key id, invalid signature, unsupported envelope, or -unsupported payload type means the hosted feed must not become install or search -authority. +```text +"DSSEv1 " + utf8ByteLength(payloadType) + " " + payloadType + +" " + payloadByteLength + " " + payloadBytes +``` -This addendum is a verification layer, not a packaging requirement. Publishers -can still host ordinary core feeds, and clients can still use local or unsigned -development feeds through explicit local configuration. Signed verification -becomes a requirement only for profiles configured with `mode: "signed"` or for -default ClawHub-hosted feeds that OpenClaw treats as signed by platform policy. +Lengths are unsigned decimal ASCII without leading signs or padding. `SP` is +one ASCII space (`0x20`). The final component is the exact decoded payload byte +sequence and may contain arbitrary bytes. Publishers SHOULD validate against +OpenClaw's signed-envelope test vectors before production use. -## Source Profile Verification Configuration +## Local Configuration -A feed profile can require signed verification. +Custom signed feeds are configured under `marketplaces.feeds`: ```json { - "catalog": { + "marketplaces": { "feeds": { - "acme": { - "url": "https://packages.acme.example/openclaw/feed", + "partner-catalog": { + "url": "https://packages.example.com/openclaw/feed.json", + "feedId": "partner-official", "verification": { "mode": "signed", - "rootKeys": [ + "keys": [ { - "id": "acme-feed-2026-q3", - "publicKey": "base64:..." + "keyId": "partner-feed-2026-q3", + "publicKey": "base64url-raw-ed25519-key-or-pem" } ], - "rootThreshold": 1 + "threshold": 1 } } } @@ -148,117 +134,133 @@ A feed profile can require signed verification. } ``` -`rootKeys` are public keys. They are not secrets. Private signing keys must stay -outside OpenClaw client configuration. - -The default ClawHub platform feed should not require ordinary users to paste -`rootKeys`. OpenClaw can ship the ClawHub public feed-signing key as part of the -OpenClaw release. Explicit `rootKeys` remain useful for private ClawHub -deployments, third-party publishers, development, emergency override, and -operator-managed deployments. - -## ClawHub Platform Signing - -ClawHub-hosted feeds can use a ClawHub platform feed-signing key. The same -platform signing path can sign: - -- `clawhub-public`; -- ClawHub named feeds; -- ClawHub account feeds; -- ClawHub organization feeds; -- ClawHub-served composed feeds. - -OpenClaw verifies these feeds with the bundled ClawHub public key when the feed -identity in the signed payload matches a ClawHub-hosted feed profile. ClawHub -enforces ACLs before serving private or organization-scoped feeds; feed -verification proves payload authenticity, not viewer authorization. - -ClawHub private signing material should live in ClawHub secret storage or a -signing service. It must not be checked into feed documents, OpenClaw config, or -public source. - -The feed-signing key is a feed integrity key. It should not reuse OpenClaw -release-signing identities, Apple Developer ID certificates, notarization -credentials, npm tokens, GitHub tokens, or package-publishing credentials. -Keeping these identities separate limits blast radius when a feed-signing key or -package-publishing path needs emergency rotation. - -## Key Rotation - -If a publisher needs remote key rotation, it should publish a signed rotation -document verified by an already trusted key. A rotation document is separate -from an ordinary feed. - -```json -{ - "type": "openclaw.feed-key-rotation.v1", - "feedId": "clawhub-public", - "sequence": 3, - "expiresAt": "2026-09-01T00:00:00.000Z", - "threshold": 1, - "feedKeys": [ - { - "id": "clawhub-feed-2026-q4", - "publicKey": "base64:..." - } - ] -} -``` - -Clients that support rotation should persist accepted rotation sequence and -expiry metadata to prevent rollback and freeze attacks. Emergency root -replacement remains a local operator or OpenClaw release action. - -## Refresh And Snapshot Trust State - -When a signed hosted feed is accepted, clients should persist bounded trust -state with the cached snapshot: - -- feed profile name; -- feed id from the verified payload; -- payload checksum; -- feed sequence; -- expiry; -- verification mode; -- signed/unsigned outcome; -- signature count; -- threshold; -- accepted key ids or bounded key fingerprints; -- verification failure category when rejected. - -Diagnostics must not include private keys, raw bearer tokens, credential-bearing -URLs, full payload bytes, or unbounded identity values. - -## Unsigned Feeds - -Unsigned remote feeds require explicit local opt-in through -`verification.mode: "unsigned"`. Unsigned remote feeds should still require -HTTPS unless a local development profile explicitly allows loopback or local -file input. - -Clients must make unsigned state visible in diagnostics so operators can tell -that a feed is accepted without signature verification. - -## Publisher Checklist - -A signed-feed publisher is compatible with this addendum when it: - -- signs exact feed payload bytes through the v1 envelope; -- uses Ed25519 keys with stable key ids; -- keeps private signing material outside feed documents and client config; -- publishes test vectors for valid signatures and expected failures; -- documents who owns key rotation and emergency revocation; -- provides a rotation story before retiring old keys; -- avoids using ordinary `/public-key` endpoints as trust bootstrap. +`feedId` is the expected signed payload identity. Signed profiles MUST bind the +decoded payload's `id` to this configured or built-in expected value. The local +profile name and payload feed id may differ; for example the built-in profile +`clawhub-public` expects payload id `clawhub-official`. + +`keys` MUST contain distinct key ids and distinct normalized Ed25519 public key +material. `threshold` defaults to 1, MUST be positive, and MUST NOT exceed the +number of configured distinct keys. Private signing keys MUST NOT appear in +OpenClaw configuration. + +## Verification + +Before accepting a signed refresh, a client MUST: -## Client Checklist +1. Parse and validate the bounded envelope. +2. Require the supported `schemaVersion` and exact `payloadType`. +3. Decode `payload` and each candidate signature. +4. Construct the exact DSSE PAE bytes above. +5. Resolve signature key ids only against the selected profile's trusted keys. +6. Count only valid signatures from distinct trusted public key material. +7. Require the configured threshold. +8. Parse the decoded payload as Hosted Feed v1. +9. Require payload `id` to equal the profile's expected `feedId`. +10. Apply schema, source-profile, expiry, and monotonic sequence checks. -An OpenClaw client is compatible with this addendum when it: +Failure at any step MUST reject the new payload. A client MUST NOT retry the +same bytes as unsigned content or silently replace the configured signed profile +with an unsigned feed. + +## Snapshots And Rollback Protection + +An accepted signed snapshot SHOULD persist: + +- exact envelope bytes; +- exact decoded payload bytes or their checksum; +- payload feed id and sequence; +- accepted signature key ids, count, and threshold; +- verification time; +- HTTP validators and fetch metadata. + +Clients MUST reverify a stored signed snapshot before using it. A newly fetched +valid feed with a lower sequence than the previously accepted feed id MUST be +rejected as rollback. A verification-policy change MUST NOT make it impossible +to compare a newly valid feed against metadata from the previously accepted +snapshot; the old snapshot remains the rollback baseline even when it no longer +satisfies the new key set. + +The old snapshot may be served as fallback only if it satisfies the currently +configured verification policy. Otherwise the client MUST fail closed with a +clear diagnostic. + +## Bundled ClawHub Trust + +The official ClawHub feed uses a dedicated ClawHub feed-signing identity: + +- ClawHub stores and uses the private Ed25519 key; +- OpenClaw bundles the matching public key and stable key id; +- the built-in `clawhub-public` profile expects feed id `clawhub-official`; +- normal users do not configure the official key manually; +- an environment override MAY exist for development or staged rollout, but + MUST fail closed when only part of the key pair configuration is present. + +Bundling a public key is source-controlled trust distribution. It is not secret +storage and it does not put the private key in the OpenClaw release. + +## Rotation And Revocation + +Signed Feed v1 does not define an in-band key-rotation document. Rotation uses +the same authenticated channel that established the trust anchor. + +A normal rotation SHOULD proceed as follows: + +1. Generate a new dedicated feed-signing key. +2. Add the new public key to bundled or operator-managed trust configuration. +3. Publish envelopes signed by enough old trusted keys to satisfy the current + threshold, optionally also signed by the new key. +4. Distribute the updated trust configuration. +5. After the update window, sign with the new active set and remove retired keys + in a later configuration or release update. + +Merely including a signature from one old key is insufficient when the current +threshold is greater than one. Emergency revocation may intentionally make the +last snapshot unusable; clients must fail closed and identify the revoked or +missing trust state without revealing key material beyond public key ids. + +Future in-band rotation requires a separately versioned payload type, exact wire +schema, replay rules, and threshold transition semantics. Implementations MUST +NOT invent incompatible rotation documents under this v1 payload type. + +## Diagnostics + +Clients SHOULD distinguish: + +- malformed envelope; +- unsupported payload type; +- malformed feed payload; +- expected feed-id mismatch; +- unknown key id; +- invalid signature; +- duplicate key id or duplicate public key material; +- threshold not met; +- expired feed; +- rollback sequence; +- snapshot rejected under the current policy. + +Diagnostics MAY expose feed profile, expected and actual feed ids, sequence, +public key ids, threshold, verification time, and fallback source. They MUST NOT +expose private keys, bearer tokens, credential-bearing URLs, or payload bytes. + +## Publisher Conformance + +A signed v1 publisher: + +- signs the exact stored feed payload bytes using the DSSE PAE above; +- uses the required envelope and payload type; +- keeps sequence monotonic across deletion and demotion; +- keeps private keys in publisher-owned secret storage; +- publishes stable public key ids through an authenticated trust channel; +- provides positive, tampered-payload, wrong-key, and rollback test vectors. + +## Client Conformance + +A signed v1 client: -- verifies signed envelopes before decoding feed payloads; -- fails closed for configured signed feeds; -- rejects malformed, duplicate, or unbounded signature inputs; -- verifies thresholds against distinct trusted keys; -- supports bundled ClawHub public-key trust for default ClawHub feeds; -- keeps direct public-key config as an operator override path; -- records bounded verification diagnostics with accepted snapshots. +- obtains initial trust independently of the feed endpoint; +- binds signed payload identity to the selected profile; +- enforces distinct-key thresholds and fail-closed behavior; +- stores and reverifies last-known-good signed snapshots; +- preserves rollback metadata across verification-policy changes; +- reports bounded, actionable verification diagnostics. From 92846d1f21d327da7b73fa09e7db876495f1f81f Mon Sep 17 00:00:00 2001 From: Gio Della-Libera Date: Wed, 15 Jul 2026 17:43:51 -0700 Subject: [PATCH 02/11] docs(rfc-0009): clarify shared ClawHub feed signing --- rfcs/0009/clawhub-account-feeds-v1-spec.md | 10 +++++++--- rfcs/0009/signed-feed-trust-v1-spec.md | 8 ++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/rfcs/0009/clawhub-account-feeds-v1-spec.md b/rfcs/0009/clawhub-account-feeds-v1-spec.md index fdfee8f0..f23c78d8 100644 --- a/rfcs/0009/clawhub-account-feeds-v1-spec.md +++ b/rfcs/0009/clawhub-account-feeds-v1-spec.md @@ -192,9 +192,13 @@ type `openclaw.official-external-plugin-catalog-feed.v1`. It MUST NOT be reused for account-feed bytes. Signed account feeds require a separately specified payload type, expected -identity binding, test vectors, and client implementation. Until that lands, -HTTPS transport and ClawHub origin identify the API source, but account-feed -content still grants discovery only and never install authority. +identity binding, test vectors, and client implementation. That future payload +type MAY use the same dedicated ClawHub platform feed-signing key and bundled +public trust anchor as other ClawHub-operated feeds; it MUST remain distinct +from the catalog payload type and bind the stable account or publisher +`feedId`. Until that lands, HTTPS transport and ClawHub origin identify the API +source, but account-feed content still grants discovery only and never install +authority. ## Downstream Registry Consumption diff --git a/rfcs/0009/signed-feed-trust-v1-spec.md b/rfcs/0009/signed-feed-trust-v1-spec.md index 898c6665..24fbe316 100644 --- a/rfcs/0009/signed-feed-trust-v1-spec.md +++ b/rfcs/0009/signed-feed-trust-v1-spec.md @@ -199,6 +199,14 @@ The official ClawHub feed uses a dedicated ClawHub feed-signing identity: Bundling a public key is source-controlled trust distribution. It is not secret storage and it does not put the private key in the OpenClaw release. +The same dedicated ClawHub feed-signing identity MAY sign multiple +ClawHub-operated feed classes, including public catalog, account, named, +organization, and composed feeds. Each feed class MUST define its own versioned +payload type, expected identity binding, schema, and verifier before clients +accept it. Sharing the platform feed-signing key does not permit one payload +type or feed identity to be replayed as another, and it does not grant access, +approval, or install authority. + ## Rotation And Revocation Signed Feed v1 does not define an in-band key-rotation document. Rotation uses From 50fa2a66965e3e4e4b5e87093c9110716f200d54 Mon Sep 17 00:00:00 2001 From: Gio Della-Libera Date: Thu, 16 Jul 2026 10:38:34 -0700 Subject: [PATCH 03/11] docs(rfc-0009): define scalable feed distribution --- ...009-hosted-feeds-for-plugins-and-skills.md | 157 ++++---- rfcs/0009/clawhub-account-feeds-v1-spec.md | 339 ++++++++-------- rfcs/0009/hosted-feed-distribution-v1-spec.md | 379 ++++++++++++++++++ rfcs/0009/hosted-feed-v1-spec.md | 21 +- rfcs/0009/signed-feed-trust-v1-spec.md | 19 +- 5 files changed, 671 insertions(+), 244 deletions(-) create mode 100644 rfcs/0009/hosted-feed-distribution-v1-spec.md diff --git a/rfcs/0009-hosted-feeds-for-plugins-and-skills.md b/rfcs/0009-hosted-feeds-for-plugins-and-skills.md index af54b4f3..1f438c3a 100644 --- a/rfcs/0009-hosted-feeds-for-plugins-and-skills.md +++ b/rfcs/0009-hosted-feeds-for-plugins-and-skills.md @@ -30,7 +30,7 @@ uses. External plugins can continue to install from npm or ClawHub, with Git available for immutable source installs. The feed provides package selection, version, and checksum data; local configuration supplies the source endpoint and credentials. ClawHub can publish the default public feed and other -ClawHub-hosted public, named, account, organization, or composed feeds. Those +ClawHub-hosted public, named, publisher, organization, or composed feeds. Those ClawHub-hosted feeds should be signed by the ClawHub platform feed-signing key, with the matching public key bundled in OpenClaw so ordinary ClawHub feed use is zero-config. Organizations can publish effective feeds by subsetting, filtering, @@ -140,7 +140,11 @@ The implementer-facing v1 core contract is captured in [`0009/hosted-feed-v1-spec.md`](0009/hosted-feed-v1-spec.md). Trust and account feed addenda are captured separately in [`0009/signed-feed-trust-v1-spec.md`](0009/signed-feed-trust-v1-spec.md) and -[`0009/clawhub-account-feeds-v1-spec.md`](0009/clawhub-account-feeds-v1-spec.md). +[`0009/clawhub-account-feeds-v1-spec.md`](0009/clawhub-account-feeds-v1-spec.md) +(publisher feeds; the historical filename is retained while the addendum is +under review). Scalable snapshots, signed queries, and changed-since retrieval +are defined in +[`0009/hosted-feed-distribution-v1-spec.md`](0009/hosted-feed-distribution-v1-spec.md). This RFC remains the design rationale and rollout plan; the sidecar specs are the concise schema, example, verification, refresh, and conformance references for feed publishers and OpenClaw clients. @@ -149,6 +153,13 @@ for feed publishers and OpenClaw clients. A feed document should be a deterministic JSON document with a schema version, feed id, generated timestamp, monotonic sequence number, expiry, and entries. +The complete atomic document is the simplest representation, not a permanent +assumption that catalogs remain small. Main catalogs and high-volume publisher +feeds may exceed response limits. Those feeds use a signed root manifest over +immutable content-addressed shards for complete synchronization, and signed +revision-bound query or change projections for server-side search and +incremental refresh. Changed-since responses include tombstones so removals and +blocks cannot survive indefinitely in client state. Entry ids must be stable registry identities, not mutable display slugs. If a registry allows user-editable slugs, the feed should either carry an immutable package id or use the canonical package coordinate as the stable id. Display @@ -223,11 +234,12 @@ installer behavior already trusted by that deployment. The default ClawHub feed profile is special only in its bootstrap trust. OpenClaw can ship a bundled ClawHub platform public key and use it to verify any ClawHub-hosted feed URL whose identity is inside the signed payload. That covers -`clawhub-public`, ClawHub named feeds, account feeds, organization feeds, and +`clawhub-public`, ClawHub named feeds, publisher feeds, organization feeds, and ClawHub-served composed feeds without requiring users to paste keys for each -feed. The feed document still carries its `feedId`, owner or organization scope, -visibility, sequence, expiry, and entries; ClawHub still enforces ACLs before it -serves private account or organization feeds. Non-ClawHub feed URLs do not +feed. The feed document still carries its `feedId`, publisher, organization, or +collection scope, visibility, sequence, expiry, and entries; ClawHub still +enforces ACLs before it serves private named, organization, or composed feeds. +Non-ClawHub feed URLs do not inherit this trust and must either use an explicitly configured trust root or an explicit unsigned opt-in. @@ -471,50 +483,48 @@ hosted feed payload according to local configuration. Later PRs can consume that verified state when they wire trusted feed entries into install eligibility, search filtering, regional variants, or tenant-composed effective feeds. -### ClawHub account feeds and following +### ClawHub publisher feeds and following -ClawHub can also publish account-backed feeds as a discovery feature. A ClawHub -account feed is produced by ClawHub for a stable ClawHub account or publisher id, -not by an arbitrary third-party endpoint. If OpenClaw verifies the feed as -ClawHub-authored, the client can trust that the feed reflects ClawHub's view of -that account's published plugins or skills and account metadata. +ClawHub can also publish publisher-backed feeds as a discovery feature. +Publishers are ClawHub's generic public identity for both people and +organizations. Internal user accounts and memberships remain authentication and +ownership details; they are not parallel public feed identities. A publisher +feed uses a stable publisher id rather than a mutable handle or display name. -This is different from treating every account feed entry as official or safe to +This is different from treating every publisher feed entry as official or safe to install. The trust layers stay separate: - Feed source trust means OpenClaw verified that ClawHub produced the feed. - Publisher identity means the feed entry is attributed to a stable ClawHub - account or publisher id, not a mutable display name. -- Official status means the publisher or package has passed ClawHub's official - account or package process. + publisher id, not a mutable display name. - Registry inclusion means a downstream registry, such as a Microsoft registry, has accepted the publisher or package under its own rules. - Install eligibility still depends on package-source artifact verification, ClawHub/OpenClaw release trust, and any downstream security scans or policy checks required by the consuming deployment. -Following an account should therefore be a discovery and notification signal. A -user can follow a ClawHub account to see that account's new skills or plugins, -filter search to followed accounts, or receive update notifications. Following -must not by itself make a package official, bypass security scans, bypass -tenant-admin approval, or allow a feed to introduce new source profiles or -credentials. - -For Microsoft or another enterprise registry, the expected flow is: a publisher -creates or claims a ClawHub account, ClawHub publishes account-backed feed state, -the enterprise registry selects the relevant subset, runs its own scans and -approval checks, and then publishes an approved effective feed for its clients. - -The account-feed work should move on two tracks: - -1. ClawHub product track: define the account or publisher feed model, ownership - and claim flow, follow graph, notifications, profile surfaces, and search - filters such as "people I follow" or "new from followed publishers". This - track owns the user experience for following publishers and discovering new - plugins or skills. +Following a publisher is therefore a social-discovery signal. ClawHub should +show new work from followed publishers in a pull-based activity timeline rather +than send one notification for every publication. OpenClaw or Control UI may +generate notifications for followed-publisher updates that affect content +installed locally, because ClawHub does not know an instance's lockfile. +Following must not make a package official, bypass security scans or approval, +or allow a feed to introduce new source profiles or credentials. + +For an enterprise registry, the expected flow is: a publisher publishes through +ClawHub, ClawHub exposes publisher-feed discovery state, the registry selects +the relevant subset, runs its own scans and approval checks, and publishes an +approved effective feed for its clients. + +The publisher-feed work should move on two tracks: + +1. ClawHub product track: define the publisher feed model, public follow graph, + timeline and profile surfaces, and discovery filters such as "people I + follow" or "new from followed publishers". This track owns the user + experience for following publishers and discovering new plugins or skills. 2. OpenClaw trust and runtime track: verify ClawHub-authored feed envelopes, record source-profile trust state, expose bounded CLI and diagnostics - visibility, and then consume verified account-feed state for discovery. This + visibility, and then consume verified publisher-feed state for discovery. This track owns what the client can safely display, cache, refresh, and use for search or notification surfaces. @@ -523,34 +533,28 @@ status, Microsoft registry inclusion, tenant approval, security-scan results, and package artifact verification remain separate gates that later PRs must wire explicitly. -Implementation update: the ClawHub account-feed discovery work in this RFC is +Implementation update: the ClawHub publisher-feed discovery work in this RFC is now backed by the current ClawHub PR stack. The code-backed slices are -`#2948` through `#2959`: account-feed model/API (`#2948`), claim and -official-state facts (`#2949`), follow graph API (`#2950`), profile and -discovery surfaces (`#2951`), registry and scan bridge (`#2953`), follow -controls and followed-publisher discovery (`#2957`), follow notification -delivery (`#2958`), and public feed/profile routes (`#2959`). Those PRs keep -the same boundary described above: following and discovery do not imply -official status, registry inclusion, install eligibility, or security-scan -bypass. +`#2948`, `#2950`, `#2957`, `#2958`, and `#2959`: publisher feed model/API, +public follow graph, follow controls and discovery, a publisher activity +timeline, and machine-readable feed discovery. Those PRs keep the same boundary +described above: following and discovery do not imply official status, registry +inclusion, install eligibility, or security-scan bypass. The likely PR stacks are: ClawHub product stack: -1. Account-feed model and API: define stable account or publisher ids, account - feed URLs, feed ownership metadata, and whether a feed represents a person, - organization, or curated publisher collection. -2. Account claim and official-state flow: let publishers create or claim a - ClawHub account, record verification state, and keep official account or - package status separate from feed publication. -3. Follow graph and notifications: add follow and unfollow state, notification - events for new or updated skills and plugins, and user preferences for those - notifications. +1. Publisher-feed model and API: define stable publisher ids, publisher feed + URLs, deterministic continuation, and coherent feed revisions. +2. Public follow graph: add follow and unfollow state plus public follower and + following lists; keep private mute or notification preferences separate. +3. Activity timeline: show new or updated public skills and plugins from + followed publishers without generating one alert per publication. 4. Profile and discovery surfaces: expose publisher profile pages, followed publisher lists, and search filters such as "people I follow" or "new from followed publishers". -5. Registry and scan bridge: expose the subset of ClawHub account and feed state +5. Registry and scan bridge: expose the subset of ClawHub publisher and feed state that downstream registries can consume, while preserving their own scans, approval workflows, and registry-inclusion decisions. @@ -561,13 +565,12 @@ collapsing the trust gates: controls on publisher surfaces, followed-publisher lists, and search filters such as "people I follow" or "new from followed publishers", with clear reasons for why entries appear. -2. Follow notification delivery: emit notification events when followed - publishers publish or update skills and plugins, with user preferences, - mute or unsubscribe controls, replay/backfill rules, rate limits, and audit - records. -3. Public feed and profile pages: expose account and publisher feed pages backed - by the account-feed API, including empty, restricted, official, review, and - scan states that reflect only recorded facts. +2. Publisher activity timeline: expose a bounded timeline of new or updated + public skills and plugins from followed publishers. Do not create ClawHub + alerts for every publication. +3. Machine-readable feed discovery: expose canonical publisher feed URLs through + APIs. A visible profile-page feed control is optional; OpenClaw should offer + a handle-based command such as `openclaw publisher follow `. 4. Registry submission workflow: let eligible ClawHub publisher entries be idempotently queued, retried, withdrawn, or resubmitted for downstream registry review and scanning while keeping submission separate from approval. @@ -580,29 +583,29 @@ OpenClaw trust and runtime stack: 1. Feed envelope and verifier primitives: parse ClawHub-authored feed envelopes, verify configured trust anchors, fail closed on invalid signatures, and keep unsigned or failed-verification bodies inert. -2. Source-profile trust config: define which ClawHub feed profiles and account +2. Source-profile trust config: define which ClawHub feed profiles and publisher feed endpoints are locally trusted, including directly configured publisher public keys and explicit unsigned opt-in behavior for self-hosted feeds. 3. Refresh and snapshot trust state: persist verification result, sequence, freshness, and fallback state with the cached feed snapshot without granting install or search authority by persistence alone. -4. CLI and operator visibility: show feed trust, source profile, account-feed, +4. CLI and operator visibility: show feed trust, source profile, publisher-feed, stale, fallback, and verification-failure states in refresh, entries, and diagnostics using bounded fields. -5. Discovery consumption: add followed-account and publisher-specific discovery - filters only after verified account-feed state exists, while keeping install +5. Discovery consumption: add followed-publisher discovery + filters only after verified publisher-feed state exists, while keeping install eligibility and package-source artifact verification on their existing gates. After both tracks land, a joint ecosystem phase can build on the verified -account-feed foundation: +publisher-feed foundation: -1. Install and policy integration: let verified feed, account, and publisher +1. Install and policy integration: let verified feed and publisher state participate in install eligibility, tenant policy, allow lists, block lists, and admin-approved effective feeds. 2. Security and scanning maturity: carry ClawHub/OpenClaw scan results, vulnerability signals, malware checks, provenance attestations, and review - status without treating account follows as scan bypasses. -3. Ranking and recommendations: use followed accounts, official status, scan + status without treating publisher follows as scan bypasses. +3. Ranking and recommendations: use followed publishers, official status, scan state, popularity, recency, and enterprise approval to shape search ranking and recommendations. 4. Enterprise composition: let Microsoft/MOS3 or another registry compose @@ -611,7 +614,7 @@ account-feed foundation: 5. Regional and mirror strategy: support regional ClawHub mirrors, partner mirrors, enterprise mirrors, key rotation, and failover rules. 6. Publishing workflow: define the path from local skill or plugin authoring to - ClawHub account-feed publication, scanning, signing, follower notification, + ClawHub publisher-feed publication, scanning, signing, timeline discovery, and discovery. 7. Admin and audit surfaces: add audit logs, install provenance, visibility explanations, drift reports, stale-feed reports, and tenant admin controls. @@ -898,7 +901,7 @@ activated. of platform release-signing identities. 5. Publish the first ClawHub-hosted feed for the current external plugin catalog. The same ClawHub platform signing path should sign `clawhub-public`, named - feeds, account feeds, organization feeds, and ClawHub-served composed feeds. + feeds, publisher feeds, organization feeds, and ClawHub-served composed feeds. The first public feed may include all current external entries; ClawHub can narrow future default feeds to official packages as the official catalog grows. @@ -909,9 +912,9 @@ activated. 9. Extend the same discovery contract to skills after the skill installer accepts catalog candidates, including GitHub-indexed skills that do not have ClawHub-hosted artifacts. -10. Add ClawHub account-backed feeds and following as a discovery layer for +10. Add ClawHub publisher-backed feeds and following as a discovery layer for publisher-specific skill and plugin updates. These feeds should be - ClawHub-authored and verified, but following an account should not imply + ClawHub-authored and verified, but following a publisher should not imply official status, registry inclusion, install eligibility, or security-scan bypass. 11. Add composition guidance and examples for Microsoft/MOS3 and other @@ -933,9 +936,9 @@ activated. "feed" for the protocol and propagation artifact? - Which runtime policy metadata should feed entries be allowed to reference without turning the feed into a policy engine? -- Which ClawHub account-feed states should OpenClaw expose for following, - notifications, and search filters without turning followed accounts into - install authority? +- Which ClawHub publisher-feed states should OpenClaw expose for following, + timelines, local installed-content notifications, and search filters without + turning followed publishers into install authority? - How many stable OpenClaw releases should ship hosted feed fallback before Scout, Microsoft, and other clients depend on the contract? - Should a later LTS release define stronger compatibility guarantees for feed diff --git a/rfcs/0009/clawhub-account-feeds-v1-spec.md b/rfcs/0009/clawhub-account-feeds-v1-spec.md index f23c78d8..6d06c4e8 100644 --- a/rfcs/0009/clawhub-account-feeds-v1-spec.md +++ b/rfcs/0009/clawhub-account-feeds-v1-spec.md @@ -1,7 +1,8 @@ -# ClawHub Account Feeds v1 Addendum Specification +# ClawHub Publisher Feeds v1 Addendum Specification -This document defines the ClawHub account and publisher discovery-feed contract -associated with RFC 0009. +This document defines the ClawHub publisher discovery-feed contract associated +with RFC 0009. The historical filename is retained to avoid breaking links +while the addendum is under review. Status: draft addendum, tied to RFC 0009. @@ -11,35 +12,59 @@ The key words MUST, MUST NOT, SHOULD, SHOULD NOT, and MAY are normative. This addendum defines: -- stable account and publisher feed identity; -- the strict account-feed document and entry shapes; +- stable publisher feed identity; +- the strict publisher-feed document and entry shapes; - bounded pagination and refresh semantics; +- public publisher-follow discovery; - the boundary between following, discovery, trust, and installation; - downstream registry provenance and diagnostics. -It does not define an installable catalog entry, package candidate, signed -account-feed payload type, notification transport, organization approval, -artifact trust, or security-scanning implementation. +It does not define a separate account feed, installable catalog entries, +package candidates, a signed atomic publisher-feed document payload type, +notification transport, organization approval, artifact trust, or security +scanning. -## Relationship To Hosted Catalog Feeds +Publisher feeds are the first rollout target for the reusable query and change +contracts in `hosted-feed-distribution-v1-spec.md`. A publisher with hundreds of +skills or plugins must remain discoverable without requiring one unbounded +response. + +Signed publisher query pages use payload type +`openclaw.clawhub-publisher-feed-query-results.v1`. Signed publisher change +pages use `openclaw.clawhub-publisher-feed-changes.v1`. These payload types MUST +NOT be accepted as install catalogs. A sharded complete publisher-feed root uses +`openclaw.clawhub-publisher-feed-shard-root.v1`. + +## Publisher Identity + +ClawHub publishers are the public identity for both people and organizations. +Internal user or membership records are authentication and ownership details; +they are not separate public feed identities. + +A publisher feed MUST use the stable publisher id, not a mutable handle, +display name, profile URL, linked user id, or organization membership id. -An account feed is a ClawHub discovery projection. It is not the Hosted Feed v1 -install catalog defined by `hosted-feed-v1-spec.md`. +```text +clawhub.publisher. +``` + +Changing a handle, display name, members, or owners MUST NOT change the feed id. + +## Relationship To Hosted Catalog Feeds -Account-feed entries identify public skills and plugins associated with an -account or publisher. A client that wants to install one of those entries MUST -resolve it through an accepted install catalog or another explicit package -resolver and MUST still apply source, integrity, scan, approval, and runtime -policy checks. +A publisher feed is a ClawHub discovery projection. It is not the Hosted Feed +v1 install catalog defined by `hosted-feed-v1-spec.md`. -This distinction allows users to follow people and organizations without -turning social discovery into install authority. +Publisher-feed entries identify public skills and plugins associated with a +publisher. To install one, a client MUST resolve it through an accepted install +catalog or another explicit package resolver and MUST still apply source, +integrity, scan, approval, and runtime policy checks. ## Wire Contract -The normative v1 schema is the generated `AccountFeedSchema` in the ClawHub -`clawhub-schema` package. The schema is strict. Conforming publishers MUST NOT -add unknown fields to a v1 document or entry. +The normative v1 schema is the generated `PublisherFeedSchema` in the ClawHub +schema package. The schema is strict. Conforming publishers MUST NOT add unknown +fields to a v1 document or entry. ## Feed Document @@ -47,12 +72,10 @@ add unknown fields to a v1 document or entry. { "schemaVersion": 1, "feedId": "clawhub.publisher.publishers:01JEXAMPLE", - "scope": "publisher", - "accountId": "users:01JEXAMPLE", "publisherId": "publishers:01JEXAMPLE", "handle": "openclaw", "displayName": "OpenClaw", - "generatedAt": "2026-07-15T00:00:00.000Z", + "generatedAt": "2026-07-16T00:00:00.000Z", "sequence": 17, "entries": [], "nextCursor": null @@ -62,194 +85,190 @@ add unknown fields to a v1 document or entry. | Field | Type | Required | Semantics | | --- | --- | --- | --- | | `schemaVersion` | integer | Yes | MUST be `1`. | -| `feedId` | string | Yes | Stable feed identity. | -| `scope` | string | Yes | `account` or `publisher`. | -| `accountId` | string or null | Yes | Stable account id when applicable. | -| `publisherId` | string or null | Yes | Stable publisher id when applicable. | -| `handle` | string or null | Yes | Current mutable public handle. | -| `displayName` | string | Yes | Current public display name. | -| `generatedAt` | RFC 3339 string | Yes | Projection generation time. | +| `feedId` | string | Yes | `clawhub.publisher.`, 1 to 256 UTF-8 bytes. | +| `publisherId` | string | Yes | Stable ClawHub publisher id, 1 to 200 UTF-8 bytes. | +| `handle` | string or null | Yes | Current mutable public handle, 1 to 64 UTF-8 bytes when present. | +| `displayName` | string | Yes | Current public display name, 1 to 256 UTF-8 bytes. | +| `generatedAt` | RFC 3339 string | Yes | Generation time for this logical revision. | | `sequence` | non-negative safe integer | Yes | Monotonic revision for this `feedId`. | -| `entries` | array | Yes | Ordered public discovery entries. | -| `nextCursor` | string or null | Yes | Opaque continuation cursor. | +| `entries` | array | Yes | At most 200 ordered public discovery entries. | +| `nextCursor` | string or null | Yes | Opaque continuation cursor, at most 4096 UTF-8 bytes. | -An account-scoped feed MUST have a non-null `accountId`. A publisher-scoped -feed MUST have a non-null `publisherId`. The other identity may be present when -ClawHub has a public relationship between them. - -Feed ids use stable opaque ids, not mutable handles: - -```text -clawhub.account. -clawhub.publisher. -``` +Every page for one coherent revision SHOULD report the same `feedId`, +`generatedAt`, and `sequence`. A publisher MUST NOT derive `sequence` from the +newest visible entry timestamp because deletion or demotion could make it +decrease. -Changing a handle or display name MUST NOT change `feedId`. +Each new accepted publisher-feed revision MUST increment `sequence` by exactly +one. Re-serving the same revision MUST preserve the same sequence and logical +content. ## Entry Object -Every account-feed entry has exactly these fields: +Every publisher-feed entry has exactly these fields: | Field | Type | Required | Semantics | | --- | --- | --- | --- | | `kind` | string | Yes | `skill` or `plugin`. | -| `id` | string | Yes | Stable ClawHub object or package identity. | -| `name` | string | Yes | Current package name or slug. | -| `displayName` | string | Yes | User-facing title. | -| `summary` | string or null | Yes | Bounded public summary. | -| `url` | string | Yes | Absolute HTTPS URL or origin-relative URL-reference for the canonical public ClawHub page. | -| `updatedAt` | number | Yes | Finite, non-negative Unix epoch time in milliseconds used for ordering. | +| `id` | string | Yes | Stable ClawHub object or package identity, 1 to 256 UTF-8 bytes. | +| `name` | string | Yes | Current package name or slug, 1 to 256 UTF-8 bytes. | +| `displayName` | string | Yes | User-facing title, 1 to 256 UTF-8 bytes. | +| `summary` | string or null | Yes | Public summary, at most 1024 UTF-8 bytes. | +| `url` | string | Yes | Canonical public ClawHub page URL or safe origin-relative reference, at most 2048 UTF-8 bytes. | +| `updatedAt` | number | Yes | Finite, non-negative Unix epoch time in milliseconds. | -Example: +An entry intentionally has no install candidate, artifact URL, integrity claim, +official status, approval state, or scan result. Those facts have separate +owners. -```json -{ - "kind": "plugin", - "id": "packages:01JEXAMPLE", - "name": "@openclaw/acpx", - "displayName": "ACP-X", - "summary": "ACP integration for OpenClaw.", - "url": "https://clawhub.ai/plugins/@openclaw/acpx", - "updatedAt": 1784073600000 -} -``` +Clients MUST resolve an origin-relative `url` against the origin of the +publisher-feed request. Publishers MUST reject backslashes, control characters, +protocol-relative references, and any reference that resolves away from that +origin. -An entry intentionally has no install candidate, artifact URL, integrity claim, -trust label, approval state, or scan result. Those facts have separate owners. +Publishers MUST order entries by descending `updatedAt` and MUST use stable +kind and object-identity tie-breakers when timestamps are equal. -Clients MUST resolve an origin-relative `url` against the origin of the account -feed request, not against an unrelated configured catalog origin. Publishers -MUST order entries by descending `updatedAt` and MUST use a stable identity -tie-breaker when timestamps are equal. +## Pagination And Revision -## Pagination And Sequence +`nextCursor` is opaque and MUST NOT be constructed or interpreted by clients. A +client follows it until it is null or a local page or entry bound is reached. -Account feeds are bounded projections. `nextCursor` is opaque and MUST NOT be -constructed or interpreted by clients. A client follows `nextCursor` until it -is null or until a local page/entry bound is reached. +ClawHub MUST preserve deterministic ordering across page boundaries and MUST +NOT report `nextCursor: null` when a bounded scan stopped before all eligible +entries were considered. Cursor errors SHOULD return a bounded client error +rather than silently restarting at the first page. -The sequence identifies the logical feed revision, not an individual page. -Every page for one coherent revision SHOULD report the same `feedId`, -`generatedAt`, and `sequence`. A publisher MUST NOT derive sequence from the -newest visible entry timestamp because deletion or demotion could make it -decrease. +ClawHub MUST bound requested and effective page size, indexed scan work, +response bytes, and cursor lifetime. A page contains at most 200 entries and is +at most 1 MiB; a cursor is at most 4096 UTF-8 bytes and expires within 15 +minutes. Cursor expiry MUST return a bounded client error rather than restart. + +If ClawHub cannot yet provide coherent monotonic revisions and deterministic +continuation, it MUST label the route experimental rather than publish it as a +stable v1 feed contract. + +### Search And Changed-Since Retrieval + +ClawHub SHOULD expose signed publisher-feed query projections for bounded text +and kind filters. Query cursors MUST bind the stable publisher id, feed +revision, normalized query, and authorization context. + +ClawHub SHOULD also expose signed publisher-feed change projections from a +caller's accepted sequence. The change stream MUST include complete upserts and +tombstones for content that is deleted, blocked, made private, or transferred +away from the publisher. All pages MUST be pinned to one `toSequence`, and a +client MUST apply them atomically. -Cursor errors SHOULD return a bounded client error rather than silently -restarting at the first page. ClawHub MUST bound page size, scan work, response -size, and cursor lifetime. +If the requested publisher-feed revision is older than retained change history, +ClawHub MUST require a full publisher-feed refresh. It MUST NOT silently return +only the retained tail. These projections remain discovery data; installation +still resolves entries through an accepted install catalog. ## Public API -The initial ClawHub routes are: +The initial publisher routes are: ```text -GET /api/v1/accounts/{accountId} -GET /api/v1/accounts/{accountId}/feed GET /api/v1/publishers/{publisherId} GET /api/v1/publishers/{publisherId}/feed ``` -Detail routes expose public identity and profile facts. Feed routes expose the -strict discovery projection above. APIs SHOULD distinguish missing, non-public, -restricted, suspended, and temporarily unavailable identities without exposing -private moderation evidence. +ClawHub does not expose parallel `/accounts` feed routes. Publisher detail may +include public profile facts and a canonical feed URL, but MUST NOT expose +private owner, member, linked-user, moderation, or authentication records. -## Following Semantics +APIs SHOULD distinguish missing, non-public, restricted, suspended, and +temporarily unavailable publishers without exposing private moderation +evidence. -Following is a reversible user preference. It MAY: +## Following And Timeline -- include the identity in a followed-publisher list; +Following a publisher is a reversible discovery preference. It MAY: + +- include the publisher in public follower and following lists; - include matching entries in discovery filters; -- produce bounded update notifications; +- populate a pull-based activity timeline; - explain why an entry appeared. -Following MUST NOT mean that: - -- the publisher is official, reviewed, or trusted; -- an entry passed security scanning; -- an entry is locally or organization approved; -- an entry is installable; -- source, integrity, or runtime policy checks may be bypassed. +Follower and following lists are public social-discovery data. Notification and +mute preferences remain private. Follow and unfollow operations SHOULD be +authenticated, idempotent, rate-limited, and based on stable publisher ids. -UI and APIs MUST use follow/following language rather than trust or approval -language. +ClawHub SHOULD prefer a timeline over per-publication notification fanout. A +high-volume publisher can publish hundreds of entries, and following that +publisher does not imply that every publish deserves an alert. -## Privacy And Abuse Controls +OpenClaw or Control UI MAY generate local notifications for followed-publisher +updates that affect content actually installed by that OpenClaw instance. That +filter depends on local install state and is not owned by ClawHub. -Follower lists are private by default. Publishing an account feed MUST NOT -reveal who follows it. +Following MUST NOT mean that a publisher is official, reviewed, trusted, +scanned, approved, or installable. UI and APIs MUST use follow/following +language rather than trust or approval language. -Follow and unfollow operations SHOULD be authenticated, reversible, idempotent, -rate-limited, and separately mutable from notification preferences. Systems -SHOULD suppress self-follow notifications and make blocked, deleted, private, -or suspended identities explicit rather than representing them as successful -empty feeds. +## Client Consumption -## Trust And Signing Boundary +A client MAY expose a command such as: -The signed-feed trust v1 addendum currently defines only the catalog payload -type `openclaw.official-external-plugin-catalog-feed.v1`. It MUST NOT be reused -for account-feed bytes. +```text +openclaw publisher follow +``` -Signed account feeds require a separately specified payload type, expected -identity binding, test vectors, and client implementation. That future payload -type MAY use the same dedicated ClawHub platform feed-signing key and bundled -public trust anchor as other ClawHub-operated feeds; it MUST remain distinct -from the catalog payload type and bind the stable account or publisher -`feedId`. Until that lands, HTTPS transport and ClawHub origin identify the API -source, but account-feed content still grants discovery only and never install -authority. +The handle is a lookup convenience only. The client MUST resolve it through +ClawHub and persist or transmit the stable publisher id. Feed URLs remain +machine-readable API discovery, not a required profile-page control. -## Downstream Registry Consumption +## Trust And Signing Boundary -A downstream registry MAY consume account feeds as discovery input, then -resolve selected entries through an install catalog and apply its own review, -scan, allow-list, block-list, and organization policy. +The atomic publisher-feed document does not yet have a signed payload type and +MUST NOT reuse `openclaw.official-external-plugin-catalog-feed.v1`. Its complete +sharded representation, plus signed query and change projections, use the +distinct payload types defined above and the strict distribution-addendum +schemas. Each requires expected publisher-feed identity binding, test vectors, +and explicit client verifier registration. -Derived records SHOULD preserve: +All publisher-feed representations MAY use the same dedicated ClawHub platform +feed-signing key and bundled public trust anchor as other ClawHub-operated +feeds, but each payload type remains distinct from install-catalog authority. -- account `feedId`, sequence, and cursor or page checksum; -- account and publisher ids when present; -- entry kind and stable id; -- the separately resolved catalog feed id, sequence, package version, source - reference, and integrity value; -- downstream policy state and decision time. +Until the corresponding producer and verifier for a representation land, HTTPS +transport and ClawHub origin identify the API source, but publisher-feed content +still grants discovery only and never install authority. -Following state MUST NOT be translated into approval. +## Downstream Registry Consumption -## Diagnostics +A downstream registry MAY consume publisher feeds as discovery input, resolve +selected entries through an install catalog, and apply its own review, scan, +allow-list, block-list, and organization policy. -ClawHub and clients SHOULD distinguish: +Derived records SHOULD preserve the publisher feed id and revision, publisher +id, entry kind and stable id, separately resolved catalog provenance, and the +downstream decision. Following state MUST NOT be translated into approval. -- malformed account feed; -- missing, private, restricted, suspended, or deleted identity; -- invalid or expired cursor; -- bounded-scan or page-limit exhaustion; -- follow and unfollow outcomes; -- discovery results included because of a follow; -- catalog resolution failure for a discovered entry. +## Diagnostics -Diagnostics MUST NOT expose tokens, private profile data, moderation evidence, -unbounded URLs, package payload bytes, or private follower identities. +ClawHub and clients SHOULD distinguish malformed feeds, non-public publishers, +invalid cursors, bounded-scan exhaustion, follow outcomes, timeline query +limits, and catalog resolution failures. -## ClawHub Conformance +Diagnostics MUST NOT expose tokens, private profile or membership data, +moderation evidence, package payload bytes, or private notification settings. -A v1 account-feed publisher: +## Conformance -- emits the strict schema above using stable opaque identity; -- keeps mutable presentation values separate from feed identity; -- paginates deterministically with monotonic revision semantics; -- exposes public discovery facts only; -- keeps following separate from official, scan, approval, and install state; -- distinguishes unavailable identity states from successful empty feeds. +A v1 publisher-feed producer: -## Client Conformance +- uses stable publisher identity only; +- exposes only public publisher and entry facts; +- emits deterministic bounded pages and monotonic revisions; +- keeps following separate from trust and install authority; +- does not expose internal account, ownership, or moderation state. -A v1 client: +A client: -- treats account entries as discovery references only; -- preserves account and publisher provenance; -- uses opaque bounded pagination; -- resolves installation through a separate accepted catalog path; -- displays follow state separately from trust, scan, review, and approval; -- provides bounded diagnostics for feed, follow, and catalog-resolution errors. +- treats the feed as discovery rather than install authority; +- resolves entries through an accepted catalog before installation; +- stores stable publisher ids rather than mutable handles; +- bounds pagination and reports unavailable or malformed state; +- keeps local installed-content notifications separate from ClawHub following. diff --git a/rfcs/0009/hosted-feed-distribution-v1-spec.md b/rfcs/0009/hosted-feed-distribution-v1-spec.md new file mode 100644 index 00000000..d1827807 --- /dev/null +++ b/rfcs/0009/hosted-feed-distribution-v1-spec.md @@ -0,0 +1,379 @@ +# Hosted Feed Distribution and Query v1 Addendum Specification + +This document defines scalable snapshot, query, and incremental-refresh +transport for Hosted Feed v1. It builds on `hosted-feed-v1-spec.md` and the +signed transport in `signed-feed-trust-v1-spec.md`. + +Status: draft addendum, tied to RFC 0009. + +The key words MUST, MUST NOT, SHOULD, SHOULD NOT, and MAY are normative. + +## Scope + +This addendum defines: + +- signed root manifests and immutable catalog shards; +- signed, revision-bound query result pages; +- signed incremental change pages, including tombstones; +- cursor, consistency, retention-gap, and atomic-application rules. + +It does not define ranking algorithms, access-control policy, package +installation, or artifact trust. + +## Distribution Modes + +A feed publisher MAY expose any combination of these representations: + +1. **Atomic snapshot:** the complete Hosted Feed v1 document when it fits + publisher and client limits. +2. **Sharded snapshot:** a signed root manifest referencing immutable, + digest-addressed shards for complete synchronization. +3. **Query projection:** signed pages containing the ordered subset matching a + bounded query at one feed revision. +4. **Change projection:** signed pages describing every change after a known + sequence through one fixed target sequence. + +Publishers MUST NOT silently truncate any representation. Query and change +projections do not prove that a client possesses the complete feed. A client +that requires complete offline state MUST accept an atomic or sharded snapshot. + +## Payload-Type Binding + +This addendum defines reusable representation shapes, not one universal signed +payload type. Each concrete feed contract MUST assign a distinct versioned +payload type to each representation it accepts. For example, ClawHub publisher +feeds use `openclaw.clawhub-publisher-feed-query-results.v1` and +`openclaw.clawhub-publisher-feed-changes.v1`. Its sharded root uses +`openclaw.clawhub-publisher-feed-shard-root.v1`. + +The official external plugin install catalog uses these exact scalable +representation types: + +- `openclaw.official-external-plugin-catalog-shard-root.v1`; +- `openclaw.official-external-plugin-catalog-query-results.v1`; +- `openclaw.official-external-plugin-catalog-changes.v1`. + +Other install catalogs, including a future skills catalog, MUST assign their +own types. A verifier MUST reject a valid signature whose payload type does not +match the selected feed class and representation. + +This prevents a signed discovery projection from being replayed as an install +catalog, or one feed operator's response from being interpreted under another +schema. + +All v1 documents and nested objects in this addendum are strict and MUST reject +unknown fields. `feedId` is 1 to 256 UTF-8 bytes. Cursor strings are at most +4096 UTF-8 bytes. URLs are at most 2048 UTF-8 bytes. Timestamp strings are at +most 64 UTF-8 bytes and MUST be valid RFC 3339 instants. + +## Sharded Snapshot Root + +```json +{ + "schemaVersion": 1, + "feedId": "clawhub-official", + "sequence": 42, + "generatedAt": "2026-07-16T00:00:00.000Z", + "expiresAt": "2026-07-23T00:00:00.000Z", + "metadata": { + "description": "Official OpenClaw plugins published on ClawHub." + }, + "entryCount": 2450, + "shards": [ + { + "index": 0, + "url": "https://clawhub.ai/v1/feeds/plugins/shards/sha256-abc.json", + "sha256": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", + "byteLength": 524288, + "entryCount": 500 + } + ] +} +``` + +The root fields are strict: + +| Field | Type | Semantics | +| --- | --- | --- | +| `schemaVersion` | integer | MUST be `1`. | +| `feedId` | string | Stable feed identity. | +| `sequence` | non-negative safe integer | Complete snapshot revision. | +| `generatedAt` | RFC 3339 string | Root publication time. | +| `expiresAt` | RFC 3339 string | Root and shard-set expiry. | +| `metadata` | object | Complete feed-level metadata defined by the concrete payload type. | +| `entryCount` | non-negative safe integer | Exact entries across all shards. | +| `shards` | array | Ordered immutable shard descriptors. Empty only when `entryCount` is zero. | + +Each shard descriptor has exactly these fields: + +| Field | Type | Semantics | +| --- | --- | --- | +| `index` | non-negative safe integer | Position in the root's shard array. | +| `url` | string | Immutable HTTPS shard URL. | +| `sha256` | string | Exact lowercase hexadecimal digest described below. | +| `byteLength` | positive safe integer | Exact response-byte length, at most 1 MiB. | +| `entryCount` | positive safe integer | Exact shard entry count, at most 10,000. | + +When `entryCount` is zero, `shards` MUST be empty. Otherwise `shards` MUST be +non-empty and indexes MUST be contiguous from zero. URLs MUST use HTTPS and +satisfy the selected feed profile's origin policy. `sha256` is exactly 64 +lowercase hexadecimal characters encoding the 32-byte SHA-256 digest of the +decoded representation-data bytes after HTTP content decoding. `byteLength` +is the length of those same decoded bytes, independent of gzip, Brotli, or +another transfer/content encoding. `entryCount` is exact. Publishers MUST serve +the same decoded representation bytes for a shard URL regardless of negotiated +HTTP encoding. + +A shard document has exactly these fields: + +| Field | Type | Semantics | +| --- | --- | --- | +| `schemaVersion` | integer | MUST be `1`. | +| `feedId` | string | MUST match the signed root. | +| `sequence` | non-negative safe integer | MUST match the signed root. | +| `index` | non-negative safe integer | MUST match the descriptor. | +| `entries` | array | Entries validated by the concrete feed contract. | + +Entries across the shard set MUST have unique identities and deterministic +global ordering. A root contains at most 1024 shards and 1,000,000 entries and +is at most 1 MiB. The aggregate shard set is at most 256 MiB. A concrete feed +contract MAY impose lower limits but MUST NOT raise these v1 maxima. + +The official external plugin catalog root metadata has exactly `description`, +which is a string of at most 1024 UTF-8 bytes or null. Publisher-feed root +metadata has exactly `publisherId`, `handle`, and `displayName`, with the types +and bounds in the publisher-feed addendum. Other root payload types MUST define +their complete strict metadata object before use. + +Publishers MUST write every immutable shard before publishing the signed root. +They MUST NOT mutate bytes at a published shard URL. Clients MUST verify the +signed root, enforce aggregate byte, shard, and entry limits, fetch every shard, +verify lengths and digests, validate all entries, and atomically replace the +prior root and shard set only after the entire snapshot succeeds. + +## Signed Query Projection + +```json +{ + "schemaVersion": 1, + "feedId": "clawhub.publisher.publishers:01JEXAMPLE", + "sequence": 17, + "generatedAt": "2026-07-16T00:00:00.000Z", + "expiresAt": "2026-07-16T00:05:00.000Z", + "query": { + "text": "cuda", + "kinds": ["plugin", "skill"] + }, + "requestCursor": null, + "pageIndex": 0, + "startIndex": 0, + "resultCount": 0, + "entries": [], + "nextCursor": null +} +``` + +The query object is a strict normalized representation of the request. A +concrete feed contract defines its allowed filters. ClawHub publisher-feed v1 +allows `text` and `kinds`; the install catalog may additionally allow entry +states and publisher ids. Arrays MUST be sorted and deduplicated. Omitted +filters are absent from the normalized query object. Publisher-defined ranking is +allowed, but ordering MUST be deterministic for a given query and sequence. + +Text normalization is exact: decode valid UTF-8, normalize to Unicode NFC, +trim leading and trailing ASCII whitespace characters (`U+0009` through +`U+000D` and `U+0020`), and replace each remaining run of those characters with +one `U+0020`. Case is preserved; matching and ranking may be case-insensitive +but do not change the signed query value. Enum values use the lowercase +spellings in their defining schema. Arrays are deduplicated and sorted by +lexicographic UTF-8 byte order. An omitted filter is absent from the query +object. Empty text and empty arrays are invalid and MUST NOT be treated as +omitted. Clients compare the parsed normalized query structurally, not by JSON +object member order. + +Every page MUST repeat the same normalized query, feed id, sequence, and expiry. +It MUST include `requestCursor`, exactly matching the cursor sent for that +request or null for the first page, plus a zero-based `pageIndex`. The opaque +`nextCursor` MUST be integrity-protected and bound to those values plus any +access-control context needed to prevent replay across principals. A client +MUST verify `requestCursor` and the expected page index before accepting a page. +A cursor MUST NOT continue against a newer feed revision. Publishers SHOULD +return a bounded conflict or reset response when the pinned revision is gone. + +A valid signature authenticates the returned projection and the publisher's +claim that it matches the bound query at that revision. It does not make an +entry installable or prove that the client has synchronized the complete feed. + +A query page has exactly these fields: + +| Field | Type | Semantics | +| --- | --- | --- | +| `schemaVersion` | integer | MUST be `1`. | +| `feedId` | string | Bound feed identity. | +| `sequence` | non-negative safe integer | Pinned feed revision. | +| `generatedAt` | RFC 3339 string | Page generation time. | +| `expiresAt` | RFC 3339 string | MUST be later than `generatedAt`. | +| `query` | object | Strict normalized filters defined by the concrete payload type. | +| `requestCursor` | string or null | Exact request cursor; null on page zero. | +| `pageIndex` | non-negative safe integer | Zero-based page position. | +| `startIndex` | non-negative safe integer | Global result offset of the first entry. | +| `resultCount` | non-negative safe integer | Exact matching entries across all pages. | +| `entries` | array | At most 200 entries using the concrete feed entry schema. | +| `nextCursor` | string or null | Integrity-protected continuation. | + +A query page is at most 1 MiB. For publisher-feed query payloads, `query` has +only optional `text` and `kinds` fields. `text` is 1 to 256 UTF-8 bytes after +normalization. `kinds` is a sorted, deduplicated, non-empty subset of `plugin` +and `skill`. At least one query field MUST be present. The official install +catalog query has only optional `text`, `types`, `states`, and `publisherIds`. +`text` has the same bound; `types` is a non-empty subset of `plugin` and +`skill`; `states` is a non-empty subset of the Hosted Feed v1 entry states; and +`publisherIds` is a sorted, deduplicated array of 1 to 100 publisher ids, each +1 to 256 UTF-8 bytes. At least one query field MUST be present. + +The first query page MUST have `startIndex: 0`. Every continuation page's +`startIndex` MUST equal the prior page's `startIndex + entries.length`. +`resultCount` MUST be identical on every page, and a terminal page MUST end +exactly at `resultCount`. Clients MUST reject a gap, overlap, or premature +terminal page. + +## Signed Change Projection + +```json +{ + "schemaVersion": 1, + "feedId": "clawhub.publisher.publishers:01JEXAMPLE", + "fromSequence": 14, + "toSequence": 16, + "generatedAt": "2026-07-16T00:00:00.000Z", + "expiresAt": "2026-07-16T00:05:00.000Z", + "requestCursor": null, + "pageIndex": 0, + "startIndex": 0, + "changeCount": 2, + "changes": [ + { + "sequence": 15, + "operation": "upsert", + "entry": { + "kind": "skill", + "id": "skills:01JEXAMPLE", + "name": "cuda-helper", + "displayName": "CUDA Helper", + "summary": "Tools for CUDA development.", + "url": "/example/cuda-helper", + "updatedAt": 1784160000000 + } + }, + { + "sequence": 16, + "operation": "remove", + "entryId": "skills:01JRETIRED", + "entryKind": "skill" + } + ], + "nextCursor": null +} +``` + +`fromSequence` is the client's accepted revision and is exclusive. +`toSequence` is the fixed target revision and is inclusive. Every continuation +page MUST remain pinned to this range and carry the exact request cursor and +expected zero-based page index as defined for query projections. Changes MUST +be ordered by sequence and a stable within-sequence order. An `upsert` carries +the complete current entry. A `remove` tombstone carries the stable entry id and +type. + +A concrete change payload MAY also carry a metadata record with exactly +`sequence`, `operation: "metadata"`, and `metadata`. The metadata object is the +same complete strict object used by that feed's sharded root. A publisher-feed +metadata record therefore replaces `publisherId`, `handle`, and `displayName` +together; an install-catalog metadata record replaces `description`. + +Publishers MUST emit tombstones when an entry is deleted, made private, or +otherwise leaves the requesting principal's effective feed. In a discovery +feed, a blocked entry that is no longer publicly discoverable uses a tombstone. +In an install catalog, `blocked` is an explicit deny with composition precedence +and MUST be emitted as a complete upsert carrying that state; it MUST NOT be +converted to a removal. A changed-items endpoint that omits required removals +or explicit deny-state upserts is not conformant. + +A change page has exactly these fields: + +| Field | Type | Semantics | +| --- | --- | --- | +| `schemaVersion` | integer | MUST be `1`. | +| `feedId` | string | Bound feed identity. | +| `fromSequence` | non-negative safe integer | Exclusive accepted revision. | +| `toSequence` | non-negative safe integer | Inclusive pinned target; not below `fromSequence`. | +| `generatedAt` | RFC 3339 string | Page generation time. | +| `expiresAt` | RFC 3339 string | MUST be later than `generatedAt`. | +| `requestCursor` | string or null | Exact request cursor; null on page zero. | +| `pageIndex` | non-negative safe integer | Zero-based page position. | +| `startIndex` | non-negative safe integer | Global change-record offset of the first record. | +| `changeCount` | non-negative safe integer | Exact records across all pages. | +| `changes` | array | At most 500 ordered change records. | +| `nextCursor` | string or null | Integrity-protected continuation. | + +An upsert record has exactly `sequence`, `operation: "upsert"`, and `entry`. +The entry uses the concrete feed entry schema. A remove record has exactly +`sequence`, `operation: "remove"`, `entryId`, and the concrete schema's entry +discriminator: `entryKind` for publisher feeds or `entryType` for install +catalogs. Sequence values are +non-negative safe integers in `(fromSequence, toSequence]`; entry ids are 1 to +256 UTF-8 bytes. A change page is at most 1 MiB. + +Every feed revision in `(fromSequence, toSequence]` MUST have at least one +change record, including a metadata record for a metadata-only revision. The +first page MUST have `startIndex: 0`. Every continuation page's `startIndex` +MUST equal the prior page's `startIndex + changes.length`; `changeCount` MUST be +identical on every page; and a terminal page MUST end exactly at `changeCount`. +These offsets, page indexes, cursor chaining, and per-revision records make +gaps and overlaps detectable. + +Clients MUST verify and collect every page through `nextCursor: null`, reject a +missing revision, offset gap, overlap, identity mismatch, or range change, then +apply the full change set atomically. They MUST NOT advance their accepted +sequence after only part of a paginated change set. + +Publishers MAY retain a bounded change history. If `fromSequence` predates that +history, the endpoint MUST return an explicit reset-required response and the +current snapshot/root location or revision. It MUST NOT return a partial delta +as if it were complete. Clients then perform a full atomic or sharded refresh. + +The reset response is signed with the same concrete change payload type and is +a strict alternative to a change page. It contains exactly `schemaVersion: 1`, +`feedId`, `fromSequence`, `currentSequence`, `generatedAt`, `expiresAt`, +`resetRequired: true`, and `snapshotUrl`. The sequence values are non-negative +safe integers, `currentSequence` is greater than `fromSequence`, and +`snapshotUrl` is an HTTPS URL for the feed's atomic snapshot or signed shard +root. It MUST satisfy the selected feed profile's origin policy before any +request is made. Clients MUST NOT forward credentials across origins and MUST +verify this response before changing accepted state. + +## Access Control + +For private publisher, named, organization, or composed feeds, the publisher +MUST authorize the request before selecting or signing entries. Cursors MUST be +bound to the authorization context without exposing user identifiers or ACLs. +A signed response authenticates what the feed operator served; it does not +grant access to another principal or bypass downstream policy. + +## Conformance + +A conforming publisher: + +- never silently truncates snapshots, query results, or change history; +- pins every paginated operation to one immutable revision or range; +- emits removal tombstones and explicit reset-required responses; +- publishes immutable shards before the signed root; +- signs each root, query page, and change page with the concrete feed contract's + distinct payload type. + +A conforming client: + +- enforces per-response and aggregate bounds; +- binds query and change continuations to the initial request and revision; +- verifies all shard digests and all signed projection pages; +- applies complete snapshots and complete change ranges atomically; +- falls back to a full snapshot after an explicit retention gap. diff --git a/rfcs/0009/hosted-feed-v1-spec.md b/rfcs/0009/hosted-feed-v1-spec.md index 9489a806..1ac21cae 100644 --- a/rfcs/0009/hosted-feed-v1-spec.md +++ b/rfcs/0009/hosted-feed-v1-spec.md @@ -18,10 +18,15 @@ This specification defines: - entry state, refresh, validation, and fallback behavior; - publisher and client conformance requirements. -It does not define signed envelopes, account following, account-feed discovery, +It does not define signed envelopes, publisher following, publisher-feed discovery, organization approval, artifact signing, malware scanning, or runtime policy. Those are separate contracts. +Large-feed sharding, signed server-side queries, and incremental change +projections are defined in `hosted-feed-distribution-v1-spec.md`. A publisher +MUST NOT truncate this atomic document to remain within a local limit; it must +fail publication or expose the scalable representation. + ## Wire Contract The normative v1 publisher schema is the generated `CatalogFeedSchema` in the @@ -65,10 +70,16 @@ Example: } ``` -Publishers MUST increment `sequence` when publishing a new accepted revision. +Publishers MUST increment `sequence` by exactly one when publishing a new +accepted revision. Deleting or demoting an entry MUST NOT make the sequence decrease. Republished bytes at the same sequence MUST be identical. +The atomic document is intentionally not cursor-paginated. Pagination without a +pinned revision can combine different catalog states and cannot be accepted as +one signed snapshot. Large complete feeds use a signed root plus immutable +shards; interactive search and changed-since retrieval use signed projections. + ## Entry Object Every v1 entry has exactly these fields: @@ -275,6 +286,12 @@ Clients SHOULD: 8. Use the last accepted snapshot during transient failures. 9. Report expired or stale content without silently granting new authority. +If a complete atomic feed exceeds configured limits, clients SHOULD discover a +sharded root representation when the selected profile supports it. Clients MAY +use signed query projections for interactive discovery and signed change +projections for incremental refresh, but MUST NOT mistake either for possession +of a complete snapshot. + Search and install rendering MUST consume accepted local state rather than performing an unbounded request-time refresh. diff --git a/rfcs/0009/signed-feed-trust-v1-spec.md b/rfcs/0009/signed-feed-trust-v1-spec.md index 24fbe316..bf7f8738 100644 --- a/rfcs/0009/signed-feed-trust-v1-spec.md +++ b/rfcs/0009/signed-feed-trust-v1-spec.md @@ -67,7 +67,7 @@ Envelope fields: | Field | Type | Required | Semantics | | --- | --- | --- | --- | | `schemaVersion` | integer | Yes | MUST be `1`. | -| `payloadType` | string | Yes | MUST be `openclaw.official-external-plugin-catalog-feed.v1`. | +| `payloadType` | string | Yes | MUST equal the exact type selected by the concrete feed contract and representation. The baseline catalog type is `openclaw.official-external-plugin-catalog-feed.v1`. | | `payload` | string | Yes | Base64 or unpadded base64url encoding of the exact UTF-8 feed bytes. | | `signatures` | array | Yes | Between 1 and 16 signature records. | @@ -95,7 +95,7 @@ PAE("DSSEv1", T, P) = SP || decimal(byte_length(P)) || SP || P ``` -For the required payload type this is equivalent to: +For a selected payload type this is equivalent to: ```text "DSSEv1 " + utf8ByteLength(payloadType) + " " + payloadType + @@ -149,20 +149,29 @@ OpenClaw configuration. Before accepting a signed refresh, a client MUST: 1. Parse and validate the bounded envelope. -2. Require the supported `schemaVersion` and exact `payloadType`. +2. Require the supported `schemaVersion` and the exact `payloadType` selected by + the concrete feed contract and requested representation. 3. Decode `payload` and each candidate signature. 4. Construct the exact DSSE PAE bytes above. 5. Resolve signature key ids only against the selected profile's trusted keys. 6. Count only valid signatures from distinct trusted public key material. 7. Require the configured threshold. -8. Parse the decoded payload as Hosted Feed v1. -9. Require payload `id` to equal the profile's expected `feedId`. +8. Dispatch only to the schema validator registered for that exact payload + type. The baseline catalog type parses as Hosted Feed v1; distribution + addendum types parse as their strict root, query, or change schema. +9. Require the payload's `id` or `feedId`, as defined by that schema, to equal + the selected profile or endpoint's expected feed identity. 10. Apply schema, source-profile, expiry, and monotonic sequence checks. Failure at any step MUST reject the new payload. A client MUST NOT retry the same bytes as unsigned content or silently replace the configured signed profile with an unsigned feed. +Supporting the envelope does not make every payload type acceptable. A client +MUST maintain an explicit allowlist from endpoint or operation to exact payload +type and validator. It MUST NOT select a validator from untrusted payload +contents or treat an unknown but validly signed type as a catalog. + ## Snapshots And Rollback Protection An accepted signed snapshot SHOULD persist: From 39b0295105044a9523423735a8062ea9985a56e4 Mon Sep 17 00:00:00 2001 From: Gio Della-Libera Date: Thu, 16 Jul 2026 14:18:51 -0700 Subject: [PATCH 04/11] docs(rfc-0009): define signed publisher snapshots --- rfcs/0009/clawhub-account-feeds-v1-spec.md | 38 ++++++++++++++----- rfcs/0009/hosted-feed-distribution-v1-spec.md | 7 ++-- 2 files changed, 32 insertions(+), 13 deletions(-) diff --git a/rfcs/0009/clawhub-account-feeds-v1-spec.md b/rfcs/0009/clawhub-account-feeds-v1-spec.md index 6d06c4e8..75ba4c21 100644 --- a/rfcs/0009/clawhub-account-feeds-v1-spec.md +++ b/rfcs/0009/clawhub-account-feeds-v1-spec.md @@ -20,16 +20,17 @@ This addendum defines: - downstream registry provenance and diagnostics. It does not define a separate account feed, installable catalog entries, -package candidates, a signed atomic publisher-feed document payload type, -notification transport, organization approval, artifact trust, or security -scanning. +package candidates, notification transport, organization approval, artifact +trust, or security scanning. Publisher feeds are the first rollout target for the reusable query and change contracts in `hosted-feed-distribution-v1-spec.md`. A publisher with hundreds of skills or plugins must remain discoverable without requiring one unbounded response. -Signed publisher query pages use payload type +Signed complete publisher snapshots use payload type +`openclaw.clawhub-publisher-feed-snapshot.v1`. Signed publisher query pages use +payload type `openclaw.clawhub-publisher-feed-query-results.v1`. Signed publisher change pages use `openclaw.clawhub-publisher-feed-changes.v1`. These payload types MUST NOT be accepted as install catalogs. A sharded complete publisher-feed root uses @@ -165,6 +166,21 @@ ClawHub MUST require a full publisher-feed refresh. It MUST NOT silently return only the retained tail. These projections remain discovery data; installation still resolves entries through an accepted install catalog. +### Signed Complete Snapshot + +`GET /api/v1/publishers/{publisherId}/feed/snapshot` returns a DSSE envelope +whose payload type is `openclaw.clawhub-publisher-feed-snapshot.v1`. Its decoded +payload is a strict complete publisher feed with exactly `schemaVersion`, +`feedId`, `publisherId`, `handle`, `displayName`, `generatedAt`, `expiresAt`, +`sequence`, and `entries`. It has no cursor and MUST NOT be truncated. + +The payload repeats the identity and entry bounds above, contains at most 400 +entries, and is at most 1 MiB. `expiresAt` is an RFC 3339 instant later than +`generatedAt`. A client MUST verify the envelope, expected payload type, +publisher-derived feed id, expiry, response bounds, and entry uniqueness before +atomically replacing accepted state. This discovery snapshot does not grant +install authority. + ## Public API The initial publisher routes are: @@ -172,6 +188,7 @@ The initial publisher routes are: ```text GET /api/v1/publishers/{publisherId} GET /api/v1/publishers/{publisherId}/feed +GET /api/v1/publishers/{publisherId}/feed/snapshot ``` ClawHub does not expose parallel `/accounts` feed routes. Publisher detail may @@ -221,12 +238,13 @@ machine-readable API discovery, not a required profile-page control. ## Trust And Signing Boundary -The atomic publisher-feed document does not yet have a signed payload type and -MUST NOT reuse `openclaw.official-external-plugin-catalog-feed.v1`. Its complete -sharded representation, plus signed query and change projections, use the -distinct payload types defined above and the strict distribution-addendum -schemas. Each requires expected publisher-feed identity binding, test vectors, -and explicit client verifier registration. +The atomic publisher-feed snapshot MUST use +`openclaw.clawhub-publisher-feed-snapshot.v1` and MUST NOT reuse +`openclaw.official-external-plugin-catalog-feed.v1`. Its complete sharded +representation, plus signed query and change projections, use the distinct +payload types defined above and the strict distribution-addendum schemas. Each +requires expected publisher-feed identity binding, test vectors, and explicit +client verifier registration. All publisher-feed representations MAY use the same dedicated ClawHub platform feed-signing key and bundled public trust anchor as other ClawHub-operated diff --git a/rfcs/0009/hosted-feed-distribution-v1-spec.md b/rfcs/0009/hosted-feed-distribution-v1-spec.md index d1827807..2f8f5811 100644 --- a/rfcs/0009/hosted-feed-distribution-v1-spec.md +++ b/rfcs/0009/hosted-feed-distribution-v1-spec.md @@ -42,7 +42,8 @@ that requires complete offline state MUST accept an atomic or sharded snapshot. This addendum defines reusable representation shapes, not one universal signed payload type. Each concrete feed contract MUST assign a distinct versioned payload type to each representation it accepts. For example, ClawHub publisher -feeds use `openclaw.clawhub-publisher-feed-query-results.v1` and +feeds use `openclaw.clawhub-publisher-feed-snapshot.v1`, +`openclaw.clawhub-publisher-feed-query-results.v1`, and `openclaw.clawhub-publisher-feed-changes.v1`. Its sharded root uses `openclaw.clawhub-publisher-feed-shard-root.v1`. @@ -367,8 +368,8 @@ A conforming publisher: - pins every paginated operation to one immutable revision or range; - emits removal tombstones and explicit reset-required responses; - publishes immutable shards before the signed root; -- signs each root, query page, and change page with the concrete feed contract's - distinct payload type. +- signs each atomic snapshot, root, query page, and change page with the + concrete feed contract's distinct payload type. A conforming client: From 3b19d35fe75442b87e6ede6c31c81216ff31e8d3 Mon Sep 17 00:00:00 2001 From: Gio Della-Libera Date: Fri, 17 Jul 2026 07:16:32 -0700 Subject: [PATCH 05/11] docs(rfc-0009): use standard DSSE envelope fields --- rfcs/0009/signed-feed-trust-v1-spec.md | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/rfcs/0009/signed-feed-trust-v1-spec.md b/rfcs/0009/signed-feed-trust-v1-spec.md index bf7f8738..1fa50ac8 100644 --- a/rfcs/0009/signed-feed-trust-v1-spec.md +++ b/rfcs/0009/signed-feed-trust-v1-spec.md @@ -45,18 +45,16 @@ signing identities even when the same secret-management system operates them. ## Signed Envelope -The v1 envelope has this exact shape: +The v1 envelope uses the standard DSSE JSON shape: ```json { - "schemaVersion": 1, "payloadType": "openclaw.official-external-plugin-catalog-feed.v1", "payload": "eyJzY2hlbWFWZXJzaW9uIjoxLC4uLn0", "signatures": [ { - "keyId": "clawhub-feed-2026-q3", - "algorithm": "ed25519", - "signature": "base64url-signature" + "keyid": "clawhub-feed-2026-q3", + "sig": "base64url-signature" } ] } @@ -66,7 +64,6 @@ Envelope fields: | Field | Type | Required | Semantics | | --- | --- | --- | --- | -| `schemaVersion` | integer | Yes | MUST be `1`. | | `payloadType` | string | Yes | MUST equal the exact type selected by the concrete feed contract and representation. The baseline catalog type is `openclaw.official-external-plugin-catalog-feed.v1`. | | `payload` | string | Yes | Base64 or unpadded base64url encoding of the exact UTF-8 feed bytes. | | `signatures` | array | Yes | Between 1 and 16 signature records. | @@ -75,12 +72,13 @@ Signature fields: | Field | Type | Required | Semantics | | --- | --- | --- | --- | -| `keyId` | string | Yes | Non-empty configured publisher key id. | -| `algorithm` | string | Yes | MUST be `ed25519`. | -| `signature` | string | Yes | Base64url Ed25519 signature over the DSSE PAE bytes. | +| `keyid` | string | Yes for Hosted Feed v1 | Non-empty configured publisher key id. DSSE permits omission, but this profile requires it to resolve an out-of-band trusted key. | +| `sig` | string | Yes | Base64 or unpadded base64url Ed25519 signature over the DSSE PAE bytes. | -Duplicate `keyId` values make the envelope invalid. Unknown fields MUST NOT be -assigned trust meaning. +Ed25519 is selected by the trusted feed profile; the envelope does not carry an +algorithm field. Duplicate `keyid` values make the envelope invalid. Producers +MAY add fields and consumers MUST ignore unrecognized envelope and signature +fields, as required by DSSE. Unknown fields MUST NOT be assigned trust meaning. ## DSSE Pre-Authentication Encoding @@ -149,8 +147,8 @@ OpenClaw configuration. Before accepting a signed refresh, a client MUST: 1. Parse and validate the bounded envelope. -2. Require the supported `schemaVersion` and the exact `payloadType` selected by - the concrete feed contract and requested representation. +2. Require the exact `payloadType` selected by the concrete feed contract and + requested representation. 3. Decode `payload` and each candidate signature. 4. Construct the exact DSSE PAE bytes above. 5. Resolve signature key ids only against the selected profile's trusted keys. From 8db5bfc45e79e76ead0d41d44f957ff680f0739c Mon Sep 17 00:00:00 2001 From: Gio Della-Libera Date: Fri, 17 Jul 2026 17:14:05 -0700 Subject: [PATCH 06/11] docs(rfc-0009): finalize sharded snapshot contract --- rfcs/0009/hosted-feed-distribution-v1-spec.md | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/rfcs/0009/hosted-feed-distribution-v1-spec.md b/rfcs/0009/hosted-feed-distribution-v1-spec.md index 2f8f5811..a9d94a5c 100644 --- a/rfcs/0009/hosted-feed-distribution-v1-spec.md +++ b/rfcs/0009/hosted-feed-distribution-v1-spec.md @@ -54,9 +54,11 @@ representation types: - `openclaw.official-external-plugin-catalog-query-results.v1`; - `openclaw.official-external-plugin-catalog-changes.v1`. -Other install catalogs, including a future skills catalog, MUST assign their -own types. A verifier MUST reject a valid signature whose payload type does not -match the selected feed class and representation. +The official ClawHub skills catalog shard root uses +`openclaw.official-skills-catalog-shard-root.v1`. Any query or change +representations for that catalog, and all other install catalogs, MUST assign +their own distinct types before use. A verifier MUST reject a valid signature +whose payload type does not match the selected feed class and representation. This prevents a signed discovery projection from being replayed as an install catalog, or one feed operator's response from being interpreted under another @@ -125,6 +127,11 @@ another transfer/content encoding. `entryCount` is exact. Publishers MUST serve the same decoded representation bytes for a shard URL regardless of negotiated HTTP encoding. +Descriptor URLs MUST be unique within a root, and descriptor digests MUST be +unique within a root. The sum of descriptor `entryCount` values MUST equal the +root `entryCount`; the sum of descriptor `byteLength` values is the aggregate +shard-set size used for the limit below. + A shard document has exactly these fields: | Field | Type | Semantics | @@ -152,6 +159,16 @@ signed root, enforce aggregate byte, shard, and entry limits, fetch every shard, verify lengths and digests, validate all entries, and atomically replace the prior root and shard set only after the entire snapshot succeeds. +Shards are authenticated by their exact byte length and digest in the signed +root and are not independently DSSE-signed. A client that persists a sharded +snapshot MUST retain the exact accepted root payload and exact shard bytes, or +an equivalent lossless representation. Before using that snapshot as fallback, +it MUST reverify the root under the currently selected trust policy, reject an +expired root using the current load time, and reverify every retained shard's +length and digest before parsing or exposing entries. A partial or expired +cached shard set MUST fail closed; it MUST NOT replace or extend the root's +validity window. + ## Signed Query Projection ```json @@ -376,5 +393,7 @@ A conforming client: - enforces per-response and aggregate bounds; - binds query and change continuations to the initial request and revision; - verifies all shard digests and all signed projection pages; +- reverifies current root trust, expiry, and retained shard bytes before using + a cached sharded snapshot; - applies complete snapshots and complete change ranges atomically; - falls back to a full snapshot after an explicit retention gap. From d52fa55d9231d6a19e19e7a3aa9fd499eb73ef65 Mon Sep 17 00:00:00 2001 From: Gio Della-Libera Date: Fri, 17 Jul 2026 18:03:59 -0700 Subject: [PATCH 07/11] docs(rfc-0009): make feed notifications first class --- ...009-hosted-feeds-for-plugins-and-skills.md | 24 ++++++---- rfcs/0009/clawhub-account-feeds-v1-spec.md | 25 +++++++--- rfcs/0009/hosted-feed-distribution-v1-spec.md | 46 +++++++++++++++++++ 3 files changed, 81 insertions(+), 14 deletions(-) diff --git a/rfcs/0009-hosted-feeds-for-plugins-and-skills.md b/rfcs/0009-hosted-feeds-for-plugins-and-skills.md index 1f438c3a..2e260e29 100644 --- a/rfcs/0009-hosted-feeds-for-plugins-and-skills.md +++ b/rfcs/0009-hosted-feeds-for-plugins-and-skills.md @@ -505,9 +505,10 @@ install. The trust layers stay separate: Following a publisher is therefore a social-discovery signal. ClawHub should show new work from followed publishers in a pull-based activity timeline rather -than send one notification for every publication. OpenClaw or Control UI may -generate notifications for followed-publisher updates that affect content -installed locally, because ClawHub does not know an instance's lockfile. +than send one notification for every publication. First-class alerts should use +explicit item watches and a durable account inbox anchored to signed feed +revisions. OpenClaw may offer to synchronize installed-item watches, but only +with an explicit account setting because that discloses local inventory. Following must not make a package official, bypass security scans or approval, or allow a feed to introduce new source profiles or credentials. @@ -551,10 +552,13 @@ ClawHub product stack: following lists; keep private mute or notification preferences separate. 3. Activity timeline: show new or updated public skills and plugins from followed publishers without generating one alert per publication. -4. Profile and discovery surfaces: expose publisher profile pages, followed +4. Item watches and account inbox: add explicit item-scoped subscriptions, + durable cross-device read state, coalescing, and delivery hints that point + back to signed feed revisions without performing updates. +5. Profile and discovery surfaces: expose publisher profile pages, followed publisher lists, and search filters such as "people I follow" or "new from followed publishers". -5. Registry and scan bridge: expose the subset of ClawHub publisher and feed state +6. Registry and scan bridge: expose the subset of ClawHub publisher and feed state that downstream registries can consume, while preserving their own scans, approval workflows, and registry-inclusion decisions. @@ -568,13 +572,17 @@ collapsing the trust gates: 2. Publisher activity timeline: expose a bounded timeline of new or updated public skills and plugins from followed publishers. Do not create ClawHub alerts for every publication. -3. Machine-readable feed discovery: expose canonical publisher feed URLs through +3. Item watches and notification inbox: expose authenticated item watches and a + durable account inbox with idempotent acknowledgement, private preferences, + bounded delivery, and signed-revision references. Push and channel delivery + wake clients but do not authorize updates. +4. Machine-readable feed discovery: expose canonical publisher feed URLs through APIs. A visible profile-page feed control is optional; OpenClaw should offer a handle-based command such as `openclaw publisher follow `. -4. Registry submission workflow: let eligible ClawHub publisher entries be +5. Registry submission workflow: let eligible ClawHub publisher entries be idempotently queued, retried, withdrawn, or resubmitted for downstream registry review and scanning while keeping submission separate from approval. -5. Reflected registry and scan state: persist and display downstream review, +6. Reflected registry and scan state: persist and display downstream review, scan, inclusion, and local approval states, including status history, only when those systems report them back to ClawHub. diff --git a/rfcs/0009/clawhub-account-feeds-v1-spec.md b/rfcs/0009/clawhub-account-feeds-v1-spec.md index 75ba4c21..da55f7dc 100644 --- a/rfcs/0009/clawhub-account-feeds-v1-spec.md +++ b/rfcs/0009/clawhub-account-feeds-v1-spec.md @@ -16,12 +16,13 @@ This addendum defines: - the strict publisher-feed document and entry shapes; - bounded pagination and refresh semantics; - public publisher-follow discovery; +- item-watch and durable notification-inbox semantics; - the boundary between following, discovery, trust, and installation; - downstream registry provenance and diagnostics. It does not define a separate account feed, installable catalog entries, -package candidates, notification transport, organization approval, artifact -trust, or security scanning. +package candidates, concrete push or channel transports, organization +approval, artifact trust, or security scanning. Publisher feeds are the first rollout target for the reusable query and change contracts in `hosted-feed-distribution-v1-spec.md`. A publisher with hundreds of @@ -216,9 +217,20 @@ ClawHub SHOULD prefer a timeline over per-publication notification fanout. A high-volume publisher can publish hundreds of entries, and following that publisher does not imply that every publish deserves an alert. -OpenClaw or Control UI MAY generate local notifications for followed-publisher -updates that affect content actually installed by that OpenClaw instance. That -filter depends on local install state and is not owned by ClawHub. +ClawHub SHOULD separately support authenticated item watches and a durable +account notification inbox using the distribution addendum's watch contract. +An item watch is explicit alert intent and may synchronize across OpenClaw, +Control UI, and ClawHub clients. Following a publisher MUST NOT automatically +create one item alert per publication. + +OpenClaw MAY offer to watch installed content and synchronize those watches to +the account inbox. That synchronization requires an explicit account setting +because it discloses installed-item identities. Without it, OpenClaw may keep +installed-item watches local while still consuming explicit account watches. + +Inbox delivery is a hint. OpenClaw or Control UI MUST verify the referenced +signed publisher or catalog change before presenting an actionable update, and +an alert MUST NOT install, update, enable, approve, or remove content. Following MUST NOT mean that a publisher is official, reviewed, trusted, scanned, approved, or installable. UI and APIs MUST use follow/following @@ -289,4 +301,5 @@ A client: - resolves entries through an accepted catalog before installation; - stores stable publisher ids rather than mutable handles; - bounds pagination and reports unavailable or malformed state; -- keeps local installed-content notifications separate from ClawHub following. +- keeps item-watch alerts separate from ClawHub publisher following; +- verifies referenced signed feed state before presenting actionable alerts. diff --git a/rfcs/0009/hosted-feed-distribution-v1-spec.md b/rfcs/0009/hosted-feed-distribution-v1-spec.md index a9d94a5c..a1dd5f9c 100644 --- a/rfcs/0009/hosted-feed-distribution-v1-spec.md +++ b/rfcs/0009/hosted-feed-distribution-v1-spec.md @@ -369,6 +369,52 @@ root. It MUST satisfy the selected feed profile's origin policy before any request is made. Clients MUST NOT forward credentials across origins and MUST verify this response before changing accepted state. +## Watches And Notifications + +Notifications are a first-class consumption capability built on signed feed +changes. They are not a substitute for the signed change or snapshot +representations and do not add install authority. + +A feed service MAY expose authenticated subscriptions for a feed, publisher, +or stable item identity. Item watches are the baseline alerting scope. A +publisher or whole-feed subscription SHOULD populate a pull-based timeline or +bounded digest by default rather than emit one alert per changed item. + +An account-capable service SHOULD provide a durable notification inbox with: + +- stable event ids and idempotent acknowledgement; +- the feed id, representation class, item identity, and observed feed sequence; +- a bounded reason such as `updated`, `removed`, `blocked`, or + `security-state-changed`; +- created, read, and dismissal state suitable for multiple clients; +- a reference sufficient to retrieve the signed change range or current signed + snapshot that produced the event. + +Inbox and subscription state are account data, not signed feed content. A push, +email, webhook, or channel delivery is only a wake-up hint. Before presenting +an actionable update, a client MUST retrieve and verify the referenced signed +feed state, confirm the expected feed and item identity, and apply local policy. +Rejected, expired, rolled-back, or incomplete feed state MUST NOT produce an +actionable item update. + +Services MUST deduplicate retries and SHOULD coalesce repeated updates to the +same item. They MUST bound fanout, retention, page size, and delivery retries. +Removal, blocked-state, and security-state events SHOULD remain distinguishable +from ordinary version or metadata updates. A baseline sync MUST NOT report every +existing item as newly published, and a reset-required refresh SHOULD compare +the newly accepted complete snapshot with the prior accepted snapshot when one +is available. + +Explicit server-side watches may sync across devices. A client MAY derive +watches from locally installed content, but sending installed-item identities +to a service requires an explicit account setting because it discloses local +inventory. Subscription, mute, delivery-channel, and read state MUST remain +private even when publisher follows are public. + +A notification MUST NOT install, update, enable, remove, approve, or otherwise +activate content. Any such action uses the normal install or update path and +re-evaluates artifact integrity, trust, and policy. + ## Access Control For private publisher, named, organization, or composed feeds, the publisher From 0db4a6ab72ed58f5132d17cecec3b83c52f5d5fa Mon Sep 17 00:00:00 2001 From: Gio Della-Libera Date: Fri, 17 Jul 2026 21:01:15 -0700 Subject: [PATCH 08/11] docs(rfc-0009): make feed watches client-first --- ...009-hosted-feeds-for-plugins-and-skills.md | 28 ++++++---- rfcs/0009/clawhub-account-feeds-v1-spec.md | 29 ++++++----- rfcs/0009/hosted-feed-distribution-v1-spec.md | 51 +++++++++++-------- 3 files changed, 63 insertions(+), 45 deletions(-) diff --git a/rfcs/0009-hosted-feeds-for-plugins-and-skills.md b/rfcs/0009-hosted-feeds-for-plugins-and-skills.md index 2e260e29..94047e1a 100644 --- a/rfcs/0009-hosted-feeds-for-plugins-and-skills.md +++ b/rfcs/0009-hosted-feeds-for-plugins-and-skills.md @@ -505,10 +505,11 @@ install. The trust layers stay separate: Following a publisher is therefore a social-discovery signal. ClawHub should show new work from followed publishers in a pull-based activity timeline rather -than send one notification for every publication. First-class alerts should use -explicit item watches and a durable account inbox anchored to signed feed -revisions. OpenClaw may offer to synchronize installed-item watches, but only -with an explicit account setting because that discloses local inventory. +than send one notification for every publication. First-class alerts should +start as local OpenClaw item watches anchored to verified signed feed revisions. +ClawHub may later synchronize watches or host a durable account inbox for +cross-device and offline delivery. Synchronizing installed-item watches requires +an explicit account setting because that discloses local inventory. Following must not make a package official, bypass security scans or approval, or allow a feed to introduce new source profiles or credentials. @@ -552,9 +553,10 @@ ClawHub product stack: following lists; keep private mute or notification preferences separate. 3. Activity timeline: show new or updated public skills and plugins from followed publishers without generating one alert per publication. -4. Item watches and account inbox: add explicit item-scoped subscriptions, - durable cross-device read state, coalescing, and delivery hints that point - back to signed feed revisions without performing updates. +4. Optional account watch sync: after the local client model is proven, consider + explicit item-scoped subscriptions, durable cross-device read state, + coalescing, and delivery hints that point back to signed feed revisions + without performing updates. 5. Profile and discovery surfaces: expose publisher profile pages, followed publisher lists, and search filters such as "people I follow" or "new from followed publishers". @@ -572,10 +574,10 @@ collapsing the trust gates: 2. Publisher activity timeline: expose a bounded timeline of new or updated public skills and plugins from followed publishers. Do not create ClawHub alerts for every publication. -3. Item watches and notification inbox: expose authenticated item watches and a - durable account inbox with idempotent acknowledgement, private preferences, - bounded delivery, and signed-revision references. Push and channel delivery - wake clients but do not authorize updates. +3. Optional account watch sync and notification inbox: defer authenticated watch + synchronization and a durable account inbox until the local OpenClaw watch + model validates cross-device demand. Any later delivery points to signed + revisions, remains private and bounded, and does not authorize updates. 4. Machine-readable feed discovery: expose canonical publisher feed URLs through APIs. A visible profile-page feed control is optional; OpenClaw should offer a handle-based command such as `openclaw publisher follow `. @@ -603,6 +605,10 @@ OpenClaw trust and runtime stack: 5. Discovery consumption: add followed-publisher discovery filters only after verified publisher-feed state exists, while keeping install eligibility and package-source artifact verification on their existing gates. +6. Local item watches: persist stable watch identities, accepted feed + checkpoints, and local update history; evaluate changes only after signed + feed verification, suppress baseline floods, and expose bounded CLI and + Control UI state without requiring a ClawHub account inbox. After both tracks land, a joint ecosystem phase can build on the verified publisher-feed foundation: diff --git a/rfcs/0009/clawhub-account-feeds-v1-spec.md b/rfcs/0009/clawhub-account-feeds-v1-spec.md index da55f7dc..5166f241 100644 --- a/rfcs/0009/clawhub-account-feeds-v1-spec.md +++ b/rfcs/0009/clawhub-account-feeds-v1-spec.md @@ -16,7 +16,7 @@ This addendum defines: - the strict publisher-feed document and entry shapes; - bounded pagination and refresh semantics; - public publisher-follow discovery; -- item-watch and durable notification-inbox semantics; +- local item-watch semantics and optional hosted synchronization; - the boundary between following, discovery, trust, and installation; - downstream registry provenance and diagnostics. @@ -217,20 +217,21 @@ ClawHub SHOULD prefer a timeline over per-publication notification fanout. A high-volume publisher can publish hundreds of entries, and following that publisher does not imply that every publish deserves an alert. -ClawHub SHOULD separately support authenticated item watches and a durable -account notification inbox using the distribution addendum's watch contract. -An item watch is explicit alert intent and may synchronize across OpenClaw, -Control UI, and ClawHub clients. Following a publisher MUST NOT automatically -create one item alert per publication. +OpenClaw SHOULD support local item watches using the distribution addendum's +watch contract. An item watch is explicit alert intent and is evaluated against +verified signed publisher or catalog changes. Following a publisher MUST NOT +automatically create one item alert per publication. -OpenClaw MAY offer to watch installed content and synchronize those watches to -the account inbox. That synchronization requires an explicit account setting -because it discloses installed-item identities. Without it, OpenClaw may keep -installed-item watches local while still consuming explicit account watches. +ClawHub MAY later synchronize authenticated item watches or provide a durable +account inbox for cross-device and offline delivery. That hosted capability is +not required for publisher-feed v1. Synchronizing watches derived from installed +content requires an explicit account setting because it discloses installed-item +identities; without it, OpenClaw keeps installed-item watches local. -Inbox delivery is a hint. OpenClaw or Control UI MUST verify the referenced -signed publisher or catalog change before presenting an actionable update, and -an alert MUST NOT install, update, enable, approve, or remove content. +Hosted inbox delivery is a hint. OpenClaw or Control UI MUST verify the +referenced signed publisher or catalog change before presenting an actionable +update, and an alert MUST NOT install, update, enable, approve, or remove +content. Following MUST NOT mean that a publisher is official, reviewed, trusted, scanned, approved, or installable. UI and APIs MUST use follow/following @@ -301,5 +302,5 @@ A client: - resolves entries through an accepted catalog before installation; - stores stable publisher ids rather than mutable handles; - bounds pagination and reports unavailable or malformed state; -- keeps item-watch alerts separate from ClawHub publisher following; +- keeps local item-watch alerts separate from ClawHub publisher following; - verifies referenced signed feed state before presenting actionable alerts. diff --git a/rfcs/0009/hosted-feed-distribution-v1-spec.md b/rfcs/0009/hosted-feed-distribution-v1-spec.md index a1dd5f9c..f10da1d8 100644 --- a/rfcs/0009/hosted-feed-distribution-v1-spec.md +++ b/rfcs/0009/hosted-feed-distribution-v1-spec.md @@ -371,16 +371,25 @@ verify this response before changing accepted state. ## Watches And Notifications -Notifications are a first-class consumption capability built on signed feed -changes. They are not a substitute for the signed change or snapshot -representations and do not add install authority. - -A feed service MAY expose authenticated subscriptions for a feed, publisher, -or stable item identity. Item watches are the baseline alerting scope. A -publisher or whole-feed subscription SHOULD populate a pull-based timeline or -bounded digest by default rather than emit one alert per changed item. - -An account-capable service SHOULD provide a durable notification inbox with: +Watches are a first-class client capability built on signed feed changes. They +are not a substitute for the signed change or snapshot representations and do +not add install authority. + +A client MAY persist local watches for a feed, publisher, or stable item +identity. Item watches are the baseline alerting scope. A publisher or +whole-feed watch SHOULD populate a pull-based timeline or bounded digest by +default rather than emit one alert per changed item. + +For each watched feed representation, the client SHOULD persist its last +accepted sequence or snapshot digest and enough local history to deduplicate, +read, dismiss, or coalesce observed changes. It MUST evaluate a watch only after +verifying and atomically accepting the corresponding signed change range or +snapshot. The first accepted baseline MUST NOT report every existing item as a +new change. A client that was offline catches up on its next verified refresh. + +A feed service MAY expose authenticated watch synchronization or a durable +notification inbox for cross-device and offline delivery. If it does, the inbox +SHOULD provide: - stable event ids and idempotent acknowledgement; - the feed id, representation class, item identity, and observed feed sequence; @@ -390,12 +399,12 @@ An account-capable service SHOULD provide a durable notification inbox with: - a reference sufficient to retrieve the signed change range or current signed snapshot that produced the event. -Inbox and subscription state are account data, not signed feed content. A push, -email, webhook, or channel delivery is only a wake-up hint. Before presenting -an actionable update, a client MUST retrieve and verify the referenced signed -feed state, confirm the expected feed and item identity, and apply local policy. -Rejected, expired, rolled-back, or incomplete feed state MUST NOT produce an -actionable item update. +Hosted inbox and subscription state are account data, not signed feed content. +A push, email, webhook, or channel delivery is only a wake-up hint. Before +presenting an actionable update from a hosted inbox, a client MUST retrieve and +verify the referenced signed feed state, confirm the expected feed and item +identity, and apply local policy. Rejected, expired, rolled-back, or incomplete +feed state MUST NOT produce an actionable item update. Services MUST deduplicate retries and SHOULD coalesce repeated updates to the same item. They MUST bound fanout, retention, page size, and delivery retries. @@ -405,11 +414,13 @@ existing item as newly published, and a reset-required refresh SHOULD compare the newly accepted complete snapshot with the prior accepted snapshot when one is available. -Explicit server-side watches may sync across devices. A client MAY derive +Local watch state is the v1 baseline and does not require an account service. +Explicit server-side watch synchronization is optional. A client MAY derive watches from locally installed content, but sending installed-item identities -to a service requires an explicit account setting because it discloses local -inventory. Subscription, mute, delivery-channel, and read state MUST remain -private even when publisher follows are public. +or local watch history to a service requires an explicit account setting +because it discloses local inventory and interests. Hosted subscription, mute, +delivery-channel, and read state MUST remain private even when publisher follows +are public. A notification MUST NOT install, update, enable, remove, approve, or otherwise activate content. Any such action uses the normal install or update path and From be1026dc4aa4e3af66dc29762a2638bdacbc8a3c Mon Sep 17 00:00:00 2001 From: Gio Della-Libera Date: Sat, 18 Jul 2026 06:48:47 -0700 Subject: [PATCH 09/11] docs(rfc-0009): track feeds implementation series --- ...009-hosted-feeds-for-plugins-and-skills.md | 95 +++++++++++++++++-- 1 file changed, 86 insertions(+), 9 deletions(-) diff --git a/rfcs/0009-hosted-feeds-for-plugins-and-skills.md b/rfcs/0009-hosted-feeds-for-plugins-and-skills.md index 94047e1a..b0bf09df 100644 --- a/rfcs/0009-hosted-feeds-for-plugins-and-skills.md +++ b/rfcs/0009-hosted-feeds-for-plugins-and-skills.md @@ -5,7 +5,7 @@ authors: - Patrick - Gio created: 2026-06-18 -last_updated: 2026-07-08 +last_updated: 2026-07-18 status: draft issue: rfc_pr: https://github.com/openclaw/rfcs/pull/19 @@ -483,6 +483,84 @@ hosted feed payload according to local configuration. Later PRs can consume that verified state when they wire trusted feed entries into install eligibility, search filtering, regional variants, or tenant-composed effective feeds. +### Implementation status (July 2026) + +This status is informative rather than normative. Open PRs remain subject to +review and may change without changing the v1 contract. + +- **Hosted catalog foundation (merged):** OpenClaw + [#95846](https://github.com/openclaw/openclaw/pull/95846), + [#95868](https://github.com/openclaw/openclaw/pull/95868), + [#95877](https://github.com/openclaw/openclaw/pull/95877), + [#95964](https://github.com/openclaw/openclaw/pull/95964), + [#95969](https://github.com/openclaw/openclaw/pull/95969), + [#95981](https://github.com/openclaw/openclaw/pull/95981), + [#96155](https://github.com/openclaw/openclaw/pull/96155), + [#96158](https://github.com/openclaw/openclaw/pull/96158), and + [#96194](https://github.com/openclaw/openclaw/pull/96194) provide hosted + fetch, durable fallback, source/config profiles, refresh, entries, and + bounded telemetry for the external plugin catalog. +- **Signed-feed trust (merged):** OpenClaw + [#98299](https://github.com/openclaw/openclaw/pull/98299), + [#98316](https://github.com/openclaw/openclaw/pull/98316), + [#98338](https://github.com/openclaw/openclaw/pull/98338), and + [#98350](https://github.com/openclaw/openclaw/pull/98350) provide envelope + verification, trusted source-profile configuration, verified snapshot state, + rollback protection, and operator trust visibility. +- **ClawHub signing and bootstrap trust (active):** ClawHub + [#3005](https://github.com/openclaw/clawhub/pull/3005) signs stored catalog + publications with a dedicated feed key. OpenClaw + [#101981](https://github.com/openclaw/openclaw/pull/101981) binds the built-in + `clawhub-public` profile to the matching bundled or environment-provided + public trust anchor. OpenClaw + [#110037](https://github.com/openclaw/openclaw/pull/110037) aligns the client + with standard DSSE while retaining the shipped beta format as an isolated + compatibility path. +- **ClawHub publisher discovery (active):** ClawHub + [#2948](https://github.com/openclaw/clawhub/pull/2948), + [#2949](https://github.com/openclaw/clawhub/pull/2949), + [#2950](https://github.com/openclaw/clawhub/pull/2950), + [#2951](https://github.com/openclaw/clawhub/pull/2951), + [#2953](https://github.com/openclaw/clawhub/pull/2953), + [#2957](https://github.com/openclaw/clawhub/pull/2957), + [#2958](https://github.com/openclaw/clawhub/pull/2958), and + [#2959](https://github.com/openclaw/clawhub/pull/2959) cover publisher feed + identity, public follows, state facts, discovery/profile surfaces, registry + export, a pull-based activity timeline, and machine-readable feed URLs. +- **Signed publisher-feed distribution (active):** ClawHub + [#3116](https://github.com/openclaw/clawhub/pull/3116) and + [#3117](https://github.com/openclaw/clawhub/pull/3117) provide revisioned + publisher state plus signed snapshot, query, change, and reset projections. + The OpenClaw consumer stack is + [#109305](https://github.com/openclaw/openclaw/pull/109305), + [#109340](https://github.com/openclaw/openclaw/pull/109340), + [#109378](https://github.com/openclaw/openclaw/pull/109378), + [#109397](https://github.com/openclaw/openclaw/pull/109397), + [#109407](https://github.com/openclaw/openclaw/pull/109407), + [#109461](https://github.com/openclaw/openclaw/pull/109461), + [#109518](https://github.com/openclaw/openclaw/pull/109518), and + [#109584](https://github.com/openclaw/openclaw/pull/109584), covering strict + verification and transport, durable refresh/follows, gateway scheduling and + RPC, Control UI following, and signed-profile discovery. +- **Main-catalog scale and incremental refresh (active):** ClawHub + [#3147](https://github.com/openclaw/clawhub/pull/3147), + [#3149](https://github.com/openclaw/clawhub/pull/3149), + [#3151](https://github.com/openclaw/clawhub/pull/3151), + [#3160](https://github.com/openclaw/clawhub/pull/3160), and + [#3163](https://github.com/openclaw/clawhub/pull/3163) define and produce + strict query/change/reset schemas, retained change history, signed delta and + query pages, and signed sharded snapshots. OpenClaw + [#110250](https://github.com/openclaw/openclaw/pull/110250) consumes complete + signed shard roots and immutable digest-addressed shards. +- **Client-first item watches (active):** OpenClaw + [#110438](https://github.com/openclaw/openclaw/pull/110438) is the first + watch-series slice: durable local plugin watches, accepted per-watch + baselines, bounded update history, source isolation, and CLI read, dismiss, + and mute operations evaluated only after signed refresh. A bounded Control + UI updates surface is the next slice; optional local delivery is a later + slice. ClawHub [#3171](https://github.com/openclaw/clawhub/pull/3171) is a + parked server-hosted watch/inbox prototype, not a v1 dependency. + ### ClawHub publisher feeds and following ClawHub can also publish publisher-backed feeds as a discovery feature. @@ -535,13 +613,10 @@ status, Microsoft registry inclusion, tenant approval, security-scan results, and package artifact verification remain separate gates that later PRs must wire explicitly. -Implementation update: the ClawHub publisher-feed discovery work in this RFC is -now backed by the current ClawHub PR stack. The code-backed slices are -`#2948`, `#2950`, `#2957`, `#2958`, and `#2959`: publisher feed model/API, -public follow graph, follow controls and discovery, a publisher activity -timeline, and machine-readable feed discovery. Those PRs keep the same boundary -described above: following and discovery do not imply official status, registry -inclusion, install eligibility, or security-scan bypass. +The implementation-status section above tracks the current ClawHub publisher +and OpenClaw consumer PRs. Those slices keep the same boundary described here: +following and discovery do not imply official status, registry inclusion, +install eligibility, or security-scan bypass. The likely PR stacks are: @@ -608,7 +683,9 @@ OpenClaw trust and runtime stack: 6. Local item watches: persist stable watch identities, accepted feed checkpoints, and local update history; evaluate changes only after signed feed verification, suppress baseline floods, and expose bounded CLI and - Control UI state without requiring a ClawHub account inbox. + Control UI state without requiring a ClawHub account inbox. OpenClaw + [#110438](https://github.com/openclaw/openclaw/pull/110438) implements the + durable store and CLI slice; the bounded Control UI surface follows. After both tracks land, a joint ecosystem phase can build on the verified publisher-feed foundation: From 72fa327e58a0b73b7e56e7c8eba6974feb99f670 Mon Sep 17 00:00:00 2001 From: Gio Della-Libera Date: Sat, 18 Jul 2026 08:51:01 -0700 Subject: [PATCH 10/11] docs(rfc-0009): align feed merge lanes --- ...009-hosted-feeds-for-plugins-and-skills.md | 177 +++++++----------- rfcs/0009/clawhub-account-feeds-v1-spec.md | 2 +- rfcs/0009/signed-feed-trust-v1-spec.md | 2 +- 3 files changed, 70 insertions(+), 111 deletions(-) diff --git a/rfcs/0009-hosted-feeds-for-plugins-and-skills.md b/rfcs/0009-hosted-feeds-for-plugins-and-skills.md index b0bf09df..17913712 100644 --- a/rfcs/0009-hosted-feeds-for-plugins-and-skills.md +++ b/rfcs/0009-hosted-feeds-for-plugins-and-skills.md @@ -137,8 +137,8 @@ The first feed version should preserve those semantics while moving the catalog source from bundled-only JSON to hosted JSON with bundled fallback. The implementer-facing v1 core contract is captured in -[`0009/hosted-feed-v1-spec.md`](0009/hosted-feed-v1-spec.md). Trust and account -feed addenda are captured separately in +[`0009/hosted-feed-v1-spec.md`](0009/hosted-feed-v1-spec.md). Trust and +publisher-feed addenda are captured separately in [`0009/signed-feed-trust-v1-spec.md`](0009/signed-feed-trust-v1-spec.md) and [`0009/clawhub-account-feeds-v1-spec.md`](0009/clawhub-account-feeds-v1-spec.md) (publisher feeds; the historical filename is retained while the addendum is @@ -515,37 +515,35 @@ review and may change without changing the v1 contract. public trust anchor. OpenClaw [#110037](https://github.com/openclaw/openclaw/pull/110037) aligns the client with standard DSSE while retaining the shipped beta format as an isolated - compatibility path. -- **ClawHub publisher discovery (active):** ClawHub - [#2948](https://github.com/openclaw/clawhub/pull/2948), + compatibility path. OpenClaw + [#108342](https://github.com/openclaw/openclaw/pull/108342) preserves + monotonic rollback comparison across trusted-key rotation. +- **Publisher-feed producer (active, first product lane):** ClawHub + [#2948](https://github.com/openclaw/clawhub/pull/2948) defines stable + publisher identity, coherent snapshots, and bounded pagination. ClawHub + [#3116](https://github.com/openclaw/clawhub/pull/3116) and + [#3117](https://github.com/openclaw/clawhub/pull/3117) add revisioned query + and change state plus signed snapshot, query, change, and reset routes. +- **Optional ClawHub publisher discovery (active):** ClawHub [#2949](https://github.com/openclaw/clawhub/pull/2949), [#2950](https://github.com/openclaw/clawhub/pull/2950), - [#2951](https://github.com/openclaw/clawhub/pull/2951), - [#2953](https://github.com/openclaw/clawhub/pull/2953), - [#2957](https://github.com/openclaw/clawhub/pull/2957), - [#2958](https://github.com/openclaw/clawhub/pull/2958), and - [#2959](https://github.com/openclaw/clawhub/pull/2959) cover publisher feed - identity, public follows, state facts, discovery/profile surfaces, registry - export, a pull-based activity timeline, and machine-readable feed URLs. -- **Signed publisher-feed distribution (active):** ClawHub - [#3116](https://github.com/openclaw/clawhub/pull/3116) and - [#3117](https://github.com/openclaw/clawhub/pull/3117) provide revisioned - publisher state plus signed snapshot, query, change, and reset projections. - The OpenClaw consumer stack is + and [#2958](https://github.com/openclaw/clawhub/pull/2958) cover publisher + state and labels, the public follow graph, follow controls, and a pull-based + activity timeline. This social lane is not required for signed publisher + feed consumption. ClawHub + [#2953](https://github.com/openclaw/clawhub/pull/2953) is parked until a + concrete registry-export consumer exists. +- **Publisher-feed consumer (active):** The OpenClaw stack is [#109305](https://github.com/openclaw/openclaw/pull/109305), [#109340](https://github.com/openclaw/openclaw/pull/109340), [#109378](https://github.com/openclaw/openclaw/pull/109378), [#109397](https://github.com/openclaw/openclaw/pull/109397), - [#109407](https://github.com/openclaw/openclaw/pull/109407), [#109461](https://github.com/openclaw/openclaw/pull/109461), - [#109518](https://github.com/openclaw/openclaw/pull/109518), and - [#109584](https://github.com/openclaw/openclaw/pull/109584), covering strict - verification and transport, durable refresh/follows, gateway scheduling and - RPC, Control UI following, and signed-profile discovery. + and [#109584](https://github.com/openclaw/openclaw/pull/109584). Together they + cover strict verification and transport, durable refresh/follows, gateway + scheduling and RPC, Control UI following, and signed-profile discovery. - **Main-catalog scale and incremental refresh (active):** ClawHub - [#3147](https://github.com/openclaw/clawhub/pull/3147), [#3149](https://github.com/openclaw/clawhub/pull/3149), - [#3151](https://github.com/openclaw/clawhub/pull/3151), [#3160](https://github.com/openclaw/clawhub/pull/3160), and [#3163](https://github.com/openclaw/clawhub/pull/3163) define and produce strict query/change/reset schemas, retained change history, signed delta and @@ -557,9 +555,10 @@ review and may change without changing the v1 contract. watch-series slice: durable local plugin watches, accepted per-watch baselines, bounded update history, source isolation, and CLI read, dismiss, and mute operations evaluated only after signed refresh. A bounded Control - UI updates surface is the next slice; optional local delivery is a later - slice. ClawHub [#3171](https://github.com/openclaw/clawhub/pull/3171) is a - parked server-hosted watch/inbox prototype, not a v1 dependency. + UI updates surface is deliberately not authored until this storage and CLI + boundary is accepted. ClawHub + [#3171](https://github.com/openclaw/clawhub/pull/3171) is a parked + server-hosted watch/inbox prototype, not a v1 dependency. ### ClawHub publisher feeds and following @@ -585,7 +584,7 @@ Following a publisher is therefore a social-discovery signal. ClawHub should show new work from followed publishers in a pull-based activity timeline rather than send one notification for every publication. First-class alerts should start as local OpenClaw item watches anchored to verified signed feed revisions. -ClawHub may later synchronize watches or host a durable account inbox for +ClawHub may later synchronize watches or host a durable user inbox for cross-device and offline delivery. Synchronizing installed-item watches requires an explicit account setting because that discloses local inventory. Following must not make a package official, bypass security scans or approval, @@ -596,17 +595,16 @@ ClawHub, ClawHub exposes publisher-feed discovery state, the registry selects the relevant subset, runs its own scans and approval checks, and publishes an approved effective feed for its clients. -The publisher-feed work should move on two tracks: +The publisher-feed work separates its required protocol/runtime path from +optional ClawHub social discovery: -1. ClawHub product track: define the publisher feed model, public follow graph, - timeline and profile surfaces, and discovery filters such as "people I - follow" or "new from followed publishers". This track owns the user - experience for following publishers and discovering new plugins or skills. -2. OpenClaw trust and runtime track: verify ClawHub-authored feed envelopes, - record source-profile trust state, expose bounded CLI and diagnostics - visibility, and then consume verified publisher-feed state for discovery. This - track owns what the client can safely display, cache, refresh, and use for - search or notification surfaces. +1. Core producer/consumer path: ClawHub defines coherent publisher projections; + OpenClaw verifies their envelopes, records source-profile trust, and exposes + bounded follow, refresh, diagnostics, and discovery state. +2. Optional social path: ClawHub may add public follows, profile controls, + activity timelines, and discovery filters such as "people I follow" or + "new from followed publishers". This path does not block signed publisher + feed consumption. These tracks intentionally meet at discovery first. Install authority, official status, Microsoft registry inclusion, tenant approval, security-scan results, @@ -618,77 +616,38 @@ and OpenClaw consumer PRs. Those slices keep the same boundary described here: following and discovery do not imply official status, registry inclusion, install eligibility, or security-scan bypass. -The likely PR stacks are: - -ClawHub product stack: - -1. Publisher-feed model and API: define stable publisher ids, publisher feed - URLs, deterministic continuation, and coherent feed revisions. -2. Public follow graph: add follow and unfollow state plus public follower and - following lists; keep private mute or notification preferences separate. -3. Activity timeline: show new or updated public skills and plugins from - followed publishers without generating one alert per publication. -4. Optional account watch sync: after the local client model is proven, consider - explicit item-scoped subscriptions, durable cross-device read state, - coalescing, and delivery hints that point back to signed feed revisions - without performing updates. -5. Profile and discovery surfaces: expose publisher profile pages, followed - publisher lists, and search filters such as "people I follow" or "new from - followed publishers". -6. Registry and scan bridge: expose the subset of ClawHub publisher and feed state - that downstream registries can consume, while preserving their own scans, - approval workflows, and registry-inclusion decisions. - -The next ClawHub implementation stack should build on those foundations without -collapsing the trust gates: - -1. Follow controls and followed-publisher discovery: add follow and unfollow - controls on publisher surfaces, followed-publisher lists, and search filters - such as "people I follow" or "new from followed publishers", with clear - reasons for why entries appear. -2. Publisher activity timeline: expose a bounded timeline of new or updated - public skills and plugins from followed publishers. Do not create ClawHub - alerts for every publication. -3. Optional account watch sync and notification inbox: defer authenticated watch - synchronization and a durable account inbox until the local OpenClaw watch - model validates cross-device demand. Any later delivery points to signed - revisions, remains private and bounded, and does not authorize updates. -4. Machine-readable feed discovery: expose canonical publisher feed URLs through - APIs. A visible profile-page feed control is optional; OpenClaw should offer - a handle-based command such as `openclaw publisher follow `. -5. Registry submission workflow: let eligible ClawHub publisher entries be - idempotently queued, retried, withdrawn, or resubmitted for downstream - registry review and scanning while keeping submission separate from approval. -6. Reflected registry and scan state: persist and display downstream review, - scan, inclusion, and local approval states, including status history, only - when those systems report them back to ClawHub. - -OpenClaw trust and runtime stack: - -1. Feed envelope and verifier primitives: parse ClawHub-authored feed envelopes, - verify configured trust anchors, fail closed on invalid signatures, and keep - unsigned or failed-verification bodies inert. -2. Source-profile trust config: define which ClawHub feed profiles and publisher - feed endpoints are locally trusted, including directly configured publisher - public keys and explicit unsigned opt-in behavior for self-hosted feeds. -3. Refresh and snapshot trust state: persist verification result, sequence, - freshness, and fallback state with the cached feed snapshot without granting - install or search authority by persistence alone. -4. CLI and operator visibility: show feed trust, source profile, publisher-feed, - stale, fallback, and verification-failure states in refresh, entries, and - diagnostics using bounded fields. -5. Discovery consumption: add followed-publisher discovery - filters only after verified publisher-feed state exists, while keeping install - eligibility and package-source artifact verification on their existing gates. -6. Local item watches: persist stable watch identities, accepted feed - checkpoints, and local update history; evaluate changes only after signed - feed verification, suppress baseline floods, and expose bounded CLI and - Control UI state without requiring a ClawHub account inbox. OpenClaw - [#110438](https://github.com/openclaw/openclaw/pull/110438) implements the - durable store and CLI slice; the bounded Control UI surface follows. - -After both tracks land, a joint ecosystem phase can build on the verified -publisher-feed foundation: +The implementation is organized into independent merge lanes. + +Publisher-feed core is the first product lane: + +1. ClawHub publisher identity, coherent snapshot, and bounded feed API. +2. ClawHub durable revisions, query state, and changed-since state. +3. ClawHub signed snapshot, query, change, and reset routes. +4. OpenClaw verification, bounded transport, durable refresh, and local follow + CLI. Gateway automation and Control UI are a second publisher-feed wave. + +Main-catalog scale is the second product lane: + +1. Shared strict query/change/reset schemas and durable change history. +2. Signed changed-since and indexed query routes. +3. Signed sharded full snapshots. +4. OpenClaw sharded-root and immutable-shard consumption. + +Optional ClawHub social discovery remains independent: + +1. Public publisher follow graph. +2. Follow controls, publisher discovery, and pull-based activity timeline. +3. Publisher state and labels. Registry export remains parked until a concrete + consumer exists. + +Client-first item watches are a third, independent lane. OpenClaw +[#110438](https://github.com/openclaw/openclaw/pull/110438) implements the +durable local store and CLI boundary. A Control UI slice follows only after +that boundary is accepted. Hosted synchronization and inbox delivery remain +optional future work. + +After the publisher and catalog-scale lanes land, a joint ecosystem phase can +build on the verified publisher-feed foundation: 1. Install and policy integration: let verified feed and publisher state participate in install eligibility, tenant policy, allow lists, block diff --git a/rfcs/0009/clawhub-account-feeds-v1-spec.md b/rfcs/0009/clawhub-account-feeds-v1-spec.md index 5166f241..62c7e431 100644 --- a/rfcs/0009/clawhub-account-feeds-v1-spec.md +++ b/rfcs/0009/clawhub-account-feeds-v1-spec.md @@ -223,7 +223,7 @@ verified signed publisher or catalog changes. Following a publisher MUST NOT automatically create one item alert per publication. ClawHub MAY later synchronize authenticated item watches or provide a durable -account inbox for cross-device and offline delivery. That hosted capability is +hosted inbox for cross-device and offline delivery. That hosted capability is not required for publisher-feed v1. Synchronizing watches derived from installed content requires an explicit account setting because it discloses installed-item identities; without it, OpenClaw keeps installed-item watches local. diff --git a/rfcs/0009/signed-feed-trust-v1-spec.md b/rfcs/0009/signed-feed-trust-v1-spec.md index 1fa50ac8..098bd09b 100644 --- a/rfcs/0009/signed-feed-trust-v1-spec.md +++ b/rfcs/0009/signed-feed-trust-v1-spec.md @@ -207,7 +207,7 @@ Bundling a public key is source-controlled trust distribution. It is not secret storage and it does not put the private key in the OpenClaw release. The same dedicated ClawHub feed-signing identity MAY sign multiple -ClawHub-operated feed classes, including public catalog, account, named, +ClawHub-operated feed classes, including public catalog, publisher, named, organization, and composed feeds. Each feed class MUST define its own versioned payload type, expected identity binding, schema, and verifier before clients accept it. Sharing the platform feed-signing key does not permit one payload From e4b78a524d4bbe8fad35a34ccd3c702c768d59c4 Mon Sep 17 00:00:00 2001 From: Gio Della-Libera Date: Sat, 18 Jul 2026 08:56:08 -0700 Subject: [PATCH 11/11] docs(rfc-0009): tighten feed interoperability --- ...009-hosted-feeds-for-plugins-and-skills.md | 28 +++++++++++-------- rfcs/0009/clawhub-account-feeds-v1-spec.md | 23 +++++++++++++-- rfcs/0009/hosted-feed-distribution-v1-spec.md | 7 +++++ rfcs/0009/signed-feed-trust-v1-spec.md | 9 ++++++ 4 files changed, 52 insertions(+), 15 deletions(-) diff --git a/rfcs/0009-hosted-feeds-for-plugins-and-skills.md b/rfcs/0009-hosted-feeds-for-plugins-and-skills.md index 17913712..d06eaf38 100644 --- a/rfcs/0009-hosted-feeds-for-plugins-and-skills.md +++ b/rfcs/0009-hosted-feeds-for-plugins-and-skills.md @@ -136,18 +136,22 @@ points are: The first feed version should preserve those semantics while moving the catalog source from bundled-only JSON to hosted JSON with bundled fallback. -The implementer-facing v1 core contract is captured in -[`0009/hosted-feed-v1-spec.md`](0009/hosted-feed-v1-spec.md). Trust and -publisher-feed addenda are captured separately in -[`0009/signed-feed-trust-v1-spec.md`](0009/signed-feed-trust-v1-spec.md) and -[`0009/clawhub-account-feeds-v1-spec.md`](0009/clawhub-account-feeds-v1-spec.md) -(publisher feeds; the historical filename is retained while the addendum is -under review). Scalable snapshots, signed queries, and changed-since retrieval -are defined in -[`0009/hosted-feed-distribution-v1-spec.md`](0009/hosted-feed-distribution-v1-spec.md). -This RFC remains the design rationale and rollout plan; the sidecar specs are -the concise schema, example, verification, refresh, and conformance references -for feed publishers and OpenClaw clients. +This RFC contains the rationale and rollout plan. Implementers should use the +sidecar specification matching their task: + +- [`0009/hosted-feed-v1-spec.md`](0009/hosted-feed-v1-spec.md) defines the + strict atomic install-catalog document and baseline client behavior. +- [`0009/signed-feed-trust-v1-spec.md`](0009/signed-feed-trust-v1-spec.md) + defines DSSE transport, trusted public keys, verification, and rotation. +- [`0009/clawhub-account-feeds-v1-spec.md`](0009/clawhub-account-feeds-v1-spec.md) + defines publisher discovery feeds. The historical filename is retained for + link stability; the contract does not define a separate account identity. +- [`0009/hosted-feed-distribution-v1-spec.md`](0009/hosted-feed-distribution-v1-spec.md) + defines sharded snapshots, signed queries, changed-since retrieval, and + client-first watches. + +An install-catalog producer starts with the core and trust specs. A publisher +feed producer or consumer also uses the publisher and distribution specs. ### Feed document diff --git a/rfcs/0009/clawhub-account-feeds-v1-spec.md b/rfcs/0009/clawhub-account-feeds-v1-spec.md index 62c7e431..1931d059 100644 --- a/rfcs/0009/clawhub-account-feeds-v1-spec.md +++ b/rfcs/0009/clawhub-account-feeds-v1-spec.md @@ -190,15 +190,30 @@ The initial publisher routes are: GET /api/v1/publishers/{publisherId} GET /api/v1/publishers/{publisherId}/feed GET /api/v1/publishers/{publisherId}/feed/snapshot +GET /api/v1/publishers/{publisherId}/feed/query?q=&kind=&limit= +GET /api/v1/publishers/{publisherId}/feed/changes?fromSequence=&limit= ``` +The first query or change request carries its selectors and optional bounded +limit. A continuation request carries only the opaque `cursor`; clients MUST +NOT repeat or alter initial selectors alongside it. ClawHub MUST reject a +cursor combined with initial selectors rather than guess which values win. + +The unsigned paginated `/feed` route is machine-readable public discovery, not +signed synchronization state. It SHOULD use `Cache-Control: private, no-store` +so a shared cache cannot bypass cursor freshness or publisher visibility. A +signed-feed client follows `/feed/snapshot`, `/feed/query`, and +`/feed/changes`; it MUST NOT upgrade the unsigned route into signed authority. + ClawHub does not expose parallel `/accounts` feed routes. Publisher detail may include public profile facts and a canonical feed URL, but MUST NOT expose private owner, member, linked-user, moderation, or authentication records. -APIs SHOULD distinguish missing, non-public, restricted, suspended, and -temporarily unavailable publishers without exposing private moderation -evidence. +Public APIs SHOULD return the same not-found shape for missing, non-public, +restricted, and suspended publishers. Operator-only diagnostics MAY distinguish +those conditions. A producer that cannot prove a coherent complete revision or +cannot sign a required projection MUST return a non-cacheable service-unavailable +response; it MUST NOT return a truncated or unsigned substitute. ## Following And Timeline @@ -285,6 +300,8 @@ limits, and catalog resolution failures. Diagnostics MUST NOT expose tokens, private profile or membership data, moderation evidence, package payload bytes, or private notification settings. +Public diagnostics MUST NOT distinguish a nonexistent publisher from a hidden, +restricted, or suspended publisher. ## Conformance diff --git a/rfcs/0009/hosted-feed-distribution-v1-spec.md b/rfcs/0009/hosted-feed-distribution-v1-spec.md index f10da1d8..84a075ff 100644 --- a/rfcs/0009/hosted-feed-distribution-v1-spec.md +++ b/rfcs/0009/hosted-feed-distribution-v1-spec.md @@ -387,6 +387,13 @@ verifying and atomically accepting the corresponding signed change range or snapshot. The first accepted baseline MUST NOT report every existing item as a new change. A client that was offline catches up on its next verified refresh. +Clients MUST NOT issue one network refresh per watched item. One bounded source +refresh SHOULD fan out locally across all watches bound to that accepted feed +state. Automatic refresh MUST bound concurrency, use backoff and jitter after +failures, and avoid overlapping runs for the same source. An unavailable watched +source MUST NOT block runtime readiness or discard the last accepted state; +clients report the failure and retry according to the source lifecycle policy. + A feed service MAY expose authenticated watch synchronization or a durable notification inbox for cross-device and offline delivery. If it does, the inbox SHOULD provide: diff --git a/rfcs/0009/signed-feed-trust-v1-spec.md b/rfcs/0009/signed-feed-trust-v1-spec.md index 098bd09b..36ac9c6b 100644 --- a/rfcs/0009/signed-feed-trust-v1-spec.md +++ b/rfcs/0009/signed-feed-trust-v1-spec.md @@ -80,6 +80,15 @@ algorithm field. Duplicate `keyid` values make the envelope invalid. Producers MAY add fields and consumers MUST ignore unrecognized envelope and signature fields, as required by DSSE. Unknown fields MUST NOT be assigned trust meaning. +Signed HTTP responses MUST use `Content-Type: application/vnd.dsse+json`. +After parsing optional media-type parameters, clients MUST reject another media +type on an operation that requires a signed envelope. A producer SHOULD +calculate `ETag` over the exact envelope bytes. +`Last-Modified` MUST NOT be the sole validator for a signed representation: +signing-key rotation can change envelope bytes without changing the stored +payload publication time. After a `304 Not Modified`, a client MUST still +reverify its retained envelope under the current trust policy before use. + ## DSSE Pre-Authentication Encoding Signatures MUST be calculated over the DSSE v1 pre-authentication encoding of