Skip to content

feat(relay): accept kind:30179 private managed-agent events at ingest - #5133

Merged
tlongwell-block merged 1 commit into
mainfrom
eva/relay-accept-30179
Aug 7, 2026
Merged

feat(relay): accept kind:30179 private managed-agent events at ingest#5133
tlongwell-block merged 1 commit into
mainfrom
eva/relay-accept-30179

Conversation

@tlongwell-block

@tlongwell-block tlongwell-block commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

What

Relay-only carve-out of the ingest half of #4999: generic EVENT ingest now accepts kind:30179 (NIP-PMA private managed-agent config). One file, crates/buzz-relay/src/handlers/ingest.rs, 16 insertions / 15 deletions; two semantic lines, byte-identical to the ingest hunk of #4999 at 6f486e88:

  1. required_scope_for_kind: 30179 requires Scope::UsersWrite — same arm as its public sibling 30177 and the other owner-authored NIP-AP kinds.
  2. is_global_only_kind: 30179 is owner-global, keyed (pubkey, kind, d-tag); a stray h tag must not channel-scope it.

The rest is import reflow plus replacing the guard test with a positive one (private_managed_agent_kind_is_owner_scoped_global_user_data: asserts UsersWrite scope, global-only, no h-channel scope).

Why the guard test can be retired

The removed test (private_managed_agent_kind_remains_rejected_until_atomic_ingest_exists) pinned a stated precondition: "must not enter generic EVENT ingest before privacy and aggregate CAS deploy." Both halves are resolved:

Why this is inert to existing relays and clients

  • No production desktop code on main authors kind:30179 — the codec (private_managed_agent.rs) has zero non-test callers. This PR accepts a kind nobody can produce yet.
  • Content is opaque NIP-44 ciphertext to the relay; the relay never decrypts it.
  • Reads remain author-only via the already-shipped gates above.
  • Storage is the standard parameterized-replaceable path already exercised by kinds 30175–30178. No schema, config, or migration changes.

Testing

  • Full buzz-relay package suite at this commit: 859 passed, 1 failed — api::mesh_demo::tests::demo_join_forwarded_arm_round_trips_echo (504 vs 200), which reproduces identically on clean main 769ac70b with this change stashed; pre-existing/environmental, not introduced here.
  • New positive ingest test passes.
  • Pre-push hooks green (branch-skew, rust-tests, desktop-tauri-checks).

Relationship to #4999

#4999 (relay-primary agent config, desktop half) stays DO-NOT-MERGE pending live relay receipts + real CI; once this lands and deploys, its live test simplifies to plain desktop-standalone against the real relay, and #4999 rebases to drop its now-duplicate ingest hunk (identical bytes → trivial rebase).

Originating thread: buzz://message?channel=06f13ed3-0557-4ac2-922c-1545dd00bf97&id=2a43b3b4933a2ea78b77088619251c061355f9b7b6dc29ea0d702193f2344149

Brownfield FTS note (review findings, operator-ruled non-blocking for this PR)

Max and Sami independently identified that the FTS privacy skip-set is regime-dependent: migration 0008 installs the positive allowlist (kind IN (0, 9, 40002, 45001, 45003)) only on an empty events table; an already-populated database keeps the 0001/0005 negative skip-list (wrapped by 0014 to add 30350), which omits 30179 — so on such an installation this PR admits 30179 rows whose NIP-44 ciphertext gets indexed by to_tsvector. Sami measured both regimes against real Postgres (brownfield: 30179 INDEXED; fresh: NULL) and demonstrated the existing drift test only exercises the fresh regime. schema/schema.sql:222's canonical literal is also the negative list and omits 30179. Migration dates put any relay deployed with data before 0008 landed (2026-07-13) in the brownfield class.

Scope of exposure (Sami's trace): not a content leak — event_visible_to_reader / is_author_only_event gates hold on both search surfaces (req.rs:725, bridge.rs:1770), so foreign readers receive nothing. Lost is the storage-level NULL-tsv backstop plus FTS page budget burned on post-filtered hits.

Operator ruling (Tyler, events 1472e5b6, cbd368ed): ship this PR without an exclusion migration. Safety argument that makes this sound rather than merely accepted: main has zero non-test 30179 writers until #4999's desktop half deploys — no 30179 rows can exist, so nothing can be indexed in any regime while this PR is the only half live.

Additional review characterizations (Sami, non-blocking, on the record):

  • Behavioral delta enumerated: routing triple (required_scope_for_kind / is_global_only_kind / requires_h_channel_scope) compared for all 65,536 kinds at base 769ac70b vs head 77eeba6e — exactly one row differs (30179). No other kind or client changes behavior.
  • "SQL visibility before LIMIT" (NIP-PMA step 2): no AUTHOR_ONLY_KINDS pushdown clause exists in buzz-db (only SHARED_GATED_KINDS has one). Author-only kinds are protected by the pre-filter (author_only_filters_authorized) plus post-filter omission; mixed-kind filters can burn candidate-page budget on discarded rows. Pre-existing and identical for 30300/30350 — not introduced here; noted so the NIP's step-2 checkbox is not read as fully ticked.
  • Envelope validation gap: 30179 is the only parameterized-replaceable kind at ingest with no per-kind envelope validator (codec grammar checks run in the desktop writer, not the relay). Generic limits only (256 KiB, ±15 min, pubkey==identity, d-tag bound). Self-inflicted footgun bounded to the author's own coordinate — candidate companion to the exclusion migration in the Relay-primary private managed-agent config #4999 rebase, deliberately not added here.

Bound follow-up (required before/with the #4999 desktop half): a 0014-shape additive migration (pg_get_expr capture + CASE WHEN kind = 30179 THEN NULL ELSE (<existing>) END wrap), add 30179 to the schema/schema.sql:221 literal, and a brownfield-regime variant of the FTS drift test, per Sami's finding. Deploy-time spot check if ever wanted: SELECT pg_get_expr(d.adbin, d.adrelid) FROM pg_attrdef d JOIN pg_attribute a ON a.attrelid = d.adrelid AND a.attnum = d.adnum WHERE d.adrelid = 'events'::regclass AND a.attname = 'search_tsv';

Relay-only carve-out of the ingest half of #4999, so the shared relay can
accept the private managed-agent head before the desktop half lands.

Problem: kind:30179 (NIP-PMA private managed-agent config, owner-encrypted)
is fully specified on main since #4593 -- constant, AUTHOR_ONLY_KINDS
membership, result-gated reads, req/count/event/bridge author-only gates,
parameterized-replaceable storage -- but generic EVENT ingest still rejects
it as an unknown kind, so no client can publish one. Every end-to-end
exercise of the desktop half currently requires a locally built relay.

Change: two semantic lines in ingest.rs, byte-identical to the ingest hunk
of #4999 at 6f486e8:

- required_scope_for_kind: 30179 requires Scope::UsersWrite, same arm as its
  public sibling 30177 and the other owner-authored NIP-AP kinds;
- is_global_only_kind: 30179 is owner-global, keyed (pubkey, kind, d-tag);
  a stray h tag must not channel-scope it.

The remainder is import reflow plus replacing the guard test with positive
assertions (UsersWrite scope, global-only, no h-scope requirement).

Why the guard's precondition is met: the removed test pinned "must not enter
generic EVENT ingest before privacy and aggregate CAS deploy". Both halves
are resolved. Privacy: the author-only read gates for 30179 shipped to main
with #4593 (req.rs pre-filter + result gates, count.rs, event.rs fanout,
bridge pre-filter) -- only the author can read the event back. Aggregate CAS:
#4999 settled generation as advisory -- the g tag is shape-validated, never
relay-enforced; last-write-wins per coordinate is the contract of record, so
no CAS mechanism is pending on the relay side.

Why this is inert to existing relays and clients: no production desktop code
on main authors kind:30179 (the codec has zero non-test callers), so this
accepts a kind nobody can produce yet. Content is opaque NIP-44 ciphertext
to the relay; reads remain author-only; storage semantics are the standard
parameterized-replaceable path already exercised by 30175-30178. No schema,
config, or migration changes.

Contract of record for kind:30179 remains as documented in #4999; the
desktop half rebases to pure-desktop scope once this lands.

Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
@tlongwell-block
tlongwell-block requested a review from a team as a code owner August 7, 2026 01:30
@tlongwell-block
tlongwell-block merged commit ad92335 into main Aug 7, 2026
33 checks passed
@tlongwell-block
tlongwell-block deleted the eva/relay-accept-30179 branch August 7, 2026 02:26
tlongwell-block pushed a commit that referenced this pull request Aug 7, 2026
Absorbs the relay half that shipped separately in #5133 (squash commit
ad92335): the kind:30179 ingest acceptance hunk in
crates/buzz-relay/src/handlers/ingest.rs was byte-identical on both
sides, so this merge removes all relay-side changes from this PR's
diff. #4999 now carries only the desktop + buzz-core codec half.

No rebase, no force-push — history preserved per operator instruction.

Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>

* origin/main:
  fix(bench): mention the orchestrator by pubkey when posting the task (#5136)
  feat(relay): accept kind:30179 private managed-agent events at ingest (#5133)
  fix(media): require authenticated reads (#4610)
  fix(desktop): preserve authoritative agent avatars (#4984)
  fix(desktop): next/back navigation during key creation onboarding (#4978)
  Alert community owners and admins when a new key joins (#4900)
  fix(desktop): prevent sidebar prefs from reverting on stale-localStorage boot (#5086)
  chore(hooks): run desktop typecheck in pre-push (#5110)
  feat(identity): recover desktop identity from a signed-in phone (#4845)
  fix(buzz-agent): classify read timeouts distinctly in LLM error messages (#4959)
  Refine agent runtime controls (#5026)
  test(desktop): await thread scroll anchor (#3174)
  Improve desktop mobile pairing flow (#5024)
  feat(desktop): show selected community in rail (#5000)
  fix(desktop): stop rate-limited reconnect backfill from tearing down the authenticated socket (#4990)
  fix(desktop): skip native notifications outside app bundles (#5004)
  ci: prove the relay-driven mesh lifecycle — discover, join, infer, deny — with real nodes (#3862)
  fix(desktop): virtualize channel member lists (#4991)

Signed-off-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
bradseiler added a commit that referenced this pull request Aug 7, 2026
…igration

* origin/main:
  fix(bench): mention the orchestrator by pubkey when posting the task (#5136)
  feat(relay): accept kind:30179 private managed-agent events at ingest (#5133)
  fix(media): require authenticated reads (#4610)
  fix(desktop): preserve authoritative agent avatars (#4984)
  fix(desktop): next/back navigation during key creation onboarding (#4978)
  Alert community owners and admins when a new key joins (#4900)
  fix(desktop): prevent sidebar prefs from reverting on stale-localStorage boot (#5086)
  chore(hooks): run desktop typecheck in pre-push (#5110)
  feat(identity): recover desktop identity from a signed-in phone (#4845)
  fix(buzz-agent): classify read timeouts distinctly in LLM error messages (#4959)
  Refine agent runtime controls (#5026)
  test(desktop): await thread scroll anchor (#3174)
  Improve desktop mobile pairing flow (#5024)
  feat(desktop): show selected community in rail (#5000)
  fix(desktop): stop rate-limited reconnect backfill from tearing down the authenticated socket (#4990)
  fix(desktop): skip native notifications outside app bundles (#5004)
  ci: prove the relay-driven mesh lifecycle — discover, join, infer, deny — with real nodes (#3862)

Signed-off-by: Brad Seiler <seiler@squareup.com>

# Conflicts:
#	deploy/charts/buzz/templates/deployment.yaml
#	deploy/charts/buzz/tests/render_test.yaml
#	deploy/charts/buzz/values.schema.json
#	deploy/charts/buzz/values.yaml
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant