wedge: T013 property tests + T014 Dockerfile merged; fix zero-amount install rows
T013 worker found record_install violating CHECK(amount>0) when platform_pct rounds cut or seller_amount to zero (unhandled IntegrityError -> HTTP 500). Fixed: skip zero rows, grant references the paying tx; regression test added. 9 ledger tests green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
bc650ea4ee
commit
aeba02f287
6 changed files with 37 additions and 5 deletions
Binary file not shown.
|
|
@ -121,8 +121,17 @@ def record_install(
|
||||||
cut = int(amount * platform_pct)
|
cut = int(amount * platform_pct)
|
||||||
seller_amount = amount - cut
|
seller_amount = amount - cut
|
||||||
|
|
||||||
|
# The tx table CHECKs amount > 0: a pct that rounds either side to zero
|
||||||
|
# (0.0 -> cut == 0, 1.0 -> seller_amount == 0) must skip that row, not
|
||||||
|
# violate the constraint. amount >= 1 guarantees at least one row.
|
||||||
|
tx_ids: list[int] = []
|
||||||
|
net_tx_id = None
|
||||||
|
if seller_amount > 0:
|
||||||
net_tx_id = _insert_tx(conn, ts, buyer, seller, seller_amount, "install", ref)
|
net_tx_id = _insert_tx(conn, ts, buyer, seller, seller_amount, "install", ref)
|
||||||
cut_tx_id = _insert_tx(conn, ts, buyer, "platform", cut, "install", ref)
|
tx_ids.append(net_tx_id)
|
||||||
|
if cut > 0:
|
||||||
|
tx_ids.append(_insert_tx(conn, ts, buyer, "platform", cut, "install", ref))
|
||||||
|
paying_tx_id = net_tx_id if net_tx_id is not None else tx_ids[0]
|
||||||
|
|
||||||
grant_id = f"gr_{uuid.uuid4().hex}"
|
grant_id = f"gr_{uuid.uuid4().hex}"
|
||||||
conn.execute(
|
conn.execute(
|
||||||
|
|
@ -131,14 +140,14 @@ def record_install(
|
||||||
solution_version_major, tx_id, ts)
|
solution_version_major, tx_id, ts)
|
||||||
VALUES (?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?)
|
||||||
""",
|
""",
|
||||||
(grant_id, buyer, ref, 1, net_tx_id, ts),
|
(grant_id, buyer, ref, 1, paying_tx_id, ts),
|
||||||
)
|
)
|
||||||
|
|
||||||
grant_row = conn.execute(
|
grant_row = conn.execute(
|
||||||
"SELECT * FROM grant_ WHERE grant_id = ?", (grant_id,)
|
"SELECT * FROM grant_ WHERE grant_id = ?", (grant_id,)
|
||||||
).fetchone()
|
).fetchone()
|
||||||
|
|
||||||
return {"tx_ids": [net_tx_id, cut_tx_id], "grant": dict(grant_row)}
|
return {"tx_ids": tx_ids, "grant": dict(grant_row)}
|
||||||
|
|
||||||
|
|
||||||
def record_refund(conn: sqlite3.Connection, ref: str) -> dict:
|
def record_refund(conn: sqlite3.Connection, ref: str) -> dict:
|
||||||
|
|
|
||||||
BIN
ledger/tests/__pycache__/conftest.cpython-312-pytest-9.1.1.pyc
Normal file
BIN
ledger/tests/__pycache__/conftest.cpython-312-pytest-9.1.1.pyc
Normal file
Binary file not shown.
Binary file not shown.
|
|
@ -246,3 +246,26 @@ def test_api_install_402_leaves_tx_log_unchanged(client, service_headers, admin_
|
||||||
|
|
||||||
after = client.get("/tx", headers=service_headers).json()["transactions"]
|
after = client.get("/tx", headers=service_headers).json()["transactions"]
|
||||||
assert after == before
|
assert after == before
|
||||||
|
|
||||||
|
|
||||||
|
def test_edge_platform_pct_zero_and_one(tmp_path):
|
||||||
|
"""Regression: pct that rounds a row to zero must skip it, not 500.
|
||||||
|
|
||||||
|
(T013 worker finding: CHECK amount > 0 was violated by cut == 0 or
|
||||||
|
seller_amount == 0; fixed in models.record_install.)
|
||||||
|
"""
|
||||||
|
from m2_ledger import models as M
|
||||||
|
|
||||||
|
conn = M.init_db(str(tmp_path / "edge.db"))
|
||||||
|
M.record_grant(conn, "buyer", 100)
|
||||||
|
|
||||||
|
r0 = M.record_install(conn, "buyer", "seller", 10, 0.0, "ref:pct0")
|
||||||
|
assert M.balance(conn, "seller") == 10
|
||||||
|
assert M.balance(conn, "platform") == 0
|
||||||
|
assert len(r0["tx_ids"]) == 1
|
||||||
|
|
||||||
|
r1 = M.record_install(conn, "buyer", "seller", 10, 1.0, "ref:pct1")
|
||||||
|
assert M.balance(conn, "platform") == 10
|
||||||
|
assert len(r1["tx_ids"]) == 1
|
||||||
|
assert r1["grant"]["tx_id"] == r1["tx_ids"][0]
|
||||||
|
assert M.balance(conn, "buyer") == 80
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ published listing
|
||||||
- [x] T010 [P] [US1] Implement tx + grant models and derived-balance queries in `ledger/src/m2_ledger/models.py` (SQLite WAL, append-only invariants, data-model.md Transaction/LicenseGrant)
|
- [x] T010 [P] [US1] Implement tx + grant models and derived-balance queries in `ledger/src/m2_ledger/models.py` (SQLite WAL, append-only invariants, data-model.md Transaction/LicenseGrant)
|
||||||
- [x] T011 [P] [US1] Implement X-API-Key auth (service/admin key classes) in `ledger/src/m2_ledger/auth.py`
|
- [x] T011 [P] [US1] Implement X-API-Key auth (service/admin key classes) in `ledger/src/m2_ledger/auth.py`
|
||||||
- [x] T012 [US1] Implement API routes `/health`, `/balance/{op}`, `/tx/install`, `/tx/refund`, `/tx`, `/license/{op}/{listing}` in `ledger/src/m2_ledger/api.py` (402/409 semantics, all-or-nothing install tx, idempotent by ref)
|
- [x] T012 [US1] Implement API routes `/health`, `/balance/{op}`, `/tx/install`, `/tx/refund`, `/tx`, `/license/{op}/{listing}` in `ledger/src/m2_ledger/api.py` (402/409 semantics, all-or-nothing install tx, idempotent by ref)
|
||||||
- [ ] T013 [US1] Ledger unit + property tests in `ledger/tests/test_properties.py`: balance==fold(log) under generated sequences; no grant without tx; install atomicity; duplicate-ref 409
|
- [x] T013 [US1] Ledger unit + property tests in `ledger/tests/test_properties.py`: balance==fold(log) under generated sequences; no grant without tx; install atomicity; duplicate-ref 409
|
||||||
- [ ] T014 [US1] Dockerfile + Coolify deployment for m2-ledger on the `coolify` network in `ledger/Dockerfile` + `ledger/README.md` deploy notes; verify `GET /health` from the network
|
- [ ] T014 [US1] Dockerfile + Coolify deployment for m2-ledger on the `coolify` network in `ledger/Dockerfile` + `ledger/README.md` deploy notes; verify `GET /health` from the network
|
||||||
- [x] T015 [US1] Create `market:catalog` partition and implement indexer `indexer/src/m2_market_indexer/reindex.py` + `watch.py` per contracts/catalog-index.md (registry → memory-api upsert, tenant_visibility payload, delist removal)
|
- [x] T015 [US1] Create `market:catalog` partition and implement indexer `indexer/src/m2_market_indexer/reindex.py` + `watch.py` per contracts/catalog-index.md (registry → memory-api upsert, tenant_visibility payload, delist removal)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue