Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <ref> [-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
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Align the v1.0 promotion gate with ROADMAP.md.

Line 13 requires one completed Phase 2 purchase, but ROADMAP.md Lines 29-30 also accept one verified Phase 2 abort. Use the same criterion in both documents to avoid inconsistent release decisions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` at line 13, Update the v1.0 promotion-gate statement in the README
status paragraph to accept either one completed Phase 2 purchase or one verified
Phase 2 abort, matching the criterion defined in ROADMAP.md while preserving the
existing two-week continuous live-traffic requirement.


---

Expand All @@ -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
Expand Down
4 changes: 2 additions & 2 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Update the stale roadmap status alongside the new burn-in tag.

This line now promotes 0.5.0, while the surrounding roadmap still identifies v0.2.3 as the current state and describes v0.2.x as containing all Phase 2 code. Refresh those status references so the roadmap consistently represents the 0.5.0 release.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ROADMAP.md` at line 17, Update the surrounding ROADMAP.md status references
to identify v0.5.0 as the current release and replace the outdated v0.2.x Phase
2 description with wording consistent with the 0.5.0 release. Keep the existing
production burn-in and promotion criteria unchanged.


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).

Expand All @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -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" }
Expand Down
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading