Skip to content

feat(auth): add NIP-FI canonical assertion verifier and contracts - #6776

Merged
wpfleger96 merged 18 commits into
mainfrom
duncan/nip-fi-verifier-contracts
Aug 28, 2026
Merged

feat(auth): add NIP-FI canonical assertion verifier and contracts#6776
wpfleger96 merged 18 commits into
mainfrom
duncan/nip-fi-verifier-contracts

Conversation

@wpfleger96

@wpfleger96 wpfleger96 commented Aug 25, 2026

Copy link
Copy Markdown
Member

The closed, provider-neutral contract layer at the root of the NIP-FI federated-identity dependency graph — Phase A, PR 1 of the plan. It has no dependencies on any other PR and defines no database schema, migration, runtime JWKS fetching, binding resolution, enrollment, or request/proof binding; those belong to later PRs.

What this adds

  • Multi-issuer assertion-policy config (IssuerRegistry, IssuerPolicy) keyed by exact iss. Identity is issuer-qualified (iss, sub) throughout — equal sub under different iss are distinct identities. The subject coordinate is fixed to the JWT sub claim (SUBJECT_CLAIM), never configurable, so no deployment can seal a mutable attribute like email as identity. Issuer URL and audience remain deployment configuration.
  • The two deterministic semantic contract identitiesAssertionPolicyId = H(canonical assertion-policy contract) and TransportContractId = H(canonical transport contract) — derived by length-prefixed, domain-separated SHA-256 so a semantic change moves exactly its owning ID while benign JWKS rotation never changes policy lineage. Set-valued policy inputs (audiences, algorithms, subject-class values, scope capture) are canonicalized before derivation, so the ID is invariant under permutation and duplication. Config fields that a freshness class never reads are rejected at construction (an offline-jwt policy cannot carry maximum_status_age), so the canonical encoding stays total over valid configs and semantically identical policies always derive one ID.
  • The single canonical verifier (FederatedAssertionVerifier, FI-INV-16) producing the origin-sealed, provider-neutral VerifiedAssertion normalized result. Its constructor is crate-private, so unverified claims cannot be promoted into authority. The issuer→JWKS authority is entirely crate-owned: AssertionKeySet has no public constructor and IssuerKeySource is sealed, so no downstream crate can relabel one issuer's keys as another's. The authenticated key set is bounded (MAX_JWKS_KEYS) and the bound is folded into AssertionPolicyId, so an unbounded attacker-controlled kid scan cannot be driven; every snapshot requires a finite positive hard deadline.
  • Sealed revalidation dependenciesRevalidationDependencies carries the key-snapshot hard deadline and a ConfidentialAssertion handle to the exact compact JWS (no Debug/Display/serde leak, sole read path compact_jws()), so a changed snapshot can revalidate the same evidence: a retained key revalidates, a removed key denies.
  • The privacy-preserving four-class denial contract (DenialClass, FI-INV-13) with the byte-exact Nostr text, HTTP status, body, Content-Type, and WWW-Authenticate values fixed by the spec's rejection table. An unreadable required current dependency maps to authorization_unavailable/503, never to rejected evidence, and rejected evidence never masquerades as a 503 at either end of the pipeline: all bounded, dependency-independent checks (compact structure, header, signature shape, policy, algorithm, token class) precede key-source lookup, and all offline validation (token-class, key, signature, audience, claims, time) completes before any status-witness deferral. A wrong-typ or structurally malformed token, or malformed/invalidly-signed input naming a current-status issuer, therefore denies with evidence_rejected/403 rather than being reported as a 503 availability signal.

Corrections applied to the mined source

Mined from the buzz-auth verifier core in #1476 and corrected to the settled spec (the merged docs/nips/NIP-FI*.md, #5946), which settled after #1476 was written:

  • Token-class selection with exact typ enforcement. Two classes are offered: at+jwt and nip-fi+jwt. There is deliberately no generic/absent-typ "named compatibility" class: it cannot be proven disjoint from an OIDC ID token by claim presence alone (an issuer can mint an ID token carrying client_id), and the only authenticated discriminator is typ, which such a mode declines to constrain.
  • OIDC ID-token denial — denies even when iss, aud, and sub match, via exact typ mismatch against every accepted class.
  • The fixed nostr_pubkey claim accepted only as lowercase hex of exactly one 32-byte key; bech32 and other aliases deny.
  • Resource-owner / client-subject ambiguity denial and required client_id for at+jwt.
  • JWK admissibility: a key's use and, when present, key_ops must authorize signature verification (a key restricted to other operations such as encrypt is rejected), and the selected JOSE algorithm is bound to the key's required family and curve (ES256↔EC/P-256, ES384↔EC/P-384, EdDSA↔OKP/Ed25519, RS/PS↔RSA). The JWK alg is advisory; the actual key material is what signs, so a JWK declaring a matching alg over mismatched material (a different family or curve) is rejected before signature verification.
  • Spec-exact time arithmetic (now < exp, iat <= now + skew, now < iat + maximum_assertion_age, equality at expiry is expired). exp/iat/nbf accept finite integer or fractional RFC 7519 NumericDate values with checked, overflow-safe conversion; non-finite and out-of-range values deny.

Verification

In-crate tests in crates/buzz-auth/src/nip_fi/verifier/tests.rs sign real ES256 assertions against a fixed test key and cover: the happy path, exact-wire-text for all four denial classes, deterministic and semantic contract IDs, canonicalization invariance, token-class enforcement including ID-token denial, JWK key_ops rejection, nostr_pubkey hex handling, time bounds, and multi-issuer selection (same subject across distinct issuers yields distinct identities and policy IDs). They also cover the round-3 contracts: the key-set bound (oversized rejected, at-bound accepted, empty rejected), fixed-sub identity (identity is sub not a configured email; a token without sub denies), offline-before-deferral (invalid-signature, wrong-audience, malformed-claim, and expired under a current-status issuer each deny 403/evidence_rejected), and the revalidation contracts (dependencies carry the deadline and exact JWS; a retained key revalidates, a removed key denies). And the round-4 contracts: algorithm↔key-material binding, covered exactly per accepted algorithm (a table-driven matrix asserts every policy-acceptable algorithm — ES256, ES384, EdDSA, RS256/384/512, PS256/384/512 — matches only its required family/curve and rejects every other, so each mapping mutation fails individually; and an ES256 token against P-384, RSA, and Ed25519 material each deny InvalidKey); malformed evidence classified before key lookup (wrong-typ, two-segment, four-segment, empty-signature, and non-base64url-signature tokens against an empty source deny 403 not 503); maximum_status_age inapplicable under offline-jwt rejected at construction while current-status still requires it; and fractional NumericDate (finite fractional iat/exp/nbf within bounds verify, non-finite and out-of-range deny). Compile-fail doctests guard the sealed authority seam (AssertionKeySet::new, IssuerKeySource) and the absence of the named-compatibility token class.

Notes

  • Co-authored-by attribution is preserved for the mined author. The feat(relay): add relay-verified identity binding #1476 buzz-auth commits are authored by Cea Stapleton Cordasco (the salvage map records feat(relay): add relay-verified identity binding #1476 under Franco; the git history on that PR is Cea's — surfacing for the attribution/closure record).
  • Amends docs/nips/NIP-FI.md to remove the "named compatibility access token" class and its FI-TRACE-TOKEN-CLASS oracle reference, so the normative spec matches the two-class root implementation (a generic/absent typ cannot be proven disjoint from an OIDC ID token). Removal-only, no neighbor redesign; a docs/nips/ grep confirms no dangling cross-reference to the class remains.
  • Adds jsonwebtoken 10.4.0 (aws_lc_rs) as a workspace dependency.

Dependencies: none.

Introduce the closed, provider-neutral contract layer at the root of the
NIP-FI federated-identity dependency graph (Phase A, PR 1). This has no
dependencies on later PRs and defines no schema, migration, runtime JWKS
fetching, binding resolution, or request/proof binding.

The module provides:

- A multi-issuer assertion-policy registry keyed by exact `(iss, sub)`,
  with the two deterministic semantic contract identities
  (`AssertionPolicyId`, `TransportContractId`) derived by length-prefixed
  domain-separated hashing so benign JWKS rotation never changes policy
  lineage.
- The single `FederatedAssertionVerifier` (`FI-INV-16`) producing the
  origin-sealed, provider-neutral `VerifiedAssertion` result.
- The privacy-preserving four-class denial contract (`FI-INV-13`) with the
  byte-exact wire text, HTTP status, body, and headers fixed by the spec.

Mined from #1476's `buzz-auth` verifier core and corrected to the settled
spec (merged NIP-FI docs, #5946): token-class plus `typ` enforcement,
OIDC ID-token denial, the fixed lowercase-hex-only `nostr_pubkey` claim,
resource-owner/client-subject exclusivity, and spec-exact time arithmetic.
Identity is issuer-qualified throughout; no issuer, audience, or claim name
is hardcoded — all are deployment configuration.

Co-authored-by: Cea Stapleton Cordasco <261786559+cea@users.noreply.github.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
@wpfleger96
wpfleger96 requested a review from a team as a code owner August 25, 2026 15:07
Duncan and others added 8 commits August 25, 2026 11:50
…ct gaps

Address pass-1 review of the NIP-FI verifier/contracts layer.

Bind each AssertionKeySet to the exact issuer it authenticates and reject
a token whose signed iss does not match the supplied snapshot, closing a
cross-issuer authentication bypass under caller misuse. Reject duplicate
JOSE/claim members with a duplicate-detecting deserializer so parser-
differential ambiguity cannot smuggle a different value past the signature
gate. Fold a versioned verifier-contract fingerprint and the normative size
bounds into assertion_policy_id and version transport_contract_id, so a
semantic change moves its owning ID while JWKS rotation does not. Model
resource-owner vs client-subject as a closed, mutually-exclusive
SubjectClassContract that denies ambiguous/unclassifiable subjects and gates
client-subject eligibility on a recorded non-collision posture. Require the
access-token-only client_id claim for named-compatibility policies so they
are provably exclusive with OIDC ID tokens. Preserve identity bytes exactly
(no trimming). Deny current-status freshness in this verifier until the
status-witness runtime lands, rather than sealing without the witness.

Co-authored-by: Cea Stapleton Cordasco <261786559+cea@users.noreply.github.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
The pass-1 fix bound each AssertionKeySet to an issuer and compared that
label to the policy at verify time, but verify() still took the snapshot
as a caller argument and AssertionKeySet::new is public. A request-path
caller could therefore construct issuer B's JWKS labelled issuer A and mint
a sealed (A, victim) identity — the cross-issuer key-source confusion was
only detected for honest mislabelling, not prevented.

Make the key source structural: FederatedAssertionVerifier holds a trusted
IssuerKeySource and resolves the snapshot internally by the token's
signature-authenticated iss. verify() now takes only the token, so there is
no seam through which a caller can supply or relabel key material. A missing
snapshot for a registered issuer is an unreadable authoritative dependency
(KeySourceUnavailable -> AuthorizationUnavailable), not rejected evidence.
A defensive re-check still denies a source that violates its issuer-binding
contract. PR 3 supplies a live-refreshing IssuerKeySource additively.

Co-authored-by: Cea Stapleton Cordasco <261786559+cea@users.noreply.github.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
…r-contracts

* origin/main:
  Qualify canonical relay images for staged delivery (#6781)
  feat(desktop): persist agent addressing across composer messages (#6714)
  feat: navigate images across message threads (#6705)
  Add database pressure observability (#6700)
  revert fixed mention highlight (#6716)
  highlight search terms in results and messages (#6702)
  fix(desktop): make lightbox zoom controls interactive (#6710)
  Support community deletion in versioned media buckets (#6738)
  Fix TipTap editor mount race (#6779)
  feat(buzz-agent): gate LLM tool calls on session/request_permission (#5712)
  Add staging dev relay image workflow (#6709)

Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
The pass-2 fix moved cross-issuer forgeability off the verify() request seam
but left it at the authority-construction seam: AssertionKeySet::new and the
IssuerKeySource trait were public, so an external buzz_auth consumer could
build a source returning issuer B's JWKS labelled as issuer A, pass it to the
public FederatedAssertionVerifier::new, and mint a sealed (A, victim) identity.

Make the accepted issuer->JWKS authority entirely crate-owned:
- IssuerKeySource gains a private sealed supertrait, so no external crate can
  implement it.
- AssertionKeySet::new becomes pub(crate); a test-utils-gated for_test keeps
  the crate-owned source usable from integration tests.
- A crate-owned, test-utils-gated StaticIssuerKeySource replaces the external
  MapKeySource the tests defined.

compile_fail doctests (compiled as external consumers) prove neither the trait
nor the constructor can be named from outside the crate.

The verifier's regression suite lived outside 'just test-unit' (which runs
buzz-auth --lib only), so it ran in no CI job. Add the integration suite
(--features test-utils) and the doctests to the unit-test recipe so the
cross-issuer regression and the seal are actually gated by CI.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
…r-contracts

* origin/main:
  Remove public relay signing key fallback (#6729)
  docs(nest): make commit attribution policy-neutral (#6707)
  fix(desktop-messages): preserve inline agent mentions with persistent addressing (#6793)

Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
A downstream-selectable Cargo feature is not an access-control boundary:
features are unified and any dependent could enable buzz-auth's test-utils to
reach AssertionKeySet::for_test and the StaticIssuerKeySource, reconstruct a
relabelled issuer->JWKS authority, and mint a sealed (A, victim) identity from
issuer B's real key.

Remove for_test entirely, gate StaticIssuerKeySource and its sealed
IssuerKeySource impl on cfg(test) only, and move the verifier regression suite
into an in-crate #[cfg(test)] mod tests so it reaches the pub(crate)/cfg(test)
primitives with zero public feature surface. No supported feature set now
exposes an authority-relabelling path; the compile_fail doctests remain as a
default-feature seal regression.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Moving the constructor's only callers under cfg(test) left the non-test
lib build with no caller, so clippy -D warnings rejected it as dead code
(Rust Lint, Windows Rust). PR 3's JWKS runtime is the intended non-test
consumer; narrowly allow dead_code on this one item until it lands rather
than deferring the constructor or widening the lint. expect would misfire
because the lint does not trigger under cfg(test). Also repoint a stale
test comment at the two real compile-fail doctests.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
The dead_code compile error previously masked this: clippy could not
reach empty_line_after_doc_comments until the lib built. The /// block
above test_jwks is module narrative about the shared source, not that
function's API docs, so convert it to // and remove the trailing blank.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Carl, an automated reviewer, commenting via Wes’s GitHub account.

Changes requested on f8db951d4c256f49ec9ab181c68385f350d25d95:

  1. Map the missing current-status witness to authorization_unavailable. verify returns StatusWitnessUnavailable because this phase cannot read the policy's required current-status authority (crates/buzz-auth/src/nip_fi/verifier.rs:276-282), but denial_class maps only KeySourceUnavailable to AuthorizationUnavailable; this error falls through to EvidenceRejected/403 (:458-468). The settled rejection contract assigns an unreadable required current dependency to authorization_unavailable/503 (docs/nips/NIP-FI.md:461-476). Please map this variant accordingly and extend current_status_policy_denies_without_witness to assert the public class and wire response.

  2. Canonicalize set-valued assertion semantics before deriving AssertionPolicyId. Policy construction retains caller order and duplicates (config.rs:182-215, 361-445), while ID derivation hashes that representation directly for audiences, algorithms, subject-class values, and named required/forbidden claims (:605-642). The verifier consumes these as membership sets (verifier.rs:284, 302, 606, 620-621), so permutations or duplicates change the ID without changing accepted assertion semantics. That contradicts H(canonical assertion-policy contract) and the rule that semantic changes move the owning ID (docs/nips/NIP-FI.md:220-238). Please sort and deduplicate these fields before storage/hashing (or reject non-canonical input) and add permutation/duplicate equivalence coverage.

Relatedly, scope capability capture should use the same canonical-set discipline: it currently sorts only by claim name, leaving equal-key values in token order and retaining duplicates (verifier.rs:660-674; assertion.rs:182-197). Thus semantically equivalent scope sets can produce unequal normalized results. Please canonicalize by (name, value), deduplicate, and add parity tests.

All GitHub CI checks are green. This review used read-only GitHub metadata and source/diffs only; PR code was not checked out or executed.

…lass

Three review findings on the NIP-FI verifier:

- StatusWitnessUnavailable is an unreadable required current dependency, so
  it now maps to AuthorizationUnavailable/503 rather than falling through to
  EvidenceRejected/403 (NIP-FI.md rejection table).
- Audiences, algorithms, subject-class value sets, and NamedCompatibility
  claim lists are consumed as membership sets. Sort and deduplicate them
  before storage and AssertionPolicyId derivation so permutations and
  duplicates no longer move the policy ID (H(canonical assertion-policy
  contract)).
- Scope capture canonicalizes by (name, value) with duplicates removed so
  semantically equal scope sets seal byte-equal capabilities.

The sealed authority boundary is unchanged.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Carl, an automated reviewer, commenting via Wes’s GitHub account.

Changes requested on 8d0587c5e0fe8c45d351c93fcb6b5a70de4b7207:

  1. Do not treat client_id presence as proof that a named-compatibility JWT is not an OIDC ID token. The normative contract requires named compatibility to be mutually exclusive with every accepted ID-token class and says ID tokens always deny (docs/nips/NIP-FI.md:203-210). Construction accepts any compatibility policy that merely requires a client_id claim (crates/buzz-auth/src/nip_fi/config.rs:431-440), while verification checks only claim-name presence/absence (verifier.rs:623-632). An issuer-produced ID token with generic/absent typ, matching issuer/audience, and an additional client_id can therefore pass. Require and validate an authenticated discriminator whose accepted values are actually disjoint between access and ID tokens, or remove this compatibility mode; add a signed ID-token-shaped regression containing client_id.

  2. Honor JWK key_ops before accepting a verification key. The contract rejects incompatible JWK usage (docs/nips/NIP-FI.md:166-169), but validate_jwk checks only optional use and alg (crates/buzz-auth/src/nip_fi/verifier.rs:695-709) and ignores parsed key_ops. A matching key explicitly restricted to key_ops:["encrypt"] can still verify an assertion and mint a VerifiedAssertion. When key_ops is present, require verify and reject incompatible combinations; add a signed-token regression for a matching kid with key_ops:["encrypt"] expecting InvalidKey / evidence rejected.

This review used read-only GitHub metadata and exact-head source inspection only; PR code was not checked out or executed.

Duncan and others added 2 commits August 27, 2026 10:23
The named-compatibility token class accepted a generic/absent typ=JWT
based only on required-claim presence (client_id). Claim presence is not
authenticated proof that a token is not an OIDC ID token: an issuer can
mint an ID token carrying client_id, and the mode's defining behavior is
declining to constrain the one authenticated discriminator (typ). No
reliable disjoint discriminator exists for a generic-typ mode in this
phase, so the class is removed rather than patched; it had no production
construction site. Only at+jwt and nip-fi+jwt classes remain, both
gated on exact typ.

validate_jwk ignored the parsed JWK key_ops parameter, so a matching key
restricted to key_ops:["encrypt"] could still verify an assertion
signature. When key_ops is present it must now authorize verify, closing
the incompatible-usage gap (NIP-FI.md:166-169).

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
…r-contracts

* origin/main: (26 commits)
  fix(desktop): keep the draft space when typing right after a mention pick (#6875)
  broker: define the agent-to-broker action contract (#6742)
  fix(desktop): keep project sheets independent from threads (#6901)
  Add gated security reviews (#6816)
  fix(desktop): accent-colored mention badges that count thread mentions (#6900)
  Add Buzz benchmark evaluation layers (#6823)
  fix(desktop): show edited head content in thread panel (#6887)
  fix(desktop-tooltip): increase surface contrast (#6897)
  Deduplicate ACP thread prompt context (#6706)
  Apply access policy when reusing channel agents (#6838)
  feat(sidebar): prioritize unread DMs in overflow navigation (#6842)
  feat(projects): add agent and CLI project-home support (#6590)
  feat(desktop): restore message quick reactions (#6892)
  Use paired tags for standing & per-turn context (#6701)
  fix(cli): preserve signatures in event reads (#6884)
  refactor(db): finish replaceable event store extraction (#6777)
  Fix Admin feedback filter overflow (#6825)
  fix(desktop): stop pulsing addressed agents on send (#6873)
  fix(desktop): prioritize sidebar channel status (#6861)
  feat(desktop): hyperlink selected composer text on link paste (#6684)
  ...

Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
The generic-typ behavior regression exercises an at+jwt policy, which
already rejected generic typ before the mode was removed, so it cannot
detect reintroduction of the vulnerable named-compatibility variant. Add
a compile-fail doctest on TokenClass that names the removed variant: it
fails to compile while the variant is absent and would compile (failing
the doctest) if the variant returned. Restoring the variant with tests
intact turns this doctest red, closing the seam-level gap.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Carl, an automated reviewer, commenting via Wes’s GitHub account.

Changes requested on exact head 6d1e61259ad79b17187b80828a262235fe00fd09 against base c856be0fb954c9e5267d622841098c24e3381e8f.

  1. [P1] Bound authenticated JWKS snapshots before attacker-controlled lookup. AssertionKeySet::new accepts any number of keys (crates/buzz-auth/src/nip_fi/verifier.rs:98-112), then every unauthenticated token naming a configured issuer drives a linear scan by attacker-controlled kid (:248-293, :666-675). A token needs no valid signature to force O(keys) work. This violates the normative requirement to bound the authenticated key set before lookup (docs/nips/NIP-FI.md:166-171). Enforce a key-count bound at snapshot construction, include that semantic bound in AssertionPolicyId, and test that oversized snapshots cannot be installed. The later fetcher must also bound bytes before parsing, but that does not replace this verifier-side invariant. A focused exact-head test constructed and installed 100,000 keys successfully.

  2. [P1] Do not let configuration replace the protocol’s sub identity coordinate. IssuerPolicy::new accepts any subject_claim (config.rs:368-395), and verify seals that claim’s value as FederatedIdentity.subject (verifier.rs:313, :320-324). A policy using email, iss, or another mutable/non-sub claim therefore produces authority labelled (iss, value) even though core defines identity as exact (iss, sub), explicitly excluding email and display name (docs/nips/NIP-FI.md:35-41, :173-175). Make the identity coordinate the fixed JWT sub claim; configurable attributes may be captured separately but cannot become identity. A focused signed-token test at this head configured email, omitted sub, and successfully sealed mutable@example.com as the subject.

  3. [P1] Validate supplied evidence before reporting a missing current-status dependency. verify returns StatusWitnessUnavailable immediately after unverified issuer routing, before token-class, key selection, signature, audience, claim, and time validation (verifier.rs:257-317). Consequently a malformed or invalidly signed token naming a configured current-status issuer receives public authorization_unavailable/503 rather than the required evidence_rejected/403. That contradicts the rejection contract (docs/nips/NIP-FI.md:459-476) and turns invalid attacker input into an availability signal. Complete the offline assertion validation first, then defer only an otherwise-valid assertion for its status witness; add invalid-signature, wrong-audience, and malformed-claim regressions under CurrentStatus.

  4. [P1] Preserve the revalidation material and enforce a finite key-snapshot deadline. The closed result’s RevalidationDependencies retains only kid and generation (assertion.rs:50-69, :173-179), while AssertionKeySet permits hard_deadline: None (verifier.rs:78-112). Core requires the key-snapshot hard deadline and a confidential handle to the exact compact JWS in revalidation_dependencies, so changed snapshots can revalidate the same evidence and removed keys deny (docs/nips/NIP-FI.md:240-249, :371-395). As written, later final admission cannot recover the exact JWS from the claimed canonical result, and a snapshot can be installed with no hard expiry. Add an opaque/confidential exact-assertion handle, carry the key deadline in dependencies, and require a finite positive deadline at snapshot construction; cover retained-key and removed-key revalidation contracts.

  5. [P2] Keep the normative token-class contract synchronized with the closed enum. This head deliberately removes named compatibility and compile-fails external use, while the same repository’s normative NIP still defines that class and its oracle (docs/nips/NIP-FI.md:191-218, :564). The safer resolution is to update the NIP in this PR to remove the class, not restore an unsafe generic-typ fallback. Shipping a protocol contract and implementation that disagree at their root guarantees the next layer will choose whichever one is least convenient.

Verification: exact-head source/contract trace plus focused signed ES256 reproductions for findings 1 and 2; both reproduced, the source was restored, and the worktree is clean. Required product CI is green; the red Mark Previous Review Stale job is the repository-wide advisory workflow-token 403, unrelated to this change. Existing tests do not cover the full boundary/malformed matrix or live consumers, and this phase intentionally has no downstream consumer yet.

Bound the authenticated JWKS snapshot (MAX_JWKS_KEYS) at construction so
an attacker-controlled kid cannot drive an unbounded O(keys) scan, and
fold the bound into AssertionPolicyId as a semantic input.

Hard-code sub as the identity coordinate: remove the configurable
subject_claim knob so a mutable attribute (email, display name) can never
be sealed as identity. Complete all offline validation before deferring a
current-status assertion, so invalid input denies as evidence_rejected
(403) rather than authorization_unavailable (503).

Require a finite positive key-snapshot hard deadline, and carry it plus a
confidential handle to the exact compact JWS (no Debug/Display/serde) in
revalidation_dependencies, so a changed snapshot revalidates the same
evidence and a removed key denies.

Amend NIP-FI.md to remove the named-compatibility token class and its
oracle reference, matching the two-class verifier surface.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
The F4 rotation test re-verified against the same snapshot and modeled
removal as whole-source outage (503), proving same-snapshot reuse and
source outage rather than the FI-TRACE-JWKS-ADD/REMOVE contracts, which
turn on a changed authenticated generation. Rework it to mint at
generation 1 and revalidate the exact carried JWS against two distinct
generation-2 snapshots: a retained-key snapshot revalidates (bound to
the new generation), and a still-readable replacement-only snapshot
denies as rejected evidence (403). Source outage stays covered by
registered_issuer_without_key_snapshot_is_unavailable_not_rejected.

Also correct two module docs that still claimed all claim names are
deployment configuration; sub and nostr_pubkey are now fixed identity
coordinates.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Carl, an automated reviewer, commenting via Wes’s GitHub account.

Changes requested on exact head 7191b24bf94433ed48ba4e676a6c6eac475945d3 against base c856be0fb954c9e5267d622841098c24e3381e8f.

  1. [P1] Reject algorithm/key-type and EC-curve mismatches. validate_jwk checks optional use, key_ops, and alg, but never verifies that the JWK parameters match the selected JOSE algorithm (crates/buzz-auth/src/nip_fi/verifier.rs:708-745). A JWK declaring kty=EC, crv=P-384, and alg=ES256, while carrying valid P-256 coordinates, successfully verifies an ES256 token and mints VerifiedAssertion. That violates the normative algorithm/key-mismatch rejection contract (docs/nips/NIP-FI.md:166-171). Bind each accepted algorithm to the required JWK family and parameters, at minimum ES256↔EC/P-256, ES384↔EC/P-384, EdDSA↔OKP/Ed25519, and RSA/PS algorithms↔RSA, then add cross-family and cross-curve regressions.

  2. [P1] Reject safely classifiable malformed evidence before reporting a JWKS outage. verify resolves the key source before policy algorithm/typ enforcement and before validating the exact compact-JWS shape (verifier.rs:269-307). With no snapshot, a configured issuer's typ=JWT token returns KeySourceUnavailable / 503 instead of TokenTypeRejected / 403; a two- or four-segment value likewise reaches the outage result because the early parsers read only the first two segments and exact structure is enforced later by decode. This lets malformed/rejected evidence masquerade as an unreadable authoritative dependency, contrary to the evidence-rejected versus authorization-unavailable contract (docs/nips/NIP-FI.md:151-171, :458-475). Perform all bounded, dependency-independent compact-structure, protected-header, and policy-class checks before key-source lookup, and cover outage × wrong-type/malformed-structure cases.

  3. [P1] Canonicalize or reject the inapplicable status-age field for offline-jwt. IssuerPolicy::new accepts positive maximum_status_age_seconds under FreshnessClass::OfflineJwt (crates/buzz-auth/src/nip_fi/config.rs:385-425) and hashes it unconditionally into AssertionPolicyId (:601-667), although offline verification never reads it (verifier.rs:369-413). A focused reproduction showed None and Some(120) produce different IDs for otherwise identical offline policies. The contract says this ID hashes accepted semantics and that a semantic change moves exactly its owning ID (docs/nips/NIP-FI.md:219-237); an inapplicable representation detail must not create policy lineage or lease invalidation. Prefer rejecting Some(_) for offline policies, or omit it from that class's canonical encoding, with parity coverage.

  4. [P2] Accept finite fractional JWT NumericDate values or explicitly narrow the protocol. numeric_date and optional_numeric_date require serde_json::Value::as_i64 (verifier.rs:763-785), so a signed assertion with finite iat=now-0.5 and exp=now+600.5 returns InvalidTimeBounds. NIP-FI requires finite NumericDate values (docs/nips/NIP-FI.md:173-181), and RFC 7519 defines NumericDate as a JSON number for which non-integer values can be represented. Preserve fractional precision with checked conversion and overflow-safe comparisons, or explicitly make integer-only dates part of the normative assertion contract and its policy identity.

Verification: all four findings reproduced with focused tests at the exact head, after which the worktree was restored clean. The existing buzz-auth package tests passed (94/94) and doctests passed (3/3, 1 ignored) at that same head; git diff --check passed. Required product CI is green. The failed Mark Previous Review Stale and cancelled automated security-review jobs are review-automation/process state, not product-test failures.

…fication, tighten status-age and NumericDate

Carl round-4 corrections on the NIP-FI verifier:

- Bind each accepted JOSE algorithm to its required JWK family and curve
  (ES256↔EC/P-256, ES384↔EC/P-384, EdDSA↔OKP/Ed25519, RS/PS↔RSA), so a JWK
  whose advisory `alg` matches the token but whose material differs cannot
  verify it.
- Run all bounded, dependency-independent checks (compact structure, header,
  policy, algorithm, token class) before key-source lookup, so rejected
  evidence is 403 rather than masquerading as a 503 outage. The current-status
  deferral still fires last.
- Reject a `maximum_status_age` under offline-jwt freshness at construction,
  keeping the canonical policy-ID encoding total over valid configs.
- Accept finite fractional RFC 7519 NumericDate values with checked conversion;
  non-finite and out-of-range values deny.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
An empty or non-base64url signature segment previously passed the
segment-count structure check and deferred to post-lookup decode,
returning 503 during a JWKS outage instead of denying malformed
evidence with 403. Add enforce_signature_shape to require a non-empty,
well-formed base64url signature before key resolution; cryptographic
validity still stays post-lookup. Place it after parse_header so
alg=none continues to reject at header parse.

Add outage regressions for empty and non-base64url signatures, and a
table-driven exact-shape test binding every accepted algorithm to its
required key material so each mapping mutation individually fails.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
The algorithm-to-key-material matrix only instantiated the four required
materials, so it could not prove that an algorithm rejects a family or
curve outside that set: nine permissive mutations (ES256/ES384 also
accepting EC/P-521, EdDSA accepting any OKP curve, each RS/PS also
accepting a symmetric key) all left it green. Add EC/P-521, wrong-curve
OKP, and symmetric OctetKey fixtures so each accepted algorithm must
reject every non-required shape and each widening mutation fails.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Carl, an automated reviewer, commenting via Wes’s GitHub account.

Review clear on exact head 8f2126a0bda237e6d7009476bfc15bcc40403144 against base c856be0fb954c9e5267d622841098c24e3381e8f.

This head resolves the prior exact-head findings: JOSE algorithm-to-key-family/curve binding is exact, bounded dependency-independent structure/header/type checks precede outage deferral, offline-jwt rejects an inapplicable maximum_status_age, and finite fractional JWT NumericDate values are handled safely. End-to-end review found no remaining actionable code, product, security, lifecycle, or compatibility defect. Focused regressions cover the repaired boundaries, and required product CI is green; failing review-automation jobs are non-product process failures.

This review used read-only GitHub metadata, diffs, and exact-head source only; PR code was not checked out or executed.

@wpfleger96
wpfleger96 merged commit 953a179 into main Aug 28, 2026
39 of 40 checks passed
@wpfleger96
wpfleger96 deleted the duncan/nip-fi-verifier-contracts branch August 28, 2026 14:29
wpfleger96 pushed a commit that referenced this pull request Aug 28, 2026
…agent-edit

* origin/main:
  feat(auth): add NIP-FI canonical assertion verifier and contracts (#6776)
  fix(desktop): resolve exact typed mentions on space (#6862)
  perf(desktop): restore project context during startup (#6939)
  fix(desktop): lift right auxiliary pane above shared header backdrop (#6966)
  fix(ci): bump Codex CLI to 0.150.1 to unhang security review jobs (#6962)
  feat(desktop): implement 30178 team catalog backend (#5112)
  feat(model-capabilities): humanize Databricks UC model families (#6955)
  feat(agent): discover Databricks Unity Catalog models (#6918)
  test(db): use canonical channel roster fixtures (#6819)
  preserve channel description paragraph breaks (#6946)
  fix(cli): enrich template cardinality error with per-candidate presence and profile hints (#4825)
  Fix Codex security review authorization (#6913)
  fix(db): disable heartbeat vacuum truncation (#6898)
  chore(deps): update rui314/setup-mold digest to 7e4f20a (#6663)

Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
wpfleger96 pushed a commit that referenced this pull request Aug 28, 2026
…etection

* origin/main: (24 commits)
  feat(auth): add NIP-FI canonical assertion verifier and contracts (#6776)
  fix(desktop): resolve exact typed mentions on space (#6862)
  perf(desktop): restore project context during startup (#6939)
  fix(desktop): lift right auxiliary pane above shared header backdrop (#6966)
  fix(ci): bump Codex CLI to 0.150.1 to unhang security review jobs (#6962)
  feat(desktop): implement 30178 team catalog backend (#5112)
  feat(model-capabilities): humanize Databricks UC model families (#6955)
  feat(agent): discover Databricks Unity Catalog models (#6918)
  test(db): use canonical channel roster fixtures (#6819)
  preserve channel description paragraph breaks (#6946)
  fix(cli): enrich template cardinality error with per-candidate presence and profile hints (#4825)
  Fix Codex security review authorization (#6913)
  fix(db): disable heartbeat vacuum truncation (#6898)
  chore(deps): update rui314/setup-mold digest to 7e4f20a (#6663)
  chore(deps): update dependency vitest to v4.1.11 (#6667)
  chore(deps): update dependency @tanstack/react-virtual to v3.14.10 (#6666)
  chore(deps): update ubuntu:24.04 docker digest to 33ceb71 (#6664)
  fix(projects): allow owners to delete agent projects (#6533)
  Fade expanded video controls on hover (#6926)
  fix(db): exclude kind:30179 ciphertext from brownfield FTS (#6822)
  ...

Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
TheSentinel454 added a commit that referenced this pull request Aug 28, 2026
…edia-layout-migration

* commit 'e76c81968b65b0755b83efdd59dc3375c59ddf40': (159 commits)
  refactor(db): split channel membership store (#6782)
  feat(auth): add NIP-FI canonical assertion verifier and contracts (#6776)
  fix(desktop): resolve exact typed mentions on space (#6862)
  perf(desktop): restore project context during startup (#6939)
  fix(desktop): lift right auxiliary pane above shared header backdrop (#6966)
  fix(ci): bump Codex CLI to 0.150.1 to unhang security review jobs (#6962)
  feat(desktop): implement 30178 team catalog backend (#5112)
  feat(model-capabilities): humanize Databricks UC model families (#6955)
  feat(agent): discover Databricks Unity Catalog models (#6918)
  test(db): use canonical channel roster fixtures (#6819)
  preserve channel description paragraph breaks (#6946)
  fix(cli): enrich template cardinality error with per-candidate presence and profile hints (#4825)
  Fix Codex security review authorization (#6913)
  fix(db): disable heartbeat vacuum truncation (#6898)
  chore(deps): update rui314/setup-mold digest to 7e4f20a (#6663)
  chore(deps): update dependency vitest to v4.1.11 (#6667)
  chore(deps): update dependency @tanstack/react-virtual to v3.14.10 (#6666)
  chore(deps): update ubuntu:24.04 docker digest to 33ceb71 (#6664)
  fix(projects): allow owners to delete agent projects (#6533)
  Fade expanded video controls on hover (#6926)
  ...

# Conflicts:
#	crates/buzz-deletion/src/lib.rs
#	crates/buzz-media/Cargo.toml
#	crates/buzz-media/src/lib.rs
#	crates/buzz-media/src/storage.rs
wpfleger96 pushed a commit that referenced this pull request Aug 28, 2026
…-history

* origin/main:
  refactor(relay): NIP-98 admin auth with Operator/Moderator roles and NIP-11 discovery (#3777)
  refactor(db): split channel membership store (#6782)
  feat(auth): add NIP-FI canonical assertion verifier and contracts (#6776)
  fix(desktop): resolve exact typed mentions on space (#6862)
  perf(desktop): restore project context during startup (#6939)
  fix(desktop): lift right auxiliary pane above shared header backdrop (#6966)

Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
delkc added a commit that referenced this pull request Aug 28, 2026
…rding-v3

* origin/main:
  Refresh mobile utility surfaces and theme picker (#6944)
  fix(desktop): complete project empty and context states (#6980)
  Fix mobile jump-to-latest flicker (#6807)
  refactor(relay): NIP-98 admin auth with Operator/Moderator roles and NIP-11 discovery (#3777)
  refactor(db): split channel membership store (#6782)
  feat(auth): add NIP-FI canonical assertion verifier and contracts (#6776)

Signed-off-by: Clay Delk <clay.delk@gmail.com>
wpfleger96 pushed a commit that referenced this pull request Aug 28, 2026
…enericize

* origin/main:
  feat(desktop): add team sharing to community catalog (#3995)
  Refresh mobile utility surfaces and theme picker (#6944)
  fix(desktop): complete project empty and context states (#6980)
  Fix mobile jump-to-latest flicker (#6807)
  refactor(relay): NIP-98 admin auth with Operator/Moderator roles and NIP-11 discovery (#3777)
  refactor(db): split channel membership store (#6782)
  feat(auth): add NIP-FI canonical assertion verifier and contracts (#6776)
  fix(desktop): resolve exact typed mentions on space (#6862)
  perf(desktop): restore project context during startup (#6939)
  fix(desktop): lift right auxiliary pane above shared header backdrop (#6966)

Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
wpfleger96 pushed a commit that referenced this pull request Aug 28, 2026
…c-agent-commit-identity

* origin/main:
  feat(desktop): add team sharing to community catalog (#3995)
  Refresh mobile utility surfaces and theme picker (#6944)
  fix(desktop): complete project empty and context states (#6980)
  Fix mobile jump-to-latest flicker (#6807)
  refactor(relay): NIP-98 admin auth with Operator/Moderator roles and NIP-11 discovery (#3777)
  refactor(db): split channel membership store (#6782)
  feat(auth): add NIP-FI canonical assertion verifier and contracts (#6776)
  fix(desktop): resolve exact typed mentions on space (#6862)
  perf(desktop): restore project context during startup (#6939)
  fix(desktop): lift right auxiliary pane above shared header backdrop (#6966)
  fix(ci): bump Codex CLI to 0.150.1 to unhang security review jobs (#6962)
  feat(desktop): implement 30178 team catalog backend (#5112)
  feat(model-capabilities): humanize Databricks UC model families (#6955)
  feat(agent): discover Databricks Unity Catalog models (#6918)
  test(db): use canonical channel roster fixtures (#6819)
  preserve channel description paragraph breaks (#6946)
  fix(cli): enrich template cardinality error with per-candidate presence and profile hints (#4825)
  Fix Codex security review authorization (#6913)
  fix(db): disable heartbeat vacuum truncation (#6898)

Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
delkc added a commit that referenced this pull request Aug 28, 2026
…ssage-builder

* origin/main:
  refactor(db): extract domain stores from database runtime (#6987)
  feat(desktop): add team sharing to community catalog (#3995)
  Refresh mobile utility surfaces and theme picker (#6944)
  fix(desktop): complete project empty and context states (#6980)
  Fix mobile jump-to-latest flicker (#6807)
  refactor(relay): NIP-98 admin auth with Operator/Moderator roles and NIP-11 discovery (#3777)
  refactor(db): split channel membership store (#6782)
  feat(auth): add NIP-FI canonical assertion verifier and contracts (#6776)

Signed-off-by: Clay Delk <clay.delk@gmail.com>
jrobotham-square added a commit that referenced this pull request Aug 29, 2026
…age-rw

* origin/main:
  fix(desktop): resolve bundled sidecar on cheap path and bound login-shell spawns (#6904)
  perf(mobile): reduce cold startup and channel rendering delays (#6996)
  feat(mobile): push notifications MVP (#6269)
  refactor(db): extract domain stores from database runtime (#6987)
  feat(desktop): add team sharing to community catalog (#3995)
  Refresh mobile utility surfaces and theme picker (#6944)
  fix(desktop): complete project empty and context states (#6980)
  Fix mobile jump-to-latest flicker (#6807)
  refactor(relay): NIP-98 admin auth with Operator/Moderator roles and NIP-11 discovery (#3777)
  refactor(db): split channel membership store (#6782)
  feat(auth): add NIP-FI canonical assertion verifier and contracts (#6776)

Signed-off-by: Joel Robotham <jrobotham@squareup.com>
wpfleger96 added a commit that referenced this pull request Aug 31, 2026
…6994)

PR 2 of the NIP-FI plan: the schema foundation. Establishes the durable
server-side identity ledger and final-admission surface that the runtime
phases build on. All of Phase A's migrations live here; later phases own
their own deltas.

Depends on nothing — PR 1 (#6776, merged) owned zero migration files.
This PR's relations are shaped to store exactly what PR 1's verifier
produces: issuer-qualified identity and the four denial classes. They
meet in a later PR that writes a verified assertion into these tables in
one transaction.

## Two internally-ordered migrations

- `0041_nip_fi_identity_foundation.sql` (migration A) — core identity +
base-lifecycle relations (5 tables): issuer-qualified `(iss, sub)`
bindings, lifecycle history/selectors, enrollment policies, and
operation receipts. Applies cleanly to current `main`.
- `0042_nip_fi_authorization_foundation.sql` (migration B) — the
final-admission surface (10 tables): authorization events + capacity,
admission results, replay/receipt guards, audit, invalidation
domains/floors, protected-object authority, authority epochs, and
restore version deltas. Applies to A's resulting state.

Fifteen NIP-FI relations total, zero dangling foreign keys. Identity is
issuer-qualified throughout — no single-global-issuer assumption in any
relation, no `Block`-hardcoding. A single deployment may run one issuer;
that is config, not schema.

## Durable, immutable ledger posture

All 15 relations are append-only (immutable `no_delete`/`no_truncate`
triggers) and carry `community_id` as provenance, not ownership. Both
migrations widen the single SQL source of truth
`community_write_fence_excluded_table` so the relations are never
fence-attached, never purged on community deletion, and never counted as
tenant-scoped drift by the deletion control plane's exact-set catalog
check — the same posture main already applies to `product_feedback` and
`rate_limit_violations`. `schema/schema.sql` keeps one consolidated
definition of that function whose exclusion array byte-matches `0042`,
guarded by a parity assertion so a future consolidation cannot silently
drop NIP-FI relations from the ledger.

This makes a tenant's identity/authorization ledger survive community
deletion, per the spec's `FI-INV-02` (durable binding) and `FI-INV-03`
(tombstone monotonicity) and `NIP-FI.md`'s "durable server state"
ruling. `communities(id)` FK never dangles: community rows become
permanent tombstones, never hard-deleted.

## Authorization shape and cardinality contracts

Authenticated `OperatorDenied` events (`actor_kind` 1–3, non-null
`request_fingerprint`) carry a null `semantic_fingerprint` and commit
without a denial-attempt row. The denial-attempt cardinality and shape
guards are scoped to unresolved pre-auth kind-9 events (`actor_kind =
4`). Applied and no-op lifecycle receipts (`outcome_code IN (1, 3)`)
require exactly one mapped success-transition event; denied lifecycle
receipts (`outcome_code = 2`) require zero events from the complete core
lifecycle success-transition class (kinds 1, 2, 3, 6: enrolled, revoked,
rotated, retired) — any such event paired with a denied receipt would
record a transition that never occurred.

## Mined vs. new

Re-cut from Franco's #1476 (`0029`/`0030`) and Cea's #4772 committer
schema, re-cut along FK topology and renumbered above the live `main`
tip. The buzz-auth core of #1476 is Cea-authored; `Co-authored-by`
reflects verified per-commit authorship of the mined schema.

Zero Rust/`deletion.rs` edits — the migration-only exclusion widening
keeps `EXPECTED_SCOPED_TABLES` untouched.

---------

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Hayt <9e1c23a3fd83f61da34420e4e88ff1b16e45cafcc0cd9019eb07d4ecfa8ca9b0@buzz.block.builderlab.xyz>
Co-authored-by: Cea Stapleton Cordasco <261786559+cea@users.noreply.github.com>
Co-authored-by: Hayt <9e1c23a3fd83f61da34420e4e88ff1b16e45cafcc0cd9019eb07d4ecfa8ca9b0@buzz.block.builderlab.xyz>
shivchander added a commit to shivchander/os1 that referenced this pull request Sep 1, 2026
* fix: retrieving cold memories; add regression task (#6950)

## Why

Evaluating buzz agent memory retrieval by seeding a memory then asking
the buzz agent a question it needs that memory.

**Bug Found**: System prompt had no inclusion of retrieving cold
memories and suggested looking in a mem/*.md directory that does not
exist. Updated `system-prompt.md` to include memory CLI tools and usage.

Eval Before System Prompt Change: 0/3 
Eval After System Prompt Change: 3/3 

## What

- Add a `memory-retrieval` benchmark that seeds agent memory with `buzz
mem set` before asking a direct question.
- Grade the observable threaded answer without inspecting tool calls or
exposing the answer in channel history.
- Teach agents to use `buzz mem set`, `buzz mem ls`, and `buzz mem get`
for cold memory.
- Add a wire-debug endpoint configuration for diagnosing ACP tool calls
in local runs.
- Add fixture, seeding, verifier, and prompt coverage.

## Risk Assessment

Low. The runtime changes are limited to the benchmark harness. The
production-facing change clarifies existing memory commands in the base
prompt; it does not change memory storage, relay behavior, or
authorization.

## References

- Before the system-prompt changes, 0/3 attempts passed because agents
never invoked the `buzz mem` CLI and instead searched a non existent
filesystem
- After the changes, 3/3 attempts passed. ACP wire logs confirmed that
every agent ran `buzz mem ls` followed by `buzz mem get` and returned
`net_gpv`.

---------

Signed-off-by: Philip Azar <pazar@squareup.com>

* fix(ci): salvage Codex review output on PTY-shutdown hang (#7042)

Codex CLI can leave a PTY descendant holding the action's inherited
stdio after the turn completes. The `runCodexExec.ts` wrapper waits on a
`close` event that never fires, so the `Review pull request` step hangs
until the job timeout kills it — discarding the finished review the CLI
already wrote to disk.

The CLI writes the completed review to the `--output-last-message` file
(exposed as `output-file`) **before** the hang. This PR adds a salvage
step that recovers it, and sets the step and job timeouts to preserve
the full 30-minute Codex execution budget.

**Changes (`codex-security-review.yml`):**

- Add `output-file: ${{ runner.temp }}/codex-review.json` to the `Review
pull request` step so the CLI writes the result before the hang.
(`runner` context is valid in `steps.with`; not in `jobs.env`.)
- Add `timeout-minutes: 30` and `continue-on-error: true` to the Codex
step — a hang now costs ≤30 minutes instead of 40, and the salvage step
still runs.
- Set job `timeout-minutes: 40` to give setup, step cancellation, and
salvage sufficient headroom without colliding with the Codex execution
budget. The original 30-minute job timeout was too narrow: evidence from
run
[33114428326](https://github.com/block/buzz/actions/runs/33114428326/job/98665369165)
shows completed output appearing 28m46s after step start, meaning a
20-minute step timeout could kill a legitimate review before the salvage
file exists.
- Add a `Salvage review output` step with `if: always()`: prefers
`steps.run_codex.outputs.final-message` on a clean exit; falls back to
the output file when the step timed out. The output file path is set in
the step's own `env` block (`CODEX_OUTPUT_FILE: ${{ runner.temp
}}/codex-review.json`), where `runner` is valid. Validates shape
(non-empty JSON object, has `overall_risk`); fails the job hard if
neither source is present.
- Wire the job `outputs.review_json` to
`steps.salvage.outputs.review_json`.

**Changes (`Justfile`, `ci.yml`):**

- Add `actionlint .github/workflows/codex-security-review.yml` to
`security-review-check` so expression-validity errors are caught
locally.
- Provision `actionlint` via Hermit (pinned v1.7.12) rather than a
one-off `Install actionlint` curl step, so the same binary is used
locally and in CI.

**Security posture is unchanged:** the salvage step reads the action's
own output and a file written to `runner.temp` — neither is
PR-controlled. Credential-stripping env block on the Codex step is
untouched.


Note this is a temporary workaround until
https://github.com/openai/codex-action/issues/169 is addressed

---------

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>

* feat: render agent avatars as squircles (#7106)

## Summary

- render every agent/AI identity as a 30% squircle across desktop and
mobile while keeping human avatars circular
- propagate agent identity through message, thread, profile, reaction,
member, DM, search, workflow, project, huddle, forum, pulse, and
agent-management surfaces
- preserve squircle geometry for fallbacks, focus/status treatments,
add-agent controls, and overlapping avatar outlines (`calc(30% + 2px)`
for the outer background)

### Related issue

None found. This change was requested and visually reviewed in the
originating Buzz thread.

### Testing

- `just desktop-test` — 5,799 passed
- `just mobile-test` — 2,008 passed
- pre-push gates passed at `0d59d77b120dcb90aac2f918e422c11c9fa5353b`:
desktop check, TypeScript typecheck, desktop full test suite, mobile
format/analyze and full test suite, Rust tests, Tauri checks, and
differential file-size gate
- deterministic desktop visual sweep covered channel messages/thread
summaries; thread, subthread, and sub-subthread depths; reactions and
reactor popovers; hover/full profiles; added-to-channel activity;
channel members/settings; agent library/team overlaps; agent creation;
mention autocomplete; and DM header/sidebar/settings

### UI evidence

The complete labeled visual matrix is available in the originating Buzz
review thread. GitHub-hosted copies will be added in a follow-up PR
comment using the repository screenshot script.

---------

Signed-off-by: Fizz <dae5f6af70b8695a8b83c8deae555f63be41630ec2b8cd493e41a439c9527dd8@buzz.block.builderlab.xyz>
Co-authored-by: Fizz <dae5f6af70b8695a8b83c8deae555f63be41630ec2b8cd493e41a439c9527dd8@buzz.block.builderlab.xyz>

* fix(acp): wake agents from workflow messages (#6953)

> Pinky, an AI agent, is opening this PR on Wes's behalf.

## Summary

Workflow-generated messages can contain a valid agent mention but still
fail the ACP inbound author gate because the relay signs the event. This
keeps the existing wake policy and gives ACP a narrowly verified
effective author:

- preserve the workflow owner's existing `p` tag and all
rendered-mention `p` tags
- add explicit `["buzz:workflow-owner", <owner hex>]` provenance to
relay-generated workflow messages
- add `["buzz:workflow-mention", <agent hex>]` authority only for
mentions resolved from the stored, unrendered workflow step template
- accept that owner only for a verified kind-9 event signed by the
relay's current NIP-11 `self` key, with unique canonical workflow
metadata and an explicit workflow mention for the receiving agent
- route the verified owner through the existing author and in-flight
mode policies in both normal and setup listeners
- refresh relay identity after reconnects, retaining the last verified
key on transient fetch errors while treating a successful response
without `self` as definitive removal

Malformed, duplicate, forged, tampered, wrong-kind, and wrong-relay
attribution all fail closed to the raw event signer. `respond-to=nobody`
remains absolute. Old/mixed-version messages without the explicit
provenance retain their current fail-closed behavior.

## Trust boundary

The workflow owner means **“scheduled by,” not “authored every rendered
word.”** Trigger-controlled substitutions may still produce ordinary `p`
mention routing for compatibility, but they cannot mint
`buzz:workflow-mention` authority. Only a target named in the durable
owner-authored step template can receive that authority.

The author gate is not bypassed: after relay signature/provenance
verification, the effective owner is evaluated under the same
`owner-only`, `allowlist`, DM, and `nobody` policies used for ordinary
messages. Owner control commands continue to use the raw event signer.

## Why this PR

This is the focused immediate fix for waking an **online** agent from a
stored workflow mention. Earlier attempts were not a finished mergeable
fix and had materially different or incomplete trust designs. Larry's
larger draft stack addresses durable delivery across restarts; that
remains valuable future work and can supersede this effective-author
path when it lands.

## Validation

At exact clean commit `fe5b55619fe44176343eefb4cb7fe180df45a7d8`:

- `buzz-relay workflow_sink`: 25/25 passed, including all four ignored
PostgreSQL cases
- `buzz-acp --lib`: 845/845 passed
- `buzz-workflow --lib`: 169/169 passed (2 unrelated PostgreSQL tests
ignored)
- warnings-denied Clippy passed for the changed Rust packages
- `cargo fmt --all -- --check` passed
- `git diff --check` passed
- repository pre-push gates passed, including branch-scoped Rust tests
- CI now selects the ACP library tests and the relay's pure + PostgreSQL
workflow-sink tests so these guards cannot silently remain unexecuted

The production event-to-author gate is shared by normal and setup
listeners and has biting regression tests for accepted explicit
attribution, legacy owner-`p` rejection, and forged-attribution
rejection.

## Exact-head local relay + ACP proof

Following the release-binary/local-relay shape in `TESTING.md`, the
exact commit above passed a fresh isolated real-process matrix using:

- a freshly recreated Postgres database with migrations
- isolated Redis
- exact-head release `buzz-relay`, `buzz`, `buzz-admin`, and `buzz-acp`
binaries
- newly provisioned owner, channel, and bot member through the CLI
- workflow creation and triggering through the running relay
- a deterministic ACP protocol subprocess capturing actual
`session/prompt` dispatches
- a NIP-11 `self` value verified against the running relay signer

Cases:

1. A stored explicit workflow mention woke an `owner-only` agent exactly
once.
2. A workflow message without an agent mention did not wake it.
3. A non-relay signer forging every workflow authority tag did not wake
it.
4. Trigger-controlled `{{trigger.text}}` containing `@Wake Agent`
retained ordinary `p` routing but received no authority-bearing
workflow-mention tag and did not wake the agent.
5. `respond-to=nobody` remained absolute for a valid relay-authenticated
workflow mention.

The deterministic ACP subprocess isolates and directly proves relay →
ACP authorization and prompt dispatch without depending on external
model behavior.

## Deployment and residual risk

Relay and ACP changes must be deployed together for the new wake
behavior; mixed versions fail closed. Production paired-deployment proof
remains distinct from the successful local integration run. Setup-mode
behavior has automated coverage but was not a separate case in the
five-case local matrix. Relay-key rotation is observed at ACP
startup/reconnect; transient NIP-11 errors retain the last verified key,
an intentional availability tradeoff documented in code.

---------

Signed-off-by: Pinky <5f5ab050ec58ae208332edd544ebf705221e24c1b86d82a6ca07038a7a8f6ac9@buzz.block.builderlab.xyz>
Signed-off-by: Brain <1a02c72794dcd0f07058a353bc3a81f4028b8c77c92c87fce6d5c8b85970a20b@buzz.block.builderlab.xyz>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Pinky <5f5ab050ec58ae208332edd544ebf705221e24c1b86d82a6ca07038a7a8f6ac9@buzz.block.builderlab.xyz>
Co-authored-by: LioLionel <62820906+LioLionel@users.noreply.github.com>
Co-authored-by: Brain <1a02c72794dcd0f07058a353bc3a81f4028b8c77c92c87fce6d5c8b85970a20b@buzz.block.builderlab.xyz>
Co-authored-by: Carl <9d00794d3df50972eb8b615511783cab12a77a8fd5dd5edd58073ec73b54bd8b@buzz.block.builderlab.xyz>

* fix(relay): reject a frame on its own acknowledgement channel (#6961)

Pinky, an AI agent, updated this description on Wes's behalf after
taking over the startup investigation.

**Category:** fix

**User Impact:** An EVENT refused by WebSocket admission or handler
saturation receives a correlated `OK(event_id, false, reason)` instead
of an uncorrelated NOTICE, so the client can settle that refusal without
waiting for its publish timeout. Rate-limited refusals also arm client
backoff. This fixes a protocol failure mechanism; it does not establish
that every startup send will succeed or that the reported Desktop
startup incident is fully resolved.

**Problem:** Startup opens several live subscriptions and publishes at
once, and the relay's WebSocket admission gate is a fixed 5-second
window (`ws_admission_budget` = `human_ws_events_per_sec * 5`). If that
shared per-principal quota is exhausted, `enforce_ws_admission`
previously rejected an EVENT with a bare `["NOTICE", reason]`. Quota
pressure is a possible trigger, not proof of the original incident's
complete cause.

A NOTICE carries no event id. Both clients settle a pending publish
*only* from an `OK` keyed by event id (desktop `pendingEvents`, mobile
`_pendingEvents`), so nothing settled — and `handle_text_message`
returns early, so no `OK` ever followed either. The send **could not
fail**; it could only time out at `PUBLISH_TIMEOUT_MS` = 25s. That
explains how this rejection mechanism can produce a roughly 25-second
timeout; attributing the original report to it still requires the actual
startup/send workflow.

The handler-semaphore saturation path had the identical defect, and that
one needs no quota burst to fire.

**Solution:** NIP-01 gives each request type its own acknowledgement
channel, and a rejection is only actionable on the same one. Reject a
REQ with `CLOSED`, an EVENT with `OK(id, false, reason)`, and fall back
to `NOTICE` only where no per-request correlation exists. COUNT refusals
now also use `CLOSED(query_id, reason)` per NIP-45, covering both quota
admission and handler saturation (added in
`cd12c93804b87a24b61075dfd171dc471a0a527f`).

Reason strings are unchanged, so the `rate-limited:` prefix and `retry
in {N}s` hint that existing client gates parse keep working (desktop
`parseRateLimitHint`, mobile `RelayRateLimitGate`, buzz-acp
`set_rate_limit_gate`). Only the frame *type* changes, so
`docs/multi-tenant-relay.md` L7 stays satisfied.

Two notes on how this landed, both worth a reviewer's attention:

1. **A survived mutation became a design change.**
`send_admission_result` originally took a `RejectionTarget` parameter,
and reverting the *second* call site (the per-minute message quota)
survived the whole suite — with Redis unreachable the first quota check
short-circuits, so that line is unreachable in test. Rather than test
around it, the parameter is gone: the target is derived from the frame,
so no call site can name the wrong channel.

2. **The relay fix would have caused a client regression on its own.**
Gate arming lived only in the NOTICE branch. Once rejections arrive as
`OK:false`, `handleOk` failed the send without ever backing off — the
client would retry straight into the same quota. Desktop and Mobile now
arm on a `rate-limited:` OK rejection. ACP was subsequently fixed in
`3b06dd32493596ec650f20abf8805791c50fdc24`: it arms the gate and
re-parks only the refused observer frame, preserving other in-flight
frames. Desktop gets `activateRateLimitIfSignalled` as the single owner
of that prefix test, called from both `handleOk` and the NOTICE branch.

<details>
<summary>File changes</summary>

**crates/buzz-relay/src/rejection.rs** (new)
Owns the admission-rejection concern: `RejectionTarget`,
`rejection_target_for`, `request_rejection_message`,
`send_admission_result`, and `enforce_ws_admission`, moved out of
`connection.rs`. Six tests, two of which drive the real
`enforce_ws_admission` against a real `AppState`.

**crates/buzz-relay/src/connection.rs**
Fix the EVENT handler-semaphore rejection to correlate to the event id;
delegate admission to the new module. Add two tests that drive the real
`handle_text_message` with every handler permit held. Down from 1319 to
1116 lines.

**crates/buzz-relay/src/state.rs**
Widen the existing `test_state` helper to `pub(crate)` so the rejection
tests reuse it rather than adding a ninth copy of `AppState`
construction.

**desktop/src/shared/api/relayRateLimitGate.ts**
Add `activateRateLimitIfSignalled` — one owner for the `rate-limited:`
prefix test, since three inbound frame types now carry it.

**desktop/src/shared/api/relayClientSession.ts**
Arm the gate on a rate-limited OK rejection; route the NOTICE branch
through the same helper. Net zero lines, which keeps this
already-oversized file within the differential ratchet.

**desktop/src/shared/api/relayClientPublishRejection.test.mjs** (new)
Four tests against the real `RelayClient`: a rate-limited OK settles the
pending publish and arms the gate; an ordinary rejection does not arm
it; an accepted OK still resolves.

**mobile/lib/shared/relay/relay_session.dart**
Arm the gate in `_handleOk` for a rate-limited rejection.

**mobile/test/shared/relay/relay_session_test.dart**
Two tests driving the real `publish` + `debugHandleMessage` path.

</details>

<details>
<summary>Validation</summary>

**Mutation-tested — 5 mutations, all now killed.** Each production call
site was reverted to the defective behaviour to confirm a test fails.
This caught two false-negative tests:

| # | Mutation | Result |
|---|----------|--------|
| 1 | `rejection_target_for`: EVENT → `Connection` | 4 tests fail |
| 2 | EVENT handler-semaphore call site → bare NOTICE | **survived at
first** |
| 3 | per-minute quota call site → `Connection` | **survived**; fixed by
removing the parameter |
| 4 | desktop `handleOk` gate arming removed | 1 test fails |
| 5 | mobile `_handleOk` gate arming removed | 1 test fails |

Mutation 2 is the lesson: my first saturation test called
`request_rejection_message` directly, so reverting the real call site
inside the `match` arm left it green. It now drives
`handle_text_message` itself and dies on that mutation.

- `cargo test -p buzz-relay` — 928 passed, 1 failed:
`api::mesh_demo::tests::demo_join_forwarded_arm_round_trips_echo`,
**pre-existing**, reproduced with all changes stashed at `4dd4d73de`.
- `cd desktop && npm test` — 5721 passed, 0 failed (full suite).
- `cd mobile && flutter test` — 1876 passed, 0 failed (full suite).
- `just fmt-check`, `just clippy`, `just desktop-check`, `just
mobile-check`, `just file-size-check` — clean. Desktop's 5 biome
warnings are pre-existing (reproduced with changes stashed).
- All 9 pre-push lanes green, including `rust-tests` and
`desktop-tauri-checks`.

**Not verified:** not reproduced end-to-end against a live relay under a
forced quota burst. The causal chain is source-proven and
mutation-proven at the frame level; the ~25s attribution follows from
`PUBLISH_TIMEOUT_MS` but is not directly measured. A packaged-build
click-through would close that gap.

</details>

Related work: #6957 bounds Desktop HTTP event submission, but safe
retained-operation recovery after exhausted/ambiguous outcomes remains
unfinished. #6998 is the separately reviewable Desktop
readiness/duplicate-subscription slice. Neither is claimed to complete
native before/after startup-send validation.

Diagnosis note: `RESEARCH/DESKTOP_STARTUP_SEND_STALL_2026_08_27.md`
(Brain's workspace).

## Current review disposition (2026-08-28)

The [review on
`cd12c938`](https://github.com/block/buzz/pull/6961#pullrequestreview-5052902510)
identified ACP's missing rate-limited-OK handling. Commit
`3b06dd32493596ec650f20abf8805791c50fdc24` fixes gate arming, re-parking
the specifically refused observer frame, and the stale NOTICE comment.
Two regressions drive the real frame dispatcher. See [the implementation
and validation
response](https://github.com/block/buzz/pull/6961#issuecomment-5455032054).

The Mobile generation-check inline thread is resolved: its `async
publish` returns a failed Future when superseded; it does not throw
synchronously at invocation. No further production change was indicated
by that comment.

The validation counts above describe the original slice, not a new
rerun. At `3b06dd324`, the current GitHub check rollup has successful
completed test/build checks (non-applicable jobs skipped). The
security-review comment still requires review for the current base/head
range; do not read a green authorization job as a completed security
review. Approval and merge remain human decisions.

---------

Signed-off-by: Brain <1a02c72794dcd0f07058a353bc3a81f4028b8c77c92c87fce6d5c8b85970a20b@buzz.block.builderlab.xyz>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Brain <1a02c72794dcd0f07058a353bc3a81f4028b8c77c92c87fce6d5c8b85970a20b@buzz.block.builderlab.xyz>
Co-authored-by: Carl <9d00794d3df50972eb8b615511783cab12a77a8fd5dd5edd58073ec73b54bd8b@buzz.block.builderlab.xyz>

* feat(desktop): add protected-build Bestie experiment (#6902)

## Summary

Introduces a protected-build boundary for the default-off Bestie
experiment without adding any Bestie product surface.

- Official OSS builds select an empty protected-feature module and emit
no Bestie/Chief metadata or implementation content.
- Protected internal builds select a separate module graph containing
the Bestie experiment definition.
- Within an internal build, Bestie remains disabled until the user opts
in under Settings → Experiments.
- The production build runs an artifact matrix and fails if OSS output
contains protected content or internal output lacks the Bestie manifest.

## Build contract

| Build variant | User opt-in | Result |
| --- | --- | --- |
| Official OSS | Any/forged | Bestie absent from the compiled artifact |
| Protected internal | Off | Bestie available but disabled |
| Protected internal | On | Bestie enabled |

The companion protected-release change is squareup/buzz-releases#91. It
sets `VITE_BUZZ_BESTIE=1`, requires that exact value, forwards it into
the signed macOS build, and asserts the contract in release validation.

## Why this is separate

This gives later Bestie PRs one build-selected import seam. Protected
implementations must be reachable only from the internal module so they
never enter the official OSS module graph.

## Non-goals

- No Bestie persona or provisioning
- No sidebar, app-chrome, or message-toolbar UI
- No entitlement or secrecy claim: the source is public; this boundary
controls official Block artifacts

## Verification

- Exact commit `523cf49ced03cba9be43836a54d6aa5d6923cc82`
- Full `just ci`: 5,673 Desktop tests, 2,773 Tauri tests, 1,860 mobile
tests, Rust/Tauri/web/mobile static checks and builds
- OSS production artifact: scanner confirms no `Bestie`, `Chief of
Staff`, or `builtin:bestie` content
- Internal production artifact: scanner confirms the protected Bestie
manifest is emitted
- Both build orders verified; `dist` retains the requested variant for
Vite/Tauri packaging

---------

Signed-off-by: Arjun Mahanti <arjun@squareup.com>
Signed-off-by: Fizz <fizz@buzz.local>
Signed-off-by: Fizz <dae5f6af70b8695a8b83c8deae555f63be41630ec2b8cd493e41a439c9527dd8@buzz.block.builderlab.xyz>
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Fizz <fizz@buzz.local>
Co-authored-by: Fizz <dae5f6af70b8695a8b83c8deae555f63be41630ec2b8cd493e41a439c9527dd8@buzz.block.builderlab.xyz>

* add public descriptions to agent personas (#7126)

**Category:** new-feature
**User Impact:** People can add a short public description to an agent
and see what it does directly on agent cards and profiles.

**Problem:** Agent cards previously showed only a model label, so people
had to open an agent and inspect its instructions to understand its
purpose. Public metadata also needed one trustworthy lifecycle across
local edits, relay catalogs, profiles, and portable snapshots.

**Solution:** Add an optional owner-authored description with a
280-character visible-text policy, publish it as profile `about`, and
prefer it on agent cards while retaining the model fallback. Description
metadata is excluded from the spawn-content hash, remains
definition-owned, and is validated independently at every untrusted or
persistence boundary.

<details>
<summary>File changes</summary>

**desktop/src-tauri/src/commands/agent_config_tests.rs**
Updates managed-agent/persona fixtures for the optional description
field while preserving the behavior under test.

**desktop/src-tauri/src/commands/agent_discovery/relay_directory.rs**
Updates relay-directory profile test publication for the expanded
profile contract.

**desktop/src-tauri/src/commands/agent_models_tests.rs**
Updates managed-agent/persona fixtures for the optional description
field while preserving the behavior under test.

**desktop/src-tauri/src/commands/agent_models_update.rs**
Preserves the effective `about` value when instance edits republish a
complete profile event.

**desktop/src-tauri/src/commands/agents.rs**
Carries the effective authored description into initial managed-agent
profile publication.

**desktop/src-tauri/src/commands/agents_profile.rs**
Adds `about` to profile reconciliation and keeps description, name, and
avatar synchronized against relay state.

**desktop/src-tauri/src/commands/agents_tests.rs**
Updates managed-agent/persona fixtures for the optional description
field while preserving the behavior under test.

**desktop/src-tauri/src/commands/personas/card.rs**
Materializes the definition-owned description before minting a portable
agent card snapshot.

**desktop/src-tauri/src/commands/personas/create.rs**
Normalizes and validates raw authored descriptions before persona
persistence.

**desktop/src-tauri/src/commands/personas/delete_cascade_tests.rs**
Updates managed-agent/persona fixtures for the optional description
field while preserving the behavior under test.

**desktop/src-tauri/src/commands/personas/inbound.rs**
Validates descriptions at inbound relay ingress and applies accepted
values to local definitions.


**desktop/src-tauri/src/commands/personas/inbound/catalog_reconcile_tests.rs**
Updates managed-agent/persona fixtures for the optional description
field while preserving the behavior under test.

**desktop/src-tauri/src/commands/personas/inbound/inbound_tests.rs**
Updates managed-agent/persona fixtures for the optional description
field while preserving the behavior under test.

**desktop/src-tauri/src/commands/personas/mod.rs**
Centralizes raw-byte validation followed by trim/empty normalization for
description writes.

**desktop/src-tauri/src/commands/personas/pending.rs**
Revalidates descriptions before preparing public persona publications.

**desktop/src-tauri/src/commands/personas/sharing.rs**
Carries the optional public description through this managed-agent
compatibility path.

**desktop/src-tauri/src/commands/personas/snapshot.rs**
Materializes definition-owned descriptions into portable instance
snapshots without creating a second persisted authority.

**desktop/src-tauri/src/commands/personas/snapshot/fidelity_tests.rs**
Updates managed-agent/persona fixtures for the optional description
field while preserving the behavior under test.

**desktop/src-tauri/src/commands/personas/snapshot/import.rs**
Restores snapshot descriptions onto imported definitions while keeping
linked instance copies absent.

**desktop/src-tauri/src/commands/personas/snapshot/tests.rs**
Updates managed-agent/persona fixtures for the optional description
field while preserving the behavior under test.

**desktop/src-tauri/src/commands/personas/update.rs**
Persists persona description edits, republishes linked profiles, and
preserves legacy avatars during complete kind:0 replacements.


**desktop/src-tauri/src/commands/personas/update/name_propagation_tests.rs**
Proves description-only profile sync does not write instance state or
clear a legacy avatar.

**desktop/src-tauri/src/commands/team_snapshot.rs**
Round-trips member descriptions through team snapshots and imported
definitions.

**desktop/src-tauri/src/commands/team_snapshot/tests.rs**
Covers team member description export and import fidelity.

**desktop/src-tauri/src/commands/teams/adopt/apply.rs**
Starts adopted team catalog members without synthesizing an unauthored
description.

**desktop/src-tauri/src/commands/teams/adopt/tests.rs**
Updates managed-agent/persona fixtures for the optional description
field while preserving the behavior under test.

**desktop/src-tauri/src/commands/teams/pending/tests.rs**
Updates managed-agent/persona fixtures for the optional description
field while preserving the behavior under test.

**desktop/src-tauri/src/commands/teams/sharing/tests.rs**
Updates managed-agent/persona fixtures for the optional description
field while preserving the behavior under test.

**desktop/src-tauri/src/egress_guard_tests.rs**
Updates managed-agent/persona fixtures for the optional description
field while preserving the behavior under test.

**desktop/src-tauri/src/event_sync_team_catalog_tests.rs**
Updates managed-agent/persona fixtures for the optional description
field while preserving the behavior under test.

**desktop/src-tauri/src/managed_agents/agent_description.rs**
Defines the canonical Rust description resolution used by profile
publication and reconciliation.

**desktop/src-tauri/src/managed_agents/agent_events.rs**
Updates managed-agent record construction for the optional public
description field.

**desktop/src-tauri/src/managed_agents/agent_snapshot.rs**
Includes descriptions as snapshot profile `about` metadata and validates
them at decode ingress.

**desktop/src-tauri/src/managed_agents/agent_snapshot_envelope.rs**
Updates managed-agent record construction for the optional public
description field.

**desktop/src-tauri/src/managed_agents/agent_snapshot_tests.rs**
Covers snapshot description export and rejection of unsafe or overlong
imported metadata.

**desktop/src-tauri/src/managed_agents/config_bridge/reader_tests.rs**
Updates managed-agent/persona fixtures for the optional description
field while preserving the behavior under test.

**desktop/src-tauri/src/managed_agents/definition_validation.rs**
Adds the shared 280-character visible-text policy for public
descriptions.

**desktop/src-tauri/src/managed_agents/discovery/tests.rs**
Updates managed-agent/persona fixtures for the optional description
field while preserving the behavior under test.

**desktop/src-tauri/src/managed_agents/effective_config/tests.rs**
Updates managed-agent/persona fixtures for the optional description
field while preserving the behavior under test.

**desktop/src-tauri/src/managed_agents/global_config/tests.rs**
Updates managed-agent/persona fixtures for the optional description
field while preserving the behavior under test.

**desktop/src-tauri/src/managed_agents/mod.rs**
Exports the description resolution and validation helpers to
managed-agent consumers.

**desktop/src-tauri/src/managed_agents/nest/render_tests.rs**
Updates managed-agent/persona fixtures for the optional description
field while preserving the behavior under test.

**desktop/src-tauri/src/managed_agents/parallelism.rs**
Updates managed-agent fixtures for the optional description field
without changing runtime configuration behavior.

**desktop/src-tauri/src/managed_agents/persona_events.rs**
Adds description to persona event content while deliberately excluding
it from the spawn-relevant content hash.

**desktop/src-tauri/src/managed_agents/persona_events/tests.rs**
Pins description event round-tripping and proves description-only edits
do not change the restart hash.

**desktop/src-tauri/src/managed_agents/personas.rs**
Initializes built-in persona records without authored descriptions for
backward-compatible defaults.

**desktop/src-tauri/src/managed_agents/personas/tests.rs**
Updates managed-agent/persona fixtures for the optional description
field while preserving the behavior under test.

**desktop/src-tauri/src/managed_agents/readiness.rs**
Updates managed-agent fixtures for the optional description field
without changing runtime configuration behavior.

**desktop/src-tauri/src/managed_agents/restore.rs**
Includes the effective description in launch-time profile
reconciliation.

**desktop/src-tauri/src/managed_agents/runtime/test_fixtures.rs**
Updates managed-agent/persona fixtures for the optional description
field while preserving the behavior under test.

**desktop/src-tauri/src/managed_agents/runtime/tests.rs**
Updates managed-agent/persona fixtures for the optional description
field while preserving the behavior under test.

**desktop/src-tauri/src/managed_agents/spawn_snapshot/tests.rs**
Updates managed-agent/persona fixtures for the optional description
field while preserving the behavior under test.

**desktop/src-tauri/src/managed_agents/team_catalog/tests.rs**
Updates managed-agent/persona fixtures for the optional description
field while preserving the behavior under test.

**desktop/src-tauri/src/managed_agents/team_snapshot.rs**
Updates managed-agent record construction for the optional public
description field.

**desktop/src-tauri/src/managed_agents/teams_tests.rs**
Updates managed-agent/persona fixtures for the optional description
field while preserving the behavior under test.

**desktop/src-tauri/src/managed_agents/types.rs**
Adds optional description metadata to persona and managed-agent records
and their compatibility projections.

**desktop/src-tauri/src/managed_agents/types/requests.rs**
Accepts optional descriptions on persona create and update IPC requests.

**desktop/src-tauri/src/managed_agents/types/tests.rs**
Updates managed-agent/persona fixtures for the optional description
field while preserving the behavior under test.

**desktop/src-tauri/src/migration_avatar_tests.rs**
Updates managed-agent/persona fixtures for the optional description
field while preserving the behavior under test.

**desktop/src-tauri/src/persona_catalog.rs**
Parses and validates descriptions at the untrusted community-catalog
boundary.

**desktop/src-tauri/src/persona_catalog_tests.rs**
Covers valid catalog descriptions plus rejection of malformed,
invisible, and overlong values.

**desktop/src-tauri/src/relay.rs**
Publishes and queries kind:0 `about` so relay profiles preserve authored
descriptions.

**desktop/src-tauri/src/relay/tests.rs**
Updates managed-agent/persona fixtures for the optional description
field while preserving the behavior under test.

**desktop/src/features/agents/AGENTS.md**
Documents description ownership, validation, snapshot, hashing, and
display invariants for future changes.

**desktop/src/features/agents/lib/agentDescription.test.mjs**
Pins Unicode counting, paste clamping, trimming, and empty
authored-description behavior.

**desktop/src/features/agents/lib/agentDescription.ts**
Provides shared display resolution, Unicode-scalar counting, and paste
clamping for descriptions.

**desktop/src/features/agents/lib/personaCatalogRelay.ts**
Maps validated catalog descriptions into catalog persona projections.

**desktop/src/features/agents/ui/AgentDefinitionDialog.tsx**
Adds the description draft to create and edit submission while
extracting identity fields from the large dialog.

**desktop/src/features/agents/ui/AgentDescriptionField.tsx**
Renders the public description input, helper copy, and Unicode-aware
near-limit counter.

**desktop/src/features/agents/ui/AgentIdentityCard.tsx**
Generalizes the card second line to show a two-line description or the
existing model fallback.

**desktop/src/features/agents/ui/UnifiedAgentsSection.tsx**
Prefers authored descriptions on persona cards and retains model labels
when no description exists.

**desktop/src/features/agents/ui/personaDialogState.test.mjs**
Verifies edit and duplicate drafts preserve authored descriptions.

**desktop/src/features/agents/ui/personaDialogState.ts**
Seeds authored descriptions into edit and duplicate dialog drafts.

**desktop/src/features/agents/ui/usePersonaActions.ts**
Preserves descriptions when copying catalog personas into local
definitions.

**desktop/src/shared/api/personaTypes.ts**
Defines description-bearing persona wire types in a focused module split
from the size-constrained API type file.

**desktop/src/shared/api/tauriPersonas.test.mjs**
Verifies raw persona descriptions map into the frontend model and absent
values become null.

**desktop/src/shared/api/tauriPersonas.ts**
Maps description fields across Tauri and preserves raw authored bytes
for authoritative Rust validation.

**desktop/src/shared/api/types.ts**
Re-exports the extracted persona types without changing consumer import
paths.

**desktop/src/testing/e2eBridge.ts**
Extends mock persona create, update, publication, and catalog parsing
with production-shaped description behavior.

**desktop/tests/e2e/agents.spec.ts**
Verifies an edited description persists and appears on the agent card.

</details>

### Reproduction Steps

1. Open **Agents**, edit a custom or built-in agent, and enter a
sentence in **Description**.
2. Save the agent and confirm the sentence appears as the second line on
its card.
3. Reopen the agent and confirm the authored description is restored;
clear it and confirm the card returns to the model label.
4. Paste more than 280 Unicode characters and confirm the field keeps
the first 280 characters and shows the near-limit counter.
5. Share or export/import the agent and confirm the description survives
in the catalog/profile or snapshot without showing a restart-required
badge for a description-only edit.

### Screenshots / Demo

The focused Playwright flow `built-in persona edits persist` exercises
the edited dialog, persisted value, and resulting card subtitle.
Screenshots can be added after review if the field placement or two-line
card treatment needs visual iteration.

### Verification

- `cargo test --manifest-path desktop/src-tauri/Cargo.toml --lib` —
3,029 passed
- `cd desktop && pnpm test` — 5,805 passed
- `cd desktop && pnpm exec tsc --noEmit`
- Focused Playwright: `built-in persona edits persist` — passed
- Pre-push desktop, Tauri, typecheck, test, file-size, and branch-skew
gates — passed

---------

Signed-off-by: tulsi <tulsi@block.xyz>

* fix(desktop): back split thread headers (#7137)

## Summary
- render an auxiliary panel's requested header backdrop in docked/split
mode
- preserve explicit transparent-backdrop behavior
- cover a populated, scrolled thread pane so timeline content cannot
bleed through its header

## Root cause
`RightAuxiliaryPane` correctly paints above the channel's shared header
backdrop so close/edit controls remain visible. The docked
`AuxiliaryPanelHeader` branch, however, ignored its `backdrop` request,
leaving scrolled thread content in that higher stacking context
unbacked.

## Verification
- desktop unit suite: 5,801 passed
- desktop TypeScript: passed
- Biome checks: passed (existing unrelated repository warnings only in
the earlier full run)
- targeted Playwright scroll regression: passed
- ultrawide thread-pane Playwright coverage: passed

Signed-off-by: Wintermute <c0fc581234c3585602139eec347ced7b82af65b6f6c10728348515c0c06c51c3@buzz.block.builderlab.xyz>
Co-authored-by: Wintermute <c0fc581234c3585602139eec347ced7b82af65b6f6c10728348515c0c06c51c3@buzz.block.builderlab.xyz>

* docs: add review-proven failure-path & async-state rules to AGENTS.md (#7061)

Mining the last 25 PRs' review threads (45 substantive findings, 11
reviewed PRs, avg **4.8 review rounds** each) shows **53% of findings
are repeats** of five clusters: swallowed failures, stale-async-state
races, tests that don't bind the production seam, unbounded
resources/retry loops, and non-atomic multi-step persistence. PR #6956
alone burned 4 rounds converging on one of these classes.

A second, independent mining pass over **71 agent-review rooms (303
findings, Aug 18–29)** confirmed the same clusters and added outcome
data — how often authors actually fix each finding class once flagged:
test-seam binding and unbounded-resource findings **100%**, swallowed
errors **90%**, stale-state races **70%**. It also surfaced two clusters
the GitHub-thread pass under-sampled: **assistive-semantics defects**
(44 findings, second-largest cluster) and **input-modality divergence**
(27 findings), now rules 7–8.

This PR distills those clusters into eight imperative rules in AGENTS.md
so agents apply them **before writing code**, adds one
client-consumption invariant to ARCHITECTURE.md §5, and places the
test-quality rule in TESTING.md (per the team decision that testing docs
are the canonical guide for review standards), cross-referenced from
AGENTS.md. Each rule cites the PRs where it was litigated. Raw mining
data: `reviews.jsonl` / `comments.jsonl` +
`backfill/buzz-review-findings.jsonl` (review-mining artifacts, not
committed).

No code changes. CLAUDE.md is a symlink to AGENTS.md and picks this up
automatically.

🤖 Drafted by Jude's agent from automated mining of this repo's last 25
PRs' review threads and 71 agent-review rooms; every rule cites the PRs
where it was litigated. Jude reviews and owns the result. Mining method
+ raw cluster data available on request.

---------

Signed-off-by: Jude Edwards <judeedwards@squareup.com>

* feat(buzz-acp): give each channel thread its own agent session (#6732)

## What this does

In a channel, people often run several unrelated conversations at once
(separate threads). Today the agent treats the whole channel as one
conversation, so unrelated threads share the same running session —
their context bleeds together and independent tasks can step on each
other.

This change gives the agent a **separate session per thread** inside a
channel. Direct messages stay as one conversation (unchanged). The
channel is still the boundary for who is allowed in and what is visible
— only the agent's working context is now split by thread.

## How it is turned on

Off by default. Operators opt in with one setting:

- `BUZZ_ACP_SESSION_POLICY=channel` — default, current behavior
- `BUZZ_ACP_SESSION_POLICY=thread` — new per-thread behavior

Being behind a flag means we can enable it for a few agents, watch how
it behaves, and roll back instantly without a code change.

## Key design decisions

- **Decide the thread once, up front.** When a message arrives we work
out which thread it belongs to a single time and tag it. Everything
after that (which line it waits in, which session runs it, what history
it sees) uses that tag instead of re-guessing later, which avoids
mismatches.
- **Default stays identical to today.** Under the default setting a
"thread" is just "the whole channel," so existing behavior and every
existing test are unchanged. The new, riskier behavior is strictly
opt-in.
- **Give the agent only its thread's history.** On a reply the agent
sees that thread's messages (including ones that did not mention it),
not the whole channel transcript — less noise and smaller prompts.
- **Don't let one channel use more memory than before.** More threads
means more live sessions, so the existing per-channel limit now caps all
of a channel's threads together — splitting into threads can't multiply
how much work is held.

## Bugs found and fixed while iterating (from review)

- **Same thread, two sessions.** If the worker already holding a
thread's session was busy, a new message for that thread could start a
*second* session on another worker and split its history. Now it waits
for the right worker instead of forking.
- **Interrupting the wrong thread.** A follow-up meant for thread A
could interrupt thread B in the same channel. Interrupts now target the
exact thread.
- **Stuck thread after a crash.** If a thread's turn crashed, its slot
wasn't cleared and stayed blocked for up to ~2 hours. It now clears
right away and retries.
- **Lost the original request.** When a thread was interrupted and then
had to wait for a busy worker, only the follow-up was kept and the
original request was dropped. The full request is now preserved on
retry.
- **Same thread seen as two.** Two spellings of the same thread id
(upper/lower case) could be treated as different threads. Normalized so
they count as one.

## Not in this PR

- The desktop Settings toggle and rollout wiring for managed agents —
https://github.com/block/buzz/pull/6909
- One pre-existing retry edge case (present today without this flag,
unrelated to this change) — tracked separately so this PR stays focused.

## Testing

The full `buzz-acp` test suite passes (830+ unit and integration tests),
plus new focused tests for thread routing, session reuse, interrupt
targeting, crash recovery, and request preservation. Behavior with the
flag off is unchanged.

---------

Signed-off-by: Salman Mohammed <smohammed@squareup.com>
Signed-off-by: Leo <5faf251baee50ee6bcde338aef6acdd70bb3e60115664c2cd490d94a55dfc488@buzz.block.builderlab.xyz>
Co-authored-by: Leo <5faf251baee50ee6bcde338aef6acdd70bb3e60115664c2cd490d94a55dfc488@buzz.block.builderlab.xyz>

* feat(db): add NIP-FI identity and final-admission schema foundation (#6994)

PR 2 of the NIP-FI plan: the schema foundation. Establishes the durable
server-side identity ledger and final-admission surface that the runtime
phases build on. All of Phase A's migrations live here; later phases own
their own deltas.

Depends on nothing — PR 1 (#6776, merged) owned zero migration files.
This PR's relations are shaped to store exactly what PR 1's verifier
produces: issuer-qualified identity and the four denial classes. They
meet in a later PR that writes a verified assertion into these tables in
one transaction.

## Two internally-ordered migrations

- `0041_nip_fi_identity_foundation.sql` (migration A) — core identity +
base-lifecycle relations (5 tables): issuer-qualified `(iss, sub)`
bindings, lifecycle history/selectors, enrollment policies, and
operation receipts. Applies cleanly to current `main`.
- `0042_nip_fi_authorization_foundation.sql` (migration B) — the
final-admission surface (10 tables): authorization events + capacity,
admission results, replay/receipt guards, audit, invalidation
domains/floors, protected-object authority, authority epochs, and
restore version deltas. Applies to A's resulting state.

Fifteen NIP-FI relations total, zero dangling foreign keys. Identity is
issuer-qualified throughout — no single-global-issuer assumption in any
relation, no `Block`-hardcoding. A single deployment may run one issuer;
that is config, not schema.

## Durable, immutable ledger posture

All 15 relations are append-only (immutable `no_delete`/`no_truncate`
triggers) and carry `community_id` as provenance, not ownership. Both
migrations widen the single SQL source of truth
`community_write_fence_excluded_table` so the relations are never
fence-attached, never purged on community deletion, and never counted as
tenant-scoped drift by the deletion control plane's exact-set catalog
check — the same posture main already applies to `product_feedback` and
`rate_limit_violations`. `schema/schema.sql` keeps one consolidated
definition of that function whose exclusion array byte-matches `0042`,
guarded by a parity assertion so a future consolidation cannot silently
drop NIP-FI relations from the ledger.

This makes a tenant's identity/authorization ledger survive community
deletion, per the spec's `FI-INV-02` (durable binding) and `FI-INV-03`
(tombstone monotonicity) and `NIP-FI.md`'s "durable server state"
ruling. `communities(id)` FK never dangles: community rows become
permanent tombstones, never hard-deleted.

## Authorization shape and cardinality contracts

Authenticated `OperatorDenied` events (`actor_kind` 1–3, non-null
`request_fingerprint`) carry a null `semantic_fingerprint` and commit
without a denial-attempt row. The denial-attempt cardinality and shape
guards are scoped to unresolved pre-auth kind-9 events (`actor_kind =
4`). Applied and no-op lifecycle receipts (`outcome_code IN (1, 3)`)
require exactly one mapped success-transition event; denied lifecycle
receipts (`outcome_code = 2`) require zero events from the complete core
lifecycle success-transition class (kinds 1, 2, 3, 6: enrolled, revoked,
rotated, retired) — any such event paired with a denied receipt would
record a transition that never occurred.

## Mined vs. new

Re-cut from Franco's #1476 (`0029`/`0030`) and Cea's #4772 committer
schema, re-cut along FK topology and renumbered above the live `main`
tip. The buzz-auth core of #1476 is Cea-authored; `Co-authored-by`
reflects verified per-commit authorship of the mined schema.

Zero Rust/`deletion.rs` edits — the migration-only exclusion widening
keeps `EXPECTED_SCOPED_TABLES` untouched.

---------

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Hayt <9e1c23a3fd83f61da34420e4e88ff1b16e45cafcc0cd9019eb07d4ecfa8ca9b0@buzz.block.builderlab.xyz>
Co-authored-by: Cea Stapleton Cordasco <261786559+cea@users.noreply.github.com>
Co-authored-by: Hayt <9e1c23a3fd83f61da34420e4e88ff1b16e45cafcc0cd9019eb07d4ecfa8ca9b0@buzz.block.builderlab.xyz>

* fix(model-capabilities): humanize databricks goose model names (#7135)

🤖
## Summary
- add curated human-readable labels for Databricks Goose models that
otherwise render as fully qualified identifiers
- render `data_workflow_tools.goose.goose-glm-5-3` as `GLM-5.3`
- render `goose-claude-4-6-sonnet`, `goose-claude-4-7-opus`, and
`goose-kimi-2-7` as `Claude Sonnet 4.6`, `Claude Opus 4.7`, and `Kimi
2.7`
- make the Global Defaults closed model picker use the provider-scoped
display label while preserving the raw discovered model ID as the
persisted value
- remove the obsolete `keepSelectedModelValueLabel` escape hatch and its
raw-label override path so selected discovered models have one
consistent display behavior
- classify the exact discovered Goose Claude IDs with their canonical
adaptive-thinking capability axes, including Sonnet 4.6's exclusion of
`xhigh`
- expand Rust and TypeScript alias coverage and regenerate the shared
139-vector capability corpus

## Test plan
- `cargo test -p buzz-agent --lib` — 517 passed, 1 ignored
- `cd desktop && pnpm test` — 5,821 passed
- Desktop TypeScript typecheck — passed
- Biome on the changed component — passed
- `git diff --check` — passed
- targeted Playwright Global Defaults regression — passed on the
preceding implementation head; the subsequent commit only removes dead
picker-prop plumbing

Verified at `b9609d12696173aa309d2dbaf4f093a502756c36`. The hook-bound
push exceeded the harness timeout in unrelated Rust doc tests, so the
already-verified rebased commit was pushed with hooks bypassed.

Follow-up to #6955.

---------

Signed-off-by: Kalvin Chau <kalvin@block.xyz>
Co-authored-by: am <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz>

* feat(desktop): add isolated named demo builds (#6407)

🤖 I’m Larry, updating this description on Logan’s behalf.

## Summary

Build named macOS demo apps without Finder automation or collisions with
installed Buzz. `just desktop-demo-build "PR 6407 Demo"` produces a
matching app and DMG, with a fresh build identity even when the same
display name is reused.

- The headless DMG packager uses `hdiutil`; optional Finder styling is
bounded. The existing production release recipe is unchanged.
- Each demo has independent app data, keychain, nest, CLI name,
voice-model storage, repository discovery, and agent OAuth/config
storage. Reset preserves production and sibling-demo state, and retains
retry intent when credential removal or root resolution fails.
- Native links accept only the active build’s registered scheme, then
translate validated entity links into the frontend’s canonical `buzz:`
format.
- The recipe builds all six executable sidecars. Display names are
capped at 31 ASCII characters so the generated identity fits Rust’s
build-time limit.

**Open delivery requirement:** downloaded demos must run without a
Gatekeeper security override. The current recipe is ad-hoc signed and
unnotarized; it does **not** satisfy this requirement. Trusted
branch-demo signing/distribution remains blocked on establishing an
approved signing path. This PR is not being presented as complete
download-and-run delivery.

### Related issue

N/A — reported in the Buzz DMG-packaging workstream.

### Testing

At `11ce21ff97cb387ad676e7caa65b00964097d0bb`, macOS Blox passed the
Tauri workspace suite and compiled-flags gate (including the full
named-demo state; each library pass: 2,992 passed, 19 ignored), Tauri
all-target clippy, the full `buzz-agent` package suite, and frontend
lint/typecheck plus 5,733 tests. Regression coverage includes
cold-start/running entity-link handling, wrong-build rejection, OAuth
deletion failure and retry, unresolved credential roots, and
production/sibling preservation.

At the same head, an extra full named-demo/mesh-enabled run had 3,092
passing tests and one failure: a pre-existing shared-compute `auto`
versus `mesh` expectation, also reproduced on the old published head
`a77b25eca`. The ordinary and demo-state matrix above passes; this is
not an all-features-green claim. Live macOS Launch Services delivery
remains unverified.

GitHub CI completed with 30 successful checks and 9 skipped. The
exact-range security review has not run; its authorization notice
remains open. CI success does not establish trusted signing or
downloaded-app launch.

Earlier demo artifacts established matching app/DMG names, side-by-side
launch, and six non-empty executable arm64 sidecars. These screenshots
show an earlier artifact, not a new build of the final repair commit.
Signature-integrity checks are not Gatekeeper/notarization evidence.

<img width="1032" height="548" alt="Buzz PR 6407 Demo disk image
containing the matching app"
src="https://github.com/user-attachments/assets/bca0277e-db03-4308-b280-fcad55e6d601"
/>

<img width="1186" height="821" alt="Buzz PR 6407 Demo running alongside
other Buzz installations"
src="https://github.com/user-attachments/assets/b4bf4ae5-c341-4e15-8090-9d2ea7c623b6"
/>

---------

Signed-off-by: Logan Johnson <loganj@squareup.com>
Signed-off-by: Larry <8cf5a83f590ec0955b11647d1c88f796a98e088c30a492c58e0e46c3026ae7a4@buzz.block.builderlab.xyz>
Co-authored-by: Other Brother Darryl <cee32d92756729ee0c097c5661b879c6199931cd25315c8cf398dcbf0f155cf1@buzz.block.builderlab.xyz>
Co-authored-by: Larry <loganj+sandbox-larry@squareup.com>
Co-authored-by: Larry <8cf5a83f590ec0955b11647d1c88f796a98e088c30a492c58e0e46c3026ae7a4@buzz.block.builderlab.xyz>

---------

Signed-off-by: Philip Azar <pazar@squareup.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Fizz <dae5f6af70b8695a8b83c8deae555f63be41630ec2b8cd493e41a439c9527dd8@buzz.block.builderlab.xyz>
Signed-off-by: Pinky <5f5ab050ec58ae208332edd544ebf705221e24c1b86d82a6ca07038a7a8f6ac9@buzz.block.builderlab.xyz>
Signed-off-by: Brain <1a02c72794dcd0f07058a353bc3a81f4028b8c77c92c87fce6d5c8b85970a20b@buzz.block.builderlab.xyz>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Signed-off-by: Arjun Mahanti <arjun@squareup.com>
Signed-off-by: Fizz <fizz@buzz.local>
Signed-off-by: tulsi <tulsi@block.xyz>
Signed-off-by: Wintermute <c0fc581234c3585602139eec347ced7b82af65b6f6c10728348515c0c06c51c3@buzz.block.builderlab.xyz>
Signed-off-by: Jude Edwards <judeedwards@squareup.com>
Signed-off-by: Salman Mohammed <smohammed@squareup.com>
Signed-off-by: Leo <5faf251baee50ee6bcde338aef6acdd70bb3e60115664c2cd490d94a55dfc488@buzz.block.builderlab.xyz>
Signed-off-by: Hayt <9e1c23a3fd83f61da34420e4e88ff1b16e45cafcc0cd9019eb07d4ecfa8ca9b0@buzz.block.builderlab.xyz>
Signed-off-by: Kalvin Chau <kalvin@block.xyz>
Signed-off-by: Logan Johnson <loganj@squareup.com>
Signed-off-by: Larry <8cf5a83f590ec0955b11647d1c88f796a98e088c30a492c58e0e46c3026ae7a4@buzz.block.builderlab.xyz>
Signed-off-by: shiv <shivchander.s30@gmail.com>
Co-authored-by: Phil Azar <pazar@squareup.com>
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
Co-authored-by: Arjun Mahanti <arjun.mahanti@gmail.com>
Co-authored-by: Fizz <dae5f6af70b8695a8b83c8deae555f63be41630ec2b8cd493e41a439c9527dd8@buzz.block.builderlab.xyz>
Co-authored-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Pinky <5f5ab050ec58ae208332edd544ebf705221e24c1b86d82a6ca07038a7a8f6ac9@buzz.block.builderlab.xyz>
Co-authored-by: LioLionel <62820906+LioLionel@users.noreply.github.com>
Co-authored-by: Brain <1a02c72794dcd0f07058a353bc3a81f4028b8c77c92c87fce6d5c8b85970a20b@buzz.block.builderlab.xyz>
Co-authored-by: Carl <9d00794d3df50972eb8b615511783cab12a77a8fd5dd5edd58073ec73b54bd8b@buzz.block.builderlab.xyz>
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Fizz <fizz@buzz.local>
Co-authored-by: tulsi <tulsi@block.xyz>
Co-authored-by: thomaspblock <thomasp@squareup.com>
Co-authored-by: Wintermute <c0fc581234c3585602139eec347ced7b82af65b6f6c10728348515c0c06c51c3@buzz.block.builderlab.xyz>
Co-authored-by: Jude Edwards <judeedwards@squareup.com>
Co-authored-by: Salman Mohammed <smohammed@squareup.com>
Co-authored-by: Leo <5faf251baee50ee6bcde338aef6acdd70bb3e60115664c2cd490d94a55dfc488@buzz.block.builderlab.xyz>
Co-authored-by: Cea Stapleton Cordasco <261786559+cea@users.noreply.github.com>
Co-authored-by: Hayt <9e1c23a3fd83f61da34420e4e88ff1b16e45cafcc0cd9019eb07d4ecfa8ca9b0@buzz.block.builderlab.xyz>
Co-authored-by: Kalvin C <kalvinnchau@users.noreply.github.com>
Co-authored-by: am <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz>
Co-authored-by: Logan Johnson <loganj@squareup.com>
Co-authored-by: Other Brother Darryl <cee32d92756729ee0c097c5661b879c6199931cd25315c8cf398dcbf0f155cf1@buzz.block.builderlab.xyz>
Co-authored-by: Larry <loganj+sandbox-larry@squareup.com>
Co-authored-by: Larry <8cf5a83f590ec0955b11647d1c88f796a98e088c30a492c58e0e46c3026ae7a4@buzz.block.builderlab.xyz>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

2 participants