Commit Graph

2361 Commits

Author SHA1 Message Date
Wren
e720a5cda8 Clarify missing delegated skew
Distinguish missing delegated skew configuration from the optional
not_before evidence field.

Co-authored-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz>
Signed-off-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz>
2026-08-21 15:57:22 -04:00
Wren
1eb0639a55 Clarify NIP-FI conformance boundaries
Define delegated skew locally, make the DELEG and EDGE path interaction
explicit, and specify mutation subjects for CONF's own oracles. Also align
core and discovery terminology with their actual semantics.

Co-authored-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz>
Signed-off-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz>
2026-08-21 15:52:15 -04:00
Tyler
b8db13d884 docs(nip-fi): state, don't argue — CONF −29%, rationale section, four pins (alt to #5946) (#6437)
## What this is

An alternative shape for the NIP-FI document family, built on top of
#5946 at its current head `772ba7a72` (so it carries R1–R12 and diffs
cleanly against it). Thesis: the current text *argues* where a standard
should *state*, and is silent where a reader needs one paragraph of
*why*. This PR makes every section either a rule or labeled as not one,
and fixes four correctness items found in review.

Base: `772ba7a72` (= `eva/nip-fi-comprehensive`). Eight FF commits, no
rebase. Three files. Oracle census unchanged at **58** (30/6/4/11/7).

**Review lap (Wren, Dawn, Eva) folded in as FF commits
`be72f0a5d`…`db35013c1`** — see "Review findings applied" below.

## Changes

### NIP-FI-CONF — 4,325 → 3,088 words (−29%), no obligation lost
- Denial fixtures 1,516 → ~740 words. Same 16-row table.
Enumeration-agreement checks 4 → 3 (checks 1 and 2 quantified over the
same set). Design history ("Two of these checks shipped red…", the
`Www-Authenticate` canonicalization paragraph, "one layer down", "a slow
way to discover…") removed from normative text.
- Interoperability exit test 934 → ~620 words. Same fixture contents,
same compared objects, same deferral rule, same negative control.
- Release gate restructured as a list; identical conjuncts.
- **BCP 14 keyword census 37 → 36.** Every `MUST`/`MUST
NOT`/`MAY`/`REQUIRED` sentence in the old text was fuzzy-matched to the
new text; the two that don't map are (a) "Their public responses MUST
compare byte-identical to each other, not merely equal in prefix or
status" — fully subsumed by the anonymity comparison one paragraph
later, and (b) the standalone "The suite MUST include a negative
control:" whose obligation is retained in the **Negative control**
paragraph. Reviewers: please try to find a third.

### NIP-FI core
- **New non-normative section "Relationship to other work"** (~190
words): DPoP / mTLS `cnf` with the NIP-42/98 event as the proof; durable
binding vs per-token claim; why 1:1 vs WebAuthn's N credentials; why two
contract IDs plus dependency versions; RFC 6750 error codes; RFC 9421 /
SigV4 family for the edge MAC. The "Thus two contract identities…"
argument paragraph moved here from the normative snapshot section. Three
source links added.
- **Pin:** `skew`, `maximum_assertion_age`, `maximum_status_age`
declared as configured finite values (missing → deny). Previously used
at :177-178/:266/:278 and never declared; EDGE already did this for its
own.
- **Pin:** NIP-98 `payload` hashes one defined referent — "body bytes:
the complete content after transfer decoding and before any content
decoding" — the same stage EDGE MACs as `payload_octets`. Previously
:315 said "bytes consumed by the application" and :320 said "body bytes
received", which differ under `Content-Encoding`. **This is a semantic
shift, not a clarification** (Eva's A3): under the old "consumed"
reading a `Content-Encoding` flip in flight was caught by hash mismatch;
under this pin the same octets hash equal, so the defense moves onto the
server-resolved-body-semantics rule (core:305-308, EDGE:176-178) and the
EDGE Metadata negative row (:367), which are now load-bearing for it.
All four body-wording sites say "substitution of the protected octets",
not "transformation", so content decoding after validation is permitted.
- **Pin:** a denial decided on a WebSocket upgrade, before any NIP-42
proof exists, is the HTTP table row sent instead of `101`; post-connect
denials are the Nostr text. `FI-TRACE-DENIAL-ORACLE` requires each row
"on every surface where its condition can be decided" (a private-state
row needing the proof is not decidable at upgrade — Wren). CONF exit
list aligned.

### NIP-FI-EDGE
- **R12 exception class-scoped** (Dawn's finding): "except an
independent Nostr proof validated on its own signature, such as the
NIP-98 event in `Authorization` or the NIP-42 event after connect"
instead of naming the field. Members are illustrative ("such as") so
`0x03`/`0x04` can conform once their contracts publish. Prohibition
widened to "request or connection component" — Dawn's model shows this
closes 6144 unprotected-connection derivations to 0. Requirement 3 gets
the matching carve-out.
- **`Authorization` byte-identity gains an oracle — as a two-point
witness, not a denial.** EDGE:62-65 makes altering it nonconformant; no
oracle named it. First attempt said "altered … denies"; Eva (A1) and
Dawn's `a1_oracle.py` showed that's false as a universal — an edge
substituting a fresh, valid NIP-98 event from the same actor *admits*,
because `Authorization` is outside the MAC by R12's design.
`FI-TRACE-PROXY-CROSS-REQUEST` now requires the bytes at final admission
to equal the client-sent bytes, witnessed at both points, and names the
mutant that must fail it (valid-proof substitution). The Request matrix
row no longer claims a denial for it.

## Review findings applied (all FF commits on `dcd0b2ff2`)
| # | Finder | Finding | Fix |
|---|---|---|---|
| W1 | Wren | Denial oracle demanded every private row on all three
surfaces; undecidable pre-proof | core:586 "on every surface where its
condition can be decided"; CONF:235-237 aligned |
| W2 | Wren | EDGE:168 and :372 still forbade any post-snapshot
"transformation" | "substitution of the protected octets denies" at all
four sites (core:315, :573; EDGE:168, :372) |
| W3/A2 | Wren, Eva | CONF dropped exit-evidence obligation (old :393) |
CONF:287 "Exit evidence includes the exchanged artifacts and each
implementation's statement of independence." |
| W4/A6 | Wren | Two-device sentence prescribed custody outside the
standard | core:605 "additional devices do not create additional active
bindings." |
| D1/A4 | Dawn, Eva | Em-dash members read exhaustive; `0x03` couldn't
conform | "such as" |
| D2 | Dawn | "a missing value denies" had no detecting oracle |
`FI-TRACE-ASSERTION-VALIDATION` gains "missing-configuration" negative |
| A1 | Eva, Dawn | `Authorization` oracle asserted a false denial
universal | two-point byte witness + valid-substitution mutant (above) |
| A5/W5 | Eva, Wren | Promotion guard retargeted by 4→3 collapse, now
inert | sentence deleted; check 2's disjointness already fails promotion
until the allowlist entry is removed, check 1 validates the class. CONF
MUST NOT 9→8, deliberate |
| D3 | Dawn | Replacement sentence said "only check 2's disjointness
fails" — false in 2/4 promotion cases (check 1 fails too when classes
disagree); "then" implied an ordering the independent conjuncts don't
have | both words dropped (`624429bd8`) |

Not taken this lap (predate this PR): EDGE:180 `maximum_provenance_age`
missing-config oracle; `0x04` Blossom row says "authorization event" not
"Nostr", so the class exception can't reach it until its contract is
written.

## Not changed
LIFECYCLE, DELEG, MODEL: zero bytes. Oracle tables, HMAC vectors,
negative matrix, denial table: zero bytes beyond the two cells named
above.

## Verification
- `git rev-parse HEAD` == `624429bd8` in the shell that produced the
census/keyword numbers below; section word counts above are from
`dcd0b2ff2` (CONF now 3,1k).
- Oracle census: `rg -c '^\| \`FI-[A-Z0-9-]+\` \|'` per file →
30/6/4/11/7.
- Distribution markers: 0 hits across all six docs and the commit
message.
- Keyword census and sentence mapping: script in the PR thread on
request.

## Open questions
1. ~~Two-device sentence~~ — closed by Wren's wording.
2. ~~R12 widening~~ — Dawn's model: net stricter than `772ba7a72`.
3. ~~CONF floor~~ — Eva measured Evidence rules at 317 words, every
sentence an obligation. Floor reached.
4. ~~A3 body-referent shift~~ — accepted by Wren and Eva at `db35013c1`:
the defense is a listed oracle row, hence inside the mutation regime.

## Verdicts
At `624429bd8`: Wren 9/9/9/9 · Eva 9/9/9/9 · Meli 9/9/9/9, each
re-pinned at this exact head. Dawn's three instruments (conf/a1/r12)
rc=0 at this head. CI 14 pass / 0 fail / 9 skipped. Zero known
falsehoods in the text.

Census partition note (Dawn): "58" is unique oracle IDs per document
across FI-TRACE/CONF/LC/DELEG prefixes; partitioned per prefix across
all docs including FI-INV the same corpus totals 75. Quote the partition
with the number.

---------

Signed-off-by: Meli <5aaa86bce934fc3445fc254aab560a40923f10252f92107e665073dede0e04d3@buzz.block.builderlab.xyz>
Co-authored-by: Meli <5aaa86bce934fc3445fc254aab560a40923f10252f92107e665073dede0e04d3@buzz.block.builderlab.xyz>
2026-08-21 09:22:40 -04:00
Eva
772ba7a723 docs(nip-fi): except the NIP-98 proof from the pre-MAC derivation bound
The derivation rule stated that no authorization decision derives from
any request component outside the protected pre-MAC components. The
pre-MAC input enumerates exactly ten components and the Authorization
field is not one of them, yet on every proof-transport-0x02 route the
decision must derive from the NIP-98 proof that field carries: the
trusted-edge requirements mandate the field reach final admission
unmodified and make admitting a 0x02 request without it nonconformant,
and core requires the actor be returned by fresh Nostr-proof
validation. Read literally, the rule and those obligations were jointly
unsatisfiable. The fix excepts the one component that is
integrity-protected by its own signed event rather than by the MAC;
unprotected transport metadata remains excluded, and the Metadata
negative case is unchanged. Oracle census unchanged at 58.

Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Signed-off-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
2026-08-20 16:05:53 -04:00
Eva
783019a95b docs(nip-fi): bind the pending-fixture currency check to the review event
A claim minted while the canonical exit fixture was unpublished (EXIT
validly deferred, digest pending-canonical-fixture) could clear the
release gate after the fixture was published, because
FI-CONF-CLAIM-COMPLETE evaluates at mint time and structurally cannot
observe post-mint publication. Revision-pinned wording is inert: :354
licenses the placeholder only while the fixture is unpublished at
pinning, so a published-at-the-pinned-revision condition can never be
true. The only observer with post-mint visibility is the release-gate
review itself, so the currency check binds there: if the canonical
fixture has been published before the review, the tuple's exit fixture
digest must not be the placeholder. One conjunct, no new terms, no
oracle-lifecycle change; the legitimate deferred shape (fixture still
unpublished at review) survives. Oracle census unchanged at 58.

Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Signed-off-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
2026-08-20 11:14:20 -04:00
Eva
6e03ef2679 docs(nip-fi): restore the list join in the claim-completeness rejection grammar
Adding the fixture-placeholder rejection ground dropped the "or" before
the mutant-evidence ground, grammatically attaching "omitting" to the
status clause instead of continuing the rejection list. A closed
rejection grammar cannot be malformed. One word restored; no semantic
change. Oracle census unchanged at 58.

Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Signed-off-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
2026-08-20 11:00:28 -04:00
Eva
a3fb049a09 docs(nip-fi): carve release conjunct 1 and detect fixture-placeholder misuse
Two residuals from the gate-satisfiability lap, both single-clause,
both found by a two-encoding model of the landed bytes:

Release-gate conjunct 1 still required the claim tuple to pass every
applicable oracle. FI-CONF-INTEROP-EXIT is applicable by construction
-- the deferred exception presupposes it, and not-applicable requires
behavioral proof of an absent surface, which a missing second
implementation is not. Deferred is neither pass nor not-applicable, so
under the literal reading the gate still failed on its first conjunct
for exactly the shape the deferral exists to permit; only a reader
supplying the unwritten carve made it pass. The conjunct gains the same
validly-deferred pointer as the other two. This residual was created by
this lap: the sentence is byte-identical across heads, but "deferred"
entered the vocabulary it quantifies over.

The pending-canonical-fixture reserved value was valid only alongside a
deferred exit result, but no listed oracle detected a violation --
FI-CONF-CLAIM-COMPLETE enumerated its rejection grounds and tuple-value
validity was not among them, admitting the one reachable lying-tuple
state: exit run and passed against the published fixture while the
tuple still records the placeholder, defeating the tuple's purpose of
naming the inputs actually run. The row gains that rejection ground.

Verified: two-encoding gate model at the amended text reads literal
one-implementation admission 0 -> 3 with the strict/literal delta
closing to 0, attribution control varying only the conjunct-1 carve
(arms disagree), exemption inert with a second implementation, no leak
to ordinary oracles; fixture-misuse enumeration has the old row
admitting both reachable misuse states and the amended row zero, with
every legal shape kept and an over-broad clause that kills the
legitimate deferred-plus-placeholder shape as the positive control.
Oracle census unchanged at 58.

Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Signed-off-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
2026-08-20 10:57:12 -04:00
Eva
f76231be52 docs(nip-fi): make the global-control obligation oracle-detectable
The gate-satisfiability repair left the two global controls
(deny-everything, allow-everything) as normative prose no listed oracle
could detect: FI-CONF-MUTATION required mutants and survivor handling
only, FI-CONF-CLAIM-COMPLETE caught omitted mutant evidence only, and
the release gate names mutants and the denial-fixture control only. A
report omitting both global controls satisfied every listed oracle
outcome -- by this document's own untestable-prose rule, a defect.

FI-CONF-MUTATION gains one clause: the deny-everything and
allow-everything global controls fail every oracle Mutation adequacy
requires of them, with retained evidence. "Requires of them" inherits
the centralized deferred-exit exclusion, so a one-implementation
deferred claim stays satisfiable -- the controls run over every other
listed oracle -- and the observation obligation attaches with the run.

Verified by enumeration of the report space (control evidence presence,
exit status, exit observation): the prior row accepts all 12
control-omission reports; the amended row accepts exactly the two
intended shapes (deferred without the impossible exit observation,
passed with it) and rejects omission, partial evidence, and claims of
the impossible observation. Positive control: a clause ignoring the
deferred exclusion re-breaks one-implementation satisfiability. Oracle
census unchanged at 58.

Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Signed-off-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
2026-08-20 10:46:52 -04:00
Eva
0863c736d6 docs(nip-fi): defer every impossible interop-exit obligation, not just the result
The R6 fix let FI-CONF-INTEROP-EXIT be recorded as deferred while no
independent second implementation exists, but four unchanged rules still
demanded evidence only the two-party run can produce: the per-oracle
mutant with failing output (Mutation adequacy), the release-gate mutant
clause, the FI-CONF-MUTATION row's universal quantifier, and the
deny-everything global control that must observe every positive oracle
fail. The exact condition permitting deferral made all four obligations
impossible to discharge, so the release gate stayed unsatisfiable with
one implementation -- the same defect class R6 was fixing, one layer
down.

The repair is one centralized rule in Mutation adequacy: while
FI-CONF-INTEROP-EXIT is validly deferred it remains in claim
completeness but is excluded from that section's mutation and
global-control obligations, which attach with the run and must be
discharged before either implementation's interoperable conformance
claim is accepted. The release gate and the FI-CONF-MUTATION row point
at that rule rather than restating it, and FI-CONF-CLAIM-COMPLETE
rejects mutant-evidence omission for any other oracle, so misuse of the
exemption is itself oracle-detected.

The claim tuple's exit-fixture-digest element also carried an
obligation no single implementation could meet: CONF said the canonical
fixture "is published" at docs/nips/fixtures/nip-fi-conf-exit.json, but
the file does not exist yet and every claim must record its digest. The
fixture sentence becomes a publication requirement bound to the first
run, and while the file is unpublished a claim records the reserved
value pending-canonical-fixture, valid only alongside a deferred exit
result; publication changes the tuple element and therefore creates a
new claim.

Verified by exhaustive enumeration of the gate over second-implementation
presence, exit status, exit mutant evidence, control observation,
ordinary-oracle gaps, smuggled deferrals, and fixture-digest state
(128 states): the prior text accepts no one-implementation claim; the
three-site repair without the global-control shape still accepts none;
this text accepts exactly the two intended deferred-claim shapes and
rejects all six modeled abuse states. Positive control: an over-broad
exemption that waives ordinary mutants under deferral is accepted by a
deliberately broken gate and rejected by this one. With a second
implementation present, deferral is invalid and every obligation is
restored. Oracle census unchanged at 58.

Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Signed-off-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
2026-08-20 10:42:05 -04:00
Eva
b0aafc41e7 docs(nip-fi): classify every revalidation dependency and reread status at final admission
The equivalence catch-all sent every unclassified field to identity-class
byte-equality, but the provenance class named only snapshot version and
verification-key identity. The remaining revalidation_dependencies members
-- binding, lifecycle, local-policy, and resource versions; status source
and version; proof and replay witnesses; the confidential JWS handle --
defaulted to byte-equality, making "changed dependencies require current
revalidation to an equivalent result" unsatisfiable on any version bump
and the concurrent-enrollment recompute never equivalent. Both readings
failed closed; this was an availability and interoperability defect.

The repair classifies every revalidation_dependencies member: bounds-class
if it is a deadline, provenance-class otherwise, with the identity-class
catch-all narrowed to assertion-content fields. Provenance-class ignoring
is conditional on successful current revalidation, so it is safe only for
members final admission actually rereads -- and the reread list at final
admission named status nowhere, while the per-use check already checks
status versions. Under the guards as literally written, demoting status to
provenance-class would have let final admission admit with upstream status
inactive: the one dependency the residual-revocation bound exists to
close. Status joins the final-admission reread list.

Verified by a bounded model of the composed system in two encodings
(strict = inferred guards, literal = written guards only), 8748 reachable
states per arm: baseline classification reads 0 authority extensions /
127 unsatisfiable in both encodings; the reclassification without the
status reread reads 64 extensions under the literal encoding, all rooted
in status_active=False; with the status reread, 0 extensions and 0
unsatisfiable in both encodings. Controls: a deadline demoted to
provenance-class is detected (128 states, still detected against the
fixed reread set), and a per-member sweep is structurally blind to the
status defect -- only promoting a predicate's whole detector group
exposes it. Delegated leases carry their own dependency list in
NIP-FI-DELEG and were not remodelled; this classification governs the
direct-path equivalence rule it amends.

Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Signed-off-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
2026-08-20 10:24:08 -04:00
Eva
56e74141e7 docs(nip-fi): resolve contradictions between the spec, its own rules, and the deployed wire
External line-by-line review against the implementation found compositions
no internal-consistency pass could see: rules that contradict each other
across documents, and registry text that denies the wire the deployed proxy
actually signs. Every reading failed closed, but a correct implementation
could not be built from the text alone. Fixes, one per defect:

- EDGE strip rule no longer removes the NIP-98 proof: requirement 1 named
  "authorization" among the fields every trusted edge strips, while core
  reserves the Authorization header for the NIP-98 proof that must reach
  final admission. The rule now names the three NIP-FI fields plus other
  edge profiles' fields, explicitly preserves Authorization, and a negative
  conformance sentence makes stripping or rewriting it nonconformant.

- Proof-transport codes 0x03 (Git smart-HTTP session) and 0x04 (Blossom
  media) are reserved with fixed meanings: the deployed proxy emits both
  inside the MAC input, and the registry declared them unassigned, so a
  conforming verifier denied every Git and Blossom envelope. Allocation
  completes when each transport contract is published; until then the codes
  are valid only under an explicit shared proxy/verifier contract and are
  not portable interoperability.

- Replay moves from evidence_rejected to authorization_denied: replay
  status is committed per-principal server state, so classing it as
  evidence rejection contradicted the supplied-evidence privacy rule and
  let an attacker resubmit captured evidence to learn whether the original
  request committed. The CONF fixture table mirrors the move.

- The current-status witness match now requires issuer equality: the
  witness tuple carried iss but the match rule checked only subject and
  session, so on a shared status source an issuer-B witness could satisfy
  a pre-revocation issuer-A assertion. The stale-status oracle gains the
  mismatch arm.

- The interoperability exit test is deferrable while no independent
  implementation exists (machine-readable reason, re-evidenced when the
  second implementation claims), and the canonical shared exit fixture
  gains provenance: editor-authored, published at
  docs/nips/fixtures/nip-fi-conf-exit.json with its SHA-256 digest. A
  required gate no implementation could ever pass was, by this document's
  own standard, a defect in the document.

- The "RFC 9068 access token" class is renamed at+jwt access token at all
  three sites: it selects on typ and aud and validates under this
  document's claim contract, not the full RFC 9068 profile.

- EDGE states the unbound-body-metadata rule directly instead of by
  cross-document inference, and the negative matrix gains the
  Content-Type/Content-Encoding mutation row.

- Absent or incomplete edge provenance maps to missing_evidence; present
  but failing provenance maps to evidence_rejected. The CONF fixture table
  gains the absent-provenance row; rows are renumbered, which is safe
  because CONF itself requires that no text reference rows by number.

- A payload tag on an authorization-irrelevant body is validated
  identically, closing a verifier divergence the text left open.

Oracle census is unchanged at 58 (30 core, 6 EDGE, 11 LIFECYCLE, 7 DELEG,
4 CONF): the two table additions are a negative-matrix row and a
denial-fixture row, neither in the oracle denominator. One review item
(equivalence classification of revalidation dependencies) is deliberately
not in this commit pending model verification that the reclassification
opens no authority-extension path.

Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Signed-off-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
2026-08-20 10:19:47 -04:00
Max
4f913a8132 docs(nip-fi): close discovery and conformance gaps
Co-authored-by: Max <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@buzz.block.builderlab.xyz>
Signed-off-by: Max <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@buzz.block.builderlab.xyz>
2026-08-18 11:43:07 -04:00
Dawn
17d455a9bf docs(nip-fi): scope administrative expiry to the grant and stop rotation clearing it
Two defects found by running a bounded state model of the composed core +
LIFECYCLE machine in two encodings -- one holding every guard the documents
state plus the guards a careful implementer would infer, one holding only the
guards actually written. Both were invisible to four line-by-line readers
because reading supplies the missing rule unconsciously.

Defect 1: administrative expiry was escapable by rotation. LIFECYCLE said an
expired binding "remains durable and occupies both sides of the core partial
bijection", so it stays in the relation -- and core Rotate replaces one exact
"active" binding with "a fresh binding version" while no document said the
replacement inherits binding_not_after. Reading the expired binding as active
let rotation authority alone clear a bound only expiry authority had set;
reading it as inactive removed it from TargetEligible, letting
ProvisionBinding double-bind the identity or bind the administratively
terminated key to a different identity, violating FI-INV-01 -- the invariant
LIFECYCLE was leaning on. FI-LC-ADMIN-EXPIRY did not detect either: core Rotate
is authorized and version-checked, so the escape satisfied the oracle's own
wording.

Defect 2: nothing in the six files said what binding_not_after is a bound *on*.
Two reviewers resolved it in opposite directions -- one grant-scoped, one
identity-scoped -- and the readings differ by 25,200 reachable states. The
identity-scoped reading also makes RetirePair followed by ordinary enrollment an
escape, which under the grant-scoped reading is a legitimately new grant.

Repairs:

- Core Rotate states that it continues one grant onto a new key and preserves
  every profile-defined administrative bound as an opaque field core neither
  interprets nor clears.
- LIFECYCLE declares the bound grant-scoped, names the transitions that
  establish a new grant carrying no prior bound, and points identity-scoped
  deadlines at the capability projection in authoritative local-policy state,
  which already carries the revocation-bound machinery such a deadline needs.
  Encoding one here instead would put an authorization-relevant deadline in a
  store with no revocation bound.
- LIFECYCLE defines a binding with a reached bound as active for every core
  relation and eligibility test, closing the TargetEligible horn.
- Binding-creating transitions must declare continuation or establishment; the
  two cases partition that set with no remainder, so a future profile cannot
  add a creator whose bound semantics are undefined.
- FI-LC-ADMIN-EXPIRY gains the rotation-must-not-clear arm and the negative arm
  that retire-then-enroll yields an unbounded binding and is conformant, so the
  oracle no longer admits the violation it exists to catch.

Verification of the shipped bytes, not the intent: re-encoding the model from
this text gives 0 grant-scoped escapes (17,184 before) and 0 FI-INV-01/FI-INV-03
regressions over 282,528 states across all three enrollment policies. Each
clause was mutated separately and each goes red only for its own clause --
dropping the Rotate carry restores 21,216 escapes, and dropping the active
definition admits both FI-INV-01 violations. A clause that cannot be made to
fail is not a guard.

Co-authored-by: Tyler Longwell <tlongwell@squareup.com>
Signed-off-by: Tyler Longwell <tlongwell@squareup.com>
2026-08-18 11:32:56 -04:00
Eva
513e03b069 docs(nip-fi): fix pinned NIP source links and identifier-classification contradiction
Review findings from Mari and Quinn on #5946 at 2511d1baf:

- Repoint the pinned NIP-42/NIP-98 source links from a nonexistent
  upstream commit (404) to the resolved commit
  6d2979b3f503a8539c983efbcdcf901bbcf9ed23 (both 200).
- NIP-FI-EDGE no longer classifies transport_contract_id as
  interoperability-critical, which contradicted core's exclusive
  deployment-local classification. The value stays deployment-local;
  the contract semantics EDGE contributes to it are what is normative.
- Core's rejection-and-privacy absolute ("never of private server
  state") now carries the authorization_unavailable carve-out its own
  table and NIP-FI-CONF already relied on.

Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Signed-off-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
2026-08-17 22:32:29 -04:00
Eva
2511d1baff docs(nip-fi): align the worked example with the repaired exit contract
The non-normative worked example still promised that the NIP-FI-CONF exit
fixture pins complete byte-exact values. After the compared-object repair,
CONF deliberately does not pin byte-exact minted requests: it pins the
request compared objects (signing inputs and semantic JSON values) and
excludes signature octets. Restating the removed contract is exactly the
stale-restatement defect this batch closed; align the phrase.

Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Signed-off-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
2026-08-17 15:44:19 -04:00
Dawn
15d3fb2e0d docs(nip-fi): close the denominator contract in its own oracle
The denominator rewrite changed the body and left every summary of it
behind. Three sites still promised the deleted requirement-level contract:
the global-controls paragraph, the release gate, and -- worst -- the
FI-CONF-MUTATION row itself, which is one of the four CONF entries the
denominator counts. The gate contradicted the body it gates, so the
original ambiguity survived inside the oracle that claims to close it.
FI-CONF-INTEROP-EXIT likewise still demanded byte-identical requests the
section above had already stopped requiring. All four now say listed
oracle and compared object.

The exit test promised a request on the WebSocket upgrade and defined a
signing-input object only for NIP-98. Define the NIP-42 half alongside it,
compared against its own transport's NIP-01 serialization, and pin the
complete unsigned event fields for both transports in the fixture. The
assertion object compared a protected header the fixture never pinned;
pin the complete protected-header and claim-set JSON values.

Correct a false premise: signatures were said to differ because ES256 and
BIP-340 each draw fresh randomness. Deterministic ECDSA and fixed-aux
BIP-340 are conforming and need not differ. The conclusion is unchanged --
conforming implementations may disagree on octets, so an object requiring
equal octets fails conforming pairs -- but the reason now matches the
algorithms as specified rather than as commonly configured.

Give FI-TRACE-CAPABILITY-REVOCATION a lawful N/A. Core makes it
conditional on a configured revocation-bounded external projection, while
Applicability required every unlisted oracle, so a deployment with no such
projection had no disposition it could honestly claim. Same defect class
the offline-jwt fix closed for the current-status oracles, one oracle over.

All findings are Wren's, verified against the cited lines before editing.
A rewrite that changes a contract has to grep for every restatement of the
old one; the body moved and the summaries did not, which is exactly the
drift this document exists to make impossible.

Co-authored-by: Tyler Longwell <tlongwell@squareup.com>
Signed-off-by: Tyler Longwell <tlongwell@squareup.com>
2026-08-17 15:44:19 -04:00
Dawn
f29e2092b6 docs(nip-fi): make the conformance denominator countable
CONF's mutation adequacy said "each normative requirement in core," which
admits four readings with a 3x spread. Make the denominator the listed
oracle, selected structurally: rows whose first cell is one complete literal
oracle ID, in core and each claimed normative profile. Selecting by section
title would silently drop EDGE's five oracles, whose table lives under
"Discovery and conformance"; core's heading is "Core behavioral oracles" and
no two of the tables agree. Verified 30/5/11/7/4 = 57 rows, zero shorthand,
zero duplicates, zero orphans, at a8fab1ad0.

The exit test required byte-exact requests from two implementations. ES256
and BIP-340 draw fresh randomness per signature and nothing here pins JSON
member order, so no conforming pair could ever pass. Compare signing inputs
instead: the NIP-01 serialization for the proof, decoded header and claim
set as JSON values for the assertion. Shipping pre-minted bytes would have
stopped testing whether an implementer can mint from the document at all.

Add the governing document revision and exit fixture digest to the claim
tuple: evidence means different things under different text, and a claim
that does not name what it was judged against is unfalsifiable once the
specification moves. Exempt the synthetic exit fixture from the redaction
rule it otherwise contradicts, and give offline-jwt deployments a lawful
N/A for the two current-status oracles, which have no witness to test.

Subtraction: the enumeration-agreement subsystem hand-copied core's denial
symbols into CONF prose, then spent four checks and two rationale paragraphs
guarding the copy. Read the set from core instead and the copy's guards have
nothing to guard: seven checks collapse to three. Promotion detection is
kept by folding disjointness into check 3 rather than restoring a check --
found because a six-mutant matrix showed the reduced set letting a promoted
allowlist entry survive, falsifying a sentence I had just written. All six
mutants now killed, green on the conforming document. Also fold three
paragraphs restating one rule about unpinned header fields into one.

Net +24 lines on CONF. The row splits and the two new tuple elements are
not recoverable by deleting text that carries a guarantee, and compressing
identifiers to reach a target would trade a real property for a number.

Co-authored-by: Tyler Longwell <tlongwell@squareup.com>
Signed-off-by: Tyler Longwell <tlongwell@squareup.com>
2026-08-17 15:44:19 -04:00
Eva
edc66ef8d8 docs(nip-fi): add the promised worked wire example
The PR description promises a worked wire example in core; none existed.
Add one compact non-normative section: a protected HTTP POST under
client-attached with NIP-98 proof and a relevant body, showing how the
two evidence fields, body binding, and the admission phases compose on
the wire. Byte-exact values remain the province of the NIP-FI-CONF exit
fixture; the example elides credentials so it cannot be mistaken for a
conformance vector.

Twenty-six lines, kept deliberately smaller than the alternative of
striking the promise would save in credibility.

Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Signed-off-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
2026-08-17 15:44:18 -04:00
Eva
ffc4e654c7 docs(nip-fi): close temporal, allocation, and denominator ambiguities
Three defects from the independent review lap, each fixed by making the
text impossible to read two ways:

DELEG evidence boundaries used 'live' (undefined) and declared equality
at either temporal boundary expired, which is incoherent for a lower
bound and contradicted core's own inclusive-nbf/exclusive-exp idiom.
State both comparisons explicitly, matching core: now < mandatory_expiry
and not_before <= now + skew.

The delegated lease deadline mixed a configured duration into a minimum
of absolute instants. Anchor it: lease issue instant plus the configured
delegated maximum.

EDGE allocated proof-transport codes 0x03/0x04 to Git and Blossom
profiles that do not exist, violating the table's own allocation rule
(a published stable specification with validation, binding, freshness,
replay, and vectors). Unassign both; the unassigned range now starts at
0x03.

Core's oracle table compressed six pairs of oracle IDs with slash
shorthand that has no consistent expansion rule: three pairs read
replace-last, three read keep-prefix, so no reader can infer the
grammar. Split all six into one row per literal ID. The table now lists
exactly 30 rows, one complete ID each, making the oracle set mechanically
enumerable as the mutation-adequacy denominator CONF will reference.
Every prose citation resolves to a table row; no shorthand remains.

Review findings: Wren (DELEG boundaries, delegated maximum, EDGE codes,
shorthand ambiguity), Dawn (denominator enumerability measurement).

Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Signed-off-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
2026-08-17 15:44:18 -04:00
Dawn
d123e4e0bb docs(nip-fi): make the conformance oracles derivable
Two of the seven enumeration checks were red on the conforming document.
Check 7 compared a set of rows against a set of symbols, and two
core-attributed rows name no symbol, so it could not pass at any head.
Check 5's head clause admitted the one row its own qualifier excluded. A
check that is red on a conforming document detects nothing: it cannot be
observed to flip, so every mutant reads as caught. Both are corrected and
the requirement to run the checks green before scoring a mutant is now
written where the checks live.

Address the allowlist and the anonymity set by symbol instead of row
number. This table is required to grow, and a profile appending one row
renumbers the positional references: the allowlist would name the wrong
condition and a new denied condition would fall outside the byte-identity
requirement without any check firing.

State the three readings the compared object left to convention. An
independently written conforming implementation diverges on each by
language default: a canonicalizing HTTP library emits Www-Authenticate, a
server that sets no Content-Length frames the body as chunked, and a JSON
encoder spaces its separators. Header names match case-insensitively per
RFC 9110 Section 5.1, the compared body is the content per Section 6.4,
and the Nostr message is compact JSON per NIP-01's serialization rules.
Content-Length stays unpinned; framing is the sender's choice and pinning
it would widen the object for no privacy gain. An exit test whose result
depends on an unstated convention is the same defect as an oracle no pair
can satisfy, one layer down.

Co-authored-by: Dawn <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@buzz.block.builderlab.xyz>
Signed-off-by: Dawn <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@buzz.block.builderlab.xyz>
2026-08-17 15:44:18 -04:00
Dawn
d163ad172f docs(nip-fi): make conformance oracles satisfiable
The denial-fixture enumeration named four core denial symbols; core now
denies six. Correct the count and names, reattribute binding_required to
core, and widen row 9 to cover any unrecognized enrollment policy, which
core's terminal else also catches. Add enumeration-agreement checks so a
future core symbol cannot silently falsify the sentence again, including
two that guard the prose-only allowlist itself: promoting an allowlisted
condition to a real symbol satisfies the attribution check rather than
failing it, so only a minimality check makes that rot visible.

Replace the interoperability compared object with what core pins: status,
body, and the values of only the header fields core names. Comparing
ordered header names failed every conforming pair. State that the
anonymity comparison stays wider, so a private reason code in an unnamed
header is still a defect.

Parameterize the exit test by a shared fixture. Independence is a claim
about code, not inputs: two implementations given different issuers, keys
or clocks cannot produce equal bytes however correct both are.

Co-authored-by: Dawn <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@buzz.block.builderlab.xyz>
Signed-off-by: Dawn <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@buzz.block.builderlab.xyz>
2026-08-17 15:44:18 -04:00
Max
6b450122ac docs(nip-fi): close assertion and lifecycle semantics
Co-authored-by: Max <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@buzz.block.builderlab.xyz>
Signed-off-by: Max <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@buzz.block.builderlab.xyz>
2026-08-17 15:44:18 -04:00
Wren
1211a71591 docs(nip-fi): close core interoperability gaps
Co-authored-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz>
Signed-off-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz>
2026-08-17 15:44:18 -04:00
Max
99d9155e6f docs(nip-fi): require domain and issuer separation
Co-authored-by: Max <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@buzz.block.builderlab.xyz>
Signed-off-by: Max <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@buzz.block.builderlab.xyz>
2026-08-17 15:44:17 -04:00
Wren
8fad46d5cb docs(nip-fi): close direct enrollment denials
Co-authored-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz>
Signed-off-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz>
2026-08-17 15:44:17 -04:00
Dawn
7dfaa74aa6 docs(nips): specify FI conformance evidence profile
Defines what counts as evidence that an implementation has the behavior core
and the profiles require: the immutable claim unit, evidence and anti-evidence
rules, the complete per-private-condition denial fixture enumeration, mutation
adequacy, and the two-implementation interoperability exit test.

Three rules carry the weight. Denial fixtures are enumerated per private
condition rather than per public class, because a per-class suite compares a
class against itself and passes trivially. Mutation adequacy requires each
normative requirement to have a singly-applied, attribution-checked,
reachability-witnessed mutant killed by its named oracle, because naming an
oracle proves a requirement is claimed, not that the oracle can fail. The exit
test requires byte-exact request and per-class denial bytes produced from the
documents alone by two independent implementations.

FI-TRACE-TOFU-THEFT takes a configuration witness only: under the private
posture ruling no discovery output distinguishes enrollment mode, so a
discovery witness for that oracle cannot exist.

Co-authored-by: Dawn <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@buzz.block.builderlab.xyz>
Signed-off-by: Dawn <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@buzz.block.builderlab.xyz>
2026-08-17 15:44:17 -04:00
Max
528c847262 docs(nips): tighten FI assertion security semantics
Co-authored-by: Max <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@buzz.block.builderlab.xyz>
Signed-off-by: Max <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@buzz.block.builderlab.xyz>
2026-08-17 15:44:17 -04:00
Wren
8f6b5c7685 docs(nips): define federated identity core
Co-authored-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz>
Signed-off-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz>
2026-08-17 15:44:17 -04:00
Perci
0c283a558d docs(nips): specify trusted edge profile
Co-authored-by: Perci <5a968df9a7494b4e019b9ecf739e088ba61097b4312124e9a88ae5b42e3f5f3e@buzz.block.builderlab.xyz>
Signed-off-by: Perci <5a968df9a7494b4e019b9ecf739e088ba61097b4312124e9a88ae5b42e3f5f3e@buzz.block.builderlab.xyz>
2026-08-17 15:44:17 -04:00
Max
9f2cb82ff3 docs(nips): specify FI binding lifecycle profile
Co-authored-by: Max <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@buzz.block.builderlab.xyz>
Signed-off-by: Max <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@buzz.block.builderlab.xyz>
2026-08-17 15:44:17 -04:00
Eva
19dce1ade6 docs(nips): skeleton for comprehensive NIP-FI restructure (core + 4 profiles)
Pins the file layout for the approved 9/9/9/9 blueprint: a ~350-line normative
core plus separately claimable EDGE / LIFECYCLE / DELEG / CONF profiles and a
non-normative model companion. Slice owners replace stub content; the skeleton
exists so parallel authors share one layout.

Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Signed-off-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
2026-08-17 15:44:16 -04:00
Jordan Mecom
85bacea52b Remove GitHub security advisory commitment (#6144)
Removes the promise in `SECURITY.md` to publish a GitHub Security
Advisory after every security fix is released.

The disclosure policy continues to state that Buzz follows coordinated
disclosure and credits reporters unless they request anonymity.

Checked with `git diff --check`.

Signed-off-by: Jordan Mecom <jm@squareup.com>
2026-08-17 11:48:38 -07:00
klopez4212
076081bfc6 Rename Bumble agent to Pollen (#5864)
## Summary
- Rename the built-in Bumble agent to Pollen across desktop, onboarding,
docs, and test fixtures.
- Migrate existing stock definitions and instances in place while
preserving customized fields and the stable persona coordinate.
- Reserve the Pollen name by removing it from Fizz's generated-name
pool.

## Validation
- Pre-push desktop checks, typecheck, 4,791 frontend tests, Tauri
clippy, and 2,432 native tests
- Desktop E2E build

---------

Signed-off-by: kenny lopez <klopez4212@gmail.com>
Signed-off-by: Kenny Lopez <klopez4212@gmail.com>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <3c4caeafb646d23867f1c4832e68211d77e2561946171625f75c3ce1a3f2670f@buzz.block.builderlab.xyz>
Co-authored-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
2026-08-17 10:49:03 -07:00
Will Pfleger
d12d825778 fix(desktop): resolve agent profiles through one archive-aware selector (#5706)
Agent profiles resolve through one shared selector (`pickProfileAgent`)
at every entry point — the persona card, the profile panel, and library
grouping. That selector ranked instances only by active/name, with no
archive awareness, so a relay-archived instance early in file order
could hijack the persona card and the profile panel. The persona card
also recorded a durable pubkey target, which could strand the panel on
an archived identity when the click landed during the archive-snapshot
fail-open window. The profile panel's Runtime → Instances roster had the
same blind spot: it rendered every persona instance raw, so archived
instances appeared mixed in with live ones as if active.

This makes the shared resolution path archive-aware via the existing
fail-open `useIsArchivedPredicate`:

- `pickProfileAgent` filters archived instances before ranking and
returns `undefined` when every instance is archived (persona-only mode).
- `buildUnifiedGroups` drops archived agents from the standalone `Custom
agents` and `Unknown agents` buckets; matched persona groups keep their
full list and rely on the selector's persona-only fallback.
- `useCanonicalManagedAgentProfile` resolves through a pure
`resolveCanonicalManagedAgent` helper that applies the target-provenance
rules: a deliberately requested archived pubkey stays exact (so its
archive controller can unarchive it, even when a live sibling exists),
`preserveRequestedInstance` still pins a Runtime → Instances selection,
and non-archived historical navigation keeps its canonicalization.
- The persona card's main click records a persona target that
re-resolves every render, so it self-corrects to a live sibling after
hydration. Deliberate instance navigation and the runtime-error
affordance keep their explicit-pubkey path.
- The Runtime → Instances roster (`ProfileInstancesSection`) buckets
instances off the same predicate via `bucketPersonaInstances`: live rows
render as before, and archived rows move under a labeled `Archived`
subsection. The instance count reflects both buckets, and archived rows
keep their explicit-pubkey click so unarchive stays UI-reachable (the
deliberate-navigation path above).

The predicate is fail-open (treats every identity as live while the
relay archive snapshot loads) and self-exempt, so a cold start never
hides an identity and a user is never folded from their own client.
While the snapshot is loading, every instance renders in the live list —
nothing hidden, nothing labeled.

---------

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
2026-08-17 13:46:25 -04:00
Luke Tornquist
54f11219ef fix(acp): gate relay-signed workflow messages on their attributed author (#6129)
## Problem

Scheduled workflow `send_message` actions fire and land in the channel
with correct `p` tags for the mentioned agents — but the agents never
wake. The wake-up is silently dropped.

**Root cause:** workflow messages are signed by the **relay keypair**
(`workflow_sink.rs` signs with `state.relay_keypair`), so `event.pubkey`
is the relay's pubkey, not the workflow owner. In `buzz-acp`, the
inbound author gate (`author_allowed`) runs **before** the `p`-tag
mention check. Under the default `respond_to = owner-only`, the relay
pubkey is neither the owner nor a sibling, so every workflow wake-up
dies at the gate with a debug-level `"inbound author gate — dropping
event"`.

The relay-side comment even says the mention `p` tags exist *"so
mentioned agents are woken (wake is p-tag gated)"* — but wake is also
author-gated, and that path was missed.

## Fix

Gate relay-signed workflow messages on their **attributed author** — the
pubkey that created the workflow — instead of the relay pubkey:

- **Relay:** `workflow_sink.rs` now emits an explicit
`buzz:workflow-owner` tag carrying `workflow.owner_pubkey` (the workflow
creator, which the executor already passes as `author_pubkey` and whose
channel access the relay verifies before emitting). Ownership is never
inferred from `p`-tag order; mention `p` tags play no role in
attribution.
- **Harness:** at startup, `buzz-acp` fetches the relay's NIP-11 `self`
pubkey (new `RestClient::fetch_relay_self`, public `/info` endpoint).
Best-effort: fetch failure just logs a warning and preserves pre-fix
behavior.
- **Gate:** an event that is (a) authored by the relay `self` key, (b)
tagged `buzz:workflow`, and (c) carries a well-formed
`buzz:workflow-owner` pubkey is gated on that owner, through the exact
same owner/sibling/allowlist policy as a direct author.

## Security notes (all fail closed)

- No NIP-11 `self` pubkey → no exemption.
- `buzz:workflow` / `buzz:workflow-owner` tags on a non-relay-signed
event → ignored (a member cannot forge the exemption; the relay verifies
signatures on submission and only the relay holds its key).
- Relay-signed event without the tags, or with a malformed owner value
(not 64-hex) → plain author gate.
- Who is @mentioned in the message has no bearing on whose authority is
evaluated.
- A workflow owned by a random channel member still cannot wake an
owner-only agent — the owner's pubkey must pass the same policy.

## Testing

- 7 unit tests (`workflow_attributed_author_tests`) covering
attribution, fail-closed paths, p-tag independence, malformed owner
values, and the forgery case.
- Extended the PG-gated `workflow_send_message_p_tags_mentioned_member`
integration test to assert the `buzz:workflow-owner` tag.
- `cargo test -p buzz-acp`: 785 passed, 0 failed. `cargo test -p
buzz-relay --lib workflow_sink`: 17 passed. Clippy + fmt clean. (9
pre-existing `buzz-relay` failures in unrelated
`api::media`/`api::admin` tests fail identically on the base commit
without this change.)

Found while debugging scheduled automations in a Buzz review-pipeline
channel: two cron workflows fired daily @mentions at agents that never
responded, while direct human @mentions woke them instantly.

---------

Signed-off-by: Luke Tornquist <tornquist@squareup.com>
Co-authored-by: Fizz <3a9f8a30fbb462abec1e2977b2280a7ae50c7ff794433790be15bd48bfd52d0b@buzz.block.builderlab.xyz>
2026-08-17 13:29:20 -04:00
Atish Patel
5b3f0375a2 fix(acp): replace Goose native system prompt (#5964)
## Why

Buzz currently appends its managed prompt to Goose's native prompt, so
managed agents receive both instruction sets instead of the intended
Buzz-only system prompt.

## What

- Send Goose's custom session system-prompt request with `mode: "set"`
- Lock the replacement contract in the ACP request test

## Risk Assessment

Low — the change is limited to Goose session setup; adapters that do not
implement Goose's custom method keep the existing method-not-found
fallback behavior.

## References

Goose v1.46.0 routes `set` to `override_system_prompt`, and its prompt
builder selects that override instead of rendering the native
`system.md`: [ACP
handler](98c11ce2ee/crates/goose/src/acp/server/manage_sessions.rs (L57-L93)),
[prompt
builder](98c11ce2ee/crates/goose/src/agents/prompt_manager.rs (L153-L191)).

Validated end to end against the official Goose v1.46.0 binary with a
local OpenAI-compatible capture server: the provider request contained
the exact Buzz replacement prompt and did not contain Goose's native
base-prompt marker.

---

**Update Aug 15, 13:17 CDT:** Added the [Terra-high prompt-ablation
comparison](4492f76349/data/prompt-ablation/20260815-terra-high/comparison.md).

The Goose conditions used GPT 5.6 Terra at high effort on the same 11
Terminal-Bench 2.1 tasks, with two attempts per task and concurrency
four. The matched `append-full` and `set-full` runs used the same
persona and included the same Buzz platform prompt; Active-h is the
primary measure because it excludes Buzz lifecycle overhead.

| Goose condition | Pass | Active-h | Median active | Agent-h | Wall-h |
Tool calls |
|---|---:|---:|---:|---:|---:|---:|
| Native prompt + Buzz prompt (`append-full`) | 21/22 | 0.3042 | 0.85
min | 0.3974 | 0.1496 | 234 |
| Native prompt + persona only (`append-persona-only`) | 22/22 | 0.3050
| 0.75 min | 0.3990 | 0.1498 | 204 |
| Buzz prompt replaces native prompt (`set-full`) | 22/22 | 0.3340 |
0.87 min | 0.4296 | 0.1551 | 275 |

Replacing instead of appending produced one additional passing attempt,
but it was not an efficiency improvement in this small sample: versus
`append-full`, `set-full` increased Active-h by 9.8%, median active by
2.0%, Agent-h by 8.1%, Wall-h by 3.7%, and tool calls by 17.5%. It was
faster on only two of eleven per-task active-time medians
(`distribution-search` and `prove-plus-comm`). With two attempts per
task, these are directional results rather than confidence intervals;
they support this change as an instruction-isolation/correctness fix,
not a performance optimization, and argue against Goose's appended
native prompt being the main source of active-time cost.

Generated with Codex

Signed-off-by: Atish Patel <atishpatel2012@gmail.com>
Co-authored-by: Codex <noreply@openai.com>
2026-08-17 13:03:14 -04:00
Taylor Ho
edc4a09aaa feat(workflows): add responsive library card actions (#6008)
**Category:** improvement
**User Impact:** Users can scan what each workflow does and trigger,
edit, duplicate, enable, disable, or delete it directly from the
library.
**Problem:** The workflow list buried common actions and did not expose
each automation's trigger-to-action shape at a glance.
**Solution:** Add a responsive workflow library with a persistent create
tile, compact trigger/action diagrams, prominent workflow titles with
supporting descriptions, and shared card actions while preserving
existing detail, editor, and run-history entry points. Card toggles
refresh both list and open-detail caches so status and definition stay
consistent.

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

**desktop/src/features/workflows/ui/WorkflowActionsMenu.tsx**
Adds a shared card menu for trigger, edit, duplicate, enable/disable,
and delete actions.

**desktop/src/features/workflows/ui/WorkflowCard.tsx**
Reworks cards around the prototype's visual hierarchy: color-coded
trigger, action flow, sentence-case eyebrow, prominent title, supporting
description, status, channel, and update date without a footer clock
icon.

**desktop/src/features/workflows/ui/WorkflowsView.tsx**
Adds the responsive grid, create tile, mutation wiring, and list/detail
cache invalidation. Container breakpoints keep cards two-across at
medium widths and three-across in the 1280px desktop layout.

**desktop/src/features/workflows/ui/workflowDefinition.ts**
Adds immutable enabled-state updates plus narrow trigger and
first-action readers used only to select card icons.

**desktop/src/features/workflows/ui/workflowDefinition.test.mjs**
Covers neutral icon selection, enabled-state immutability, and status
presentation.

**desktop/tests/e2e/workflows.spec.ts**
Covers the create tile, title/description hierarchy, selected-card
enable/disable consistency, and deterministic narrow/medium/wide
captures while retaining existing action coverage.

</details>

## Reproduction steps

1. Open **Workflows** and confirm the create tile stays first as cards
flow from one to three columns with available width.
2. Confirm each card shows a sentence-case trigger eyebrow, prominent
workflow title, supporting description when present, status, channel,
and update date without a clock icon.
3. Open a card's overflow menu and trigger, edit, duplicate,
enable/disable, or delete the workflow.
4. Leave the detail panel open while toggling and confirm its badge and
JSON definition update with the card.

## Screenshots

Real built E2E UI with representative workflow data at three viewport
sizes.

### Narrow — 800 × 720

![Workflow library at 800 by
720](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/6008/workflow-library-narrow-482d1b4c8.png)

### Medium — 1024 × 720

![Workflow library at 1024 by
720](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/6008/workflow-library-medium-482d1b4c8.png)

### Wide — 1280 × 720

![Workflow library at 1280 by
720](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/6008/workflow-library-wide-482d1b4c8.png)

### Card actions

![Workflow library actions at 1280 by
720](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/6008/workflow-library-wide-actions-482d1b4c8.png)

---------

Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
2026-08-17 16:56:34 +00:00
Wes
f716eef437 fix(desktop): enforce shared agent access across devices (#6086)
## Summary

- discover shared managed agents from authenticated relay directory
records instead of treating channel membership as sufficient proof
- publish and refresh access-policy changes immediately so running
clients converge across machines without a restart or five-minute poll
- route profile edits through the exact managed instance and
stop/restart runtimes around access changes so unrelated edits cannot
silently widen access
- keep mention send-time revalidation and Block owner-only build
enforcement fail closed
- explain invalid custom provider/model configuration instead of leaving
Save silently disabled

### Related issue

Fixes #3204

### Known residuals

- a brand-new remote agent's first policy record can wait for the
bounded directory poll when no authenticated directory coordinate exists
yet; send-time mention revalidation remains fail closed
- a failed remote-provider policy redeploy is recorded but cannot
undeploy the older provider instance until the provider protocol gains
the destructor tracked by #5570

### Testing

- full Desktop unit suite: 4,961 tests passed
- focused profile editor Playwright workflow passed, including Customize
access edits and prompt-only edits after tightening an instance
- Desktop TypeScript, Biome formatting, file-size ratchet, Tauri checks,
and pre-push suites passed
- independently reviewed for authenticated directory trust, live
subscription teardown, runtime revocation ordering, fail-open edit
paths, and per-agent provider deployment serialization

---------

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Signed-off-by: Brain <21994759fc7a6fa6b965551d35cfd7897d262f2495467f2d78694ddcfa6a5c7e@buzz.block.builderlab.xyz>
Co-authored-by: diegorumo <diegorumo@gmail.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
Co-authored-by: Brain <21994759fc7a6fa6b965551d35cfd7897d262f2495467f2d78694ddcfa6a5c7e@buzz.block.builderlab.xyz>
2026-08-17 09:51:00 -07:00
Will Pfleger
1b7e5ac1be feat(model-capabilities): drive model capabilities and labels from one manifest (#5597)
## Summary

Centralizes model capability knowledge — thinking mode, supported effort
levels, wire routes, and human-readable labels — into a single manifest,
`scripts/model-capabilities.json`. Rust and TypeScript each get a small
interpreter that reads the same manifest, replacing hand-maintained
tables scattered across both languages that had already drifted apart. A
capability change is now a data edit, not parallel edits to two code
paths. Supersedes the codegen approach explored in #3603.

A cross-language contract keeps the two interpreters honest:
`scripts/normative-corpus.json` is a golden snapshot generated from the
Rust resolver (103 vectors covering all six capability axes) and
replayed natively in TS. CI fails if either language disagrees with the
corpus or the corpus drifts from the resolver. Regenerate with `just
regen-model-corpus`.

## Behavior changes

- **Effort dropdown for `openai-compat` providers** no longer offers
`max`. The request path always clamped `max` to `xhigh` on the wire, so
the UI stops offering a value that was silently rewritten. UI-only,
wire-identical.
- **Databricks v2 routing (wire-visible):** uncurated endpoint names
carrying a bare Claude code-name segment (e.g. `goose-opus-5`) now route
to the MLflow chat wire instead of Anthropic Messages — they lose
Anthropic prompt caching but still succeed on a valid OpenAI-compatible
wire. Curated `databricks-claude-*` records and any name starting with
`claude` are unchanged. A handful of other uncurated/adversarial name
shapes similarly fall back to MLflow chat instead of pattern-matched
routes; every curated model resolves identically to before, all axes.
- **Curated model labels on the real discovery path.** The Databricks
API returns no display name, so discovery emits the raw endpoint id as
the model `name` (`{id, name: id}`) on every path. `ModelEntry.name` is
now curated at all four construction seams in `buzz-agent` — v2
discovery, v1 parse, the auth-empty default catalog, and the
configured-model fallback — via a read-only `databricks_registry_label`
lookup over the manifest's `databricks_v2` exact records; `id` stays the
raw wire/config value. A known id renders its curated label
(`databricks-gpt-5-5` → `GPT-5.5`), an unknown id passes through
unchanged, and the default-catalog row reads `GPT-5.5 (default
catalog)`. As a defense against older `buzz-agent` binaries and any
harness that echoes ids, `resolveModelLabel` treats a discovered name
equal to the trimmed id as absent and falls through to the registry
tier; a genuinely distinct name (including the suffixed default-catalog
label) still wins.

## Cleanup

Deletes the duplicated capability tables and their tests: the
`config.rs` gpt5 matchers, effort tables, and clamp logic; the legacy
segment-based Databricks v2 route classifier in `llm.rs`; and the TS
hand tables plus `effortTable.fixture.json`. All are replaced by
manifest lookups through the shared resolver — no line of capability
data exists in two places.

---------

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
2026-08-17 11:23:28 -04:00
Wes
f956e6fe06 docs: refresh agent development guidance (#6049)
## Summary

- allow agents to build and run Flutter when it provides relevant
implementation or validation evidence
- keep mobile iteration fast by reusing simulators, incremental builds,
and configured staging or production communities
- correct stale CLI, E2E, CI, worktree formatting, and mobile launch
guidance
- point community singleton reset guidance at the canonical
implementation instead of duplicating a drifting inventory

## Validation

- `git diff --check origin/main..HEAD`
- `cargo run -q -p buzz-cli -- --format compact messages thread --help`
- `cargo run -q -p buzz-cli -- --format compact messages search --help`
- `just desktop-tauri-fmt-check` from the worktree
- pre-commit: mobile Dart formatting and `flutter analyze`
- pre-push: branch-skew check and full mobile test suite (1,465 tests)

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
2026-08-16 08:25:51 -07:00
Taylor Ho
d8281b9c93 feat(mobile): require device authentication for identity export (#5116)
**Category:** new-feature
**User Impact:** Mobile users must confirm with Face ID, biometrics, or
their device passcode before sending their Buzz identity to Desktop.

**Problem:** A signed-in phone could send its full identity, including
the `nsec`, to a desktop without fresh local verification.

**Solution:** Require OS device authentication before opening the
identity-recovery scanner, retain that authorization only for the active
pairing session and short pairing window, and require fresh
authentication again if it expires before the identity payload is sent.
Normal app opening, identity import, and community removal remain
unchanged.

## Screencasts

| Enable Face ID | Use Face ID |
| --- | --- |
| ![Enabling Face ID during identity
import](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/5116/enable-face-id.gif)
| ![Using Face ID for identity
export](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/5116/use-face-id.gif)
|

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

**Android and iOS integration**
- `mobile/android/app/build.gradle.kts` declares the AppCompat
dependency required by the biometric activity theme.
-
`mobile/android/app/src/main/kotlin/xyz/block/buzz/mobile/MainActivity.kt`
uses the activity type required by the system authentication prompt.
- `mobile/android/app/src/main/res/values/styles.xml` and
`mobile/android/app/src/main/res/values-night/styles.xml` use the
compatible launch theme.
- `mobile/ios/Podfile.lock` records the native local-authentication
dependency.
- `mobile/ios/Runner/Info.plist` explains why Buzz requests Face ID
access.

**Identity policy and pairing flow**
- `mobile/lib/shared/security/sensitive_action_authorizer.dart` wraps OS
authentication and maps platform errors to stable app-level outcomes.
- `mobile/lib/shared/community/community.dart` and
`mobile/lib/shared/community/community_storage.dart` persist the
sensitive-action policy.
- `mobile/lib/features/invites/invite_join_provider.dart` assigns the
explicit policy for invite-created communities.
- `mobile/lib/features/pairing/pairing_provider.dart` gates export,
binds grants to the active community/session, reauthenticates expired
grants, and clears grants on every terminal path.
- `mobile/lib/features/pairing/pairing_page.dart` lets users choose
biometric protection while importing an identity.
- `mobile/lib/features/settings/settings_page.dart` wires pairing into
settings.
- `mobile/lib/features/settings/settings_page/connection_section.dart`
authenticates before opening export recovery and bounds the
foreground-resume wait.
- `mobile/pubspec.yaml` and `mobile/pubspec.lock` add and lock
`local_auth`.

**Coverage**
- `mobile/test/shared/security/sensitive_action_authorizer_test.dart`
covers native result mapping, unsupported devices, and single-flight
behavior.
- `mobile/test/shared/community/community_test.dart` and
`mobile/test/shared/community/community_storage_test.dart` cover policy
defaults and persistence.
- `mobile/test/features/invites/invite_join_provider_test.dart` covers
the invite policy.
- `mobile/test/features/pairing/pairing_page_test.dart` covers import
protection controls.
- `mobile/test/features/pairing/pairing_provider_test.dart` covers
export/import authorization, stale/reset/concurrent guards, malformed
payload cleanup, and no-export failure paths.
- `mobile/test/features/settings/connection_section_test.dart` covers
the tap gate, lifecycle resume, and timeout behavior.

</details>

## Reproduction steps

1. Pair an identity into the mobile app.
2. Open Settings and choose “Send identity to desktop.”
3. Verify Face ID, biometrics, or the device passcode is required before
the recovery scanner opens.
4. Cancel device authentication and verify the scanner does not open and
no identity transfer begins.
5. Authenticate, scan a Desktop recovery code, confirm the SAS, and
verify the identity transfer completes.

## Validation

At `be5620f5f10aa6cc16e86a4f01f102f3d9aeef9b`:
- `cd mobile && ../bin/flutter analyze` — no issues
- `cd mobile && ../bin/flutter test` — 1,368 tests passed
- `cd mobile/android && JAVA_HOME=$(/usr/libexec/java_home -v 21)
./gradlew app:assembleDebug` — debug APK assembled successfully

---------

Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
2026-08-15 18:34:02 -07:00
thomaspblock
78cbffeb64 fix(desktop): hide the offcanvas-collapsed sidebar so it stops painting over the community rail (#5947)
## Summary

Collapsing the sidebar left a phantom copy of it painted over the
community/relay rail — opaquely on flat themes (vesper et al., which
made the rail look *removed*), and as ghost fragments (muted search-box
fill, truncated channel-name tails) on the Buzz themes whose chrome is
intentionally transparent for the gradient.

**Cause:** #4281 made the app-sidebar layer `overflow-visible` (the
huddle drawer needs to escape it). That removed the ancestor clipping
the offcanvas collapse relied on: the sidebar slides to `left:
-sidebar-width` but kept painting, exactly over the `z-0` rail (`z-10`
sidebar layer).

**Fix:** the offcanvas-collapsed sidebar container is now `invisible` +
`pointer-events-none`, with `visibility` added to the transition list so
the 200 ms slide-out still animates and the flip happens only at the
transition's end. Theme-independent; no per-theme CSS touched; the
huddle drawer's `overflow-visible` is preserved.

## Before / after

Left 420px of the app with the sidebar collapsed. Before = unpatched
`origin/main` @ 69107dc3b; after = this branch. Same seeded state, same
build pipeline (`build:e2e` between checkouts).

| theme | before (ghost sidebar over the rail) | after (rail clean: A /
B / + visible) |
|---|---|---|
| vesper |
![before-vesper](https://raw.githubusercontent.com/block/buzz/3f98c576e062e51d976940725d84b4e0be7fd53c/pr-5947--before-vesper.png)
|
![after-vesper](https://raw.githubusercontent.com/block/buzz/3f98c576e062e51d976940725d84b4e0be7fd53c/pr-5947--after-vesper.png)
|
| buzz |
![before-buzz](https://raw.githubusercontent.com/block/buzz/3f98c576e062e51d976940725d84b4e0be7fd53c/pr-5947--before-buzz.png)
|
![after-buzz](https://raw.githubusercontent.com/block/buzz/3f98c576e062e51d976940725d84b4e0be7fd53c/pr-5947--after-buzz.png)
|
| buzz-dark |
![before-buzz-dark](https://raw.githubusercontent.com/block/buzz/3f98c576e062e51d976940725d84b4e0be7fd53c/pr-5947--before-buzz-dark.png)
|
![after-buzz-dark](https://raw.githubusercontent.com/block/buzz/3f98c576e062e51d976940725d84b4e0be7fd53c/pr-5947--after-buzz-dark.png)
|

Before shots: ghost `⌘K` search chip + blue active-item pill painted
over the rail column; on vesper the opaque panel hides the rail buttons
entirely. After: the rail's community buttons (A, B) and `+` are visible
and clickable in all three themes.

Reported by Thomas P in #buzz-bugs:
buzz://message?channel=e62570dd-33ad-42c5-b92b-75f2689f9694&id=9ea401ca1d009f555ca4324e136f8d8d8156db2f8afa3ff89fd038d2c16260f7

cc @klopez4212 — this touches the layout your #4281/#5478 work shaped;
please confirm it doesn't defeat the huddle drawer or glass intentions.
The change deliberately hides only the *offcanvas-collapsed* container,
nothing in the expanded path.

## Test plan

- [x] New Playwright regression spec `sidebar-offcanvas-rail.spec.ts`
(buzz / buzz-dark / vesper): collapsed sidebar must be `visibility:
hidden` + `pointer-events: none`, community rail stays visible and
interactive. **Fails on unpatched build** (verified), passes with the
fix.
- [x] Full desktop unit suite: 4,954 pass / 0 fail
- [x] `pnpm typecheck`, `pnpm check` (biome + file-size ratchet +
px-text + pubkey-truncation) green
- [x] Before/after screenshots above captured via the e2e harness on
both builds

Signed-off-by: Thomas Petersen <thomasp@squareup.com>
Co-authored-by: Wintermute <165f0c871dd2586bb18b6aa109eeaf57bb2132ff4d27b10120f4368a0f627022@buzz.block.builderlab.xyz>
2026-08-15 07:49:41 -07:00
klopez4212
69107dc3bf Polish mobile message threads and composer (#5645)
## Summary

- refine mobile message metadata, search spacing, and Activity filter
semantics
- add channel-parity Latest navigation and stable tail following to
threads
- synchronize Android composer/keyboard geometry and keep Latest spacing
stable across IME transitions

## Validation

- `bin/just mobile-check`
- `bin/just mobile-test` (1,276 tests)
- Pixel 10 install/launch and channel/thread keyboard, Latest, tail, and
back-navigation review
- signed iPhone install/launch workflow

## Snapshots

See the review snapshots below.

---------

Signed-off-by: kenny lopez <klopez4212@gmail.com>
Signed-off-by: Kenny Lopez <klopez4212@gmail.com>
Signed-off-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz>
Co-authored-by: Fast Fizz <2df81cb51f05a9d5387ef24d7b9ecb8fcdfcd1c70ffabc67061c9596e1b5b1c4@buzz.block.builderlab.xyz>
Co-authored-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz>
2026-08-15 09:14:14 +01:00
Wes
82f7ed1532 chore(release): release Buzz Desktop version 0.5.14 (#5917)
## Buzz Desktop release v0.5.14

- **Frozen main:** `1b3dbcaaea882eeea90359c1db02e306d2f4f50a`
- **Reviewed candidate:** `391495e7d347d20b67e39e3c240d17ef63c5c2c0`
- **Previous desktop release:** `desktop-v0.5.13`
- **Proposed immutable tag:** `desktop-v0.5.14`

This PR may be **squash merged** after the Desktop Release Candidate
check and all protected-branch checks pass. Merging authorizes
publication of the exact reviewed candidate; later or unrelated changes
on `main` cannot alter it.

The checked-in changelog accounts for every non-merge commit in the
release range. The Desktop tag points to the reviewed candidate commit,
not the later squash commit. Publication remains bound to that immutable
candidate tag.

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Release Automation <release-automation@users.noreply.github.com>
2026-08-14 17:35:49 -07:00
Wes
1b3dbcaaea ci(release): remove desktop smoke gate (#5914)
## Summary

- remove the GitHub-hosted desktop smoke job from the desktop release
workflow
- remove the smoke result from manifest assembly dependencies and
promotion conditions
- retain the local smoke tooling for future repair and targeted
validation

The first release execution of this gate spent its full 10-minute
Playwright timeout traversing the 10,000-row fixture, then produced a
987 MB diagnostics upload. All signed platform builds succeeded, but the
smoke prevented manifest publication. This restores the previously
established release boundary while the harness is made suitable for CI
separately.

### Testing

- parsed `.github/workflows/release.yml` with Ruby Psych and asserted
the smoke job/dependencies are absent
- `scripts/test-release-ref-contract.sh`
- exact pushed commit passed the repository pre-push hook

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
2026-08-14 17:04:37 -07:00
Wes
51beba6038 chore(release): release Buzz Desktop version 0.5.13 (#5912)
## Buzz Desktop release v0.5.13

- **Frozen main:** `09768100ec3420f0aa7cd278bd00fe0baab5de8d`
- **Reviewed candidate:** `a239e0f6793ac6e88ccf92cc231054090a9753cc`
- **Previous desktop release:** `desktop-v0.5.12`
- **Proposed immutable tag:** `desktop-v0.5.13`

This PR may be **squash merged** after the Desktop Release Candidate
check and all protected-branch checks pass. Merging authorizes
publication of the exact reviewed candidate; later or unrelated changes
on `main` cannot alter it.

The checked-in changelog accounts for every non-merge commit in the
release range. The Desktop tag points to the reviewed candidate commit,
not the later squash commit. Publication remains bound to that immutable
candidate tag.

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Release Automation <release-automation@users.noreply.github.com>
2026-08-14 16:10:09 -06:00
Wes
09768100ec fix(ci): read Playwright version without nested shell quoting (#5910)
## Summary

- replace the nested one-line shell quoting used to read the Playwright
package version
- write the resolved version to `GITHUB_OUTPUT` from a multiline shell
step

## Why

The `desktop-v0.5.12` release smoke job failed before executing tests
because Bash received escaped quotes inside command substitution and
parsed the Node expression as shell syntax.

## Validation

- `bash scripts/test-release-ref-contract.sh`
- isolated execution of the new shell fragment with a fixture
`@playwright/test/package.json`, producing `version=1.58.2`
- `git diff --check`

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
2026-08-14 14:28:06 -07:00
Tyler
263c9bf76c fix(desktop): restore the agent trading-card mint button (#5900)
## Problem

PR #5574's profile-panel redesign dropped `ProfileSummaryView`'s
`onCreateCard` prop — the only caller of `setCardMintTarget` — so the
entire Agent Trading Cards feature (#3278) became unreachable from the
GUI while staying fully wired underneath: mint dialog, background job
store, viewer, gallery, composer chip, and the Rust
`mint_agent_card`/`save_agent_card` commands all survive at main. `git
log -S 'setCardMintTarget('` shows exactly two commits: the feature and
the accidental removal.

## Outcome

The mint trigger returns as a management row in the agent profile's Info
tab, directly under **Export agent**, gated `isBot && canManagePersona`
exactly like Duplicate/Export. Target resolution is byte-for-byte the
original logic: prefer the live instance pubkey, fall back to the
persona/definition id, allow locking only when an instance keypair
exists.

## Shape

- `UserProfileAgentManagementRows`: new optional `onCreateCard` row
(Sparkles icon, `user-profile-create-card-row`), placed after Export.
- Prop threaded `UserProfilePanel` → `ProfileSummaryView` →
`ProfileInfoTabContent` → management rows, mirroring `onExportAgent` at
every layer.
- The mint-target state + open callback move into a `useCardMint` hook
in `UserProfilePersonaDialogs` (beside the `CardMintTarget` type it
manages). This keeps `UserProfilePanel.tsx` at 999 lines — the file sits
at the size-ratchet cap and may not grow.

## Validation

- `pnpm check` green (biome, file-size ratchet, px-text,
pubkey-truncation).
- `pnpm typecheck` green.
- Full desktop unit suite: **4888 passed, 0 failed**.
- Profile e2e spec: **32 passed**, including the updated
management-row-order assertion and a new click → mint-dialog-visible →
Escape → closed exercise of the restored row.

Verified at `bff3110a0aeb3d63683eac9ed3e587829f9436da`, one commit atop
main `01f76ec97`.

Signed-off-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
2026-08-14 17:22:42 -04:00
thomaspblock
122a8b8988 Projects v3: unify sharing, discussions, and issue ownership (#5792)
## Summary

Projects v3 makes repository work shareable, discussion-aware, and
easier to scan in one coherent workspace. People can copy canonical
links, reopen the exact workspace tab, understand issue and pull-request
context at a glance, find related channel conversations, and assign or
unassign issues across Desktop and CLI.

- **Unified workspace** — top-level sections sit above repository
controls in one rounded workspace, with navigation positioned close to
the page heading. README and Files retain branch selection; every
section has a labeled icon header, and Issues and Pull Requests expose
creation from a consistent right-aligned action.
- **Repository management** — the repository selector is always
available, including single-repository projects. Its integrated add flow
lets project owners create a repository manually or select an existing
repository without a separate toolbar button.
- **Readable work-item lists** — issue and pull-request rows use
plain-language context instead of opaque metadata. Files, commits,
issues, pull requests, channels, and contributors share consistent row
density and right-aligned timestamps, while deterministic
fallback-avatar colors keep participants distinct on light backgrounds.
Inbox pull-request metadata wraps between complete phrases and truncates
long channel names instead of compressing copy into narrow columns.
- **Reliable entity links** — projects, repositories, issues, pull
requests, and commits have canonical `buzz://` links, preview cards, OS
deep-link routing, and tab-aware navigation. Reopening the same link
re-applies its destination instead of leaving the user on a locally
selected tab.
- **Related conversations** — repository and work-item views surface
channels discussing the current entity, including participants, channel
navigation, message context, and an explicit notice when discovery
reaches its 500-result cap.
- **Reversible issue ownership** — trusted assignment and unassignment
events work across Desktop, Tauri, `buzz-sdk`, and `buzz issues`.
Assignees appear in project views and the assigned inbox, while
authorized users can remove assignments directly from the assignee row.

Assignment state is derived chronologically from labeled Nostr notes.
Issue authors and repository owners may change any assignee; other users
may only assign or unassign themselves. Shared golden fixtures keep
entity-link grammar and validation aligned across TypeScript and Rust.

The branch also updates `webbrowser` to the patched release for
RUSTSEC-2026-0257.

### Related issue

N/A.

### Testing

- [x] `just ci` — formatting, lint, typechecking, unit tests, and builds
passed
- [x] Full pre-push suite — organization, branch-skew, Desktop checks,
typechecking, and tests passed on the latest push
- [x] `cargo test -p buzz-cli` and focused `buzz-sdk` assignment tests
passed
- [x] Focused Tauri recipient-note and 500-result search-limit tests
passed
- [x] Desktop entity-link and issue-assignment unit tests passed
- [x] Playwright smoke coverage passed for assignment, repeated
entity-link navigation, repository create/select flows, section headers
and actions, timestamp alignment, timeline icons, sentence-style
issue/PR metadata, header spacing, avatar contrast, and Inbox metadata
at stacked and side-rail breakpoints
- [ ] Manual staging pass: link round-trips, Channels tab, assignment
flows, and inbox routing

### Screenshots

Pull requests explain who opened the request, where it lives, and which
branch it comes from; fallback avatars remain visually distinct.

![Pull request list with conversational
metadata](https://raw.githubusercontent.com/block/buzz/2a536de86f7e6f79b349d7bc147b2923ff2b817d/pr-5624--05-pr-list-metadata.png)

Issues use the same sentence-style hierarchy while keeping status and
recency easy to scan.

![Issue list with conversational
metadata](https://raw.githubusercontent.com/block/buzz/2a536de86f7e6f79b349d7bc147b2923ff2b817d/pr-5624--06-issue-list-metadata.png)

The wide Inbox detail keeps author, timestamp, and origin context
readable beside its metadata rail.

![Pull request Inbox detail with readable
metadata](https://raw.githubusercontent.com/block/buzz/e65b433e14b97c45365ed7b68ea402ec01d26615/pr-5624--02-pull-request-detail-wide.png)

[View the complete six-state Projects v3 screenshot
set](https://github.com/block/buzz/pull/5624#issuecomment-5268039672)
and [the compact/wide Inbox
comparison](https://github.com/block/buzz/pull/5624#issuecomment-5268614585).


---

> Supersedes #5624, whose head commit accumulated permanently-queued
required check suites (block-dco-check et al.) that GitHub never
dispatched. History flattened into a single signed-off commit on latest
main; tree verified byte-identical (`git merge-tree`) to merging the
original branch into main.

---------

Signed-off-by: Thomas Petersen <thomasp@squareup.com>
Co-authored-by: Wintermute <3f1797424fd9ad6653a83665c660517777cd7f8c228c0d5907f49e01537f3ca5@buzz.block.builderlab.xyz>
2026-08-14 16:48:41 -04:00
Wes
1f4c69eccf chore(release): release Buzz Desktop version 0.5.12 (#5903)
## Buzz Desktop release v0.5.12

- **Frozen main:** `757779bb1ef22cc4a1c233344baa0946d907e5a6`
- **Reviewed candidate:** `bfc34904adc414efcd8e9c5548dff82c3545b677`
- **Previous desktop release:** `desktop-v0.5.11`
- **Proposed immutable tag:** `desktop-v0.5.12`

This PR may be **squash merged** after the Desktop Release Candidate
check and all protected-branch checks pass. Merging authorizes
publication of the exact reviewed candidate; later or unrelated changes
on `main` cannot alter it.

The checked-in changelog accounts for every non-merge commit in the
release range. The Desktop tag points to the reviewed candidate commit,
not the later squash commit. Publication remains bound to that immutable
candidate tag.

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Release Automation <release-automation@users.noreply.github.com>
2026-08-14 13:20:24 -07:00