From 840e1c4359d7147af2e566ac8d88fa216702ea1c Mon Sep 17 00:00:00 2001 From: type-two Date: Tue, 7 Jul 2026 21:50:46 +1000 Subject: [PATCH] =?UTF-8?q?refactor:=20wowplatter=5Fv1=20=E2=86=92=20store?= =?UTF-8?q?god=5Fv1;=20mount=20/storegod/v1=20canonical=20+=20/wowplatter/?= =?UTF-8?q?v1=20legacy=20alias?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .DS_Store | Bin 0 -> 6148 bytes REVIEW_BRIEF.md | 184 ++++++++++++++++++ app/main.py | 5 +- app/{wowplatter_v1.py => storegod_v1.py} | 9 +- .../Chart.csv | 86 ++++++++ .../Critical issues.csv | 14 ++ .../Metadata.csv | 2 + .../Non-critical issues.csv | 2 + 8 files changed, 296 insertions(+), 6 deletions(-) create mode 100644 .DS_Store create mode 100644 REVIEW_BRIEF.md rename app/{wowplatter_v1.py => storegod_v1.py} (91%) create mode 100644 monsterrobot.party-Coverage-2026-07-06/Chart.csv create mode 100644 monsterrobot.party-Coverage-2026-07-06/Critical issues.csv create mode 100644 monsterrobot.party-Coverage-2026-07-06/Metadata.csv create mode 100644 monsterrobot.party-Coverage-2026-07-06/Non-critical issues.csv diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..9e730d318d93ab21b40d7d8d1922cdd8f9d398f7 GIT binary patch literal 6148 zcmeHKyG{c^3>-s>NHi%ax1tLQ6iK@Z;S2Zy5`t&~hmwFqcOAdY_yOW{p``#jvS-)l z+0{*PjseL0WqkpR01WAhc-PQ3eXicKqsR!6&NKG7$Ckeat6uftgmTaDNX8R(9e%^z zIi9iPd3(HJj4M{X+6_xQ5XZR0gg)hJf|kP3V$px=i=SFC}pqkTFUYy=<%=Kn4!cd0-s@TU}z z!Qy5y=PN~T?Y*4#+Csmje;RA8oWWW#(ONM#+KNxV)fHXyc@1nGjn2H$iTOuBbxBJF HenWu|m=_(` literal 0 HcmV?d00001 diff --git a/REVIEW_BRIEF.md b/REVIEW_BRIEF.md new file mode 100644 index 0000000..5ad27a7 --- /dev/null +++ b/REVIEW_BRIEF.md @@ -0,0 +1,184 @@ +# 🌌 RECORDGOD β€” Review Brief & Action List +*the flagship πŸ’Ώ Β· left for you by Claude, 2026-07-05. Part of the GODVERSE major review β€” the full cross-repo report is the artifact in chat. This one's just your repo, homie: what it is, where it's at, and what's worth doing, ranked. Nothing here is scary; it's a to-do list with receipts.* + +**Findings in this repo:** 3πŸ”΄ 8🟠 4🟑 5πŸ”΅ Β· total 20 + +> _Verification tags:_ `βœ“ verified` = two adversarial skeptics tried to disprove it and couldn't (or I re-checked it by hand). `Β· unverified` = the review found it with a real file citation, but the verify pass ran out of session before double-checking β€” very likely real, just confirm before you act. + +## What this is +The record store β€” real customers, real orders, real money, real PII. This is the one where a mistake has a human on the other end. + +**Live/deployed:** Docker on the VPS (root@100.94.195.115): app container from Dockerfile (uvicorn :8010) + recordgod-db Postgres container, refreshed from the M3 Ultra by refresh.sh (ssh-piped pg_dump, vault table preserved). Public at robotmonster.party β€” 3D store live at robotmonster.party/store, new admin at robotmonster.party/admin (a June commit added a legacy-panel banner pointing WowPlatter users there). recordgod.com appears in UAs/docs as intended future base URL. WowPlatter/WordPress still runs the live shop floor and checkout; live WP MariaDB is still source-of-truth (refresh.sh clean-rebuilds local tables, documented as correct "while live WP is source-of-truth"). + +**Stack:** Python 3.12 + FastAPI + async SQLAlchemy/asyncpg on Postgres (pg_trgm search, jsonb layouts); vanilla-JS/HTML admin+storefront pages (site/); Three.js ES-module 3D store (webstore/); PHP WordPress bridge plugin (wp-bridge/); pymysql one-way MariaDBβ†’Postgres sync scripts; Docker (python:3.12-slim, uvicorn :8010). + +## πŸ“ Where it's at right now +RECORDGOD is well past scaffold: main is clean and pushed, and the app is deployed in Docker on the VPS behind robotmonster.party, with the Three.js 3D store live at /store and the new admin at /admin (WowPlatter's legacy panel now banners users to it). Working today: the FastAPI back-office (inventory, POS with split payments/holds/trade-ins, staff+timeclock, intake with condition-based price suggestions, customer CRM with non-destructive Woo sync, buylist scarcity ranking via DealGod /api/supply), the public storefront pages, sitemap/redirect SEO plumbing, and the ultraβ†’VPS refresh pipeline. Recently landed (Jul 2-3): the StoreGod modules/packs system, provider-agnostic payments (mock proven, Square creds set but web SDK id missing, Stripe/PayPal untested), Discogs marketplace cross-listing, and the THRIFTGOD crate-flip ported into the 3D store. Half-finished: live WordPress/Woo still owns checkout and sale-state (wp-bridge is the adapter, cutover incomplete), customer accounts/login on the RecordGod side are new, and the storefront page-editor plan is design-stage. Security housekeeping is mid-flight: two purge commits cleaned PII from the tree, but the RareWaves JWT dumps are still in pushed git history, and the fresh customers.sql dump in the repo root is covered by neither .gitignore nor .dockerignore β€” the single most urgent fix in the repo. + +## πŸ”₯ Do first β€” the 11 that can actually bite +Knock these down before the medium/low stuff. Each is a real failure path, not a style nit. + +### πŸ”΄ CRITICAL β€” Fresh customer PII dumps (customers.sql, site-update.sql) are baked into the deployed Docker image and customers.sql is one `git add .` from being committed +`.dockerignore:1` Β· *data-exposure / build correctness* Β· `βœ“ verified` + +customers.sql (610KB, Jul 3) sits untracked in the repo root but is covered by NEITHER .gitignore (which only lists site-update.sql and *-dump.sql) NOR .dockerignore (which has no *.sql entries at all). site-update.sql (6.8MB full live-site MariaDB export with customer/order PII) is gitignored but also missing from .dockerignore. Dockerfile line 5 is `COPY . .`, so every image build ships both PII dumps to /app/customers.sql and /app/site-update.sql inside the container running publicly at robotmonster.party. Failure scenario: (a) any image push/leak or container filesystem access exposes the full customer DB; (b) the next `git add .`/`git commit -a` permanently commits customers.sql to history, repeating the RareWaves incident. + +**β†’ Fix:** Add `customers.sql` (or `*.sql` with `!schema.sql`) to .gitignore, and add `customers.sql` + `site-update.sql` + `*-dump.sql` to .dockerignore; rebuild and redeploy the image to purge the copies already baked in. + +### πŸ”΄ CRITICAL β€” Customer PII + WordPress password-hash dump (customers.sql) is un-ignored and baked into the deployed Docker image +`customers.sql:1` Β· *pii_exposure* Β· `βœ“ verified` + +customers.sql (610KB, phpMyAdmin dump generated 2026-07-03) sits in the repo root and is matched by NEITHER .gitignore (which only lists site-update.sql and *-dump.sql) NOR .dockerignore (which excludes *.md, *.xlsx, rarw*.txt but not *.sql). It contains wp_rmp_users with user_login/user_email/user_pass (WordPress phpass password hashes), plus wp_rmp_disc_sales_customers / wp_rmp_usermeta with real customer first/last names, emails, phones and full postal addresses (15 INSERT blocks). Because the Dockerfile does `COPY . .`, this file is copied verbatim into the image that is deployed to the public VPS behind robotmonster.party β€” anyone who can pull/inspect the image (or exec into the running container) obtains the entire customer table and reusable WP credential hashes. Equally, since it is untracked-but-not-ignored, a single `git add .`/`git add -A` will stage it and push customer PII into the shared origin history permanently. + +**β†’ Fix:** Delete customers.sql from the working tree now (shred it), add `customers.sql` (or `*.sql` with a `!schema.sql` exception) to BOTH .gitignore and .dockerignore, and rebuild/redeploy the image so the layer containing it is gone. Confirm `git ls-files` and the built image no longer contain it. + +### πŸ”΄ CRITICAL β€” Customer PII dumps (customers.sql, site-update.sql) get baked into the deployed Docker image +`.dockerignore:14` Β· *data hygiene / PII* Β· `βœ“ verified` + +customers.sql (600KB, fresh Jul 3: wp_rmp_disc_sales_customers + wp_rmp_disc_user with real names, emails, phones, full street addresses) sits untracked in the repo root and is covered by NEITHER .gitignore (which only lists site-update.sql and *-dump.sql) NOR .dockerignore. site-update.sql (6.5MB full live-site export with customer/order PII) is gitignored but also missing from .dockerignore. Dockerfile line 5 is 'COPY . .', so the next image build ships ~7MB of customer PII inside the container running on the public VPS (robotmonster.party) β€” and one 'git add -A' commits customers.sql, repeating the exact RareWaves incident that already forced purge commit 9e3c132. + +**β†’ Fix:** Append 'customers.sql' to .gitignore and both 'customers.sql' + 'site-update.sql' (or a blanket '*.sql' β€” schema.sql isn't needed in the image; startup DDL is inline in app/main.py) to .dockerignore, then rebuild/redeploy the image. Better long-term: keep live-site MariaDB dumps outside the repo root entirely (same pattern as moving discogs_monthly_update.sh out of ~/Documents). + +### 🟠 HIGH β€” RareWaves account-JWT and supplier dumps are still fully recoverable from pushed git history +`.gitignore:8` Β· *data-exposure* Β· `βœ“ verified` + +The purge commit 9e3c132 removed the files from the working tree only. Commit 5551963 (an ancestor of origin/main, confirmed pushed β€” main is in sync with origin at cfd6b54) still contains rarwacct.txt (live RareWaves buyer JWT), rarworder*.txt, rarwwish*.txt, inertia.txt and 'Inertia Warner Stock List - 26062026.xlsx'. Failure scenario: anyone with read access to the Gitea remote (ssh://100.71.119.27:222/monster/RECORDGOD.git) runs `git log --all --diff-filter=A` and checks out the token and supplier PII; the JWT may still be valid. + +**β†’ Fix:** Rewrite history (git filter-repo --invert-paths on the dump paths), force-push, have Gitea GC, and rotate/invalidate the RareWaves session token. + +### 🟠 HIGH β€” RareWaves account JWT + supplier PII scrape dumps still recoverable from pushed git history +`rarwacct.txt:1` Β· *secret_and_pii_in_history* Β· `βœ“ verified` + +Commit 9e3c132 ('purge distro raw dumps ... from git + image') only removed the files from the working tree; the blobs remain fully retrievable from the pushed history. `git show 926468a:rarwacct.txt` returns a live RareWaves (rarewaves.myshopify.com) buyer_flags JWT in a URL (iss=rarewaves.myshopify.com, nbf 2026-06-27, exp 2026-07-04), and 926468a:rarworder.txt / rarwwish1.txt / rarwwish2.txt / rarworder2.txt (150KB–800KB each) carry supplier order/wishlist scrapes with buyer PII. History is pushed to origin (ssh://100.71.119.27:222/monster/RECORDGOD.git), so anyone with repo access β€” or anyone who ever gains it if the Gitea host is exposed β€” can extract the account token and PII. The JWT is short-lived and expiring, but tokens of this type get reissued into the same scrape files, and the supplier customer PII is sensitive indefinitely. + +**β†’ Fix:** Rotate/revoke the RareWaves session and treat the token as compromised, then rewrite history (git filter-repo --path rarwacct.txt --path rarworder.txt --path rarworder2.txt --path rarwwish1.txt --path rarwwish2.txt --invert-paths --path inertia.txt) and force-push, and have all clones re-clone. The .gitignore already blocks re-adding them going forward. + +### 🟠 HIGH β€” RareWaves live-account JWTs and supplier PII still sit in pushed git history +`.gitignore:9` Β· *data hygiene / secrets in history* Β· `βœ“ verified` + +Purge commit 9e3c132 only deleted the files from the tree. Commits 926468a and ef37063 β€” both reachable from origin/main on the Gitea remote (ssh://100.71.119.27:222/monster/RECORDGOD.git) β€” still carry the blobs: rarwacct.txt (363KB, contains live HS256 JWTs, verified 'eyJhbGciOiJIU...' present), rarworder.txt, rarworder2.txt, rarwwish1.txt, rarwwish2.txt, and 'Inertia Warner Stock List - 26062026.xlsx' (~3.5MB total, largest blobs in the repo). Any clone of the repo (VPS deploys, AI agent worktrees, a future collaborator) gets the buyer-account tokens and supplier PII. + +**β†’ Fix:** git filter-repo --path rarwacct.txt --path rarworder.txt --path rarworder2.txt --path rarwwish1.txt --path rarwwish2.txt --path 'Inertia Warner Stock List - 26062026.xlsx' --invert-paths, force-push both branches, gc/prune on the Gitea server, and rotate the RareWaves login regardless β€” treat the JWT as burned. + +### 🟠 HIGH β€” /shop/checkout charges the card before any atomic stock claim β€” concurrent buyers of the same single-copy record are BOTH charged +`app/shop_routes.py:447` Β· *race condition / money* Β· `Β· unverified` + +The in_stock check (line 431) is a plain read; the charge (line 447) awaits an external HTTP call for up to 30s; only afterwards is inventory marked sold and committed (lines 462-464). Two concurrent checkouts (or a checkout racing a POS create_sale, which also never re-checks in_stock before its UPDATE at sales_routes.py:111) both pass the read, both cards are charged, both orders commit β€” one paying customer gets a record that no longer exists, with no refund path in code. Conversely, if anything after the charge raises β€” e.g. the sale_number UNIQUE collision (sale_number is 'W'+date+4 hex chars = 65,536/day, ~7% daily collision odds at 100 orders/day, sales.sale_number is UNIQUE in schema.sql:64) β€” the transaction rolls back and returns 500: money taken, no order recorded anywhere. + +**β†’ Fix:** Claim stock first with `UPDATE inventory SET in_stock=false ... WHERE sku=ANY(:skus) AND in_stock` and verify rowcount == len(items) before charging; on charge failure, release the claim; wrap post-charge writes in try/except that auto-refunds or logs the payment_id for manual recovery; widen the sale_number token. + +### 🟠 HIGH β€” /shop/checkout trusts client qty on single-copy inventory β€” a cart with qty:3 (or the same SKU twice) charges for copies that don't exist +`app/shop_routes.py:432` Β· *money / input validation* Β· `Β· unverified` + +Every inventory row is one physical copy (POS, dig.js and RG_Cart all treat it as such; Woo products are sold_individually with stock 1). But the public checkout computes `subtotal += price * max(1, it.qty)` with no cap, and duplicate SKUs in the items list each pass the in_stock check independently. Failure scenario: POST /shop/checkout {items:[{sku:'MRP-X', qty:3}]} charges 3Γ— the price, records a sale_items row with qty 3, and marks the single copy sold β€” the customer paid for two records the store cannot ship. + +**β†’ Fix:** Clamp qty to 1 for single-copy SKUs (or to inventory.qty) and dedupe body.items by sku before pricing. + +### 🟠 HIGH β€” refresh.sh clean-rebuild pushes wipe live VPS data: POS sales, holds, staged intake, wantlist, staff shifts recorded since the last refresh are destroyed +`refresh.sh:23` Β· *data loss / state divergence* Β· `Β· unverified` + +Step 1 runs `migrate.py --truncate` (truncates inventory, sales, sale_items, customer, crate, discounts, settings on the ultra), then step 4 pipes `pg_dump --clean --if-exists` of the ENTIRE ultra recordgod DB into the live VPS database β€” dropping and recreating every table (not just the migrated ones: staff, staff_shift, wantlist, page, url_redirect, buylist, store_config too) with the ultra's stale copies. The app is deployed and taking real writes on the VPS (POS sales with holds/split payments, /shop/wantlist submissions, discogs_listing_id cross-refs, staff clock-ins). Failure scenario: run refresh.sh after a week of live trading β†’ every RecordGod-native sale, open layby, staged intake row and wantlist request on the VPS silently disappears; open holds vanish while customers still hold deposits. The header comment only acknowledges the intake half of this. Additionally `| grep -i error || true` swallows push failures, so a partial restore still prints 'done'. + +**β†’ Fix:** Stop using --clean full-DB push now that the VPS is authoritative for sales/intake: restrict the dump to the read-only mirror tables (disc_*, virtual_*, post_*) and convert migrate steps to UPSERTs, or refresh only into staging tables. + +### 🟠 HIGH β€” Woo bridge oversell: once minted, a Woo product never re-checks RecordGod stock or price, so records sold in-store/Discogs remain buyable on the live webstore +`wp-bridge/includes/class-rg-cart.php:17` Β· *state divergence / API contract* Β· `Β· unverified` + +ensure_product() consults /shop/item only when the SKU has no existing Woo product; after the first ?rg_add= mint, the early `if ($id) return $id;` skips all freshness checks. Nothing in the RecordGodβ†’Woo direction ever zeroes a minted product's stock. Failure scenario: customer A adds a record to their Woo cart (product minted, stock 1) and abandons it; the record then sells at the POS (or via Discogs sync) β€” RecordGod marks it sold, but the Woo product still shows stock 1; customer B clicks Add-to-cart days later, completes Woo checkout (which is the LIVE checkout today) and pays for a gone record. Price edits in RecordGod are likewise never reflected β€” the stale minted price is what gets charged. + +**β†’ Fix:** In handle_add()/ensure_product(), always re-fetch /shop/item (ttl 0) and refuse or update when in_stock is false or the price changed; optionally zero Woo stock from the woo-order/POS delist path. + +### 🟠 HIGH β€” RECORDGOD/app and STOREGOD/core are two live forks of the same store backend β€” money code duplicated byte-for-byte +`app/payments.py:1` Β· *cross-god duplication* Β· `Β· unverified` + +STOREGOD/core is a copy of RECORDGOD/app: payments.py, square.py, receipts.py, vault.py, db.py, mailer.py, layout_routes.py and collections_routes.py are byte-identical across the two repos, while the big route files have already drifted (shop_routes.py 514 vs 857 lines, navigator_routes.py 369 vs 739, dealgod.py/auth.py/packs.py diverging). Both are deployed products handling real payments and customer data. Concrete failure: a refund/rounding/auth bug fixed in RECORDGOD's payments.py or square.py silently persists in StoreGod (and vice versa) β€” the drift in dealgod.py shows this is already happening, not hypothetical. + +**β†’ Fix:** Pick a canonical home now, while 8 files are still identical: make STOREGOD/core an installable package (or vendored subtree) that RECORDGOD imports, per the stated 'StoreGod = base, recordgod = module' architecture. Minimum viable: a CI/cron diff-check that fails when the intended-identical modules (payments, square, receipts, vault, db, mailer) diverge. + + +## πŸ“‹ Backlog β€” medium & low (9) +Not verified individually (lower stakes). Triage as you're already in the area. + +### 🟑 Medium (4) +- **Order sync back to RecordGod fires only on Woo status 'completed' β€” paid orders leave items in_stock for hours/days** `Β· triage` + `wp-bridge/includes/class-rg-orders.php:8` Β· *state divergence* + RG_Orders hooks woocommerce_order_status_completed only. For physical goods Woo sets paid orders to 'processing'; 'completed' typically requires a manual staff action (fulfilment). Until then RecordGod never learns of the sale: the item stays in_stock in the POS, the 3D store crates, /shop/catalog and the Discogs cross-listing β€” the exact double-sell window the bridge exists to close. The failure note added on error ('retry from order actions') references an order action that is never registered, so failed posts are also never retried. + β†’ **Fix:** Hook woocommerce_order_status_processing (or woocommerce_payment_complete) in addition to completed, and register the retry order action the note promises. + +- **Web-checkout and Discogs sales are invisible to every /admin/report KPI: their status 'publish' is excluded by the _DONE filter** `Β· triage` + `app/admin_routes.py:815` Β· *wrong reporting / state divergence* + _DONE = "s.status IN ('completed','paid')" drives report_kpis, report_series, report_breakdown and report_top. But /shop/checkout inserts sales with status='publish' (shop_routes.py:454) and discogs_mp.sync_orders does the same (discogs_mp.py:187). Failure scenario: every online order and every imported Discogs order is silently missing from revenue, units, series and top-seller reports β€” the dashboard understates takings by the whole online channel while /sales/past shows the orders, so the numbers visibly disagree. + β†’ **Fix:** Either insert those sales as status='completed', or add 'publish' to _DONE (holds are already excluded via payment_status). + +- **Receipt math doesn't add up when line discounts are used: subtotal is stored net of line discounts while discount_amount includes them** `Β· triage` + `app/sales_routes.py:96` Β· *wrong money display / reporting* + create_sale stores subtotal = gross βˆ’ line_discounts but discount_amount = line_discounts + cart_discount. receipts.py then prints Subtotal, a single 'βˆ’Discount' line, and Total. Failure scenario: sale of $100 gross with $10 line discount and $5 cart discount β†’ receipt shows Subtotal $90, Discount βˆ’$15, Total $85; the visible arithmetic says $75. The customer-facing receipt (printed and emailed) is internally inconsistent whenever a line discount exists, and report_kpis' 'discounts' sum double-counts line discounts relative to the stored subtotal. + β†’ **Fix:** Store subtotal = gross (pre-discount) so subtotal βˆ’ discount_amount = pre-tax total, or print line and cart discounts separately. + +- **Server-side request forgery in /layout/import-brand URL fetch** `Β· triage` + `app/layout_routes.py:66` Β· *ssrf* + POST /layout/import-brand takes an arbitrary `url` from the request body, prepends https:// if missing, and fetches it server-side with follow_redirects=True and no host/scheme/IP allow-list (app/layout_routes.py:70-76). An authenticated staff user (or anyone who obtains a staff bearer token) can point it at internal-only targets from the VPS/container network β€” e.g. http://169.254.169.254/latest/meta-data/ (cloud metadata), http://127.0.0.1:8010/ or other internal services β€” turning the server into a proxy/port-scanner. Exfiltration is partly limited (the handler returns extracted theme-color/og:image/font, and og:image URLs are echoed back), and it requires require_token, which caps severity; but the fetch itself is unrestricted and follows redirects, so redirect-to-internal bypasses any naive front-end URL check. + β†’ **Fix:** Resolve the hostname and reject private/loopback/link-local/metadata IP ranges before fetching (and re-validate after each redirect, or disable redirects), restrict to http/https on public IPs, and set a small timeout + response-size cap. + +### πŸ”΅ Low (5) +- **discogs_condition() silently regrades full-format condition strings β€” 'Very Good (VG)' becomes a VG+ listing, 'Near Mint (NM or M-)' drops to VG+** `Β· triage` + `app/discogs_mp.py:34` Β· *wrong data / marketplace listing* + The fallback `_COND.get(k.split()[0] ...)` maps 'VERY GOOD (VG)' via 'VERY' β†’ default 'Very Good Plus (VG+)' (an upgrade β€” overstating condition to buyers), 'NEAR MINT (NM OR M-)' β†’ VG+ (a downgrade), and 'Good Plus (G+)' via 'GOOD' β†’ 'Good (G)'. The new UI writes short grades (site/admin.html CONDS), but inventory.condition is migrated verbatim from WowPlatter media_condition and general-inventory condition_desc; any row holding a full Discogs-style string that is cross-listed via /admin/discogs/list/{sku} goes up on the marketplace at the wrong grade. + β†’ **Fix:** Add the exact Discogs strings themselves as _COND keys (identity mappings) and match multi-word prefixes ('VERY GOOD PLUS', 'VERY GOOD', 'NEAR MINT', 'GOOD PLUS') before the first-word fallback. + +- **take_payment read-modify-write race loses layby payments, and neither it nor create_sale guards against stale/oversold state** `Β· triage` + `app/sales_routes.py:217` Β· *race condition / money* + take_payment reads amount_paid, adds in Python, and writes back with no row lock or `amount_paid = amount_paid + :x` β€” two simultaneous payments against the same hold (two counter tabs) both read the old value and one payment vanishes from the record even though the cash was taken. create_sale likewise never verifies items are still in_stock (its inventory UPDATE has no `AND in_stock` and rowcount is unchecked), so a stale POS search list rings up an already-sold SKU as a normal completed sale. + β†’ **Fix:** Use `UPDATE sales SET amount_paid = coalesce(amount_paid,0) + :amt ... RETURNING amount_paid, total`, and in create_sale require the inventory UPDATE (with AND in_stock) to affect len(items) rows. + +- **Stripe idempotency key sent as a URL query parameter instead of the Idempotency-Key header β€” retries will double-charge once Stripe is enabled** `Β· triage` + `app/payments.py:72` Β· *API contract / money* + Stripe's idempotency mechanism is the `Idempotency-Key` HTTP header; `params={'idempotency_key': ref}` puts it in the query string where the PaymentIntents API ignores it. The module is marked UNTESTED, but the interface is the point: as soon as a caller retries a timed-out charge with the same ref (the intended safety of passing ref through charge()), Stripe sees two independent PaymentIntents and charges the card twice. + β†’ **Fix:** Send it as headers={'Authorization': ..., 'Idempotency-Key': ref}. + +- **Public checkout can mint free 'paid' orders if STOREGOD_ALLOW_MOCK_PAY is left set in production** `Β· triage` + `app/payments.py:118` Β· *payment_bypass* + The unauthenticated public storefront checkout (POST /shop/checkout) accepts a `provider` and only rejects it if not in enabled_providers(db). enabled_providers returns 'mock' whenever env STOREGOD_ALLOW_MOCK_PAY=='1' (app/payments.py:118), and _mock_charge returns ok=True for any token except the literal 'mock-decline' (app/payments.py:29-33). The order is then written with payment_status='paid' and stock is marked sold (app/shop_routes.py:447-463). Given the mapping notes say mock is the proven/primary flow pre-launch, leaving this env var set when the storefront goes public lets any visitor place fully 'paid' orders (and depletes/ships stock) for free. It is correctly gated by an env var, so this is a deployment-config footgun rather than a code bug, but it is a public-internet, no-auth money path. + β†’ **Fix:** Ensure STOREGOD_ALLOW_MOCK_PAY is unset in the production compose/env before the storefront is public, and/or hard-block provider=='mock' in the /shop/checkout path regardless of the flag (keep mock only on the admin-authenticated /admin/pay virtual terminal). + +- **Crate-riffle mechanics forked between RECORDGOD/webstore/dig.js and OPSHOPGAME/web/dig.js** `Β· triage` + `webstore/dig.js:7` Β· *cross-god duplication* + The two dig.js files (~220 lines each) share the riffle physics, damped-cursor input, procedural fwip/thunk audio, stack/crate builders and tuned constants (~90% of the mechanics); only the panel/purchase flow differs (add-to-cart vs /api/buy). The port-back commit d0c91fa already proves the cost: THRIFTGOD's feel improvements had to be manually re-applied to RECORDGOD, and the next physics/feel fix must again be applied twice or the crates drift apart. + β†’ **Fix:** Either extract the shared mechanics (physics, audio, buildStack/buildCrate) into one module in 3GOD (the shared 3D depot) with the panel/buy behaviour injected as callbacks β€” or explicitly declare one file canonical in docs/DIG_FLIP_HANDOVER.md and stop bidirectional porting. For ~150 shared lines, the declare-and-document option is the lazy-correct one. + + +## 🧹 Git & hygiene +**Current tree state:** Branch main, in sync with origin/main (self-hosted Gitea-style remote ssh://100.71.119.27:222/monster/RECORDGOD.git); feat/virtual-store-port fully merged. Working tree clean EXCEPT one untracked file: customers.sql (611KB phpMyAdmin dump of wp_rmp_disc_sales_customers from the live wp_rmp_db, generated Jul 3 β€” customer PII). It was never committed (clean history), but it does NOT match any .gitignore pattern (the recent cfd6b54 gitignore covers site-update.sql and *-dump.sql only), so it is one `git add .` away from being pushed; it is also NOT in .dockerignore, so `COPY . .` would bake it (and the 6.8MB site-update.sql, which .dockerignore also misses) into the next Docker image. No uncommitted code work at risk β€” the dirty file is a data dump that should be ignored/relocated, not committed. + +**Flagged during mapping:** +- PII dump exposure gap: customers.sql (live customer table dump, Jul 3) is untracked but NOT gitignored and NOT dockerignored β€” one careless `git add .` or `docker build` ships customer PII. site-update.sql is gitignored but also missing from .dockerignore. +- Known-unscubbed history: commit 9e3c132 purged the RareWaves/Inertia dumps (supplier PII + a LIVE RareWaves buyer JWT) from the tree, but its own message notes they remain reachable in history at ef37063/926468a β€” and that history is pushed to the origin remote. The JWT should be rotated or history rewritten. +- ingest_raw/ still holds the raw supplier files with the JWT on local disk (intentionally local-only, but the token itself may still be live). +- refresh.sh is destructive by design (clean-rebuild of migrated tables); the script itself warns that once staged intake rows are entered via the RecordGod dash, it must switch to UPSERT or refreshes will clobber real work β€” a time bomb as dash usage grows. +- Payments: stripe/paypal providers are coded to spec but explicitly UNTESTED; square is live-creds-ready but lacks a web application_id for browser charges β€” half-armed payment stack. +- Ops-by-ssh-as-root: all data sync pipes SQL as root@VPS into docker exec psql β€” works, but no auth/audit seam and errors are grep-swallowed (`| grep -i error || true`). + +## πŸ”— Don't break these β€” how this god wires into the verse +Change these contracts and something downstream breaks. Grep the other repo before you touch them. + +- DealGod: app/dealgod.py client β†’ https://api.dealgod.pro (X-Api-Key) for market value + /api/supply scarcity ranking (buylist); packs/key scope gating also lives DealGod-side +- PRICEGOD: consumes RecordGod's wowplatter/v1 contract routes (app/wowplatter_v1.py); undefined routes deliberately 404 = 'pending' +- WowPlatter / WordPress+WooCommerce: source MariaDB clone at /opt/homebrew/var/www/monsterrobot.localsite (pymysql over local socket) read by migrate.py/disc_sync.py/customer_woo_sync.py; wp-bridge plugin calls RecordGod /shop/* + posts completed Woo orders back to /shop/woo-order (X-Bridge-Key) +- VPS: root@100.94.195.115 (tailscale) β€” docker containers recordgod (app :8010) + recordgod-db (Postgres); data pushed by ssh-piped pg_dump/psql +- 3GOD/THRIFTGOD lineage: webstore/dig.js crate-flip ported from THRIFTGOD; cyclorama + decals ported from WowPlatter 3D +- SCANGOD: bridge brief/reply docs (docs/SCANGOD_BRIDGE_*.md) define a scanner integration seam +- Local mirrors on M3 Ultra: discogs_full Postgres (build_disc_cache.py) for enrichment; Discogs marketplace API (app/discogs_mp.py) for cross-channel listings +- Square (app/square.py, prod creds in vault) for payments; Apple/YouTube preview enrichment via audio_sync.py + +## πŸšͺ Entry points (where to start reading) +- app.main:app β€” uvicorn :8010 (Dockerfile CMD); mounts /store (3D webstore) and / (site pages), pretty routes /shop /admin /dash /pos /builder /kiosk /release/{id}, startup runs ~90 idempotent DDL statements +- refresh.sh β€” the data pipeline: MariaDB clone (of live WP site) β†’ local recordgod Postgres β†’ pg_dump piped over ssh into the VPS docker recordgod-db, then customer_woo_sync.py (non-destructive Wooβ†’CRM upsert) +- disc_sync.py / customer_woo_sync.py / audio_sync.py / shipping_sync.py β€” stdout-SQL CLIs piped to ssh root@100.94.195.115 +- wp-bridge/recordgod-bridge.php β€” WordPress plugin installed on the Woo site +- test_smoke.py β€” infra-free self-check + +## πŸ’¬ The bottom line +RECORDGOD is the most *finished* and the most *exposed*. The code is good; the risk is entirely data-handling: customer dumps that shouldn't be near git or Docker, and old secrets living in history. Fix those five things and this repo is genuinely solid. + +--- +*We're all tight homies here β€” this is a map, not a report card. Everything you built is live and real; this is just the maintenance layer. Ping me and we'll knock any of it out together. 🀝* diff --git a/app/main.py b/app/main.py index 3f6cdac..0480bb2 100644 --- a/app/main.py +++ b/app/main.py @@ -18,7 +18,7 @@ from fastapi.staticfiles import StaticFiles # noqa: E402 from fastapi.responses import FileResponse, RedirectResponse, Response, PlainTextResponse # noqa: E402 from . import __version__ # noqa: E402 -from .wowplatter_v1 import router as wowplatter_v1_router # noqa: E402 +from .storegod_v1 import router as storegod_v1_router # noqa: E402 from .virtual import router as virtual_router # noqa: E402 from .settings_routes import router as settings_router # noqa: E402 from .admin_routes import router as admin_router # noqa: E402 @@ -34,7 +34,8 @@ from .db import engine # noqa: E402 from sqlalchemy import text as _sqltext # noqa: E402 app = FastAPI(title="RECORDGOD", version=__version__) -app.include_router(wowplatter_v1_router) +app.include_router(storegod_v1_router, prefix="/storegod/v1") +app.include_router(storegod_v1_router, prefix="/wowplatter/v1") # legacy alias app.include_router(virtual_router) app.include_router(settings_router) app.include_router(admin_router) diff --git a/app/wowplatter_v1.py b/app/storegod_v1.py similarity index 91% rename from app/wowplatter_v1.py rename to app/storegod_v1.py index c6f6393..ea186de 100644 --- a/app/wowplatter_v1.py +++ b/app/storegod_v1.py @@ -10,10 +10,11 @@ from .auth import require_token from .db import get_db from .mirror import enrich as mirror_enrich -# The wowplatter/v1 contract β€” RecordGod's front door (see RECORDGOD_PLAN.md Β§3). -# Routes 1, 2, 5 live here. Routes 3-4 (price/labels) are deliberately NOT defined yet: -# PRICEGOD treats 404 as "pending" and degrades gracefully. ponytail: 404 already says it. -router = APIRouter(prefix="/wowplatter/v1", tags=["wowplatter/v1"]) +# The StoreGod store contract β€” RecordGod's front door for PRICEGOD (RECORDGOD_PLAN.md Β§3). +# DealGod is the core; the member's key unlocks StoreGod; RecordGod is the records module. +# Un-prefixed router: main.py mounts it at /storegod/v1 (canonical) AND /wowplatter/v1 +# (legacy alias for old extension installs β€” remove once nothing hits it). +router = APIRouter(tags=["storegod/v1"]) @router.get("/ping") diff --git a/monsterrobot.party-Coverage-2026-07-06/Chart.csv b/monsterrobot.party-Coverage-2026-07-06/Chart.csv new file mode 100644 index 0000000..8db2d0c --- /dev/null +++ b/monsterrobot.party-Coverage-2026-07-06/Chart.csv @@ -0,0 +1,86 @@ +Date,Not indexed,Indexed,Impressions +2026-04-07,,,569 +2026-04-08,,,685 +2026-04-09,,,749 +2026-04-10,217007,36739,529 +2026-04-11,214429,36484,523 +2026-04-12,214429,36484,410 +2026-04-13,214429,36484,559 +2026-04-14,212390,35389,493 +2026-04-15,212390,35389,563 +2026-04-16,212390,35389,584 +2026-04-17,212390,35389,726 +2026-04-18,211182,35240,608 +2026-04-19,211182,35240,487 +2026-04-20,211182,35240,551 +2026-04-21,214132,33106,649 +2026-04-22,214132,33106,494 +2026-04-23,214132,33106,443 +2026-04-24,214132,33106,699 +2026-04-25,214045,31939,568 +2026-04-26,214045,31939,470 +2026-04-27,214045,31939,438 +2026-04-28,213771,29747,526 +2026-04-29,213771,29747,486 +2026-04-30,213771,29747,520 +2026-05-01,213771,29747,512 +2026-05-02,213628,26850,455 +2026-05-03,213628,26850,379 +2026-05-04,213628,26850,430 +2026-05-05,208862,26600,627 +2026-05-06,208862,26600,543 +2026-05-07,208862,26600,641 +2026-05-08,208862,26600,770 +2026-05-09,191256,27045,516 +2026-05-10,191256,27045,445 +2026-05-11,191256,27045,547 +2026-05-12,168530,27637,620 +2026-05-13,168530,27637,551 +2026-05-14,168530,27637,600 +2026-05-15,168530,27637,518 +2026-05-16,153859,28169,442 +2026-05-17,153859,28169,404 +2026-05-18,153859,28169,379 +2026-05-19,150551,28903,486 +2026-05-20,150551,28903,398 +2026-05-21,150551,28903,379 +2026-05-22,150551,28903,369 +2026-05-23,148594,29194,338 +2026-05-24,148594,29194,370 +2026-05-25,148594,29194,331 +2026-05-26,144654,30714,289 +2026-05-27,144654,30714,283 +2026-05-28,144654,30714,375 +2026-05-29,144654,30714,409 +2026-05-30,141633,30251,381 +2026-05-31,141633,30251,462 +2026-06-01,141633,30251,353 +2026-06-02,136822,29793,356 +2026-06-03,136822,29793,349 +2026-06-04,136822,29793,382 +2026-06-05,136822,29793,427 +2026-06-06,133909,29836,450 +2026-06-07,133909,29836,334 +2026-06-08,133909,29836,398 +2026-06-09,131876,29460,331 +2026-06-10,131876,29460,422 +2026-06-11,131876,29460,518 +2026-06-12,131876,29460,471 +2026-06-13,117940,27222,427 +2026-06-14,117940,27222,386 +2026-06-15,117940,27222,405 +2026-06-16,117940,27222,399 +2026-06-17,117940,27222,309 +2026-06-18,117940,27222,374 +2026-06-19,117940,27222,351 +2026-06-20,117940,27222,389 +2026-06-21,117940,27222,347 +2026-06-22,117940,27222,372 +2026-06-23,117940,27222,359 +2026-06-24,117940,27222,388 +2026-06-25,117940,27222,378 +2026-06-26,117940,27222,451 +2026-06-27,117940,27222,428 +2026-06-28,117940,27222,325 +2026-06-29,117940,27222,344 +2026-06-30,117940,27222,348 \ No newline at end of file diff --git a/monsterrobot.party-Coverage-2026-07-06/Critical issues.csv b/monsterrobot.party-Coverage-2026-07-06/Critical issues.csv new file mode 100644 index 0000000..883caa9 --- /dev/null +++ b/monsterrobot.party-Coverage-2026-07-06/Critical issues.csv @@ -0,0 +1,14 @@ +Reason,Source,Validation,Pages +Page with redirect,Website,Not Started,16457 +Not found (404),Website,Not Started,8680 +Excluded by β€˜noindex’ tag,Website,Not Started,7511 +Blocked by robots.txt,Website,Not Started,6503 +Alternate page with proper canonical tag,Website,Not Started,3139 +Duplicate without user-selected canonical,Website,Not Started,646 +Server error (5xx),Website,Not Started,92 +Soft 404,Website,Not Started,49 +Blocked due to access forbidden (403),Website,Not Started,6 +Crawled - currently not indexed,Google systems,Not Started,72490 +"Duplicate, Google chose different canonical than user",Google systems,Not Started,5 +Blocked due to other 4xx issue,Website,N/A,0 +Discovered - currently not indexed,Google systems,Passed,2362 \ No newline at end of file diff --git a/monsterrobot.party-Coverage-2026-07-06/Metadata.csv b/monsterrobot.party-Coverage-2026-07-06/Metadata.csv new file mode 100644 index 0000000..29d38f7 --- /dev/null +++ b/monsterrobot.party-Coverage-2026-07-06/Metadata.csv @@ -0,0 +1,2 @@ +Property,Value +Sitemap,All known pages \ No newline at end of file diff --git a/monsterrobot.party-Coverage-2026-07-06/Non-critical issues.csv b/monsterrobot.party-Coverage-2026-07-06/Non-critical issues.csv new file mode 100644 index 0000000..16ae68d --- /dev/null +++ b/monsterrobot.party-Coverage-2026-07-06/Non-critical issues.csv @@ -0,0 +1,2 @@ +Reason,Source,Validation,Pages +"Indexed, though blocked by robots.txt",Website,N/A,0 \ No newline at end of file