Skip to content

docs(readme): clarify desktop setup#4

Merged
wesbillman merged 1 commit into
mainfrom
wesb/readme-desktop-setup
Mar 9, 2026
Merged

docs(readme): clarify desktop setup#4
wesbillman merged 1 commit into
mainfrom
wesb/readme-desktop-setup

Conversation

@wesbillman
Copy link
Copy Markdown
Collaborator

Summary

  • update Quick Start to use Hermit first and just setup
  • document just desktop-install before desktop-aware checks and hooks
  • note that the documented desktop workflow is macOS-first for now

Testing

  • pre-commit hook (cargo fmt --all -- --check, pnpm check)
  • pre-push hook (cargo clippy --workspace --all-targets -- -D warnings, ./scripts/run-tests.sh unit, pnpm build, cargo check --manifest-path desktop/src-tauri/Cargo.toml)

@wesbillman wesbillman merged commit 70ccc6e into main Mar 9, 2026
7 checks passed
@wesbillman wesbillman deleted the wesb/readme-desktop-setup branch March 9, 2026 20:22
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 Mar 12, 2026
Critical fixes:
- REQ #e channel filters now translated to #h UUIDs (was: ignored, breaking NIP-28 interop)
- Local kind:40/41 serving respects #e, ids, since, until, limit constraints (was: all channels)
- Mixed-kind single filters split into local + upstream (was: entire filter treated as local)
- pending_oks error cleanup uses correct key (was: wrong key, memory leak)

Important fixes:
- u16::try_from assertion at all kind cast boundaries (was: silent wrap on overflow)
- NIP-42 relay tag validation (debug warning, non-fatal)
- Hash-then-compare for admin secret (eliminates length oracle)
- pending_oks capped at 1000 entries (prevents unbounded growth)

Cleanup:
- Removed dead ProxyConfig/ProxyService from lib.rs
- track/untrack_subscription → pub(crate) with #[allow(dead_code)]

Tests: 54 passing (was 52), clippy clean
tlongwell-block added a commit that referenced this pull request Apr 11, 2026
- #2: Document display_name reverse-lookup fragility in agents.rs
  with TODO for long-term fix (store internal name on PersonaRecord)
- #3: Add on_message to ResolvedHooks so it's not silently dropped
  during resolution
- #4: Fix misleading comment on ResolvedPack.description (was 'use
  pack name as fallback', now 'not yet wired')

156 crate tests pass, desktop compiles clean.
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>
wpfleger96 added a commit that referenced this pull request May 22, 2026
…iew findings

The original implementation created a second parallel Tauri command
(discover_all_acp_providers) alongside the existing one to avoid
changing the return type. This produced two commands, two hooks, two
query keys, and two raw type converters. Consolidates into a single
command returning the full catalog, with a useAvailableAcpProviders
hook that type-narrows for callers needing non-null command/binaryPath.

Also fixes: pipe deadlock in install command (#1), UTF-8 truncation
panic (#2/#4), adds install concurrency guard (#11), exact provider ID
match (#15), error display stdout fallback (#5), success banner
suppression when already available (#12), misleading re-run text (#13),
IIFE refactor in PersonaDialog (#14), hidden internal query lift (#7),
configurable e2e mocks (#9), shared raw type exports (#8), and
classify_provider unit tests (#10).
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>
wpfleger96 added a commit that referenced this pull request May 22, 2026
…iew findings

The original implementation created a second parallel Tauri command
(discover_all_acp_providers) alongside the existing one to avoid
changing the return type. This produced two commands, two hooks, two
query keys, and two raw type converters. Consolidates into a single
command returning the full catalog, with a useAvailableAcpProviders
hook that type-narrows for callers needing non-null command/binaryPath.

Also fixes: pipe deadlock in install command (#1), UTF-8 truncation
panic (#2/#4), adds install concurrency guard (#11), exact provider ID
match (#15), error display stdout fallback (#5), success banner
suppression when already available (#12), misleading re-run text (#13),
IIFE refactor in PersonaDialog (#14), hidden internal query lift (#7),
configurable e2e mocks (#9), shared raw type exports (#8), and
classify_provider unit tests (#10).
wpfleger96 added a commit that referenced this pull request May 22, 2026
…iew findings

The original implementation created a second parallel Tauri command
(discover_all_acp_providers) alongside the existing one to avoid
changing the return type. This produced two commands, two hooks, two
query keys, and two raw type converters. Consolidates into a single
command returning the full catalog, with a useAvailableAcpProviders
hook that type-narrows for callers needing non-null command/binaryPath.

Also fixes: pipe deadlock in install command (#1), UTF-8 truncation
panic (#2/#4), adds install concurrency guard (#11), exact provider ID
match (#15), error display stdout fallback (#5), success banner
suppression when already available (#12), misleading re-run text (#13),
IIFE refactor in PersonaDialog (#14), hidden internal query lift (#7),
configurable e2e mocks (#9), shared raw type exports (#8), and
classify_provider unit tests (#10).
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