mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
Define private managed agent wire protocol (#4593)
## Summary - reserve kind `30179` for owner-private managed-agent aggregates - define the fail-closed owner-self NIP-44 v2 envelope and versioned payload codec - bind runnable identity/configuration to complete signed `30175`/`30177` recovery projections - validate NIP-OA owner→agent attestations and reject self-attestation - document NIP-PMA authority, migration prerequisites, privacy, and deployment order - keep generic relay ingest closed until private storage and atomic aggregate CAS exist ## Safety boundary This is the inert protocol/codec slice only. It does not publish secrets, change agent authority, migrate local records, or enable kind `30179` ingestion. The relay regression test proves generic EVENT ingest still rejects the kind. The finalized migration plan adds later prerequisites for relay-private storage/CAS, runtime lease/fencing, Desktop cutover, and harness authentication. Those belong in staged follow-up PRs rather than expanding this inert foundation. ## Validation At commit `67f0ea4ebb8d3ccba3a3eb9374e89a7178913f74`: - `cargo test -p buzz-core` — 246 unit + 2 doc tests passed - `cargo test -p buzz-relay private_managed_agent_kind_remains_rejected_until_atomic_ingest_exists` — passed - push hooks: Rust tests and desktop checks passed (`2145` desktop tests passed, `14` ignored) - `cargo fmt --all -- --check` - `git diff --check` ## Review Princess Donut cleared security/data integrity with no remaining high/medium findings. Mongo cleared migration compatibility and wire grammar. The later runtime lease/fencing protocol was also adversarially cleared as a plan; implementation slices still require independent evidence before activation. Deterministic plaintext/signed-projection/auth-tag interoperability vectors remain a valuable follow-up, not an S0 merge gate; random NIP-44 ciphertext is intentionally not snapshotted. --------- Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
This commit is contained in:
@@ -108,6 +108,15 @@ pub const KIND_EVENT_REMINDER: u32 = 30300;
|
||||
/// dedicated push lease tables.
|
||||
pub const KIND_PUSH_LEASE: u32 = 30350;
|
||||
|
||||
/// NIP-PMA: owner-encrypted private managed-agent aggregate.
|
||||
///
|
||||
/// Addressed by `(owner pubkey, kind, agent pubkey)`. The signed outer tags
|
||||
/// expose only the agent coordinate, CAS generation/predecessor, and active/deleted
|
||||
/// state required for relay enforcement. Content is NIP-44 v2 encrypted from
|
||||
/// the owner's key to itself and contains the runnable identity/configuration
|
||||
/// plus exact public projection bindings. See `docs/nips/NIP-PMA.md`.
|
||||
pub const KIND_PRIVATE_MANAGED_AGENT: u32 = 30179;
|
||||
|
||||
/// Kinds whose stored events are readable only by their author.
|
||||
///
|
||||
/// The relay must never reveal the existence, count, tags, content, schedule,
|
||||
@@ -117,7 +126,11 @@ pub const KIND_PUSH_LEASE: u32 = 30350;
|
||||
///
|
||||
/// Currently a tiny linear set. If this grows past ~4 kinds, convert to a
|
||||
/// compile-time bitset or sorted array with binary search for hot-path use.
|
||||
pub const AUTHOR_ONLY_KINDS: &[u32] = &[KIND_EVENT_REMINDER, KIND_PUSH_LEASE];
|
||||
pub const AUTHOR_ONLY_KINDS: &[u32] = &[
|
||||
KIND_EVENT_REMINDER,
|
||||
KIND_PUSH_LEASE,
|
||||
KIND_PRIVATE_MANAGED_AGENT,
|
||||
];
|
||||
|
||||
/// Kinds that require a result-level read gate beyond the filter-layer
|
||||
/// `#p` check: even a reader who knows an event id MUST match the event's
|
||||
@@ -643,6 +656,7 @@ pub const ALL_KINDS: &[u32] = &[
|
||||
KIND_TEAM,
|
||||
KIND_MANAGED_AGENT,
|
||||
KIND_TEAM_CATALOG,
|
||||
KIND_PRIVATE_MANAGED_AGENT,
|
||||
KIND_REPORT,
|
||||
KIND_PRODUCT_FEEDBACK,
|
||||
KIND_NIP29_PUT_USER,
|
||||
@@ -843,6 +857,7 @@ const _: () = assert!(is_parameterized_replaceable(KIND_PERSONA)); // 30175 ∈
|
||||
const _: () = assert!(is_parameterized_replaceable(KIND_TEAM)); // 30176 ∈ 30000–39999
|
||||
const _: () = assert!(is_parameterized_replaceable(KIND_MANAGED_AGENT)); // 30177 ∈ 30000–39999
|
||||
const _: () = assert!(is_parameterized_replaceable(KIND_TEAM_CATALOG)); // 30178 ∈ 30000–39999
|
||||
const _: () = assert!(is_parameterized_replaceable(KIND_PRIVATE_MANAGED_AGENT)); // 30179 ∈ 30000–39999
|
||||
const _: () = assert!(is_parameterized_replaceable(KIND_WORKFLOW_DEF)); // 30620 ∈ 30000–39999
|
||||
const _: () = assert!(is_parameterized_replaceable(KIND_EVENT_REMINDER)); // 30300 ∈ 30000–39999
|
||||
const _: () = assert!(is_parameterized_replaceable(KIND_DM_VISIBILITY)); // 30622 ∈ 30000–39999
|
||||
|
||||
@@ -32,6 +32,8 @@ pub mod observer;
|
||||
pub mod pairing;
|
||||
/// Presence status types shared across crates.
|
||||
pub mod presence;
|
||||
/// NIP-PMA owner-encrypted private managed-agent wire codec.
|
||||
pub mod private_managed_agent;
|
||||
/// Canonical relay runtime identities.
|
||||
pub mod relay;
|
||||
/// Tenant identity — the server-resolved community key carried on scoped paths.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3216,6 +3216,18 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn private_managed_agent_kind_remains_rejected_until_atomic_ingest_exists() {
|
||||
assert!(
|
||||
required_scope_for_kind(
|
||||
buzz_core::kind::KIND_PRIVATE_MANAGED_AGENT,
|
||||
&make_dummy_event(),
|
||||
)
|
||||
.is_err(),
|
||||
"kind 30179 must not enter generic EVENT ingest before privacy and aggregate CAS deploy"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ephemeral_kinds_not_in_scope_allowlist() {
|
||||
assert!(required_scope_for_kind(KIND_PRESENCE_UPDATE, &make_dummy_event()).is_err());
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
# NIP-PMA: Private Managed-Agent Aggregate
|
||||
|
||||
`draft` — protocol/codec reservation only. Relays MUST reject this kind until
|
||||
privacy, transactional CAS, backup/restore, revocation, and capability gates are
|
||||
deployed.
|
||||
|
||||
## Purpose and kind
|
||||
|
||||
Kind `30179` is an owner-authored, addressable, owner-readable aggregate for one
|
||||
runnable managed agent. Its coordinate is `(owner pubkey, 30179, agent pubkey)`.
|
||||
It is the only durable authority after a per-agent migration is independently
|
||||
verified. Kinds `30175` and `30177` remain public/compatibility projections.
|
||||
|
||||
This reservation does not change current agent authority, storage, startup,
|
||||
mutation, deletion, catalog, or sharing behavior.
|
||||
|
||||
## Signed outer envelope
|
||||
|
||||
Exactly these two-element tags are permitted:
|
||||
|
||||
- `d = <64 lowercase hex agent pubkey>` exactly once;
|
||||
- `g = <canonical positive decimal generation>` exactly once;
|
||||
- `prev = <64 lowercase hex predecessor event id>` exactly once after
|
||||
generation 1 and absent at generation 1;
|
||||
- `state = active|deleted` exactly once.
|
||||
|
||||
Content is bounded NIP-44 v2 ciphertext encrypted owner-to-owner. Event ID and
|
||||
signature, exact kind/author/tag grammar, canonical curve-valid agent keys, and size
|
||||
are validated before decrypt. The decrypted payload repeats owner, agent,
|
||||
generation, predecessor, and state; any mismatch is corruption.
|
||||
|
||||
## Decrypted v1 payload
|
||||
|
||||
Top-level and nested core schemas reject unknown and duplicate JSON member
|
||||
names. Forward-compatible data is confined to namespaced `extensions` entries;
|
||||
core semantics never depend on an extension. Projection recovery v1 contains
|
||||
the complete signed public event; validation verifies its signature and ID,
|
||||
owner, kind and `d` coordinate, and hashes its exact content bytes against the
|
||||
binding. This makes reconstruction deterministic rather than an agreement over
|
||||
an untyped JSON blob.
|
||||
|
||||
An active payload binds exact signed `30175` and `30177` event IDs, SHA-256 of
|
||||
their exact content bytes, and complete versioned recovery material. It also
|
||||
contains the preserved agent nsec and an optional NIP-OA attestation, plus
|
||||
explicitly allowlisted private runnable configuration. When present, the
|
||||
attestation MUST be a cryptographically valid unconditional (`conditions = ""`)
|
||||
owner-to-agent authorization: its owner equals the aggregate author and its
|
||||
agent equals the nsec-derived `d` coordinate. Conditional, malformed, wrong-owner,
|
||||
or wrong-agent attestations are rejected. The nsec MUST derive the `d`
|
||||
coordinate.
|
||||
|
||||
All active aggregates require a stable `30175` definition binding. Before a
|
||||
legacy definition-less agent can be encoded, the migrator MUST deterministically
|
||||
materialize its definition fields as a non-shared `30175` under the owner, with
|
||||
a stable collision-safe slug derived from the agent pubkey. Materialization and
|
||||
read-back verification are prerequisites: failure leaves the agent `LegacyOnly`
|
||||
and preserves its local record/key unchanged. No client may synthesize a default
|
||||
or mint a replacement identity to satisfy this schema.
|
||||
|
||||
A deleted payload is minimal: it contains no active body, advances generation
|
||||
from its predecessor, and includes `deleted_at`. Relay anti-resurrection and
|
||||
undelete rules are specified by the later transactional CAS contract; generic
|
||||
NIP-33 LWW is explicitly insufficient.
|
||||
|
||||
## Field authority
|
||||
|
||||
- `30175` definition projection: display name, prompt, runtime/model/provider,
|
||||
name pool, definition behavior defaults, sharing/provenance, public avatar.
|
||||
- `30177` instance projection: agent pubkey/name/definition linkage,
|
||||
parallelism, `respond_to`, and allowlist.
|
||||
- private portable canonical: nsec, auth tag, env, durable timeout/team fields,
|
||||
and secret-bearing backend configuration.
|
||||
- private but device-validated: relay URL, explicit command/args, backend remote
|
||||
identity, and any explicitly portable path/provider reference.
|
||||
- local device policy/derived: start-on-launch, auto-restart, effective binary
|
||||
paths, installed team directory, and catalog-derived commands.
|
||||
- legacy conversion only: create-time command/model/provider mirrors,
|
||||
deprecated MCP/turn timeout, source-version drift markers, and relay-mesh
|
||||
fallback markers where a definition is authoritative.
|
||||
- transient local only: PID and all last start/stop/exit/error receipts/logs.
|
||||
|
||||
Adding a `ManagedAgentRecord` field must update an exhaustive Desktop
|
||||
classification/conversion fixture before migration-writing code can merge.
|
||||
This inert core-only reservation does not yet depend on the Desktop type and
|
||||
therefore does not claim to provide that compile-time tripwire.
|
||||
|
||||
## Aggregate submission boundary
|
||||
|
||||
Three ordinary Nostr `EVENT` writes cannot atomically commit an aggregate. The
|
||||
future relay contract accepts independently signed projection candidates plus
|
||||
the signed private head through one authenticated aggregate submission and one
|
||||
PostgreSQL transaction. It validates CAS predecessor/generation, signatures,
|
||||
hashes, recovery material, definition revision, tombstone watermark, and all
|
||||
coordinates before exposing any candidate. Fan-out begins only after commit.
|
||||
|
||||
Public catalog definitions require an independently verifiable public
|
||||
CAS/revision head; browsing must never require decrypting kind `30179`.
|
||||
|
||||
## Required deployment order
|
||||
|
||||
1. this inert codec/kind reservation while ingest still rejects `30179`;
|
||||
2. author-only privacy gates, SQL visibility before `LIMIT`, and verification
|
||||
that the positive FTS allowlist continues to exclude `30179`;
|
||||
3. dark CAS schema/transaction;
|
||||
4. feature-gated aggregate submission;
|
||||
5. read/repair/export/import and destructive restore drill;
|
||||
6. tombstone revocation across authentication/ingest/session caches;
|
||||
7. owner rotation epoch/freeze/receipts/activation;
|
||||
8. Desktop reader and verified dual-write migration.
|
||||
|
||||
No phase may publish secrets before step 2 or retire local recovery evidence
|
||||
before the complete migration exit gate passes.
|
||||
Reference in New Issue
Block a user