Skip to content

feat(desktop): connect chat to relay - #6

Merged
wesbillman merged 1 commit into
mainfrom
wesb/desktop-relay-chat
Mar 9, 2026
Merged

wesbillman merged 1 commit into
mainfrom
wesb/desktop-relay-chat

Conversation

@wesbillman

Copy link
Copy Markdown
Collaborator

Summary

  • add Tauri commands and client hooks to load channels, authenticate to the relay, fetch history, subscribe live, and send messages
  • connect the desktop shell, sidebar, header, composer, and timeline to live channel data with optimistic sending and loading and empty states
  • add the desktop React Query and websocket dependencies needed for the relay-backed flow

Testing

  • pre-commit: desktop-check, rust-fmt
  • pre-push: desktop-check, desktop-build, desktop-tauri-check, rust-clippy, rust-tests

@wesbillman

Copy link
Copy Markdown
Collaborator Author
Screenshot 2026-03-09 at 2 46 31 PM Screenshot 2026-03-09 at 2 46 35 PM

@wesbillman
wesbillman merged commit 0705327 into main Mar 9, 2026
7 checks passed
@wesbillman
wesbillman deleted the wesb/desktop-relay-chat branch March 9, 2026 21:49
tlongwell-block added a commit that referenced this pull request Mar 10, 2026
* origin/main:
  Add desktop Home feed (#12)
  Add desktop Playwright e2e harness (#11)
  Update desktop icon and persist window state (#9)
  feat: add channel creation flow (#8)
  Improve message markdown display and formatting (#7)
  feat(desktop): connect chat to relay (#6)
  docs(readme): clarify desktop setup (#4)
  feat: add desktop app (#3)

# Conflicts:
#	crates/sprout-test-client/tests/e2e_rest_api.rs
tlongwell-block added a commit that referenced this pull request May 14, 2026
Adds a Settings → Agent Provider panel to the desktop GUI for configuring
the `sprout-agent` provider, model, API key, and behavior knobs. Settings
are encrypted at rest with NIP-44 self-encryption (the user's own nostr
key) and injected into the sprout-agent child's env at spawn time. The
panel also gains automatic provider-URL detection based on the API key
format the user pastes.

## Backend (`desktop/src-tauri/src/commands/agent_provider_settings/`)

- `mod.rs` — IPC types + plaintext `StoredSettings` (Drop zeroizes
  `api_key`; no Debug derive on input/stored). v2 envelope binds the
  plaintext to its owner pubkey for rollback / envelope-swap protection.
- `storage.rs` — envelope read/write, NIP-44 encrypt/decrypt with
  `Zeroizing<String>` for plaintext, atomic-rename writes, file-size
  cap, `normalize_origin` (rejects non-loopback `http://`, userinfo,
  query, fragment), `validate_input` (provider whitelist, control-char
  rejection, size caps for key/model/base_url/system_prompt, positive-
  int knobs). `validate_stored` mirrors the same rules on the decrypted
  blob at spawn time — fails closed on a rolled-back pre-validation
  envelope so a redirected `http://api.example.com/v1` cannot escape.
- `commands.rs` — `get_*`, `save_*`, `delete_*`, `get_*_env_presence`
  Tauri commands. The save command trims whitespace + zeroes the input
  api_key before validation can early-return.
- `spawn.rs` — `LoadForSpawn` enum + `EnvPairs` newtype whose Drop
  zeroizes every value buffer. `apply_to_command` hands each env pair
  to `Command::env` by reference, zeroizing the local buffer after.
  Spawn policy: Ok → strip OWNED_AGENT_ENV_VARS + ACP-level vars then
  inject; None → no-op; IdentityMismatch / Error → fail closed (strip
  inherited owned vars, inject nothing).
- `tests.rs` — round-trip envelope I/O, identity-rotation, save-time
  validation (oversized prompt, zero timeouts, tiny history bytes,
  unknown provider, control chars, oversized fields, api-key whitespace
  trim, owner_pubkey v2), `apply_to_command` × `LoadForSpawn` matrix
  (Ok/None/IdentityMismatch-fails-closed/Error-fails-closed, openai
  dialect), `stored_to_env_pairs` for each dialect, R7 `validate_stored`
  coverage (non-loopback http, control chars in key/model/base_url,
  userinfo/query in base_url, unknown provider, oversized prompt,
  empty-key + loopback local accepted).

## Runtime integration (`desktop/src-tauri/src/managed_agents/runtime.rs`)

- `build_agent_command` calls `agent_provider_settings::apply_to_command`
  exactly when the harness is `sprout-agent`. ACP-level vars
  (SPROUT_AGENT_PROVIDER etc.) are stripped from inherited parent env
  before injection so a stale shell `ANTHROPIC_API_KEY` never shadows
  saved settings. `respond-to` gate env (`SPROUT_ACP_RESPOND_TO[_ALLOWLIST]`)
  threads through with the new `owner_hex: Option<&str>` parameter
  (origin/main merge).

## Frontend

- `lib/detectProvider.ts` — pure key-format detector. Recognizes
  Anthropic, OpenAI (legacy/proj/svcacct via fixed infix), OpenRouter,
  Groq, xAI, Cerebras, Together, Perplexity, Fireworks (medium), bare
  sk- → DeepSeek (low + ambiguity-aware), plus localhost/127.0.0.1
  patterns for Ollama / vLLM / llama.cpp. Includes ADMIN_ONLY_PROVIDER_ID
  sentinel for `sk-ant-admin01-` which we explicitly refuse to save.
  Key format wins over a prefilled default base URL; an explicit non-
  default base URL wins back for medium-confidence keys (e.g. Fireworks
  + api.openai.com host). Fixture strings construct the OpenAI infix
  via concat so GitHub's secret scanner doesn't regex-match an inline
  OpenAI-shaped service-account/project key (`detectProvider.test.mjs`,
  `settings-agent-provider.spec.ts`).
- `lib/providerCatalog.ts` — declarative catalog (id, label, dialect,
  isLocal, default model + base URL, key-shape hint). Drives the
  picker, the auto-fill on detection, the local-provider placeholder
  enforcement, and the per-provider model field default.
- `lib/agentProviderFormState.ts` — FormState shape + reducers.
  `applyProviderSwitch` is the single source of truth for what gets
  reset on a provider change (model when empty or still previous
  default; baseUrl when new provider has a default OR user hasn't
  edited it; clears previous default host for null-default providers;
  drops apiKey on switch TO a local provider). Used by both the
  manual picker and the auto-detect effect so the policy can't drift.
- `lib/agentProviderSettingsApi.ts` + `hooks/useAgentProviderSettings.ts`
  — typed IPC wrappers + React-Query hooks (load / save / delete /
  envPresence).
- `ui/AgentProviderSettingsCard.tsx` — the panel itself. Empty state
  with shell-env hint, identity-rotation banner, load-error banner,
  detected-provider badge, reveal/hide toggle, advanced section,
  inline provider-change warning, confirm-clear dialog. On save success
  the plaintext is wiped from form state + reveal toggles off,
  independent of any React-Query refresh effect (covers the structural-
  sharing identical-redacted-view edge case).
- `ui/AgentProviderAdvancedFields.tsx`, `AgentProviderBanners.tsx`,
  `AgentProviderClearDialog.tsx` — split components.

## Per-agent dialog (sprout-agent special case)

- `agents/ui/CreateAgentDialogSections.tsx` — Model + System prompt
  inputs are hidden for sprout-agent paths (those are owned globally).
  A note line points users to Settings → Agent Provider.
- `agents/ui/EditAgentDialog.tsx` — passes `selectedProviderId="custom"`
  to the shared runtime fields so the agent-command input stays
  editable for existing rows; the system-prompt hide still resolves
  via `isSproutAgentPath`'s `agentCommand` arm.
- `agents/ui/ManagedAgentRow.tsx` — "Model managed by Sprout settings"
  link is a span with role="button" + stopPropagation (was a nested
  `<button>` inside the row button — both invalid HTML and double-
  triggering).
- `agents/lib/resolveAcpProviderId.ts` — TS / Rust alignment for
  inline-args resolution (Rust `known_acp_provider` strips args; TS
  now matches).

## Tests

- Rust: 328 tests passing (R7 added 7; full agent_provider_settings
  suite at 44/44).
- TS / node-test: 55 cases for the form-state reducer + provider
  detector.
- Playwright integration: 12 settings-agent-provider scenarios (empty
  state + detection + save round-trip, identity-rotation banner,
  provider-change-warning, key-format-beats-prefilled-baseUrl, load-
  error banner, clear flow + Escape cancel, rotation banner a11y,
  local-provider switch with saved key, manual switch reset, detected-
  provider model reset, post-save key-input clear).

## just ci summary

- Rust: 321/321 + 7 new validate_stored tests
- Mobile: 336/336
- Desktop / web: format + biome + file-size + ts-check all green
- Playwright integration agents + settings-agent-provider: 19/19 green

## Codex review history

- Reviews #1, #2, #3, #5, #6 surfaced and fixed: non-loopback HTTP at
  save AND spawn, identity-mismatch fails closed, local-provider key
  leak prevention, inline-args resolver alignment, Zeroize on api_key
  before early-return, no Debug derive, control-char / length caps +
  trim, owner_pubkey v2 envelope, local-provider switch unblock,
  EditAgentDialog command field, model reset on detection switch,
  nested-button fix, `applyProviderSwitch` reducer extraction.
- Review #7 (P2-UI + P2-Rust): clear form.apiKey + revealKey on save
  success; validate decrypted settings at spawn time.
- Review #8: 9/10, no blocking findings.

Signed-off-by: Tyler Longwell <109685178+tlongwell-block@users.noreply.github.com>
Co-authored-by: Dawn <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@sprout-oss.stage.blox.sqprod.co>
tlongwell-block pushed a commit that referenced this pull request May 22, 2026
Adds crates/sprout-relay/src/api/git/cas_publish.rs — the pure async
function that turns a post-receive-pack workspace into a durable manifest
CAS. Composes Dawn's GitStore primitives (put_pack, put_manifest,
get_pointer, put_pointer) and Dawn's Manifest schema (canonical_bytes,
validate, Inv_Closed at compose time) into the spec's step 2-7 sequence:

  read pointer (e, d_before)            §step 3
  fetch + verify m_before                §step 3 + A1 detectability
  snapshot refs + symref-HEAD from disk (HEAD inherits parent on detach)
  pack new objects via pack-objects --revs    §step 1-2
  put_pack(bytes) -> packs/<sha256>          §step 2 (A1)
  compose m_after (parent packs + new pack, parent = digest only) §step 5
  m_after.validate()                          (Sami/Max/Perci #2-#4)
  put_manifest(canonical_bytes) -> manifests/<sha256>  §step 6
  put_pointer(IfMatch(e) | IfNoneMatchStar)  §step 7 (CAS)
    Won  -> CasSuccess { manifest, manifest_key }
    Lost -> CasError::Conflict { winner_manifest, winner_manifest_key }
            (→ HTTP 409, with winner for disk reconcile)

The function returns *before* a Response is constructed — it is called
from finalize_push, which is the unique site that builds a push 2xx, so
the structural seam still enforces Theorem 1 (success-after-CAS).

## Review fixes folded in

Sami's review (#1#6) + Perci's #1 + Max's pre-CAS-validation blocker
are all addressed in this commit:

- **parent = bare 64-hex digest, not full key** (Perci #1, Max). Pointer
  body is `<digest>`; `Manifest.parent` stores the same digest, matching
  `Inv_RefDerivedFromParent` literally. `read_parent` strips the
  `manifests/` prefix before assigning. Dawn's new `MalformedParent`
  validator catches any drift at the write seam.

- **Pre-CAS validation** (Sami #2, Max). `m_after.validate()?` runs
  between `compose_after` and `put_manifest`. Unsafe refnames, malformed
  oids, empty HEAD — all surface as `CasError::ManifestInvalid(...)`
  (4xx-class) before any S3 write, *not* as "valid CAS, un-clone-able
  output." Typed variant (not reused `ManifestReadFailed`) so logs /
  status mapping distinguish "client input rejected" from "stored parent
  failed A1" (Max + Dawn).

- **Detached-HEAD fallback** (Sami #3). `snapshot_workspace_state`
  returns empty `head` on detached HEAD; `cas_publish` falls back to
  `parent.head` if non-empty. `validate()` rejects the first-push-+-
  detached case (Sami #4 — no parent to inherit from, manifest is
  un-clone-able).

- **Conflict carries the winner** (Sami #5 + Dawn). `Conflict {
  winner_manifest, winner_manifest_key }` lets `finalize_push` invoke
  Eva's `reconcile_to_manifest` mechanically from the error arm, without
  a second pointer GET in the caller. `warn!()` at the `LostRace` site
  logs (pointer, expected etag, attempted manifest) for debugging
  concurrent-push patterns. Boxed for `clippy::result_large_err`.

- **Empty-pack comment** (Sami #6). Clarified `capture_pack` returns
  `None` in both the delete-all (`refs_after.is_empty()`) and refs-only
  (`pack-objects` empty stdout) cases.

- **`pointer_key` consolidated** in `manifest.rs` (Sami #1, Dawn,
  Max — Sami's "single source of truth" argument). `cas_publish`
  imports it; the duplicate definition is gone.

- **`validate-invocation` test added** in `cas_publish.rs` (Sami's
  recommendation). Pins that a future refactor dropping the `validate?`
  call between `compose_after` and `put_manifest` is caught by unit
  test, not by every subsequent un-clone-able read.

## What this deliberately does NOT do (each with citation)

- No retry on LostRace. Per Sami's TLA-action guidance: the receive-pack
  output is derived against a now-superseded parent; reusing it would
  violate Inv_RefDerivedFromParent. Client re-pushes, which re-hydrates
  + re-runs receive-pack against the advanced pointer — the only safe
  retry, which git already performs. Spec §Push step 7: 'GOTO 3 (retry)
  or respond non-ff' — both arms safe; we take non-ff.
- No kind:30618 emission. That is derived after CAS — finalize_push
  calls Sami's build_ref_state_event over m_after.refs / m_after.head
  on Ok. Spec §Implementation Correspondence: 'kind:30618 is derived
  after CAS, never the commit.'
- No advisory lock. Spec §Push 'no advisory lock in v1' — writer
  serialization is the CAS. A mutex would hide the contention Inv_NoFork
  proves safe.

## Tests

10 unit tests pin digest_from_key (manifest/<...> prefix invariant),
compose_after (Inv_Closed coverage, sort, dedupe, refs-only-no-new-pack,
first-push, parent-is-digest-not-key), validate invocation (unsafe
refname + first-push-empty-HEAD both rejected pre-CAS). 244 relay tests
green; clippy --tests -D warnings clean.

The integration into finalize_push lands separately — Eva owns the
AppState::git_store wiring + main.rs startup probe gate. This module
is callable today: cas_publish(&store, repo_path, owner, repo,
&refs_before) -> Result<CasSuccess, CasError>.

Refs:
- docs/git-on-object-storage.md §Push step 2-7, §Implementation
  Correspondence, §Mechanized Verification (Inv_NoFork,
  Inv_RefEffectApplied, Inv_RefDerivedFromParent, Inv_Closed).

Also makes transport::harden_git_env pub(crate) for reuse by
cas_publish's two subprocess sites (for-each-ref, pack-objects).

Co-authored-by: Tyler Longwell <tyler@block.xyz>
tlongwell-block pushed a commit that referenced this pull request May 22, 2026
Adds crates/sprout-relay/src/api/git/cas_publish.rs — the pure async
function that turns a post-receive-pack workspace into a durable manifest
CAS. Composes Dawn's GitStore primitives (put_pack, put_manifest,
get_pointer, put_pointer) and Dawn's Manifest schema (canonical_bytes,
validate, Inv_Closed at compose time) into the spec's step 2-7 sequence:

  read pointer (e, d_before)            §step 3
  fetch + verify m_before                §step 3 + A1 detectability
  snapshot refs + symref-HEAD from disk (HEAD inherits parent on detach)
  pack new objects via pack-objects --revs    §step 1-2
  put_pack(bytes) -> packs/<sha256>          §step 2 (A1)
  compose m_after (parent packs + new pack, parent = digest only) §step 5
  m_after.validate()                          (Sami/Max/Perci #2-#4)
  put_manifest(canonical_bytes) -> manifests/<sha256>  §step 6
  put_pointer(IfMatch(e) | IfNoneMatchStar)  §step 7 (CAS)
    Won  -> CasSuccess { manifest, manifest_key }
    Lost -> CasError::Conflict { winner_manifest, winner_manifest_key }
            (→ HTTP 409, with winner for disk reconcile)

The function returns *before* a Response is constructed — it is called
from finalize_push, which is the unique site that builds a push 2xx, so
the structural seam still enforces Theorem 1 (success-after-CAS).

## Review fixes folded in

Sami's review (#1#6) + Perci's #1 + Max's pre-CAS-validation blocker
are all addressed in this commit:

- **parent = bare 64-hex digest, not full key** (Perci #1, Max). Pointer
  body is `<digest>`; `Manifest.parent` stores the same digest, matching
  `Inv_RefDerivedFromParent` literally. `read_parent` strips the
  `manifests/` prefix before assigning. Dawn's new `MalformedParent`
  validator catches any drift at the write seam.

- **Pre-CAS validation** (Sami #2, Max). `m_after.validate()?` runs
  between `compose_after` and `put_manifest`. Unsafe refnames, malformed
  oids, empty HEAD — all surface as `CasError::ManifestInvalid(...)`
  (4xx-class) before any S3 write, *not* as "valid CAS, un-clone-able
  output." Typed variant (not reused `ManifestReadFailed`) so logs /
  status mapping distinguish "client input rejected" from "stored parent
  failed A1" (Max + Dawn).

- **Detached-HEAD fallback** (Sami #3). `snapshot_workspace_state`
  returns empty `head` on detached HEAD; `cas_publish` falls back to
  `parent.head` if non-empty. `validate()` rejects the first-push-+-
  detached case (Sami #4 — no parent to inherit from, manifest is
  un-clone-able).

- **Conflict carries the winner** (Sami #5 + Dawn). `Conflict {
  winner_manifest, winner_manifest_key }` lets `finalize_push` invoke
  Eva's `reconcile_to_manifest` mechanically from the error arm, without
  a second pointer GET in the caller. `warn!()` at the `LostRace` site
  logs (pointer, expected etag, attempted manifest) for debugging
  concurrent-push patterns. Boxed for `clippy::result_large_err`.

- **Empty-pack comment** (Sami #6). Clarified `capture_pack` returns
  `None` in both the delete-all (`refs_after.is_empty()`) and refs-only
  (`pack-objects` empty stdout) cases.

- **`pointer_key` consolidated** in `manifest.rs` (Sami #1, Dawn,
  Max — Sami's "single source of truth" argument). `cas_publish`
  imports it; the duplicate definition is gone.

- **`validate-invocation` test added** in `cas_publish.rs` (Sami's
  recommendation). Pins that a future refactor dropping the `validate?`
  call between `compose_after` and `put_manifest` is caught by unit
  test, not by every subsequent un-clone-able read.

## What this deliberately does NOT do (each with citation)

- No retry on LostRace. Per Sami's TLA-action guidance: the receive-pack
  output is derived against a now-superseded parent; reusing it would
  violate Inv_RefDerivedFromParent. Client re-pushes, which re-hydrates
  + re-runs receive-pack against the advanced pointer — the only safe
  retry, which git already performs. Spec §Push step 7: 'GOTO 3 (retry)
  or respond non-ff' — both arms safe; we take non-ff.
- No kind:30618 emission. That is derived after CAS — finalize_push
  calls Sami's build_ref_state_event over m_after.refs / m_after.head
  on Ok. Spec §Implementation Correspondence: 'kind:30618 is derived
  after CAS, never the commit.'
- No advisory lock. Spec §Push 'no advisory lock in v1' — writer
  serialization is the CAS. A mutex would hide the contention Inv_NoFork
  proves safe.

## Tests

10 unit tests pin digest_from_key (manifest/<...> prefix invariant),
compose_after (Inv_Closed coverage, sort, dedupe, refs-only-no-new-pack,
first-push, parent-is-digest-not-key), validate invocation (unsafe
refname + first-push-empty-HEAD both rejected pre-CAS). 244 relay tests
green; clippy --tests -D warnings clean.

The integration into finalize_push lands separately — Eva owns the
AppState::git_store wiring + main.rs startup probe gate. This module
is callable today: cas_publish(&store, repo_path, owner, repo,
&refs_before) -> Result<CasSuccess, CasError>.

Refs:
- docs/git-on-object-storage.md §Push step 2-7, §Implementation
  Correspondence, §Mechanized Verification (Inv_NoFork,
  Inv_RefEffectApplied, Inv_RefDerivedFromParent, Inv_Closed).

Also makes transport::harden_git_env pub(crate) for reuse by
cas_publish's two subprocess sites (for-each-ref, pack-objects).

Co-authored-by: Tyler Longwell <tyler@block.xyz>
tlongwell-block added a commit that referenced this pull request May 22, 2026
Adds crates/sprout-relay/src/api/git/cas_publish.rs — the pure async
function that turns a post-receive-pack workspace into a durable manifest
CAS. Composes Dawn's GitStore primitives (put_pack, put_manifest,
get_pointer, put_pointer) and Dawn's Manifest schema (canonical_bytes,
validate, Inv_Closed at compose time) into the spec's step 2-7 sequence:

  read pointer (e, d_before)            §step 3
  fetch + verify m_before                §step 3 + A1 detectability
  snapshot refs + symref-HEAD from disk (HEAD inherits parent on detach)
  pack new objects via pack-objects --revs    §step 1-2
  put_pack(bytes) -> packs/<sha256>          §step 2 (A1)
  compose m_after (parent packs + new pack, parent = digest only) §step 5
  m_after.validate()                          (Sami/Max/Perci #2-#4)
  put_manifest(canonical_bytes) -> manifests/<sha256>  §step 6
  put_pointer(IfMatch(e) | IfNoneMatchStar)  §step 7 (CAS)
    Won  -> CasSuccess { manifest, manifest_key }
    Lost -> CasError::Conflict { winner_manifest, winner_manifest_key }
            (→ HTTP 409, with winner for disk reconcile)

The function returns *before* a Response is constructed — it is called
from finalize_push, which is the unique site that builds a push 2xx, so
the structural seam still enforces Theorem 1 (success-after-CAS).

## Review fixes folded in

Sami's review (#1#6) + Perci's #1 + Max's pre-CAS-validation blocker
are all addressed in this commit:

- **parent = bare 64-hex digest, not full key** (Perci #1, Max). Pointer
  body is `<digest>`; `Manifest.parent` stores the same digest, matching
  `Inv_RefDerivedFromParent` literally. `read_parent` strips the
  `manifests/` prefix before assigning. Dawn's new `MalformedParent`
  validator catches any drift at the write seam.

- **Pre-CAS validation** (Sami #2, Max). `m_after.validate()?` runs
  between `compose_after` and `put_manifest`. Unsafe refnames, malformed
  oids, empty HEAD — all surface as `CasError::ManifestInvalid(...)`
  (4xx-class) before any S3 write, *not* as "valid CAS, un-clone-able
  output." Typed variant (not reused `ManifestReadFailed`) so logs /
  status mapping distinguish "client input rejected" from "stored parent
  failed A1" (Max + Dawn).

- **Detached-HEAD fallback** (Sami #3). `snapshot_workspace_state`
  returns empty `head` on detached HEAD; `cas_publish` falls back to
  `parent.head` if non-empty. `validate()` rejects the first-push-+-
  detached case (Sami #4 — no parent to inherit from, manifest is
  un-clone-able).

- **Conflict carries the winner** (Sami #5 + Dawn). `Conflict {
  winner_manifest, winner_manifest_key }` lets `finalize_push` invoke
  Eva's `reconcile_to_manifest` mechanically from the error arm, without
  a second pointer GET in the caller. `warn!()` at the `LostRace` site
  logs (pointer, expected etag, attempted manifest) for debugging
  concurrent-push patterns. Boxed for `clippy::result_large_err`.

- **Empty-pack comment** (Sami #6). Clarified `capture_pack` returns
  `None` in both the delete-all (`refs_after.is_empty()`) and refs-only
  (`pack-objects` empty stdout) cases.

- **`pointer_key` consolidated** in `manifest.rs` (Sami #1, Dawn,
  Max — Sami's "single source of truth" argument). `cas_publish`
  imports it; the duplicate definition is gone.

- **`validate-invocation` test added** in `cas_publish.rs` (Sami's
  recommendation). Pins that a future refactor dropping the `validate?`
  call between `compose_after` and `put_manifest` is caught by unit
  test, not by every subsequent un-clone-able read.

## What this deliberately does NOT do (each with citation)

- No retry on LostRace. Per Sami's TLA-action guidance: the receive-pack
  output is derived against a now-superseded parent; reusing it would
  violate Inv_RefDerivedFromParent. Client re-pushes, which re-hydrates
  + re-runs receive-pack against the advanced pointer — the only safe
  retry, which git already performs. Spec §Push step 7: 'GOTO 3 (retry)
  or respond non-ff' — both arms safe; we take non-ff.
- No kind:30618 emission. That is derived after CAS — finalize_push
  calls Sami's build_ref_state_event over m_after.refs / m_after.head
  on Ok. Spec §Implementation Correspondence: 'kind:30618 is derived
  after CAS, never the commit.'
- No advisory lock. Spec §Push 'no advisory lock in v1' — writer
  serialization is the CAS. A mutex would hide the contention Inv_NoFork
  proves safe.

## Tests

10 unit tests pin digest_from_key (manifest/<...> prefix invariant),
compose_after (Inv_Closed coverage, sort, dedupe, refs-only-no-new-pack,
first-push, parent-is-digest-not-key), validate invocation (unsafe
refname + first-push-empty-HEAD both rejected pre-CAS). 244 relay tests
green; clippy --tests -D warnings clean.

The integration into finalize_push lands separately — Eva owns the
AppState::git_store wiring + main.rs startup probe gate. This module
is callable today: cas_publish(&store, repo_path, owner, repo,
&refs_before) -> Result<CasSuccess, CasError>.

Refs:
- docs/git-on-object-storage.md §Push step 2-7, §Implementation
  Correspondence, §Mechanized Verification (Inv_NoFork,
  Inv_RefEffectApplied, Inv_RefDerivedFromParent, Inv_Closed).

Also makes transport::harden_git_env pub(crate) for reuse by
cas_publish's two subprocess sites (for-each-ref, pack-objects).

Co-authored-by: Tyler Longwell <tyler@block.xyz>

Co-authored-by: Quinn <quinn@users.noreply.sprout>
Signed-off-by: tlongwell-block <109685178+tlongwell-block@users.noreply.github.com>
DevYonghunT added a commit to DevYonghunT/buzz that referenced this pull request Aug 4, 2026
완료 기준 7개 중 부분 충족이던 셋(block#3 renamed, block#6 upgrade 경로, block#7 UI 실행
증거)을 충족으로 올리고 Phase 3을 완료로 표시한다. 세션 D 시점의 판정은 각
행에 그대로 남겼다 — 표를 고치고 근거를 나중에 맞추는 순서가 아니다.

WORKSPACE_CATALOG.md의 두 곳을 정정했다. §7의 renamed 「구현 상태」는 이제
미리보기와 ledger 둘 다이고, 검증 표의 「데스크톱 | pnpm test | 설정 카드
렌더」는 애초에 없던 테스트를 가리키고 있었으므로 실제 Playwright 스펙으로
바꿨다. §5에는 도출식이 버전을 빼는 것을 무엇이 고정하는지 적었다.

renamed의 필드 추가에는 §4의 리더-우선 순서가 적용되지 않는다는 것도
명시했다. 그 순서는 relay에 저장돼 구버전이 읽는 provenance의 steps 어휘를
위한 것이고, Ledger는 apply command의 반환값으로만 살아 생산자와 소비자가
같은 빌드 안에 있다. steps 쪽 규칙은 그대로다.

E1 계획서에 세 번째 계획 이탈을 보탰다 — Task 5의 featureGate는 구현 중에
뒤집혔고(매니페스트에 없는 ID는 fail-open이라 무음 no-op) 실제 게이트는
SettingsView의 역할 검사다. 앞서 둘만 적어 목록이 불완전했다.

BASELINE에는 게이트 18줄과 재주입 결과표를 남겼다. 셋 중 둘만 단독
방어선이었고, upgrade 테스트의 캔버스 단언은 아니었다 — no_change가 캔버스
단계 앞에서 반환하기 때문이다. 그 사실과, 새 Tauri command를 더하는 세션은
mock 핸들러도 함께 더해야 한다는 것을 기록했다.

Phase 3 밖에 남은 것 넷은 그대로다: generation 증가 경로, 선점의 약한
형태와 위임 실행 요청, CLI 적용 경로, 나머지 8개 업무방 콘텐츠.

Signed-off-by: Dev_YongT <devyongt@gmail.com>
tkrnkym pushed a commit to tkrnkym/nuxx that referenced this pull request Aug 4, 2026
Creating one was a text field and a toast. That is enough to name a
community and nothing like enough to have one: the person doing it has
just become its owner, and the two decisions that follow — who can get in,
and who else is in it — are exactly the ones that are painful to discover
later. The third door of "add a community" now opens a three-step setup
flow that asks them while the reader is still thinking about the community
rather than a week after they gave up looking for the setting.

The draft starts invite-only, which is the safe default for something that
does not exist yet; an open door on an empty community cannot be
reconsidered after the fact. Inviting nobody is allowed, because a
community of one is a legitimate thing to start and blocking on "invite
somebody" would be a demand rather than an offer. The community it creates
lands in the rail, since one that was created and then did not appear
reads as the creation having failed.

The flow is also what the eight `--nuxx-hosted-community-*` tokens were
for. They were copied over with nothing reading them, which is the exact
mistake AGENTS.md warns about — so this adds their Tailwind mapping and
their consumer together, plus an e2e that asserts the computed colours,
because a missing mapping degrades silently to inherited ones rather than
failing. The light palette is deliberate and stays put under every theme:
this is the one screen a person sees before they have a community, so it
cannot follow a theme they have not chosen.

It is a plain overlay rather than the `Dialog` primitive. That one calls
`showModal()`, and a full-window flow inside the browser's top layer
cannot be scrolled past on a short viewport — the same top-layer behaviour
as gotcha block#6.

Also drops `--nuxx-gradient-top` / `--nuxx-gradient-bottom`, two aliases
with no reader at all.

Signed-off-by: Claude <noreply@anthropic.com>
heintonny added a commit to heintonny/buzz that referenced this pull request Aug 6, 2026
Wizard plan Phase 2: a provider-backed create now walks Identity →
Where → Harness → Sign-in → Model → Review; a local create keeps the
one-screen fast path untouched. A provider create is a chain of
dependent questions — the host's catalog depends on the connection,
the harness on the catalog, the auth story and models on the harness —
and the wizard makes that ordering structural instead of guarded.

The step model (createWizardSteps.ts) is pure and reuses the existing
validity signals as gates: config-complete + host-answered opens
Harness, a picked harness opens the rest, and the current step clamps
back automatically when an edit invalidates a later answer (changing
the host walks you back from Model to Where). The submit button exists
only on the Review step; Enter mid-wizard means Next, never Create.

This structurally fixes the two open provider-mode UX bugs: the
AI/model section cannot render before the host answered (block#6 — it
previously offered THIS computer's models for an unpicked host), and
Advanced now lives on the Review step instead of overlapping the SSH
controls at the bottom of the dialog (block#5). The Sign-in step consumes
the per-harness auth states (probed yes/no with hint, manual
side-task) and never gates — deploy-then-login stays legitimate.

WhereToRunSection renders in slices (where/harness/hidden) so the
wizard uses it as two step bodies while keeping the ONE component —
and its in-flight host requests, probed names, and error state —
mounted across steps. Its model-probe writes now re-assert the harness
fields of the request they answer: draftRef can lag a frame behind the
catalog write, and a fast host (or the e2e mocks) answered before
React re-rendered, silently clobbering the catalog. The file-size
ratchet drove the extraction of the AI-configuration section and the
Advanced disclosure into their own components.

E2E: the mock bridge now mocks check_backend_provider /
discover_provider_harnesses / probe_provider_models, and
create-wizard-screenshots.spec.ts walks every step against a mocked
SSH provider (five step screenshots) and pins the unreachable-model
and local-fast-path properties. where-to-run-config.spec.ts is
repaired for the Radix run-target dropdown (selectOption never worked
against it) and names the agent first, as the wizard requires.

Known, pre-existing on this branch (unchanged here): 8 smoke tests
against the redesigned instance-edit dialog (edit-agent-run-on,
parts of agent-numeric-tuning/readiness) assert the old upstream
layout and fail with or without this change — they need their own
update pass before the upstream PR.

Signed-off-by: Hein Tonny Køien <hein.tonny@koien.no>
BradGroux pushed a commit to BradGroux/buzz that referenced this pull request Aug 23, 2026
TheSentinel454 added a commit that referenced this pull request Aug 26, 2026
## Why
Finish the replaceable-event slice of [tracker
#2](TheSentinel454#2) and [domain issue
#6](TheSentinel454#6) without disturbing
the runtime/store boundary established by #6660 and #6668. PR #6700 has
merged; this PR now targets current main containing its replacement lock
and transaction observability.

## What
- Move `Db::replace_addressable_event`, its SQL, lock/transaction
instrumentation, and focused addressable/parameterized replacement tests
from `lib.rs` to `replaceable.rs`
- Preserve the transaction-required parameterized API, replacement
ordering, rollback semantics, mention indexing, and exactly one
datastore span per public operation
- Keep implementation and focused PostgreSQL tests co-located; follow-up
review removed the dedicated replaceable ownership source guard as low
value

## Stack
- Exact base: main at f249710
- Exact head: codex/issue-6-finish-replaceable-store at
da01840
- Tracker: TheSentinel454#2
- Domain: TheSentinel454#6
- Test/span acceptance: TheSentinel454#17
and TheSentinel454#19

## Non-goals
- No SQL, schema, retry, timeout, lock ordering, transaction boundary,
or client-visible behavior changes
- No store traits, domain-handle redesign, `PgExecutor` migration, raw
pool accessor, new crate, or directory reorganization
- No changes to, retargeting of, or merge action on PR #6700

## Risk Assessment
Low. This is a mechanical ownership move with unchanged signatures and
SQL. The primary review risk is losing or nesting instrumentation,
covered by exact moved PostgreSQL tests and the cumulative
production-diff audit.

## Blox Verification
Author workstation: `buzz-tornquist-issue-2-store-stack` (`2046520`),
exact head `2de5444e14606ce6912d1bb932bc88bffa379a77`.

- `cargo fmt --all --check` — passed
- `cargo clippy -p buzz-db -p buzz-relay --all-targets -- -D warnings` —
passed
- `cargo test -p buzz-db --quiet` — 108 passed, 200 ignored; both
integration source guards passed
- Native PostgreSQL: `cargo test -p buzz-db replaceable::tests:: --
--ignored --test-threads=1` — 11 passed
- `cargo test -p buzz-relay --lib
api::mesh_demo::tests::demo_join_forwarded_arm_round_trips_echo --
--exact --test-threads=1` — passed
- Full serial relay suite was also exercised: 908 tests passed but the
existing load-sensitive mesh demo test returned 504 under suite load,
matching PR #6700's documented baseline; it passed in isolation and this
diff does not touch that subsystem

Independent exact-head Blox review: `buzz-tornquist-pr-6777-review`
(`2047422`) at `2de5444e14606ce6912d1bb932bc88bffa379a77`.

- Review findings — none (critical, important, or minor)
- `cargo fmt --all --check` and the ownership guard — passed
- `cargo clippy -p buzz-db -p buzz-relay --all-targets -- -D warnings` —
passed
- `cargo test -p buzz-db --quiet` — 108 passed, 200 ignored; source
guards passed
- Native PostgreSQL moved suite — 11 passed
- `cargo test -p buzz-relay --lib` — 909 passed, 48 ignored

Generated with Codex

## Superseded pre-comment restack verification

PR #6700 merged before publication completed. This layer was restacked
onto current main through the exact parent named above; the final
cumulative tip is 2ddcc8a. Cumulative
author gates passed: formatting and diff checks; buzz-db and buzz-relay
all-target clippy with -D warnings; DB lib 111 passed / 200 ignored;
ownership 22/22; observability 1/1; the full isolated PostgreSQL domain
matrix; and relay lib 910 passed / 49 ignored.

- Workstation: `buzz-tornquist-pr-6777-final-review` (`2057617`), fresh
shallow checkout
- Base: `f24971033178926153b49d320bd876d15d9cb2bf`
- Head: `ffbeaaf00810aa359ab85818ff4820f92263e45f`
- Findings: none

Reviewed `base..head` for the replaceable-store extraction. The
addressable replacement SQL, stale/duplicate outcomes, advisory-lock
ordering, transaction rollback/commit boundaries, mention-index
atomicity, and public `Db` signature are retained in `replaceable.rs`.
The PR #6700 transaction timer remains around the same logical
transaction through `TransactionTimer::observe`, and the public
operation has exactly one datastore span. Focused addressable and
parameterized tests moved with the implementation; the ownership guard
excludes duplicates from `lib.rs`.

Verification: format and diff checks passed; `buzz-db --all-targets`
clippy passed with `-D warnings`; DB lib tests passed (111 passed, 200
PostgreSQL tests ignored); ownership (1/1) and observability (1/1)
guards passed; all 11 replaceable PostgreSQL tests passed on native
PostgreSQL 17 with migrations 1-32 successful; relay lib test target
compiled successfully. Final worktree was detached at the exact head and
clean.

Complete evidence archive SHA-256:
`e37f92bbde4e2343196085095b3093b1bc78196be58bc86826325e0e1639d7ea`.

## Comment-addressed restack

Review follow-up removed the dedicated
`replaceable_store_has_single_ownership` source test as requested. No
production code changed.

- Exact base: `f24971033178926153b49d320bd876d15d9cb2bf`
- Exact head: `da018405cc83605362125c2d5e5a3f91492431ac`
- Final cumulative tip: `6fa2f104d42c6ba85bdf62e7ccb74ceaf4a84f67`
- Focused PR verification: formatting and diff checks passed; strict
`buzz-db`/`buzz-relay` Clippy passed; DB lib 111 passed / 200 ignored;
all 11 replaceable PostgreSQL tests passed.
- Cumulative Blox gate: formatting and diff checks; strict
`buzz-db`/`buzz-relay` Clippy; DB lib 111 passed / 200 ignored;
ownership 21/21; observability 1/1; every moved PostgreSQL test; relay
lib 910 passed / 49 ignored.
- Independent re-review at this exact head: no findings; fresh
exact-parent/head Blox review passed fmt/diff, strict `buzz-db` Clippy,
DB lib 111 passed / 200 ignored, manual one-owner/one-span replacement
checks, observability, 11 replaceable PostgreSQL tests, and relay
compilation.

---------

Signed-off-by: tornquist <tornquist@squareup.com>
TheSentinel454 added a commit that referenced this pull request Aug 28, 2026
## Summary

Finish the remaining database-store extraction tracked by
[TheSentinel454#2](TheSentinel454#2)
in one reviewable PR.

This consolidates the previously stacked domain slices after #6782
merged. It preserves the runtime/store boundary established by #6660,
#6668, #6700, and #6782 while separating database runtime infrastructure
from domain-owned persistence:

- `runtime/` owns pool construction and sizing, writer/reader routing,
read sessions and route proofs, transaction infrastructure,
observability primitives, replica fencing, health support, migrations,
and cross-cutting runtime tests.
- `store/` owns domain records, SQL, row parsing, locks and invariants,
`Db` domain methods, focused tests, and logical-operation datastore
spans.
- `lib.rs` remains a 57-line compatibility facade that preserves
existing crate-root paths and `Db` method signatures through re-exports.

Domain coverage includes API tokens, authentication allowlists,
reminders, event queries, threads, reactions, feeds, users and DMs,
push, workflows/runs/approvals, relay membership and invites, product
feedback, moderation/admin moderation, relay admin actions/operators,
git repositories, archived identities, usage, partition maintenance,
deletion, channel membership inherited from merged #6782, and the final
runtime/store layout.

The branch has been rebased onto current `main`. Database changes that
landed there were incorporated rather than overwritten:
`relay_admin_actions.rs` and `relay_operators.rs` now live under
`store/`, their 27 public `Db` wrappers and existing behavior remain
intact, and every wrapper has exactly one fixed-name datastore span.
Concurrent changes to migration, moderation, admin moderation, and error
handling are also retained.

### Exact base and head

- Base: `main` at `ed11c8d8bf0a17402be5cf243724f89471530d2f`
- Head: `codex/issue-2-store-extraction` at
`be24430472d1a87ac5c0d6026c620cd6caea3537`

### Related issue

- Structural tracker:
[TheSentinel454#2](TheSentinel454#2)
- Domain trackers:
[#6](TheSentinel454#6),
[#7](TheSentinel454#7),
[#12](TheSentinel454#12),
[#13](TheSentinel454#13)
- Acceptance trackers:
[#17](TheSentinel454#17),
[#19](TheSentinel454#19)

This supersedes #6783, #6784, #6787, #6788, #6789, #6792, #6820, #6794,
#6796, #6797, #6798, #6799, #6804, #6805, #6806, #6808, #6809, #6811,
#6812, #6813, #6814, #6815, and #6890. Their discussions remain
available for review history.

### #17 / #19 acceptance

- Preserves the metric names, fixed labels, transaction/lock timing
boundaries, and privacy/cardinality constraints introduced by #6700.
- Keeps exactly one datastore span per public logical operation,
including the 27 relay-admin wrappers added on `main`.
- Removes `store_ownership.rs`; physical ownership and focused source
guards now enforce the boundary directly.
- Leaves no `impl Db`, domain SQL, focused domain test group, or
datastore span in `lib.rs`.
- Preserves existing public paths such as `buzz_db::channel`,
`buzz_db::event`, and `buzz_db::workflow` through crate-root re-exports
while keeping internal `runtime` and `store` namespaces private.

### Non-goals

- No SQL, schema, locking, transaction, retry, timeout, or
client-visible behavior changes.
- No generic store traits, domain handles, broad `PgExecutor` migration,
new store crate, raw pool accessor, or broader directory reorganization.
- No tracker issues are closed by this PR.

### Risk

The cumulative diff is large but structural. Risk is primarily
module-path, ownership, or conflict-resolution drift. It is mitigated by
preserving public re-exports, comparing the newly moved `main`
implementations to their upstream source, source guards, touched-crate
compilation, PostgreSQL-backed test coverage, and an independent
exact-head review on a separate clean Blox workstation.

### Testing

Author workstation `buzz-tornquist-pr-6987-rebase`, rebased branch
ending at exact head `be24430472d1a87ac5c0d6026c620cd6caea3537`:

- `cargo fmt --all --check`
- `cargo clippy -p buzz-db -p buzz-relay --all-targets -- -D warnings`
- `cargo test -p buzz-db --lib` — 113 passed, 240 PostgreSQL tests
intentionally ignored
- `cargo test -p buzz-db --test observability_source` — 2 passed
- PostgreSQL-backed `buzz-db` coverage under native PostgreSQL — 235
passed in the shared serial run; the five shared-state/config-sensitive
cases passed as isolated reruns against fresh schemas, including the two
owner-limit tests with their fixture's
`BUZZ_MAX_COMMUNITIES_PER_OWNER=3`
- `cargo test -p buzz-relay --lib -- --test-threads=1` under native
PostgreSQL/Redis — 991 passed; the three current-month
partition-sensitive identity-archive cases passed after provisioning the
August 2026 test partition; 87 infrastructure-marked tests remained
ignored
- Source/diff guards — relay-admin implementation bodies match current
`main`; all 27 public wrapper signatures are retained; exactly one
datastore span wraps each wrapper; `lib.rs` has zero `impl Db` blocks
and zero datastore spans; no duplicate top-level relay-admin modules or
`store_ownership.rs`; `error.rs` matches current `main`

Independent clean review workstation `buzz-tornquist-pr-6987-review`,
detached at exact head `be24430472d1a87ac5c0d6026c620cd6caea3537`:

- `cargo fmt --all --check`
- `cargo clippy -p buzz-db -p buzz-relay --all-targets -- -D warnings`
- `cargo test -p buzz-db --lib` — 113 passed, 240 ignored
- `cargo test -p buzz-db --test observability_source` — 2 passed
- Exact-head ownership/re-export/instrumentation audit — no remaining
actionable findings

---------

Signed-off-by: OpenAI Codex <codex@openai.com>
Signed-off-by: tornquist <tornquist@squareup.com>
Co-authored-by: OpenAI Codex <codex@openai.com>
wpfleger96 pushed a commit that referenced this pull request Aug 29, 2026
## Summary

Finish the remaining database-store extraction tracked by
[TheSentinel454#2](TheSentinel454#2)
in one reviewable PR.

This consolidates the previously stacked domain slices after #6782
merged. It preserves the runtime/store boundary established by #6660,
#6668, #6700, and #6782 while separating database runtime infrastructure
from domain-owned persistence:

- `runtime/` owns pool construction and sizing, writer/reader routing,
read sessions and route proofs, transaction infrastructure,
observability primitives, replica fencing, health support, migrations,
and cross-cutting runtime tests.
- `store/` owns domain records, SQL, row parsing, locks and invariants,
`Db` domain methods, focused tests, and logical-operation datastore
spans.
- `lib.rs` remains a 57-line compatibility facade that preserves
existing crate-root paths and `Db` method signatures through re-exports.

Domain coverage includes API tokens, authentication allowlists,
reminders, event queries, threads, reactions, feeds, users and DMs,
push, workflows/runs/approvals, relay membership and invites, product
feedback, moderation/admin moderation, relay admin actions/operators,
git repositories, archived identities, usage, partition maintenance,
deletion, channel membership inherited from merged #6782, and the final
runtime/store layout.

The branch has been rebased onto current `main`. Database changes that
landed there were incorporated rather than overwritten:
`relay_admin_actions.rs` and `relay_operators.rs` now live under
`store/`, their 27 public `Db` wrappers and existing behavior remain
intact, and every wrapper has exactly one fixed-name datastore span.
Concurrent changes to migration, moderation, admin moderation, and error
handling are also retained.

### Exact base and head

- Base: `main` at `ed11c8d8bf0a17402be5cf243724f89471530d2f`
- Head: `codex/issue-2-store-extraction` at
`be24430472d1a87ac5c0d6026c620cd6caea3537`

### Related issue

- Structural tracker:
[TheSentinel454#2](TheSentinel454#2)
- Domain trackers:
[#6](TheSentinel454#6),
[#7](TheSentinel454#7),
[#12](TheSentinel454#12),
[#13](TheSentinel454#13)
- Acceptance trackers:
[#17](TheSentinel454#17),
[#19](TheSentinel454#19)

This supersedes #6783, #6784, #6787, #6788, #6789, #6792, #6820, #6794,
#6796, #6797, #6798, #6799, #6804, #6805, #6806, #6808, #6809, #6811,
#6812, #6813, #6814, #6815, and #6890. Their discussions remain
available for review history.

### #17 / #19 acceptance

- Preserves the metric names, fixed labels, transaction/lock timing
boundaries, and privacy/cardinality constraints introduced by #6700.
- Keeps exactly one datastore span per public logical operation,
including the 27 relay-admin wrappers added on `main`.
- Removes `store_ownership.rs`; physical ownership and focused source
guards now enforce the boundary directly.
- Leaves no `impl Db`, domain SQL, focused domain test group, or
datastore span in `lib.rs`.
- Preserves existing public paths such as `buzz_db::channel`,
`buzz_db::event`, and `buzz_db::workflow` through crate-root re-exports
while keeping internal `runtime` and `store` namespaces private.

### Non-goals

- No SQL, schema, locking, transaction, retry, timeout, or
client-visible behavior changes.
- No generic store traits, domain handles, broad `PgExecutor` migration,
new store crate, raw pool accessor, or broader directory reorganization.
- No tracker issues are closed by this PR.

### Risk

The cumulative diff is large but structural. Risk is primarily
module-path, ownership, or conflict-resolution drift. It is mitigated by
preserving public re-exports, comparing the newly moved `main`
implementations to their upstream source, source guards, touched-crate
compilation, PostgreSQL-backed test coverage, and an independent
exact-head review on a separate clean Blox workstation.

### Testing

Author workstation `buzz-tornquist-pr-6987-rebase`, rebased branch
ending at exact head `be24430472d1a87ac5c0d6026c620cd6caea3537`:

- `cargo fmt --all --check`
- `cargo clippy -p buzz-db -p buzz-relay --all-targets -- -D warnings`
- `cargo test -p buzz-db --lib` — 113 passed, 240 PostgreSQL tests
intentionally ignored
- `cargo test -p buzz-db --test observability_source` — 2 passed
- PostgreSQL-backed `buzz-db` coverage under native PostgreSQL — 235
passed in the shared serial run; the five shared-state/config-sensitive
cases passed as isolated reruns against fresh schemas, including the two
owner-limit tests with their fixture's
`BUZZ_MAX_COMMUNITIES_PER_OWNER=3`
- `cargo test -p buzz-relay --lib -- --test-threads=1` under native
PostgreSQL/Redis — 991 passed; the three current-month
partition-sensitive identity-archive cases passed after provisioning the
August 2026 test partition; 87 infrastructure-marked tests remained
ignored
- Source/diff guards — relay-admin implementation bodies match current
`main`; all 27 public wrapper signatures are retained; exactly one
datastore span wraps each wrapper; `lib.rs` has zero `impl Db` blocks
and zero datastore spans; no duplicate top-level relay-admin modules or
`store_ownership.rs`; `error.rs` matches current `main`

Independent clean review workstation `buzz-tornquist-pr-6987-review`,
detached at exact head `be24430472d1a87ac5c0d6026c620cd6caea3537`:

- `cargo fmt --all --check`
- `cargo clippy -p buzz-db -p buzz-relay --all-targets -- -D warnings`
- `cargo test -p buzz-db --lib` — 113 passed, 240 ignored
- `cargo test -p buzz-db --test observability_source` — 2 passed
- Exact-head ownership/re-export/instrumentation audit — no remaining
actionable findings

---------

Signed-off-by: OpenAI Codex <codex@openai.com>
Signed-off-by: tornquist <tornquist@squareup.com>
Co-authored-by: OpenAI Codex <codex@openai.com>
matt2e added a commit that referenced this pull request Sep 2, 2026
…e after send-path side effects

Address the two review warnings on the publish-first mention-send stack
(f3df4be4f, f0a9915b5), keeping its design intact.

1) Provider replay-floor gap — fixed via the preferred route: thread the
floor into the deploy payload. start_managed_agent's provider branch now
passes replay_floor_unix through deploy_to_provider, which injects it
into the payload REBUILT after the deploy lock as
launch.policy_env.BUZZ_ACP_REPLAY_FLOOR — the same env var a local spawn
sets — so a cold remote harness's startup watermark replays back past
the already-published triggering mention instead of booting blind to it.
No synchronous fallback was needed: launch.policy_env already transports
per-spawn env (effort level, model, session title) to providers, so the
deploy handoff carries the floor cleanly. Any same-named launch.env key
is stripped when a caller floor is present (that tier later-wins
remotely); the floor is invocation state, never persisted, so redeploys
without one never inherit a stale floor. Other deploy callers (create
flow, access reconciliation, inbound personas) pass None and are
unchanged.

2) Revalidation-at-publish window: the single-pass dedupe now reuses the
pre-side-effect authorization pass only when nothing separated it from
the publish. completeSend tracks whether an awaited relay round-trip
actually ran in between — DM expansion (onPrepareSendChannel),
managed-agent access-policy or membership attach writes (reported by
ensureManagedAgentMentionsReady; a matching policy that returns the same
record does not count), or active-huddle enrollment
(sync_agents_to_active_huddle's matched_active_huddle; with no active
huddle it returns before touching the relay) — and re-validates at the
publish boundary when any did. The common send (member agent, no
expansion, no huddle) stays single-pass.

3) startAgentDetached now depends on startAgentMutation.mutateAsync
(stable) instead of the whole mutation object (fresh each render, repo
gotcha #6), keeping the downstream useCallback chain reference-stable.

Tests: five Rust unit tests pin the payload injection (policy_env ride,
case-insensitive launch.env shadow strip, None passthrough, tolerance
for missing launch/policy_env); the in-channel provider E2E spec now
also pins replayFloorUnix on the detached deploy invoke. New E2E specs
pin publish-boundary revalidation for the attach path (revocation
injected while a delayed membership write holds the publish open strips
the p tag; +2 passes with no update_managed_agent) and for a live huddle
on the channel (+2 passes, +1 huddle sync); the managed relay-agent
DM-expansion spec pins +2; the two fast-path specs keep pinning +1.

Verified: cargo test --lib on desktop/src-tauri (3013 passed), clippy -D
warnings + fmt, desktop tsc --noEmit, biome, desktop unit tests (5799
passed), full mentions Playwright smoke (81/83 — both failures are
pre-existing under-load flakes, green in isolation), a 3x stress rerun
of the new specs, and the five DM-expansion channels specs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>
matt2e added a commit that referenced this pull request Sep 3, 2026
…e after send-path side effects

Address the two review warnings on the publish-first mention-send stack
(f3df4be4f, f0a9915b5), keeping its design intact.

1) Provider replay-floor gap — fixed via the preferred route: thread the
floor into the deploy payload. start_managed_agent's provider branch now
passes replay_floor_unix through deploy_to_provider, which injects it
into the payload REBUILT after the deploy lock as
launch.policy_env.BUZZ_ACP_REPLAY_FLOOR — the same env var a local spawn
sets — so a cold remote harness's startup watermark replays back past
the already-published triggering mention instead of booting blind to it.
No synchronous fallback was needed: launch.policy_env already transports
per-spawn env (effort level, model, session title) to providers, so the
deploy handoff carries the floor cleanly. Any same-named launch.env key
is stripped when a caller floor is present (that tier later-wins
remotely); the floor is invocation state, never persisted, so redeploys
without one never inherit a stale floor. Other deploy callers (create
flow, access reconciliation, inbound personas) pass None and are
unchanged.

2) Revalidation-at-publish window: the single-pass dedupe now reuses the
pre-side-effect authorization pass only when nothing separated it from
the publish. completeSend tracks whether an awaited relay round-trip
actually ran in between — DM expansion (onPrepareSendChannel),
managed-agent access-policy or membership attach writes (reported by
ensureManagedAgentMentionsReady; a matching policy that returns the same
record does not count), or active-huddle enrollment
(sync_agents_to_active_huddle's matched_active_huddle; with no active
huddle it returns before touching the relay) — and re-validates at the
publish boundary when any did. The common send (member agent, no
expansion, no huddle) stays single-pass.

3) startAgentDetached now depends on startAgentMutation.mutateAsync
(stable) instead of the whole mutation object (fresh each render, repo
gotcha #6), keeping the downstream useCallback chain reference-stable.

Tests: five Rust unit tests pin the payload injection (policy_env ride,
case-insensitive launch.env shadow strip, None passthrough, tolerance
for missing launch/policy_env); the in-channel provider E2E spec now
also pins replayFloorUnix on the detached deploy invoke. New E2E specs
pin publish-boundary revalidation for the attach path (revocation
injected while a delayed membership write holds the publish open strips
the p tag; +2 passes with no update_managed_agent) and for a live huddle
on the channel (+2 passes, +1 huddle sync); the managed relay-agent
DM-expansion spec pins +2; the two fast-path specs keep pinning +1.

Verified: cargo test --lib on desktop/src-tauri (3013 passed), clippy -D
warnings + fmt, desktop tsc --noEmit, biome, desktop unit tests (5799
passed), full mentions Playwright smoke (81/83 — both failures are
pre-existing under-load flakes, green in isolation), a 3x stress rerun
of the new specs, and the five DM-expansion channels specs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>
birdblues added a commit to birdblues/buzz that referenced this pull request Sep 5, 2026
…side

Review follow-up (Codex + Grok on e4bf6fa). Both flagged the same blocker:
the WebView loaded the app door over plain HTTP and trusted the response
CSP, so a LAN MITM (or a header regression) could strip the policy and
restore fetch/XHR/WebSocket/subresource exfiltration — the navigation
delegate only sees navigations. Grok also warned that iOS 17+ ATS may block
raw private-IP HTTP in WKWebView regardless of NSAllowsLocalNetworking.

Both go away by never letting the WebView touch the network, the way the
desktop proxy works:
- shared/relay/app_content.dart: fetchAppDocument — Authorization header,
  no redirects (a 3xx fails; a custom header must never follow one),
  text/html only, 8 MiB cap, timeout; stampSandboxCsp — the relay/desktop
  policy minus `sandbox` (not expressible in <meta>) inserted as the first
  element after a leading doctype so no script can precede it.
- app_webview_page.dart: loadHtmlString with no base URL → about:blank,
  opaque origin, no storage. Navigation is one-shot: exactly the first
  main-frame about:blank load; reloads, window.open, same-URL posts and
  every subframe are prevented (decideAppNavigation, Codex block#2). Retries are
  generation-fenced so a stale callback cannot overwrite the current load
  (Codex block#4). Before running, Dart asks the native side whether the WebRTC
  hook is installed and fails closed otherwise (Codex block#3, Android included).
- SandboxWebViewHardening.swift: hook loadHTMLString(_:baseURL:) instead of
  load(_:) — the page's only entry point now; `installed` is set only after
  the exchange succeeds; kill list gains WebTransport and
  webkitGetUserMedia (Grok block#4; desktop script updated for parity).
- Info.plist: the ATS exception is removed — nothing in the WebView loads
  from the network any more.
- message_content.dart: an `![…]()` reference to an HTML app never reaches
  the image decoder; without a door it renders an inert download pill
  (Codex block#6). New `allowAppCards` flag turned off on the forum post card and
  compose-note preview, which crop with a box instead of maxLines and could
  hide a card's safety chrome (Codex block#5). authorLabel wired on forum thread
  posts/replies and pulse notes (Codex block#7, Grok block#7).
- relay_info.dart: a failed NIP-11 lookup while connected retries after 30 s
  instead of waiting for the next reconnect (Grok block#9).
- AppCard: the preview is a plain tap target; Run is the one announced
  action (Grok block#8).
- docs/sandboxed-apps.md: threat-model rows and the mobile table updated;
  iPad step 3 no longer checks ATS, the probe must see location.href ==
  about:blank.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UAm5qbNPszvcwnMMbfGVGa
Signed-off-by: dhseo <birdblues@mac.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.

1 participant