mirror of
https://github.com/block/buzz.git
synced 2026-10-31 06:21:34 +00:00
An agent that holds no secret key still has to work: read its channels, answer a mention, react, keep its profile, address its memory, and create the agents it needs. This is the contract for asking a host to do those things — the interface half of #6467, with no host, no transport, and no signing. The open question in that RFC is per-operation versus a single "sign this" primitive. This answers per-operation. A sign-anything primitive tells the broker who is asking but not what for, so the only policies it can express are "all" and "none" — it is a signing oracle with a nicer name. Named operations let a host serve channel.read while refusing agents.create, and give a later policy or IFC layer a per-operation surface to attach to. The cost is that a new operation needs a new variant, which is the point: adding one becomes a reviewable change to the contract instead of another use of an existing blank cheque. For the same reason signing, publishing, and NIP-42/44/98 are not actions. They are mechanisms internal to whoever holds the key, and this contract never names them. Identity is a pubkey and nothing else. `PubkeyHex` has no counterpart here for the corresponding secret, every args type is `deny_unknown_fields`, and no outcome can structurally hold key material — agents.create returns a pubkey and a handle, never the minted nsec. A test scans the module's own source for secret-key tokens, because a deserialization test can only reject fields someone thought to add, while the scanner fails the moment such a type appears in a signature at all. Both halves were checked against deliberate mutations rather than assumed. The envelope carries no requester, owner, or scope. Those come from the session credential the host authenticated, so authority is never something a body can assert: a request that could name its own subject would let any caller act as anyone. Ownership of a created agent follows the same rule — the owner is the requester, implicitly, which is what makes an agent able to own the agents it creates while the chain still ends at a human. Bounding that chain's depth needs resources and policy this contract cannot see, so it stays a host concern. Reads are actions too, because #6467 asks for a deployment where the host is the only route to the relay. One `channel.read` covers channel, thread, and mention-feed scopes: they differ by filter, not by permission, and three names for one decision would be three policy knobs pretending to be independent. Its outcome is a projection rather than a signed event, since a caller with no relay cannot verify signatures anyway — trust here is trust in the host, stated rather than implied. `presence.set` and `typing.set` are left out of v1. They are housekeeping a host may decline regardless, and a closed enum makes adding them purely additive. Carried over from #6543: the envelope, requestId validation, the three-way Succeeded/Failed/Indeterminate result, the error codes, the retry-is-identical- bytes contract, and the agents.* argument shapes. Changed deliberately: pubkeys are a validated `PubkeyHex` instead of `String`, `capability` is renamed `action`, `CapabilityFailed` becomes `ActionFailed`, and `Unsupported` is new so a host can decline a best-effort operation without it reading as a fault. #6543 also lands in this module path; whichever merges second needs a deliberate reconcile, which I own. Addresses the interface half of #6467. Signed-off-by: ss-dev-00 <a02c4e0850e5e612b4ddf95dbe2f5c56467cf27c6552203bc833ff438fb31971@buzz.block.builderlab.xyz> Co-authored-by: Bradley Axen <baxen@squareup.com> Signed-off-by: Bradley Axen <baxen@squareup.com>