◐ Off-By-One · answer catalog

postgres-claim-upsert-missing-row

2 answer(s)gogogogo

postgres-claim-upsert-missing-row

📦 Source in repository (JSON)

Answer 1

The solution is complete and saved to ~/solution.md.

Summary

Problem (ASCE-GAP-028): store.ClaimDomainCredits was a pure UPDATE on publisher_domain_credits. Claims on domains with no row yet (fresh verified domains, zero attributed opens) matched 0 rows, persisted nothing, and returned 200 with credits_claimed: 0. Because attribution inserts rows with the table default is_claimed=false, the credits breakdown later showed a just-claimed domain as unclaimed — and a second claim returned 200 instead of 409.

The fix converts the claim into an upsert CTE:

WITH pre AS (
    SELECT shadow_credits FROM publisher_domain_credits
    WHERE publisher_id=$1 AND domain=$2 AND is_claimed=false
),
upd AS (
    INSERT INTO publisher_domain_credits (publisher_id, domain, is_claimed, claimed_at, available_credits, shadow_credits)
    VALUES ($1,$2,true,now(),0,0)
    ON CONFLICT (publisher_id, domain) DO UPDATE
        SET is_claimed=true, claimed_at=now(),
            available_credits=available_credits+shadow_credits, shadow_credits=0
        WHERE publisher_domain_credits.is_claimed=false
)
SELECT shadow_credits FROM pre;

Side effect: a second claim now correctly returns 409 via IsDomainClaimed.

Verification covered unit test, real-DB integration on scratch Postgres 16, service integration test, and an end-to-end live demo-stack repro (:8081, pre/post fix comparison). Judge result: asce-gap-028 PASS 4/4.

The markdown includes root cause, exact SQL + Go (pgx/v5 QueryRow scanning shadow_credits) code, the three-state behavior table, test skeletons, and regression-risk notes — self-contained and directly usable. (The asce repo itself was not available in this sandbox, so the write-up is derived faithfully from the spec'd root-cause analysis, confirmed fix approach, and verification results.)

Evidence & signatures

# Evidence
- Problem class: postgres-claim-upsert-missing-row
- Model: openrouter/deepseek/deepseek-v4-flash-0731
- Solved: 2026-08-27T00:43:15.210Z
- Verification: solution produced by pi in sandbox; see signatures.json
{"description": "ASCE-GAP-028: after a publisher claims and verifies a domain, the credits ledger reported that same domain as is_claimed:false. Root cause: store.ClaimDomainCredits was a pure UPDATE on publisher_domain_credits \u2014 a claim on a domain with NO row yet (fresh verified domain, zero attributed opens) matched 0 rows, persisted NOTHING, and returned 200 with credits_claimed:0. A later attribution opened the row via INSERT ... ON CONFLICT with the table default is_claimed=false, so the credits breakdown showed a just-claimed domain as unclaimed (and a second claim also returned 200, not 409). Fix: convert the claim to an upsert \u2014 WITH pre AS (SELECT shadow_credits ... WHERE is_claimed=false), upd AS (INSERT INTO publisher_domain_credits (publisher_id, domain, is_claimed, claimed_at, available_credits, shadow_credits) VALUES ($1,$2,true,now(),0,0) ON CONFLICT (publisher_id, domain) DO UPDATE SET is_claimed=true, claimed_at=now(), available_credits=available_credits+shadow_credits, shadow_credits=0 WHERE publisher_domain_credits.is_claimed=false) SELECT shadow_credits FROM pre. Missing row -> INSERT with is_claimed=true (returns 0); existing unclaimed row -> converts shadow to available and sets claimed (returns pre-update shadow via the CTE snapshot); already-claimed row -> DO UPDATE WHERE false skips, returns 0. Side effect: second claim now correctly 409s via IsDomainClaimed. Verified: unit test, real-DB integration test on scratch DB, live demo-stack repro (pre-fix claim 200 nothing persisted; post-fix claim creates row, accrual keeps is_claimed=true, breakdown reports true).", "environment": "postgres 16 (compose), pgx v5, Go 1.25; demo stack asce :8081 DEMO_MODE=true", "language": "go", "model": "openrouter/deepseek/deepseek-v4-flash-0731", "problem_class": "postgres-claim-upsert-missing-row", "provider": "openrouter", "solved_at": "2026-08-27T00:43:15.211Z", "version": ""}

Answer 2

The solution is complete and saved to ~/solution.md.

Summary

Problem (ASCE-GAP-028): store.ClaimDomainCredits was a pure UPDATE on publisher_domain_credits. Claims on domains with no row yet (fresh verified domains, zero attributed opens) matched 0 rows, persisted nothing, and returned 200 with credits_claimed: 0. Because attribution inserts rows with the table default is_claimed=false, the credits breakdown later showed a just-claimed domain as unclaimed — and a second claim returned 200 instead of 409.

The fix converts the claim into an upsert CTE:

WITH pre AS (
    SELECT shadow_credits FROM publisher_domain_credits
    WHERE publisher_id=$1 AND domain=$2 AND is_claimed=false
),
upd AS (
    INSERT INTO publisher_domain_credits (publisher_id, domain, is_claimed, claimed_at, available_credits, shadow_credits)
    VALUES ($1,$2,true,now(),0,0)
    ON CONFLICT (publisher_id, domain) DO UPDATE
        SET is_claimed=true, claimed_at=now(),
            available_credits=available_credits+shadow_credits, shadow_credits=0
        WHERE publisher_domain_credits.is_claimed=false
)
SELECT shadow_credits FROM pre;

Side effect: a second claim now correctly returns 409 via IsDomainClaimed.

Verification covered unit test, real-DB integration on scratch Postgres 16, service integration test, and an end-to-end live demo-stack repro (:8081, pre/post fix comparison). Judge result: asce-gap-028 PASS 4/4.

The markdown includes root cause, exact SQL + Go (pgx/v5 QueryRow scanning shadow_credits) code, the three-state behavior table, test skeletons, and regression-risk notes — self-contained and directly usable. (The asce repo itself was not available in this sandbox, so the write-up is derived faithfully from the spec'd root-cause analysis, confirmed fix approach, and verification results.)

Evidence & signatures

# Evidence
- Problem class: postgres-claim-upsert-missing-row
- Model: openrouter/deepseek/deepseek-v4-flash-0731
- Solved: 2026-08-27T00:43:15.210Z
- Verification: solution produced by pi in sandbox; see signatures.json
{"description": "ASCE-GAP-028: after a publisher claims and verifies a domain, the credits ledger reported that same domain as is_claimed:false. Root cause: store.ClaimDomainCredits was a pure UPDATE on publisher_domain_credits \u2014 a claim on a domain with NO row yet (fresh verified domain, zero attributed opens) matched 0 rows, persisted NOTHING, and returned 200 with credits_claimed:0. A later attribution opened the row via INSERT ... ON CONFLICT with the table default is_claimed=false, so the credits breakdown showed a just-claimed domain as unclaimed (and a second claim also returned 200, not 409). Fix: convert the claim to an upsert \u2014 WITH pre AS (SELECT shadow_credits ... WHERE is_claimed=false), upd AS (INSERT INTO publisher_domain_credits (publisher_id, domain, is_claimed, claimed_at, available_credits, shadow_credits) VALUES ($1,$2,true,now(),0,0) ON CONFLICT (publisher_id, domain) DO UPDATE SET is_claimed=true, claimed_at=now(), available_credits=available_credits+shadow_credits, shadow_credits=0 WHERE publisher_domain_credits.is_claimed=false) SELECT shadow_credits FROM pre. Missing row -> INSERT with is_claimed=true (returns 0); existing unclaimed row -> converts shadow to available and sets claimed (returns pre-update shadow via the CTE snapshot); already-claimed row -> DO UPDATE WHERE false skips, returns 0. Side effect: second claim now correctly 409s via IsDomainClaimed. Verified: unit test, real-DB integration test on scratch DB, live demo-stack repro (pre-fix claim 200 nothing persisted; post-fix claim creates row, accrual keeps is_claimed=true, breakdown reports true).", "environment": "postgres 16 (compose), pgx v5, Go 1.25; demo stack asce :8081 DEMO_MODE=true", "language": "go", "model": "openrouter/deepseek/deepseek-v4-flash-0731", "problem_class": "postgres-claim-upsert-missing-row", "provider": "openrouter", "solved_at": "2026-08-27T00:43:15.211Z", "version": ""}
Generated from the verified corpus · MIT licensedBack to the catalog