Release b7a8d38380c5
Source: b7a8d38380c53caf99c35d2507be8071076870be
Documentation fingerprint: 4343c3d4332f709f5d4758310e4563133183040803d7dddd8aa8ab35dfa47b36
b7a8d38 Apply private travel radius to all pickup offers (#5)a46ec25 Require collection readiness and reject ambiguous or expired offers (#4)5dee6a3 Harden collection, recovery, dependency health and versioned documentation00442dd Enforce local radius, discovery filters, and product variant boundariesf7ad08c Build PriceDip research, tracking, owner access, and release infrastructureThe following guides are preserved for this exact application release.
Using PriceDip
Section titled “Using PriceDip”Track a product
Section titled “Track a product”Sign in with Google, create a watchlist, paste an HTTPS listing URL, choose its condition, and set a USD item-price target. PriceDip verifies the listing and seller before recording observations. Watchlists can be public or private. Account-specific ZIP code and radius remain private.
The initial release supports 25 active products per account. Edit the target or pause monitoring from product details. Prices are checked approximately hourly; provider blocks or retries can delay observations. History begins with the first verified observation.
Research and discovery
Section titled “Research and discovery”Search a product name or model. Local discovery requires your own ZIP code and radius (1–500 miles). Direct search links remain available when search engines block automatic retrieval. Open a tracked product and request Qwen research; the brief is queued and cites retrieved sources. Reload the product after processing.
Alerts
Section titled “Alerts”An available, eligible fixed-price USD offer below the target generates an event, including on the initial check. One event is generated until the price rises back to the target or above. Changing a target rearms eligibility. Email delivery is retried using an idempotency key. Shipping and taxes are not part of the target; shipping is shown when known. Auctions, membership pricing, and unverified offers are excluded.
Public preview
Section titled “Public preview”Visitors can browse published products and events. They cannot search externally, generate research, change targets, or read location/account settings. The sample showcase never sends alerts.
Discovery filters support retailer, condition, and maximum item price. Local tracking requires configured account location and rejects listings outside the selected radius. Paused products can be reactivated only within the active-product limit.
Discovery accepts product names, model numbers, and exact supported HTTPS listing URLs. Filter by retailer, condition, seller name, maximum item price, and delivery or pickup. Unknown delivery methods do not pass a delivery filter. Local searches use your private ZIP and radius from Settings; unverifiable locations are excluded. Shipping is shown separately when the source provides it.
Pickup offers from national retailers follow the same private ZIP/radius eligibility rules as local marketplaces. Selecting pickup requires a configured ZIP code; missing or out-of-range locations are excluded both during discovery and during monitoring before alerts. Delivery offers remain independent of travel radius.
Seller standards
Section titled “Seller standards”Eligibility rules
Section titled “Eligibility rules”Direct sales by Amazon, Walmart, Best Buy, Target, and verified manufacturer stores qualify when the source explicitly identifies the seller. A retailer domain alone is not sufficient evidence.
eBay requires at least 99% positive feedback and a feedback score of 100. Other marketplace sellers require attributable ratings of at least 4.5/5 from 20 reviews, or a business identity independently verified against an established retailer.
Missing seller identity or reputation fails closed. Qwen explains evidence but cannot override deterministic eligibility checks. A verified marker describes recorded evidence, not a guarantee.
Local marketplace listings also need a verifiable location within the account’s configured radius. Listings behind login or without reputation/location evidence may produce no qualifying results.
Rechecking
Section titled “Rechecking”Every recorded observation includes source evidence and timestamp. A new extraction rechecks eligibility before an alert. Different conditions and listing identities are not merged. Changed product titles halt automatic observation until the owner adds the new listing.
Ambiguous structured data containing multiple product variants is rejected. Marketplace ratings must use a five-point scale; unrecognized scales do not qualify.
The manufacturer-store registry includes Sony Electronics, Apple, Dyson, and Samsung. These official storefront domains are explicitly classified as direct sellers; arbitrary new domains are not admitted.
Expired advertised prices are marked unavailable. eBay offers that combine auction and fixed-price buying options are excluded from ordinary alerts. Ambiguous or missing eBay condition descriptions remain unknown; they are not silently treated as used or new.
Architecture
Section titled “Architecture”Components
Section titled “Components”React/Vite renders the dark amber interface. Fastify serves the frontend and API on loopback port 4350. A separate Node worker performs hourly collection, Qwen requests, and email delivery. Private SearXNG listens on 4352. Ollama remains the existing local service; PriceDip selects qwen2.5:3b by default.
SQLite uses WAL, foreign keys, busy timeout, versioned migrations, and indexes. Accounts own watchlists. Products reference watchlists. Observations and research reference products. Jobs have durable leases; events and the email outbox are transactionally created with price observations. Worker restart reclaims expired leases.
Authentication
Section titled “Authentication”Observe acts as a narrow Google sign-in broker. PriceDip issues browser-bound state. Observe accepts only the fixed PriceDip destination and exact owner email, signs a 60-second audience-restricted assertion, and PriceDip consumes state once before issuing its own HttpOnly Secure session cookie. Each application retains independent sessions. Observe being unavailable does not stop monitoring or existing PriceDip sessions, but new sign-ins require it.
Evidence boundaries
Section titled “Evidence boundaries”Source adapters normalize observations. Seller policy, price parsing, variant consistency, and alert crossings run in deterministic code. Qwen receives bounded untrusted text and produces schema-validated research with source IDs. Invalid citations fail the job.
Fetch security
Section titled “Fetch security”HTTPS only, no URL credentials, public-address validation, pinned DNS for HTTP extraction, bounded redirects and response sizes. Browser extraction is isolated to the original origin with pinned resolution and bounded time. Private source URLs are rejected.
Data retention
Section titled “Data retention”Verified observations and events persist. Completed job records expire after seven days; active jobs and failures persist until resolved. Chart responses are bounded without deleting stored observations. Secrets are external runtime configuration.
Browser extraction blocks all WebSocket connections as well as requests outside the original origin. Product dialogs trap keyboard focus and restore it when closed. The System status link opens the dedicated PriceDip page in Observe.
Collection jobs take priority over research jobs. Research is cached for 24 hours. Both fresh source extraction and local ZIP/radius eligibility are rechecked during hourly monitoring. Exact listing identifiers, model, color, size, and condition are kept stable; ambiguous offer collections are rejected. Seller evidence must be fresh within 15 minutes when recording an alert. Public chart samples are selected in SQLite, while original observations remain intact. The overview supports bounded limit/offset pagination.
Development and testing
Section titled “Development and testing”Use Node 22.22 or newer. Run npm ci, npm test, and npm run build in the app repository. Set DB_PATH to an isolated development database. Start the compiled API with npm start and frontend with npm run dev. The Vite proxy forwards API/auth calls to loopback port 4350. Never use production cookies in tests.
Tests cover seller boundaries, SSRF exclusions, strict price comparisons, observations, deduplicated alerts, and lease recovery. Browser verification covers public views, research, responsive layouts, and owner controls using isolated fixtures.
Documentation contract
Section titled “Documentation contract”Documentation is canonical in pricedip-wiki. Update affected guides for each behavioral/configuration/dependency change, then run node scripts/docs.mjs record ../pricedip-wiki from the tracked application checkout. This records a reviewed source fingerprint; never use it to conceal stale explanations.
The check mode rejects a different source tree. Release mode generates API/configuration references and a commit-linked release page only after the contract passes. PRs declare documentation impact and include the companion wiki change. Untrusted PR jobs have no deployment secrets.
Pull requests must declare exactly one documentation impact and link a companion pricedip-wiki commit or pull request. The required validation check enforces this declaration, source fingerprints, tests, build, and a high-severity production dependency audit. Direct dependencies and Action revisions are pinned; Dependabot proposals must pass the same documentation gate.
Deployment and releases
Section titled “Deployment and releases”GitHub Actions validates tests, TypeScript, production build, and the exact documentation contract. The release job resolves the wiki revision, generates references, pushes wiki publication, waits for the published release manifest, then deploys an immutable application artifact through the existing Cloudflare SSH route.
The production host runs pricedip.service, pricedip-worker.service, and pricedip-search.service. Persistent data is stored under /var/lib/pricedip. Runtime secrets live in /etc/pricedip/runtime.env. The application release lives beneath /opt/pricedip/releases and current is a symlink.
A server-side lock serializes deployment. Activation runs only after an HTTP health check; failed activation restores the previous symlink and services. Database migrations must remain compatible with the previous release; destructive changes require a separate migration/recovery plan.
The Cloudflare tunnel publishes only the web service. Search, Ollama, metrics, and SQLite are not exposed publicly. Changes to shared host infrastructure must use its existing repository and wiki workflow.
Dependency maintenance
Section titled “Dependency maintenance”Dependabot opens update PRs. Lockfiles and pinned Actions support repeatable builds. Run behavior and deployment tests before merging. Production secrets must never enter pull_request jobs.
Documentation publication
Section titled “Documentation publication”The Starlight wiki runs on GitHub Pages. The app source commit is available in the wiki release manifest and app health. A failed or mismatched wiki publication blocks application deployment.
Each release page preserves a complete snapshot of the reviewed guides and generated API/configuration references. The application Documentation link opens its exact release. The commit ledger covers the complete repository history. Deployment and rollback records retain both application and wiki commit IDs in the private database.
Application main requires pull requests, the validate check, resolved conversations, and linear history. Production jobs are restricted to protected branches. Wiki main rejects force pushes and deletion; the restricted release key can append validated documentation commits. Activation requires authenticated readiness for the expected application commit, database access, and a fresh worker heartbeat. Readiness returns HTTP 503 when collection is not ready, independently of the minimal public availability endpoint.
Observe integration
Section titled “Observe integration”Observe registers PriceDip and PriceDip Wiki as separate applications. The app navigation links to Observe; the portal links to both sites and repositories.
The existing minute collector fetches private PriceDip Prometheus metrics and forwards them through Alloy/Grafana. Dedicated dashboards cover availability, requests, errors, memory, worker heartbeat, queue age, stale products, collection outcomes, Qwen duration/errors, email delivery, backup age, restore evidence, and docs alignment.
Telemetry warns at three minutes and fails at five. Tracked products with previous successful observations become stale after two hours. A blocked provider is a data-quality limitation; it does not mean the web service is down. Missing observations and missing metrics do not count as healthy.
Public views contain aggregate operational evidence. Logs, account settings, ZIP codes, query text, private delivery details, and secrets are excluded. Existing owner access controls diagnostics. Operational incidents use Observe’s existing mechanisms; product-price emails are independent.
PriceDip does not depend on Observe for collection, research, or existing sessions. New Google sign-ins require the broker. Documentation synchronization failure blocks releases and remains visible through release evidence.
Qwen model availability and SearXNG health are checked every minute. These checks describe dependency reachability; retailer accessibility is measured by individual collection outcomes. Never infer verified offer coverage from a healthy search service. Source counters use only fixed retailer IDs, with no product names, URLs, or location labels.
Observe also tracks the latest main-branch application validation, wiki publication, and daily reconciliation workflows. GitHub evidence is refreshed every 15 minutes; missing or stale evidence is unknown. Wiki publication runs appear in deployment history. The daily fleet inspection imports PriceDip operational findings into its existing issue and incident workflow.
Backup and recovery
Section titled “Backup and recovery”The backup timer creates a consistent SQLite snapshot daily and records a timestamp only after a successful integrity check. It restores the snapshot into a separate verification database and validates integrity and required tables; this does not modify the active database. Backups remain in the protected host backup directory and are included in existing host backup coverage.
Before activation, create a fresh snapshot. To roll back application code, restore the previous current symlink and restart the web/worker services. Keep schema changes additive so this remains safe.
For data restoration, stop the worker and web service, preserve the current database and WAL files, verify the desired backup in isolation, install it under the service account, then start services and run health checks. Restore never runs automatically over current data.
The recovery test records evidence separately from backup creation. Observe distinguishes fresh backups from tested recovery. Preserve snapshots before upgrades and keep at least 14 daily backups.
Failure drills
Section titled “Failure drills”Use an isolated test database for target crossings and email outbox checks. Stop/restart only PriceDip’s worker to verify heartbeat degradation and lease recovery. Simulate provider and Qwen errors using fixtures. Do not disrupt shared Ollama or Observe to test PriceDip.
Run the current release’s scripts/rollback.sh as root to activate the prior immutable release. This records the transition without reverting backward-compatible database additions. Run it again to return to the newer release after verification. Daily snapshots are opened as independent restored databases, integrity-checked, and queried before restoration evidence is recorded.
Coverage and limitations
Section titled “Coverage and limitations”The service uses no paid API or AI subscriptions. Automatic source access is best effort; hourly scheduling cannot guarantee that a retailer will permit retrieval.
Amazon, Walmart, Best Buy, and Target require explicit seller identity and a single exact offer. Marketplace merchant attribution may not be present in structured data. eBay API requires configured production credentials and any required approval; without it, public extraction may not expose reputation evidence. Craigslist frequently lacks attributable reputation. Facebook Marketplace and OfferUp can require login. These situations are excluded, not silently trusted.
Local radius is enforced only when the source supplies verifiable location data. ZIP centroids provide approximate distance, not driving distance. Missing location is not assumed nearby.
Qwen is a local research aid. Inaccessible reviews or specifications are reported as missing; AI text is never used as observed price data. Search-engine snippets may be stale; offers are extracted from their source.
Email is bounded by free service allowances. Failed deliveries remain queued. Alerts compare item prices before shipping and tax.
Sample products, research, charts, and events are explicitly illustrative. They never populate real watchlists or monitoring metrics.
PriceDip reserves a conservative email budget of 30 successful deliveries per day and 500 per rolling 31 days by default. The Resend account is shared: provider quota rejection leaves messages pending with backoff. The sender uses the existing verified nutsnews.com domain; this does not change the PriceDip website domain.
eBay URLs with a var parameter are currently excluded because the adapter cannot yet verify that specific variation. It never substitutes the default item for the requested variant.
API reference
Section titled “API reference”Generated from application commit b7a8d38380c53caf99c35d2507be8071076870be.
| Method | Route | Access |
|---|---|---|
| GET | /healthz | Public / authentication flow |
| GET | /api/session | Public / authentication flow |
| GET | /auth/login | Public / authentication flow |
| GET | /auth/callback | Public / authentication flow |
| POST | /api/owner/logout | Owner session + same-origin writes |
| GET | /api/public/overview | Public / authentication flow |
| GET | /api/public/products/:id | Public / authentication flow |
| GET | /api/owner/settings | Owner session + same-origin writes |
| PUT | /api/owner/settings | Owner session + same-origin writes |
| POST | /api/owner/watchlists | Owner session + same-origin writes |
| GET | /api/owner/products | Owner session + same-origin writes |
| POST | /api/owner/search | Owner session + same-origin writes |
| POST | /api/owner/products | Owner session + same-origin writes |
| PATCH | /api/owner/products/:id | Owner session + same-origin writes |
| POST | /api/owner/products/:id/:action | Owner session + same-origin writes |
| GET | /internal/metrics | Private bearer token |
| GET | /internal/readyz | Private bearer token |
Configuration reference
Section titled “Configuration reference”| Variable | Default or requirement |
|---|---|
APP_ORIGIN | https://pricedip.ramideltoro.com |
PORT | 4350 |
DB_PATH | /var/lib/pricedip/pricedip.sqlite |
AUTH_BRIDGE_SECRET | Secret / optional; configure privately |
METRICS_TOKEN | Secret / optional; configure privately |
SEARXNG_URL | http://127.0.0.1:4352 |
OLLAMA_URL | http://127.0.0.1:11434 |
QWEN_MODEL | qwen2.5:3b |
BROWSER_EXTRACTION | true |
PLAYWRIGHT_BROWSERS_PATH | /opt/pricedip/browsers |
RESEND_API_KEY | Secret / optional; configure privately |
EMAIL_FROM | Secret / optional; configure privately |
EBAY_CLIENT_ID | Secret / optional; configure privately |
EBAY_CLIENT_SECRET | Secret / optional; configure privately |
EMAIL_DAILY_LIMIT | 30 |
EMAIL_MONTHLY_LIMIT | 500 |