decision_log is the table everything resolves to. Catalog transitions, discount tests, drafts, revivals — each is a row. Rows are never deleted; reads are how you audit, appends are how the agents act.
Row shape
{
"id": "a1b2c3d4-9f2e-4b7a-8c1d-6e5f4a3b2c1d",
"store_id": "0f8e7d6c-…",
"product_id": "5b4a3c2d-…",
"variant_id": null,
"from_state": "ACTIVE",
"to_state": "OPTIMIZING",
"trigger": "roas_below_floor",
"evidence": {
"action": "optimize_loser",
"roas_30d": 0.8,
"reasoning": "ROAS under the store floor for 30 days; rewrite before discounting."
},
"cost_tier_at_decision": "A",
"applied_to_shopify": true,
"status": "applied",
"acted_at": "2026-07-02T06:14:09Z"
}Integrity
The table is append-only by convention and versioned bitemporally: system_from / system_to columns record when each version of a row was current, so a correction produces a new version instead of silently overwriting history. Access is controlled by row-level security — each store sees only its own rows.
Reading it
for row in client.decisions.stream(since="2026-05-24T00:00:00Z"):
print(row.from_state, row.to_state, row.trigger, row.evidence["action"])Reversing an action
To undo, the revert executor reads the row and derives the inverse mutation from evidence.action — a draft or vault republishes the product, a discount_test restores the original price, an optimize_loser restores the previous content version. It stamps reverted_at and reverted_by on the row and rolls the lifecycle state back. See ALLOWED_TRANSITIONS for which state moves are valid on catalog rows.
