diff --git a/.github/workflows/ci-proto.yml b/.github/workflows/ci-proto.yml index 43eb0662c..678a889a6 100644 --- a/.github/workflows/ci-proto.yml +++ b/.github/workflows/ci-proto.yml @@ -73,6 +73,7 @@ jobs: breaking: runs-on: ubuntu-latest + if: ${{ !contains(github.event.pull_request.labels.*.name, 'buf:skip:breaking') }} steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 diff --git a/docs/adr/0036-agent-self-certifying-identity.md b/docs/adr/0036-agent-self-certifying-identity.md new file mode 100644 index 000000000..0b3079449 --- /dev/null +++ b/docs/adr/0036-agent-self-certifying-identity.md @@ -0,0 +1,133 @@ +--- +number: "0036" +slug: agent-self-certifying-identity +status: draft +date: 2026-07-26 +--- + +# ADR#0036: Agent Self-Certifying Cryptographic Identity + +## Context + +An agent needs an identity that is provable anywhere, by anyone, without a +central authority issuing or vouching for it. [ADR#0024](./0024-agent-platform-stream-topology.md) +and [ADR#0025](./0025-agent-definition-data-ownership.md) define the agent +record and its configuration but say nothing about how an agent is +*cryptographically* identified, nor how a consumer proves that an action came +from a given agent. Today the genesis `AgentProvisioned` event carries an +opaque `agent_id` string, `display_name`, `parent`, an `AgentConfiguration`, a +`revision`, and a `content_digest`. Nothing ties the agent to a verifying +key, so the registry +holds agents with no cryptographic anchor: an `agent_id` alone proves nothing. + +Two different things need verification, and they are independent: + +- **Configuration integrity**, "is this the same definition?", answered by + `content_digest` and already present. +- **Actor authenticity**, "did *this agent* produce this?", currently absent. + +The prevailing solution for globally provable identity without a central +allocator is *self-certifying identity*: the public key is the name. +[Nostr](https://github.com/block/buzz) (which the Buzz agent platform uses), +Bitcoin, and W3C DIDs all take this shape. Two properties fall out of it for +free: + +- **Global uniqueness**: a key from a 256-bit keyspace is unique with + overwhelming probability, generated by anyone with no coordinator. +- **Global verifiability**: anyone, offline, can verify a signature against + the public key; no phone-home to an issuer. + +The platform already has the verification machinery. +[ADR#0017](./0017-aauth-agent-authentication.md) binds an agent to a `cnf.jwk` +public key and verifies proof-of-possession in `trogon-aauth-verify`, and +[ADR#0026](./0026-command-authorization-principal.md) hands the decider a typed +`CommandPrincipal` *after* authorization runs, so `decide` never sees keys or +signatures. [ADR#0023](./0023-secret-management-and-key-custody-direction.md) +and [ADR#0033](./0033-two-tier-key-custody-product-model.md) keep private key +custody on the security plane. What is missing is the *binding at +registration*: recording the agent's public identity on the genesis event, and +requiring the registrant to prove control of the key it claims. + +## Decision + +An agent's identity is a **self-certifying keypair: the public key is the +name.** + +1. **Self-certifying identifier.** The agent's identity is derived from its + root public key (a DID / JWK-thumbprint form, aligned with AAuth's + `agent_jkt`; the concrete algorithm, identifier method, and signature + format are pinned in [ADR#0038](./0038-agent-identity-crypto-suite.md)), + not an opaque allocator-assigned string. Global uniqueness and global + verifiability are therefore intrinsic, with no central registry. + +2. **One root key in v1.** A single root identity key, which is also the + signing key. A two-tier hierarchy (an offline root key that only authorizes + rotation, plus rotatable operational signing keys) is deferred until key + operations justify it; it is a strictly additive layer that does not change + the genesis anchor. + +3. **Bound at registration by proof-of-possession.** Provisioning carries the + agent's public key and a signature proving the registrant controls the + corresponding private key. That proof is verified on the AAuth plane + ([ADR#0017](./0017-aauth-agent-authentication.md)) before `decide` runs + ([ADR#0026](./0026-command-authorization-principal.md)); the genesis event + records the **public** key as the agent's immutable identity anchor. No + private key material ever enters a command, event, or `AgentConfiguration`. + +4. **Public identity is a genesis fact; secrets are not.** The public key is + agent-owned identity and belongs on the agent record. Private keys and + custody remain on the security plane + ([ADR#0023](./0023-secret-management-and-key-custody-direction.md), + [ADR#0033](./0033-two-tier-key-custody-product-model.md)). This is distinct + from the provisioning *actor* ("who ran the command"), which is envelope + metadata, not agent identity. + +### Deferred layers + +These do not change the genesis anchor and are added when needed: + +- **Entity attestation**: binding the key to a real-world entity ("this is + Acme's reviewer agent") via verifiable credentials, a web of trust, or + reputation accrued in the signed history. The key proves control, not + affiliation. +- **Global resolution**: publishing the identifier-to-current-key mapping to a + replicated substrate (relays, a DID method, a transparency log) so parties + outside this system can resolve it. Until then the agent's event stream is + the authoritative key record. +- **Key rotation**: a later signed event authorized by the current key, with + the identifier held stable by the resolution layer above. Pure self-certifying + identifiers are immutable, so rotation and resolution arrive together. + +## Consequences + +- An agent is a first-class cryptographic principal: its identity proves itself + anywhere, offline, without a central authority, and actions attributed to it + are verifiable against the key anchored at genesis. +- The genesis contract gains the agent's public identity key; `agent_id` becomes + a self-certifying identifier rather than an opaque string. Verification reuses + the single AAuth path [ADR#0038](./0038-agent-identity-crypto-suite.md) fixes + rather than a parallel mechanism. +- The governance half of the picture, decentralized verification under governed + authority (admission, revocation, tenancy, attestation), is recorded in + [ADR#0037](./0037-agent-identity-governance.md); how the anchored identity + extends to command and event authorship is recorded in + [ADR#0039](./0039-self-authenticating-event-provenance.md). +- Global uniqueness and verifiability are obtained by construction; the harder, + ongoing work (entity attestation, global resolution, rotation) is isolated in + deferred layers that leave the anchor unchanged. +- Choosing self-certifying identity now forecloses an allocator-assigned opaque + identity later without a migration, which is the intended trade. + +## References + +- [ADR#0017: AAuth Agent Authentication over a Trogon NATS PoP Binding](./0017-aauth-agent-authentication.md) +- [ADR#0023: Secret Management and Key Custody Direction](./0023-secret-management-and-key-custody-direction.md) +- [ADR#0024: Agent Platform Stream Topology](./0024-agent-platform-stream-topology.md) +- [ADR#0025: Agent Definition Data Ownership](./0025-agent-definition-data-ownership.md) +- [ADR#0026: Command Authorization Principal and Authorizer Hook for Decider Execution](./0026-command-authorization-principal.md) +- [ADR#0033: Two-Tier Key Custody Product Model](./0033-two-tier-key-custody-product-model.md) +- [ADR#0037: Agent Identity Governance: Decentralized Verification under Governed Authority](./0037-agent-identity-governance.md) +- [ADR#0038: Agent Identity Cryptographic Suite and Crypto-Agility](./0038-agent-identity-crypto-suite.md) +- [ADR#0039: Self-Authenticating Event Provenance](./0039-self-authenticating-event-provenance.md) +- [Buzz: Nostr-based agent identity](https://github.com/block/buzz) +- [ADR index](./index.md) diff --git a/docs/adr/0037-agent-identity-governance.md b/docs/adr/0037-agent-identity-governance.md new file mode 100644 index 000000000..a668a6ede --- /dev/null +++ b/docs/adr/0037-agent-identity-governance.md @@ -0,0 +1,203 @@ +--- +number: "0037" +slug: agent-identity-governance +status: draft +date: 2026-07-27 +--- + +# ADR#0037: Agent Identity Governance: Decentralized Verification under Governed Authority + +## Context + +[ADR#0036](./0036-agent-self-certifying-identity.md) adopts a self-certifying +keypair as the [agent](../glossary/agent)'s name, citing +[Nostr](https://github.com/block/buzz), and the Buzz agent platform built on +it, as the reference model: the public key is the identifier, generated by +anyone, verified by anyone, with no coordinator. + +Nostr is not one idea, it is two, and they are separable: + +1. **Self-certifying cryptographic identity**, a technical property. A key + proves itself offline, by anyone, anywhere: no phone-home to an issuer, no + shared secret, no pre-registration. It is unique by construction (a + 256-bit keyspace collision is not a practical concern) and portable across + trust boundaries, because verifying it never depends on which party is + asking. +2. **Permissionless decentralization**, a governance stance. Anyone mints a + key with no admission gate. There is no adopted revocation: a key, once + minted, cannot be un-minted, only abandoned. The namespace is flat and + global, + with no [tenant](../glossary/tenant) or organizational boundary above the + key. Custody is personal, the holder of the private key is the only + party who can act as that identity. Trust accrues only as reputation, + built up over a signed history that other parties choose to weight. + +[ADR#0036](./0036-agent-self-certifying-identity.md) adopts the first without +saying anything about the second, and this platform's existing decisions +already reject every element of it. +[ADR#0026](./0026-command-authorization-principal.md) defines a +`CommandAuthorizer` hook for decider execution, opt-in per call site and +defaulting to allow-all, so a caller that adopts it can refuse a command +whose principal is merely nameable. +[ADR#0028](./0028-decider-admission-control-and-backpressure.md) gates +execution itself behind an admission limiter. +[ADR#0027](./0027-decider-multi-tenancy-primitive.md) scopes every stream and +snapshot to a validated `Tenant`, closing exactly the flat-global-namespace +shape a permissionless key space assumes. +[ADR#0023](./0023-secret-management-and-key-custody-direction.md) and +[ADR#0033](./0033-two-tier-key-custody-product-model.md) put private key +custody behind a platform secrets service and a two-tier managed or +customer-managed product model, not in the sole personal custody of whoever +generated the key. And the platform carries an enterprise obligation Nostr +structurally cannot express at all: to offboard an agent and revoke its +standing, on demand, in a way other parties can check. A Nostr key cannot be +un-existed; there is no adopted protocol message that makes a `npub` stop +being valid. + +The open question [ADR#0036](./0036-agent-self-certifying-identity.md) leaves +unanswered is which half of the Nostr model this platform actually adopts. +Left unanswered, "self-certifying identity" reads as tacit endorsement of the +permissionless governance it is usually bundled with. This ADR answers the +question directly. + +## Decision + +### 1. Verification and authority are different planes + +The platform adopts the self-certifying property and rejects permissionless +decentralization. It keeps the technical property, an identity that proves +itself, and replaces the governance stance with the one this platform already +runs: gated admission, scoped tenancy, managed custody, and revocable +standing. Verification, "is this signature valid for this key", and +authority, "may this key currently act", are answered on two different +planes, and nothing in this decision lets one plane answer for the other. + +### 2. Verification stays decentralized + +Any party verifies an agent's signature offline against the public key +anchored at the agent's genesis event +([ADR#0036](./0036-agent-self-certifying-identity.md)). No central service +sits in the verification path: a verifier needs the signed payload and the +anchored key, nothing else, and reaches the same answer regardless of who is +asking or which party operates the platform. This is the distributed-computing +property this ADR keeps from Nostr's model, unmodified: it is what makes an +agent's identity portable across trust boundaries in the first place. + +### 3. Authority is a governed plane above verification + +Authority decides who may act. It never decides how a signature is checked, +and it is never consulted to answer "is this signature valid", only "is this +key currently authorized". Four concerns live on this plane: + +- **Admission.** Registering an agent's identity is gated, not open to + anyone who generates a key. The `CommandAuthorizer` hook of + [ADR#0026](./0026-command-authorization-principal.md) and the admission + limiter of [ADR#0028](./0028-decider-admission-control-and-backpressure.md) + provide the enforcement points; the authorizer is opt-in per call site, so + the registration path must wire one deliberately rather than inherit the + allow-all default. Registration itself requires the registrant to prove + possession of the private key it claims, per + [ADR#0017](./0017-aauth-agent-authentication.md) and + [ADR#0036](./0036-agent-self-certifying-identity.md). A key that can sign + is not, by that fact alone, a key the platform has admitted. +- **Revocation.** An agent's authorization is expressed as a published, + cryptographically checkable status: a revocation event on the agent's own + stream, a status-list artifact, or both. This is the same shape the X.509 + world uses for CRLs and OCSP and the shape the W3C verifiable-credentials + world uses for status lists: the signature check stays offline and + unchanged, and currency is a separate, explicit lookup. A verifier that + only checks the signature and never consults status will accept a + correctly signed message from an agent the platform has already offboarded. + "The signature is valid" and "the agent is currently authorized" are + distinct questions, and this decision requires that they never be + conflated into one check. +- **Tenancy.** Identity scoping follows the `Tenant` value object of + [ADR#0027](./0027-decider-multi-tenancy-primitive.md). A self-certifying + key is globally unique by construction, but which tenant an agent belongs + to, and which streams and snapshots it may resolve against, remains a + tenancy fact enforced at the store boundary, not something the key + namespace itself expresses or limits. +- **Entity attestation.** Binding a key to a real-world claim, "this key is + Acme's reviewer agent", is asserted by credentials issued on this + authority plane. [ADR#0036](./0036-agent-self-certifying-identity.md) + already defers this layer; it stays deferred here. The key proves control + of itself, never affiliation, and affiliation is an authority-plane + credential, not a property of the key. + +### 4. This is a model, not a protocol adoption + +The platform adopts the self-certifying shape, not Nostr's protocol stack. +`secp256k1`/Schnorr keys, the NIP-01 event format, NIP-42 and NIP-98 +authentication, and relays are not part of this decision. The property is +realized instead over DIDs, [JWS](https://www.rfc-editor.org/rfc/rfc7515) +signatures, and the AAuth plane this platform already runs +([ADR#0017](./0017-aauth-agent-authentication.md)). The concrete +cryptographic suite, key formats, and signature algorithms are pinned in +[ADR#0038](./0038-agent-identity-crypto-suite.md); how a signature attaches +provenance to an event is pinned in +[ADR#0039](./0039-self-authenticating-event-provenance.md). Neither of those +decisions is this one: this ADR fixes which governance model applies, not +which bytes get signed. + +### 5. Nostr interoperability is preserved, one path is foreclosed + +An agent's identity may bind more than one key, via the stream-attested key +binding [ADR#0038](./0038-agent-identity-crypto-suite.md) defines, so nothing +here prevents adding a `secp256k1` key later, alongside the root key, and +fronting it with a bridge service that speaks Nostr's relay protocol, the +same translation role `a2a-bridge` already plays for the A2A protocol: decode +on one side, re-sign or re-frame, and carry the result over this platform's +own transport on the other. That path stays open and requires no change to +the identity anchor decided here. + +What this decision forecloses deliberately is Nostr-native root identity, an +agent whose identifier is literally an `npub`. That would require pinning +`secp256k1` as the root key algorithm from genesis, which trades away the +algorithm agility [ADR#0038](./0038-agent-identity-crypto-suite.md)'s +conditional profiles are meant to preserve. The trade is declined: Nostr +reachability is an optional bridge added later, not the root identity itself. + +## Consequences + +- Verifiers never depend on a central service to check an agent's signature, + but every verifier that cares about current standing, not just past + authenticity, must add a status lookup the pure Nostr model has no + equivalent for. The platform keeps the power to offboard and revoke without + ever becoming the party that has to sit in the verification path. +- There is no relay to run, no NIP surface to implement or keep pinned, and + no flat global namespace to defend against; the governed-authority plane + reuses machinery ([ADR#0017](./0017-aauth-agent-authentication.md), + [ADR#0026](./0026-command-authorization-principal.md), + [ADR#0027](./0027-decider-multi-tenancy-primitive.md), + [ADR#0028](./0028-decider-admission-control-and-backpressure.md)) this + platform already runs for other reasons. +- The hybrid stance is now explicit and citable: "this platform has + self-certifying agent identity" no longer implies, or has to be defended + against implying, "this platform has permissionless agent identity." + Anyone reading [ADR#0036](./0036-agent-self-certifying-identity.md) in + isolation is pointed here for the governance half of the picture. +- A revocation check becomes a required step every verifier must implement, + not an optional hardening; a verifier that skips it is not wrong about the + signature, it is answering a question ("was this ever valid") the caller + did not ask ("is this valid now"). + +## References + +- [ADR#0017: AAuth Agent Authentication over a Trogon NATS PoP Binding](./0017-aauth-agent-authentication.md) +- [ADR#0023: Secret Management and Key Custody on OpenBao behind a Platform Secrets Service](./0023-secret-management-and-key-custody-direction.md) +- [ADR#0026: Command Authorization Principal and Authorizer Hook for Decider Execution](./0026-command-authorization-principal.md) +- [ADR#0027: Tenant Value Object for Decider Stream and Snapshot Resolution](./0027-decider-multi-tenancy-primitive.md) +- [ADR#0028: Admission Control for Decider Command Execution](./0028-decider-admission-control-and-backpressure.md) +- [ADR#0033: Two-Tier Key Custody Product Model](./0033-two-tier-key-custody-product-model.md) +- [ADR#0036: Agent Self-Certifying Cryptographic Identity](./0036-agent-self-certifying-identity.md) +- [ADR#0038: Agent Identity Cryptographic Suite and Crypto-Agility](./0038-agent-identity-crypto-suite.md) +- [ADR#0039: Self-Authenticating Event Provenance](./0039-self-authenticating-event-provenance.md) +- [Buzz: Nostr-based agent identity](https://github.com/block/buzz) +- [Nostr Implementation Possibilities (NIPs)](https://github.com/nostr-protocol/nips) +- [BIP-340: Schnorr Signatures for secp256k1](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki) +- [W3C Decentralized Identifiers (DIDs) v1.0](https://www.w3.org/TR/did-core/) +- [RFC 7515: JSON Web Signature (JWS)](https://www.rfc-editor.org/rfc/rfc7515) +- [RFC 5280: Internet X.509 Public Key Infrastructure Certificate and CRL Profile](https://www.rfc-editor.org/rfc/rfc5280) +- [RFC 6960: X.509 Internet PKI Online Certificate Status Protocol (OCSP)](https://www.rfc-editor.org/rfc/rfc6960) +- [W3C Bitstring Status List v1.0](https://www.w3.org/TR/vc-bitstring-status-list/) +- [ADR index](./index.md) diff --git a/docs/adr/0038-agent-identity-crypto-suite.md b/docs/adr/0038-agent-identity-crypto-suite.md new file mode 100644 index 000000000..7ba3bc804 --- /dev/null +++ b/docs/adr/0038-agent-identity-crypto-suite.md @@ -0,0 +1,211 @@ +--- +number: "0038" +slug: agent-identity-crypto-suite +status: draft +date: 2026-07-27 +--- + +# ADR#0038: Agent Identity Cryptographic Suite and Crypto-Agility + +## Context + +[ADR#0036](./0036-agent-self-certifying-identity.md) anchors an agent's +identity to its root public key, in a DID or JWK-thumbprint form, and leaves +the algorithm, the identifier method, and the signature format unfixed. +[ADR#0037](./0037-agent-identity-governance.md) requires decentralized +verification under governed authority: any party must be able to check a +signature against the anchored key without asking a central issuer, while +governance decides which keys a verifier trusts. This ADR extends that +governed stance from keys to algorithms, through the verifier allowlist of +Decision 3. Neither prior decision names a curve, a key encoding, or a +signature envelope, and an agent identity anchor cannot be implemented +without picking all three. + +The platform already has running parts of an answer. +[ADR#0017](./0017-aauth-agent-authentication.md) binds an agent to a +`cnf.jwk` public key and verifies proof of possession in +`trogon-aauth-verify`; the claim set is JOSE/JWK throughout. NATS NKeys, the +identity primitive the mesh's transport layer already uses, are Ed25519 +keys. Picking a suite that ignores this stack would mean a second +verification path alongside the one AAuth already runs. + +The risk this ADR manages is narrower than "which curve is best": it is +hardcoding one algorithm with no migration story. Every asymmetric signature +scheme has a shelf life, whether from a classical cryptanalytic break or from +a cryptographically relevant quantum computer running Shor's algorithm. An +identity format that bakes in one algorithm with no path to add or replace it +turns that eventual break into an identity redesign, forcing every agent to +be re-anchored under a new scheme with no continuity from the old one. This +ADR fixes the default suite for agent identity keys and signatures, and fixes +crypto-agility as a requirement of the format itself, so that a break is +survived by rotation rather than by another architecture decision. + +## Decision + +### 1. Default suite: Ed25519, JWK, did:key, JWS + +The root identity key defined in [ADR#0036](./0036-agent-self-certifying-identity.md) +is an **Ed25519** key (EdDSA, [RFC 8032](https://www.rfc-editor.org/rfc/rfc8032)). +It is carried as a **JWK** with `kty: OKP`, `crv: Ed25519`, `alg: EdDSA`, per +[RFC 8037](https://www.rfc-editor.org/rfc/rfc8037), and verified on the +existing AAuth path ([ADR#0017](./0017-aauth-agent-authentication.md)): +`agent_jkt` is the [RFC 7638](https://www.rfc-editor.org/rfc/rfc7638) JWK +thumbprint of that same key, so identity and AAuth's proof-of-possession +binding derive from one key encoding, not two. + +The identifier form is `did:key`. Its multicodec prefix self-describes the +curve, so an Ed25519 key always yields an identifier beginning `did:key:z6Mk`; +the identifier is never ambiguous about which algorithm produced it, and +resolving it never requires a lookup, only decoding the prefix. + +Signatures are carried as [JWS](https://www.rfc-editor.org/rfc/rfc7515) +(RFC 7515). Where the signed payload already lives elsewhere, such as an +event body recorded separately from its provenance envelope +([ADR#0039](./0039-self-authenticating-event-provenance.md)), the signature +is a detached JWS: the protected header and signature travel without a copy +of the payload the header commits to. + +### 2. Why Ed25519 over the alternatives + +- **Misuse resistance.** EdDSA derives its per-signature nonce + deterministically inside the scheme itself, so no caller can supply a weak + or repeated nonce. This removes the failure class that has produced real + key-recovery incidents against randomized ECDSA: the 2010 Sony PlayStation + 3 firmware signing key, recovered because the signer reused a constant + nonce across signatures, and the 2013 Android `SecureRandom` weakness that + exposed Bitcoin wallet private keys derived from ECDSA signatures with + correlated nonces. [RFC 6979](https://www.rfc-editor.org/rfc/rfc6979) + retrofits deterministic nonce derivation onto ECDSA, and + [BIP-340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki) + Schnorr specifies a default nonce derivation, but no scheme's verifier can + see how a signer derived a nonce; the structural difference is the signing + interface. EdDSA's signing path takes no caller-supplied nonce and no + per-signature randomness, so there is nothing for an implementation to get + wrong, whereas deployed randomized ECDSA demands fresh randomness on every + signature and fails catastrophically when it is weak. The critique above + therefore targets classic randomized ECDSA as deployed, not the secp256k1 + curve itself. +- **Parameter provenance.** Curve25519's parameters derive from a published, + rigid generation procedure with every constant explained. NIST P-256 carries + an unexplained-seed provenance question that Curve25519 does not, because + the seed used to generate its curve constants has never been shown to + derive from a documented, reproducible process. +- **Deployment maturity.** Ed25519 is a default or supported signing scheme + in OpenSSH and TLS 1.3, and Signal signs with the closely related XEdDSA + construction over the same curve, so the platform is adopting an + already-hardened, widely reviewed implementation surface rather than a + novel one. +- **Stack fit.** NATS NKeys are Ed25519 and AAuth's claim sets are JOSE/JWK, + so this default keeps one verification path end to end instead of adding a + second key type and a second verifier for identity alone. + +### 3. Crypto-agility is a first-class requirement + +Crypto-agility is not deferred as future work; it is a property the format +enforces from the start. Every persisted key and signature is self-describing: +the JWK `kty`/`crv`/`alg` fields, the JWS protected `alg` header, and the +`did:key` multicodec prefix all name their own algorithm inline. Verifiers +enforce an explicit algorithm allowlist rather than accepting whatever `alg` +a message claims. + +An agent's identity may bind more than one key, and the binding mechanism +must be named, because plain `did:key` cannot express it: a `did:key` +document is derived entirely from the single key encoded in the identifier. +An additional key is bound by a signed event on the agent's own stream, +authorized by the root key, which any holder of the stream can verify +without an external registry; the stream is the authoritative key record, as +[ADR#0036](./0036-agent-self-certifying-identity.md) already establishes. +The `did:key` identifier therefore names the root anchor only, and adding a +second algorithm is additive, never a replacement of the identifier. +Publishing that key record beyond the stream remains the deferred global +resolution layer of [ADR#0036](./0036-agent-self-certifying-identity.md), +and rotation of the root key itself, which needs that resolution layer to +hold the identifier stable, remains deferred with it. The rotation this ADR +speaks of is algorithm rotation under an unchanged root: rebinding keys and +retiring algorithms from the verifier allowlist, not replacing the root +identity key. + +The format itself never constrains which algorithms may exist; it only makes +every key and signature name its own algorithm so a verifier's policy, not +the wire format, decides which algorithms are accepted at a given time. That +verifier-side allowlist, not any single chosen curve, is the durable security +decision this ADR makes: it is what lets a broken algorithm be survived by +rebinding a new key and rotating verifier policy rather than by redesigning +the identity format itself. + +### 4. Conditional profiles, added as bound keys, never as root replacements + +Two further curves are recorded as conditional profiles, each with the +trigger that would justify adopting it. Either arrives as an additional key +bound to the agent's identity through the stream-attested binding of +Decision 3, never as a replacement for the Ed25519 root: + +- **P-256 (ES256)**, carried in [COSE](https://www.rfc-editor.org/rfc/rfc9052) + ([RFC 9052](https://www.rfc-editor.org/rfc/rfc9052) and + [RFC 9053](https://www.rfc-editor.org/rfc/rfc9053)) where a compact binary + envelope fits better than JOSE's JSON encoding, if hardware-backed custody + or WebAuthn-class attestation drives the requirement. FIPS 186-5 approves + EdDSA at the algorithm level, so the differentiator is not FIPS approval in + the abstract but the FIPS 140-validated module and hardware fleet: HSMs, + TPMs, secure enclaves, and FIDO2 authenticators overwhelmingly speak P-256 + today. A custody requirement that lands on one of those roots is the + trigger for this profile, not a general preference for it. +- **secp256k1 with BIP-340 Schnorr**, only if Nostr-native or blockchain-native + interop becomes a requirement. Whether that requirement should be taken on + is a governance question, recorded as a stance in + [ADR#0037](./0037-agent-identity-governance.md), not a decision this ADR + makes on its own. + +### 5. Post-quantum path + +Every elliptic-curve scheme named above, Ed25519, P-256, and secp256k1 alike, +falls to Shor's algorithm on a cryptographically relevant quantum computer; +none of them is quantum-resistant by construction. The recorded upgrade path +is hybrid signing: Ed25519 plus [ML-DSA](https://csrc.nist.gov/pubs/fips/204/final) +(FIPS 204), added as an additional tagged key under Decision 3 once JOSE, +COSE, and DID conventions for representing ML-DSA keys and signatures mature. +ML-DSA keys and signatures are kilobyte-scale, roughly forty to eighty times +larger than Ed25519's 32-byte keys and 64-byte signatures, which makes +adopting it before those conventions stabilize and before a concrete threat +justifies the cost premature. Migration to the hybrid suite is +therefore planned work, tracked against maturing standards and an assessed +threat timeline, not an emergency response to an already-broken algorithm. + +## Consequences + +- There is one verification path today: EdDSA signatures over Ed25519 keys, + verified on the existing AAuth plane ([ADR#0017](./0017-aauth-agent-authentication.md)). + Adding a curve later, whether P-256, secp256k1, or a post-quantum scheme, is + a verifier policy change plus a key binding on an existing agent identity, + not a redesign of the identity format or a re-anchoring of existing agents. +- Curve-fixed identifier forms, such as a Nostr `npub`-style encoding that + presumes one fixed curve in the identifier itself, are rejected. Identifiers + and keys in this platform are always algorithm-tagged, so an identifier + never has to be reinterpreted if the algorithm it names is later retired. +- Private key custody is unaffected by this ADR and remains on the security + plane defined by [ADR#0023](./0023-secret-management-and-key-custody-direction.md) + and [ADR#0033](./0033-two-tier-key-custody-product-model.md). This ADR + governs public-key algorithms, encodings, and signature formats only; it + says nothing about where or how the corresponding private keys are stored + or protected. + +## References + +- [ADR#0017: AAuth Agent Authentication over a Trogon NATS PoP Binding](./0017-aauth-agent-authentication.md) +- [ADR#0023: Secret Management and Key Custody on OpenBao behind a Platform Secrets Service](./0023-secret-management-and-key-custody-direction.md) +- [ADR#0033: Two-Tier Key Custody Product Model](./0033-two-tier-key-custody-product-model.md) +- [ADR#0036: Agent Self-Certifying Cryptographic Identity](./0036-agent-self-certifying-identity.md) +- [ADR#0037: Agent Identity Governance](./0037-agent-identity-governance.md) +- [ADR#0039: Self-Authenticating Event Provenance](./0039-self-authenticating-event-provenance.md) +- [RFC 6979: Deterministic Usage of DSA and ECDSA](https://www.rfc-editor.org/rfc/rfc6979) +- [RFC 7515: JSON Web Signature (JWS)](https://www.rfc-editor.org/rfc/rfc7515) +- [RFC 7638: JSON Web Key (JWK) Thumbprint](https://www.rfc-editor.org/rfc/rfc7638) +- [RFC 8032: Edwards-Curve Digital Signature Algorithm (EdDSA)](https://www.rfc-editor.org/rfc/rfc8032) +- [RFC 8037: CFRG Elliptic Curve Diffie-Hellman and Signatures in JOSE](https://www.rfc-editor.org/rfc/rfc8037) +- [RFC 9052: CBOR Object Signing and Encryption (COSE): Structures and Process](https://www.rfc-editor.org/rfc/rfc9052) +- [RFC 9053: CBOR Object Signing and Encryption (COSE): Initial Algorithms](https://www.rfc-editor.org/rfc/rfc9053) +- [NIST FIPS 204: Module-Lattice-Based Digital Signature Standard (ML-DSA)](https://csrc.nist.gov/pubs/fips/204/final) +- [BIP-340: Schnorr Signatures for secp256k1](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki) +- [did:key Method Specification (W3C Credentials Community Group draft)](https://w3c-ccg.github.io/did-method-key/) +- [Buzz: Nostr-based agent identity](https://github.com/block/buzz) +- [ADR index](./index.md) diff --git a/docs/adr/0039-self-authenticating-event-provenance.md b/docs/adr/0039-self-authenticating-event-provenance.md new file mode 100644 index 000000000..78afe0150 --- /dev/null +++ b/docs/adr/0039-self-authenticating-event-provenance.md @@ -0,0 +1,167 @@ +--- +number: "0039" +slug: self-authenticating-event-provenance +status: draft +date: 2026-07-27 +--- + +# ADR#0039: Self-Authenticating Event Provenance + +## Context + +Authenticity is checked once, at the edge. AAuth proof-of-possession +([ADR#0017](./0017-aauth-agent-authentication.md)) authenticates the calling +agent, and the authorizer hook hands `decide` a typed `CommandPrincipal` +before it runs ([ADR#0026](./0026-command-authorization-principal.md)). Once +`decide` returns, the resulting events are appended with no actor signature of +their own. Event headers are freeform envelope metadata, and the runtime +deliberately does not derive them: an application that wants a fixed header +builds it itself before execution +([ADR#0026](./0026-command-authorization-principal.md), citing +[Event Metadata](../architecture/event-metadata.md)). + +The consequence is that an event copied out of the store carries no proof of +who authored it. Its trustworthiness rests entirely on the append path: a +reader trusts an event because they trust the service that wrote it, not +because the event itself proves anything. Proof does not travel with the +data. Move the event to cold storage, replicate it into another organization, +or hand it to an auditor with no access to the original store, and the +authorship claim goes with it. + +The property this platform wants is the one +[ADR#0036](./0036-agent-self-certifying-identity.md) and +[ADR#0037](./0037-agent-identity-governance.md) already establish for +identity: the Nostr-shaped reference model, where every artifact is +self-authenticating and can be verified by anyone, offline, without a +central authority. Applied to events, the goal is that every event is +self-authenticating and can move between stores, streams, or organizations +without losing proof of authorship. + +The [command](../glossary/command)/[event](../glossary/event) split of CQRS +changes where the signature has to attach. In Nostr the actor signs the +event directly: there is exactly one artifact, and the signature covers it. +Here an agent authors a command, an expression of intent, and a +deterministic [decider](../glossary/decider) derives the events, the +outcome, from that command and the prior stream. There is no single artifact +for the actor to sign after the fact, because the actor never produces the +event. Authorship proof therefore has to attach to the command, and the +events that command produces have to carry that proof forward. + +## Decision + +### 1. The agent signs the exact command bytes + +The agent submitting a command signs the exact serialized command bytes with +its identity key ([ADR#0036](./0036-agent-self-certifying-identity.md)), +under the suite [ADR#0038](./0038-agent-identity-crypto-suite.md) fixes: an +Ed25519 default, with conditional P-256 and +[BIP-340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki) +Schnorr profiles and a future hybrid +[FIPS 204](https://csrc.nist.gov/pubs/fips/204/final) ML-DSA path recorded +alongside it. This ADR takes no position on which algorithm is in force at +a given time; it only fixes that the command bytes are what gets signed. The +signature is a detached JWS ([RFC 7515](https://www.rfc-editor.org/rfc/rfc7515)): +the protected header carries `alg` and a `kid` that references the agent's +[did:key](https://w3c-ccg.github.io/did-method-key/) identifier, and the +payload is detached because the signed material is the command bytes already +on the wire, not a copy re-embedded in the signature envelope. + +### 2. The runtime persists provenance as envelope headers, never payload fields + +The runtime persists provenance on every event emitted by that command's +execution, and it does so as envelope headers, never as payload fields: the +detached JWS, the signer's DID/`kid`, and a digest of the signed command +bytes. The exact command bytes are preserved, either inline in the stream or +content-addressed alongside it, so any party holding an event can retrieve +the bytes the signature covers and re-verify later, without depending on the +store that first appended it. + +### 3. No re-serialization: sign and store the same bytes + +Signing the exact bytes and storing those same bytes deliberately sidesteps +[protobuf](../glossary/protocol-buffers)'s non-canonical encoding. Nothing is +ever re-serialized in order to verify a signature: the verifier checks the +signature against the identical byte string the signer produced and the +runtime stored. Canonicalization would only be needed by a consumer that +decodes the command into a typed message and then re-serializes it to +reconstruct the signed bytes; this design never asks a consumer to do that, +so the canonicalization problem does not arise. + +### 4. Signing stays at key-holding boundaries; the decider stays keyless + +Signing happens only where a private key can legitimately live: the agent or +client itself, or a custody-plane signer +([ADR#0023](./0023-secret-management-and-key-custody-direction.md), +[ADR#0033](./0033-two-tier-key-custody-product-model.md)). The +[decider](../glossary/decider), native and [WASM](../glossary/wasm) alike, +remains keyless and signature-unaware: it never sees a private key, never +verifies a signature, and never branches on one. This is the same boundary +[ADR#0026](./0026-command-authorization-principal.md) already draws for +identity generally, keeping it out of `decide`. No signature logic enters +domain payloads or `AgentConfiguration` +([ADR#0025](./0025-agent-definition-data-ownership.md)). + +### 5. Scope: author-attributable events only + +Provenance applies to author-attributable events, the ones produced by a +command a specific agent submitted. System-derived events, timers, +projections, and reconciliation, and any other event the runtime produces +without an authoring command, carry no provenance header. This keeps hot +internal paths cheap: nothing forces a signature-verification cost onto a +path that never had an authoring principal to prove. + +### 6. Outcome verification, beyond authorship + +Authorship is not the only thing a signed command buys. Because the decider +is deterministic, a third party holding the signed command, the prior events +in the stream, and the exact decider build can re-execute `decide` and +confirm that the recorded events follow from that command under the rules +the decider encodes. This is strictly stronger than the reference model: +Nostr's signature proves only who authored a blob, never that the blob is +the correct consequence of anything. Here the same signed artifact anchors +both checks, who submitted it, and whether what followed from it is +correct. + +### 7. Status currency composes with, but does not replace, provenance + +A provenance signature proves authorship at write time: this agent, holding +this key, produced this command, at this moment. It says nothing about +whether that key is still trusted at read time. Whether the signing key has +since been revoked is answered by the status mechanisms of +[ADR#0037](./0037-agent-identity-governance.md). The two checks compose and +neither substitutes for the other: a valid signature over a revoked key +still proves authorship at the time of signing, and a status check without a +signature proves nothing about who acted. + +## Consequences + +- The audit trail becomes portable and trustless: events move across + streams, cold storage, or organizations with their proof of authorship + intact, instead of that trust resting on the append path that first wrote + them. +- The cost is real: one signature per command, the command bytes themselves + now need to be stored somewhere reachable, and a provenance header schema + still needs to be defined. That schema lands alongside the deferred + command layer this ADR does not build; this ADR records the direction, + not the wire shape. +- Verification reuses the single AAuth/JOSE path + [ADR#0038](./0038-agent-identity-crypto-suite.md) fixes; this ADR adds no + parallel signature stack to build, operate, or key separately. + +## References + +- [ADR#0017: AAuth Agent Authentication over a Trogon NATS PoP Binding](./0017-aauth-agent-authentication.md) +- [ADR#0023: Secret Management and Key Custody on OpenBao behind a Platform Secrets Service](./0023-secret-management-and-key-custody-direction.md) +- [ADR#0025: Agent Definition Data Ownership](./0025-agent-definition-data-ownership.md) +- [ADR#0026: Command Authorization Principal and Authorizer Hook for Decider Execution](./0026-command-authorization-principal.md) +- [ADR#0033: Two-Tier Key Custody Product Model](./0033-two-tier-key-custody-product-model.md) +- [ADR#0036: Agent Self-Certifying Cryptographic Identity](./0036-agent-self-certifying-identity.md) +- [ADR#0037: Agent Identity Governance: Decentralized Verification under Governed Authority](./0037-agent-identity-governance.md) +- [ADR#0038: Agent Identity Cryptographic Suite and Crypto-Agility](./0038-agent-identity-crypto-suite.md) +- [Event Metadata](../architecture/event-metadata.md) +- [RFC 7515: JSON Web Signature (JWS)](https://www.rfc-editor.org/rfc/rfc7515) +- [NIST FIPS 204: Module-Lattice-Based Digital Signature Standard (ML-DSA)](https://csrc.nist.gov/pubs/fips/204/final) +- [BIP-340: Schnorr Signatures for secp256k1](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki) +- [did:key Method Specification (W3C Credentials Community Group draft)](https://w3c-ccg.github.io/did-method-key/) +- [Buzz: Nostr-based agent identity](https://github.com/block/buzz) +- [ADR index](./index.md) diff --git a/docs/adr/0040-contract-field-vocabulary.md b/docs/adr/0040-contract-field-vocabulary.md new file mode 100644 index 000000000..270c90d3d --- /dev/null +++ b/docs/adr/0040-contract-field-vocabulary.md @@ -0,0 +1,167 @@ +--- +number: "0040" +slug: contract-field-vocabulary +status: draft +date: 2026-07-27 +--- + +# ADR#0040: Contract Field Vocabulary: Identifiers, Handles, and Display Labels + +## Context + +One concept keeps recurring across domains: a human-facing label with no +enforced semantics. Today it is spelled three different ways. + +The agents contract calls it `name`. `AgentProvisioned.name` carries it, and +the retired value object behind it validated only that the string was +non-blank: no syntax rule, no uniqueness rule. The one place uniqueness was +even mentioned was an assumed upstream precondition noted in a since-deleted +command comment, never a domain invariant, and a per-stream decider cannot +enforce cross-stream uniqueness anyway. + +The sessions contract, the decider aggregate +[ADR#0035](./0035-session-store-decider-aggregate.md) establishes, calls the +same kind of concept `title`. `SessionRenamed.title` is documented as "the +session's display title": the same mutable label, spelled differently +because it lives in a different domain. + +[ADR#0025](./0025-agent-definition-data-ownership.md)'s example data spells +it a third way again, `display_name`, but as a key inside the `annotations` +bag rather than as a field of its own: a stringly-typed convention that +carries the right name but the wrong structure. + +Meanwhile `name` also appears where it is legitimate. `ToolUseBlock.name` +and `ToolCallRequested.name` are tool handles: keys the tool catalog +actually resolves to a tool definition. Those two fields are not the +ambiguity; they are why the ambiguity is dangerous. A reader cannot tell, +from the word `name` alone, whether a given occurrence resolves to +anything. + +The platform's recorded stance is explicit over implicit. The genesis +`AgentProvisioned.revision` field is recorded explicitly, "so no consumer +derives it from convention," for exactly this reason: nothing is left to a +reader's inference when a contract can say it directly. A field called +`name` that nothing resolves makes an implicit promise that nothing keeps. +Left unpinned, each new domain re-invents the choice on its own, and the +drift compounds: three spellings today become four, five, and six as the +next domain faces the same field and guesses again. + +Everything this decision touches is greenfield. Nothing described here is +deployed. A breaking rename costs once, now; a permanent vocabulary +exception costs every future reader, forever. + +## Decision + +### 1. The two-question test + +Apply this test to any string field on a platform-owned record whose +purpose is to name or refer to something. Opaque and derived data, +digests, serialized payloads, and free-form content (`content_digest`, +`input_json`) name nothing; they are outside the test's scope and keep +their descriptive spellings. + +- **Q1: Does any mechanism resolve this value?** If nothing looks the + value up, joins on it, or treats it as a key into a catalog, the value is + a display label. Spell it `display_name`. Stop here. +- **Q2 (only if Q1 is yes): Does the value denote the same referent + forever, or whatever is currently bound to it?** + - Rigid, opaque, and minted once at creation: the value is an + identifier. Spell it `id`, nested inside its own record, or + `_id` when referenced flatly. + - Legible, registry-owned, late-bound, and rebindable to a different + referent over time: the value is a handle. Spell it `name`. + +### 2. Spelling rules + +The test above compiles to a small set of spelling rules: + +- A record's own identity is `id`, nested inside that record, or + `_id` when referenced flatly. It never changes after creation. +- A reference to another entity by identity is `_id`. + `parent_tool_use_id` and `tool_execution_id` already comply. +- Placement keeps its pre-existing spelling rule and is a recorded + exception to the bullet above: bare `parent` means a placement node ref, + and kinship is always `parentId`, per the + [agent-platform decision record](../research/agent-platform/decision-record.md) + and the comment quoted on the field itself. A reader applying + `_id` mechanically to `parent` would be undoing a deliberate + convention, not fixing an oversight. +- A reference to another entity by handle is `_name`. +- A record's own display label is `display_name`. It is always an + explicit field, never a key inside an `annotations` map or any other + opaque bag. This revises the illustrative placement in + [ADR#0025](./0025-agent-definition-data-ownership.md)'s example data, + which put `display_name` inside `annotations`; that placement predates + this decision and is superseded by it, consistent with the + explicit-over-implicit stance recorded above. + +### 3. Bare `name` is reserved + +Bare `name` is not available for a display label under any circumstance. +Only a record that is itself the named entry in a registry that resolves +and enforces the handle may carry bare `name`. A label that nothing +resolves may not be called `name`, no matter how convenient the word feels +in the moment. + +### 4. Wire-fidelity exemption + +A message whose entire purpose is to mirror an external protocol verbatim +keeps that protocol's vocabulary, even where it would otherwise fail the +test above. `ToolUseBlock` mirrors the provider's emitted tool-use block +exactly, `{id, name, input}`, because faithful provider replay requires +reproducing what the provider actually sent, not a platform-preferred +respelling of it. `ToolUseBlock.name` therefore keeps `name`. The same +exemption covers any future record whose job is to mirror a provider or +external protocol rather than to express the platform's own contract. + +### 5. Authorized greenfield renames + +Applying this decision authorizes the following renames: + +- `AgentProvisioned.name` becomes `AgentProvisioned.display_name`. Nothing + resolves it; it is a label, not a handle. +- `SessionRenamed.title` becomes `SessionRenamed.display_name`. It is the + same concept as the field above, under its third spelling; the field is + `v1alpha1` and undeployed, so the rename costs nothing beyond this pass. +- `ToolCallRequested.name` becomes `ToolCallRequested.tool_name`. It is a + platform-owned execution record referencing a tool by handle, so it + takes the `_name` spelling from rule 2. + +Tool invocation stays addressed by handle deliberately. `tool_name` must +not become an id: late binding to whatever the catalog currently resolves +for that name is the intended semantics, not an accident this ADR should +close. + +### 6. Addressable agent names are deliberately absent + +This decision does not give an agent a resolvable handle. Making the +agent's label a real, resolved handle would require a reservation +mechanism, and a per-stream decider cannot provide uniqueness across +streams on its own; that is its own future decision with its own +machinery, not a side effect of a vocabulary fix. This ADR names that +trigger and leaves it unpulled. Agent identity remains `agent_id`, +self-certifying per [ADR#0036](./0036-agent-self-certifying-identity.md). + +## Consequences + +- One greppable spelling exists per concept across every future domain. + Choosing a field name becomes a decision-tree lookup, not a taste call. +- The word `name` regains teeth. Seeing it on a platform-owned record now + means a registry resolves it; it is no longer a coin flip between handle + and label. +- The rename cost above is paid exactly once, before deployment. The + alternative was carrying a permanent vocabulary exception in every + future reader of `AgentProvisioned`, `SessionRenamed`, and + `ToolCallRequested`. +- Wire-fidelity records such as `ToolUseBlock` are a recorded, deliberate + boundary rather than an unexplained inconsistency; a future reader can + tell the two apart on sight. + +## References + +- [ADR#0025: Agent Definition Data Ownership](./0025-agent-definition-data-ownership.md) +- [ADR#0035: Session Store as a Decider Aggregate on NATS JetStream](./0035-session-store-decider-aggregate.md) +- [ADR#0036: Agent Self-Certifying Cryptographic Identity](./0036-agent-self-certifying-identity.md) +- [AIP-122: Resource names](https://google.aip.dev/122) +- [AIP-148: Standard fields](https://google.aip.dev/148) +- [ADR index](./index.md) diff --git a/docs/adr/index.md b/docs/adr/index.md index 697e49098..46a0d39a0 100644 --- a/docs/adr/index.md +++ b/docs/adr/index.md @@ -41,3 +41,8 @@ future implementation work. - [ADR#0033: Two-Tier Key Custody Product Model](./0033-two-tier-key-custody-product-model.md) - [ADR#0034: Rust Crate Domain Grouping](./0034-rust-crate-domain-grouping.md) - [ADR#0035: Session Store as a Decider Aggregate on NATS JetStream](./0035-session-store-decider-aggregate.md) +- [ADR#0036: Agent Self-Certifying Cryptographic Identity](./0036-agent-self-certifying-identity.md) +- [ADR#0037: Agent Identity Governance: Decentralized Verification under Governed Authority](./0037-agent-identity-governance.md) +- [ADR#0038: Agent Identity Cryptographic Suite and Crypto-Agility](./0038-agent-identity-crypto-suite.md) +- [ADR#0039: Self-Authenticating Event Provenance](./0039-self-authenticating-event-provenance.md) +- [ADR#0040: Contract Field Vocabulary: Identifiers, Handles, and Display Labels](./0040-contract-field-vocabulary.md) diff --git a/proto/trogonai/agents/agents/v1/agent.proto b/proto/trogonai/agents/agents/v1/agent.proto new file mode 100644 index 000000000..db71db41c --- /dev/null +++ b/proto/trogonai/agents/agents/v1/agent.proto @@ -0,0 +1,20 @@ +edition = "2024"; + +package trogonai.agents.agents.v1; + +import "google/protobuf/any.proto"; + +// AgentConfiguration is the minimal, versioned declaration of an agent +// (design Q16): identity, engine, and the engine-owned settings. Nothing +// economic, temporal, evaluative, or reputational lives here; those are stances +// on their own planes (policy, evaluation, scheduling), selector-bound and +// human-owned. +message AgentConfiguration { + // Execution engine. Immutable for the agent's v1 lifetime; switching + // engines mints a new sibling agent rather than a revision (design Q24). + string runtime = 1 [features.field_presence = LEGACY_REQUIRED]; + // Runtime-owned settings: model, parameters, and dependency declarations. + // The runtime named above defines the message type carried here and + // validates its contents. Absent means the runtime runs with its defaults. + google.protobuf.Any settings = 2; +} diff --git a/proto/trogonai/agents/agents/v1/agent_provisioned.proto b/proto/trogonai/agents/agents/v1/agent_provisioned.proto new file mode 100644 index 000000000..f724e4d35 --- /dev/null +++ b/proto/trogonai/agents/agents/v1/agent_provisioned.proto @@ -0,0 +1,23 @@ +edition = "2024"; + +package trogonai.agents.agents.v1; + +import "trogonai/agents/agents/v1/agent.proto"; + +// AgentProvisioned is the Agent registry stream's genesis event, minting +// revision 1. No limits, no rubrics, no grants live here (design Q15/Q16); +// those are stances on their own planes. +message AgentProvisioned { + string agent_id = 1 [features.field_presence = LEGACY_REQUIRED]; + string display_name = 2 [features.field_presence = LEGACY_REQUIRED]; + // Placement node ref (bare spelling per the spelling rule: unsuffixed + // `parent` means placement, kinship is always `parentId`). + string parent = 3 [features.field_presence = LEGACY_REQUIRED]; + AgentConfiguration configuration = 4 [features.field_presence = LEGACY_REQUIRED]; + // Revision minted by this genesis event, recorded explicitly so no consumer + // derives it from convention. Always 1; the decider rejects any other value + // at write time. + uint64 revision = 5 [features.field_presence = LEGACY_REQUIRED]; + // SHA-256 digest of the provisioned revision bundle. + string content_digest = 6 [features.field_presence = LEGACY_REQUIRED]; +} diff --git a/proto/trogonai/agents/agents/v1/events.proto b/proto/trogonai/agents/agents/v1/events.proto new file mode 100644 index 000000000..de36eb5cd --- /dev/null +++ b/proto/trogonai/agents/agents/v1/events.proto @@ -0,0 +1,12 @@ +edition = "2024"; + +package trogonai.agents.agents.v1; + +import "trogonai/agents/agents/v1/agent_provisioned.proto"; + +// AgentEvent is the Agent registry stream's event union. +message AgentEvent { + oneof event { + AgentProvisioned agent_provisioned = 1; + } +} diff --git a/proto/trogonai/session/sessions/v1alpha1/session_renamed.proto b/proto/trogonai/session/sessions/v1alpha1/session_renamed.proto index caa10c6d0..988156ab6 100644 --- a/proto/trogonai/session/sessions/v1alpha1/session_renamed.proto +++ b/proto/trogonai/session/sessions/v1alpha1/session_renamed.proto @@ -2,10 +2,10 @@ edition = "2024"; package trogonai.session.sessions.v1alpha1; -// SessionRenamed records a change to the session's display title: reversible +// SessionRenamed records a change to the session's display name: reversible // organization state, distinct from the terminal SessionHidden (D11). It is a // commuting happened-fact (WRITE_PRECONDITION = Any). message SessionRenamed { string session_id = 1 [features.field_presence = LEGACY_REQUIRED]; - string title = 2 [features.field_presence = LEGACY_REQUIRED]; + string display_name = 2 [features.field_presence = LEGACY_REQUIRED]; } diff --git a/proto/trogonai/session/sessions/v1alpha1/tool_call_requested.proto b/proto/trogonai/session/sessions/v1alpha1/tool_call_requested.proto index 5a065e151..825869152 100644 --- a/proto/trogonai/session/sessions/v1alpha1/tool_call_requested.proto +++ b/proto/trogonai/session/sessions/v1alpha1/tool_call_requested.proto @@ -4,7 +4,7 @@ package trogonai.session.sessions.v1alpha1; // ToolCallRequested records that a tool call was requested, in arrival order. // It owns the execution-request record: what the platform was asked to run -// (name, input_json as dispatched, operation link), distinct from +// (tool_name, input_json as dispatched, operation link), distinct from // ToolUseBlock/ToolResultBlock, which own the provider-visible transcript // form. The two join by tool_call_id/tool_use_id; equality is expected but // not structurally required, since normalization may differ. This event @@ -16,7 +16,7 @@ message ToolCallRequested { string session_id = 1 [features.field_presence = LEGACY_REQUIRED]; string tool_call_id = 2 [features.field_presence = LEGACY_REQUIRED]; string tool_execution_id = 3 [features.field_presence = LEGACY_REQUIRED]; - string name = 4 [features.field_presence = LEGACY_REQUIRED]; + string tool_name = 4 [features.field_presence = LEGACY_REQUIRED]; string input_json = 5 [features.field_presence = LEGACY_REQUIRED]; string parent_tool_use_id = 6; // Operation-ledger id reserved for this call's side effect, joining it to diff --git a/rsworkspace/crates/platform/trogonai-proto/Cargo.toml b/rsworkspace/crates/platform/trogonai-proto/Cargo.toml index 361439e2f..944fe1825 100644 --- a/rsworkspace/crates/platform/trogonai-proto/Cargo.toml +++ b/rsworkspace/crates/platform/trogonai-proto/Cargo.toml @@ -15,6 +15,7 @@ schedules = ["dep:buffa", "dep:buffa-types", "dep:serde", "dep:trogon-decider", sessions = ["dep:buffa", "dep:buffa-types", "dep:serde", "dep:serde_json", "dep:trogon-decider", "chrono"] runtime-snapshot = ["schedules", "dep:trogon-decider-runtime"] runtime-host = ["schedules", "dep:trogon-decider-runtime"] +agents = ["dep:buffa", "dep:buffa-types", "dep:serde", "dep:trogon-decider"] [dependencies] thiserror = { workspace = true } diff --git a/rsworkspace/crates/platform/trogonai-proto/src/agents/agents/codec.rs b/rsworkspace/crates/platform/trogonai-proto/src/agents/agents/codec.rs new file mode 100644 index 000000000..56fa89cff --- /dev/null +++ b/rsworkspace/crates/platform/trogonai-proto/src/agents/agents/codec.rs @@ -0,0 +1,69 @@ +use buffa::Message as _; +use trogon_decider::{EventData, EventDecode, EventDecodeOutcome, EventEncode, EventPayloadError, EventType}; + +use super::{AgentEventCase, v1}; +use crate::codec::{decode_event_case, event_type}; + +#[cfg(feature = "runtime-host")] +use trogon_decider_runtime::EventIdentity; + +pub type AgentEventPayloadError = EventPayloadError; + +impl EventEncode for v1::AgentEvent { + type Error = AgentEventPayloadError; + + fn encode(&self) -> Result, Self::Error> { + self.event + .as_ref() + .map(encode_agent_event_case) + .ok_or(AgentEventPayloadError::MissingEvent) + } +} + +impl EventDecode for v1::AgentEvent { + type Error = AgentEventPayloadError; + + fn decode(event: EventData<'_>) -> Result, Self::Error> { + match decode_agent_event_case(event)? { + Some(event) => Ok(EventDecodeOutcome::Decoded(v1::AgentEvent { event: Some(event) })), + None => Ok(EventDecodeOutcome::Skipped), + } + } +} + +impl EventType for v1::AgentEvent { + type Error = AgentEventPayloadError; + + fn event_type(&self) -> Result<&'static str, Self::Error> { + self.event + .as_ref() + .map(agent_event_case_type) + .ok_or(AgentEventPayloadError::MissingEvent) + } +} + +#[cfg(feature = "runtime-host")] +impl EventIdentity for v1::AgentEvent {} + +fn encode_agent_event_case(event: &AgentEventCase) -> Vec { + match event { + AgentEventCase::AgentProvisioned(inner) => inner.encode_to_vec(), + } +} + +fn decode_agent_event_case(event: EventData<'_>) -> Result, AgentEventPayloadError> { + let Some(event) = decode_event_case::(&event) else { + return Ok(None); + }; + + event.map(Some).map_err(AgentEventPayloadError::Decode) +} + +fn agent_event_case_type(event: &AgentEventCase) -> &'static str { + match event { + AgentEventCase::AgentProvisioned(_) => event_type::(), + } +} + +#[cfg(test)] +mod tests; diff --git a/rsworkspace/crates/platform/trogonai-proto/src/agents/agents/codec/tests.rs b/rsworkspace/crates/platform/trogonai-proto/src/agents/agents/codec/tests.rs new file mode 100644 index 000000000..3d3fcc711 --- /dev/null +++ b/rsworkspace/crates/platform/trogonai-proto/src/agents/agents/codec/tests.rs @@ -0,0 +1,86 @@ +use buffa::Message as _; +use trogon_decider::{EventData, EventDecode, EventDecodeOutcome, EventEncode, EventType}; + +use super::*; + +fn agent_provisioned() -> v1::AgentProvisioned { + v1::AgentProvisioned { + agent_id: "agent-1".to_string(), + ..v1::AgentProvisioned::default() + } +} + +#[test] +fn event_encode_writes_inner_event_payload() { + let inner = agent_provisioned(); + let event = v1::AgentEvent { + event: Some(inner.clone().into()), + }; + + let encoded = EventEncode::encode(&event).unwrap(); + + assert_eq!(v1::AgentProvisioned::decode_from_slice(&encoded).unwrap(), inner); +} + +#[test] +fn event_encode_rejects_missing_event_case() { + let event = v1::AgentEvent { event: None }; + + assert!(matches!( + EventEncode::encode(&event), + Err(AgentEventPayloadError::MissingEvent) + )); +} + +#[test] +fn event_decode_dispatches_by_generated_full_name() { + let inner = agent_provisioned(); + let encoded = inner.encode_to_vec(); + + let decoded = ::decode(EventData::new( + ::FULL_NAME, + &encoded, + )) + .unwrap(); + + let decoded = decoded.into_decoded().unwrap(); + assert!(matches!(decoded.event, Some(AgentEventCase::AgentProvisioned(_)))); +} + +#[test] +fn event_decode_skips_unknown_event_type() { + assert!(matches!( + ::decode(EventData::new("trogonai.agents.agents.v1.Unknown", &[])), + Ok(EventDecodeOutcome::Skipped) + )); +} + +#[test] +fn event_decode_preserves_payload_decode_errors() { + assert!(matches!( + ::decode(EventData::new( + ::FULL_NAME, + b"\0" + )), + Err(AgentEventPayloadError::Decode(_)) + )); +} + +#[test] +fn event_type_returns_inner_event_full_name() { + let event = v1::AgentEvent { + event: Some(agent_provisioned().into()), + }; + + assert_eq!( + event.event_type().unwrap(), + ::FULL_NAME + ); +} + +#[test] +fn event_type_rejects_missing_event_case() { + let event = v1::AgentEvent { event: None }; + + assert!(matches!(event.event_type(), Err(AgentEventPayloadError::MissingEvent))); +} diff --git a/rsworkspace/crates/platform/trogonai-proto/src/agents/agents/mod.rs b/rsworkspace/crates/platform/trogonai-proto/src/agents/agents/mod.rs new file mode 100644 index 000000000..18835ca13 --- /dev/null +++ b/rsworkspace/crates/platform/trogonai-proto/src/agents/agents/mod.rs @@ -0,0 +1,11 @@ +mod codec; + +// Thin wrapper that re-exports the generated proto package, emitted as an +// inline module tree that mirrors the codegen layout. +#[cfg_attr(dylint_lib = "trogon_lints", allow(inline_module_block))] +pub mod v1 { + pub use crate::r#gen::trogonai::agents::agents::v1::*; +} + +pub use codec::AgentEventPayloadError; +pub use v1::__buffa::oneof::agent_event::Event as AgentEventCase; diff --git a/rsworkspace/crates/platform/trogonai-proto/src/agents/mod.rs b/rsworkspace/crates/platform/trogonai-proto/src/agents/mod.rs new file mode 100644 index 000000000..8006a39aa --- /dev/null +++ b/rsworkspace/crates/platform/trogonai-proto/src/agents/mod.rs @@ -0,0 +1,5 @@ +// The product area (`agents`) and the aggregate (`agents`) share a name here +// because the brief mandates `proto/trogonai/agents/agents/v1/` verbatim +// (the product area is the aggregate for this stream). +#[allow(clippy::module_inception)] +pub mod agents; diff --git a/rsworkspace/crates/platform/trogonai-proto/src/gen/mod.rs b/rsworkspace/crates/platform/trogonai-proto/src/gen/mod.rs index 801e5e83b..98eebaf9d 100644 --- a/rsworkspace/crates/platform/trogonai-proto/src/gen/mod.rs +++ b/rsworkspace/crates/platform/trogonai-proto/src/gen/mod.rs @@ -122,6 +122,49 @@ pub mod trogonai { clippy::doc_lazy_continuation, clippy::module_inception )] + pub mod agents { + use super::*; + #[allow( + non_camel_case_types, + dead_code, + unused_imports, + unused_qualifications, + clippy::derivable_impls, + clippy::match_single_binding, + clippy::uninlined_format_args, + clippy::doc_lazy_continuation, + clippy::module_inception + )] + pub mod agents { + use super::*; + #[allow( + non_camel_case_types, + dead_code, + unused_imports, + unused_qualifications, + clippy::derivable_impls, + clippy::match_single_binding, + clippy::uninlined_format_args, + clippy::doc_lazy_continuation, + clippy::module_inception + )] + pub mod v1 { + use super::*; + include!("trogonai.agents.agents.v1.mod.rs"); + } + } + } + #[allow( + non_camel_case_types, + dead_code, + unused_imports, + unused_qualifications, + clippy::derivable_impls, + clippy::match_single_binding, + clippy::uninlined_format_args, + clippy::doc_lazy_continuation, + clippy::module_inception + )] pub mod scheduler { use super::*; #[allow( diff --git a/rsworkspace/crates/platform/trogonai-proto/src/gen/trogonai.agents.agents.v1.agent.__view.rs b/rsworkspace/crates/platform/trogonai-proto/src/gen/trogonai.agents.agents.v1.agent.__view.rs new file mode 100644 index 000000000..91be127c1 --- /dev/null +++ b/rsworkspace/crates/platform/trogonai-proto/src/gen/trogonai.agents.agents.v1.agent.__view.rs @@ -0,0 +1,328 @@ +// @generated by buffa-codegen. DO NOT EDIT. +// source: trogonai/agents/agents/v1/agent.proto + +/// AgentConfiguration is the minimal, versioned declaration of an agent +/// (design Q16): identity, engine, and the engine-owned settings. Nothing +/// economic, temporal, evaluative, or reputational lives here; those are stances +/// on their own planes (policy, evaluation, scheduling), selector-bound and +/// human-owned. +#[derive(Clone, Debug, Default)] +pub struct AgentConfigurationView<'a> { + /// Execution engine. Immutable for the agent's v1 lifetime; switching + /// engines mints a new sibling agent rather than a revision (design Q24). + /// + /// Field 1: `runtime` + pub runtime: &'a str, + /// Runtime-owned settings: model, parameters, and dependency declarations. + /// The runtime named above defines the message type carried here and + /// validates its contents. Absent means the runtime runs with its defaults. + /// + /// Field 2: `settings` + pub settings: ::buffa::MessageFieldView< + ::buffa_types::google::protobuf::__buffa::view::AnyView<'a>, + >, + #[doc(hidden)] + pub __buffa_required_seen_0: u64, +} +impl<'a> AgentConfigurationView<'a> { + /**Whether required field `runtime` was present on the wire. + +Distinguishes a field that was absent from one explicitly encoded with its default value (required scalar fields are stored as bare, non-`Option` types, so the value alone cannot tell the two apart). Presence is recorded only by the wire decoder: a default or hand-built view reports `false`. Encoding is unaffected — required fields are always written.*/ + #[must_use] + #[inline] + pub const fn has_runtime(&self) -> bool { + self.__buffa_required_seen_0 & 1u64 != 0 + } +} +impl<'a> ::buffa::MessageView<'a> for AgentConfigurationView<'a> { + type Owned = super::super::AgentConfiguration; + fn decode_view(buf: &'a [u8]) -> ::core::result::Result { + let __limit = ::core::cell::Cell::new(::buffa::DEFAULT_UNKNOWN_FIELD_LIMIT); + ::decode_view_ctx( + buf, + ::buffa::DecodeContext::new(::buffa::RECURSION_LIMIT, &__limit), + ) + } + fn decode_view_with_ctx( + buf: &'a [u8], + ctx: ::buffa::DecodeContext<'_>, + ) -> ::core::result::Result { + ::decode_view_ctx(buf, ctx) + } + fn merge_view_field( + &mut self, + tag: ::buffa::encoding::Tag, + cur: &'a [u8], + _before_tag: &'a [u8], + ctx: ::buffa::DecodeContext<'_>, + ) -> ::core::result::Result<&'a [u8], ::buffa::DecodeError> { + let _ = ctx; + #[allow(unused_variables)] + let view = self; + let mut cur = cur; + match tag.field_number() { + 1u32 => { + ::buffa::encoding::check_wire_type( + tag, + ::buffa::encoding::WireType::LengthDelimited, + )?; + view.runtime = ::buffa::types::borrow_str(&mut cur)?; + view.__buffa_required_seen_0 |= 1u64; + } + 2u32 => { + ::buffa::encoding::check_wire_type( + tag, + ::buffa::encoding::WireType::LengthDelimited, + )?; + let __sub_ctx = ctx.descend()?; + let sub = ::buffa::types::borrow_bytes(&mut cur)?; + match view.settings.as_mut() { + Some(existing) => { + ::buffa::MessageView::merge_into_view(existing, sub, __sub_ctx)? + } + None => { + view.settings = ::buffa::MessageFieldView::set( + <::buffa_types::google::protobuf::__buffa::view::AnyView as ::buffa::MessageView>::decode_view_ctx( + sub, + __sub_ctx, + )?, + ); + } + } + } + _ => { + ::buffa::encoding::skip_field_depth(tag, &mut cur, ctx.depth())?; + } + } + ::core::result::Result::Ok(cur) + } + fn to_owned_message( + &self, + ) -> ::core::result::Result { + self.to_owned_from_source(None) + } + #[allow(clippy::useless_conversion, clippy::needless_update)] + fn to_owned_from_source( + &self, + __buffa_src: ::core::option::Option<&::buffa::bytes::Bytes>, + ) -> ::core::result::Result { + #[allow(unused_imports)] + use ::buffa::alloc::string::ToString as _; + let _ = __buffa_src; + ::core::result::Result::Ok(super::super::AgentConfiguration { + runtime: self.runtime.to_string(), + settings: match self.settings.as_option() { + Some(v) => { + ::buffa::MessageField::< + ::buffa_types::google::protobuf::Any, + >::some(v.to_owned_from_source(__buffa_src)?) + } + None => ::buffa::MessageField::none(), + }, + ..::core::default::Default::default() + }) + } +} +impl<'a> ::buffa::ViewEncode<'a> for AgentConfigurationView<'a> { + #[allow(clippy::needless_borrow, clippy::let_and_return)] + fn compute_size(&self, __cache: &mut ::buffa::SizeCache) -> u32 { + #[allow(unused_imports)] + use ::buffa::Enumeration as _; + let mut size = 0u32; + size += 1u32 + ::buffa::types::string_encoded_len(&self.runtime) as u32; + if self.settings.is_set() { + let __slot = __cache.reserve(); + let inner_size = self.settings.compute_size(__cache); + __cache.set(__slot, inner_size); + size + += 1u32 + ::buffa::encoding::varint_len(inner_size as u64) as u32 + + inner_size; + } + size + } + #[allow(clippy::needless_borrow)] + fn write_to( + &self, + __cache: &mut ::buffa::SizeCache, + buf: &mut impl ::buffa::bytes::BufMut, + ) { + #[allow(unused_imports)] + use ::buffa::Enumeration as _; + ::buffa::types::put_string_field(1u32, &self.runtime, buf); + if self.settings.is_set() { + ::buffa::types::put_len_delimited_header(2u32, __cache.consume_next(), buf); + self.settings.write_to(__cache, buf); + } + } +} +/// Serializes this view as protobuf JSON. +/// +/// Implicit-presence fields with default values are omitted, `required` +/// fields are always emitted, explicit-presence (`optional`) fields are +/// emitted only when set, bytes fields are base64-encoded, and enum +/// values are their proto name strings. +/// +/// This impl uses `serialize_map(None)` because the number of emitted +/// fields depends on default-omission rules; serializers that require +/// known map lengths (e.g. `bincode`) will return a runtime error. +/// Use the owned message type for those formats. +impl<'__a> ::serde::Serialize for AgentConfigurationView<'__a> { + fn serialize<__S: ::serde::Serializer>( + &self, + __s: __S, + ) -> ::core::result::Result<__S::Ok, __S::Error> { + use ::serde::ser::SerializeMap as _; + let mut __map = __s.serialize_map(::core::option::Option::None)?; + { + __map.serialize_entry("runtime", self.runtime)?; + } + { + if let ::core::option::Option::Some(__v) = self.settings.as_option() { + __map.serialize_entry("settings", __v)?; + } + } + __map.end() + } +} +impl<'a> ::buffa::MessageName for AgentConfigurationView<'a> { + const PACKAGE: &'static str = "trogonai.agents.agents.v1"; + const NAME: &'static str = "AgentConfiguration"; + const FULL_NAME: &'static str = "trogonai.agents.agents.v1.AgentConfiguration"; + const TYPE_URL: &'static str = "type.googleapis.com/trogonai.agents.agents.v1.AgentConfiguration"; +} +::buffa::impl_default_view_instance!(AgentConfigurationView); +::buffa::impl_view_reborrow!(AgentConfigurationView); +/** Self-contained, `'static` owned view of a `AgentConfiguration` message. + + Wraps [`::buffa::OwnedView`]`<`[`AgentConfigurationView`]`<'static>>`: the decoded view and the [`::buffa::bytes::Bytes`] buffer it borrows from travel together, so the handle is `'static` and `Send + Sync` — suitable for async handlers, spawned tasks, and anywhere a `'static` bound is required. + + Field accessors return borrows tied to `&self`. Use [`Self::view`] to get the full [`AgentConfigurationView`] when you need struct patterns, iteration helpers, or to pass the view to lifetime-parameterised code.*/ +#[derive(Clone, Debug)] +pub struct AgentConfigurationOwnedView( + ::buffa::OwnedView>, +); +impl AgentConfigurationOwnedView { + /// Decode an owned view from a [`::buffa::bytes::Bytes`] buffer. + /// + /// The view borrows directly from the buffer's data; the buffer is + /// retained inside the returned handle. + /// + /// # Errors + /// + /// Returns [`::buffa::DecodeError`] if the buffer contains invalid + /// protobuf data. + pub fn decode( + bytes: ::buffa::bytes::Bytes, + ) -> ::core::result::Result { + ::core::result::Result::Ok( + AgentConfigurationOwnedView(::buffa::OwnedView::decode(bytes)?), + ) + } + /// Decode with custom [`::buffa::DecodeOptions`] (recursion limit, + /// max message size). + /// + /// # Errors + /// + /// Returns [`::buffa::DecodeError`] if the buffer is invalid or + /// exceeds the configured limits. + pub fn decode_with_options( + bytes: ::buffa::bytes::Bytes, + opts: &::buffa::DecodeOptions, + ) -> ::core::result::Result { + ::core::result::Result::Ok( + AgentConfigurationOwnedView( + ::buffa::OwnedView::decode_with_options(bytes, opts)?, + ), + ) + } + /// Build from an owned message via an encode → decode round-trip. + /// + /// # Errors + /// + /// Returns [`::buffa::DecodeError`] if the re-encoded bytes are + /// somehow invalid (should not happen for well-formed messages). + pub fn from_owned( + msg: &super::super::AgentConfiguration, + ) -> ::core::result::Result { + ::core::result::Result::Ok( + AgentConfigurationOwnedView(::buffa::OwnedView::from_owned(msg)?), + ) + } + /// Borrow the full [`AgentConfigurationView`] with its lifetime tied to `&self`. + #[must_use] + pub fn view(&self) -> &AgentConfigurationView<'_> { + self.0.reborrow() + } + /// Convert to the owned message type. + /// + /// # Errors + /// + /// Returns an error if re-materializing preserved unknown fields + /// fails (e.g. the unknown-field limit is exceeded). + pub fn to_owned_message( + &self, + ) -> ::core::result::Result { + self.0.to_owned_message() + } + /// The underlying bytes buffer. + #[must_use] + pub fn bytes(&self) -> &::buffa::bytes::Bytes { + self.0.bytes() + } + /// Consume the handle, returning the underlying bytes buffer. + #[must_use] + pub fn into_bytes(self) -> ::buffa::bytes::Bytes { + self.0.into_bytes() + } + /// Execution engine. Immutable for the agent's v1 lifetime; switching + /// engines mints a new sibling agent rather than a revision (design Q24). + /// + /// Field 1: `runtime` + #[must_use] + pub fn runtime(&self) -> &'_ str { + self.0.reborrow().runtime + } + /// Runtime-owned settings: model, parameters, and dependency declarations. + /// The runtime named above defines the message type carried here and + /// validates its contents. Absent means the runtime runs with its defaults. + /// + /// Field 2: `settings` + #[must_use] + pub fn settings( + &self, + ) -> &::buffa::MessageFieldView< + ::buffa_types::google::protobuf::__buffa::view::AnyView<'_>, + > { + &self.0.reborrow().settings + } +} +impl ::core::convert::From<::buffa::OwnedView>> +for AgentConfigurationOwnedView { + fn from(inner: ::buffa::OwnedView>) -> Self { + AgentConfigurationOwnedView(inner) + } +} +impl ::core::convert::From +for ::buffa::OwnedView> { + fn from(wrapper: AgentConfigurationOwnedView) -> Self { + wrapper.0 + } +} +impl ::core::convert::AsRef<::buffa::OwnedView>> +for AgentConfigurationOwnedView { + fn as_ref(&self) -> &::buffa::OwnedView> { + &self.0 + } +} +impl ::buffa::HasMessageView for super::super::AgentConfiguration { + type View<'a> = AgentConfigurationView<'a>; + type ViewHandle = AgentConfigurationOwnedView; +} +impl ::serde::Serialize for AgentConfigurationOwnedView { + fn serialize<__S: ::serde::Serializer>( + &self, + __s: __S, + ) -> ::core::result::Result<__S::Ok, __S::Error> { + ::serde::Serialize::serialize(&self.0, __s) + } +} diff --git a/rsworkspace/crates/platform/trogonai-proto/src/gen/trogonai.agents.agents.v1.agent.rs b/rsworkspace/crates/platform/trogonai-proto/src/gen/trogonai.agents.agents.v1.agent.rs new file mode 100644 index 000000000..f6a316ad9 --- /dev/null +++ b/rsworkspace/crates/platform/trogonai-proto/src/gen/trogonai.agents.agents.v1.agent.rs @@ -0,0 +1,146 @@ +// @generated by buffa-codegen. DO NOT EDIT. +// source: trogonai/agents/agents/v1/agent.proto + +/// AgentConfiguration is the minimal, versioned declaration of an agent +/// (design Q16): identity, engine, and the engine-owned settings. Nothing +/// economic, temporal, evaluative, or reputational lives here; those are stances +/// on their own planes (policy, evaluation, scheduling), selector-bound and +/// human-owned. +#[derive(Clone, PartialEq, Default)] +#[derive(::serde::Serialize, ::serde::Deserialize)] +#[serde(default)] +pub struct AgentConfiguration { + /// Execution engine. Immutable for the agent's v1 lifetime; switching + /// engines mints a new sibling agent rather than a revision (design Q24). + /// + /// Field 1: `runtime` + #[serde(rename = "runtime", with = "::buffa::json_helpers::proto_string")] + pub runtime: ::buffa::alloc::string::String, + /// Runtime-owned settings: model, parameters, and dependency declarations. + /// The runtime named above defines the message type carried here and + /// validates its contents. Absent means the runtime runs with its defaults. + /// + /// Field 2: `settings` + #[serde( + rename = "settings", + skip_serializing_if = "::buffa::json_helpers::skip_if::is_unset_message_field" + )] + pub settings: ::buffa::MessageField<::buffa_types::google::protobuf::Any>, +} +impl ::core::fmt::Debug for AgentConfiguration { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + f.debug_struct("AgentConfiguration") + .field("runtime", &self.runtime) + .field("settings", &self.settings) + .finish() + } +} +impl AgentConfiguration { + /// Protobuf type URL for this message, for use with `Any::pack` and + /// `Any::unpack_if`. + /// + /// Format: `type.googleapis.com/` + pub const TYPE_URL: &'static str = "type.googleapis.com/trogonai.agents.agents.v1.AgentConfiguration"; +} +::buffa::impl_default_instance!(AgentConfiguration); +impl ::buffa::MessageName for AgentConfiguration { + const PACKAGE: &'static str = "trogonai.agents.agents.v1"; + const NAME: &'static str = "AgentConfiguration"; + const FULL_NAME: &'static str = "trogonai.agents.agents.v1.AgentConfiguration"; + const TYPE_URL: &'static str = "type.googleapis.com/trogonai.agents.agents.v1.AgentConfiguration"; +} +impl ::buffa::Message for AgentConfiguration { + /// Returns the total encoded size in bytes. + /// + /// The result is a `u32`; the protobuf specification requires all + /// messages to fit within 2 GiB (2,147,483,647 bytes), so a + /// compliant message will never overflow this type. + #[allow(clippy::let_and_return)] + fn compute_size(&self, __cache: &mut ::buffa::SizeCache) -> u32 { + #[allow(unused_imports)] + use ::buffa::Enumeration as _; + let mut size = 0u32; + size += 1u32 + ::buffa::types::string_encoded_len(&self.runtime) as u32; + if self.settings.is_set() { + let __slot = __cache.reserve(); + let inner_size = self.settings.compute_size(__cache); + __cache.set(__slot, inner_size); + size + += 1u32 + ::buffa::encoding::varint_len(inner_size as u64) as u32 + + inner_size; + } + size + } + fn write_to( + &self, + __cache: &mut ::buffa::SizeCache, + buf: &mut impl ::buffa::bytes::BufMut, + ) { + #[allow(unused_imports)] + use ::buffa::Enumeration as _; + ::buffa::types::put_string_field(1u32, &self.runtime, buf); + if self.settings.is_set() { + ::buffa::types::put_len_delimited_header(2u32, __cache.consume_next(), buf); + self.settings.write_to(__cache, buf); + } + } + fn merge_field( + &mut self, + tag: ::buffa::encoding::Tag, + buf: &mut impl ::buffa::bytes::Buf, + ctx: ::buffa::DecodeContext<'_>, + ) -> ::core::result::Result<(), ::buffa::DecodeError> { + #[allow(unused_imports)] + use ::buffa::bytes::Buf as _; + #[allow(unused_imports)] + use ::buffa::Enumeration as _; + match tag.field_number() { + 1u32 => { + ::buffa::encoding::check_wire_type( + tag, + ::buffa::encoding::WireType::LengthDelimited, + )?; + ::buffa::types::merge_string(&mut self.runtime, buf)?; + } + 2u32 => { + ::buffa::encoding::check_wire_type( + tag, + ::buffa::encoding::WireType::LengthDelimited, + )?; + ::buffa::Message::merge_length_delimited( + self.settings.get_or_insert_default(), + buf, + ctx, + )?; + } + _ => { + ::buffa::encoding::skip_field_depth(tag, buf, ctx.depth())?; + } + } + ::core::result::Result::Ok(()) + } + fn clear(&mut self) { + self.runtime.clear(); + self.settings = ::buffa::MessageField::none(); + } +} +impl ::buffa::json_helpers::ProtoElemJson for AgentConfiguration { + fn serialize_proto_json( + v: &Self, + s: S, + ) -> ::core::result::Result { + ::serde::Serialize::serialize(v, s) + } + fn deserialize_proto_json<'de, D: ::serde::Deserializer<'de>>( + d: D, + ) -> ::core::result::Result { + ::deserialize(d) + } +} +#[doc(hidden)] +pub const __AGENT_CONFIGURATION_JSON_ANY: ::buffa::type_registry::JsonAnyEntry = ::buffa::type_registry::JsonAnyEntry { + type_url: "type.googleapis.com/trogonai.agents.agents.v1.AgentConfiguration", + to_json: ::buffa::type_registry::any_to_json::, + from_json: ::buffa::type_registry::any_from_json::, + is_wkt: false, +}; diff --git a/rsworkspace/crates/platform/trogonai-proto/src/gen/trogonai.agents.agents.v1.agent_provisioned.__view.rs b/rsworkspace/crates/platform/trogonai-proto/src/gen/trogonai.agents.agents.v1.agent_provisioned.__view.rs new file mode 100644 index 000000000..dbe170f06 --- /dev/null +++ b/rsworkspace/crates/platform/trogonai-proto/src/gen/trogonai.agents.agents.v1.agent_provisioned.__view.rs @@ -0,0 +1,456 @@ +// @generated by buffa-codegen. DO NOT EDIT. +// source: trogonai/agents/agents/v1/agent_provisioned.proto + +/// AgentProvisioned is the Agent registry stream's genesis event, minting +/// revision 1. No limits, no rubrics, no grants live here (design Q15/Q16); +/// those are stances on their own planes. +#[derive(Clone, Debug, Default)] +pub struct AgentProvisionedView<'a> { + /// Field 1: `agent_id` + pub agent_id: &'a str, + /// Field 2: `display_name` + pub display_name: &'a str, + /// Placement node ref (bare spelling per the spelling rule: unsuffixed + /// `parent` means placement, kinship is always `parentId`). + /// + /// Field 3: `parent` + pub parent: &'a str, + /// Field 4: `configuration` + pub configuration: ::buffa::MessageFieldView< + super::super::__buffa::view::AgentConfigurationView<'a>, + >, + /// Revision minted by this genesis event, recorded explicitly so no consumer + /// derives it from convention. Always 1; the decider rejects any other value + /// at write time. + /// + /// Field 5: `revision` + pub revision: u64, + /// SHA-256 digest of the provisioned revision bundle. + /// + /// Field 6: `content_digest` + pub content_digest: &'a str, + #[doc(hidden)] + pub __buffa_required_seen_0: u64, +} +impl<'a> AgentProvisionedView<'a> { + /**Whether required field `agent_id` was present on the wire. + +Distinguishes a field that was absent from one explicitly encoded with its default value (required scalar fields are stored as bare, non-`Option` types, so the value alone cannot tell the two apart). Presence is recorded only by the wire decoder: a default or hand-built view reports `false`. Encoding is unaffected — required fields are always written.*/ + #[must_use] + #[inline] + pub const fn has_agent_id(&self) -> bool { + self.__buffa_required_seen_0 & 1u64 != 0 + } + /**Whether required field `display_name` was present on the wire. + +Distinguishes a field that was absent from one explicitly encoded with its default value (required scalar fields are stored as bare, non-`Option` types, so the value alone cannot tell the two apart). Presence is recorded only by the wire decoder: a default or hand-built view reports `false`. Encoding is unaffected — required fields are always written.*/ + #[must_use] + #[inline] + pub const fn has_display_name(&self) -> bool { + self.__buffa_required_seen_0 & 2u64 != 0 + } + /**Whether required field `parent` was present on the wire. + +Distinguishes a field that was absent from one explicitly encoded with its default value (required scalar fields are stored as bare, non-`Option` types, so the value alone cannot tell the two apart). Presence is recorded only by the wire decoder: a default or hand-built view reports `false`. Encoding is unaffected — required fields are always written.*/ + #[must_use] + #[inline] + pub const fn has_parent(&self) -> bool { + self.__buffa_required_seen_0 & 4u64 != 0 + } + /**Whether required field `configuration` is set. + +Mirrors `is_set()` on the field: `true` after decoding a message where the field was present on the wire, and `true` on a hand-built view whose field is populated. Encoding is unaffected — required fields are always written.*/ + #[must_use] + #[inline] + pub const fn has_configuration(&self) -> bool { + self.configuration.is_set() + } + /**Whether required field `revision` was present on the wire. + +Distinguishes a field that was absent from one explicitly encoded with its default value (required scalar fields are stored as bare, non-`Option` types, so the value alone cannot tell the two apart). Presence is recorded only by the wire decoder: a default or hand-built view reports `false`. Encoding is unaffected — required fields are always written.*/ + #[must_use] + #[inline] + pub const fn has_revision(&self) -> bool { + self.__buffa_required_seen_0 & 8u64 != 0 + } + /**Whether required field `content_digest` was present on the wire. + +Distinguishes a field that was absent from one explicitly encoded with its default value (required scalar fields are stored as bare, non-`Option` types, so the value alone cannot tell the two apart). Presence is recorded only by the wire decoder: a default or hand-built view reports `false`. Encoding is unaffected — required fields are always written.*/ + #[must_use] + #[inline] + pub const fn has_content_digest(&self) -> bool { + self.__buffa_required_seen_0 & 16u64 != 0 + } +} +impl<'a> ::buffa::MessageView<'a> for AgentProvisionedView<'a> { + type Owned = super::super::AgentProvisioned; + fn decode_view(buf: &'a [u8]) -> ::core::result::Result { + let __limit = ::core::cell::Cell::new(::buffa::DEFAULT_UNKNOWN_FIELD_LIMIT); + ::decode_view_ctx( + buf, + ::buffa::DecodeContext::new(::buffa::RECURSION_LIMIT, &__limit), + ) + } + fn decode_view_with_ctx( + buf: &'a [u8], + ctx: ::buffa::DecodeContext<'_>, + ) -> ::core::result::Result { + ::decode_view_ctx(buf, ctx) + } + fn merge_view_field( + &mut self, + tag: ::buffa::encoding::Tag, + cur: &'a [u8], + _before_tag: &'a [u8], + ctx: ::buffa::DecodeContext<'_>, + ) -> ::core::result::Result<&'a [u8], ::buffa::DecodeError> { + let _ = ctx; + #[allow(unused_variables)] + let view = self; + let mut cur = cur; + match tag.field_number() { + 1u32 => { + ::buffa::encoding::check_wire_type( + tag, + ::buffa::encoding::WireType::LengthDelimited, + )?; + view.agent_id = ::buffa::types::borrow_str(&mut cur)?; + view.__buffa_required_seen_0 |= 1u64; + } + 2u32 => { + ::buffa::encoding::check_wire_type( + tag, + ::buffa::encoding::WireType::LengthDelimited, + )?; + view.display_name = ::buffa::types::borrow_str(&mut cur)?; + view.__buffa_required_seen_0 |= 2u64; + } + 3u32 => { + ::buffa::encoding::check_wire_type( + tag, + ::buffa::encoding::WireType::LengthDelimited, + )?; + view.parent = ::buffa::types::borrow_str(&mut cur)?; + view.__buffa_required_seen_0 |= 4u64; + } + 4u32 => { + ::buffa::encoding::check_wire_type( + tag, + ::buffa::encoding::WireType::LengthDelimited, + )?; + let __sub_ctx = ctx.descend()?; + let sub = ::buffa::types::borrow_bytes(&mut cur)?; + match view.configuration.as_mut() { + Some(existing) => { + ::buffa::MessageView::merge_into_view(existing, sub, __sub_ctx)? + } + None => { + view.configuration = ::buffa::MessageFieldView::set( + ::decode_view_ctx( + sub, + __sub_ctx, + )?, + ); + } + } + } + 5u32 => { + ::buffa::encoding::check_wire_type( + tag, + ::buffa::encoding::WireType::Varint, + )?; + view.revision = ::buffa::types::decode_uint64(&mut cur)?; + view.__buffa_required_seen_0 |= 8u64; + } + 6u32 => { + ::buffa::encoding::check_wire_type( + tag, + ::buffa::encoding::WireType::LengthDelimited, + )?; + view.content_digest = ::buffa::types::borrow_str(&mut cur)?; + view.__buffa_required_seen_0 |= 16u64; + } + _ => { + ::buffa::encoding::skip_field_depth(tag, &mut cur, ctx.depth())?; + } + } + ::core::result::Result::Ok(cur) + } + fn to_owned_message( + &self, + ) -> ::core::result::Result { + self.to_owned_from_source(None) + } + #[allow(clippy::useless_conversion, clippy::needless_update)] + fn to_owned_from_source( + &self, + __buffa_src: ::core::option::Option<&::buffa::bytes::Bytes>, + ) -> ::core::result::Result { + #[allow(unused_imports)] + use ::buffa::alloc::string::ToString as _; + let _ = __buffa_src; + ::core::result::Result::Ok(super::super::AgentProvisioned { + agent_id: self.agent_id.to_string(), + display_name: self.display_name.to_string(), + parent: self.parent.to_string(), + configuration: match self.configuration.as_option() { + Some(v) => { + ::buffa::MessageField::< + super::super::AgentConfiguration, + >::some(v.to_owned_from_source(__buffa_src)?) + } + None => ::buffa::MessageField::none(), + }, + revision: self.revision, + content_digest: self.content_digest.to_string(), + ..::core::default::Default::default() + }) + } +} +impl<'a> ::buffa::ViewEncode<'a> for AgentProvisionedView<'a> { + #[allow(clippy::needless_borrow, clippy::let_and_return)] + fn compute_size(&self, __cache: &mut ::buffa::SizeCache) -> u32 { + #[allow(unused_imports)] + use ::buffa::Enumeration as _; + let mut size = 0u32; + size += 1u32 + ::buffa::types::string_encoded_len(&self.agent_id) as u32; + size += 1u32 + ::buffa::types::string_encoded_len(&self.display_name) as u32; + size += 1u32 + ::buffa::types::string_encoded_len(&self.parent) as u32; + if self.configuration.is_set() { + let __slot = __cache.reserve(); + let inner_size = self.configuration.compute_size(__cache); + __cache.set(__slot, inner_size); + size + += 1u32 + ::buffa::encoding::varint_len(inner_size as u64) as u32 + + inner_size; + } + size += 1u32 + ::buffa::types::uint64_encoded_len(self.revision) as u32; + size += 1u32 + ::buffa::types::string_encoded_len(&self.content_digest) as u32; + size + } + #[allow(clippy::needless_borrow)] + fn write_to( + &self, + __cache: &mut ::buffa::SizeCache, + buf: &mut impl ::buffa::bytes::BufMut, + ) { + #[allow(unused_imports)] + use ::buffa::Enumeration as _; + ::buffa::types::put_string_field(1u32, &self.agent_id, buf); + ::buffa::types::put_string_field(2u32, &self.display_name, buf); + ::buffa::types::put_string_field(3u32, &self.parent, buf); + if self.configuration.is_set() { + ::buffa::types::put_len_delimited_header(4u32, __cache.consume_next(), buf); + self.configuration.write_to(__cache, buf); + } + ::buffa::types::put_uint64_field(5u32, self.revision, buf); + ::buffa::types::put_string_field(6u32, &self.content_digest, buf); + } +} +/// Serializes this view as protobuf JSON. +/// +/// Implicit-presence fields with default values are omitted, `required` +/// fields are always emitted, explicit-presence (`optional`) fields are +/// emitted only when set, bytes fields are base64-encoded, and enum +/// values are their proto name strings. +/// +/// This impl uses `serialize_map(None)` because the number of emitted +/// fields depends on default-omission rules; serializers that require +/// known map lengths (e.g. `bincode`) will return a runtime error. +/// Use the owned message type for those formats. +impl<'__a> ::serde::Serialize for AgentProvisionedView<'__a> { + fn serialize<__S: ::serde::Serializer>( + &self, + __s: __S, + ) -> ::core::result::Result<__S::Ok, __S::Error> { + use ::serde::ser::SerializeMap as _; + let mut __map = __s.serialize_map(::core::option::Option::None)?; + { + __map.serialize_entry("agentId", self.agent_id)?; + } + { + __map.serialize_entry("displayName", self.display_name)?; + } + { + __map.serialize_entry("parent", self.parent)?; + } + { + if let ::core::option::Option::Some(__v) = self.configuration.as_option() { + __map.serialize_entry("configuration", __v)?; + } + } + { + __map + .serialize_entry( + "revision", + &::buffa::json_helpers::ProtoJson(&self.revision), + )?; + } + { + __map.serialize_entry("contentDigest", self.content_digest)?; + } + __map.end() + } +} +impl<'a> ::buffa::MessageName for AgentProvisionedView<'a> { + const PACKAGE: &'static str = "trogonai.agents.agents.v1"; + const NAME: &'static str = "AgentProvisioned"; + const FULL_NAME: &'static str = "trogonai.agents.agents.v1.AgentProvisioned"; + const TYPE_URL: &'static str = "type.googleapis.com/trogonai.agents.agents.v1.AgentProvisioned"; +} +::buffa::impl_default_view_instance!(AgentProvisionedView); +::buffa::impl_view_reborrow!(AgentProvisionedView); +/** Self-contained, `'static` owned view of a `AgentProvisioned` message. + + Wraps [`::buffa::OwnedView`]`<`[`AgentProvisionedView`]`<'static>>`: the decoded view and the [`::buffa::bytes::Bytes`] buffer it borrows from travel together, so the handle is `'static` and `Send + Sync` — suitable for async handlers, spawned tasks, and anywhere a `'static` bound is required. + + Field accessors return borrows tied to `&self`. Use [`Self::view`] to get the full [`AgentProvisionedView`] when you need struct patterns, iteration helpers, or to pass the view to lifetime-parameterised code.*/ +#[derive(Clone, Debug)] +pub struct AgentProvisionedOwnedView(::buffa::OwnedView>); +impl AgentProvisionedOwnedView { + /// Decode an owned view from a [`::buffa::bytes::Bytes`] buffer. + /// + /// The view borrows directly from the buffer's data; the buffer is + /// retained inside the returned handle. + /// + /// # Errors + /// + /// Returns [`::buffa::DecodeError`] if the buffer contains invalid + /// protobuf data. + pub fn decode( + bytes: ::buffa::bytes::Bytes, + ) -> ::core::result::Result { + ::core::result::Result::Ok( + AgentProvisionedOwnedView(::buffa::OwnedView::decode(bytes)?), + ) + } + /// Decode with custom [`::buffa::DecodeOptions`] (recursion limit, + /// max message size). + /// + /// # Errors + /// + /// Returns [`::buffa::DecodeError`] if the buffer is invalid or + /// exceeds the configured limits. + pub fn decode_with_options( + bytes: ::buffa::bytes::Bytes, + opts: &::buffa::DecodeOptions, + ) -> ::core::result::Result { + ::core::result::Result::Ok( + AgentProvisionedOwnedView( + ::buffa::OwnedView::decode_with_options(bytes, opts)?, + ), + ) + } + /// Build from an owned message via an encode → decode round-trip. + /// + /// # Errors + /// + /// Returns [`::buffa::DecodeError`] if the re-encoded bytes are + /// somehow invalid (should not happen for well-formed messages). + pub fn from_owned( + msg: &super::super::AgentProvisioned, + ) -> ::core::result::Result { + ::core::result::Result::Ok( + AgentProvisionedOwnedView(::buffa::OwnedView::from_owned(msg)?), + ) + } + /// Borrow the full [`AgentProvisionedView`] with its lifetime tied to `&self`. + #[must_use] + pub fn view(&self) -> &AgentProvisionedView<'_> { + self.0.reborrow() + } + /// Convert to the owned message type. + /// + /// # Errors + /// + /// Returns an error if re-materializing preserved unknown fields + /// fails (e.g. the unknown-field limit is exceeded). + pub fn to_owned_message( + &self, + ) -> ::core::result::Result { + self.0.to_owned_message() + } + /// The underlying bytes buffer. + #[must_use] + pub fn bytes(&self) -> &::buffa::bytes::Bytes { + self.0.bytes() + } + /// Consume the handle, returning the underlying bytes buffer. + #[must_use] + pub fn into_bytes(self) -> ::buffa::bytes::Bytes { + self.0.into_bytes() + } + /// Field 1: `agent_id` + #[must_use] + pub fn agent_id(&self) -> &'_ str { + self.0.reborrow().agent_id + } + /// Field 2: `display_name` + #[must_use] + pub fn display_name(&self) -> &'_ str { + self.0.reborrow().display_name + } + /// Placement node ref (bare spelling per the spelling rule: unsuffixed + /// `parent` means placement, kinship is always `parentId`). + /// + /// Field 3: `parent` + #[must_use] + pub fn parent(&self) -> &'_ str { + self.0.reborrow().parent + } + /// Field 4: `configuration` + #[must_use] + pub fn configuration( + &self, + ) -> &::buffa::MessageFieldView< + super::super::__buffa::view::AgentConfigurationView<'_>, + > { + &self.0.reborrow().configuration + } + /// Revision minted by this genesis event, recorded explicitly so no consumer + /// derives it from convention. Always 1; the decider rejects any other value + /// at write time. + /// + /// Field 5: `revision` + #[must_use] + pub fn revision(&self) -> u64 { + self.0.reborrow().revision + } + /// SHA-256 digest of the provisioned revision bundle. + /// + /// Field 6: `content_digest` + #[must_use] + pub fn content_digest(&self) -> &'_ str { + self.0.reborrow().content_digest + } +} +impl ::core::convert::From<::buffa::OwnedView>> +for AgentProvisionedOwnedView { + fn from(inner: ::buffa::OwnedView>) -> Self { + AgentProvisionedOwnedView(inner) + } +} +impl ::core::convert::From +for ::buffa::OwnedView> { + fn from(wrapper: AgentProvisionedOwnedView) -> Self { + wrapper.0 + } +} +impl ::core::convert::AsRef<::buffa::OwnedView>> +for AgentProvisionedOwnedView { + fn as_ref(&self) -> &::buffa::OwnedView> { + &self.0 + } +} +impl ::buffa::HasMessageView for super::super::AgentProvisioned { + type View<'a> = AgentProvisionedView<'a>; + type ViewHandle = AgentProvisionedOwnedView; +} +impl ::serde::Serialize for AgentProvisionedOwnedView { + fn serialize<__S: ::serde::Serializer>( + &self, + __s: __S, + ) -> ::core::result::Result<__S::Ok, __S::Error> { + ::serde::Serialize::serialize(&self.0, __s) + } +} diff --git a/rsworkspace/crates/platform/trogonai-proto/src/gen/trogonai.agents.agents.v1.agent_provisioned.rs b/rsworkspace/crates/platform/trogonai-proto/src/gen/trogonai.agents.agents.v1.agent_provisioned.rs new file mode 100644 index 000000000..36ea302df --- /dev/null +++ b/rsworkspace/crates/platform/trogonai-proto/src/gen/trogonai.agents.agents.v1.agent_provisioned.rs @@ -0,0 +1,211 @@ +// @generated by buffa-codegen. DO NOT EDIT. +// source: trogonai/agents/agents/v1/agent_provisioned.proto + +/// AgentProvisioned is the Agent registry stream's genesis event, minting +/// revision 1. No limits, no rubrics, no grants live here (design Q15/Q16); +/// those are stances on their own planes. +#[derive(Clone, PartialEq, Default)] +#[derive(::serde::Serialize, ::serde::Deserialize)] +#[serde(default)] +pub struct AgentProvisioned { + /// Field 1: `agent_id` + #[serde( + rename = "agentId", + alias = "agent_id", + with = "::buffa::json_helpers::proto_string" + )] + pub agent_id: ::buffa::alloc::string::String, + /// Field 2: `display_name` + #[serde( + rename = "displayName", + alias = "display_name", + with = "::buffa::json_helpers::proto_string" + )] + pub display_name: ::buffa::alloc::string::String, + /// Placement node ref (bare spelling per the spelling rule: unsuffixed + /// `parent` means placement, kinship is always `parentId`). + /// + /// Field 3: `parent` + #[serde(rename = "parent", with = "::buffa::json_helpers::proto_string")] + pub parent: ::buffa::alloc::string::String, + /// Field 4: `configuration` + #[serde(rename = "configuration")] + pub configuration: ::buffa::MessageField, + /// Revision minted by this genesis event, recorded explicitly so no consumer + /// derives it from convention. Always 1; the decider rejects any other value + /// at write time. + /// + /// Field 5: `revision` + #[serde(rename = "revision", with = "::buffa::json_helpers::uint64")] + pub revision: u64, + /// SHA-256 digest of the provisioned revision bundle. + /// + /// Field 6: `content_digest` + #[serde( + rename = "contentDigest", + alias = "content_digest", + with = "::buffa::json_helpers::proto_string" + )] + pub content_digest: ::buffa::alloc::string::String, +} +impl ::core::fmt::Debug for AgentProvisioned { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + f.debug_struct("AgentProvisioned") + .field("agent_id", &self.agent_id) + .field("display_name", &self.display_name) + .field("parent", &self.parent) + .field("configuration", &self.configuration) + .field("revision", &self.revision) + .field("content_digest", &self.content_digest) + .finish() + } +} +impl AgentProvisioned { + /// Protobuf type URL for this message, for use with `Any::pack` and + /// `Any::unpack_if`. + /// + /// Format: `type.googleapis.com/` + pub const TYPE_URL: &'static str = "type.googleapis.com/trogonai.agents.agents.v1.AgentProvisioned"; +} +::buffa::impl_default_instance!(AgentProvisioned); +impl ::buffa::MessageName for AgentProvisioned { + const PACKAGE: &'static str = "trogonai.agents.agents.v1"; + const NAME: &'static str = "AgentProvisioned"; + const FULL_NAME: &'static str = "trogonai.agents.agents.v1.AgentProvisioned"; + const TYPE_URL: &'static str = "type.googleapis.com/trogonai.agents.agents.v1.AgentProvisioned"; +} +impl ::buffa::Message for AgentProvisioned { + /// Returns the total encoded size in bytes. + /// + /// The result is a `u32`; the protobuf specification requires all + /// messages to fit within 2 GiB (2,147,483,647 bytes), so a + /// compliant message will never overflow this type. + #[allow(clippy::let_and_return)] + fn compute_size(&self, __cache: &mut ::buffa::SizeCache) -> u32 { + #[allow(unused_imports)] + use ::buffa::Enumeration as _; + let mut size = 0u32; + size += 1u32 + ::buffa::types::string_encoded_len(&self.agent_id) as u32; + size += 1u32 + ::buffa::types::string_encoded_len(&self.display_name) as u32; + size += 1u32 + ::buffa::types::string_encoded_len(&self.parent) as u32; + if self.configuration.is_set() { + let __slot = __cache.reserve(); + let inner_size = self.configuration.compute_size(__cache); + __cache.set(__slot, inner_size); + size + += 1u32 + ::buffa::encoding::varint_len(inner_size as u64) as u32 + + inner_size; + } + size += 1u32 + ::buffa::types::uint64_encoded_len(self.revision) as u32; + size += 1u32 + ::buffa::types::string_encoded_len(&self.content_digest) as u32; + size + } + fn write_to( + &self, + __cache: &mut ::buffa::SizeCache, + buf: &mut impl ::buffa::bytes::BufMut, + ) { + #[allow(unused_imports)] + use ::buffa::Enumeration as _; + ::buffa::types::put_string_field(1u32, &self.agent_id, buf); + ::buffa::types::put_string_field(2u32, &self.display_name, buf); + ::buffa::types::put_string_field(3u32, &self.parent, buf); + if self.configuration.is_set() { + ::buffa::types::put_len_delimited_header(4u32, __cache.consume_next(), buf); + self.configuration.write_to(__cache, buf); + } + ::buffa::types::put_uint64_field(5u32, self.revision, buf); + ::buffa::types::put_string_field(6u32, &self.content_digest, buf); + } + fn merge_field( + &mut self, + tag: ::buffa::encoding::Tag, + buf: &mut impl ::buffa::bytes::Buf, + ctx: ::buffa::DecodeContext<'_>, + ) -> ::core::result::Result<(), ::buffa::DecodeError> { + #[allow(unused_imports)] + use ::buffa::bytes::Buf as _; + #[allow(unused_imports)] + use ::buffa::Enumeration as _; + match tag.field_number() { + 1u32 => { + ::buffa::encoding::check_wire_type( + tag, + ::buffa::encoding::WireType::LengthDelimited, + )?; + ::buffa::types::merge_string(&mut self.agent_id, buf)?; + } + 2u32 => { + ::buffa::encoding::check_wire_type( + tag, + ::buffa::encoding::WireType::LengthDelimited, + )?; + ::buffa::types::merge_string(&mut self.display_name, buf)?; + } + 3u32 => { + ::buffa::encoding::check_wire_type( + tag, + ::buffa::encoding::WireType::LengthDelimited, + )?; + ::buffa::types::merge_string(&mut self.parent, buf)?; + } + 4u32 => { + ::buffa::encoding::check_wire_type( + tag, + ::buffa::encoding::WireType::LengthDelimited, + )?; + ::buffa::Message::merge_length_delimited( + self.configuration.get_or_insert_default(), + buf, + ctx, + )?; + } + 5u32 => { + ::buffa::encoding::check_wire_type( + tag, + ::buffa::encoding::WireType::Varint, + )?; + self.revision = ::buffa::types::decode_uint64(buf)?; + } + 6u32 => { + ::buffa::encoding::check_wire_type( + tag, + ::buffa::encoding::WireType::LengthDelimited, + )?; + ::buffa::types::merge_string(&mut self.content_digest, buf)?; + } + _ => { + ::buffa::encoding::skip_field_depth(tag, buf, ctx.depth())?; + } + } + ::core::result::Result::Ok(()) + } + fn clear(&mut self) { + self.agent_id.clear(); + self.display_name.clear(); + self.parent.clear(); + self.configuration = ::buffa::MessageField::none(); + self.revision = 0u64; + self.content_digest.clear(); + } +} +impl ::buffa::json_helpers::ProtoElemJson for AgentProvisioned { + fn serialize_proto_json( + v: &Self, + s: S, + ) -> ::core::result::Result { + ::serde::Serialize::serialize(v, s) + } + fn deserialize_proto_json<'de, D: ::serde::Deserializer<'de>>( + d: D, + ) -> ::core::result::Result { + ::deserialize(d) + } +} +#[doc(hidden)] +pub const __AGENT_PROVISIONED_JSON_ANY: ::buffa::type_registry::JsonAnyEntry = ::buffa::type_registry::JsonAnyEntry { + type_url: "type.googleapis.com/trogonai.agents.agents.v1.AgentProvisioned", + to_json: ::buffa::type_registry::any_to_json::, + from_json: ::buffa::type_registry::any_from_json::, + is_wkt: false, +}; diff --git a/rsworkspace/crates/platform/trogonai-proto/src/gen/trogonai.agents.agents.v1.events.__oneof.rs b/rsworkspace/crates/platform/trogonai-proto/src/gen/trogonai.agents.agents.v1.events.__oneof.rs new file mode 100644 index 000000000..3ec7af2ae --- /dev/null +++ b/rsworkspace/crates/platform/trogonai-proto/src/gen/trogonai.agents.agents.v1.events.__oneof.rs @@ -0,0 +1,39 @@ +// @generated by buffa-codegen. DO NOT EDIT. +// source: trogonai/agents/agents/v1/events.proto + +pub mod agent_event { + #[allow(unused_imports)] + use super::*; + #[derive(Clone, PartialEq, Debug)] + pub enum Event { + AgentProvisioned( + ::buffa::alloc::boxed::Box, + ), + } + impl ::buffa::Oneof for Event {} + impl From for Event { + fn from(v: super::super::super::AgentProvisioned) -> Self { + Self::AgentProvisioned(::buffa::alloc::boxed::Box::new(v)) + } + } + impl From for ::core::option::Option { + fn from(v: super::super::super::AgentProvisioned) -> Self { + Self::Some(Event::from(v)) + } + } + impl serde::Serialize for Event { + fn serialize( + &self, + s: S, + ) -> ::core::result::Result { + use serde::ser::SerializeMap; + let mut map = s.serialize_map(Some(1))?; + match self { + Self::AgentProvisioned(v) => { + map.serialize_entry("agentProvisioned", v)?; + } + } + map.end() + } + } +} diff --git a/rsworkspace/crates/platform/trogonai-proto/src/gen/trogonai.agents.agents.v1.events.__view.rs b/rsworkspace/crates/platform/trogonai-proto/src/gen/trogonai.agents.agents.v1.events.__view.rs new file mode 100644 index 000000000..cae4e65df --- /dev/null +++ b/rsworkspace/crates/platform/trogonai-proto/src/gen/trogonai.agents.agents.v1.events.__view.rs @@ -0,0 +1,312 @@ +// @generated by buffa-codegen. DO NOT EDIT. +// source: trogonai/agents/agents/v1/events.proto + +/// AgentEvent is the Agent registry stream's event union. +#[derive(Clone, Debug, Default)] +pub struct AgentEventView<'a> { + pub event: ::core::option::Option< + super::super::__buffa::view::oneof::agent_event::Event<'a>, + >, +} +impl<'a> ::buffa::MessageView<'a> for AgentEventView<'a> { + type Owned = super::super::AgentEvent; + fn decode_view(buf: &'a [u8]) -> ::core::result::Result { + let __limit = ::core::cell::Cell::new(::buffa::DEFAULT_UNKNOWN_FIELD_LIMIT); + ::decode_view_ctx( + buf, + ::buffa::DecodeContext::new(::buffa::RECURSION_LIMIT, &__limit), + ) + } + fn decode_view_with_ctx( + buf: &'a [u8], + ctx: ::buffa::DecodeContext<'_>, + ) -> ::core::result::Result { + ::decode_view_ctx(buf, ctx) + } + fn merge_view_field( + &mut self, + tag: ::buffa::encoding::Tag, + cur: &'a [u8], + _before_tag: &'a [u8], + ctx: ::buffa::DecodeContext<'_>, + ) -> ::core::result::Result<&'a [u8], ::buffa::DecodeError> { + let _ = ctx; + #[allow(unused_variables)] + let view = self; + let mut cur = cur; + match tag.field_number() { + 1u32 => { + ::buffa::encoding::check_wire_type( + tag, + ::buffa::encoding::WireType::LengthDelimited, + )?; + let __sub_ctx = ctx.descend()?; + let sub = ::buffa::types::borrow_bytes(&mut cur)?; + if let Some( + super::super::__buffa::view::oneof::agent_event::Event::AgentProvisioned( + ref mut existing, + ), + ) = view.event + { + ::buffa::MessageView::merge_into_view( + &mut **existing, + sub, + __sub_ctx, + )?; + } else { + view.event = Some( + super::super::__buffa::view::oneof::agent_event::Event::AgentProvisioned( + ::buffa::alloc::boxed::Box::new( + ::decode_view_ctx( + sub, + __sub_ctx, + )?, + ), + ), + ); + } + } + _ => { + ::buffa::encoding::skip_field_depth(tag, &mut cur, ctx.depth())?; + } + } + ::core::result::Result::Ok(cur) + } + fn to_owned_message( + &self, + ) -> ::core::result::Result { + self.to_owned_from_source(None) + } + #[allow(clippy::useless_conversion, clippy::needless_update)] + fn to_owned_from_source( + &self, + __buffa_src: ::core::option::Option<&::buffa::bytes::Bytes>, + ) -> ::core::result::Result { + #[allow(unused_imports)] + use ::buffa::alloc::string::ToString as _; + let _ = __buffa_src; + ::core::result::Result::Ok(super::super::AgentEvent { + event: match self.event.as_ref() { + ::core::option::Option::Some(v) => { + ::core::option::Option::Some( + match v { + super::super::__buffa::view::oneof::agent_event::Event::AgentProvisioned( + v, + ) => { + super::super::__buffa::oneof::agent_event::Event::AgentProvisioned( + ::buffa::alloc::boxed::Box::new( + v.to_owned_from_source(__buffa_src)?, + ), + ) + } + }, + ) + } + ::core::option::Option::None => ::core::option::Option::None, + }, + ..::core::default::Default::default() + }) + } +} +impl<'a> ::buffa::ViewEncode<'a> for AgentEventView<'a> { + #[allow(clippy::needless_borrow, clippy::let_and_return)] + fn compute_size(&self, __cache: &mut ::buffa::SizeCache) -> u32 { + #[allow(unused_imports)] + use ::buffa::Enumeration as _; + let mut size = 0u32; + if let ::core::option::Option::Some(ref v) = self.event { + match v { + super::super::__buffa::view::oneof::agent_event::Event::AgentProvisioned( + x, + ) => { + let __slot = __cache.reserve(); + let inner = x.compute_size(__cache); + __cache.set(__slot, inner); + size + += 1u32 + ::buffa::encoding::varint_len(inner as u64) as u32 + + inner; + } + } + } + size + } + #[allow(clippy::needless_borrow)] + fn write_to( + &self, + __cache: &mut ::buffa::SizeCache, + buf: &mut impl ::buffa::bytes::BufMut, + ) { + #[allow(unused_imports)] + use ::buffa::Enumeration as _; + if let ::core::option::Option::Some(ref v) = self.event { + match v { + super::super::__buffa::view::oneof::agent_event::Event::AgentProvisioned( + x, + ) => { + ::buffa::types::put_len_delimited_header( + 1u32, + __cache.consume_next(), + buf, + ); + x.write_to(__cache, buf); + } + } + } + } +} +/// Serializes this view as protobuf JSON. +/// +/// Implicit-presence fields with default values are omitted, `required` +/// fields are always emitted, explicit-presence (`optional`) fields are +/// emitted only when set, bytes fields are base64-encoded, and enum +/// values are their proto name strings. +/// +/// This impl uses `serialize_map(None)` because the number of emitted +/// fields depends on default-omission rules; serializers that require +/// known map lengths (e.g. `bincode`) will return a runtime error. +/// Use the owned message type for those formats. +impl<'__a> ::serde::Serialize for AgentEventView<'__a> { + fn serialize<__S: ::serde::Serializer>( + &self, + __s: __S, + ) -> ::core::result::Result<__S::Ok, __S::Error> { + use ::serde::ser::SerializeMap as _; + let mut __map = __s.serialize_map(::core::option::Option::None)?; + if let ::core::option::Option::Some(ref __ov) = self.event { + match __ov { + super::super::__buffa::view::oneof::agent_event::Event::AgentProvisioned( + v, + ) => { + __map.serialize_entry("agentProvisioned", v)?; + } + } + } + __map.end() + } +} +impl<'a> ::buffa::MessageName for AgentEventView<'a> { + const PACKAGE: &'static str = "trogonai.agents.agents.v1"; + const NAME: &'static str = "AgentEvent"; + const FULL_NAME: &'static str = "trogonai.agents.agents.v1.AgentEvent"; + const TYPE_URL: &'static str = "type.googleapis.com/trogonai.agents.agents.v1.AgentEvent"; +} +::buffa::impl_default_view_instance!(AgentEventView); +::buffa::impl_view_reborrow!(AgentEventView); +/** Self-contained, `'static` owned view of a `AgentEvent` message. + + Wraps [`::buffa::OwnedView`]`<`[`AgentEventView`]`<'static>>`: the decoded view and the [`::buffa::bytes::Bytes`] buffer it borrows from travel together, so the handle is `'static` and `Send + Sync` — suitable for async handlers, spawned tasks, and anywhere a `'static` bound is required. + + Field accessors return borrows tied to `&self`. Use [`Self::view`] to get the full [`AgentEventView`] when you need struct patterns, iteration helpers, or to pass the view to lifetime-parameterised code.*/ +#[derive(Clone, Debug)] +pub struct AgentEventOwnedView(::buffa::OwnedView>); +impl AgentEventOwnedView { + /// Decode an owned view from a [`::buffa::bytes::Bytes`] buffer. + /// + /// The view borrows directly from the buffer's data; the buffer is + /// retained inside the returned handle. + /// + /// # Errors + /// + /// Returns [`::buffa::DecodeError`] if the buffer contains invalid + /// protobuf data. + pub fn decode( + bytes: ::buffa::bytes::Bytes, + ) -> ::core::result::Result { + ::core::result::Result::Ok( + AgentEventOwnedView(::buffa::OwnedView::decode(bytes)?), + ) + } + /// Decode with custom [`::buffa::DecodeOptions`] (recursion limit, + /// max message size). + /// + /// # Errors + /// + /// Returns [`::buffa::DecodeError`] if the buffer is invalid or + /// exceeds the configured limits. + pub fn decode_with_options( + bytes: ::buffa::bytes::Bytes, + opts: &::buffa::DecodeOptions, + ) -> ::core::result::Result { + ::core::result::Result::Ok( + AgentEventOwnedView(::buffa::OwnedView::decode_with_options(bytes, opts)?), + ) + } + /// Build from an owned message via an encode → decode round-trip. + /// + /// # Errors + /// + /// Returns [`::buffa::DecodeError`] if the re-encoded bytes are + /// somehow invalid (should not happen for well-formed messages). + pub fn from_owned( + msg: &super::super::AgentEvent, + ) -> ::core::result::Result { + ::core::result::Result::Ok( + AgentEventOwnedView(::buffa::OwnedView::from_owned(msg)?), + ) + } + /// Borrow the full [`AgentEventView`] with its lifetime tied to `&self`. + #[must_use] + pub fn view(&self) -> &AgentEventView<'_> { + self.0.reborrow() + } + /// Convert to the owned message type. + /// + /// # Errors + /// + /// Returns an error if re-materializing preserved unknown fields + /// fails (e.g. the unknown-field limit is exceeded). + pub fn to_owned_message( + &self, + ) -> ::core::result::Result { + self.0.to_owned_message() + } + /// The underlying bytes buffer. + #[must_use] + pub fn bytes(&self) -> &::buffa::bytes::Bytes { + self.0.bytes() + } + /// Consume the handle, returning the underlying bytes buffer. + #[must_use] + pub fn into_bytes(self) -> ::buffa::bytes::Bytes { + self.0.into_bytes() + } + /// Oneof `event`. + #[must_use] + pub fn event( + &self, + ) -> ::core::option::Option< + &super::super::__buffa::view::oneof::agent_event::Event<'_>, + > { + self.0.reborrow().event.as_ref() + } +} +impl ::core::convert::From<::buffa::OwnedView>> +for AgentEventOwnedView { + fn from(inner: ::buffa::OwnedView>) -> Self { + AgentEventOwnedView(inner) + } +} +impl ::core::convert::From +for ::buffa::OwnedView> { + fn from(wrapper: AgentEventOwnedView) -> Self { + wrapper.0 + } +} +impl ::core::convert::AsRef<::buffa::OwnedView>> +for AgentEventOwnedView { + fn as_ref(&self) -> &::buffa::OwnedView> { + &self.0 + } +} +impl ::buffa::HasMessageView for super::super::AgentEvent { + type View<'a> = AgentEventView<'a>; + type ViewHandle = AgentEventOwnedView; +} +impl ::serde::Serialize for AgentEventOwnedView { + fn serialize<__S: ::serde::Serializer>( + &self, + __s: __S, + ) -> ::core::result::Result<__S::Ok, __S::Error> { + ::serde::Serialize::serialize(&self.0, __s) + } +} diff --git a/rsworkspace/crates/platform/trogonai-proto/src/gen/trogonai.agents.agents.v1.events.__view_oneof.rs b/rsworkspace/crates/platform/trogonai-proto/src/gen/trogonai.agents.agents.v1.events.__view_oneof.rs new file mode 100644 index 000000000..ce52e18e7 --- /dev/null +++ b/rsworkspace/crates/platform/trogonai-proto/src/gen/trogonai.agents.agents.v1.events.__view_oneof.rs @@ -0,0 +1,15 @@ +// @generated by buffa-codegen. DO NOT EDIT. +// source: trogonai/agents/agents/v1/events.proto + +pub mod agent_event { + #[allow(unused_imports)] + use super::*; + #[derive(Clone, Debug)] + pub enum Event<'a> { + AgentProvisioned( + ::buffa::alloc::boxed::Box< + super::super::super::super::__buffa::view::AgentProvisionedView<'a>, + >, + ), + } +} diff --git a/rsworkspace/crates/platform/trogonai-proto/src/gen/trogonai.agents.agents.v1.events.rs b/rsworkspace/crates/platform/trogonai-proto/src/gen/trogonai.agents.agents.v1.events.rs new file mode 100644 index 000000000..ab9d72fe0 --- /dev/null +++ b/rsworkspace/crates/platform/trogonai-proto/src/gen/trogonai.agents.agents.v1.events.rs @@ -0,0 +1,203 @@ +// @generated by buffa-codegen. DO NOT EDIT. +// source: trogonai/agents/agents/v1/events.proto + +/// AgentEvent is the Agent registry stream's event union. +#[derive(Clone, PartialEq, Default)] +#[derive(::serde::Serialize)] +#[serde(default)] +pub struct AgentEvent { + #[serde(flatten)] + pub event: ::core::option::Option<__buffa::oneof::agent_event::Event>, +} +impl ::core::fmt::Debug for AgentEvent { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + f.debug_struct("AgentEvent").field("event", &self.event).finish() + } +} +impl AgentEvent { + /// Protobuf type URL for this message, for use with `Any::pack` and + /// `Any::unpack_if`. + /// + /// Format: `type.googleapis.com/` + pub const TYPE_URL: &'static str = "type.googleapis.com/trogonai.agents.agents.v1.AgentEvent"; +} +::buffa::impl_default_instance!(AgentEvent); +impl ::buffa::MessageName for AgentEvent { + const PACKAGE: &'static str = "trogonai.agents.agents.v1"; + const NAME: &'static str = "AgentEvent"; + const FULL_NAME: &'static str = "trogonai.agents.agents.v1.AgentEvent"; + const TYPE_URL: &'static str = "type.googleapis.com/trogonai.agents.agents.v1.AgentEvent"; +} +impl ::buffa::Message for AgentEvent { + /// Returns the total encoded size in bytes. + /// + /// The result is a `u32`; the protobuf specification requires all + /// messages to fit within 2 GiB (2,147,483,647 bytes), so a + /// compliant message will never overflow this type. + #[allow(clippy::let_and_return)] + fn compute_size(&self, __cache: &mut ::buffa::SizeCache) -> u32 { + #[allow(unused_imports)] + use ::buffa::Enumeration as _; + let mut size = 0u32; + if let ::core::option::Option::Some(ref v) = self.event { + match v { + __buffa::oneof::agent_event::Event::AgentProvisioned(x) => { + let __slot = __cache.reserve(); + let inner = x.compute_size(__cache); + __cache.set(__slot, inner); + size + += 1u32 + ::buffa::encoding::varint_len(inner as u64) as u32 + + inner; + } + } + } + size + } + fn write_to( + &self, + __cache: &mut ::buffa::SizeCache, + buf: &mut impl ::buffa::bytes::BufMut, + ) { + #[allow(unused_imports)] + use ::buffa::Enumeration as _; + if let ::core::option::Option::Some(ref v) = self.event { + match v { + __buffa::oneof::agent_event::Event::AgentProvisioned(x) => { + ::buffa::types::put_len_delimited_header( + 1u32, + __cache.consume_next(), + buf, + ); + x.write_to(__cache, buf); + } + } + } + } + fn merge_field( + &mut self, + tag: ::buffa::encoding::Tag, + buf: &mut impl ::buffa::bytes::Buf, + ctx: ::buffa::DecodeContext<'_>, + ) -> ::core::result::Result<(), ::buffa::DecodeError> { + #[allow(unused_imports)] + use ::buffa::bytes::Buf as _; + #[allow(unused_imports)] + use ::buffa::Enumeration as _; + match tag.field_number() { + 1u32 => { + ::buffa::encoding::check_wire_type( + tag, + ::buffa::encoding::WireType::LengthDelimited, + )?; + if let ::core::option::Option::Some( + __buffa::oneof::agent_event::Event::AgentProvisioned( + ref mut existing, + ), + ) = self.event + { + ::buffa::Message::merge_length_delimited(&mut **existing, buf, ctx)?; + } else { + let mut val = ::core::default::Default::default(); + ::buffa::Message::merge_length_delimited(&mut val, buf, ctx)?; + self.event = ::core::option::Option::Some( + __buffa::oneof::agent_event::Event::AgentProvisioned( + ::buffa::alloc::boxed::Box::new(val), + ), + ); + } + } + _ => { + ::buffa::encoding::skip_field_depth(tag, buf, ctx.depth())?; + } + } + ::core::result::Result::Ok(()) + } + fn clear(&mut self) { + self.event = ::core::option::Option::None; + } +} +impl<'de> serde::Deserialize<'de> for AgentEvent { + fn deserialize>( + d: D, + ) -> ::core::result::Result { + struct _V; + impl<'de> serde::de::Visitor<'de> for _V { + type Value = AgentEvent; + fn expecting(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str("struct AgentEvent") + } + #[allow(clippy::field_reassign_with_default)] + fn visit_map>( + self, + mut map: A, + ) -> ::core::result::Result { + let mut __oneof_event: ::core::option::Option< + __buffa::oneof::agent_event::Event, + > = None; + while let Some(key) = map.next_key::<::buffa::alloc::string::String>()? { + match key.as_str() { + "agentProvisioned" | "agent_provisioned" => { + let v: ::core::option::Option = map + .next_value_seed( + ::buffa::json_helpers::NullableDeserializeSeed( + ::buffa::json_helpers::DefaultDeserializeSeed::< + AgentProvisioned, + >::new(), + ), + )?; + if let Some(v) = v { + if __oneof_event.is_some() { + return Err( + serde::de::Error::custom( + "multiple oneof fields set for 'event'", + ), + ); + } + __oneof_event = Some( + __buffa::oneof::agent_event::Event::AgentProvisioned( + ::buffa::alloc::boxed::Box::new(v), + ), + ); + } + } + _ => { + map.next_value::()?; + } + } + } + let mut __r = ::default(); + __r.event = __oneof_event; + Ok(__r) + } + } + d.deserialize_map(_V) + } +} +impl ::buffa::json_helpers::ProtoElemJson for AgentEvent { + fn serialize_proto_json( + v: &Self, + s: S, + ) -> ::core::result::Result { + ::serde::Serialize::serialize(v, s) + } + fn deserialize_proto_json<'de, D: ::serde::Deserializer<'de>>( + d: D, + ) -> ::core::result::Result { + ::deserialize(d) + } +} +#[doc(hidden)] +pub const __AGENT_EVENT_JSON_ANY: ::buffa::type_registry::JsonAnyEntry = ::buffa::type_registry::JsonAnyEntry { + type_url: "type.googleapis.com/trogonai.agents.agents.v1.AgentEvent", + to_json: ::buffa::type_registry::any_to_json::, + from_json: ::buffa::type_registry::any_from_json::, + is_wkt: false, +}; +pub mod agent_event { + #[allow(unused_imports)] + use super::*; + #[doc(inline)] + pub use super::__buffa::oneof::agent_event::Event; + #[doc(inline)] + pub use super::__buffa::view::oneof::agent_event::Event as EventView; +} diff --git a/rsworkspace/crates/platform/trogonai-proto/src/gen/trogonai.agents.agents.v1.mod.rs b/rsworkspace/crates/platform/trogonai-proto/src/gen/trogonai.agents.agents.v1.mod.rs new file mode 100644 index 000000000..8d3524811 --- /dev/null +++ b/rsworkspace/crates/platform/trogonai-proto/src/gen/trogonai.agents.agents.v1.mod.rs @@ -0,0 +1,57 @@ +// @generated by buffa-codegen. DO NOT EDIT. + +include!("trogonai.agents.agents.v1.agent.rs"); +include!("trogonai.agents.agents.v1.agent_provisioned.rs"); +include!("trogonai.agents.agents.v1.events.rs"); +#[allow( + non_camel_case_types, + dead_code, + unused_imports, + unused_qualifications, + clippy::derivable_impls, + clippy::match_single_binding, + clippy::uninlined_format_args, + clippy::doc_lazy_continuation, + clippy::module_inception +)] +pub mod __buffa { + #[allow(unused_imports)] + use super::*; + pub mod view { + #[allow(unused_imports)] + use super::*; + include!("trogonai.agents.agents.v1.agent.__view.rs"); + include!("trogonai.agents.agents.v1.agent_provisioned.__view.rs"); + include!("trogonai.agents.agents.v1.events.__view.rs"); + pub mod oneof { + #[allow(unused_imports)] + use super::*; + include!("trogonai.agents.agents.v1.events.__view_oneof.rs"); + } + } + pub mod oneof { + #[allow(unused_imports)] + use super::*; + include!("trogonai.agents.agents.v1.events.__oneof.rs"); + } + /// Register this package's `Any` type entries and extension entries. + pub fn register_types(reg: &mut ::buffa::type_registry::TypeRegistry) { + reg.register_json_any(super::__AGENT_CONFIGURATION_JSON_ANY); + reg.register_json_any(super::__AGENT_PROVISIONED_JSON_ANY); + reg.register_json_any(super::__AGENT_EVENT_JSON_ANY); + } +} +#[doc(inline)] +pub use self::__buffa::view::AgentConfigurationView; +#[doc(inline)] +pub use self::__buffa::view::AgentConfigurationOwnedView; +#[doc(inline)] +pub use self::__buffa::view::AgentProvisionedView; +#[doc(inline)] +pub use self::__buffa::view::AgentProvisionedOwnedView; +#[doc(inline)] +pub use self::__buffa::view::AgentEventView; +#[doc(inline)] +pub use self::__buffa::view::AgentEventOwnedView; +#[doc(inline)] +pub use self::__buffa::register_types; diff --git a/rsworkspace/crates/platform/trogonai-proto/src/gen/trogonai.session.sessions.v1alpha1.session_renamed.__view.rs b/rsworkspace/crates/platform/trogonai-proto/src/gen/trogonai.session.sessions.v1alpha1.session_renamed.__view.rs index bf93a32de..bb2817ed8 100644 --- a/rsworkspace/crates/platform/trogonai-proto/src/gen/trogonai.session.sessions.v1alpha1.session_renamed.__view.rs +++ b/rsworkspace/crates/platform/trogonai-proto/src/gen/trogonai.session.sessions.v1alpha1.session_renamed.__view.rs @@ -1,15 +1,15 @@ // @generated by buffa-codegen. DO NOT EDIT. // source: trogonai/session/sessions/v1alpha1/session_renamed.proto -/// SessionRenamed records a change to the session's display title: reversible +/// SessionRenamed records a change to the session's display name: reversible /// organization state, distinct from the terminal SessionHidden (D11). It is a /// commuting happened-fact (WRITE_PRECONDITION = Any). #[derive(Clone, Debug, Default)] pub struct SessionRenamedView<'a> { /// Field 1: `session_id` pub session_id: &'a str, - /// Field 2: `title` - pub title: &'a str, + /// Field 2: `display_name` + pub display_name: &'a str, #[doc(hidden)] pub __buffa_required_seen_0: u64, } @@ -22,12 +22,12 @@ Distinguishes a field that was absent from one explicitly encoded with its defau pub const fn has_session_id(&self) -> bool { self.__buffa_required_seen_0 & 1u64 != 0 } - /**Whether required field `title` was present on the wire. + /**Whether required field `display_name` was present on the wire. Distinguishes a field that was absent from one explicitly encoded with its default value (required scalar fields are stored as bare, non-`Option` types, so the value alone cannot tell the two apart). Presence is recorded only by the wire decoder: a default or hand-built view reports `false`. Encoding is unaffected — required fields are always written.*/ #[must_use] #[inline] - pub const fn has_title(&self) -> bool { + pub const fn has_display_name(&self) -> bool { self.__buffa_required_seen_0 & 2u64 != 0 } } @@ -71,7 +71,7 @@ impl<'a> ::buffa::MessageView<'a> for SessionRenamedView<'a> { tag, ::buffa::encoding::WireType::LengthDelimited, )?; - view.title = ::buffa::types::borrow_str(&mut cur)?; + view.display_name = ::buffa::types::borrow_str(&mut cur)?; view.__buffa_required_seen_0 |= 2u64; } _ => { @@ -95,7 +95,7 @@ impl<'a> ::buffa::MessageView<'a> for SessionRenamedView<'a> { let _ = __buffa_src; ::core::result::Result::Ok(super::super::SessionRenamed { session_id: self.session_id.to_string(), - title: self.title.to_string(), + display_name: self.display_name.to_string(), ..::core::default::Default::default() }) } @@ -107,7 +107,7 @@ impl<'a> ::buffa::ViewEncode<'a> for SessionRenamedView<'a> { use ::buffa::Enumeration as _; let mut size = 0u32; size += 1u32 + ::buffa::types::string_encoded_len(&self.session_id) as u32; - size += 1u32 + ::buffa::types::string_encoded_len(&self.title) as u32; + size += 1u32 + ::buffa::types::string_encoded_len(&self.display_name) as u32; size } #[allow(clippy::needless_borrow)] @@ -119,7 +119,7 @@ impl<'a> ::buffa::ViewEncode<'a> for SessionRenamedView<'a> { #[allow(unused_imports)] use ::buffa::Enumeration as _; ::buffa::types::put_string_field(1u32, &self.session_id, buf); - ::buffa::types::put_string_field(2u32, &self.title, buf); + ::buffa::types::put_string_field(2u32, &self.display_name, buf); } } /// Serializes this view as protobuf JSON. @@ -144,7 +144,7 @@ impl<'__a> ::serde::Serialize for SessionRenamedView<'__a> { __map.serialize_entry("sessionId", self.session_id)?; } { - __map.serialize_entry("title", self.title)?; + __map.serialize_entry("displayName", self.display_name)?; } __map.end() } @@ -242,10 +242,10 @@ impl SessionRenamedOwnedView { pub fn session_id(&self) -> &'_ str { self.0.reborrow().session_id } - /// Field 2: `title` + /// Field 2: `display_name` #[must_use] - pub fn title(&self) -> &'_ str { - self.0.reborrow().title + pub fn display_name(&self) -> &'_ str { + self.0.reborrow().display_name } } impl ::core::convert::From<::buffa::OwnedView>> diff --git a/rsworkspace/crates/platform/trogonai-proto/src/gen/trogonai.session.sessions.v1alpha1.session_renamed.rs b/rsworkspace/crates/platform/trogonai-proto/src/gen/trogonai.session.sessions.v1alpha1.session_renamed.rs index 7fa237aae..f8935ebae 100644 --- a/rsworkspace/crates/platform/trogonai-proto/src/gen/trogonai.session.sessions.v1alpha1.session_renamed.rs +++ b/rsworkspace/crates/platform/trogonai-proto/src/gen/trogonai.session.sessions.v1alpha1.session_renamed.rs @@ -1,7 +1,7 @@ // @generated by buffa-codegen. DO NOT EDIT. // source: trogonai/session/sessions/v1alpha1/session_renamed.proto -/// SessionRenamed records a change to the session's display title: reversible +/// SessionRenamed records a change to the session's display name: reversible /// organization state, distinct from the terminal SessionHidden (D11). It is a /// commuting happened-fact (WRITE_PRECONDITION = Any). #[derive(Clone, PartialEq, Default)] @@ -15,15 +15,19 @@ pub struct SessionRenamed { with = "::buffa::json_helpers::proto_string" )] pub session_id: ::buffa::alloc::string::String, - /// Field 2: `title` - #[serde(rename = "title", with = "::buffa::json_helpers::proto_string")] - pub title: ::buffa::alloc::string::String, + /// Field 2: `display_name` + #[serde( + rename = "displayName", + alias = "display_name", + with = "::buffa::json_helpers::proto_string" + )] + pub display_name: ::buffa::alloc::string::String, } impl ::core::fmt::Debug for SessionRenamed { fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { f.debug_struct("SessionRenamed") .field("session_id", &self.session_id) - .field("title", &self.title) + .field("display_name", &self.display_name) .finish() } } @@ -53,7 +57,7 @@ impl ::buffa::Message for SessionRenamed { use ::buffa::Enumeration as _; let mut size = 0u32; size += 1u32 + ::buffa::types::string_encoded_len(&self.session_id) as u32; - size += 1u32 + ::buffa::types::string_encoded_len(&self.title) as u32; + size += 1u32 + ::buffa::types::string_encoded_len(&self.display_name) as u32; size } fn write_to( @@ -64,7 +68,7 @@ impl ::buffa::Message for SessionRenamed { #[allow(unused_imports)] use ::buffa::Enumeration as _; ::buffa::types::put_string_field(1u32, &self.session_id, buf); - ::buffa::types::put_string_field(2u32, &self.title, buf); + ::buffa::types::put_string_field(2u32, &self.display_name, buf); } fn merge_field( &mut self, @@ -89,7 +93,7 @@ impl ::buffa::Message for SessionRenamed { tag, ::buffa::encoding::WireType::LengthDelimited, )?; - ::buffa::types::merge_string(&mut self.title, buf)?; + ::buffa::types::merge_string(&mut self.display_name, buf)?; } _ => { ::buffa::encoding::skip_field_depth(tag, buf, ctx.depth())?; @@ -99,7 +103,7 @@ impl ::buffa::Message for SessionRenamed { } fn clear(&mut self) { self.session_id.clear(); - self.title.clear(); + self.display_name.clear(); } } impl ::buffa::json_helpers::ProtoElemJson for SessionRenamed { diff --git a/rsworkspace/crates/platform/trogonai-proto/src/gen/trogonai.session.sessions.v1alpha1.tool_call_requested.__view.rs b/rsworkspace/crates/platform/trogonai-proto/src/gen/trogonai.session.sessions.v1alpha1.tool_call_requested.__view.rs index 59c52f2de..bdaa2b45d 100644 --- a/rsworkspace/crates/platform/trogonai-proto/src/gen/trogonai.session.sessions.v1alpha1.tool_call_requested.__view.rs +++ b/rsworkspace/crates/platform/trogonai-proto/src/gen/trogonai.session.sessions.v1alpha1.tool_call_requested.__view.rs @@ -3,7 +3,7 @@ /// ToolCallRequested records that a tool call was requested, in arrival order. /// It owns the execution-request record: what the platform was asked to run -/// (name, input_json as dispatched, operation link), distinct from +/// (tool_name, input_json as dispatched, operation link), distinct from /// ToolUseBlock/ToolResultBlock, which own the provider-visible transcript /// form. The two join by tool_call_id/tool_use_id; equality is expected but /// not structurally required, since normalization may differ. This event @@ -19,8 +19,8 @@ pub struct ToolCallRequestedView<'a> { pub tool_call_id: &'a str, /// Field 3: `tool_execution_id` pub tool_execution_id: &'a str, - /// Field 4: `name` - pub name: &'a str, + /// Field 4: `tool_name` + pub tool_name: &'a str, /// Field 5: `input_json` pub input_json: &'a str, /// Field 6: `parent_tool_use_id` @@ -59,12 +59,12 @@ Distinguishes a field that was absent from one explicitly encoded with its defau pub const fn has_tool_execution_id(&self) -> bool { self.__buffa_required_seen_0 & 4u64 != 0 } - /**Whether required field `name` was present on the wire. + /**Whether required field `tool_name` was present on the wire. Distinguishes a field that was absent from one explicitly encoded with its default value (required scalar fields are stored as bare, non-`Option` types, so the value alone cannot tell the two apart). Presence is recorded only by the wire decoder: a default or hand-built view reports `false`. Encoding is unaffected — required fields are always written.*/ #[must_use] #[inline] - pub const fn has_name(&self) -> bool { + pub const fn has_tool_name(&self) -> bool { self.__buffa_required_seen_0 & 8u64 != 0 } /**Whether required field `input_json` was present on the wire. @@ -132,7 +132,7 @@ impl<'a> ::buffa::MessageView<'a> for ToolCallRequestedView<'a> { tag, ::buffa::encoding::WireType::LengthDelimited, )?; - view.name = ::buffa::types::borrow_str(&mut cur)?; + view.tool_name = ::buffa::types::borrow_str(&mut cur)?; view.__buffa_required_seen_0 |= 8u64; } 5u32 => { @@ -180,7 +180,7 @@ impl<'a> ::buffa::MessageView<'a> for ToolCallRequestedView<'a> { session_id: self.session_id.to_string(), tool_call_id: self.tool_call_id.to_string(), tool_execution_id: self.tool_execution_id.to_string(), - name: self.name.to_string(), + tool_name: self.tool_name.to_string(), input_json: self.input_json.to_string(), parent_tool_use_id: self.parent_tool_use_id.map(|s| s.to_string()), operation_id: self.operation_id.map(|s| s.to_string()), @@ -198,7 +198,7 @@ impl<'a> ::buffa::ViewEncode<'a> for ToolCallRequestedView<'a> { size += 1u32 + ::buffa::types::string_encoded_len(&self.tool_call_id) as u32; size += 1u32 + ::buffa::types::string_encoded_len(&self.tool_execution_id) as u32; - size += 1u32 + ::buffa::types::string_encoded_len(&self.name) as u32; + size += 1u32 + ::buffa::types::string_encoded_len(&self.tool_name) as u32; size += 1u32 + ::buffa::types::string_encoded_len(&self.input_json) as u32; if let Some(ref v) = self.parent_tool_use_id { size += 1u32 + ::buffa::types::string_encoded_len(v) as u32; @@ -219,7 +219,7 @@ impl<'a> ::buffa::ViewEncode<'a> for ToolCallRequestedView<'a> { ::buffa::types::put_string_field(1u32, &self.session_id, buf); ::buffa::types::put_string_field(2u32, &self.tool_call_id, buf); ::buffa::types::put_string_field(3u32, &self.tool_execution_id, buf); - ::buffa::types::put_string_field(4u32, &self.name, buf); + ::buffa::types::put_string_field(4u32, &self.tool_name, buf); ::buffa::types::put_string_field(5u32, &self.input_json, buf); if let Some(ref v) = self.parent_tool_use_id { ::buffa::types::put_string_field(6u32, v, buf); @@ -257,7 +257,7 @@ impl<'__a> ::serde::Serialize for ToolCallRequestedView<'__a> { __map.serialize_entry("toolExecutionId", self.tool_execution_id)?; } { - __map.serialize_entry("name", self.name)?; + __map.serialize_entry("toolName", self.tool_name)?; } { __map.serialize_entry("inputJson", self.input_json)?; @@ -376,10 +376,10 @@ impl ToolCallRequestedOwnedView { pub fn tool_execution_id(&self) -> &'_ str { self.0.reborrow().tool_execution_id } - /// Field 4: `name` + /// Field 4: `tool_name` #[must_use] - pub fn name(&self) -> &'_ str { - self.0.reborrow().name + pub fn tool_name(&self) -> &'_ str { + self.0.reborrow().tool_name } /// Field 5: `input_json` #[must_use] diff --git a/rsworkspace/crates/platform/trogonai-proto/src/gen/trogonai.session.sessions.v1alpha1.tool_call_requested.rs b/rsworkspace/crates/platform/trogonai-proto/src/gen/trogonai.session.sessions.v1alpha1.tool_call_requested.rs index d750880f9..392b168d6 100644 --- a/rsworkspace/crates/platform/trogonai-proto/src/gen/trogonai.session.sessions.v1alpha1.tool_call_requested.rs +++ b/rsworkspace/crates/platform/trogonai-proto/src/gen/trogonai.session.sessions.v1alpha1.tool_call_requested.rs @@ -3,7 +3,7 @@ /// ToolCallRequested records that a tool call was requested, in arrival order. /// It owns the execution-request record: what the platform was asked to run -/// (name, input_json as dispatched, operation link), distinct from +/// (tool_name, input_json as dispatched, operation link), distinct from /// ToolUseBlock/ToolResultBlock, which own the provider-visible transcript /// form. The two join by tool_call_id/tool_use_id; equality is expected but /// not structurally required, since normalization may differ. This event @@ -36,9 +36,13 @@ pub struct ToolCallRequested { with = "::buffa::json_helpers::proto_string" )] pub tool_execution_id: ::buffa::alloc::string::String, - /// Field 4: `name` - #[serde(rename = "name", with = "::buffa::json_helpers::proto_string")] - pub name: ::buffa::alloc::string::String, + /// Field 4: `tool_name` + #[serde( + rename = "toolName", + alias = "tool_name", + with = "::buffa::json_helpers::proto_string" + )] + pub tool_name: ::buffa::alloc::string::String, /// Field 5: `input_json` #[serde( rename = "inputJson", @@ -71,7 +75,7 @@ impl ::core::fmt::Debug for ToolCallRequested { .field("session_id", &self.session_id) .field("tool_call_id", &self.tool_call_id) .field("tool_execution_id", &self.tool_execution_id) - .field("name", &self.name) + .field("tool_name", &self.tool_name) .field("input_json", &self.input_json) .field("parent_tool_use_id", &self.parent_tool_use_id) .field("operation_id", &self.operation_id) @@ -129,7 +133,7 @@ impl ::buffa::Message for ToolCallRequested { size += 1u32 + ::buffa::types::string_encoded_len(&self.tool_call_id) as u32; size += 1u32 + ::buffa::types::string_encoded_len(&self.tool_execution_id) as u32; - size += 1u32 + ::buffa::types::string_encoded_len(&self.name) as u32; + size += 1u32 + ::buffa::types::string_encoded_len(&self.tool_name) as u32; size += 1u32 + ::buffa::types::string_encoded_len(&self.input_json) as u32; if let Some(ref v) = self.parent_tool_use_id { size += 1u32 + ::buffa::types::string_encoded_len(v) as u32; @@ -149,7 +153,7 @@ impl ::buffa::Message for ToolCallRequested { ::buffa::types::put_string_field(1u32, &self.session_id, buf); ::buffa::types::put_string_field(2u32, &self.tool_call_id, buf); ::buffa::types::put_string_field(3u32, &self.tool_execution_id, buf); - ::buffa::types::put_string_field(4u32, &self.name, buf); + ::buffa::types::put_string_field(4u32, &self.tool_name, buf); ::buffa::types::put_string_field(5u32, &self.input_json, buf); if let Some(ref v) = self.parent_tool_use_id { ::buffa::types::put_string_field(6u32, v, buf); @@ -195,7 +199,7 @@ impl ::buffa::Message for ToolCallRequested { tag, ::buffa::encoding::WireType::LengthDelimited, )?; - ::buffa::types::merge_string(&mut self.name, buf)?; + ::buffa::types::merge_string(&mut self.tool_name, buf)?; } 5u32 => { ::buffa::encoding::check_wire_type( @@ -238,7 +242,7 @@ impl ::buffa::Message for ToolCallRequested { self.session_id.clear(); self.tool_call_id.clear(); self.tool_execution_id.clear(); - self.name.clear(); + self.tool_name.clear(); self.input_json.clear(); self.parent_tool_use_id = ::core::option::Option::None; self.operation_id = ::core::option::Option::None; diff --git a/rsworkspace/crates/platform/trogonai-proto/src/lib.rs b/rsworkspace/crates/platform/trogonai-proto/src/lib.rs index b291f14ce..d35af813b 100644 --- a/rsworkspace/crates/platform/trogonai-proto/src/lib.rs +++ b/rsworkspace/crates/platform/trogonai-proto/src/lib.rs @@ -1,10 +1,10 @@ #![cfg_attr(test, allow(clippy::expect_used, clippy::panic, clippy::unwrap_used))] #[allow(clippy::all)] -#[cfg(any(feature = "schedules", feature = "sessions"))] +#[cfg(any(feature = "schedules", feature = "agents", feature = "sessions"))] mod r#gen; -#[cfg(any(feature = "schedules", feature = "sessions"))] +#[cfg(any(feature = "schedules", feature = "agents", feature = "sessions"))] mod codec; #[cfg(feature = "chrono")] @@ -13,12 +13,15 @@ pub mod convert; #[cfg(feature = "schedules")] pub mod scheduler; +#[cfg(feature = "agents")] +pub mod agents; + #[cfg(feature = "sessions")] pub mod session; // Thin wrappers that re-export the generated proto packages, emitted as inline // module trees that mirror the codegen layout. -#[cfg(feature = "schedules")] +#[cfg(any(feature = "schedules", feature = "agents"))] #[cfg_attr(dylint_lib = "trogon_lints", allow(inline_module_block))] pub mod content { pub mod v1alpha1 { @@ -26,7 +29,7 @@ pub mod content { } } -#[cfg(feature = "schedules")] +#[cfg(any(feature = "schedules", feature = "agents"))] #[cfg_attr(dylint_lib = "trogon_lints", allow(inline_module_block))] pub mod google { pub mod r#type { @@ -35,7 +38,7 @@ pub mod google { } /// Failure decoding a registered event payload to canonical JSON. -#[cfg(any(feature = "schedules", feature = "sessions"))] +#[cfg(any(feature = "schedules", feature = "agents", feature = "sessions"))] #[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)] pub enum EventDecodeError { #[error("failed to decode '{type_url}' payload as json: {message}")] @@ -53,7 +56,7 @@ pub enum EventDecodeError { /// Returns `Ok(None)` only for unregistered types; a registered type whose payload /// fails to decode returns `Err`, so malformed output of a known event is never /// mistaken for an unknown type. -#[cfg(any(feature = "schedules", feature = "sessions"))] +#[cfg(any(feature = "schedules", feature = "agents", feature = "sessions"))] pub fn decode_event_to_json(type_url: &str, payload: &[u8]) -> Result, EventDecodeError> { static REGISTRY: std::sync::OnceLock = std::sync::OnceLock::new(); @@ -61,6 +64,8 @@ pub fn decode_event_to_json(type_url: &str, payload: &[u8]) -> Result Result v1alpha1::ToolCallRequested { session_id: "session-1".to_string(), tool_call_id: "tool-call-1".to_string(), tool_execution_id: "tool-exec-1".to_string(), - name: "search".to_string(), + tool_name: "search".to_string(), input_json: "{}".to_string(), parent_tool_use_id: None, operation_id: None, @@ -439,7 +439,7 @@ fn todo_updated() -> v1alpha1::TodoUpdated { fn session_renamed() -> v1alpha1::SessionRenamed { v1alpha1::SessionRenamed { session_id: "session-1".to_string(), - title: "New title".to_string(), + display_name: "New title".to_string(), } } diff --git a/rsworkspace/crates/platform/trogonai-proto/src/session/sessions/validate.rs b/rsworkspace/crates/platform/trogonai-proto/src/session/sessions/validate.rs index 21e367298..95f046aa9 100644 --- a/rsworkspace/crates/platform/trogonai-proto/src/session/sessions/validate.rs +++ b/rsworkspace/crates/platform/trogonai-proto/src/session/sessions/validate.rs @@ -485,7 +485,7 @@ fn validate_tool_call_requested(event: &v1alpha1::ToolCallRequested) -> Result<( require_non_empty(&event.session_id, "session_id")?; require_non_empty(&event.tool_call_id, "tool_call_id")?; require_non_empty(&event.tool_execution_id, "tool_execution_id")?; - require_non_empty(&event.name, "name")?; + require_non_empty(&event.tool_name, "tool_name")?; require_valid_json(&event.input_json, "input_json") } @@ -739,7 +739,7 @@ fn validate_todo_updated(event: &v1alpha1::TodoUpdated) -> Result<(), SessionEve fn validate_session_renamed(event: &v1alpha1::SessionRenamed) -> Result<(), SessionEventValidationError> { require_non_empty(&event.session_id, "session_id")?; - require_non_empty(&event.title, "title") + require_non_empty(&event.display_name, "display_name") } fn validate_session_archived(event: &v1alpha1::SessionArchived) -> Result<(), SessionEventValidationError> { diff --git a/rsworkspace/crates/platform/trogonai-proto/src/session/sessions/validate/tests.rs b/rsworkspace/crates/platform/trogonai-proto/src/session/sessions/validate/tests.rs index c54a7221e..17efab7fa 100644 --- a/rsworkspace/crates/platform/trogonai-proto/src/session/sessions/validate/tests.rs +++ b/rsworkspace/crates/platform/trogonai-proto/src/session/sessions/validate/tests.rs @@ -1641,7 +1641,7 @@ fn validate_tool_call_requested_accepts_valid_event() { session_id: "session-1".to_string(), tool_call_id: "tool-call-1".to_string(), tool_execution_id: "tool-exec-1".to_string(), - name: "search".to_string(), + tool_name: "search".to_string(), input_json: "{}".to_string(), parent_tool_use_id: None, operation_id: None, @@ -1775,7 +1775,7 @@ fn validate_session_renamed_accepts_valid_event() { event: Some( v1alpha1::SessionRenamed { session_id: "session-1".to_string(), - title: "New title".to_string(), + display_name: "New title".to_string(), } .into(), ), @@ -1790,7 +1790,7 @@ fn validate_session_renamed_rejects_empty_title() { event: Some( v1alpha1::SessionRenamed { session_id: "session-1".to_string(), - title: String::new(), + display_name: String::new(), } .into(), ), @@ -1798,7 +1798,7 @@ fn validate_session_renamed_rejects_empty_title() { assert_eq!( validate_session_event(&event), - Err(SessionEventValidationError::EmptyIdentifier { field: "title" }) + Err(SessionEventValidationError::EmptyIdentifier { field: "display_name" }) ); } @@ -3154,7 +3154,7 @@ fn validate_tool_call_requested_rejects_invalid_input_json() { session_id: "session-1".to_string(), tool_call_id: "tool-call-1".to_string(), tool_execution_id: "tool-exec-1".to_string(), - name: "search".to_string(), + tool_name: "search".to_string(), input_json: "{not json".to_string(), parent_tool_use_id: None, operation_id: None, diff --git a/rsworkspace/crates/platform/trogonai-proto/src/tests.rs b/rsworkspace/crates/platform/trogonai-proto/src/tests.rs index 93f51b9eb..4e02b6121 100644 --- a/rsworkspace/crates/platform/trogonai-proto/src/tests.rs +++ b/rsworkspace/crates/platform/trogonai-proto/src/tests.rs @@ -1,10 +1,15 @@ -use buffa::{Message as _, MessageField, MessageName as _}; +use buffa::{Message as _, MessageName as _}; +#[cfg(feature = "agents")] +use crate::agents::agents::v1::AgentProvisioned; #[cfg(feature = "schedules")] use crate::scheduler::schedules::v1::ScheduleOccurrenceRecorded; #[cfg(feature = "sessions")] use crate::session::sessions::v1alpha1::SessionCancelled; +#[cfg(feature = "schedules")] +use buffa::MessageField; +#[cfg(feature = "schedules")] fn timestamp(seconds: i64) -> buffa_types::google::protobuf::Timestamp { buffa_types::google::protobuf::Timestamp { seconds, @@ -48,11 +53,27 @@ fn decode_event_to_json_is_canonical_across_wire_orderings() { assert_eq!(from_canonical, from_reordered); } +#[cfg(feature = "agents")] +#[test] +fn decode_event_to_json_registers_agent_provisioned() { + let event = AgentProvisioned { + agent_id: "agent-1".to_string(), + ..AgentProvisioned::default() + }; + + let from_full_name = super::decode_event_to_json(AgentProvisioned::FULL_NAME, &event.encode_to_vec()); + let from_type_url = super::decode_event_to_json(AgentProvisioned::TYPE_URL, &event.encode_to_vec()); + + assert_eq!(from_full_name, from_type_url); + let json = from_full_name.unwrap().unwrap(); + assert!(json.contains(r#""agentId":"agent-1""#), "{json}"); +} + #[cfg(feature = "schedules")] #[test] fn decode_event_to_json_returns_none_for_unknown_type() { assert_eq!( - super::decode_event_to_json("type.googleapis.com/trogonai.scheduler.schedules.v1.Unknown", &[]), + super::decode_event_to_json("type.googleapis.com/trogonai.unknown.v1.Event", &[]), Ok(None) ); } @@ -67,6 +88,16 @@ fn decode_event_to_json_errors_on_malformed_known_payload() { ); } +#[cfg(feature = "agents")] +#[test] +fn decode_event_to_json_errors_on_malformed_known_agent_payload() { + let result = super::decode_event_to_json(AgentProvisioned::FULL_NAME, b"\xff\xff\xff\xff"); + assert!( + matches!(result, Err(super::EventDecodeError::Json { .. })), + "{result:?}" + ); +} + #[cfg(feature = "sessions")] #[test] fn decode_event_to_json_is_canonical_across_wire_orderings_for_sessions() {