diff --git a/CHANGELOG.md b/CHANGELOG.md index 24994fb..01c0d6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,68 @@ Nothing on the wire today. Post-v1 work is described in --- +## [0.5.0] — 2026-07-23 + +**Wallapop "hacer oferta": operator-tapped offers + the negotiable band (#55)** + +The first minor since v0.4.0 because it adds a schema migration (0004) +and a whole new operator surface. Nothing changes until a wishlist +entry opts in — with no `offer:` block anywhere, rendering, filtering, +callbacks and the daemon lifecycle are byte-identical to v0.4.4. + +- **💰 Ofertar button** on Wallapop alerts, operator-tap only (the FR29 + no-autonomous-action rule extends to offers, PRD amendment + FR58–FR65). A tap drives the offer preflight, a reconciliation + re-fetch by internal listing id, an amount recompute under the Phase 2 + tolerances, and the TinyFish-driven native offer form with EXACTLY + the bounded amount — ending in `💰 Oferta enviada` or one of 12 + closed `OfferFailureReason` variants, each fully rendered with the + "No se ha enviado ninguna oferta." reassurance. +- **Negotiable-band alerts**: Wallapop listings whose delivered total is + over the entry ceiling but within `ceiling × (1 + offer.band_pct)` + (default +20 %) on offer-enabled entries stop being silently filtered + — a new `negotiable` alert phase with the `💰` severity token and an + `Ofertar · Saltar · Ver` keyboard (never Comprar). The LLM evaluation + and confidence gates are unchanged. +- **The amount is computed, never picked**: the largest whole-euro item + price whose delivered total (item + shipping + Protección) fits the + entry's offer target (`offer.target_total_eur`, default = ceiling), + bounded by Wallapop's platform floor of 70 % of asking + (operator-verified on app + web). Shown on the alert before the tap; + drift at tap time aborts fail-closed. +- **Guardrails mirror the buy path**: one successful offer per listing + ever; a self-imposed rolling-24 h budget (`offer.daily_limit`, + default 5 — under Wallapop's 10-per-day account cap, whose on-form + counter the agent captures into the audit row); an independent + lockout + kill switch (`offer_state` — offer failures never block + real buys); the append-only `offers` table (migration `0004`, + additive); and the keyboard restored on every outcome. v1 ends at + "offer sent": acceptance opens Wallapop's 24 h buy window (item NOT + reserved meanwhile) — the alert copy says exactly where to follow + through. +- **CLI**: `salvager offer enable [-t target]` / `disable + [--all]` / `status` (budget + lockout at a glance). The Wallapop + fetcher now projects `is_refurbished` from the search payload and the + offer surface pre-filters on it (refurbished listings don't accept + offers). +- `dev emit-alert` grew 45 → 66 variants (4 offer-eligible listing + shapes, `offer_sent`, 12 failures, 4 operational events), all pinned + by golden snapshots; the new surfaces join the v1.0 criterion-3 audit + scope (see the "Pending delta" in + `docs/release-audits/v1.0/SUMMARY.md`). +- Post-merge review hardening (#55): an audit-write failure AFTER a + verified send can no longer masquerade as a send failure (it stays a + success and escalates the missing dedupe row as an operational + alert), and `offer status` reads the configured `offer.daily_limit` + for its budget denominator. + +**OpenSpec (#56)** — the `wallapop-offer-flow` capability spec promoted +to `openspec/specs/` alongside the deltas to `shipping-aware-pricing` +and `listing-alert-state-updates`; the change archived with the +operator's app/web captures of the platform's offer rules. + +--- + ## [0.4.4] — 2026-07-20 No runtime behaviour changes — this release exists so the on-device diff --git a/README.md b/README.md index e49db30..1a42fa7 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ A self-hosted personal agent for monitoring second-hand homelab parts. Watches y **Wishlist focus:** HDDs (NAS-grade and enterprise) and DDR4 RAM. Extensible to other part types. **Distribution:** single Docker image, single docker-compose service. -> **Status (July 2026): `v0.4.4` — Phase 1 + Phase 2 feature-complete preview, Phase 2 wired live.** All Epic 2–5 code shipped in v0.2.0; v0.2.1–v0.2.2 patched the Docker image; v0.2.3 stabilised the Wallapop adapter against the SPA's current production traffic (v3 `/search/section` endpoint, browser-TLS impersonation via `curl_cffi`, transparent JWT refresh, reserved-listing routing); v0.3.0 wired the Phase 2 autonomous-purchase loop end-to-end (BuyOrchestrator composed with all collaborators, marketplace-dispatched checkout), the Telegram callback listener, an in-cycle reserved-comp line on alerts, and a pretty-log toggle; v0.3.1 adds the clickable deep link to the listing on every alert (FR18); v0.3.2 ships the price-parser smoke-test in the image and runs it (startup + daily) so Phase 2 is actually armable in production; v0.3.3 makes every price ceiling shipping-aware — alert and buy gates plus receipt reconciliation compare the delivered buyer total (item + shipping + Wallapop's Protección fee), and alerts show the breakdown; v0.3.4 extends that total with an estimated flat import-charges buffer for eBay listings located outside the EU (`pricing.assumed_import_charges_eur`, default 3,63 €); v0.3.5 moves LLM evaluation to `gemini-2.5-flash` (Google retired 2.0-flash) and clips over-long takes instead of discarding valid verdicts; v0.4.0 adds live-updating alerts — dispatched alerts are watched (`alerts.watch_days`) and the original Telegram message edits itself on reserved flips and price drops, with a ping for big drops; v0.4.1 classifies a pre-buy 404 as `listing_gone` (fail-closed, no circuit hit); v0.4.2 reconciles by internal listing id after Wallapop dropped slug support; v0.4.3 restores the alert keyboard after every buy outcome; v0.4.4 is a tooling-only release carrying the passed rendering re-audit and the extended `dev emit-alert` catalog. The daemon polls Wallapop + eBay.es, evaluates listings against the wishlist via Gemini Flash or Claude Haiku, dispatches Telegram alerts, and the Phase 2 autonomous-purchase loop runs behind the safety stack and the non-bypassable Telegram tap. **Not yet validated in production**: the operator's burn-in window is in progress; v1.0 promotion is gated on at least 2 weeks of continuous live-traffic operation + one completed Phase 2 purchase. Recommended pinned tag: `ghcr.io/ifuensan/salvager:0.4.4`. See [CHANGELOG.md](CHANGELOG.md) for release notes and [ROADMAP.md](ROADMAP.md) for the v1.0 path. +> **Status (July 2026): `v0.5.0` — Phase 1 + Phase 2 feature-complete preview, Phase 2 wired live.** All Epic 2–5 code shipped in v0.2.0; v0.2.1–v0.2.2 patched the Docker image; v0.2.3 stabilised the Wallapop adapter against the SPA's current production traffic (v3 `/search/section` endpoint, browser-TLS impersonation via `curl_cffi`, transparent JWT refresh, reserved-listing routing); v0.3.0 wired the Phase 2 autonomous-purchase loop end-to-end (BuyOrchestrator composed with all collaborators, marketplace-dispatched checkout), the Telegram callback listener, an in-cycle reserved-comp line on alerts, and a pretty-log toggle; v0.3.1 adds the clickable deep link to the listing on every alert (FR18); v0.3.2 ships the price-parser smoke-test in the image and runs it (startup + daily) so Phase 2 is actually armable in production; v0.3.3 makes every price ceiling shipping-aware — alert and buy gates plus receipt reconciliation compare the delivered buyer total (item + shipping + Wallapop's Protección fee), and alerts show the breakdown; v0.3.4 extends that total with an estimated flat import-charges buffer for eBay listings located outside the EU (`pricing.assumed_import_charges_eur`, default 3,63 €); v0.3.5 moves LLM evaluation to `gemini-2.5-flash` (Google retired 2.0-flash) and clips over-long takes instead of discarding valid verdicts; v0.4.0 adds live-updating alerts — dispatched alerts are watched (`alerts.watch_days`) and the original Telegram message edits itself on reserved flips and price drops, with a ping for big drops; v0.4.1 classifies a pre-buy 404 as `listing_gone` (fail-closed, no circuit hit); v0.4.2 reconciles by internal listing id after Wallapop dropped slug support; v0.4.3 restores the alert keyboard after every buy outcome; v0.4.4 is a tooling-only release carrying the passed rendering re-audit and the extended `dev emit-alert` catalog; v0.5.0 adds the operator-tapped Wallapop "hacer oferta" flow — a 💰 Ofertar button behind the same safety stack as Comprar, plus negotiable-band alerts for listings just over the ceiling (opt-in per entry via `salvager offer enable`). The daemon polls Wallapop + eBay.es, evaluates listings against the wishlist via Gemini Flash or Claude Haiku, dispatches Telegram alerts, and the Phase 2 autonomous-purchase loop runs behind the safety stack and the non-bypassable Telegram tap. **Not yet validated in production**: the operator's burn-in window is in progress; v1.0 promotion is gated on at least 2 weeks of continuous live-traffic operation + one completed Phase 2 purchase. Recommended pinned tag: `ghcr.io/ifuensan/salvager:0.5.0`. See [CHANGELOG.md](CHANGELOG.md) for release notes and [ROADMAP.md](ROADMAP.md) for the v1.0 path. --- @@ -26,7 +26,7 @@ A self-hosted personal agent for monitoring second-hand homelab parts. Watches y Prerequisites: Docker + docker-compose, a Telegram bot, a Google Gemini API key, an eBay developer account, a TinyFish API key (for the Phase 1 Wallapop fallback path and the Phase 2 buy flows), and a Wallapop / eBay.es account dedicated to the agent (see Legal disclaimer below). -The recommended image tag for new deployments is `ghcr.io/ifuensan/salvager:0.4.4` (pinned). `:latest` follows the newest release; pin to `:0.4.4` for reproducible deploys during the v1.0 burn-in window. +The recommended image tag for new deployments is `ghcr.io/ifuensan/salvager:0.5.0` (pinned). `:latest` follows the newest release; pin to `:0.5.0` for reproducible deploys during the v1.0 burn-in window. ```bash git clone https://github.com/ifuensan/salvager diff --git a/ROADMAP.md b/ROADMAP.md index 51663c5..ba14de1 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -14,7 +14,7 @@ This document names what's planned, what's deferred post-launch, what's permanen - ✓ **Epic 4** (Telegram Surface + CLI Operability) — alert renderer, callback handler, snooze flow, audit log + audit/health/explain CLI commands, operational alert variants. - ✓ **Epic 5** (Phase 2 Autonomous Purchase + Safety Stack) — Phase 2 listing/buy renderers, TinyFish browser adapter (Wallapop Pay + eBay checkout), cross-source + receipt reconciliation, per-purchase circuit breaker, daily synthetic smoke test, BuyOrchestrator, `phase2 enable/disable/status/smoke-test/reconcile` CLI, payment-rail enforcement CI lint, 90% critical-path coverage gate, release-audit tooling. -**Promotion to `v1.0.0` is gated on production burn-in**, not feature completion. Recommended pinned tag for burn-in: `ghcr.io/ifuensan/salvager:0.4.4`. See [`CHANGELOG.md`](CHANGELOG.md) for the release notes and the `[1.0.0] — future` placeholder spelling out the promotion criteria. +**Promotion to `v1.0.0` is gated on production burn-in**, not feature completion. Recommended pinned tag for burn-in: `ghcr.io/ifuensan/salvager:0.5.0`. See [`CHANGELOG.md`](CHANGELOG.md) for the release notes and the `[1.0.0] — future` placeholder spelling out the promotion criteria. The full epic + story breakdown lives in [`_bmad-output/planning-artifacts/epics.md`](_bmad-output/planning-artifacts/epics.md). The release-gate audit artefact lives in [`docs/release-audits/v1.0/SUMMARY.md`](docs/release-audits/v1.0/SUMMARY.md). @@ -26,7 +26,7 @@ The v0.2.x line ships all Phase 1 + Phase 2 code. v1.0.0 is gated on production **Promotion criteria** (informal; tightened if reality requires it): -1. ≥ 2 weeks of the current stable image (`:0.4.4` recommended) running continuously against live Wallapop + eBay.es traffic without unhandled crashes. +1. ≥ 2 weeks of the current stable image (`:0.5.0` recommended) running continuously against live Wallapop + eBay.es traffic without unhandled crashes. 2. ≥ 1 Phase 2 purchase completed end-to-end (or one verified Phase 2 abort with the safety stack engaging as designed). Counts as "the autonomous-buy path got exercised against the real world, not just synthetic tests". 3. No critical rendering regression surfaced between v0.2.x and the v1.0.0 candidate (re-audit if `domain/alert.py` or the styling layer changes). 4. **OQ3** — measured per-purchase TinyFish Browser cost (NFR-C2 cap is ≤ €1.00). v0.2.x is when this number first appears empirically; v1.0.0 confirms it. diff --git a/pyproject.toml b/pyproject.toml index 14abb19..8ae748c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "salvager" -version = "0.4.4" +version = "0.5.0" description = "Self-hosted personal agent for second-hand homelab parts on Wallapop and eBay.es." authors = [ { name = "ifuensan", email = "ivanfs@b4os.dev" } diff --git a/uv.lock b/uv.lock index a65df82..5f239c7 100644 --- a/uv.lock +++ b/uv.lock @@ -1178,7 +1178,7 @@ wheels = [ [[package]] name = "salvager" -version = "0.4.4" +version = "0.5.0" source = { editable = "." } dependencies = [ { name = "anthropic" },