Provider surface: advertise a capability, see jobs, quote - #117
Closed
toon-backlog-bot[bot] wants to merge 4 commits into
Closed
Provider surface: advertise a capability, see jobs, quote#117toon-backlog-bot[bot] wants to merge 4 commits into
toon-backlog-bot[bot] wants to merge 4 commits into
Conversation
Task: #84, part of the agent-fleet-money epic (toon-meta#262 decision 12 / Wave 5). PR #101 shipped this ticket's freshness invariant (providerAvailability.ts) but stopped there, citing three blockers on the "advertise"/"see jobs"/"quote" pieces. Re-checked every one of those blockers against the current repo and live npm before writing any code — two no longer hold, and this ships against what's left. Key decisions: - **Advertise (item 1) needs no wire event.** The prior blocker was "no NIP-89 kind:31990 allocation" — true, and toon-meta#263 (the closed spec ticket this whole epic builds against) still only specifies request/quote/offer/result/narration, nothing for discovery. But the pull model this ticket also needs (item 2) makes a discovery broadcast unnecessary: a provider finds jobs by reading the open kind:5097 feed directly, so "advertise" is a local, permissionless toggle (providerCapabilitySettings.ts) — consistent with decision 14's "no allowlist, no approval step." - **The inbound job feed (item 2) was not actually blocked.** The prior note said "no open-relay subscription path exists" — but buzz#85's buyer surface (merged before PR #101, in the same epic) already built exactly that (ToonEventTransport.fetchEvents/subscribeLive against g.toon.relay). useInboundFactoryJobs.ts reuses it unscoped by author, filtered client-side by matchesProviderCapability. - **Gift-wrapped briefs (also item 2) were not actually blocked either.** The prior note said the only unwrap precedent (channelKeyDelivery.ts) hardcodes its rumor kind — true, but nostr-tools/nip59 exports a fully generic unwrapEvent primitive underneath it. Verified that helper is insecure to use directly (it decrypts both layers and discards the seal, so a caller never learns whether the seal's signature verifies or whether rumor.pubkey matches it — channelKeyDelivery.ts's own stated reason for not using it). unwrapFactoryJobRequest.ts duplicates that ~10-line authenticity check for the factory-job rumor kind rather than sharing code with the channel-membership trust path, a different and more sensitive blast radius. - **Quote (item 3) needed no getClaimState.** The prior note conflated "bump the client for getClaimState" (item 3's real ask was a schedule builder) with reading earned balance, which this ticket never needed — quoting is just signing and publishing a paid write, the same transport.publish path postFactoryJob.ts already uses. factoryJobQuote.ts builds the kind:7000 status:"quote" template to the exact shape factoryJobFeedback.ts's parser already reads (that module's own doc says "it never builds a request" — this is the missing builder), round-trip tested against that parser directly. - **What is still genuinely blocked, re-confirmed today**: sessionLeaseTtlMs (providerAvailability.ts's freshness gate) has no live source — pulled @toon-protocol/client@0.26.1 from npm directly and diffed its .d.ts; ToonChannelAccept/ParsedX402Challenge still have no `extra` bag, so session_lease_ttl_ms is still silently dropped during parsing, exactly as the last two agents on this ticket found. Rather than block the whole surface on that, ProviderJobsPanel gates quoting on FactoryJobAvailability (the coarse "is the TOON transport ready" signal buzz#85 already uses) instead of the full time-windowed freshness model, which stays ready for its caller once block#722's TTL reaches the client library. Files changed: desktop/src/features/providers/lib/{providerCapabilitySettings, providerJobMatch,factoryJobQuote,postFactoryJobQuote, unwrapFactoryJobRequest,useInboundFactoryJobs}.ts (new, +4 test files), desktop/src/features/providers/ui/{ProviderCapabilityToggle, InboundJobsList,QuoteForm,ProviderJobsPanel}.tsx (new), desktop/src/features/factory-jobs/ui/FactoryJobsScreen.tsx (Buying/Providing tab switch). Verified: fmt-check, desktop-tauri-fmt-check, clippy (workspace, -D warnings), test-unit (864 tests), desktop-check, desktop-test (4345 passed, 3 pre-existing skips), desktop-build, web-check, web-build — all green. Blockers/notes for next iteration: sessionLeaseTtlMs remains unwireable (see above) — no scope change needed here once it lands, just swap ProviderJobsPanel's availability gate to canQuoteJobs(deriveProviderAvailability(...)). Increment scheduling in QuoteForm is manual (owner types milestone names and prices) rather than derived from any pricing policy — toon-meta#262 explicitly leaves that undecided ("a rate card cannot price 'understand my weird codebase'"), so this is a deliberate scope stop, not a gap. No increment-offer/narration/result publishing is built — out of scope per the issue's own "What" list (delivery is rig#52's job). Gate-pass rate and reputation display for a provider's own history are not surfaced here (buyer-side only, from buzz#85). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: Claude Sonnet 5 <noreply@anthropic.com>
asLayer's validator and SealedLayer type never checked for id/sig, so the seal-verification call papered over the gap with a blind `as unknown as Parameters<typeof verifyEvent>[0]` cast instead of validating what verifyEvent actually requires. Split into asRumor (rumor fields only) and asSealedLayer (adds the id/sig fields the seal must carry), so verifyEvent's argument is now honestly typed and validated rather than force-cast. Signed-off-by: Claude Sonnet 5 <noreply@anthropic.com>
Task: #84, part of the agent-fleet-money epic (toon-meta#262 decision 12). PR #101 shipped the freshness invariant (providerAvailability.ts) and #101/1a593344 shipped advertise/see-jobs/ quote, but both left providerAvailability.ts's sessionLeaseTtlMs unwireable — no published @toon-protocol/client exposed the connector's session_lease_ttl_ms on the path this app's channel bootstrap actually negotiates through (toon-client#509, re-checked and re-blocked twice more in this issue's thread). toon-client#509 shipped in @toon-protocol/client@0.28.0 via ToonClient.getLastConnectorRouteTerms(), populated by ordinary publishEvent()/openChannel() bootstrap with no extra probe — the one remaining blocker this ticket closes. Key decisions: - Bumped @toon-protocol/client to ^0.28.0 (pnpm-workspace.yaml's minimumReleaseAgeExclude updated accordingly, lockfile regenerated). - ToonPaidWriter.getSessionLease() reads session_lease_ttl_ms off getLastConnectorRouteTerms()?.extra after every successful write, not just the first — ToonClient caches the greeting negotiation per peer, but a repeat write still proves the SESSION (not just the cached negotiation) was live at that moment, which is what the freshness window needs to bound. Undefined stays undefined (never a substituted 120_000 default) until a connector with connector#722 answers. - useProviderAvailability.ts wires that into providerAvailability.ts's deriveProviderAvailability, replacing ProviderJobsPanel's former coarse "is the transport ready" gate. - Found and fixed a real chicken-and-egg bug while wiring this: the lease is learned FROM a successful write, so gating the quote button on canQuoteJobs (kind === "available") would permanently block a provider agent's first-ever quote, since nothing else would produce the write the freshness state is waiting on. The quote action now gates on `availability.kind !== "stale"` instead — pending (unknown) proceeds, only a confirmed-dead session blocks — matching the freshness invariant's own asymmetry (a wrongly-allowed quote costs one rejected 1 µUSDC write; a wrongly-blocked one costs a provider its first job). - The @toon-protocol/client bump surfaced 5 pre-existing SVG accessibility lint failures (harness-logos icons, agent-outline.svg) and one theme.css formatting issue, unrelated to this ticket's code but now enforced by desktop-check against the new dependency resolution. Fixed via aria-hidden="true" (these are all consumed as <img src> with external alt text, matching the existing public/sounds/*.svg convention) and biome's auto-format. Files changed: desktop/package.json, pnpm-lock.yaml, pnpm-workspace.yaml, desktop/src/shared/api/toonPaidWriter.ts (+test), desktop/src/features/providers/lib/useProviderAvailability.ts (new), desktop/src/features/providers/ui/{ProviderJobsPanel,InboundJobsList, QuoteForm}.tsx, desktop/public/harness-logos/{grok,omp,openclaw, opencode}.svg, desktop/src/features/agents/assets/agent-outline.svg, desktop/src/shared/styles/globals/theme.css. Verified: fmt-check, desktop-tauri-fmt-check, clippy (workspace, -D warnings), test-unit (864 tests), desktop-check, desktop-test (4350 passed, 3 pre-existing skips), desktop-build, web-check, web-build — all green. No blockers remaining for this ticket. The provider surface (advertise, see jobs, quote, availability) is now fully wired end to end: quoting gates on a real, connector-sourced freshness window once one is known, and degrades to the coarse-but-safe "session confirmed reachable" read before the first write establishes it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: Claude Sonnet 5 <noreply@anthropic.com>
…lper The buy/provide body was a three-way nested ternary (availability -> mode -> buy content), which CODING_STANDARDS.md calls out to avoid. Pulled it into renderJobsScreenBody() with straight-line if/else returns — same output, easier to follow. Signed-off-by: Claude Sonnet 5 <noreply@anthropic.com>
9 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds the provider-side surface for factory job workflows: advertising a capability, seeing available jobs, and submitting quotes, along with a session lease TTL wiring and a couple of correctness fixes (unsafe
verifyEventcast, nested-ternary cleanup inFactoryJobsScreen).Part of #84
This PR was produced by the sandcastle
agent:implementrunner and is awaiting human review.🤖 Generated with Claude Code