create-kedge 0.0.0-stage → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/bin/create-kedge.mjs +8 -0
- package/package.json +23 -3
- package/src/create.mjs +123 -0
- package/template.json +1 -0
- package/README.md +0 -3
package/template.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":"0.9.0","files":{".claude/hooks/guard.mjs":"// Denies edits that are always wrong in a Kedge store (p0.8 §9.2), naming what\n// to do instead. A convenience: a shell can still write these, so `kedge doctor`'s\n// `lock` and `installed` checks are what enforce it.\nimport { readFileSync } from \"node:fs\";\nimport { relative, sep } from \"node:path\";\n\nconst input = JSON.parse(readFileSync(0, \"utf8\") || \"{}\");\nconst root = process.env.CLAUDE_PROJECT_DIR || input.cwd || process.cwd();\nconst file = input.tool_input?.file_path ?? input.tool_input?.notebook_path ?? \"\";\nconst rel = relative(root, file).split(sep).join(\"/\");\n\nconst rules = [\n [/(^|\\/)node_modules\\/@kedge\\//, \"Installed Kedge is never edited: the next install undoes it and kedge doctor fails. Extend through kedge.config.ts, a plugin in src/plugins, or kedge eject <unit>.\"],\n [/^migrations\\//, \"migrations/ is composed by kedge generate and applied migrations never change. Put your own migration in local/ or a plugin's migrations, then run pnpm kedge generate.\"],\n [/^kedge\\.lock\\.json$/, \"kedge.lock.json is written by kedge generate; run pnpm kedge generate instead.\"],\n [/^(INVARIANTS\\.md|invariants\\.json)$/, \"INVARIANTS.md and invariants.json are generated from the composed migrations; run pnpm kedge generate.\"],\n [/^src\\/kedge\\.tz\\.ts$/, \"src/kedge.tz.ts pins the store's timezone rules and is written by kedge generate.\"],\n];\nconst hit = rel.startsWith(\"..\") ? null : rules.find(([re]) => re.test(rel));\nif (hit)\n process.stdout.write(\n JSON.stringify({\n hookSpecificOutput: { hookEventName: \"PreToolUse\", permissionDecision: \"deny\", permissionDecisionReason: hit[1] },\n }),\n );\n",".claude/hooks/stop.mjs":"// Before Claude stops, `kedge check --fast` must pass (p0.8 §9.2): a failure\n// keeps the conversation going with the failure as the reason. When Claude is\n// already continuing because of this hook, it lets it stop, so a check it\n// can't fix doesn't loop forever.\nimport { spawnSync } from \"node:child_process\";\nimport { readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nconst input = JSON.parse(readFileSync(0, \"utf8\") || \"{}\");\nif (input.stop_hook_active) process.exit(0);\nconst root = process.env.CLAUDE_PROJECT_DIR || input.cwd || process.cwd();\nconst r = spawnSync(join(root, \"node_modules\", \".bin\", \"kedge\"), [\"check\", \"--fast\"], { cwd: root, encoding: \"utf8\" });\nif (r.status === 0) process.exit(0);\nprocess.stderr.write(`kedge check --fast failed; fix it before stopping:\\n${`${r.stdout}\\n${r.stderr}`.trim().split(\"\\n\").slice(-40).join(\"\\n\")}\\n`);\nprocess.exit(2);\n",".claude/settings.json":"{\n \"hooks\": {\n \"PreToolUse\": [\n {\n \"matcher\": \"Edit|Write|MultiEdit|NotebookEdit\",\n \"hooks\": [{ \"type\": \"command\", \"command\": \"node \\\"${CLAUDE_PROJECT_DIR}/.claude/hooks/guard.mjs\\\"\" }]\n }\n ],\n \"Stop\": [{ \"hooks\": [{ \"type\": \"command\", \"command\": \"node \\\"${CLAUDE_PROJECT_DIR}/.claude/hooks/stop.mjs\\\"\" }] }]\n }\n}\n",".dev.vars.example":"# The secrets a Kedge Worker needs, each at least 32 random bytes. kedge dev\n# writes .dev.vars with fresh ones; kedge deploy generates any missing in\n# production, and never writes them anywhere. Set your own first if you keep\n# the pepper in a vault (KEDGE_KEY_PEPPER_V1 signs every API key).\nCART_TOKEN_SECRET=\nORDER_TOKEN_SECRET=\nKEDGE_AUTH_SECRET_V1=\nKEDGE_KEY_PEPPER_V1=\nKEDGE_TOTP_KEY_V1=\n# Seals a preview's credentials in the ops database (kedge preview).\nKEDGE_PREVIEW_KEY=\n",".github/workflows/ci.yml":"# check → preview → scenarios on a pull request; deploy on main (p0.8 §9.4).\n# Secrets: CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID, KEDGE_PREVIEW_KEY. A pull\n# request from a fork gets none, so it runs kedge check only.\n#\n# A preview runs the pull request's code (its config, tests and scenarios) with\n# the Cloudflare token in its environment, and a pull request from this\n# repository runs its own copy of this file. So no Cloudflare secret is a\n# repository secret: each lives in a GitHub environment, whose rules hold\n# whatever the workflow says.\n# preview CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID, KEDGE_PREVIEW_KEY;\n# required reviewers: a person approves each run before the token\n# reaches a pull request's code, and each cleanup on close.\n# production CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID; deployment branches\n# limited to main, so only merged code deploys.\n# A coding agent that can open pull requests can't take the token by writing a\n# scenario or editing this file.\nname: ci\non:\n pull_request:\n types: [opened, synchronize, reopened, closed]\n push:\n branches: [main]\nconcurrency:\n # One run per pull request at a time; a new push queues rather than abandoning a held preview.\n group: kedge-${{ github.event.pull_request.number || github.ref }}\n cancel-in-progress: false\njobs:\n check:\n if: github.event.action != 'closed'\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n - uses: pnpm/action-setup@v4\n - uses: actions/setup-node@v4\n with: { node-version: 24, cache: pnpm }\n - run: pnpm install --frozen-lockfile\n - run: pnpm kedge check\n preview:\n needs: check\n if: github.event_name == 'pull_request' && github.event.action != 'closed' && github.event.pull_request.head.repo.full_name == github.repository\n runs-on: ubuntu-latest\n environment: preview\n # Explicit permissions zero the rest: checkout needs contents (a private repository).\n permissions: { contents: read, pull-requests: write }\n env:\n CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}\n CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}\n KEDGE_PREVIEW_KEY: ${{ secrets.KEDGE_PREVIEW_KEY }}\n steps:\n - uses: actions/checkout@v4\n - uses: pnpm/action-setup@v4\n - uses: actions/setup-node@v4\n with: { node-version: 24, cache: pnpm }\n - run: pnpm install --frozen-lockfile\n # bash, which GitHub runs with pipefail: under its default shell the pipe's status is tee's, and a\n # failed scenario would pass the job (codex code r31).\n - run: pnpm kedge preview --name pr-${{ github.event.pull_request.number }} | tee preview.log\n shell: bash\n - run: gh pr comment ${{ github.event.pull_request.number }} --body \"$(tail -n 20 preview.log)\"\n env: { GH_TOKEN: \"${{ github.token }}\" }\n destroy:\n if: github.event_name == 'pull_request' && github.event.action == 'closed' && github.event.pull_request.head.repo.full_name == github.repository\n runs-on: ubuntu-latest\n environment: preview\n env:\n CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}\n CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}\n steps:\n # The base branch, which a person merged: destroying needs no code from the pull request.\n - uses: actions/checkout@v4\n with: { ref: \"${{ github.event.pull_request.base.ref }}\" }\n - uses: pnpm/action-setup@v4\n - uses: actions/setup-node@v4\n with: { node-version: 24, cache: pnpm }\n - run: pnpm install --frozen-lockfile\n - run: pnpm kedge preview destroy --name pr-${{ github.event.pull_request.number }}\n deploy:\n needs: check\n if: github.event_name == 'push' && github.ref == 'refs/heads/main'\n runs-on: ubuntu-latest\n environment: production\n env:\n CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}\n CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}\n steps:\n # Full history: a deploy refuses an older commit over a newer release.\n - uses: actions/checkout@v4\n with: { fetch-depth: 0 }\n - uses: pnpm/action-setup@v4\n - uses: actions/setup-node@v4\n with: { node-version: 24, cache: pnpm }\n - run: pnpm install --frozen-lockfile\n - run: pnpm kedge deploy\n",".gitignore":"node_modules/\n# pnpm keeps its store at the project root when home is on another filesystem (a container).\n.pnpm-store/\n.wrangler/\n# Secrets for `kedge dev`, written by it with fresh random values.\n.dev.vars\n# Preview credentials and plans: `.kedge/eject/` stays tracked (§7.2).\n.kedge/previews/\n.kedge/deploy/\n# Copied from @kedge/docs by `kedge generate`, so they match what's installed.\n.claude/skills/\n","AGENTS.md":"# This store\n\nA Kedge store: a commerce backend on Cloudflare Workers and D1, deployed to\nthis repository's own Cloudflare account. Kedge comes in as `@kedge/*`\npackages; this repository holds the store's configuration, plugins, tests and\ncomposed migrations. `CLAUDE.md` only imports this file: edit this one.\n\n## Where a change goes\n\nClimb only when the rung below can't express the requirement, and say why.\n\n1. Data (products, prices, tax, staff): the admin API, no code.\n2. Config: `kedge.config.ts` (hooks, custom fields, providers).\n3. A plugin in `src/plugins/<name>`: its own tables, checkout rows, kinds, tracks, entities, subscribers, timers.\n4. A port, to reach a provider.\n5. `kedge eject <unit>`, one view of core, with a reason. SQL is never ejected.\n6. A change to a core table or trigger: stop and say so. That's a missing primitive, not a fork.\n\nThe `kedge-extend` skill walks this ladder.\n\n## Rules, and what breaks without them\n\n- Never edit `node_modules/@kedge`: the next install undoes it, and `kedge doctor` fails.\n- Money is integer minor units (`1999` is $19.99): a float drifts by a cent.\n- No DDL or triggers on core tables: core's triggers keep stock and money counters, and a write around them drifts them until a release goes negative.\n- Plugin SQL reads core only through `kedge_v1_*` views: a core table can change in the next release, a view's version can't.\n- Every `CONSTRAINT` and `RAISE` code in your SQL gets an entry in the plugin's `errors`: otherwise shoppers get `INTERNAL`.\n- Never call `db.transaction()` on D1: it throws. A rule that must hold is a constraint or trigger in the same batch.\n- Never write `migrations/` or `kedge.lock.json`: `kedge generate` composes them; a migration of your own goes in `local/` or a plugin. Applied migrations never change.\n- Reach a provider only through a port, never `fetch`: an effect sent around the journal repeats after a retry.\n- Never put a customer's data in an error message or a log line: logs are out of erasure's reach.\n\n## Commands\n\n| | |\n| --------------------- | ----------------------------------------------------------------------- |\n| `pnpm kedge check` | typecheck, `generate --check`, `doctor`, the tests: run before you stop |\n| `pnpm kedge generate` | compose migrations, the lock, `INVARIANTS.md` |\n| `pnpm kedge doctor` | the store's checks; each failure names its fix |\n| `pnpm kedge dev` | the store on this machine |\n| `pnpm kedge preview` | the store on Cloudflare, isolated, with `test/scenarios` run against it |\n| `pnpm kedge deploy` | production: migrations first, then the Worker |\n\n`INVARIANTS.md` lists every rule the database enforces and the error it answers.\n\n## Docs for this installed version\n\n<!-- kedge:docs-index:start -->\nKedge 0.9.0. Read the guide before writing that kind of code; they match what's installed.\n- [Hooks](node_modules/@kedge/docs/extending/hooks.md): fees, adjustments, line prices and checkout refusals; no D1 in a hook\n- [Checkout contributions](node_modules/@kedge/docs/extending/contributions.md): your rows in the order's batch: reads, facts, statements\n- [Line kinds](node_modules/@kedge/docs/extending/kinds.md): lines that aren't plain items: charge class, period, price\n- [Tracks](node_modules/@kedge/docs/extending/tracks.md): workflows beside order status, guards inside the batch\n- [Entities](node_modules/@kedge/docs/extending/entities.md): your own records with a generated admin API\n- [Custom fields](node_modules/@kedge/docs/extending/custom-fields.md): typed values on carts, lines and orders, with scopes\n- [Ports](node_modules/@kedge/docs/extending/ports.md): the only way to a provider; effects journaled, never fetch elsewhere\n- [Subscribers](node_modules/@kedge/docs/extending/subscribers.md): react after an event commits, at least once\n- [Timers](node_modules/@kedge/docs/extending/timers.md): act later, once per revision, owned by an order\n- [Admin](node_modules/@kedge/docs/extending/admin.md): what your entities and tracks get at /app/, field labels, page and block IDs\n- [Tests](node_modules/@kedge/docs/extending/tests.md): workerd scenarios with the invariant suite; HTTP scenarios on a preview\n- [API](node_modules/@kedge/docs/reference/api.md): route families, auth, idempotency, money; OpenAPI at /admin/openapi.json\n- [Error codes](node_modules/@kedge/docs/reference/errors.md): every core code and status\n- [Upgrade notes](node_modules/@kedge/docs/upgrading/): per release: what changed, codemods, manual steps\n<!-- kedge:docs-index:end -->\n","CLAUDE.md":"@AGENTS.md\n","INVARIANTS.md":"# Invariants\n\nGenerated by `kedge generate` from the composed migrations; never edit it. Each\nrule is enforced by D1 itself, in the batch, so code that breaks it is refused\nwith the error shown. Follow the rule and you never meet the error.\n\n## INV-STOCK-001: Stock is held only by reservations\n\nChange stock only through core's checkout, expiry and cancellation, which insert and release inventory_reservation rows. Never write inventory_level.reserved: triggers keep it, and a direct write drifts it from the reservations until a release goes negative.\n\n- **Errors:** `KEDGE_OOS` → OUT_OF_STOCK; `KEDGE_ORDER_SEALED` → INTERNAL (a core defect); `KEDGE_RESERVATION_IMMUTABLE` → INTERNAL (a core defect); `KEDGE_RESERVATION_INSERTED_RELEASED` → INTERNAL (a core defect); `KEDGE_RESERVATION_IN_USE` → INTERNAL (a core defect); `KEDGE_RESERVATION_ORDER_STATE` → INTERNAL (a core defect); `KEDGE_STOCK_OUT_OF_RANGE` → INTERNAL (a core defect); `KEDGE_TRACKING_MODE` → TRACKING_MODE_CHANGED\n- **Checked after every test** by `describeKedgeInvariants` (`level_reserved_matches_reservations`)\n- **Enforced by:** 11 objects (`invariants.json` lists them)\n\n## INV-STOCK-002: A level's counts stay integers in range\n\nOn hand, reserved and safety stock are non-negative integers, and the backstop refuses a reservation past what's available. A plugin can't hold stock any other way: dated capacity (pools and holds) is how a plugin claims time-bound supply.\n\n- **Errors:** `KEDGE_STOCK_BACKSTOP` → OUT_OF_STOCK; `KEDGE_STOCK_OUT_OF_RANGE` → INTERNAL (a core defect)\n- **Enforced by:** 9 objects (`invariants.json` lists them)\n\n## INV-STOCK-003: An item's tracking and its conversion keep history\n\nAn inventory item's tracking mode changes only through the conversion route, which checks no live reservation, hold or dated fact still depends on the old mode.\n\n- **Errors:** `KEDGE_ORDER_HISTORY` → INTERNAL (a core defect); `KEDGE_TRACKING_MODE` → TRACKING_MODE_CHANGED\n- **Checked after every test** by `describeKedgeInvariants` (`allocations_match_tracking`)\n- **Enforced by:** 12 objects (`invariants.json` lists them)\n\n## INV-CATALOG-001: Variants, prices and locations hold valid values\n\nPrices are integer minor units; a variant has at most 16 components; allowed kinds are a JSON array of kind codes.\n\n- **Errors:** `KEDGE_COMPONENTS_LIMIT` → CAPACITY_LIMIT\n- **Enforced by:** 6 objects (`invariants.json` lists them)\n\n## INV-ORDER-001: Order status moves only by its transitions\n\nNever UPDATE an order's status: placing, reopening, expiring, cancelling and completing are core's transitions, guarded on the status they leave, which is what makes a retried placement place once. React to a transition with a subscriber on its event.\n\n- **Errors:** `KEDGE_NOT_REOPENABLE` → NOT_REOPENABLE; `KEDGE_ORDER_IMMUTABLE` → INTERNAL (a core defect); `KEDGE_ORDER_INITIAL` → INTERNAL (a core defect); `KEDGE_ORDER_OBLIGATIONS` → NOT_PLACEABLE; `KEDGE_ORDER_SEALED` → INTERNAL (a core defect); `KEDGE_ORDER_TRANSITION` → INTERNAL (a core defect); `KEDGE_PLACEMENT_DEADLINE` → PLACEMENT_DEADLINE_PASSED; `KEDGE_RESERVATIONS_MISSING` → RESERVATIONS_MISSING; `KEDGE_STALE_ATTEMPT` → SUPERSEDED; `KEDGE_TAX_CONTEXT` → CART_TOTAL_CHANGED; `KEDGE_TZ_VERSION` → TZ_RULES_MISMATCH\n- **Checked after every test** by `describeKedgeInvariants` (`status_matches_history`)\n- **Enforced by:** 30 objects (`invariants.json` lists them)\n\n## INV-ORDER-002: A placed order's money adds up and never changes\n\nMoney is integer minor units. A plugin adds money to an order only as a fee or an adjustment from a checkout hook or contribution, which commits in the order's own batch; the commit's assertion refuses totals that don't equal the rows. Once placed, an order's money rows are sealed.\n\n- **Errors:** `KEDGE_ADJUSTMENT_UNALLOCATED` → INTERNAL (a core defect); `KEDGE_ALLOCATION_NOT_DISCOUNTABLE` → INTERNAL (a core defect); `KEDGE_ALLOCATION_ORDER` → INTERNAL (a core defect); `KEDGE_CONTRIBUTION_UNBACKED` → CONTRIBUTION_FAILED; `KEDGE_COVERAGE` → INTERNAL (a core defect); `KEDGE_FEE_LINE` → INTERNAL (a core defect); `KEDGE_HOLD_COUNT` → INTERNAL (a core defect); `KEDGE_KIND_NOT_ALLOWED` → KIND_NOT_ALLOWED; `KEDGE_LINE_NEGATIVE` → INTERNAL (a core defect); `KEDGE_LINE_PER_PARENT` → INTERNAL (a core defect); `KEDGE_MONEY_IMMUTABLE` → INTERNAL (a core defect); `KEDGE_ORDER_IMMUTABLE` → INTERNAL (a core defect); `KEDGE_ORDER_SEALED` → INTERNAL (a core defect); `KEDGE_ORDER_TOTALS` → INTERNAL (a core defect); `KEDGE_ORDER_UNSEALED` → INTERNAL (a core defect); `KEDGE_PERIOD_PROVENANCE` → INTERNAL (a core defect); `KEDGE_PLACE_BY` → INTERNAL (a core defect); `KEDGE_POLICY_MISSING` → POLICY_REQUIRED; `KEDGE_TAX_CONTEXT` → CART_TOTAL_CHANGED; `KEDGE_TAX_LINES` → INTERNAL (a core defect); `KEDGE_TAX_OWNER` → INTERNAL (a core defect); `KEDGE_TRACKS_MISSING` → INTERNAL (a core defect)\n- **Checked after every test** by `describeKedgeInvariants` (`totals_match_rows`)\n- **Enforced by:** 46 objects (`invariants.json` lists them)\n\n## INV-ORDER-003: Order lines are written once, in a valid shape\n\nLines are written by the checkout commit only. A line's kind must be allowed by its variant and declared by a plugin; a child's quantity follows its parent's; a line's period comes from its kind.\n\n- **Errors:** `KEDGE_ALLOCATION_ORDER` → INTERNAL (a core defect); `KEDGE_COMPONENT_INITIAL` → INTERNAL (a core defect); `KEDGE_KIND_NOT_ALLOWED` → KIND_NOT_ALLOWED; `KEDGE_LINE_PARENT` → INTERNAL (a core defect); `KEDGE_MONEY_IMMUTABLE` → INTERNAL (a core defect); `KEDGE_ORDER_IMMUTABLE` → INTERNAL (a core defect); `KEDGE_ORDER_INITIAL` → INTERNAL (a core defect); `KEDGE_ORDER_SEALED` → INTERNAL (a core defect); `KEDGE_PERIOD_PROVENANCE` → INTERNAL (a core defect)\n- **Checked after every test** by `describeKedgeInvariants` (`per_parent_quantities`)\n- **Enforced by:** 40 objects (`invariants.json` lists them)\n\n## INV-ORDER-004: Checkout policies are accepted as they were shown\n\nA policy revision is immutable once published; an order records the acceptance of each required revision, in the order's locale, or it isn't placed.\n\n- **Errors:** `KEDGE_ORDER_IMMUTABLE` → INTERNAL (a core defect); `KEDGE_ORDER_SEALED` → INTERNAL (a core defect); `KEDGE_POLICY_CHANGED` → POLICY_CHANGED; `KEDGE_POLICY_DUPLICATE` → INTERNAL (a core defect); `KEDGE_POLICY_IMMUTABLE` → INTERNAL (a core defect); `KEDGE_POLICY_LIMIT` → POLICY_LIMIT; `KEDGE_POLICY_LOCALE` → INTERNAL (a core defect)\n- **Checked after every test** by `describeKedgeInvariants` (`acceptances_in_order_locale`)\n- **Enforced by:** 14 objects (`invariants.json` lists them)\n\n## INV-EXT-001: A plugin's contributions are backed in the order's batch\n\nA contribution identity is backed exactly as declared by rows the commit inserts. Declare a fact for every row a checkout statement writes, so a commit whose row vanished is refused rather than placed without it.\n\n- **Errors:** `KEDGE_CONTRIBUTION_IDENTITY` → INTERNAL (a core defect); `KEDGE_FACT_IMMUTABLE` → INTERNAL (a core defect); `KEDGE_FACT_OVERBACKED` → INTERNAL (a core defect); `KEDGE_ORDER_SEALED` → INTERNAL (a core defect)\n- **Checked after every test** by `describeKedgeInvariants` (`facts_backed`)\n- **Enforced by:** 7 objects (`invariants.json` lists them)\n\n## INV-EXT-002: Plugins are installed, enabled and disabled only by core\n\nA plugin's state and its participation in an order are core's rows. Disabled means no new use: a disabled plugin's new checkouts, tracks and entities are refused, and everything already started runs on.\n\n- **Errors:** `KEDGE_ORDER_SEALED` → INTERNAL (a core defect); `KEDGE_PARTICIPATION_IMMUTABLE` → INTERNAL (a core defect); `KEDGE_PLUGIN_INACTIVE` → PLUGIN_INACTIVE; `KEDGE_PLUGIN_STATE` → INTERNAL (a core defect)\n- **Checked after every test** by `describeKedgeInvariants` (`participants_installed`)\n- **Enforced by:** 8 objects (`invariants.json` lists them)\n\n## INV-EXT-003: Tracks move one version at a time\n\nA track's state is core's: move it with a transition, whose guards hold inside the batch. Completion waits for every blocking track to be terminal; a checkout-track change is fenced until the new Worker serves.\n\n- **Errors:** `KEDGE_ORDER_TRACKS` → ORDER_TRACKS_PENDING; `KEDGE_PLUGIN_INACTIVE` → PLUGIN_INACTIVE; `KEDGE_TRACKS_GENERATION` → PLUGIN_UPGRADING; `KEDGE_TRACK_IMMUTABLE` → INTERNAL (a core defect); `KEDGE_TRACK_ORDER_COMPLETED` → ORDER_COMPLETED; `KEDGE_TRACK_VERSION_STEP` → INTERNAL (a core defect)\n- **Enforced by:** 6 objects (`invariants.json` lists them)\n\n## INV-EXT-004: History rows are append-only\n\ntransition and transition_note are core's history: never update or delete them. A plugin trigger may attach to transition, writing only its own tables (K1).\n\n- **Errors:** `KEDGE_TRANSITION_IMMUTABLE` → INTERNAL (a core defect)\n- **Checked after every test** by `describeKedgeInvariants` (`order_history_marked`)\n- **Enforced by:** 5 objects (`invariants.json` lists them)\n\n## INV-ENTITY-001: Plugin entity rows are written only by core, in a valid shape\n\nA plugin entity's table (plg_<plugin>_<entity>) is core's to write: through the admin routes or ctx.entities, never plugin SQL. Generated triggers check each field, its references and scope, refuse deletes and archived rows, and fence an older Worker's writes after a declaration changes.\n\n- **Enforced by:** 2 objects (`invariants.json` lists them)\n\n## INV-CAP-001: Dated capacity is held only by holds\n\nClaim time-bound supply with capacity holds through core. A hold is released only by its order's expiry or cancellation, or by retirement, and never deleted while in use.\n\n- **Errors:** `KEDGE_CAPACITY_BACKSTOP` → CAPACITY_UNAVAILABLE; `KEDGE_HOLD_IMMUTABLE` → INTERNAL (a core defect); `KEDGE_HOLD_INSERTED_RELEASED` → INTERNAL (a core defect); `KEDGE_HOLD_IN_USE` → INTERNAL (a core defect); `KEDGE_HOLD_OWNER` → INTERNAL (a core defect); `KEDGE_HOLD_RANGE` → INTERNAL (a core defect); `KEDGE_HOLD_RELEASE` → INTERNAL (a core defect); `KEDGE_HOLD_SPAN` → INTERNAL (a core defect); `KEDGE_ORDER_SEALED` → INTERNAL (a core defect); `KEDGE_POOL_RETIRED` → POOL_RETIRED; `KEDGE_TRACKING_MODE` → TRACKING_MODE_CHANGED; `KEDGE_TZ_VERSION` → TZ_RULES_MISMATCH; `KEDGE_UNAVAILABLE` → CAPACITY_UNAVAILABLE\n- **Checked after every test** by `describeKedgeInvariants` (`bucket_held_matches_holds`)\n- **Enforced by:** 9 objects (`invariants.json` lists them)\n\n## INV-CAP-002: Capacity pools change only by scheduled steps\n\nA pool's base changes by scheduled base changes and adjustments, each bounded; a retired pool takes no new holds. Use the capacity routes, never write these tables.\n\n- **Errors:** `KEDGE_ADJUSTMENT_IMMUTABLE` → INTERNAL (a core defect); `KEDGE_ADJUSTMENT_LIMIT` → CAPACITY_LIMIT; `KEDGE_ADJUSTMENT_STATE` → INTERNAL (a core defect); `KEDGE_BASE_CHANGE_IMMUTABLE` → INTERNAL (a core defect); `KEDGE_BASE_CHANGE_STATE` → INTERNAL (a core defect); `KEDGE_BASE_STEPS` → CAPACITY_LIMIT; `KEDGE_HOLD_SPAN` → INTERNAL (a core defect); `KEDGE_PATTERN_SLOT` → BAD_REQUEST; `KEDGE_POOL_IMMUTABLE` → INTERNAL (a core defect); `KEDGE_POOL_RETIRED` → POOL_RETIRED; `KEDGE_POOL_RETIRE_BLOCKED` → CONFLICT; `KEDGE_SCHEDULED_LIMIT` → CAPACITY_LIMIT; `KEDGE_SCOPE_MAP_ITEM` → BAD_REQUEST; `KEDGE_TRACKING_MODE` → TRACKING_MODE_CHANGED; `KEDGE_TZ_VERSION` → TZ_RULES_MISMATCH\n- **Checked after every test** by `describeKedgeInvariants` (`capacity_base_canonical`)\n- **Enforced by:** 21 objects (`invariants.json` lists them)\n\n## INV-RELAY-001: Every committed event reaches its subscribers\n\nAn event is a row written in the batch that caused it; one obligation per subscription is written with it, by trigger. Subscribe with a declared subscriber; never write the outbox or an obligation.\n\n- **Errors:** `KEDGE_OBLIGATION_IMMUTABLE` → INTERNAL (a core defect); `KEDGE_OBLIGATION_NOT_PENDING` → INTERNAL (a core defect); `KEDGE_OBLIGATION_RELAYED` → INTERNAL (a core defect); `KEDGE_OBLIGATION_STATE` → INTERNAL (a core defect); `KEDGE_SUBSCRIPTION_CAP` → INTERNAL (a core defect); `KEDGE_SUBSCRIPTION_IMMUTABLE` → INTERNAL (a core defect)\n- **Checked after every test** by `describeKedgeInvariants` (`releases_reach_participants`)\n- **Enforced by:** 20 objects (`invariants.json` lists them)\n\n## INV-RELAY-002: Timers fire once per revision\n\nTimers belong to an owner and are capped per owner; a terminal owner takes none. Declare a timer and let core schedule, reschedule and cancel it; a firing is a guarded update, once per revision.\n\n- **Errors:** `KEDGE_PLUGIN_INACTIVE` → PLUGIN_INACTIVE; `KEDGE_TIMER_CAP` → TIMER_LIMIT; `KEDGE_TIMER_EPOCH` → INTERNAL (a core defect); `KEDGE_TIMER_GENERATION` → INTERNAL (a core defect); `KEDGE_TIMER_IMMUTABLE` → INTERNAL (a core defect); `KEDGE_TIMER_OWNER_TERMINAL` → TIMER_OWNER_TERMINAL; `KEDGE_TIMER_STATE` → INTERNAL (a core defect)\n- **Enforced by:** 15 objects (`invariants.json` lists them)\n\n## INV-RELAY-003: Webhooks are capped, signed and logged\n\nEndpoints, their events and secret rotations are bounded; a deleted endpoint is final; a delivery's log is append-only.\n\n- **Errors:** `KEDGE_KEYRING_IN_USE` → CONFLICT; `KEDGE_KEYRING_VERSION` → CONFLICT; `KEDGE_OBLIGATION_IMMUTABLE` → INTERNAL (a core defect); `KEDGE_OBLIGATION_STATE` → INTERNAL (a core defect); `KEDGE_ROTATION_LIMIT` → ROTATION_LIMIT; `KEDGE_WEBHOOK_DELETED` → NOT_FOUND; `KEDGE_WEBHOOK_ENDPOINTS_MAX` → LIMIT_EXCEEDED; `KEDGE_WEBHOOK_ENDPOINTS_PER_EVENT` → LIMIT_EXCEEDED; `KEDGE_WEBHOOK_EVENTS_PER_ENDPOINT` → LIMIT_EXCEEDED; `KEDGE_WEBHOOK_GENERATION` → INTERNAL (a core defect); `KEDGE_WEBHOOK_IMMUTABLE` → INTERNAL (a core defect)\n- **Enforced by:** 14 objects (`invariants.json` lists them)\n\n## INV-ACCESS-001: Every core write carries this release's marker\n\nWrite core tables only through core's stores, whose batches write the release's marker first. A batch without it, from SQL you wrote or a previous release's Worker, is refused KEDGE_FENCED.\n\n- **Errors:** `KEDGE_AUDIT_MISSING` → INTERNAL (a core defect); `KEDGE_COMMIT_OBLIGATION` → INTERNAL (a core defect); `KEDGE_FENCED` → INTERNAL (a core defect); `KEDGE_MARKER_IMMUTABLE` → INTERNAL (a core defect)\n- **Enforced by:** 312 objects (`invariants.json` lists them)\n\n## INV-ACCESS-002: Every staff, key and CLI write is audited\n\nAudit rows are written before their batch's marker and never change. Admin mutations go through core's admin batch, which writes them.\n\n- **Errors:** `KEDGE_AUDIT_IMMUTABLE` → INTERNAL (a core defect); `KEDGE_AUDIT_ORDER` → INTERNAL (a core defect); `KEDGE_AUDIT_PENDING` → INTERNAL (a core defect)\n- **Enforced by:** 3 objects (`invariants.json` lists them)\n\n## INV-ACCESS-003: Access is bounded, and the store keeps an Owner\n\nRoles, grants, keys and invites are bounded; built-in roles don't change; a revoked key stays revoked; the last active Owner can't be removed. Manage access through the admin routes.\n\n- **Errors:** `KEDGE_ATTEMPT_DONE` → INTERNAL (a core defect); `KEDGE_BOUND` → LIMIT_EXCEEDED; `KEDGE_CUSTOMER_IMMUTABLE` → INTERNAL (a core defect); `KEDGE_IDEMPOTENCY_IMMUTABLE` → INTERNAL (a core defect); `KEDGE_INVITE_IMMUTABLE` → INTERNAL (a core defect); `KEDGE_KEY_IMMUTABLE` → INTERNAL (a core defect); `KEDGE_KEY_REVOKED` → KEY_REVOKED; `KEDGE_LAST_OWNER` → LAST_OWNER; `KEDGE_PRIVACY_STALE` → INTERNAL (a core defect); `KEDGE_PRIVACY_STORE` → INTERNAL (a core defect); `KEDGE_ROLE_BUILTIN` → FORBIDDEN; `KEDGE_ROLE_IMMUTABLE` → INTERNAL (a core defect); `KEDGE_ROLE_IN_USE` → CONFLICT; `KEDGE_ROLE_WILDCARD` → BAD_REQUEST; `KEDGE_SESSION_IMMUTABLE` → INTERNAL (a core defect); `KEDGE_STAFF_IMMUTABLE` → INTERNAL (a core defect); `KEDGE_TOTP_REPLAY` → AUTH_CEREMONY_STALE\n- **Enforced by:** 32 objects (`invariants.json` lists them)\n\n## INV-CAL-001: The store's calendar is bounded\n\nHolidays are within the calendar's window and capped; the calendar changes through its route.\n\n- **Errors:** `KEDGE_CALENDAR_DAY_OUT_OF_RANGE` → CALENDAR_DAY_OUT_OF_RANGE; `KEDGE_CALENDAR_HOLIDAYS_MAX` → LIMIT_EXCEEDED; `KEDGE_CALENDAR_IMMUTABLE` → INTERNAL (a core defect)\n- **Enforced by:** 4 objects (`invariants.json` lists them)\n","invariants.json":"{\n \"version\": 1,\n \"core\": [\n {\n \"id\": \"INV-STOCK-001\",\n \"title\": \"Stock is held only by reservations\",\n \"rule\": \"Change stock only through core's checkout, expiry and cancellation, which insert and release inventory_reservation rows. Never write inventory_level.reserved: triggers keep it, and a direct write drifts it from the reservations until a release goes negative.\",\n \"check\": \"level_reserved_matches_reservations\",\n \"objects\": [\n \"inventory_reservation_bounds\",\n \"inventory_reservation_quantity_int\",\n \"inventory_reservation_sealed\",\n \"reservation_bookkeeping\",\n \"reservation_delete\",\n \"reservation_immutable\",\n \"reservation_insert_check\",\n \"reservation_insert_count\",\n \"reservation_reactivate\",\n \"reservation_reactivate_check\",\n \"reservation_release\"\n ],\n \"raises\": [\n {\n \"code\": \"KEDGE_OOS\",\n \"error\": \"OUT_OF_STOCK\"\n },\n {\n \"code\": \"KEDGE_ORDER_SEALED\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_RESERVATION_IMMUTABLE\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_RESERVATION_INSERTED_RELEASED\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_RESERVATION_IN_USE\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_RESERVATION_ORDER_STATE\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_STOCK_OUT_OF_RANGE\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_TRACKING_MODE\",\n \"error\": \"TRACKING_MODE_CHANGED\"\n }\n ]\n },\n {\n \"id\": \"INV-STOCK-002\",\n \"title\": \"A level's counts stay integers in range\",\n \"rule\": \"On hand, reserved and safety stock are non-negative integers, and the backstop refuses a reservation past what's available. A plugin can't hold stock any other way: dated capacity (pools and holds) is how a plugin claims time-bound supply.\",\n \"check\": null,\n \"objects\": [\n \"inv_reserved_nonneg\",\n \"inventory_level_allow_backorder_bool\",\n \"inventory_level_bounds_insert\",\n \"inventory_level_bounds_update\",\n \"inventory_level_lowered\",\n \"inventory_level_on_hand_int\",\n \"inventory_level_reserved_backstop\",\n \"inventory_level_reserved_int\",\n \"inventory_level_safety_stock_int\"\n ],\n \"raises\": [\n {\n \"code\": \"KEDGE_STOCK_BACKSTOP\",\n \"error\": \"OUT_OF_STOCK\"\n },\n {\n \"code\": \"KEDGE_STOCK_OUT_OF_RANGE\",\n \"error\": \"INTERNAL (a core defect)\"\n }\n ]\n },\n {\n \"id\": \"INV-STOCK-003\",\n \"title\": \"An item's tracking and its conversion keep history\",\n \"rule\": \"An inventory item's tracking mode changes only through the conversion route, which checks no live reservation, hold or dated fact still depends on the old mode.\",\n \"check\": \"allocations_match_tracking\",\n \"objects\": [\n \"conversion_cleanup\",\n \"conversion_dated_facts\",\n \"conversion_history\",\n \"conversion_holds\",\n \"conversion_locations\",\n \"conversion_negative_base\",\n \"conversion_reservations\",\n \"conversion_unplanned\",\n \"inventory_item_history_guard\",\n \"inventory_item_order_history\",\n \"inventory_item_tracking\",\n \"inventory_item_tracking_guard\"\n ],\n \"raises\": [\n {\n \"code\": \"KEDGE_ORDER_HISTORY\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_TRACKING_MODE\",\n \"error\": \"TRACKING_MODE_CHANGED\"\n }\n ]\n },\n {\n \"id\": \"INV-CATALOG-001\",\n \"title\": \"Variants, prices and locations hold valid values\",\n \"rule\": \"Prices are integer minor units; a variant has at most 16 components; allowed kinds are a JSON array of kind codes.\",\n \"check\": null,\n \"objects\": [\n \"price_amount_int\",\n \"stock_location_priority_int\",\n \"variant_allowed_kinds_json\",\n \"variant_component_limit\",\n \"variant_component_move_limit\",\n \"variant_inventory_item_quantity_int\"\n ],\n \"raises\": [\n {\n \"code\": \"KEDGE_COMPONENTS_LIMIT\",\n \"error\": \"CAPACITY_LIMIT\"\n }\n ]\n },\n {\n \"id\": \"INV-ORDER-001\",\n \"title\": \"Order status moves only by its transitions\",\n \"rule\": \"Never UPDATE an order's status: placing, reopening, expiring, cancelling and completing are core's transitions, guarded on the status they leave, which is what makes a retried placement place once. React to a transition with a subscriber on its event.\",\n \"check\": \"status_matches_history\",\n \"objects\": [\n \"order_cancel\",\n \"order_complete\",\n \"order_completion_obligations\",\n \"order_current_alloc_cost_int\",\n \"order_custom_json\",\n \"order_event_canceled\",\n \"order_event_completed\",\n \"order_event_expired\",\n \"order_expire\",\n \"order_fence\",\n \"order_hold_count_int\",\n \"order_index_insert\",\n \"order_initial\",\n \"order_locale\",\n \"order_no_delete\",\n \"order_not_reopenable\",\n \"order_place\",\n \"order_place_by_int\",\n \"order_place_by_source_insert\",\n \"order_place_by_source_no_delete\",\n \"order_place_by_source_write_once\",\n \"order_placement_deadline\",\n \"order_release_cost_int\",\n \"order_reopen\",\n \"order_reopen_allowed\",\n \"order_reopenable\",\n \"order_reservation_count_int\",\n \"order_status\",\n \"order_status_guard\",\n \"sequence_value_int\"\n ],\n \"raises\": [\n {\n \"code\": \"KEDGE_NOT_REOPENABLE\",\n \"error\": \"NOT_REOPENABLE\"\n },\n {\n \"code\": \"KEDGE_ORDER_IMMUTABLE\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_ORDER_INITIAL\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_ORDER_OBLIGATIONS\",\n \"error\": \"NOT_PLACEABLE\"\n },\n {\n \"code\": \"KEDGE_ORDER_SEALED\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_ORDER_TRANSITION\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_PLACEMENT_DEADLINE\",\n \"error\": \"PLACEMENT_DEADLINE_PASSED\"\n },\n {\n \"code\": \"KEDGE_RESERVATIONS_MISSING\",\n \"error\": \"RESERVATIONS_MISSING\"\n },\n {\n \"code\": \"KEDGE_STALE_ATTEMPT\",\n \"error\": \"SUPERSEDED\"\n },\n {\n \"code\": \"KEDGE_TAX_CONTEXT\",\n \"error\": \"CART_TOTAL_CHANGED\"\n },\n {\n \"code\": \"KEDGE_TZ_VERSION\",\n \"error\": \"TZ_RULES_MISMATCH\"\n }\n ]\n },\n {\n \"id\": \"INV-ORDER-002\",\n \"title\": \"A placed order's money adds up and never changes\",\n \"rule\": \"Money is integer minor units. A plugin adds money to an order only as a fee or an adjustment from a checkout hook or contribution, which commits in the order's own batch; the commit's assertion refuses totals that don't equal the rows. Once placed, an order's money rows are sealed.\",\n \"check\": \"totals_match_rows\",\n \"objects\": [\n \"contribution_check_assert\",\n \"contribution_check_no_delete\",\n \"contribution_check_tracks\",\n \"contribution_check_write_once\",\n \"order_adjustment_amount_int\",\n \"order_adjustment_immutable\",\n \"order_adjustment_insert\",\n \"order_adjustment_no_delete\",\n \"order_deposit_total_int\",\n \"order_discount_total_int\",\n \"order_fee_amount_int\",\n \"order_fee_charge_class\",\n \"order_fee_immutable\",\n \"order_fee_insert\",\n \"order_fee_no_delete\",\n \"order_fee_taxable\",\n \"order_fee_total_int\",\n \"order_line_adjustment_amount_int\",\n \"order_line_adjustment_immutable\",\n \"order_line_adjustment_insert\",\n \"order_line_adjustment_no_delete\",\n \"order_line_adjustment_owner\",\n \"order_revenue_total_int\",\n \"order_seal_due_no_delete\",\n \"order_seal_due_write_once\",\n \"order_shipping_total_int\",\n \"order_subtotal_int\",\n \"order_tax_inclusive\",\n \"order_tax_line_amount_int\",\n \"order_tax_line_class\",\n \"order_tax_line_inclusive\",\n \"order_tax_line_insert\",\n \"order_tax_line_no_delete\",\n \"order_tax_line_owner\",\n \"order_tax_line_rate_int\",\n \"order_tax_line_write_once\",\n \"order_tax_rate_deposit_int\",\n \"order_tax_rate_revenue_int\",\n \"order_tax_total_int\",\n \"order_total_int\",\n \"order_write_once\",\n \"tax_setting_inclusive\",\n \"tax_setting_no_delete\",\n \"tax_setting_rate_deposit_int\",\n \"tax_setting_rate_revenue_int\",\n \"tax_setting_single\"\n ],\n \"raises\": [\n {\n \"code\": \"KEDGE_ADJUSTMENT_UNALLOCATED\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_ALLOCATION_NOT_DISCOUNTABLE\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_ALLOCATION_ORDER\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_CONTRIBUTION_UNBACKED\",\n \"error\": \"CONTRIBUTION_FAILED\"\n },\n {\n \"code\": \"KEDGE_COVERAGE\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_FEE_LINE\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_HOLD_COUNT\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_KIND_NOT_ALLOWED\",\n \"error\": \"KIND_NOT_ALLOWED\"\n },\n {\n \"code\": \"KEDGE_LINE_NEGATIVE\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_LINE_PER_PARENT\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_MONEY_IMMUTABLE\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_ORDER_IMMUTABLE\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_ORDER_SEALED\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_ORDER_TOTALS\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_ORDER_UNSEALED\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_PERIOD_PROVENANCE\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_PLACE_BY\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_POLICY_MISSING\",\n \"error\": \"POLICY_REQUIRED\"\n },\n {\n \"code\": \"KEDGE_TAX_CONTEXT\",\n \"error\": \"CART_TOTAL_CHANGED\"\n },\n {\n \"code\": \"KEDGE_TAX_LINES\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_TAX_OWNER\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_TRACKS_MISSING\",\n \"error\": \"INTERNAL (a core defect)\"\n }\n ]\n },\n {\n \"id\": \"INV-ORDER-003\",\n \"title\": \"Order lines are written once, in a valid shape\",\n \"rule\": \"Lines are written by the checkout commit only. A line's kind must be allowed by its variant and declared by a plugin; a child's quantity follows its parent's; a line's period comes from its kind.\",\n \"check\": \"per_parent_quantities\",\n \"objects\": [\n \"order_line_adjustment_amount_int\",\n \"order_line_adjustment_immutable\",\n \"order_line_adjustment_insert\",\n \"order_line_adjustment_no_delete\",\n \"order_line_adjustment_owner\",\n \"order_line_charge_class\",\n \"order_line_component_history\",\n \"order_line_component_insert\",\n \"order_line_component_no_delete\",\n \"order_line_component_requirement_insert\",\n \"order_line_component_requirement_no_delete\",\n \"order_line_component_requirement_write_once\",\n \"order_line_component_version_insert\",\n \"order_line_component_version_no_delete\",\n \"order_line_component_version_write_once\",\n \"order_line_component_write_once\",\n \"order_line_custom_json\",\n \"order_line_cutoff_minutes\",\n \"order_line_discountable\",\n \"order_line_fulfilled_int\",\n \"order_line_fulfillment_class\",\n \"order_line_insert\",\n \"order_line_kind\",\n \"order_line_kind_version\",\n \"order_line_model_insert\",\n \"order_line_no_delete\",\n \"order_line_period\",\n \"order_line_period_provenance_insert\",\n \"order_line_period_provenance_no_delete\",\n \"order_line_period_provenance_write_once\",\n \"order_line_period_start_int\",\n \"order_line_price_breakdown_json\",\n \"order_line_quantity_int\",\n \"order_line_quantity_mode\",\n \"order_line_quantity_per_parent\",\n \"order_line_resolved_int\",\n \"order_line_returnable\",\n \"order_line_taxable\",\n \"order_line_unit_price_int\",\n \"order_line_write_once\"\n ],\n \"raises\": [\n {\n \"code\": \"KEDGE_ALLOCATION_ORDER\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_COMPONENT_INITIAL\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_KIND_NOT_ALLOWED\",\n \"error\": \"KIND_NOT_ALLOWED\"\n },\n {\n \"code\": \"KEDGE_LINE_PARENT\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_MONEY_IMMUTABLE\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_ORDER_IMMUTABLE\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_ORDER_INITIAL\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_ORDER_SEALED\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_PERIOD_PROVENANCE\",\n \"error\": \"INTERNAL (a core defect)\"\n }\n ]\n },\n {\n \"id\": \"INV-ORDER-004\",\n \"title\": \"Checkout policies are accepted as they were shown\",\n \"rule\": \"A policy revision is immutable once published; an order records the acceptance of each required revision, in the order's locale, or it isn't placed.\",\n \"check\": \"acceptances_in_order_locale\",\n \"objects\": [\n \"checkout_policy_code\",\n \"checkout_policy_limit\",\n \"checkout_policy_no_update\",\n \"policy_acceptance_evidence_kind\",\n \"policy_acceptance_evidence_len\",\n \"policy_acceptance_insert\",\n \"policy_acceptance_no_delete\",\n \"policy_acceptance_write_once\",\n \"policy_revision_body\",\n \"policy_revision_code\",\n \"policy_revision_immutable\",\n \"policy_revision_locale\",\n \"policy_revision_no_delete\",\n \"policy_revision_version_int\"\n ],\n \"raises\": [\n {\n \"code\": \"KEDGE_ORDER_IMMUTABLE\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_ORDER_SEALED\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_POLICY_CHANGED\",\n \"error\": \"POLICY_CHANGED\"\n },\n {\n \"code\": \"KEDGE_POLICY_DUPLICATE\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_POLICY_IMMUTABLE\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_POLICY_LIMIT\",\n \"error\": \"POLICY_LIMIT\"\n },\n {\n \"code\": \"KEDGE_POLICY_LOCALE\",\n \"error\": \"INTERNAL (a core defect)\"\n }\n ]\n },\n {\n \"id\": \"INV-EXT-001\",\n \"title\": \"A plugin's contributions are backed in the order's batch\",\n \"rule\": \"A contribution identity is backed exactly as declared by rows the commit inserts. Declare a fact for every row a checkout statement writes, so a commit whose row vanished is refused rather than placed without it.\",\n \"check\": \"facts_backed\",\n \"objects\": [\n \"contribution_fact_backed_int\",\n \"contribution_fact_backing_bound\",\n \"contribution_fact_cardinality\",\n \"contribution_fact_immutable\",\n \"contribution_fact_insert\",\n \"contribution_fact_no_delete\",\n \"contribution_fact_sealed\"\n ],\n \"raises\": [\n {\n \"code\": \"KEDGE_CONTRIBUTION_IDENTITY\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_FACT_IMMUTABLE\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_FACT_OVERBACKED\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_ORDER_SEALED\",\n \"error\": \"INTERNAL (a core defect)\"\n }\n ]\n },\n {\n \"id\": \"INV-EXT-002\",\n \"title\": \"Plugins are installed, enabled and disabled only by core\",\n \"rule\": \"A plugin's state and its participation in an order are core's rows. Disabled means no new use: a disabled plugin's new checkouts, tracks and entities are refused, and everything already started runs on.\",\n \"check\": \"participants_installed\",\n \"objects\": [\n \"plugin_participation_barrier\",\n \"plugin_participation_immutable\",\n \"plugin_participation_no_delete\",\n \"plugin_participation_sealed\",\n \"plugin_state_guard\",\n \"plugin_state_insert\",\n \"plugin_state_no_delete\",\n \"plugin_state_state\"\n ],\n \"raises\": [\n {\n \"code\": \"KEDGE_ORDER_SEALED\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_PARTICIPATION_IMMUTABLE\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_PLUGIN_INACTIVE\",\n \"error\": \"PLUGIN_INACTIVE\"\n },\n {\n \"code\": \"KEDGE_PLUGIN_STATE\",\n \"error\": \"INTERNAL (a core defect)\"\n }\n ]\n },\n {\n \"id\": \"INV-EXT-003\",\n \"title\": \"Tracks move one version at a time\",\n \"rule\": \"A track's state is core's: move it with a transition, whose guards hold inside the batch. Completion waits for every blocking track to be terminal; a checkout-track change is fenced until the new Worker serves.\",\n \"check\": null,\n \"objects\": [\n \"checkout_fence_generation_int\",\n \"order_completion_tracks\",\n \"order_tracks_generation\",\n \"track_state_insert\",\n \"track_state_no_delete\",\n \"track_state_update\"\n ],\n \"raises\": [\n {\n \"code\": \"KEDGE_ORDER_TRACKS\",\n \"error\": \"ORDER_TRACKS_PENDING\"\n },\n {\n \"code\": \"KEDGE_PLUGIN_INACTIVE\",\n \"error\": \"PLUGIN_INACTIVE\"\n },\n {\n \"code\": \"KEDGE_TRACKS_GENERATION\",\n \"error\": \"PLUGIN_UPGRADING\"\n },\n {\n \"code\": \"KEDGE_TRACK_IMMUTABLE\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_TRACK_ORDER_COMPLETED\",\n \"error\": \"ORDER_COMPLETED\"\n },\n {\n \"code\": \"KEDGE_TRACK_VERSION_STEP\",\n \"error\": \"INTERNAL (a core defect)\"\n }\n ]\n },\n {\n \"id\": \"INV-EXT-004\",\n \"title\": \"History rows are append-only\",\n \"rule\": \"transition and transition_note are core's history: never update or delete them. A plugin trigger may attach to transition, writing only its own tables (K1).\",\n \"check\": \"order_history_marked\",\n \"objects\": [\n \"transition_immutable\",\n \"transition_no_delete\",\n \"transition_note_immutable\",\n \"transition_note_no_delete\",\n \"transition_release_obligations\"\n ],\n \"raises\": [\n {\n \"code\": \"KEDGE_TRANSITION_IMMUTABLE\",\n \"error\": \"INTERNAL (a core defect)\"\n }\n ]\n },\n {\n \"id\": \"INV-ENTITY-001\",\n \"title\": \"Plugin entity rows are written only by core, in a valid shape\",\n \"rule\": \"A plugin entity's table (plg_<plugin>_<entity>) is core's to write: through the admin routes or ctx.entities, never plugin SQL. Generated triggers check each field, its references and scope, refuse deletes and archived rows, and fence an older Worker's writes after a declaration changes.\",\n \"check\": null,\n \"objects\": [\n \"kedge_assert_check\",\n \"kedge_assert_clear\"\n ],\n \"raises\": []\n },\n {\n \"id\": \"INV-CAP-001\",\n \"title\": \"Dated capacity is held only by holds\",\n \"rule\": \"Claim time-bound supply with capacity holds through core. A hold is released only by its order's expiry or cancellation, or by retirement, and never deleted while in use.\",\n \"check\": \"bucket_held_matches_holds\",\n \"objects\": [\n \"capacity_bucket_backstop\",\n \"capacity_hold_count\",\n \"capacity_hold_delete\",\n \"capacity_hold_immutable\",\n \"capacity_hold_insert\",\n \"capacity_hold_reactivate\",\n \"capacity_hold_reactivate_check\",\n \"capacity_hold_release\",\n \"capacity_hold_release_check\"\n ],\n \"raises\": [\n {\n \"code\": \"KEDGE_CAPACITY_BACKSTOP\",\n \"error\": \"CAPACITY_UNAVAILABLE\"\n },\n {\n \"code\": \"KEDGE_HOLD_IMMUTABLE\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_HOLD_INSERTED_RELEASED\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_HOLD_IN_USE\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_HOLD_OWNER\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_HOLD_RANGE\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_HOLD_RELEASE\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_HOLD_SPAN\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_ORDER_SEALED\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_POOL_RETIRED\",\n \"error\": \"POOL_RETIRED\"\n },\n {\n \"code\": \"KEDGE_TRACKING_MODE\",\n \"error\": \"TRACKING_MODE_CHANGED\"\n },\n {\n \"code\": \"KEDGE_TZ_VERSION\",\n \"error\": \"TZ_RULES_MISMATCH\"\n },\n {\n \"code\": \"KEDGE_UNAVAILABLE\",\n \"error\": \"CAPACITY_UNAVAILABLE\"\n }\n ]\n },\n {\n \"id\": \"INV-CAP-002\",\n \"title\": \"Capacity pools change only by scheduled steps\",\n \"rule\": \"A pool's base changes by scheduled base changes and adjustments, each bounded; a retired pool takes no new holds. Use the capacity routes, never write these tables.\",\n \"check\": \"capacity_base_canonical\",\n \"objects\": [\n \"capacity_adjustment_canceled\",\n \"capacity_adjustment_guard\",\n \"capacity_adjustment_insert\",\n \"capacity_adjustment_lowered\",\n \"capacity_adjustment_no_delete\",\n \"capacity_base_change_apply\",\n \"capacity_base_change_cancel\",\n \"capacity_base_change_guard\",\n \"capacity_base_change_insert\",\n \"capacity_base_change_no_delete\",\n \"capacity_pattern_insert\",\n \"capacity_pattern_lowered\",\n \"capacity_pattern_lowered_update\",\n \"capacity_pattern_update\",\n \"capacity_pool_check\",\n \"capacity_pool_guard\",\n \"capacity_pool_insert\",\n \"capacity_pool_no_delete\",\n \"capacity_pool_retired_unmapped\",\n \"capacity_scope_map_insert\",\n \"capacity_scope_map_update\"\n ],\n \"raises\": [\n {\n \"code\": \"KEDGE_ADJUSTMENT_IMMUTABLE\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_ADJUSTMENT_LIMIT\",\n \"error\": \"CAPACITY_LIMIT\"\n },\n {\n \"code\": \"KEDGE_ADJUSTMENT_STATE\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_BASE_CHANGE_IMMUTABLE\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_BASE_CHANGE_STATE\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_BASE_STEPS\",\n \"error\": \"CAPACITY_LIMIT\"\n },\n {\n \"code\": \"KEDGE_HOLD_SPAN\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_PATTERN_SLOT\",\n \"error\": \"BAD_REQUEST\"\n },\n {\n \"code\": \"KEDGE_POOL_IMMUTABLE\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_POOL_RETIRED\",\n \"error\": \"POOL_RETIRED\"\n },\n {\n \"code\": \"KEDGE_POOL_RETIRE_BLOCKED\",\n \"error\": \"CONFLICT\"\n },\n {\n \"code\": \"KEDGE_SCHEDULED_LIMIT\",\n \"error\": \"CAPACITY_LIMIT\"\n },\n {\n \"code\": \"KEDGE_SCOPE_MAP_ITEM\",\n \"error\": \"BAD_REQUEST\"\n },\n {\n \"code\": \"KEDGE_TRACKING_MODE\",\n \"error\": \"TRACKING_MODE_CHANGED\"\n },\n {\n \"code\": \"KEDGE_TZ_VERSION\",\n \"error\": \"TZ_RULES_MISMATCH\"\n }\n ]\n },\n {\n \"id\": \"INV-RELAY-001\",\n \"title\": \"Every committed event reaches its subscribers\",\n \"rule\": \"An event is a row written in the batch that caused it; one obligation per subscription is written with it, by trigger. Subscribe with a declared subscriber; never write the outbox or an obligation.\",\n \"check\": \"releases_reach_participants\",\n \"objects\": [\n \"effect_resolution_guard\",\n \"effect_resolution_no_delete\",\n \"event_subscription_cap\",\n \"event_subscription_guard\",\n \"event_subscription_no_delete\",\n \"outbox_obligations\",\n \"outbox_shortage_queue\",\n \"outbox_webhooks\",\n \"release_delivery_needs_pending\",\n \"release_delivery_writer\",\n \"release_obligation_attempts_int\",\n \"release_obligation_guard\",\n \"release_obligation_no_delete\",\n \"release_obligation_state\",\n \"release_obligation_writer\",\n \"resolution_delivery_needs_pending\",\n \"subscriber_delivery_needs_open\",\n \"subscriber_obligation_guard\",\n \"subscriber_obligation_no_delete\",\n \"transition_release_obligations\"\n ],\n \"raises\": [\n {\n \"code\": \"KEDGE_OBLIGATION_IMMUTABLE\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_OBLIGATION_NOT_PENDING\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_OBLIGATION_RELAYED\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_OBLIGATION_STATE\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_SUBSCRIPTION_CAP\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_SUBSCRIPTION_IMMUTABLE\",\n \"error\": \"INTERNAL (a core defect)\"\n }\n ]\n },\n {\n \"id\": \"INV-RELAY-002\",\n \"title\": \"Timers fire once per revision\",\n \"rule\": \"Timers belong to an owner and are capped per owner; a terminal owner takes none. Declare a timer and let core schedule, reschedule and cancel it; a firing is a guarded update, once per revision.\",\n \"check\": null,\n \"objects\": [\n \"order_timers_canceled\",\n \"order_timers_completed\",\n \"timer_count_cancel\",\n \"timer_count_firing_closed\",\n \"timer_count_insert\",\n \"timer_count_reschedule\",\n \"timer_fired\",\n \"timer_firing_guard\",\n \"timer_firing_no_delete\",\n \"timer_identity\",\n \"timer_insert_canceled\",\n \"timer_insert_check\",\n \"timer_no_delete\",\n \"timer_owner_no_delete\",\n \"timer_reschedule_check\"\n ],\n \"raises\": [\n {\n \"code\": \"KEDGE_PLUGIN_INACTIVE\",\n \"error\": \"PLUGIN_INACTIVE\"\n },\n {\n \"code\": \"KEDGE_TIMER_CAP\",\n \"error\": \"TIMER_LIMIT\"\n },\n {\n \"code\": \"KEDGE_TIMER_EPOCH\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_TIMER_GENERATION\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_TIMER_IMMUTABLE\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_TIMER_OWNER_TERMINAL\",\n \"error\": \"TIMER_OWNER_TERMINAL\"\n },\n {\n \"code\": \"KEDGE_TIMER_STATE\",\n \"error\": \"INTERNAL (a core defect)\"\n }\n ]\n },\n {\n \"id\": \"INV-RELAY-003\",\n \"title\": \"Webhooks are capped, signed and logged\",\n \"rule\": \"Endpoints, their events and secret rotations are bounded; a deleted endpoint is final; a delivery's log is append-only.\",\n \"check\": null,\n \"objects\": [\n \"keyring_auth_webhook_secrets\",\n \"webhook_attempt_guard\",\n \"webhook_delivery_completed\",\n \"webhook_delivery_failed\",\n \"webhook_delivery_guard\",\n \"webhook_delivery_no_delete\",\n \"webhook_endpoint_cap\",\n \"webhook_endpoint_disabled\",\n \"webhook_endpoint_event_insert\",\n \"webhook_endpoint_event_no_update\",\n \"webhook_endpoint_guard\",\n \"webhook_endpoint_no_delete\",\n \"webhook_secret_guard\",\n \"webhook_secret_insert\"\n ],\n \"raises\": [\n {\n \"code\": \"KEDGE_KEYRING_IN_USE\",\n \"error\": \"CONFLICT\"\n },\n {\n \"code\": \"KEDGE_KEYRING_VERSION\",\n \"error\": \"CONFLICT\"\n },\n {\n \"code\": \"KEDGE_OBLIGATION_IMMUTABLE\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_OBLIGATION_STATE\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_ROTATION_LIMIT\",\n \"error\": \"ROTATION_LIMIT\"\n },\n {\n \"code\": \"KEDGE_WEBHOOK_DELETED\",\n \"error\": \"NOT_FOUND\"\n },\n {\n \"code\": \"KEDGE_WEBHOOK_ENDPOINTS_MAX\",\n \"error\": \"LIMIT_EXCEEDED\"\n },\n {\n \"code\": \"KEDGE_WEBHOOK_ENDPOINTS_PER_EVENT\",\n \"error\": \"LIMIT_EXCEEDED\"\n },\n {\n \"code\": \"KEDGE_WEBHOOK_EVENTS_PER_ENDPOINT\",\n \"error\": \"LIMIT_EXCEEDED\"\n },\n {\n \"code\": \"KEDGE_WEBHOOK_GENERATION\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_WEBHOOK_IMMUTABLE\",\n \"error\": \"INTERNAL (a core defect)\"\n }\n ]\n },\n {\n \"id\": \"INV-ACCESS-001\",\n \"title\": \"Every core write carries this release's marker\",\n \"rule\": \"Write core tables only through core's stores, whose batches write the release's marker first. A batch without it, from SQL you wrote or a previous release's Worker, is refused KEDGE_FENCED.\",\n \"check\": null,\n \"objects\": [\n \"batch_marker_audit\",\n \"batch_marker_discharge\",\n \"batch_marker_immutable\",\n \"commit_obligation_empty\",\n \"fence_admin_notice_delete\",\n \"fence_admin_notice_insert\",\n \"fence_admin_notice_update\",\n \"fence_api_key_delete\",\n \"fence_api_key_grant_delete\",\n \"fence_api_key_grant_insert\",\n \"fence_api_key_grant_update\",\n \"fence_api_key_insert\",\n \"fence_api_key_location_delete\",\n \"fence_api_key_location_insert\",\n \"fence_api_key_location_update\",\n \"fence_api_key_update\",\n \"fence_auth_attempt_delete\",\n \"fence_auth_attempt_insert\",\n \"fence_auth_attempt_update\",\n \"fence_auth_challenge_delete\",\n \"fence_auth_challenge_insert\",\n \"fence_auth_challenge_update\",\n \"fence_auth_code_delete\",\n \"fence_auth_code_insert\",\n \"fence_auth_code_update\",\n \"fence_auth_device_delete\",\n \"fence_auth_device_insert\",\n \"fence_auth_device_update\",\n \"fence_auth_event_delete\",\n \"fence_auth_event_insert\",\n \"fence_auth_event_update\",\n \"fence_auth_rate_counter_delete\",\n \"fence_auth_rate_counter_insert\",\n \"fence_auth_rate_counter_update\",\n \"fence_auth_session_delete\",\n \"fence_auth_session_insert\",\n \"fence_auth_session_update\",\n \"fence_auth_totp_delete\",\n \"fence_auth_totp_insert\",\n \"fence_auth_totp_pending_delete\",\n \"fence_auth_totp_pending_insert\",\n \"fence_auth_totp_pending_update\",\n \"fence_auth_totp_update\",\n \"fence_capacity_adjustment_delete\",\n \"fence_capacity_adjustment_insert\",\n \"fence_capacity_adjustment_update\",\n \"fence_capacity_base_change_delete\",\n \"fence_capacity_base_change_insert\",\n \"fence_capacity_base_change_update\",\n \"fence_capacity_base_delete\",\n \"fence_capacity_base_insert\",\n \"fence_capacity_base_update\",\n \"fence_capacity_bucket_delete\",\n \"fence_capacity_bucket_insert\",\n \"fence_capacity_bucket_update\",\n \"fence_capacity_buffer_delete\",\n \"fence_capacity_buffer_insert\",\n \"fence_capacity_buffer_update\",\n \"fence_capacity_guard_delete\",\n \"fence_capacity_guard_insert\",\n \"fence_capacity_guard_update\",\n \"fence_capacity_hold_delete\",\n \"fence_capacity_hold_insert\",\n \"fence_capacity_hold_update\",\n \"fence_capacity_pattern_delete\",\n \"fence_capacity_pattern_insert\",\n \"fence_capacity_pattern_update\",\n \"fence_capacity_pool_delete\",\n \"fence_capacity_pool_insert\",\n \"fence_capacity_pool_update\",\n \"fence_capacity_scope_map_delete\",\n \"fence_capacity_scope_map_insert\",\n \"fence_capacity_scope_map_update\",\n \"fence_checkout_fence_delete\",\n \"fence_checkout_fence_insert\",\n \"fence_checkout_fence_update\",\n \"fence_checkout_policy_delete\",\n \"fence_checkout_policy_insert\",\n \"fence_checkout_policy_update\",\n \"fence_checkout_tracks_delete\",\n \"fence_checkout_tracks_insert\",\n \"fence_checkout_tracks_update\",\n \"fence_contribution_check_delete\",\n \"fence_contribution_check_insert\",\n \"fence_contribution_check_update\",\n \"fence_contribution_fact_delete\",\n \"fence_contribution_fact_insert\",\n \"fence_contribution_fact_update\",\n \"fence_customer_delete\",\n \"fence_customer_insert\",\n \"fence_customer_update\",\n \"fence_effect_resolution_delete\",\n \"fence_effect_resolution_insert\",\n \"fence_effect_resolution_update\",\n \"fence_event_subscription_delete\",\n \"fence_event_subscription_insert\",\n \"fence_event_subscription_update\",\n \"fence_hold_retirement_due_delete\",\n \"fence_hold_retirement_due_insert\",\n \"fence_hold_retirement_due_update\",\n \"fence_idempotency_key_delete\",\n \"fence_idempotency_key_insert\",\n \"fence_idempotency_key_update\",\n \"fence_inventory_item_delete\",\n \"fence_inventory_item_insert\",\n \"fence_inventory_item_update\",\n \"fence_inventory_level_delete\",\n \"fence_inventory_level_insert\",\n \"fence_inventory_level_update\",\n \"fence_inventory_reservation_delete\",\n \"fence_inventory_reservation_insert\",\n \"fence_inventory_reservation_update\",\n \"fence_kedge_assert_delete\",\n \"fence_kedge_assert_insert\",\n \"fence_kedge_assert_update\",\n \"fence_keyring_setting_delete\",\n \"fence_keyring_setting_insert\",\n \"fence_keyring_setting_update\",\n \"fence_maintenance_cursor_delete\",\n \"fence_maintenance_cursor_insert\",\n \"fence_maintenance_cursor_update\",\n \"fence_order_adjustment_delete\",\n \"fence_order_adjustment_insert\",\n \"fence_order_adjustment_update\",\n \"fence_order_delete\",\n \"fence_order_event_delete\",\n \"fence_order_event_insert\",\n \"fence_order_event_update\",\n \"fence_order_fee_delete\",\n \"fence_order_fee_insert\",\n \"fence_order_fee_update\",\n \"fence_order_index_delete\",\n \"fence_order_index_insert\",\n \"fence_order_index_update\",\n \"fence_order_insert\",\n \"fence_order_line_adjustment_delete\",\n \"fence_order_line_adjustment_insert\",\n \"fence_order_line_adjustment_update\",\n \"fence_order_line_component_delete\",\n \"fence_order_line_component_insert\",\n \"fence_order_line_component_requirement_delete\",\n \"fence_order_line_component_requirement_insert\",\n \"fence_order_line_component_requirement_update\",\n \"fence_order_line_component_update\",\n \"fence_order_line_component_version_delete\",\n \"fence_order_line_component_version_insert\",\n \"fence_order_line_component_version_update\",\n \"fence_order_line_delete\",\n \"fence_order_line_insert\",\n \"fence_order_line_period_provenance_delete\",\n \"fence_order_line_period_provenance_insert\",\n \"fence_order_line_period_provenance_update\",\n \"fence_order_line_update\",\n \"fence_order_place_by_source_delete\",\n \"fence_order_place_by_source_insert\",\n \"fence_order_place_by_source_update\",\n \"fence_order_seal_due_delete\",\n \"fence_order_seal_due_insert\",\n \"fence_order_seal_due_update\",\n \"fence_order_tax_line_delete\",\n \"fence_order_tax_line_insert\",\n \"fence_order_tax_line_update\",\n \"fence_order_update\",\n \"fence_outbox_event_delete\",\n \"fence_outbox_event_insert\",\n \"fence_outbox_event_update\",\n \"fence_pending_expiry_delete\",\n \"fence_pending_expiry_insert\",\n \"fence_pending_expiry_update\",\n \"fence_plugin_participation_delete\",\n \"fence_plugin_participation_insert\",\n \"fence_plugin_participation_update\",\n \"fence_plugin_state_delete\",\n \"fence_plugin_state_insert\",\n \"fence_plugin_state_update\",\n \"fence_policy_acceptance_delete\",\n \"fence_policy_acceptance_insert\",\n \"fence_policy_acceptance_update\",\n \"fence_policy_revision_delete\",\n \"fence_policy_revision_insert\",\n \"fence_policy_revision_update\",\n \"fence_price_delete\",\n \"fence_price_insert\",\n \"fence_price_list_delete\",\n \"fence_price_list_insert\",\n \"fence_price_list_update\",\n \"fence_price_update\",\n \"fence_principal_alias_delete\",\n \"fence_principal_alias_insert\",\n \"fence_principal_alias_update\",\n \"fence_privacy_change_delete\",\n \"fence_privacy_change_insert\",\n \"fence_privacy_change_update\",\n \"fence_privacy_generation_delete\",\n \"fence_privacy_generation_insert\",\n \"fence_privacy_generation_update\",\n \"fence_privacy_store_delete\",\n \"fence_privacy_store_insert\",\n \"fence_privacy_store_update\",\n \"fence_product_delete\",\n \"fence_product_insert\",\n \"fence_product_update\",\n \"fence_release_delivery_delete\",\n \"fence_release_delivery_insert\",\n \"fence_release_delivery_update\",\n \"fence_release_obligation_delete\",\n \"fence_release_obligation_insert\",\n \"fence_release_obligation_update\",\n \"fence_reopenable_hold_pool_delete\",\n \"fence_reopenable_hold_pool_insert\",\n \"fence_reopenable_hold_pool_update\",\n \"fence_resolution_delivery_delete\",\n \"fence_resolution_delivery_insert\",\n \"fence_resolution_delivery_update\",\n \"fence_role_delete\",\n \"fence_role_grant_delete\",\n \"fence_role_grant_insert\",\n \"fence_role_grant_update\",\n \"fence_role_insert\",\n \"fence_role_update\",\n \"fence_sequence_delete\",\n \"fence_sequence_insert\",\n \"fence_sequence_update\",\n \"fence_shortage_queue_delete\",\n \"fence_shortage_queue_insert\",\n \"fence_shortage_queue_update\",\n \"fence_shortage_seen_delete\",\n \"fence_shortage_seen_insert\",\n \"fence_shortage_seen_update\",\n \"fence_staff_delete\",\n \"fence_staff_insert\",\n \"fence_staff_invite_delete\",\n \"fence_staff_invite_insert\",\n \"fence_staff_invite_update\",\n \"fence_staff_location_delete\",\n \"fence_staff_location_insert\",\n \"fence_staff_location_update\",\n \"fence_staff_role_delete\",\n \"fence_staff_role_insert\",\n \"fence_staff_role_update\",\n \"fence_staff_update\",\n \"fence_stock_location_delete\",\n \"fence_stock_location_insert\",\n \"fence_stock_location_update\",\n \"fence_store_calendar_delete\",\n \"fence_store_calendar_insert\",\n \"fence_store_calendar_update\",\n \"fence_store_holiday_delete\",\n \"fence_store_holiday_insert\",\n \"fence_store_holiday_update\",\n \"fence_store_time_delete\",\n \"fence_store_time_insert\",\n \"fence_store_time_update\",\n \"fence_storefront_delete\",\n \"fence_storefront_insert\",\n \"fence_storefront_update\",\n \"fence_subscriber_delivery_delete\",\n \"fence_subscriber_delivery_insert\",\n \"fence_subscriber_delivery_update\",\n \"fence_subscriber_obligation_delete\",\n \"fence_subscriber_obligation_insert\",\n \"fence_subscriber_obligation_update\",\n \"fence_tax_setting_delete\",\n \"fence_tax_setting_insert\",\n \"fence_tax_setting_update\",\n \"fence_timer_delete\",\n \"fence_timer_firing_delete\",\n \"fence_timer_firing_insert\",\n \"fence_timer_firing_update\",\n \"fence_timer_insert\",\n \"fence_timer_owner_delete\",\n \"fence_timer_owner_insert\",\n \"fence_timer_owner_update\",\n \"fence_timer_update\",\n \"fence_track_state_delete\",\n \"fence_track_state_insert\",\n \"fence_track_state_update\",\n \"fence_transition_delete\",\n \"fence_transition_insert\",\n \"fence_transition_note_delete\",\n \"fence_transition_note_insert\",\n \"fence_transition_note_update\",\n \"fence_transition_update\",\n \"fence_tz_transition_delete\",\n \"fence_tz_transition_insert\",\n \"fence_tz_transition_update\",\n \"fence_variant_delete\",\n \"fence_variant_insert\",\n \"fence_variant_inventory_item_delete\",\n \"fence_variant_inventory_item_insert\",\n \"fence_variant_inventory_item_update\",\n \"fence_variant_update\",\n \"fence_webhook_attempt_delete\",\n \"fence_webhook_attempt_insert\",\n \"fence_webhook_attempt_update\",\n \"fence_webhook_delivery_delete\",\n \"fence_webhook_delivery_insert\",\n \"fence_webhook_delivery_update\",\n \"fence_webhook_endpoint_delete\",\n \"fence_webhook_endpoint_event_delete\",\n \"fence_webhook_endpoint_event_insert\",\n \"fence_webhook_endpoint_event_update\",\n \"fence_webhook_endpoint_grant_delete\",\n \"fence_webhook_endpoint_grant_insert\",\n \"fence_webhook_endpoint_grant_update\",\n \"fence_webhook_endpoint_insert\",\n \"fence_webhook_endpoint_update\",\n \"fence_webhook_secret_delete\",\n \"fence_webhook_secret_insert\",\n \"fence_webhook_secret_update\",\n \"kedge_assert_check\",\n \"kedge_assert_clear\"\n ],\n \"raises\": [\n {\n \"code\": \"KEDGE_AUDIT_MISSING\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_COMMIT_OBLIGATION\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_FENCED\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_MARKER_IMMUTABLE\",\n \"error\": \"INTERNAL (a core defect)\"\n }\n ]\n },\n {\n \"id\": \"INV-ACCESS-002\",\n \"title\": \"Every staff, key and CLI write is audited\",\n \"rule\": \"Audit rows are written before their batch's marker and never change. Admin mutations go through core's admin batch, which writes them.\",\n \"check\": null,\n \"objects\": [\n \"audit_log_delete\",\n \"audit_log_insert\",\n \"audit_log_update\"\n ],\n \"raises\": [\n {\n \"code\": \"KEDGE_AUDIT_IMMUTABLE\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_AUDIT_ORDER\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_AUDIT_PENDING\",\n \"error\": \"INTERNAL (a core defect)\"\n }\n ]\n },\n {\n \"id\": \"INV-ACCESS-003\",\n \"title\": \"Access is bounded, and the store keeps an Owner\",\n \"rule\": \"Roles, grants, keys and invites are bounded; built-in roles don't change; a revoked key stays revoked; the last active Owner can't be removed. Manage access through the admin routes.\",\n \"check\": null,\n \"objects\": [\n \"api_key_delete\",\n \"api_key_grant_delete\",\n \"api_key_grant_immutable\",\n \"api_key_grant_insert\",\n \"api_key_guard\",\n \"api_key_location_bound\",\n \"api_key_location_delete\",\n \"api_key_location_immutable\",\n \"api_key_storefront_bound\",\n \"auth_attempt_once\",\n \"auth_session_guard\",\n \"auth_totp_step\",\n \"customer_delete\",\n \"customer_guard\",\n \"idempotency_key_guard\",\n \"privacy_store_activate\",\n \"privacy_store_no_delete\",\n \"role_delete\",\n \"role_grant_delete\",\n \"role_grant_immutable\",\n \"role_grant_insert\",\n \"role_grant_version_delete\",\n \"role_grant_version_insert\",\n \"role_guard\",\n \"staff_delete\",\n \"staff_guard\",\n \"staff_invite_bound\",\n \"staff_invite_guard\",\n \"staff_location_bound\",\n \"staff_role_bound\",\n \"staff_role_immutable\",\n \"staff_role_last_owner\"\n ],\n \"raises\": [\n {\n \"code\": \"KEDGE_ATTEMPT_DONE\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_BOUND\",\n \"error\": \"LIMIT_EXCEEDED\"\n },\n {\n \"code\": \"KEDGE_CUSTOMER_IMMUTABLE\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_IDEMPOTENCY_IMMUTABLE\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_INVITE_IMMUTABLE\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_KEY_IMMUTABLE\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_KEY_REVOKED\",\n \"error\": \"KEY_REVOKED\"\n },\n {\n \"code\": \"KEDGE_LAST_OWNER\",\n \"error\": \"LAST_OWNER\"\n },\n {\n \"code\": \"KEDGE_PRIVACY_STALE\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_PRIVACY_STORE\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_ROLE_BUILTIN\",\n \"error\": \"FORBIDDEN\"\n },\n {\n \"code\": \"KEDGE_ROLE_IMMUTABLE\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_ROLE_IN_USE\",\n \"error\": \"CONFLICT\"\n },\n {\n \"code\": \"KEDGE_ROLE_WILDCARD\",\n \"error\": \"BAD_REQUEST\"\n },\n {\n \"code\": \"KEDGE_SESSION_IMMUTABLE\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_STAFF_IMMUTABLE\",\n \"error\": \"INTERNAL (a core defect)\"\n },\n {\n \"code\": \"KEDGE_TOTP_REPLAY\",\n \"error\": \"AUTH_CEREMONY_STALE\"\n }\n ]\n },\n {\n \"id\": \"INV-CAL-001\",\n \"title\": \"The store's calendar is bounded\",\n \"rule\": \"Holidays are within the calendar's window and capped; the calendar changes through its route.\",\n \"check\": null,\n \"objects\": [\n \"store_calendar_changed\",\n \"store_calendar_guard\",\n \"store_holiday_day\",\n \"store_holiday_insert\"\n ],\n \"raises\": [\n {\n \"code\": \"KEDGE_CALENDAR_DAY_OUT_OF_RANGE\",\n \"error\": \"CALENDAR_DAY_OUT_OF_RANGE\"\n },\n {\n \"code\": \"KEDGE_CALENDAR_HOLIDAYS_MAX\",\n \"error\": \"LIMIT_EXCEEDED\"\n },\n {\n \"code\": \"KEDGE_CALENDAR_IMMUTABLE\",\n \"error\": \"INTERNAL (a core defect)\"\n }\n ]\n }\n ],\n \"plugins\": []\n}\n","kedge.config.ts":"// Behaviour, not data (docs/07): plugins and hooks. Markets, products and\n// prices live in the database. Evaluated at build time; a bad declaration is\n// a boot error.\nimport { defineConfig } from \"@kedge/plugin-sdk\";\nimport { captureEmailProvider } from \"@kedge/runtime-cf/synthetic\";\n\nexport default defineConfig({\n plugins: [],\n // What a preview uses instead (p0.8 §5.2): sign-in emails go to its capture\n // log, never to anyone. Production never reads this.\n preview: { email: captureEmailProvider() },\n});\n","kedge.lock.json":"{\n \"version\": 1,\n \"entries\": [\n {\n \"file\": \"0000_schema.sql\",\n \"source\": \"core:0000_schema.sql\",\n \"sha256\": \"31d99f65263f43480cc25e249197a6555e33166e28785cf0859b7cfd2bed53ad\"\n },\n {\n \"file\": \"0001_triggers.sql\",\n \"source\": \"core:0001_triggers.sql\",\n \"sha256\": \"61f5a9a0aec224dc5b69778a4143f3476d5c814350979bfbbb9547269bd688f7\"\n },\n {\n \"file\": \"0002_stock_bounds.sql\",\n \"source\": \"core:0002_stock_bounds.sql\",\n \"sha256\": \"4e4b63d22aacceb196a2a54caadee9619d392b83b2eb82a3396871f0538d914b\"\n },\n {\n \"file\": \"0003_placed_snapshot.sql\",\n \"source\": \"core:0003_placed_snapshot.sql\",\n \"sha256\": \"562f081551ae31f831b3639e2be80ed42c8297728b76318ba0433f33c0309188\"\n },\n {\n \"file\": \"0004_extension_spine.sql\",\n \"source\": \"core:0004_extension_spine.sql\",\n \"sha256\": \"0334f44e4d845a1c10ff93f21182ab51ef3eac94609d68302c3f2c58e54487af\"\n },\n {\n \"file\": \"0005_extension_triggers.sql\",\n \"source\": \"core:0005_extension_triggers.sql\",\n \"sha256\": \"d8eaf64378c6edb363b3097dbc801ca124a3760384b0972040ec9fcab143048d\"\n },\n {\n \"file\": \"0006_line_adjustment_index.sql\",\n \"source\": \"core:0006_line_adjustment_index.sql\",\n \"sha256\": \"a7f6e22697774822ad49251fe7fe9fcb3afcff98c7613f0c81295d84e698a98a\"\n },\n {\n \"file\": \"0007_order_email_index.sql\",\n \"source\": \"core:0007_order_email_index.sql\",\n \"sha256\": \"b209f5885204bbad74fd3e4893852d30d0f34b632dd2799396ff9d3286e0bd0c\"\n },\n {\n \"file\": \"0008_obligation_plugin_index.sql\",\n \"source\": \"core:0008_obligation_plugin_index.sql\",\n \"sha256\": \"a75db2f3935f9b10ac296cdefc3e9205d3c38ac28ccf3372b8133c74969cbf4d\"\n },\n {\n \"file\": \"0009_allocation_owner.sql\",\n \"source\": \"core:0009_allocation_owner.sql\",\n \"sha256\": \"158760a56e6f86dfb94daa54bdbe16c29cd090a4206262c698e3515150094bef\"\n },\n {\n \"file\": \"0010_backfill_spine.sql\",\n \"source\": \"core:0010_backfill_spine.sql\",\n \"sha256\": \"214f39e150cc50890d108193a0162ec87bf08173a3928f294abf2c44d97ebd5b\"\n },\n {\n \"file\": \"0011_fact_backing_bound.sql\",\n \"source\": \"core:0011_fact_backing_bound.sql\",\n \"sha256\": \"9152ccdcc2f5338bea7b02d83150e8aecf6b398dc10bbc5131ec2da89611ec05\"\n },\n {\n \"file\": \"0012_participation_releases.sql\",\n \"source\": \"core:0012_participation_releases.sql\",\n \"sha256\": \"d67a2c82e5bbe8bcf35009fa49c0d6775681d6fb1ad0dca591861c0d8c4636b7\"\n },\n {\n \"file\": \"0013_release_obligations_by_handler.sql\",\n \"source\": \"core:0013_release_obligations_by_handler.sql\",\n \"sha256\": \"38edc6b87276a61ec261bd0f1a3c42dfc55d6b276fce2d948ab3dbf2f2d75b4a\"\n },\n {\n \"file\": \"0014_obligation_kind_index.sql\",\n \"source\": \"core:0014_obligation_kind_index.sql\",\n \"sha256\": \"86f5955cc867257429ca3356ec77218e1256506b9baaf3cb9a9444edb9b58db5\"\n },\n {\n \"file\": \"0015_release_delivery.sql\",\n \"source\": \"core:0015_release_delivery.sql\",\n \"sha256\": \"981148fe204e63d1d6842a73c0e2035985de28fe5421c50ebcd890a6accdea70\"\n },\n {\n \"file\": \"0016_canonical_order_email.sql\",\n \"source\": \"core:0016_canonical_order_email.sql\",\n \"sha256\": \"b9d4ec09846c732f08f6d679307b221f91341d8cf87998c649a7421f63b5c611\"\n },\n {\n \"file\": \"0017_delivery_needs_pending.sql\",\n \"source\": \"core:0017_delivery_needs_pending.sql\",\n \"sha256\": \"11bec96e42f623514d6cfa1fb7823586e984280bcf31d67fca1519ac2cb704c8\"\n },\n {\n \"file\": \"0018_line_model.sql\",\n \"source\": \"core:0018_line_model.sql\",\n \"sha256\": \"69f0f70964407a65f3293e6ff0fb173c5e4e01467a1426927f970199d8a0a7c9\"\n },\n {\n \"file\": \"0019_line_model_triggers.sql\",\n \"source\": \"core:0019_line_model_triggers.sql\",\n \"sha256\": \"d3bf992706f1fa4d372efb23bc8f114c0a2893663566152cfbe279bca38ede16\"\n },\n {\n \"file\": \"0020_placed_class_totals.sql\",\n \"source\": \"core:0020_placed_class_totals.sql\",\n \"sha256\": \"8d6af37eb31f2b39e57a9d4894d3baffdb8f4975416b948deebbc1186ec5d420\"\n },\n {\n \"file\": \"0021_reopenable.sql\",\n \"source\": \"core:0021_reopenable.sql\",\n \"sha256\": \"f01394d484227f7d0dfdf1b664cc16a3ed5fbb0d641973eff19356687dfde71b\"\n },\n {\n \"file\": \"0022_dated_capacity.sql\",\n \"source\": \"core:0022_dated_capacity.sql\",\n \"sha256\": \"d8b6095572d9b8a0f30d1f34ee5641f4c127e2fe10c19731c41197b959918b50\"\n },\n {\n \"file\": \"0023_dated_capacity_triggers.sql\",\n \"source\": \"core:0023_dated_capacity_triggers.sql\",\n \"sha256\": \"1477a4af2ab95212eca4b375248cce89d62a0f9972b8a61d285ce670fbf1b4b0\"\n },\n {\n \"file\": \"0024_inventory_level_rebuild.sql\",\n \"source\": \"core:0024_inventory_level_rebuild.sql\",\n \"sha256\": \"0322261c8c49598a3879869648b47b0fa2d3025f398eee7708f9df013439b796\"\n },\n {\n \"file\": \"0025_auth_access.sql\",\n \"source\": \"core:0025_auth_access.sql\",\n \"sha256\": \"4badc08fad681fd9a777e7599ad9fa6dff578f67cfff3fc46a1764726d53356c\"\n },\n {\n \"file\": \"0026_relay_timers.sql\",\n \"source\": \"core:0026_relay_timers.sql\",\n \"sha256\": \"eed518a6ef4834f1c62e9f374c02118adabaa03a1d2c1c94a71421992f51d3c4\"\n },\n {\n \"file\": \"0027_webhooks_calendar.sql\",\n \"source\": \"core:0027_webhooks_calendar.sql\",\n \"sha256\": \"6140f8b1c1e3a00dd56fb9e2f42e076ac469f492b6f94c2902f58a6b5554d71e\"\n },\n {\n \"file\": \"0028_tracks_entities.sql\",\n \"source\": \"core:0028_tracks_entities.sql\",\n \"sha256\": \"bbdad5c12570bc528abd5b294fa4277b780940fcf773ea67403e9b2324321c12\"\n }\n ],\n \"plugins\": {}\n}\n","local/.gitkeep":"","migrations/0000_schema.sql":"CREATE TABLE `checkout_fence` (\n\t`cart_id` text PRIMARY KEY NOT NULL,\n\t`generation` integer NOT NULL,\n\t`attempt_id` text NOT NULL,\n\tCONSTRAINT \"checkout_fence_generation_int\" CHECK(typeof(\"checkout_fence\".\"generation\") = 'integer' AND \"checkout_fence\".\"generation\" >= 1)\n);\n--> statement-breakpoint\nCREATE TABLE `inventory_item` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`sku` text NOT NULL\n);\n--> statement-breakpoint\nCREATE UNIQUE INDEX `inventory_item_sku_unique` ON `inventory_item` (`sku`);--> statement-breakpoint\nCREATE TABLE `inventory_level` (\n\t`inventory_item_id` text NOT NULL,\n\t`location_id` text NOT NULL,\n\t`on_hand` integer NOT NULL,\n\t`reserved` integer DEFAULT 0 NOT NULL,\n\t`safety_stock` integer DEFAULT 0 NOT NULL,\n\t`allow_backorder` integer DEFAULT 0 NOT NULL,\n\tPRIMARY KEY(`inventory_item_id`, `location_id`),\n\tFOREIGN KEY (`inventory_item_id`) REFERENCES `inventory_item`(`id`) ON UPDATE no action ON DELETE no action,\n\tFOREIGN KEY (`location_id`) REFERENCES `stock_location`(`id`) ON UPDATE no action ON DELETE no action,\n\tCONSTRAINT \"inventory_level_on_hand_int\" CHECK(typeof(\"inventory_level\".\"on_hand\") = 'integer' AND \"inventory_level\".\"on_hand\" >= 0),\n\tCONSTRAINT \"inventory_level_reserved_int\" CHECK(typeof(\"inventory_level\".\"reserved\") = 'integer'),\n\tCONSTRAINT \"inventory_level_safety_stock_int\" CHECK(typeof(\"inventory_level\".\"safety_stock\") = 'integer' AND \"inventory_level\".\"safety_stock\" >= 0),\n\tCONSTRAINT \"inventory_level_allow_backorder_bool\" CHECK(\"inventory_level\".\"allow_backorder\" IN (0, 1)),\n\tCONSTRAINT \"inv_no_oversell\" CHECK(\"inventory_level\".\"reserved\" >= 0 AND (\"inventory_level\".\"allow_backorder\" = 1 OR \"inventory_level\".\"reserved\" <= \"inventory_level\".\"on_hand\"))\n);\n--> statement-breakpoint\nCREATE TABLE `inventory_reservation` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`order_id` text NOT NULL,\n\t`order_line_id` text NOT NULL,\n\t`inventory_item_id` text NOT NULL,\n\t`location_id` text NOT NULL,\n\t`quantity` integer NOT NULL,\n\t`released_at` integer,\n\tFOREIGN KEY (`order_id`) REFERENCES `order`(`id`) ON UPDATE no action ON DELETE no action,\n\tFOREIGN KEY (`order_line_id`) REFERENCES `order_line`(`id`) ON UPDATE no action ON DELETE no action,\n\tFOREIGN KEY (`inventory_item_id`,`location_id`) REFERENCES `inventory_level`(`inventory_item_id`,`location_id`) ON UPDATE no action ON DELETE no action,\n\tCONSTRAINT \"inventory_reservation_quantity_int\" CHECK(typeof(\"inventory_reservation\".\"quantity\") = 'integer' AND \"inventory_reservation\".\"quantity\" BETWEEN 1 AND 10000000000000)\n);\n--> statement-breakpoint\nCREATE INDEX `inventory_reservation_order` ON `inventory_reservation` (`order_id`);--> statement-breakpoint\nCREATE INDEX `inventory_reservation_level` ON `inventory_reservation` (`inventory_item_id`,`location_id`);--> statement-breakpoint\nCREATE TABLE `order` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`cart_id` text NOT NULL,\n\t`attempt_id` text NOT NULL,\n\t`status` text NOT NULL,\n\t`payment_status` text DEFAULT 'unpaid' NOT NULL,\n\t`number` integer,\n\t`currency_code` text NOT NULL,\n\t`email` text,\n\t`subtotal` integer NOT NULL,\n\t`discount_total` integer NOT NULL,\n\t`shipping_total` integer NOT NULL,\n\t`fee_total` integer NOT NULL,\n\t`tax_total` integer NOT NULL,\n\t`total` integer NOT NULL,\n\t`reservation_count` integer NOT NULL,\n\t`expires_at` integer NOT NULL,\n\t`created_at` integer NOT NULL,\n\t`placed_at` integer,\n\t`expired_at` integer,\n\tCONSTRAINT \"order_status\" CHECK(\"order\".\"status\" IN ('pending_payment', 'open', 'expired', 'canceled', 'completed')),\n\tCONSTRAINT \"order_subtotal_int\" CHECK(typeof(\"order\".\"subtotal\") = 'integer' AND \"order\".\"subtotal\" BETWEEN -10000000000000 AND 10000000000000),\n\tCONSTRAINT \"order_discount_total_int\" CHECK(typeof(\"order\".\"discount_total\") = 'integer' AND \"order\".\"discount_total\" BETWEEN -10000000000000 AND 10000000000000),\n\tCONSTRAINT \"order_shipping_total_int\" CHECK(typeof(\"order\".\"shipping_total\") = 'integer' AND \"order\".\"shipping_total\" BETWEEN -10000000000000 AND 10000000000000),\n\tCONSTRAINT \"order_fee_total_int\" CHECK(typeof(\"order\".\"fee_total\") = 'integer' AND \"order\".\"fee_total\" BETWEEN -10000000000000 AND 10000000000000),\n\tCONSTRAINT \"order_tax_total_int\" CHECK(typeof(\"order\".\"tax_total\") = 'integer' AND \"order\".\"tax_total\" BETWEEN -10000000000000 AND 10000000000000),\n\tCONSTRAINT \"order_total_int\" CHECK(typeof(\"order\".\"total\") = 'integer' AND \"order\".\"total\" BETWEEN -10000000000000 AND 10000000000000),\n\tCONSTRAINT \"order_reservation_count_int\" CHECK(typeof(\"order\".\"reservation_count\") = 'integer' AND \"order\".\"reservation_count\" >= 0)\n);\n--> statement-breakpoint\nCREATE UNIQUE INDEX `order_cart_id_unique` ON `order` (`cart_id`);--> statement-breakpoint\nCREATE UNIQUE INDEX `order_number_unique` ON `order` (`number`);--> statement-breakpoint\nCREATE INDEX `order_status_expires` ON `order` (`status`,`expires_at`);--> statement-breakpoint\nCREATE INDEX `order_status_created` ON `order` (`status`,`created_at`);--> statement-breakpoint\nCREATE TABLE `order_event` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`order_id` text NOT NULL,\n\t`type` text NOT NULL,\n\t`actor` text,\n\t`created_at` integer NOT NULL\n);\n--> statement-breakpoint\nCREATE INDEX `order_event_order` ON `order_event` (`order_id`);--> statement-breakpoint\nCREATE TABLE `order_line` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`order_id` text NOT NULL,\n\t`variant_id` text NOT NULL,\n\t`product_id` text NOT NULL,\n\t`sku` text NOT NULL,\n\t`title` text NOT NULL,\n\t`unit_price` integer NOT NULL,\n\t`quantity` integer NOT NULL,\n\tFOREIGN KEY (`order_id`) REFERENCES `order`(`id`) ON UPDATE no action ON DELETE no action,\n\tCONSTRAINT \"order_line_unit_price_int\" CHECK(typeof(\"order_line\".\"unit_price\") = 'integer' AND \"order_line\".\"unit_price\" BETWEEN 0 AND 100000000000),\n\tCONSTRAINT \"order_line_quantity_int\" CHECK(typeof(\"order_line\".\"quantity\") = 'integer' AND \"order_line\".\"quantity\" BETWEEN 1 AND 100000)\n);\n--> statement-breakpoint\nCREATE INDEX `order_line_order` ON `order_line` (`order_id`);--> statement-breakpoint\nCREATE TABLE `outbox_event` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`aggregate_type` text NOT NULL,\n\t`aggregate_id` text NOT NULL,\n\t`type` text NOT NULL,\n\t`payload` text NOT NULL,\n\t`created_at` integer NOT NULL,\n\t`dispatched_at` integer\n);\n--> statement-breakpoint\nCREATE INDEX `outbox_event_undispatched` ON `outbox_event` (`dispatched_at`,`created_at`);--> statement-breakpoint\nCREATE UNIQUE INDEX `outbox_event_one_shot` ON `outbox_event` (`aggregate_id`,`type`) WHERE type IN ('order.created', 'order.placed');--> statement-breakpoint\nCREATE TABLE `price` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`price_list_id` text NOT NULL,\n\t`variant_id` text NOT NULL,\n\t`currency_code` text NOT NULL,\n\t`amount` integer NOT NULL,\n\tFOREIGN KEY (`price_list_id`) REFERENCES `price_list`(`id`) ON UPDATE no action ON DELETE no action,\n\tFOREIGN KEY (`variant_id`) REFERENCES `variant`(`id`) ON UPDATE no action ON DELETE no action,\n\tCONSTRAINT \"price_amount_int\" CHECK(typeof(\"price\".\"amount\") = 'integer' AND \"price\".\"amount\" BETWEEN 0 AND 100000000000)\n);\n--> statement-breakpoint\nCREATE UNIQUE INDEX `price_list_variant_currency` ON `price` (`price_list_id`,`variant_id`,`currency_code`);--> statement-breakpoint\nCREATE TABLE `price_list` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`name` text NOT NULL,\n\t`type` text NOT NULL\n);\n--> statement-breakpoint\nCREATE TABLE `product` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`title` text NOT NULL,\n\t`handle` text NOT NULL,\n\t`created_at` integer NOT NULL\n);\n--> statement-breakpoint\nCREATE UNIQUE INDEX `product_handle_unique` ON `product` (`handle`);--> statement-breakpoint\nCREATE TABLE `sequence` (\n\t`name` text PRIMARY KEY NOT NULL,\n\t`value` integer NOT NULL,\n\tCONSTRAINT \"sequence_value_int\" CHECK(typeof(\"sequence\".\"value\") = 'integer')\n);\n--> statement-breakpoint\nCREATE TABLE `stock_location` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`name` text NOT NULL,\n\t`priority` integer DEFAULT 100 NOT NULL,\n\tCONSTRAINT \"stock_location_priority_int\" CHECK(typeof(\"stock_location\".\"priority\") = 'integer')\n);\n--> statement-breakpoint\nCREATE TABLE `variant` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`product_id` text NOT NULL,\n\t`sku` text NOT NULL,\n\t`title` text NOT NULL,\n\tFOREIGN KEY (`product_id`) REFERENCES `product`(`id`) ON UPDATE no action ON DELETE no action\n);\n--> statement-breakpoint\nCREATE UNIQUE INDEX `variant_sku_unique` ON `variant` (`sku`);--> statement-breakpoint\nCREATE INDEX `variant_product` ON `variant` (`product_id`);--> statement-breakpoint\nCREATE TABLE `variant_inventory_item` (\n\t`variant_id` text NOT NULL,\n\t`inventory_item_id` text NOT NULL,\n\t`quantity` integer NOT NULL,\n\tPRIMARY KEY(`variant_id`, `inventory_item_id`),\n\tFOREIGN KEY (`variant_id`) REFERENCES `variant`(`id`) ON UPDATE no action ON DELETE no action,\n\tFOREIGN KEY (`inventory_item_id`) REFERENCES `inventory_item`(`id`) ON UPDATE no action ON DELETE no action,\n\tCONSTRAINT \"variant_inventory_item_quantity_int\" CHECK(typeof(\"variant_inventory_item\".\"quantity\") = 'integer' AND \"variant_inventory_item\".\"quantity\" BETWEEN 1 AND 100000)\n);\n","migrations/0001_triggers.sql":"-- Hand-written: the invariants Drizzle can't express (phase-0-foundation.md §2.4).\n-- Reservations are the fact and the level counters follow them by trigger\n-- (D-027); every consequence of an order transition is written in the\n-- transition's own trigger, so a guarded UPDATE that loses its race writes\n-- nothing else (D-028). One trigger per transition: SQLite promises no order\n-- between several triggers on the same change.\n-- RAISE with an expression names the item that ran out; Part A confirmed D1 accepts it.\n-- One-shot order events take their id from the order's ULID (evt_<ULID>.placed),\n-- so trigger-written ids stay sortable and unique per order and type.\n\n-- Reservations: inserted active, only for a pending order, only if the stock predicate allows it.\nCREATE TRIGGER reservation_insert_check BEFORE INSERT ON inventory_reservation BEGIN\n SELECT RAISE(ABORT, 'KEDGE_RESERVATION_INSERTED_RELEASED') WHERE NEW.released_at IS NOT NULL;\n SELECT RAISE(ABORT, 'KEDGE_RESERVATION_ORDER_STATE')\n WHERE (SELECT status FROM \"order\" WHERE id = NEW.order_id) IS NOT 'pending_payment';\n SELECT RAISE(ABORT, 'KEDGE_OOS:' || NEW.inventory_item_id || ':' || NEW.location_id) FROM inventory_level\n WHERE inventory_item_id = NEW.inventory_item_id AND location_id = NEW.location_id\n AND allow_backorder = 0 AND reserved + NEW.quantity > on_hand - safety_stock;\nEND;\n--> statement-breakpoint\nCREATE TRIGGER reservation_insert_count AFTER INSERT ON inventory_reservation BEGIN\n UPDATE inventory_level SET reserved = reserved + NEW.quantity\n WHERE inventory_item_id = NEW.inventory_item_id AND location_id = NEW.location_id;\nEND;\n--> statement-breakpoint\n-- Never edited in place: a change would bypass the counter. Move or resize = release + insert.\nCREATE TRIGGER reservation_immutable BEFORE UPDATE OF order_id, order_line_id, inventory_item_id, location_id, quantity\n ON inventory_reservation BEGIN\n SELECT RAISE(ABORT, 'KEDGE_RESERVATION_IMMUTABLE');\nEND;\n--> statement-breakpoint\n-- Release and re-activation fire only on a change of nullness, so stamping twice can't count twice.\nCREATE TRIGGER reservation_release AFTER UPDATE OF released_at ON inventory_reservation\n WHEN OLD.released_at IS NULL AND NEW.released_at IS NOT NULL BEGIN\n UPDATE inventory_level SET reserved = reserved - NEW.quantity\n WHERE inventory_item_id = NEW.inventory_item_id AND location_id = NEW.location_id;\nEND;\n--> statement-breakpoint\nCREATE TRIGGER reservation_reactivate_check BEFORE UPDATE OF released_at ON inventory_reservation\n WHEN OLD.released_at IS NOT NULL AND NEW.released_at IS NULL BEGIN\n -- Only the reopen transition re-activates (the order is already 'open' inside its trigger).\n SELECT RAISE(ABORT, 'KEDGE_RESERVATION_ORDER_STATE')\n WHERE (SELECT status FROM \"order\" WHERE id = NEW.order_id) IS NOT 'open';\n SELECT RAISE(ABORT, 'KEDGE_OOS:' || NEW.inventory_item_id || ':' || NEW.location_id) FROM inventory_level\n WHERE inventory_item_id = NEW.inventory_item_id AND location_id = NEW.location_id\n AND allow_backorder = 0 AND reserved + NEW.quantity > on_hand - safety_stock;\nEND;\n--> statement-breakpoint\nCREATE TRIGGER reservation_reactivate AFTER UPDATE OF released_at ON inventory_reservation\n WHEN OLD.released_at IS NOT NULL AND NEW.released_at IS NULL BEGIN\n UPDATE inventory_level SET reserved = reserved + NEW.quantity\n WHERE inventory_item_id = NEW.inventory_item_id AND location_id = NEW.location_id;\nEND;\n--> statement-breakpoint\n-- Deleting is archiving only: never an active row (its quantity is in a counter),\n-- and never while the order can still reopen (an expired order's released rows\n-- are the facts re-reservation needs).\nCREATE TRIGGER reservation_delete BEFORE DELETE ON inventory_reservation\n WHEN OLD.released_at IS NULL OR (SELECT status FROM \"order\" WHERE id = OLD.order_id) NOT IN ('canceled', 'completed') BEGIN\n SELECT RAISE(ABORT, 'KEDGE_RESERVATION_IN_USE');\nEND;\n--> statement-breakpoint\n-- Checkout fence (D-030): an order can only be inserted by the attempt holding its cart's fence.\nCREATE TRIGGER order_fence BEFORE INSERT ON \"order\"\n WHEN NEW.attempt_id IS NOT (SELECT attempt_id FROM checkout_fence WHERE cart_id = NEW.cart_id) BEGIN\n SELECT RAISE(ABORT, 'KEDGE_STALE_ATTEMPT');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER order_release AFTER UPDATE OF status ON \"order\"\n WHEN OLD.status = 'pending_payment' AND NEW.status IN ('expired', 'canceled') BEGIN\n UPDATE inventory_reservation SET released_at = CAST(unixepoch('subsec') * 1000 AS INTEGER)\n WHERE order_id = NEW.id AND released_at IS NULL;\nEND;\n--> statement-breakpoint\nCREATE TRIGGER order_place AFTER UPDATE OF status ON \"order\"\n WHEN OLD.status = 'pending_payment' AND NEW.status = 'open' BEGIN\n UPDATE sequence SET value = value + 1 WHERE name = 'order_number';\n UPDATE \"order\" SET number = (SELECT value FROM sequence WHERE name = 'order_number'),\n placed_at = CAST(unixepoch('subsec') * 1000 AS INTEGER) WHERE id = NEW.id;\n INSERT INTO outbox_event (id, aggregate_type, aggregate_id, type, payload, created_at)\n VALUES ('evt_' || substr(NEW.id, 5) || '.placed', 'order', NEW.id, 'order.placed', '{}', CAST(unixepoch('subsec') * 1000 AS INTEGER));\nEND;\n--> statement-breakpoint\nCREATE TRIGGER order_reopen AFTER UPDATE OF status ON \"order\"\n WHEN OLD.status = 'expired' AND NEW.status = 'open' BEGIN\n UPDATE inventory_reservation SET released_at = NULL WHERE order_id = NEW.id AND released_at IS NOT NULL;\n SELECT RAISE(ABORT, 'KEDGE_RESERVATIONS_MISSING')\n WHERE (SELECT count(*) FROM inventory_reservation WHERE order_id = NEW.id AND released_at IS NULL) != NEW.reservation_count;\n UPDATE sequence SET value = value + 1 WHERE name = 'order_number';\n UPDATE \"order\" SET number = (SELECT value FROM sequence WHERE name = 'order_number'),\n placed_at = CAST(unixepoch('subsec') * 1000 AS INTEGER) WHERE id = NEW.id;\n INSERT INTO outbox_event (id, aggregate_type, aggregate_id, type, payload, created_at)\n VALUES ('evt_' || substr(NEW.id, 5) || '.placed', 'order', NEW.id, 'order.placed', '{}', CAST(unixepoch('subsec') * 1000 AS INTEGER));\nEND;\n--> statement-breakpoint\n-- Structural defaults every store starts with.\nINSERT INTO sequence (name, value) VALUES ('order_number', 1000);\n--> statement-breakpoint\nINSERT INTO price_list (id, name, type) VALUES ('pl_base', 'Base', 'base');\n--> statement-breakpoint\nINSERT INTO stock_location (id, name, priority) VALUES ('loc_default', 'Default', 1);\n","migrations/0002_stock_bounds.sql":"-- Upper bounds on stock counters (D-029): each stays at or below 10^12, so sums\n-- over many locations stay far below 2^53 and availability math is exact in\n-- JavaScript. Enforced by triggers, not by changing the CHECKs: in SQLite that\n-- means rebuilding the tables, and dropping inventory_reservation would drop\n-- every trigger defined on it.\nCREATE TRIGGER inventory_level_bounds_insert BEFORE INSERT ON inventory_level\n WHEN NEW.on_hand > 1000000000000 OR NEW.reserved > 1000000000000 OR NEW.safety_stock > 1000000000000 BEGIN\n SELECT RAISE(ABORT, 'KEDGE_STOCK_OUT_OF_RANGE');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER inventory_level_bounds_update BEFORE UPDATE OF on_hand, reserved, safety_stock ON inventory_level\n WHEN NEW.on_hand > 1000000000000 OR NEW.reserved > 1000000000000 OR NEW.safety_stock > 1000000000000 BEGIN\n SELECT RAISE(ABORT, 'KEDGE_STOCK_OUT_OF_RANGE');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER inventory_reservation_bounds BEFORE INSERT ON inventory_reservation\n WHEN NEW.quantity > 1000000000000 BEGIN\n SELECT RAISE(ABORT, 'KEDGE_STOCK_OUT_OF_RANGE');\nEND;\n","migrations/0003_placed_snapshot.sql":"-- order.placed events carry a snapshot of the order taken when the event is\n-- written (docs/05 §9), not '{}': delivery is deferred, but the data must be\n-- captured at the transition, before later changes. Replaces the two placement\n-- triggers from 0001 (an applied migration is never edited).\nDROP TRIGGER order_place;\n--> statement-breakpoint\nDROP TRIGGER order_reopen;\n--> statement-breakpoint\nCREATE TRIGGER order_place AFTER UPDATE OF status ON \"order\"\n WHEN OLD.status = 'pending_payment' AND NEW.status = 'open' BEGIN\n UPDATE sequence SET value = value + 1 WHERE name = 'order_number';\n UPDATE \"order\" SET number = (SELECT value FROM sequence WHERE name = 'order_number'),\n placed_at = CAST(unixepoch('subsec') * 1000 AS INTEGER) WHERE id = NEW.id;\n INSERT INTO outbox_event (id, aggregate_type, aggregate_id, type, payload, created_at)\n SELECT 'evt_' || substr(o.id, 5) || '.placed', 'order', o.id, 'order.placed',\n json_object('id', o.id, 'number', o.number, 'status', o.status, 'currency_code', o.currency_code, 'email', o.email,\n 'subtotal', o.subtotal, 'total', o.total, 'placed_at', o.placed_at,\n 'lines', (SELECT json_group_array(json_object('id', l.id, 'variant_id', l.variant_id, 'sku', l.sku, 'title', l.title,\n 'unit_price', l.unit_price, 'quantity', l.quantity))\n FROM order_line l WHERE l.order_id = o.id)),\n o.placed_at\n FROM \"order\" o WHERE o.id = NEW.id;\nEND;\n--> statement-breakpoint\nCREATE TRIGGER order_reopen AFTER UPDATE OF status ON \"order\"\n WHEN OLD.status = 'expired' AND NEW.status = 'open' BEGIN\n UPDATE inventory_reservation SET released_at = NULL WHERE order_id = NEW.id AND released_at IS NOT NULL;\n SELECT RAISE(ABORT, 'KEDGE_RESERVATIONS_MISSING')\n WHERE (SELECT count(*) FROM inventory_reservation WHERE order_id = NEW.id AND released_at IS NULL) != NEW.reservation_count;\n UPDATE sequence SET value = value + 1 WHERE name = 'order_number';\n UPDATE \"order\" SET number = (SELECT value FROM sequence WHERE name = 'order_number'),\n placed_at = CAST(unixepoch('subsec') * 1000 AS INTEGER) WHERE id = NEW.id;\n INSERT INTO outbox_event (id, aggregate_type, aggregate_id, type, payload, created_at)\n SELECT 'evt_' || substr(o.id, 5) || '.placed', 'order', o.id, 'order.placed',\n json_object('id', o.id, 'number', o.number, 'status', o.status, 'currency_code', o.currency_code, 'email', o.email,\n 'subtotal', o.subtotal, 'total', o.total, 'placed_at', o.placed_at,\n 'lines', (SELECT json_group_array(json_object('id', l.id, 'variant_id', l.variant_id, 'sku', l.sku, 'title', l.title,\n 'unit_price', l.unit_price, 'quantity', l.quantity))\n FROM order_line l WHERE l.order_id = o.id)),\n o.placed_at\n FROM \"order\" o WHERE o.id = NEW.id;\nEND;\n","migrations/0004_extension_spine.sql":"CREATE TABLE `contribution_check` (\n\t`order_id` text PRIMARY KEY NOT NULL\n);\n--> statement-breakpoint\nCREATE TABLE `contribution_fact` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`order_id` text NOT NULL,\n\t`plugin` text NOT NULL,\n\t`fact_table` text,\n\t`fact_key` text NOT NULL,\n\t`cardinality` integer NOT NULL,\n\t`backed_count` integer DEFAULT 0 NOT NULL,\n\tFOREIGN KEY (`order_id`) REFERENCES `order_index`(`id`) ON UPDATE no action ON DELETE no action,\n\tCONSTRAINT \"contribution_fact_cardinality\" CHECK(typeof(\"contribution_fact\".\"cardinality\") = 'integer' AND \"contribution_fact\".\"cardinality\" IN (0, 1) AND (\"contribution_fact\".\"fact_table\" IS NULL) = (\"contribution_fact\".\"cardinality\" = 0)),\n\tCONSTRAINT \"contribution_fact_backed_int\" CHECK(typeof(\"contribution_fact\".\"backed_count\") = 'integer' AND \"contribution_fact\".\"backed_count\" >= 0)\n);\n--> statement-breakpoint\nCREATE UNIQUE INDEX `contribution_fact_order_plugin_id` ON `contribution_fact` (`order_id`,`plugin`,`id`);--> statement-breakpoint\nCREATE TABLE `order_adjustment` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`order_id` text NOT NULL,\n\t`plugin` text NOT NULL,\n\t`contribution_fact_id` text NOT NULL,\n\t`code` text NOT NULL,\n\t`amount` integer NOT NULL,\n\tFOREIGN KEY (`order_id`) REFERENCES `order`(`id`) ON UPDATE no action ON DELETE no action,\n\tFOREIGN KEY (`order_id`,`plugin`,`contribution_fact_id`) REFERENCES `contribution_fact`(`order_id`,`plugin`,`id`) ON UPDATE no action ON DELETE no action,\n\tCONSTRAINT \"order_adjustment_amount_int\" CHECK(typeof(\"order_adjustment\".\"amount\") = 'integer' AND \"order_adjustment\".\"amount\" BETWEEN -10000000000000 AND -1)\n);\n--> statement-breakpoint\nCREATE INDEX `order_adjustment_order` ON `order_adjustment` (`order_id`);--> statement-breakpoint\nCREATE TABLE `order_fee` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`order_id` text NOT NULL,\n\t`plugin` text NOT NULL,\n\t`contribution_fact_id` text NOT NULL,\n\t`code` text NOT NULL,\n\t`amount` integer NOT NULL,\n\t`charge_class` text NOT NULL,\n\tFOREIGN KEY (`order_id`) REFERENCES `order`(`id`) ON UPDATE no action ON DELETE no action,\n\tFOREIGN KEY (`order_id`,`plugin`,`contribution_fact_id`) REFERENCES `contribution_fact`(`order_id`,`plugin`,`id`) ON UPDATE no action ON DELETE no action,\n\tCONSTRAINT \"order_fee_amount_int\" CHECK(typeof(\"order_fee\".\"amount\") = 'integer' AND \"order_fee\".\"amount\" BETWEEN 1 AND 10000000000000),\n\tCONSTRAINT \"order_fee_charge_class\" CHECK(\"order_fee\".\"charge_class\" IN ('revenue', 'deposit'))\n);\n--> statement-breakpoint\nCREATE INDEX `order_fee_order` ON `order_fee` (`order_id`);--> statement-breakpoint\nCREATE TABLE `order_index` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`created_at` integer NOT NULL\n);\n--> statement-breakpoint\nCREATE TABLE `order_line_adjustment` (\n\t`adjustment_id` text NOT NULL,\n\t`order_line_id` text NOT NULL,\n\t`amount` integer NOT NULL,\n\tPRIMARY KEY(`adjustment_id`, `order_line_id`),\n\tFOREIGN KEY (`adjustment_id`) REFERENCES `order_adjustment`(`id`) ON UPDATE no action ON DELETE no action,\n\tFOREIGN KEY (`order_line_id`) REFERENCES `order_line`(`id`) ON UPDATE no action ON DELETE no action,\n\tCONSTRAINT \"order_line_adjustment_amount_int\" CHECK(typeof(\"order_line_adjustment\".\"amount\") = 'integer' AND \"order_line_adjustment\".\"amount\" BETWEEN -10000000000000 AND -1)\n);\n--> statement-breakpoint\nCREATE TABLE `plugin_participation` (\n\t`seq` integer PRIMARY KEY AUTOINCREMENT NOT NULL,\n\t`plugin` text NOT NULL,\n\t`order_id` text NOT NULL,\n\tFOREIGN KEY (`order_id`) REFERENCES `order_index`(`id`) ON UPDATE no action ON DELETE no action\n);\n--> statement-breakpoint\nCREATE UNIQUE INDEX `plugin_participation_plugin_order` ON `plugin_participation` (`plugin`,`order_id`);--> statement-breakpoint\nCREATE INDEX `plugin_participation_order` ON `plugin_participation` (`order_id`);--> statement-breakpoint\nCREATE TABLE `plugin_state` (\n\t`plugin` text PRIMARY KEY NOT NULL,\n\t`installation_id` text NOT NULL,\n\t`state` text NOT NULL,\n\t`changed_at` integer NOT NULL,\n\tCONSTRAINT \"plugin_state_state\" CHECK(\"plugin_state\".\"state\" IN ('active', 'disabled', 'retiring', 'retired'))\n);\n--> statement-breakpoint\nCREATE TABLE `release_obligation` (\n\t`seq` integer PRIMARY KEY AUTOINCREMENT NOT NULL,\n\t`plugin` text NOT NULL,\n\t`transition_uid` text NOT NULL,\n\t`order_id` text NOT NULL,\n\t`from_state` text NOT NULL,\n\t`to_state` text NOT NULL,\n\t`state` text NOT NULL,\n\t`attempts` integer DEFAULT 0 NOT NULL,\n\t`last_error` text,\n\t`created_at` integer NOT NULL,\n\t`updated_at` integer NOT NULL,\n\tCONSTRAINT \"release_obligation_state\" CHECK(\"release_obligation\".\"state\" IN ('pending', 'done', 'failed')),\n\tCONSTRAINT \"release_obligation_attempts_int\" CHECK(typeof(\"release_obligation\".\"attempts\") = 'integer' AND \"release_obligation\".\"attempts\" >= 0)\n);\n--> statement-breakpoint\nCREATE UNIQUE INDEX `release_obligation_plugin_transition` ON `release_obligation` (`plugin`,`transition_uid`);--> statement-breakpoint\nCREATE INDEX `release_obligation_state` ON `release_obligation` (`state`,`seq`);--> statement-breakpoint\nCREATE TABLE `transition` (\n\t`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,\n\t`uid` text NOT NULL,\n\t`aggregate` text NOT NULL,\n\t`aggregate_id` text NOT NULL,\n\t`machine` text NOT NULL,\n\t`from_state` text NOT NULL,\n\t`to_state` text NOT NULL,\n\t`at` integer NOT NULL\n);\n--> statement-breakpoint\nCREATE UNIQUE INDEX `transition_uid_unique` ON `transition` (`uid`);--> statement-breakpoint\nCREATE INDEX `transition_aggregate` ON `transition` (`aggregate`,`aggregate_id`);","migrations/0005_extension_triggers.sql":"-- Hand-written: the extension spine's invariants (kernel K1, K2, §4.1). Every\n-- order status change now ends with one `transition` row, the only place\n-- plugin SQL may attach. Replaces 0003's placement triggers and 0001's\n-- release trigger (an applied migration is never edited). One trigger per\n-- transition: SQLite promises no order between several triggers on the same\n-- change, and plugin acquire triggers must see core's effects already written.\n\n-- Every order gets its permanent identity in the same statement that creates it.\nCREATE TRIGGER order_index_insert AFTER INSERT ON \"order\" BEGIN\n INSERT INTO order_index (id, created_at) VALUES (NEW.id, NEW.created_at);\nEND;\n--> statement-breakpoint\n-- Only these moves exist. A typo'd or skipped state can't slip past the\n-- transition triggers, which match exact pairs.\nCREATE TRIGGER order_status_guard BEFORE UPDATE OF status ON \"order\"\n WHEN NEW.status IS NOT OLD.status AND NOT (\n (OLD.status = 'pending_payment' AND NEW.status IN ('open', 'expired', 'canceled'))\n OR (OLD.status = 'expired' AND NEW.status = 'open')\n OR (OLD.status = 'open' AND NEW.status IN ('canceled', 'completed'))) BEGIN\n SELECT RAISE(ABORT, 'KEDGE_ORDER_TRANSITION');\nEND;\n--> statement-breakpoint\nDROP TRIGGER order_release;\n--> statement-breakpoint\nDROP TRIGGER order_place;\n--> statement-breakpoint\nDROP TRIGGER order_reopen;\n--> statement-breakpoint\nCREATE TRIGGER order_expire AFTER UPDATE OF status ON \"order\"\n WHEN OLD.status = 'pending_payment' AND NEW.status = 'expired' BEGIN\n UPDATE inventory_reservation SET released_at = CAST(unixepoch('subsec') * 1000 AS INTEGER)\n WHERE order_id = NEW.id AND released_at IS NULL;\n INSERT INTO transition (uid, aggregate, aggregate_id, machine, from_state, to_state, at)\n VALUES (NEW.id || ':expired', 'order', NEW.id, 'status', OLD.status, NEW.status, CAST(unixepoch('subsec') * 1000 AS INTEGER));\nEND;\n--> statement-breakpoint\n-- Release on every exit, an open order's cancellation included (docs/03).\nCREATE TRIGGER order_cancel AFTER UPDATE OF status ON \"order\"\n WHEN OLD.status IN ('pending_payment', 'open') AND NEW.status = 'canceled' BEGIN\n UPDATE inventory_reservation SET released_at = CAST(unixepoch('subsec') * 1000 AS INTEGER)\n WHERE order_id = NEW.id AND released_at IS NULL;\n INSERT INTO transition (uid, aggregate, aggregate_id, machine, from_state, to_state, at)\n VALUES (NEW.id || ':canceled', 'order', NEW.id, 'status', OLD.status, NEW.status, CAST(unixepoch('subsec') * 1000 AS INTEGER));\nEND;\n--> statement-breakpoint\nCREATE TRIGGER order_place AFTER UPDATE OF status ON \"order\"\n WHEN OLD.status = 'pending_payment' AND NEW.status = 'open' BEGIN\n UPDATE sequence SET value = value + 1 WHERE name = 'order_number';\n UPDATE \"order\" SET number = (SELECT value FROM sequence WHERE name = 'order_number'),\n placed_at = CAST(unixepoch('subsec') * 1000 AS INTEGER) WHERE id = NEW.id;\n INSERT INTO outbox_event (id, aggregate_type, aggregate_id, type, payload, created_at)\n SELECT 'evt_' || substr(o.id, 5) || '.placed', 'order', o.id, 'order.placed',\n json_object('id', o.id, 'number', o.number, 'status', o.status, 'currency_code', o.currency_code, 'email', o.email,\n 'subtotal', o.subtotal, 'total', o.total, 'placed_at', o.placed_at,\n 'lines', (SELECT json_group_array(json_object('id', l.id, 'variant_id', l.variant_id, 'sku', l.sku, 'title', l.title,\n 'unit_price', l.unit_price, 'quantity', l.quantity))\n FROM order_line l WHERE l.order_id = o.id)),\n o.placed_at\n FROM \"order\" o WHERE o.id = NEW.id;\n -- Both ways into `open` are the business occurrence \"placed\" (K1).\n INSERT INTO transition (uid, aggregate, aggregate_id, machine, from_state, to_state, at)\n VALUES (NEW.id || ':placed', 'order', NEW.id, 'status', OLD.status, NEW.status, CAST(unixepoch('subsec') * 1000 AS INTEGER));\nEND;\n--> statement-breakpoint\nCREATE TRIGGER order_reopen AFTER UPDATE OF status ON \"order\"\n WHEN OLD.status = 'expired' AND NEW.status = 'open' BEGIN\n UPDATE inventory_reservation SET released_at = NULL WHERE order_id = NEW.id AND released_at IS NOT NULL;\n SELECT RAISE(ABORT, 'KEDGE_RESERVATIONS_MISSING')\n WHERE (SELECT count(*) FROM inventory_reservation WHERE order_id = NEW.id AND released_at IS NULL) != NEW.reservation_count;\n UPDATE sequence SET value = value + 1 WHERE name = 'order_number';\n UPDATE \"order\" SET number = (SELECT value FROM sequence WHERE name = 'order_number'),\n placed_at = CAST(unixepoch('subsec') * 1000 AS INTEGER) WHERE id = NEW.id;\n INSERT INTO outbox_event (id, aggregate_type, aggregate_id, type, payload, created_at)\n SELECT 'evt_' || substr(o.id, 5) || '.placed', 'order', o.id, 'order.placed',\n json_object('id', o.id, 'number', o.number, 'status', o.status, 'currency_code', o.currency_code, 'email', o.email,\n 'subtotal', o.subtotal, 'total', o.total, 'placed_at', o.placed_at,\n 'lines', (SELECT json_group_array(json_object('id', l.id, 'variant_id', l.variant_id, 'sku', l.sku, 'title', l.title,\n 'unit_price', l.unit_price, 'quantity', l.quantity))\n FROM order_line l WHERE l.order_id = o.id)),\n o.placed_at\n FROM \"order\" o WHERE o.id = NEW.id;\n INSERT INTO transition (uid, aggregate, aggregate_id, machine, from_state, to_state, at)\n VALUES (NEW.id || ':placed', 'order', NEW.id, 'status', OLD.status, NEW.status, CAST(unixepoch('subsec') * 1000 AS INTEGER));\nEND;\n--> statement-breakpoint\nCREATE TRIGGER order_complete AFTER UPDATE OF status ON \"order\"\n WHEN OLD.status = 'open' AND NEW.status = 'completed' BEGIN\n INSERT INTO transition (uid, aggregate, aggregate_id, machine, from_state, to_state, at)\n VALUES (NEW.id || ':completed', 'order', NEW.id, 'status', OLD.status, NEW.status, CAST(unixepoch('subsec') * 1000 AS INTEGER));\nEND;\n--> statement-breakpoint\n-- History is written once. Archiving (a later slice) brings its own controlled path.\nCREATE TRIGGER transition_immutable BEFORE UPDATE ON transition BEGIN\n SELECT RAISE(ABORT, 'KEDGE_TRANSITION_IMMUTABLE');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER transition_no_delete BEFORE DELETE ON transition BEGIN\n SELECT RAISE(ABORT, 'KEDGE_TRANSITION_IMMUTABLE');\nEND;\n--> statement-breakpoint\n-- A release transition is an obligation to every participating plugin, in the\n-- transition's own batch, so no release can be lost between commit and\n-- delivery. Plugin SQL never runs synchronously here: any plugin write can\n-- fail, and that must not roll back core's stock release (K1).\nCREATE TRIGGER transition_release_obligations AFTER INSERT ON transition\n WHEN NEW.aggregate = 'order' AND NEW.machine = 'status' AND NEW.to_state IN ('expired', 'canceled') BEGIN\n INSERT INTO release_obligation (plugin, transition_uid, order_id, from_state, to_state, state, attempts, created_at, updated_at)\n SELECT plugin, NEW.uid, NEW.aggregate_id, NEW.from_state, NEW.to_state, 'pending', 0, NEW.at, NEW.at\n FROM plugin_participation WHERE order_id = NEW.aggregate_id;\nEND;\n--> statement-breakpoint\nCREATE TRIGGER release_obligation_guard BEFORE UPDATE ON release_obligation BEGIN\n SELECT RAISE(ABORT, 'KEDGE_OBLIGATION_IMMUTABLE')\n WHERE NEW.plugin IS NOT OLD.plugin OR NEW.transition_uid IS NOT OLD.transition_uid OR NEW.order_id IS NOT OLD.order_id\n OR NEW.from_state IS NOT OLD.from_state OR NEW.to_state IS NOT OLD.to_state OR NEW.created_at IS NOT OLD.created_at;\n -- done is final; failed goes back to pending only by a staff retry.\n SELECT RAISE(ABORT, 'KEDGE_OBLIGATION_STATE')\n WHERE NEW.state IS NOT OLD.state AND NOT (\n (OLD.state = 'pending' AND NEW.state IN ('done', 'failed')) OR (OLD.state = 'failed' AND NEW.state = 'pending'));\nEND;\n--> statement-breakpoint\nCREATE TRIGGER release_obligation_no_delete BEFORE DELETE ON release_obligation BEGIN\n SELECT RAISE(ABORT, 'KEDGE_OBLIGATION_IMMUTABLE');\nEND;\n--> statement-breakpoint\n-- Installed active; then only active <-> disabled until plugin removal ships\n-- (plugin-lifecycle.md). Rows are never deleted, so a name stays taken.\nCREATE TRIGGER plugin_state_insert BEFORE INSERT ON plugin_state WHEN NEW.state IS NOT 'active' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_PLUGIN_STATE');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER plugin_state_guard BEFORE UPDATE ON plugin_state BEGIN\n SELECT RAISE(ABORT, 'KEDGE_PLUGIN_STATE')\n WHERE NEW.plugin IS NOT OLD.plugin OR NEW.installation_id IS NOT OLD.installation_id\n OR (NEW.state IS NOT OLD.state AND NOT (\n (OLD.state = 'active' AND NEW.state = 'disabled') OR (OLD.state = 'disabled' AND NEW.state = 'active')));\nEND;\n--> statement-breakpoint\nCREATE TRIGGER plugin_state_no_delete BEFORE DELETE ON plugin_state BEGIN\n SELECT RAISE(ABORT, 'KEDGE_PLUGIN_STATE');\nEND;\n--> statement-breakpoint\n-- The participation barrier is in the commit, not a doctor check: a checkout\n-- planned before a disable fails here, even from an older Worker (§4.1).\n-- Checkout inserts plainly: SQLite runs BEFORE INSERT triggers before it\n-- resolves ON CONFLICT, so an \"insert or ignore\" would still hit this.\nCREATE TRIGGER plugin_participation_barrier BEFORE INSERT ON plugin_participation\n WHEN (SELECT state FROM plugin_state WHERE plugin = NEW.plugin) IS NOT 'active' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_PLUGIN_INACTIVE:' || NEW.plugin);\nEND;\n--> statement-breakpoint\nCREATE TRIGGER plugin_participation_immutable BEFORE UPDATE ON plugin_participation BEGIN\n SELECT RAISE(ABORT, 'KEDGE_PARTICIPATION_IMMUTABLE');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER plugin_participation_no_delete BEFORE DELETE ON plugin_participation BEGIN\n SELECT RAISE(ABORT, 'KEDGE_PARTICIPATION_IMMUTABLE');\nEND;\n--> statement-breakpoint\n-- An identity can only be minted for a plugin that participates in the order,\n-- and its membership tuple never changes afterwards (K2).\nCREATE TRIGGER contribution_fact_insert BEFORE INSERT ON contribution_fact\n WHEN NEW.backed_count != 0 OR NOT EXISTS (\n SELECT 1 FROM plugin_participation WHERE plugin = NEW.plugin AND order_id = NEW.order_id) BEGIN\n SELECT RAISE(ABORT, 'KEDGE_CONTRIBUTION_IDENTITY');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER contribution_fact_immutable BEFORE UPDATE OF id, order_id, plugin, fact_table, fact_key, cardinality\n ON contribution_fact BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FACT_IMMUTABLE');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER contribution_fact_no_delete BEFORE DELETE ON contribution_fact BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FACT_IMMUTABLE');\nEND;\n--> statement-breakpoint\n-- The commit's last statement: every fact backed exactly as declared, every\n-- adjustment fully allocated, and the order's stored totals equal to its rows.\n-- A zero-row INSERT ... SELECT by a plugin fails here, not silently (K2).\nCREATE TRIGGER contribution_check_assert BEFORE INSERT ON contribution_check BEGIN\n -- Names the plugin: a failed batch doesn't say which statement failed (research/15).\n SELECT RAISE(ABORT, 'KEDGE_CONTRIBUTION_UNBACKED:' || plugin) FROM (\n SELECT plugin FROM contribution_fact WHERE order_id = NEW.order_id AND backed_count != cardinality LIMIT 1);\n SELECT RAISE(ABORT, 'KEDGE_ADJUSTMENT_UNALLOCATED') WHERE EXISTS (\n SELECT 1 FROM order_adjustment a WHERE a.order_id = NEW.order_id\n AND a.amount != (SELECT COALESCE(sum(amount), 0) FROM order_line_adjustment WHERE adjustment_id = a.id));\n SELECT RAISE(ABORT, 'KEDGE_ORDER_TOTALS') FROM \"order\" o WHERE o.id = NEW.order_id AND (\n o.fee_total != (SELECT COALESCE(sum(amount), 0) FROM order_fee WHERE order_id = o.id)\n OR o.discount_total != -(SELECT COALESCE(sum(amount), 0) FROM order_adjustment WHERE order_id = o.id)\n OR o.subtotal != (SELECT COALESCE(sum(unit_price * quantity), 0) FROM order_line WHERE order_id = o.id)\n OR o.total != o.subtotal - o.discount_total + o.shipping_total + o.fee_total + o.tax_total);\n -- No line is discounted below zero.\n SELECT RAISE(ABORT, 'KEDGE_LINE_NEGATIVE') WHERE EXISTS (\n SELECT 1 FROM order_line l WHERE l.order_id = NEW.order_id\n AND l.unit_price * l.quantity + (SELECT COALESCE(sum(amount), 0) FROM order_line_adjustment WHERE order_line_id = l.id) < 0);\nEND;\n--> statement-breakpoint\nCREATE TRIGGER order_fee_immutable BEFORE UPDATE ON order_fee BEGIN\n SELECT RAISE(ABORT, 'KEDGE_MONEY_IMMUTABLE');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER order_fee_no_delete BEFORE DELETE ON order_fee BEGIN\n SELECT RAISE(ABORT, 'KEDGE_MONEY_IMMUTABLE');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER order_adjustment_immutable BEFORE UPDATE ON order_adjustment BEGIN\n SELECT RAISE(ABORT, 'KEDGE_MONEY_IMMUTABLE');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER order_adjustment_no_delete BEFORE DELETE ON order_adjustment BEGIN\n SELECT RAISE(ABORT, 'KEDGE_MONEY_IMMUTABLE');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER order_line_adjustment_immutable BEFORE UPDATE ON order_line_adjustment BEGIN\n SELECT RAISE(ABORT, 'KEDGE_MONEY_IMMUTABLE');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER order_line_adjustment_no_delete BEFORE DELETE ON order_line_adjustment BEGIN\n SELECT RAISE(ABORT, 'KEDGE_MONEY_IMMUTABLE');\nEND;\n--> statement-breakpoint\n-- The versioned read surface: plugin SQL reads core only through these\n-- (K1 rule 2). A breaking change ships as kedge_v2_* beside them.\nCREATE VIEW kedge_v1_order AS\n SELECT id, status, payment_status, number, currency_code, email, subtotal, discount_total, fee_total, total,\n created_at, placed_at, expires_at\n FROM \"order\";\n--> statement-breakpoint\nCREATE VIEW kedge_v1_order_line AS\n SELECT id, order_id, variant_id, product_id, sku, title, unit_price, quantity FROM order_line;\n","migrations/0006_line_adjustment_index.sql":"CREATE INDEX `order_line_adjustment_line` ON `order_line_adjustment` (`order_line_id`);","migrations/0007_order_email_index.sql":"CREATE INDEX `order_email_status` ON `order` (`email`,`status`);","migrations/0008_obligation_plugin_index.sql":"CREATE INDEX `release_obligation_plugin_state` ON `release_obligation` (`plugin`,`state`,`seq`);","migrations/0009_allocation_owner.sql":"-- An adjustment's allocation must land on a line of the same order. The\n-- table's two foreign keys don't say that, so a malformed commit could\n-- discount order A while corrupting order B's line-level money, which\n-- refunds later read (codex r6).\nCREATE TRIGGER order_line_adjustment_owner BEFORE INSERT ON order_line_adjustment\n WHEN (SELECT order_id FROM order_adjustment WHERE id = NEW.adjustment_id)\n IS NOT (SELECT order_id FROM order_line WHERE id = NEW.order_line_id) BEGIN\n SELECT RAISE(ABORT, 'KEDGE_ALLOCATION_ORDER');\nEND;\n","migrations/0010_backfill_spine.sql":"-- Orders from before the spine (Phase 0 part 1) get their permanent identity\n-- and the history their status implies, so an upgraded store meets the same\n-- invariants as a new one. The path is recorded as the shortest one to the\n-- current status, at the timestamps the order kept: whether it was reopened\n-- before this migration wasn't recorded. Re-running inserts nothing. No\n-- plugin took part in these orders, so the inserts start no plugin work.\nINSERT INTO order_index (id, created_at)\n SELECT id, created_at FROM \"order\" WHERE id NOT IN (SELECT id FROM order_index);\n--> statement-breakpoint\nINSERT INTO transition (uid, aggregate, aggregate_id, machine, from_state, to_state, at)\n SELECT id || ':placed', 'order', id, 'status', 'pending_payment', 'open', COALESCE(placed_at, created_at) FROM \"order\"\n WHERE (status IN ('open', 'completed') OR (status = 'canceled' AND placed_at IS NOT NULL))\n AND id || ':placed' NOT IN (SELECT uid FROM transition)\n ORDER BY created_at, id;\n--> statement-breakpoint\nINSERT INTO transition (uid, aggregate, aggregate_id, machine, from_state, to_state, at)\n SELECT id || ':expired', 'order', id, 'status', 'pending_payment', 'expired', COALESCE(expired_at, expires_at) FROM \"order\"\n WHERE status = 'expired' AND id || ':expired' NOT IN (SELECT uid FROM transition)\n ORDER BY created_at, id;\n--> statement-breakpoint\nINSERT INTO transition (uid, aggregate, aggregate_id, machine, from_state, to_state, at)\n SELECT id || ':canceled', 'order', id, 'status', CASE WHEN placed_at IS NULL THEN 'pending_payment' ELSE 'open' END,\n 'canceled', COALESCE(placed_at, created_at) FROM \"order\"\n WHERE status = 'canceled' AND id || ':canceled' NOT IN (SELECT uid FROM transition)\n ORDER BY created_at, id;\n--> statement-breakpoint\nINSERT INTO transition (uid, aggregate, aggregate_id, machine, from_state, to_state, at)\n SELECT id || ':completed', 'order', id, 'status', 'open', 'completed', COALESCE(placed_at, created_at) FROM \"order\"\n WHERE status = 'completed' AND id || ':completed' NOT IN (SELECT uid FROM transition)\n ORDER BY created_at, id;\n","migrations/0011_fact_backing_bound.sql":"-- A contribution identity backs at most its declared number of facts, at all\n-- times, not only at the commit check: otherwise a later order's plugin SQL\n-- could copy an old fact under a new row id, cite the old identity, and run\n-- its debit again while that order's own check passes (codex r9).\nCREATE TRIGGER contribution_fact_backing_bound BEFORE UPDATE OF backed_count ON contribution_fact\n WHEN NEW.backed_count > NEW.cardinality BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FACT_OVERBACKED');\nEND;\n","migrations/0012_participation_releases.sql":"ALTER TABLE `plugin_participation` ADD `releases` text DEFAULT '' NOT NULL;","migrations/0013_release_obligations_by_handler.sql":"-- A release transition owes an obligation to each participant that had a\n-- handler for it when it joined the order (its recorded `releases`), and to\n-- no one else. Delivery then can't confuse \"never needed a handler\" with\n-- \"the handler was removed by an upgrade\": the second leaves the obligation\n-- pending and reported, never done (codex r13).\nDROP TRIGGER transition_release_obligations;\n--> statement-breakpoint\nCREATE TRIGGER transition_release_obligations AFTER INSERT ON transition\n WHEN NEW.aggregate = 'order' AND NEW.machine = 'status' AND NEW.to_state IN ('expired', 'canceled') BEGIN\n INSERT INTO release_obligation (plugin, transition_uid, order_id, from_state, to_state, state, attempts, created_at, updated_at)\n SELECT plugin, NEW.uid, NEW.aggregate_id, NEW.from_state, NEW.to_state, 'pending', 0, NEW.at, NEW.at\n FROM plugin_participation\n WHERE order_id = NEW.aggregate_id AND (',' || releases || ',') LIKE ('%,' || NEW.to_state || ',%');\nEND;\n","migrations/0014_obligation_kind_index.sql":"CREATE INDEX `release_obligation_plugin_kind` ON `release_obligation` (`plugin`,`to_state`,`state`,`seq`);","migrations/0015_release_delivery.sql":"CREATE TABLE `release_delivery` (\n\t`seq` integer PRIMARY KEY NOT NULL,\n\t`delivered_at` integer NOT NULL\n);\n","migrations/0016_canonical_order_email.sql":"-- Orders from before email canonicalization get the same identity new ones\n-- have (core's canonicalEmail: ASCII letters lower-cased, surrounding spaces,\n-- tabs and newlines trimmed, empty becomes NULL), so a first-order check by\n-- email can't miss an older order spelled Shopper@EXAMPLE.com (codex r17).\n-- No trigger reacts to an email change; status triggers fire on status only.\nUPDATE \"order\" SET email = NULLIF(lower(trim(email, ' ' || char(9) || char(10) || char(13))), '')\n WHERE email IS NOT NULL AND email IS NOT NULLIF(lower(trim(email, ' ' || char(9) || char(10) || char(13))), '');\n","migrations/0017_delivery_needs_pending.sql":"-- A delivery's receipt is accepted only while its obligation is pending, so a\n-- stale drain (whose obligation another drain already failed or completed)\n-- aborts before its plugin statements run, instead of leaving a receipt that\n-- would make every later retry skip the obligation for good (codex r18).\nCREATE TRIGGER release_delivery_needs_pending BEFORE INSERT ON release_delivery\n WHEN (SELECT state FROM release_obligation WHERE seq = NEW.seq) IS NOT 'pending' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_OBLIGATION_NOT_PENDING');\nEND;\n","migrations/0018_line_model.sql":"-- P0.3, the line model (docs/proposals/p0.3-line-model.md). Generated by\n-- drizzle-kit, then edited by hand before it was ever applied: the added\n-- columns carry column-level CHECKs (a table check() in schema.ts would make\n-- drizzle rebuild order_line, a hot table, docs/10), and the seal obligation's\n-- key is DEFERRABLE INITIALLY DEFERRED, which drizzle can't express (§1.7,\n-- research/16). Every added column has a default meaning today's behaviour,\n-- so the previous Worker's commits still pass during the rollout (§1.5).\nCREATE TABLE `checkout_policy` (\n\t`policy` text PRIMARY KEY NOT NULL,\n\tCONSTRAINT \"checkout_policy_code\" CHECK(length(\"policy\") BETWEEN 1 AND 40 AND \"policy\" GLOB '[a-z]*' AND \"policy\" NOT GLOB '*[^a-z0-9_]*')\n);\n--> statement-breakpoint\nCREATE TABLE `order_seal_due` (\n\t`order_id` text PRIMARY KEY NOT NULL,\n\tFOREIGN KEY (`order_id`) REFERENCES `contribution_check`(`order_id`) ON UPDATE no action ON DELETE no action DEFERRABLE INITIALLY DEFERRED\n);\n--> statement-breakpoint\nCREATE TABLE `order_tax_line` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`order_id` text NOT NULL,\n\t`order_line_id` text,\n\t`order_fee_id` text,\n\t`charge_class` text NOT NULL,\n\t`rate_ppm` integer NOT NULL,\n\t`inclusive` integer NOT NULL,\n\t`amount` integer NOT NULL,\n\tFOREIGN KEY (`order_id`) REFERENCES `order`(`id`) ON UPDATE no action ON DELETE no action,\n\tFOREIGN KEY (`order_line_id`) REFERENCES `order_line`(`id`) ON UPDATE no action ON DELETE no action,\n\tFOREIGN KEY (`order_fee_id`) REFERENCES `order_fee`(`id`) ON UPDATE no action ON DELETE no action,\n\tCONSTRAINT \"order_tax_line_owner\" CHECK((\"order_tax_line\".\"order_line_id\" IS NULL) != (\"order_tax_line\".\"order_fee_id\" IS NULL)),\n\tCONSTRAINT \"order_tax_line_class\" CHECK(\"order_tax_line\".\"charge_class\" IN ('revenue', 'deposit')),\n\tCONSTRAINT \"order_tax_line_rate_int\" CHECK(typeof(\"order_tax_line\".\"rate_ppm\") = 'integer' AND \"order_tax_line\".\"rate_ppm\" BETWEEN 1 AND 1000000),\n\tCONSTRAINT \"order_tax_line_inclusive\" CHECK(\"order_tax_line\".\"inclusive\" IN (0, 1)),\n\tCONSTRAINT \"order_tax_line_amount_int\" CHECK(typeof(\"order_tax_line\".\"amount\") = 'integer' AND \"order_tax_line\".\"amount\" BETWEEN 0 AND 10000000000000)\n);\n--> statement-breakpoint\nCREATE INDEX `order_tax_line_order` ON `order_tax_line` (`order_id`);--> statement-breakpoint\nCREATE UNIQUE INDEX `order_tax_line_line` ON `order_tax_line` (`order_line_id`) WHERE \"order_tax_line\".\"order_line_id\" IS NOT NULL;--> statement-breakpoint\nCREATE UNIQUE INDEX `order_tax_line_fee` ON `order_tax_line` (`order_fee_id`) WHERE \"order_tax_line\".\"order_fee_id\" IS NOT NULL;--> statement-breakpoint\nCREATE TABLE `policy_acceptance` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`order_id` text NOT NULL,\n\t`revision_id` text NOT NULL,\n\t`accepted_at` integer NOT NULL,\n\t`actor` text NOT NULL,\n\t`evidence_kind` text NOT NULL,\n\t`evidence_text` text NOT NULL,\n\t`retain_until` integer NOT NULL,\n\tFOREIGN KEY (`order_id`) REFERENCES `order_index`(`id`) ON UPDATE no action ON DELETE no action,\n\tFOREIGN KEY (`revision_id`) REFERENCES `policy_revision`(`id`) ON UPDATE no action ON DELETE no action,\n\tCONSTRAINT \"policy_acceptance_evidence_kind\" CHECK(\"policy_acceptance\".\"evidence_kind\" IN ('typed_name')),\n\tCONSTRAINT \"policy_acceptance_evidence_len\" CHECK(length(\"policy_acceptance\".\"evidence_text\") BETWEEN 1 AND 200)\n);\n--> statement-breakpoint\nCREATE INDEX `policy_acceptance_order` ON `policy_acceptance` (`order_id`);--> statement-breakpoint\nCREATE TABLE `policy_revision` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`policy` text NOT NULL,\n\t`version` integer NOT NULL,\n\t`locale` text NOT NULL,\n\t`body` text NOT NULL,\n\t`digest` text NOT NULL,\n\t`published_at` integer NOT NULL,\n\tCONSTRAINT \"policy_revision_version_int\" CHECK(typeof(\"policy_revision\".\"version\") = 'integer' AND \"policy_revision\".\"version\" BETWEEN 1 AND 1000000),\n\tCONSTRAINT \"policy_revision_code\" CHECK(length(\"policy\") BETWEEN 1 AND 40 AND \"policy\" GLOB '[a-z]*' AND \"policy\" NOT GLOB '*[^a-z0-9_]*'),\n\tCONSTRAINT \"policy_revision_locale\" CHECK(length(\"locale\") BETWEEN 2 AND 35),\n\tCONSTRAINT \"policy_revision_body\" CHECK(length(\"body\") BETWEEN 1 AND 200000)\n);\n--> statement-breakpoint\nCREATE UNIQUE INDEX `policy_revision_policy_locale_version` ON `policy_revision` (`policy`,`locale`,`version`);--> statement-breakpoint\nCREATE TABLE `tax_setting` (\n\t`id` integer PRIMARY KEY NOT NULL,\n\t`inclusive` integer DEFAULT 0 NOT NULL,\n\t`rate_revenue_ppm` integer DEFAULT 0 NOT NULL,\n\t`rate_deposit_ppm` integer DEFAULT 0 NOT NULL,\n\tCONSTRAINT \"tax_setting_single\" CHECK(\"tax_setting\".\"id\" = 1),\n\tCONSTRAINT \"tax_setting_inclusive\" CHECK(\"tax_setting\".\"inclusive\" IN (0, 1)),\n\tCONSTRAINT \"tax_setting_rate_revenue_int\" CHECK(typeof(\"tax_setting\".\"rate_revenue_ppm\") = 'integer' AND \"tax_setting\".\"rate_revenue_ppm\" BETWEEN 0 AND 1000000),\n\tCONSTRAINT \"tax_setting_rate_deposit_int\" CHECK(typeof(\"tax_setting\".\"rate_deposit_ppm\") = 'integer' AND \"tax_setting\".\"rate_deposit_ppm\" BETWEEN 0 AND 1000000)\n);\n--> statement-breakpoint\nALTER TABLE `order` ADD `revenue_total` integer CONSTRAINT \"order_revenue_total_int\" CHECK(\"revenue_total\" IS NULL OR (typeof(\"revenue_total\") = 'integer' AND \"revenue_total\" BETWEEN -10000000000000 AND 10000000000000));--> statement-breakpoint\nALTER TABLE `order` ADD `deposit_total` integer CONSTRAINT \"order_deposit_total_int\" CHECK(\"deposit_total\" IS NULL OR (typeof(\"deposit_total\") = 'integer' AND \"deposit_total\" BETWEEN -10000000000000 AND 10000000000000));--> statement-breakpoint\nALTER TABLE `order` ADD `tax_inclusive` integer DEFAULT 0 NOT NULL CONSTRAINT \"order_tax_inclusive\" CHECK(\"tax_inclusive\" IN (0, 1));--> statement-breakpoint\nALTER TABLE `order` ADD `tax_rate_revenue_ppm` integer DEFAULT 0 NOT NULL CONSTRAINT \"order_tax_rate_revenue_int\" CHECK(typeof(\"tax_rate_revenue_ppm\") = 'integer' AND \"tax_rate_revenue_ppm\" BETWEEN 0 AND 1000000);--> statement-breakpoint\nALTER TABLE `order` ADD `tax_rate_deposit_ppm` integer DEFAULT 0 NOT NULL CONSTRAINT \"order_tax_rate_deposit_int\" CHECK(typeof(\"tax_rate_deposit_ppm\") = 'integer' AND \"tax_rate_deposit_ppm\" BETWEEN 0 AND 1000000);--> statement-breakpoint\nALTER TABLE `order` ADD `locale` text DEFAULT 'en' NOT NULL CONSTRAINT \"order_locale\" CHECK(length(\"locale\") BETWEEN 2 AND 35);--> statement-breakpoint\nALTER TABLE `order` ADD `place_by` integer CONSTRAINT \"order_place_by_int\" CHECK(\"place_by\" IS NULL OR typeof(\"place_by\") = 'integer');--> statement-breakpoint\nALTER TABLE `order` ADD `custom` text DEFAULT '{}' NOT NULL CONSTRAINT \"order_custom_json\" CHECK(json_valid(\"custom\") AND json_type(\"custom\") = 'object' AND length(\"custom\") <= 16384);--> statement-breakpoint\nALTER TABLE `order_fee` ADD `taxable` integer DEFAULT 1 NOT NULL CONSTRAINT \"order_fee_taxable\" CHECK(\"taxable\" IN (0, 1));--> statement-breakpoint\nALTER TABLE `order_fee` ADD `order_line_id` text REFERENCES `order_line`(`id`);--> statement-breakpoint\nALTER TABLE `order_line` ADD `kind` text DEFAULT 'item' NOT NULL CONSTRAINT \"order_line_kind\" CHECK(length(\"kind\") BETWEEN 1 AND 64);--> statement-breakpoint\nALTER TABLE `order_line` ADD `parent_line_id` text REFERENCES `order_line`(`id`);--> statement-breakpoint\nALTER TABLE `order_line` ADD `quantity_mode` text DEFAULT 'own' NOT NULL CONSTRAINT \"order_line_quantity_mode\" CHECK(\"quantity_mode\" IN ('own', 'per_parent'));--> statement-breakpoint\nALTER TABLE `order_line` ADD `quantity_per_parent` integer CONSTRAINT \"order_line_quantity_per_parent\" CHECK((\"quantity_mode\" = 'own' AND \"quantity_per_parent\" IS NULL) OR (\"quantity_mode\" = 'per_parent' AND \"parent_line_id\" IS NOT NULL AND typeof(\"quantity_per_parent\") = 'integer' AND \"quantity_per_parent\" BETWEEN 1 AND 100000));--> statement-breakpoint\nALTER TABLE `order_line` ADD `period_start` integer CONSTRAINT \"order_line_period_start_int\" CHECK(\"period_start\" IS NULL OR typeof(\"period_start\") = 'integer');--> statement-breakpoint\nALTER TABLE `order_line` ADD `period_end` integer CONSTRAINT \"order_line_period\" CHECK((\"period_start\" IS NULL AND \"period_end\" IS NULL) OR (typeof(\"period_end\") = 'integer' AND \"period_start\" IS NOT NULL AND \"period_start\" < \"period_end\"));--> statement-breakpoint\nALTER TABLE `order_line` ADD `price_breakdown` text DEFAULT '{}' NOT NULL CONSTRAINT \"order_line_price_breakdown_json\" CHECK(json_valid(\"price_breakdown\") AND length(\"price_breakdown\") <= 1024);--> statement-breakpoint\nALTER TABLE `order_line` ADD `custom` text DEFAULT '{}' NOT NULL CONSTRAINT \"order_line_custom_json\" CHECK(json_valid(\"custom\") AND json_type(\"custom\") = 'object' AND length(\"custom\") <= 8192);--> statement-breakpoint\nALTER TABLE `order_line` ADD `charge_class` text DEFAULT 'revenue' NOT NULL CONSTRAINT \"order_line_charge_class\" CHECK(\"charge_class\" IN ('revenue', 'deposit'));--> statement-breakpoint\nALTER TABLE `order_line` ADD `fulfillment_class` text DEFAULT 'ship' NOT NULL CONSTRAINT \"order_line_fulfillment_class\" CHECK(\"fulfillment_class\" IN ('ship', 'ship_and_return', 'digital', 'service', 'none'));--> statement-breakpoint\nALTER TABLE `order_line` ADD `returnable` integer DEFAULT 1 NOT NULL CONSTRAINT \"order_line_returnable\" CHECK(\"returnable\" IN (0, 1));--> statement-breakpoint\nALTER TABLE `order_line` ADD `taxable` integer DEFAULT 1 NOT NULL CONSTRAINT \"order_line_taxable\" CHECK(\"taxable\" IN (0, 1));--> statement-breakpoint\nALTER TABLE `order_line` ADD `discountable` integer DEFAULT 1 NOT NULL CONSTRAINT \"order_line_discountable\" CHECK(\"discountable\" IN (0, 1) AND NOT (\"discountable\" = 1 AND \"charge_class\" = 'deposit'));--> statement-breakpoint\nALTER TABLE `order_line` ADD `kind_version` text DEFAULT 'core:item:1' NOT NULL CONSTRAINT \"order_line_kind_version\" CHECK(length(\"kind_version\") BETWEEN 1 AND 128);--> statement-breakpoint\nALTER TABLE `order_line` ADD `fulfilled_qty` integer DEFAULT 0 NOT NULL CONSTRAINT \"order_line_fulfilled_int\" CHECK(typeof(\"fulfilled_qty\") = 'integer' AND \"fulfilled_qty\" BETWEEN 0 AND \"quantity\");--> statement-breakpoint\nALTER TABLE `order_line` ADD `resolved_qty` integer DEFAULT 0 NOT NULL CONSTRAINT \"order_line_resolved_int\" CHECK(typeof(\"resolved_qty\") = 'integer' AND \"resolved_qty\" BETWEEN 0 AND \"fulfilled_qty\");--> statement-breakpoint\nCREATE INDEX `order_line_parent` ON `order_line` (`parent_line_id`);--> statement-breakpoint\nALTER TABLE `variant` ADD `allowed_kinds` text DEFAULT '[\"item\"]' NOT NULL CONSTRAINT \"variant_allowed_kinds_json\" CHECK(json_valid(\"allowed_kinds\") AND json_type(\"allowed_kinds\") = 'array' AND json_array_length(\"allowed_kinds\") BETWEEN 1 AND 32 AND length(\"allowed_kinds\") <= 2048);\n--> statement-breakpoint\n-- The store's tax context starts at zero rates (§3.2); staff set it.\nINSERT INTO `tax_setting` (`id`, `inclusive`, `rate_revenue_ppm`, `rate_deposit_ppm`) VALUES (1, 0, 0, 0);","migrations/0019_line_model_triggers.sql":"-- P0.3's invariants in D1 (docs/proposals/p0.3-line-model.md §1.3, §1.5,\n-- §1.7, §2.1, §2.2, §3.3, §5). Order matters: the backfills run before the\n-- recreated commit check, history is sealed before the write-once guards\n-- exist, and the guards come last. Each trigger names its RAISE code in\n-- @kedge/db's CORE_RAISE_CODES.\n\n-- §1.5: history and previous-Worker orders get class totals. A P0.2 order has\n-- no deposits and no tax (it can't write either), so this is exact.\nUPDATE \"order\" SET revenue_total = total, deposit_total = 0 WHERE revenue_total IS NULL;\n--> statement-breakpoint\n\n-- Rows start where the lifecycle starts (§1.7): an order reaches `open` or\n-- `completed` only through the transitions and their consequences. And it is\n-- taxed only under the context the store has (§3.2).\nCREATE TRIGGER order_initial BEFORE INSERT ON \"order\" BEGIN\n SELECT RAISE(ABORT, 'KEDGE_ORDER_INITIAL')\n WHERE NEW.status IS NOT 'pending_payment' OR NEW.payment_status IS NOT 'unpaid'\n OR NEW.number IS NOT NULL OR NEW.placed_at IS NOT NULL OR NEW.expired_at IS NOT NULL;\n SELECT RAISE(ABORT, 'KEDGE_TAX_CONTEXT') WHERE NOT EXISTS (\n SELECT 1 FROM tax_setting t WHERE t.id = 1 AND t.inclusive = NEW.tax_inclusive\n AND t.rate_revenue_ppm = NEW.tax_rate_revenue_ppm AND t.rate_deposit_ppm = NEW.tax_rate_deposit_ppm);\nEND;\n--> statement-breakpoint\n-- A previous-Worker order arrives without class totals: they are derived in\n-- its own statement (SQLite checks constraints before AFTER triggers, so the\n-- columns accept NULL and the commit check requires them set). And every\n-- order owes a seal, by a key checked when the batch ends (§1.7, research/16).\nCREATE TRIGGER order_line_model_insert AFTER INSERT ON \"order\" BEGIN\n UPDATE \"order\" SET revenue_total = total, deposit_total = 0 WHERE id = NEW.id AND revenue_total IS NULL;\n INSERT INTO order_seal_due (order_id) VALUES (NEW.id);\nEND;\n--> statement-breakpoint\n\n-- §2.1: a payment or a staff placement after the earliest kind cutoff can't\n-- open the order, and a late payment can't reopen it.\nCREATE TRIGGER order_placement_deadline BEFORE UPDATE OF status ON \"order\"\n WHEN NEW.status = 'open' AND OLD.status IN ('pending_payment', 'expired') AND OLD.place_by IS NOT NULL\n AND OLD.place_by <= CAST(unixepoch('subsec') * 1000 AS INTEGER) BEGIN\n SELECT RAISE(ABORT, 'KEDGE_PLACEMENT_DEADLINE');\nEND;\n--> statement-breakpoint\n-- §1.3: completion obligations start here. Fulfilment (P1.8) raises the counters.\nCREATE TRIGGER order_completion_obligations BEFORE UPDATE OF status ON \"order\"\n WHEN OLD.status = 'open' AND NEW.status = 'completed' AND EXISTS (\n SELECT 1 FROM order_line l WHERE l.order_id = NEW.id\n AND ((l.fulfillment_class != 'none' AND l.fulfilled_qty != l.quantity) OR l.resolved_qty != l.fulfilled_qty)) BEGIN\n SELECT RAISE(ABORT, 'KEDGE_ORDER_OBLIGATIONS');\nEND;\n--> statement-breakpoint\n\n-- §1.3, §2.2: a line's parent is an already-inserted top-level line of the\n-- same order (the commit writes parents first), and its kind is one its\n-- variant allows now, so a change between claim and commit can't sell a\n-- rental-only variant as a sale. Counters start at zero.\nCREATE TRIGGER order_line_insert BEFORE INSERT ON order_line BEGIN\n SELECT RAISE(ABORT, 'KEDGE_ORDER_SEALED') WHERE EXISTS (SELECT 1 FROM contribution_check WHERE order_id = NEW.order_id);\n SELECT RAISE(ABORT, 'KEDGE_ORDER_INITIAL') WHERE NEW.fulfilled_qty != 0 OR NEW.resolved_qty != 0;\n SELECT RAISE(ABORT, 'KEDGE_LINE_PARENT') WHERE NEW.parent_line_id IS NOT NULL AND NOT EXISTS (\n SELECT 1 FROM order_line p WHERE p.id = NEW.parent_line_id AND p.order_id = NEW.order_id AND p.parent_line_id IS NULL);\n SELECT RAISE(ABORT, 'KEDGE_KIND_NOT_ALLOWED') WHERE NOT EXISTS (\n SELECT 1 FROM variant v, json_each(v.allowed_kinds) k WHERE v.id = NEW.variant_id AND k.value = NEW.kind);\nEND;\n--> statement-breakpoint\nCREATE TRIGGER order_fee_insert BEFORE INSERT ON order_fee BEGIN\n SELECT RAISE(ABORT, 'KEDGE_ORDER_SEALED') WHERE EXISTS (SELECT 1 FROM contribution_check WHERE order_id = NEW.order_id);\n SELECT RAISE(ABORT, 'KEDGE_FEE_LINE') WHERE NEW.order_line_id IS NOT NULL\n AND (SELECT order_id FROM order_line WHERE id = NEW.order_line_id) IS NOT NEW.order_id;\nEND;\n--> statement-breakpoint\nCREATE TRIGGER order_adjustment_insert BEFORE INSERT ON order_adjustment\n WHEN EXISTS (SELECT 1 FROM contribution_check WHERE order_id = NEW.order_id) BEGIN\n SELECT RAISE(ABORT, 'KEDGE_ORDER_SEALED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER order_line_adjustment_insert BEFORE INSERT ON order_line_adjustment\n WHEN EXISTS (SELECT 1 FROM contribution_check c JOIN order_adjustment a ON a.order_id = c.order_id WHERE a.id = NEW.adjustment_id) BEGIN\n SELECT RAISE(ABORT, 'KEDGE_ORDER_SEALED');\nEND;\n--> statement-breakpoint\n-- §3.3: a tax line's owner is in the same order, taxable, and of its class.\nCREATE TRIGGER order_tax_line_insert BEFORE INSERT ON order_tax_line BEGIN\n SELECT RAISE(ABORT, 'KEDGE_ORDER_SEALED') WHERE EXISTS (SELECT 1 FROM contribution_check WHERE order_id = NEW.order_id);\n SELECT RAISE(ABORT, 'KEDGE_TAX_OWNER') WHERE NEW.order_line_id IS NOT NULL AND NOT EXISTS (\n SELECT 1 FROM order_line WHERE id = NEW.order_line_id AND order_id = NEW.order_id AND taxable = 1 AND charge_class = NEW.charge_class);\n SELECT RAISE(ABORT, 'KEDGE_TAX_OWNER') WHERE NEW.order_fee_id IS NOT NULL AND NOT EXISTS (\n SELECT 1 FROM order_fee WHERE id = NEW.order_fee_id AND order_id = NEW.order_id AND taxable = 1 AND charge_class = NEW.charge_class);\nEND;\n--> statement-breakpoint\n-- §5.2: an acceptance's locale and policy come from its revision (their one\n-- home); the revision is in the order's locale, the newest one, and the only\n-- acceptance of its policy for the order.\nCREATE TRIGGER policy_acceptance_insert BEFORE INSERT ON policy_acceptance BEGIN\n SELECT RAISE(ABORT, 'KEDGE_ORDER_SEALED') WHERE EXISTS (SELECT 1 FROM contribution_check WHERE order_id = NEW.order_id);\n SELECT RAISE(ABORT, 'KEDGE_POLICY_LOCALE') WHERE (SELECT locale FROM policy_revision WHERE id = NEW.revision_id)\n IS NOT (SELECT locale FROM \"order\" WHERE id = NEW.order_id);\n SELECT RAISE(ABORT, 'KEDGE_POLICY_DUPLICATE') WHERE EXISTS (\n SELECT 1 FROM policy_acceptance a JOIN policy_revision r ON r.id = a.revision_id\n WHERE a.order_id = NEW.order_id AND r.policy = (SELECT policy FROM policy_revision WHERE id = NEW.revision_id));\n SELECT RAISE(ABORT, 'KEDGE_POLICY_CHANGED') WHERE EXISTS (\n SELECT 1 FROM policy_revision n JOIN policy_revision r ON r.id = NEW.revision_id\n WHERE n.policy = r.policy AND n.locale = r.locale AND n.version > r.version);\nEND;\n--> statement-breakpoint\n-- The seal closes every checkout-owned table to new rows (§1.7).\nCREATE TRIGGER plugin_participation_sealed BEFORE INSERT ON plugin_participation\n WHEN EXISTS (SELECT 1 FROM contribution_check WHERE order_id = NEW.order_id) BEGIN\n SELECT RAISE(ABORT, 'KEDGE_ORDER_SEALED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER contribution_fact_sealed BEFORE INSERT ON contribution_fact\n WHEN EXISTS (SELECT 1 FROM contribution_check WHERE order_id = NEW.order_id) BEGIN\n SELECT RAISE(ABORT, 'KEDGE_ORDER_SEALED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER inventory_reservation_sealed BEFORE INSERT ON inventory_reservation\n WHEN EXISTS (SELECT 1 FROM contribution_check WHERE order_id = NEW.order_id) BEGIN\n SELECT RAISE(ABORT, 'KEDGE_ORDER_SEALED');\nEND;\n--> statement-breakpoint\n\n-- §5.1: policy texts never change; a changed policy is a new revision. At most\n-- five required policies, a row each (no flag to update), so the consent\n-- budget and checkout's reads stay bounded.\nCREATE TRIGGER policy_revision_immutable BEFORE UPDATE ON policy_revision BEGIN\n SELECT RAISE(ABORT, 'KEDGE_POLICY_IMMUTABLE');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER policy_revision_no_delete BEFORE DELETE ON policy_revision BEGIN\n SELECT RAISE(ABORT, 'KEDGE_POLICY_IMMUTABLE');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER checkout_policy_limit BEFORE INSERT ON checkout_policy\n WHEN (SELECT count(*) FROM checkout_policy) >= 5 BEGIN\n SELECT RAISE(ABORT, 'KEDGE_POLICY_LIMIT');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER checkout_policy_no_update BEFORE UPDATE ON checkout_policy BEGIN\n SELECT RAISE(ABORT, 'KEDGE_POLICY_LIMIT');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER tax_setting_no_delete BEFORE DELETE ON tax_setting BEGIN\n SELECT RAISE(ABORT, 'KEDGE_TAX_CONTEXT');\nEND;\n--> statement-breakpoint\n\n-- The commit check, recreated (§3.3): P0.2's totals assertion holds only\n-- without inclusive tax, so it is replaced, not extended. The batch's last\n-- statement; its insert is the order's seal.\nDROP TRIGGER contribution_check_assert;\n--> statement-breakpoint\nCREATE TRIGGER contribution_check_assert BEFORE INSERT ON contribution_check BEGIN\n -- Names the plugin: a failed batch doesn't say which statement failed (research/15).\n SELECT RAISE(ABORT, 'KEDGE_CONTRIBUTION_UNBACKED:' || plugin) FROM (\n SELECT plugin FROM contribution_fact WHERE order_id = NEW.order_id AND backed_count != cardinality LIMIT 1);\n SELECT RAISE(ABORT, 'KEDGE_ADJUSTMENT_UNALLOCATED') WHERE EXISTS (\n SELECT 1 FROM order_adjustment a WHERE a.order_id = NEW.order_id\n AND a.amount != (SELECT COALESCE(sum(amount), 0) FROM order_line_adjustment WHERE adjustment_id = a.id));\n -- Discounts land on revenue lines whose kind is discountable only (K4).\n SELECT RAISE(ABORT, 'KEDGE_ALLOCATION_NOT_DISCOUNTABLE') WHERE EXISTS (\n SELECT 1 FROM order_adjustment a JOIN order_line_adjustment s ON s.adjustment_id = a.id\n JOIN order_line l ON l.id = s.order_line_id\n WHERE a.order_id = NEW.order_id AND (l.discountable = 0 OR l.charge_class != 'revenue'));\n -- No line is discounted below zero.\n SELECT RAISE(ABORT, 'KEDGE_LINE_NEGATIVE') WHERE EXISTS (\n SELECT 1 FROM order_line l WHERE l.order_id = NEW.order_id\n AND l.unit_price * l.quantity + (SELECT COALESCE(sum(amount), 0) FROM order_line_adjustment WHERE order_line_id = l.id) < 0);\n -- A per_parent line's quantity is proven, not trusted from the plan (§1.3).\n SELECT RAISE(ABORT, 'KEDGE_LINE_PER_PARENT') WHERE EXISTS (\n SELECT 1 FROM order_line c JOIN order_line p ON p.id = c.parent_line_id\n WHERE c.order_id = NEW.order_id AND c.quantity_mode = 'per_parent' AND c.quantity != p.quantity * c.quantity_per_parent);\n -- The insert-time checks hold at the moment of sealing too (§1.7).\n SELECT RAISE(ABORT, 'KEDGE_KIND_NOT_ALLOWED') WHERE EXISTS (\n SELECT 1 FROM order_line l WHERE l.order_id = NEW.order_id AND NOT EXISTS (\n SELECT 1 FROM variant v, json_each(v.allowed_kinds) k WHERE v.id = l.variant_id AND k.value = l.kind));\n SELECT RAISE(ABORT, 'KEDGE_TAX_CONTEXT') FROM \"order\" o WHERE o.id = NEW.order_id AND NOT EXISTS (\n SELECT 1 FROM tax_setting t WHERE t.id = 1 AND t.inclusive = o.tax_inclusive\n AND t.rate_revenue_ppm = o.tax_rate_revenue_ppm AND t.rate_deposit_ppm = o.tax_rate_deposit_ppm);\n -- Every tax owed has its row, and only those, with the formula's amount\n -- (§3.3). base = q·d + r, so tax = q·rate + round(r·rate / d): no product\n -- exceeds about 10^13. Rounded half up; bases are never negative here.\n SELECT RAISE(ABORT, 'KEDGE_TAX_LINES') FROM (\n SELECT x.*, t.id AS tid, t.rate_ppm AS trate, t.inclusive AS tincl, t.charge_class AS tclass, t.amount AS tamount\n FROM (\n SELECT l.id, l.taxable, l.charge_class, o.tax_inclusive AS incl,\n CASE l.charge_class WHEN 'revenue' THEN o.tax_rate_revenue_ppm ELSE o.tax_rate_deposit_ppm END AS rate,\n l.unit_price * l.quantity + (SELECT COALESCE(sum(amount), 0) FROM order_line_adjustment WHERE order_line_id = l.id) AS base\n FROM order_line l JOIN \"order\" o ON o.id = l.order_id WHERE l.order_id = NEW.order_id) x\n LEFT JOIN order_tax_line t ON t.order_line_id = x.id\n UNION ALL\n SELECT y.*, t.id, t.rate_ppm, t.inclusive, t.charge_class, t.amount\n FROM (\n SELECT f.id, f.taxable, f.charge_class, o.tax_inclusive,\n CASE f.charge_class WHEN 'revenue' THEN o.tax_rate_revenue_ppm ELSE o.tax_rate_deposit_ppm END,\n f.amount\n FROM order_fee f JOIN \"order\" o ON o.id = f.order_id WHERE f.order_id = NEW.order_id) y\n LEFT JOIN order_tax_line t ON t.order_fee_id = y.id\n ) z WHERE CASE WHEN z.taxable = 1 AND z.rate > 0 THEN\n z.tid IS NULL OR z.trate != z.rate OR z.tincl != z.incl OR z.tclass != z.charge_class\n OR z.tamount != (z.base / (1000000 + z.incl * z.rate)) * z.rate\n + ((z.base % (1000000 + z.incl * z.rate)) * z.rate * 2 + (1000000 + z.incl * z.rate))\n / (2 * (1000000 + z.incl * z.rate))\n ELSE z.tid IS NOT NULL END\n LIMIT 1;\n -- The stored breakdown (§3.2): amounts as priced, tax_total every tax line,\n -- the total adding only exclusive tax, and the total split by class (K4).\n SELECT RAISE(ABORT, 'KEDGE_ORDER_TOTALS') FROM \"order\" o WHERE o.id = NEW.order_id AND (\n o.fee_total != (SELECT COALESCE(sum(amount), 0) FROM order_fee WHERE order_id = o.id)\n OR o.discount_total != -(SELECT COALESCE(sum(amount), 0) FROM order_adjustment WHERE order_id = o.id)\n OR o.subtotal != (SELECT COALESCE(sum(unit_price * quantity), 0) FROM order_line WHERE order_id = o.id)\n OR o.tax_total != (SELECT COALESCE(sum(amount), 0) FROM order_tax_line WHERE order_id = o.id)\n OR o.total != o.subtotal - o.discount_total + o.shipping_total + o.fee_total\n + (SELECT COALESCE(sum(amount), 0) FROM order_tax_line WHERE order_id = o.id AND inclusive = 0)\n OR o.revenue_total IS NULL OR o.deposit_total IS NULL\n OR o.total != o.revenue_total + o.deposit_total\n OR o.deposit_total != (SELECT COALESCE(sum(unit_price * quantity), 0) FROM order_line WHERE order_id = o.id AND charge_class = 'deposit')\n + (SELECT COALESCE(sum(amount), 0) FROM order_fee WHERE order_id = o.id AND charge_class = 'deposit')\n + (SELECT COALESCE(sum(amount), 0) FROM order_tax_line WHERE order_id = o.id AND inclusive = 0 AND charge_class = 'deposit'));\n -- Consent completeness in the order's locale (§5.1): exactly one\n -- acceptance of each required policy's current revision, whoever wrote the\n -- order, the previous Worker included.\n SELECT RAISE(ABORT, 'KEDGE_POLICY_MISSING:' || cp.policy) FROM checkout_policy cp JOIN \"order\" o ON o.id = NEW.order_id\n WHERE (SELECT count(*) FROM policy_acceptance a JOIN policy_revision r ON r.id = a.revision_id\n WHERE a.order_id = o.id AND r.policy = cp.policy AND r.locale = o.locale\n AND r.version = (SELECT max(version) FROM policy_revision WHERE policy = cp.policy AND locale = o.locale)) != 1\n LIMIT 1;\nEND;\n--> statement-breakpoint\n\n-- §1.7: history is sealed once, by the recreated check, before the guards\n-- exist; a row it can't prove fails this migration rather than stay unguarded.\nINSERT INTO contribution_check (order_id) SELECT id FROM \"order\" WHERE id NOT IN (SELECT order_id FROM contribution_check);\n--> statement-breakpoint\nINSERT INTO order_seal_due (order_id) SELECT id FROM \"order\" WHERE id NOT IN (SELECT order_id FROM order_seal_due);\n--> statement-breakpoint\n\n-- Write-once order rows (§1.7). From its insert on, a row can't be deleted\n-- and changes only in the columns named here, so whatever an insert trigger\n-- checked, and whatever the seal proved, stays true. A column added later is\n-- frozen until its slice recreates this guard (the invariant suite checks\n-- every column is named).\nCREATE TRIGGER order_write_once BEFORE UPDATE ON \"order\" BEGIN\n SELECT RAISE(ABORT, 'KEDGE_ORDER_IMMUTABLE') WHERE\n NEW.id IS NOT OLD.id OR NEW.cart_id IS NOT OLD.cart_id OR NEW.attempt_id IS NOT OLD.attempt_id\n OR NEW.currency_code IS NOT OLD.currency_code OR NEW.email IS NOT OLD.email\n OR NEW.subtotal IS NOT OLD.subtotal OR NEW.discount_total IS NOT OLD.discount_total\n OR NEW.shipping_total IS NOT OLD.shipping_total OR NEW.fee_total IS NOT OLD.fee_total\n OR NEW.tax_total IS NOT OLD.tax_total OR NEW.total IS NOT OLD.total\n OR NEW.payment_status IS NOT OLD.payment_status OR NEW.reservation_count IS NOT OLD.reservation_count\n OR NEW.expires_at IS NOT OLD.expires_at OR NEW.created_at IS NOT OLD.created_at\n OR NEW.tax_inclusive IS NOT OLD.tax_inclusive OR NEW.tax_rate_revenue_ppm IS NOT OLD.tax_rate_revenue_ppm\n OR NEW.tax_rate_deposit_ppm IS NOT OLD.tax_rate_deposit_ppm OR NEW.locale IS NOT OLD.locale\n OR NEW.place_by IS NOT OLD.place_by OR NEW.custom IS NOT OLD.custom\n -- Class totals: only from NULL, before the seal (the previous-Worker derivation, §1.5).\n OR ((NEW.revenue_total IS NOT OLD.revenue_total OR NEW.deposit_total IS NOT OLD.deposit_total)\n AND (OLD.revenue_total IS NOT NULL OR OLD.deposit_total IS NOT NULL\n OR EXISTS (SELECT 1 FROM contribution_check WHERE order_id = OLD.id)))\n -- number and placed_at: only from NULL, while open (what placement writes).\n OR ((NEW.number IS NOT OLD.number OR NEW.placed_at IS NOT OLD.placed_at)\n AND (OLD.number IS NOT NULL OR OLD.placed_at IS NOT NULL OR NEW.status IS NOT 'open'\n OR NEW.number IS NULL OR NEW.placed_at IS NULL))\n -- expired_at: only from NULL, while expired.\n OR (NEW.expired_at IS NOT OLD.expired_at AND (OLD.expired_at IS NOT NULL OR NEW.status IS NOT 'expired'));\n SELECT RAISE(ABORT, 'KEDGE_ORDER_UNSEALED') WHERE NEW.status IS NOT OLD.status\n AND NOT EXISTS (SELECT 1 FROM contribution_check WHERE order_id = OLD.id);\nEND;\n--> statement-breakpoint\nCREATE TRIGGER order_no_delete BEFORE DELETE ON \"order\" BEGIN\n SELECT RAISE(ABORT, 'KEDGE_ORDER_IMMUTABLE');\nEND;\n--> statement-breakpoint\n-- No P0.3 writer raises the counters; fulfilment (P1.8) replaces this with its own.\nCREATE TRIGGER order_line_write_once BEFORE UPDATE ON order_line BEGIN\n SELECT RAISE(ABORT, 'KEDGE_ORDER_IMMUTABLE');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER order_line_no_delete BEFORE DELETE ON order_line BEGIN\n SELECT RAISE(ABORT, 'KEDGE_ORDER_IMMUTABLE');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER order_tax_line_write_once BEFORE UPDATE ON order_tax_line BEGIN\n SELECT RAISE(ABORT, 'KEDGE_ORDER_IMMUTABLE');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER order_tax_line_no_delete BEFORE DELETE ON order_tax_line BEGIN\n SELECT RAISE(ABORT, 'KEDGE_ORDER_IMMUTABLE');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER policy_acceptance_write_once BEFORE UPDATE ON policy_acceptance BEGIN\n SELECT RAISE(ABORT, 'KEDGE_ORDER_IMMUTABLE');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER policy_acceptance_no_delete BEFORE DELETE ON policy_acceptance BEGIN\n SELECT RAISE(ABORT, 'KEDGE_ORDER_IMMUTABLE');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER order_seal_due_write_once BEFORE UPDATE ON order_seal_due BEGIN\n SELECT RAISE(ABORT, 'KEDGE_ORDER_IMMUTABLE');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER order_seal_due_no_delete BEFORE DELETE ON order_seal_due BEGIN\n SELECT RAISE(ABORT, 'KEDGE_ORDER_IMMUTABLE');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER contribution_check_write_once BEFORE UPDATE ON contribution_check BEGIN\n SELECT RAISE(ABORT, 'KEDGE_ORDER_IMMUTABLE');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER contribution_check_no_delete BEFORE DELETE ON contribution_check BEGIN\n SELECT RAISE(ABORT, 'KEDGE_ORDER_IMMUTABLE');\nEND;\n","migrations/0020_placed_class_totals.sql":"-- The order.placed snapshot carries both class totals (P0.3 §3.3, K4), so a\n-- consumer can tell revenue from a refundable deposit. Recreates both ways\n-- into `open` (first placement and reopen) with the same steps as 0005,\n-- plus revenue_total and deposit_total in the payload.\nDROP TRIGGER order_place;\n--> statement-breakpoint\nCREATE TRIGGER order_place AFTER UPDATE OF status ON \"order\"\n WHEN OLD.status = 'pending_payment' AND NEW.status = 'open' BEGIN\n UPDATE sequence SET value = value + 1 WHERE name = 'order_number';\n UPDATE \"order\" SET number = (SELECT value FROM sequence WHERE name = 'order_number'),\n placed_at = CAST(unixepoch('subsec') * 1000 AS INTEGER) WHERE id = NEW.id;\n INSERT INTO outbox_event (id, aggregate_type, aggregate_id, type, payload, created_at)\n SELECT 'evt_' || substr(o.id, 5) || '.placed', 'order', o.id, 'order.placed',\n json_object('id', o.id, 'number', o.number, 'status', o.status, 'currency_code', o.currency_code, 'email', o.email,\n 'subtotal', o.subtotal, 'total', o.total, 'revenue_total', o.revenue_total, 'deposit_total', o.deposit_total,\n 'placed_at', o.placed_at,\n 'lines', (SELECT json_group_array(json_object('id', l.id, 'variant_id', l.variant_id, 'sku', l.sku, 'title', l.title,\n 'unit_price', l.unit_price, 'quantity', l.quantity))\n FROM order_line l WHERE l.order_id = o.id)),\n o.placed_at\n FROM \"order\" o WHERE o.id = NEW.id;\n -- Both ways into `open` are the business occurrence \"placed\" (K1).\n INSERT INTO transition (uid, aggregate, aggregate_id, machine, from_state, to_state, at)\n VALUES (NEW.id || ':placed', 'order', NEW.id, 'status', OLD.status, NEW.status, CAST(unixepoch('subsec') * 1000 AS INTEGER));\nEND;\n--> statement-breakpoint\nDROP TRIGGER order_reopen;\n--> statement-breakpoint\nCREATE TRIGGER order_reopen AFTER UPDATE OF status ON \"order\"\n WHEN OLD.status = 'expired' AND NEW.status = 'open' BEGIN\n UPDATE inventory_reservation SET released_at = NULL WHERE order_id = NEW.id AND released_at IS NOT NULL;\n SELECT RAISE(ABORT, 'KEDGE_RESERVATIONS_MISSING')\n WHERE (SELECT count(*) FROM inventory_reservation WHERE order_id = NEW.id AND released_at IS NULL) != NEW.reservation_count;\n UPDATE sequence SET value = value + 1 WHERE name = 'order_number';\n UPDATE \"order\" SET number = (SELECT value FROM sequence WHERE name = 'order_number'),\n placed_at = CAST(unixepoch('subsec') * 1000 AS INTEGER) WHERE id = NEW.id;\n INSERT INTO outbox_event (id, aggregate_type, aggregate_id, type, payload, created_at)\n SELECT 'evt_' || substr(o.id, 5) || '.placed', 'order', o.id, 'order.placed',\n json_object('id', o.id, 'number', o.number, 'status', o.status, 'currency_code', o.currency_code, 'email', o.email,\n 'subtotal', o.subtotal, 'total', o.total, 'revenue_total', o.revenue_total, 'deposit_total', o.deposit_total,\n 'placed_at', o.placed_at,\n 'lines', (SELECT json_group_array(json_object('id', l.id, 'variant_id', l.variant_id, 'sku', l.sku, 'title', l.title,\n 'unit_price', l.unit_price, 'quantity', l.quantity))\n FROM order_line l WHERE l.order_id = o.id)),\n o.placed_at\n FROM \"order\" o WHERE o.id = NEW.id;\n INSERT INTO transition (uid, aggregate, aggregate_id, machine, from_state, to_state, at)\n VALUES (NEW.id || ':placed', 'order', NEW.id, 'status', OLD.status, NEW.status, CAST(unixepoch('subsec') * 1000 AS INTEGER));\nEND;\n","migrations/0021_reopenable.sql":"-- P0.4's preparatory migration (docs/proposals/p0.4-dated-capacity.md §3.3),\n-- N−1 compatible and applied before the cutover: an expired order can be\n-- marked not reopenable on an unchanged P0.3 database, so an oversized one\n-- can be resolved before the cutover's preflight. The cutover's backfill\n-- never touches the column. Until the P0.4 Worker's admin operation exists,\n-- the remedy is its single guarded statement, run with `wrangler d1 execute`:\n-- UPDATE \"order\" SET reopenable = 0 WHERE id = ? AND status = 'expired';\nALTER TABLE `order` ADD `reopenable` integer DEFAULT 1 NOT NULL CONSTRAINT \"order_reopenable\" CHECK(`reopenable` IN (0, 1));\n--> statement-breakpoint\n-- P0.3's write-once guard (D-041), recreated with reopenable's one-way rule.\nDROP TRIGGER order_write_once;\n--> statement-breakpoint\nCREATE TRIGGER order_write_once BEFORE UPDATE ON \"order\" BEGIN\n SELECT RAISE(ABORT, 'KEDGE_ORDER_IMMUTABLE') WHERE\n NEW.id IS NOT OLD.id OR NEW.cart_id IS NOT OLD.cart_id OR NEW.attempt_id IS NOT OLD.attempt_id\n OR NEW.currency_code IS NOT OLD.currency_code OR NEW.email IS NOT OLD.email\n OR NEW.subtotal IS NOT OLD.subtotal OR NEW.discount_total IS NOT OLD.discount_total\n OR NEW.shipping_total IS NOT OLD.shipping_total OR NEW.fee_total IS NOT OLD.fee_total\n OR NEW.tax_total IS NOT OLD.tax_total OR NEW.total IS NOT OLD.total\n OR NEW.payment_status IS NOT OLD.payment_status OR NEW.reservation_count IS NOT OLD.reservation_count\n OR NEW.expires_at IS NOT OLD.expires_at OR NEW.created_at IS NOT OLD.created_at\n OR NEW.tax_inclusive IS NOT OLD.tax_inclusive OR NEW.tax_rate_revenue_ppm IS NOT OLD.tax_rate_revenue_ppm\n OR NEW.tax_rate_deposit_ppm IS NOT OLD.tax_rate_deposit_ppm OR NEW.locale IS NOT OLD.locale\n OR NEW.place_by IS NOT OLD.place_by OR NEW.custom IS NOT OLD.custom\n -- Class totals: only from NULL, before the seal (the previous-Worker derivation, §1.5).\n OR ((NEW.revenue_total IS NOT OLD.revenue_total OR NEW.deposit_total IS NOT OLD.deposit_total)\n AND (OLD.revenue_total IS NOT NULL OR OLD.deposit_total IS NOT NULL\n OR EXISTS (SELECT 1 FROM contribution_check WHERE order_id = OLD.id)))\n -- number and placed_at: only from NULL, while open (what placement writes).\n OR ((NEW.number IS NOT OLD.number OR NEW.placed_at IS NOT OLD.placed_at)\n AND (OLD.number IS NOT NULL OR OLD.placed_at IS NOT NULL OR NEW.status IS NOT 'open'\n OR NEW.number IS NULL OR NEW.placed_at IS NULL))\n -- expired_at: only from NULL, while expired.\n OR (NEW.expired_at IS NOT OLD.expired_at AND (OLD.expired_at IS NOT NULL OR NEW.status IS NOT 'expired'))\n -- reopenable: only 1 → 0, only while expired (p0.4 §2.2).\n OR (NEW.reopenable IS NOT OLD.reopenable AND NOT (OLD.reopenable = 1 AND NEW.reopenable = 0 AND OLD.status = 'expired'));\n SELECT RAISE(ABORT, 'KEDGE_ORDER_UNSEALED') WHERE NEW.status IS NOT OLD.status\n AND NOT EXISTS (SELECT 1 FROM contribution_check WHERE order_id = OLD.id);\nEND;\n--> statement-breakpoint\n-- A late payment can't reopen an order staff marked not reopenable: a\n-- registered permanent refusal, so the payment is voided or refunded (K1).\nCREATE TRIGGER order_reopen_allowed BEFORE UPDATE OF status ON \"order\"\n WHEN OLD.status = 'expired' AND NEW.status = 'open' AND OLD.reopenable = 0 BEGIN\n SELECT RAISE(ABORT, 'KEDGE_NOT_REOPENABLE');\nEND;\n","migrations/0022_dated_capacity.sql":"-- P0.4 dated capacity: the model (docs/proposals/dated-capacity.md §2,\n-- p0.4-dated-capacity.md). The cutover (p0.4 §2.5): after the preflight,\n-- this file's first statements create the batch marker and fence, so a\n-- previous Worker's write is refused from here on; the backfills and guards\n-- follow in 0023. Tables only here.\n\n-- The cutover's preflight (p0.4 §3.3), before the fence: refuses to apply,\n-- naming them, while an order is over either ceiling or a variant has more\n-- than COMPONENTS_MAX parts. RAISE works only in a trigger, so the check\n-- inserts its findings into a table whose trigger raises them.\nCREATE TABLE `cutover_preflight` (`problem` text NOT NULL);\n--> statement-breakpoint\nCREATE TRIGGER cutover_preflight_refuse BEFORE INSERT ON cutover_preflight BEGIN\n SELECT RAISE(ABORT, 'KEDGE_CUTOVER_PREFLIGHT:' || NEW.problem);\nEND;\n--> statement-breakpoint\nINSERT INTO cutover_preflight (problem)\nSELECT x FROM (SELECT group_concat(p, ' ') AS x FROM (\n SELECT 'release_cost:' || o.id AS p FROM \"order\" o\n WHERE (o.status = 'pending_payment' OR (o.status = 'expired' AND o.reopenable = 1))\n AND (SELECT count(*) FROM inventory_reservation r WHERE r.order_id = o.id) > 5000\n UNION ALL\n SELECT 'alloc_cost:' || o.id FROM \"order\" o\n WHERE (SELECT count(*) FROM inventory_reservation r WHERE r.order_id = o.id AND r.released_at IS NULL) > 5000\n UNION ALL\n SELECT 'components:' || variant_id FROM variant_inventory_item GROUP BY variant_id HAVING count(*) > 16\n LIMIT 20)) WHERE x IS NOT NULL;\n--> statement-breakpoint\nDROP TRIGGER cutover_preflight_refuse;\n--> statement-breakpoint\nDROP TABLE `cutover_preflight`;\n--> statement-breakpoint\nCREATE TABLE `batch_marker` (\n\t`id` integer PRIMARY KEY NOT NULL CHECK (`id` = 1),\n\t`release` text NOT NULL,\n\t`cause` text NOT NULL\n);\n--> statement-breakpoint\n-- The write fence (p0.4 §2.5): from here on a core-table write commits only in\n-- a batch whose first statement wrote this release's marker (and whose last\n-- clears it), so a previous Worker's suspended commit or cron chunk is\n-- refused by D1, whatever the deploy did. This migration writes the marker\n-- itself around its own writes.\nINSERT INTO `batch_marker` (`id`, `release`, `cause`) VALUES (1, 'p0.4', 'migration:0022');\n--> statement-breakpoint\nCREATE TRIGGER fence_order_insert BEFORE INSERT ON \"order\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.4' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_order_update BEFORE UPDATE ON \"order\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.4' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_order_delete BEFORE DELETE ON \"order\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.4' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_order_line_insert BEFORE INSERT ON \"order_line\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.4' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_order_line_update BEFORE UPDATE ON \"order_line\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.4' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_order_line_delete BEFORE DELETE ON \"order_line\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.4' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_inventory_reservation_insert BEFORE INSERT ON \"inventory_reservation\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.4' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_inventory_reservation_update BEFORE UPDATE ON \"inventory_reservation\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.4' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_inventory_reservation_delete BEFORE DELETE ON \"inventory_reservation\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.4' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_inventory_level_insert BEFORE INSERT ON \"inventory_level\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.4' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_inventory_level_update BEFORE UPDATE ON \"inventory_level\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.4' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_inventory_level_delete BEFORE DELETE ON \"inventory_level\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.4' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_outbox_event_insert BEFORE INSERT ON \"outbox_event\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.4' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_outbox_event_update BEFORE UPDATE ON \"outbox_event\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.4' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_outbox_event_delete BEFORE DELETE ON \"outbox_event\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.4' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TABLE `store_time` (\n\t`id` integer PRIMARY KEY NOT NULL CHECK (`id` = 1),\n\t`zone` text NOT NULL,\n\t`tz_rules_version` text NOT NULL\n);\n--> statement-breakpoint\nCREATE TABLE `tz_transition` (\n\t`version` text NOT NULL,\n\t`at_utc` integer NOT NULL CHECK (typeof(`at_utc`) = 'integer'),\n\t`offset_seconds` integer NOT NULL CHECK (typeof(`offset_seconds`) = 'integer' AND `offset_seconds` BETWEEN -64800 AND 64800),\n\tPRIMARY KEY (`version`, `at_utc`)\n) WITHOUT ROWID;\n--> statement-breakpoint\n-- A store starts on UTC; `kedge generate` pins its configured zone before any dated row exists (p0.4 §4).\nINSERT INTO `store_time` (`id`, `zone`, `tz_rules_version`) VALUES (1, 'UTC', 'utc');\n--> statement-breakpoint\nINSERT INTO `tz_transition` (`version`, `at_utc`, `offset_seconds`) VALUES ('utc', 0, 0);\n--> statement-breakpoint\nALTER TABLE `inventory_item` ADD `tracking` text DEFAULT 'count' NOT NULL CONSTRAINT \"inventory_item_tracking\" CHECK(`tracking` IN ('count', 'dated', 'none'));\n--> statement-breakpoint\nALTER TABLE `inventory_item` ADD `order_history` integer DEFAULT 0 NOT NULL CONSTRAINT \"inventory_item_order_history\" CHECK(`order_history` IN (0, 1));\n--> statement-breakpoint\nCREATE TABLE `capacity_pool` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`kind` text NOT NULL CHECK (length(`kind`) BETWEEN 1 AND 64),\n\t`inventory_item_id` text REFERENCES `inventory_item`(`id`),\n\t`location_id` text REFERENCES `stock_location`(`id`),\n\t`bucket_minutes` integer NOT NULL CHECK (typeof(`bucket_minutes`) = 'integer' AND `bucket_minutes` BETWEEN 5 AND 1440 AND 1440 % `bucket_minutes` = 0),\n\t`max_span` integer NOT NULL CHECK (typeof(`max_span`) = 'integer' AND `max_span` BETWEEN 1 AND 3660),\n\t`initial_base` integer NOT NULL CHECK (typeof(`initial_base`) = 'integer' AND `initial_base` BETWEEN 0 AND 1000000000),\n\t`compacted_before` integer DEFAULT 0 NOT NULL CHECK (typeof(`compacted_before`) = 'integer' AND `compacted_before` BETWEEN 0 AND 1000000000),\n\t`state` text DEFAULT 'active' NOT NULL CHECK (`state` IN ('active', 'retired')),\n\t`tz_rules_version` text NOT NULL,\n\t`created_at` integer NOT NULL,\n\tCHECK ((`kind` = 'item') = (`inventory_item_id` IS NOT NULL AND `location_id` IS NOT NULL)),\n\tCHECK (`kind` = 'item' OR (`inventory_item_id` IS NULL AND `location_id` IS NULL))\n);\n--> statement-breakpoint\nCREATE UNIQUE INDEX `capacity_pool_item_active` ON `capacity_pool` (`inventory_item_id`, `location_id`) WHERE `state` = 'active';\n--> statement-breakpoint\n-- The compaction cursor pages live pools only, so retired history never lengthens a cron page (codex code r15).\nCREATE INDEX `capacity_pool_active` ON `capacity_pool` (`id`) WHERE `state` = 'active';\n--> statement-breakpoint\nCREATE TABLE `capacity_base_change` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`pool_id` text NOT NULL REFERENCES `capacity_pool`(`id`),\n\t`from_bucket` integer NOT NULL CHECK (typeof(`from_bucket`) = 'integer' AND `from_bucket` BETWEEN 0 AND 1000000000),\n\t`delta` integer NOT NULL CHECK (typeof(`delta`) = 'integer' AND `delta` != 0),\n\t`origin` text NOT NULL CHECK (`origin` IN ('scheduled', 'physical', 'conversion')),\n\t`intended_date` integer CHECK (`intended_date` IS NULL OR typeof(`intended_date`) = 'integer'),\n\t`state` text DEFAULT 'active' NOT NULL CHECK (`state` IN ('active', 'canceled')),\n\t`reason` text NOT NULL,\n\t`actor` text NOT NULL,\n\t`tz_rules_version` text NOT NULL,\n\t`created_at` integer NOT NULL,\n\t`canceled_at` integer,\n\tCHECK ((`origin` = 'conversion' AND `delta` BETWEEN -1000000000000 AND 1000000000000)\n\t OR (`origin` != 'conversion' AND `delta` BETWEEN -1000000000 AND 1000000000)),\n\tCHECK ((`origin` = 'scheduled') = (`intended_date` IS NOT NULL))\n);\n--> statement-breakpoint\nCREATE INDEX `capacity_base_change_scheduled` ON `capacity_base_change` (`pool_id`, `origin`, `from_bucket`) WHERE `state` = 'active';\n--> statement-breakpoint\nCREATE TABLE `capacity_base` (\n\t`pool_id` text NOT NULL REFERENCES `capacity_pool`(`id`),\n\t`from_bucket` integer NOT NULL CHECK (typeof(`from_bucket`) = 'integer' AND `from_bucket` BETWEEN 0 AND 1000000000),\n\t`base` integer NOT NULL CHECK (typeof(`base`) = 'integer' AND `base` BETWEEN -1000000000000 AND 1000000000000),\n\tPRIMARY KEY (`pool_id`, `from_bucket`)\n) WITHOUT ROWID;\n--> statement-breakpoint\nCREATE TABLE `capacity_pattern` (\n\t`pool_id` text NOT NULL REFERENCES `capacity_pool`(`id`),\n\t`slot_of_week` integer NOT NULL CHECK (typeof(`slot_of_week`) = 'integer' AND `slot_of_week` >= 0),\n\t`ceiling` integer NOT NULL CHECK (typeof(`ceiling`) = 'integer' AND `ceiling` BETWEEN 0 AND 1000000000),\n\tPRIMARY KEY (`pool_id`, `slot_of_week`)\n) WITHOUT ROWID;\n--> statement-breakpoint\nCREATE TABLE `capacity_adjustment` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`pool_id` text NOT NULL REFERENCES `capacity_pool`(`id`),\n\t`first_bucket` integer NOT NULL CHECK (typeof(`first_bucket`) = 'integer' AND `first_bucket` BETWEEN 0 AND 1000000000),\n\t`last_bucket` integer NOT NULL CHECK (typeof(`last_bucket`) = 'integer' AND `last_bucket` BETWEEN 0 AND 1000000000),\n\t`delta` integer NOT NULL CHECK (typeof(`delta`) = 'integer' AND `delta` != 0 AND `delta` BETWEEN -1000000000 AND 1000000000),\n\t`state` text DEFAULT 'active' NOT NULL CHECK (`state` IN ('active', 'ended', 'canceled')),\n\t`retire_after` integer NOT NULL CHECK (typeof(`retire_after`) = 'integer'),\n\t`reason` text NOT NULL,\n\t`actor` text NOT NULL,\n\t`tz_rules_version` text NOT NULL,\n\t`created_at` integer NOT NULL,\n\t`state_changed_at` integer,\n\tCHECK (`first_bucket` <= `last_bucket`)\n);\n--> statement-breakpoint\nCREATE INDEX `capacity_adjustment_active` ON `capacity_adjustment` (`pool_id`, `first_bucket`) WHERE `state` = 'active';\n--> statement-breakpoint\nCREATE INDEX `capacity_adjustment_due` ON `capacity_adjustment` (`retire_after`, `id`) WHERE `state` = 'active';\n--> statement-breakpoint\nCREATE TABLE `capacity_bucket` (\n\t`pool_id` text NOT NULL REFERENCES `capacity_pool`(`id`),\n\t`bucket` integer NOT NULL CHECK (typeof(`bucket`) = 'integer' AND `bucket` BETWEEN 0 AND 1000000000),\n\t`held` integer DEFAULT 0 NOT NULL CHECK (typeof(`held`) = 'integer' AND `held` BETWEEN 0 AND 1000000000000),\n\tPRIMARY KEY (`pool_id`, `bucket`)\n) WITHOUT ROWID;\n--> statement-breakpoint\nCREATE INDEX `capacity_bucket_held` ON `capacity_bucket` (`pool_id`, `bucket`) WHERE `held` > 0;\n--> statement-breakpoint\nCREATE TABLE `capacity_hold` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`pool_id` text NOT NULL REFERENCES `capacity_pool`(`id`),\n\t`kind` text NOT NULL CHECK (length(`kind`) BETWEEN 1 AND 64),\n\t`order_id` text REFERENCES `order`(`id`),\n\t`order_line_id` text REFERENCES `order_line`(`id`),\n\t`first_bucket` integer NOT NULL CHECK (typeof(`first_bucket`) = 'integer' AND `first_bucket` BETWEEN 0 AND 1000000000),\n\t`last_bucket` integer NOT NULL CHECK (typeof(`last_bucket`) = 'integer' AND `last_bucket` BETWEEN 0 AND 1000000000),\n\t`starts_at` integer NOT NULL CHECK (typeof(`starts_at`) = 'integer'),\n\t`ends_at` integer NOT NULL CHECK (typeof(`ends_at`) = 'integer'),\n\t`retire_after` integer NOT NULL CHECK (typeof(`retire_after`) = 'integer'),\n\t`qty` integer NOT NULL CHECK (typeof(`qty`) = 'integer' AND `qty` BETWEEN 1 AND 100000),\n\t`buffer_before` integer DEFAULT 0 NOT NULL CHECK (typeof(`buffer_before`) = 'integer' AND `buffer_before` BETWEEN 0 AND 3660),\n\t`buffer_after` integer DEFAULT 0 NOT NULL CHECK (typeof(`buffer_after`) = 'integer' AND `buffer_after` BETWEEN 0 AND 3660),\n\t`start_source` text DEFAULT 'scheduled' NOT NULL CHECK (`start_source` IN ('scheduled', 'physical')),\n\t`end_source` text DEFAULT 'scheduled' NOT NULL CHECK (`end_source` IN ('scheduled', 'physical')),\n\t`tz_rules_version` text NOT NULL,\n\t`released_at` integer,\n\t`created_at` integer NOT NULL,\n\tCHECK (`first_bucket` <= `last_bucket`),\n\tCHECK (`starts_at` < `ends_at`)\n);\n--> statement-breakpoint\nCREATE INDEX `capacity_hold_order` ON `capacity_hold` (`order_id`);\n--> statement-breakpoint\nCREATE INDEX `capacity_hold_order_active` ON `capacity_hold` (`order_id`) WHERE `released_at` IS NULL;\n--> statement-breakpoint\nCREATE INDEX `capacity_hold_pool_retire` ON `capacity_hold` (`pool_id`, `retire_after`) WHERE `released_at` IS NULL;\n--> statement-breakpoint\nCREATE INDEX `capacity_hold_pool_first` ON `capacity_hold` (`pool_id`, `first_bucket`, `id`) WHERE `released_at` IS NULL;\n--> statement-breakpoint\nCREATE INDEX `inventory_reservation_order_active` ON `inventory_reservation` (`order_id`) WHERE `released_at` IS NULL;\n--> statement-breakpoint\nCREATE INDEX `inventory_reservation_level_active` ON `inventory_reservation` (`inventory_item_id`, `location_id`, `id`) WHERE `released_at` IS NULL;\n--> statement-breakpoint\nCREATE TABLE `hold_retirement_due` (\n\t`hold_id` text PRIMARY KEY NOT NULL REFERENCES `capacity_hold`(`id`),\n\t`pool_id` text NOT NULL,\n\t`retire_after` integer NOT NULL\n);\n--> statement-breakpoint\nCREATE INDEX `hold_retirement_due_at` ON `hold_retirement_due` (`retire_after`, `hold_id`);\n--> statement-breakpoint\nCREATE INDEX `hold_retirement_due_pool` ON `hold_retirement_due` (`pool_id`, `retire_after`);\n--> statement-breakpoint\nCREATE TABLE `reopenable_hold_pool` (\n\t`pool_id` text NOT NULL,\n\t`order_id` text NOT NULL,\n\tPRIMARY KEY (`pool_id`, `order_id`)\n) WITHOUT ROWID;\n--> statement-breakpoint\nCREATE INDEX `reopenable_hold_pool_order` ON `reopenable_hold_pool` (`order_id`);\n--> statement-breakpoint\nCREATE TABLE `pending_expiry` (\n\t`resource_kind` text NOT NULL CHECK (`resource_kind` IN ('item', 'pool')),\n\t`resource_id` text NOT NULL,\n\t`expires_at` integer NOT NULL,\n\t`order_id` text NOT NULL,\n\tPRIMARY KEY (`resource_kind`, `resource_id`, `expires_at`, `order_id`)\n) WITHOUT ROWID;\n--> statement-breakpoint\nCREATE INDEX `pending_expiry_order` ON `pending_expiry` (`order_id`);\n--> statement-breakpoint\nCREATE TABLE `capacity_buffer` (\n\t`kind` text NOT NULL,\n\t`pool_kind` text NOT NULL,\n\t`before_buckets` integer DEFAULT 0 NOT NULL CHECK (typeof(`before_buckets`) = 'integer' AND `before_buckets` BETWEEN 0 AND 3660),\n\t`after_buckets` integer DEFAULT 0 NOT NULL CHECK (typeof(`after_buckets`) = 'integer' AND `after_buckets` BETWEEN 0 AND 3660),\n\tPRIMARY KEY (`kind`, `pool_kind`)\n) WITHOUT ROWID;\n--> statement-breakpoint\nCREATE TABLE `capacity_scope_map` (\n\t`mapping` text NOT NULL,\n\t`key` text NOT NULL,\n\t`pool_id` text NOT NULL REFERENCES `capacity_pool`(`id`),\n\tPRIMARY KEY (`mapping`, `key`, `pool_id`)\n) WITHOUT ROWID;\n--> statement-breakpoint\n-- A pool's mappings by pool: retiring it deletes them through this, never a scan.\nCREATE INDEX `capacity_scope_map_pool` ON `capacity_scope_map` (`pool_id`);\n--> statement-breakpoint\n-- Order counts and costs (§2, §4.2, §7a). Ceilings come from\n-- core/src/capacity/limits.ts; the migration composer substitutes them.\nALTER TABLE `order` ADD `hold_count` integer DEFAULT 0 NOT NULL CONSTRAINT \"order_hold_count_int\" CHECK(typeof(`hold_count`) = 'integer' AND `hold_count` BETWEEN 0 AND 10000);\n--> statement-breakpoint\nALTER TABLE `order` ADD `release_cost` integer DEFAULT 0 NOT NULL CONSTRAINT \"order_release_cost_int\" CHECK(typeof(`release_cost`) = 'integer' AND `release_cost` >= 0\n -- The expiry ceiling (p0.4 §3.3, decision 5): where expiry or reopen can still do the work.\n AND (NOT (`status` = 'pending_payment' OR (`status` = 'expired' AND `reopenable` = 1)) OR `release_cost` <= 5000));\n--> statement-breakpoint\nALTER TABLE `order` ADD `current_alloc_cost` integer DEFAULT 0 NOT NULL CONSTRAINT \"order_current_alloc_cost_int\" CHECK(typeof(`current_alloc_cost`) = 'integer' AND `current_alloc_cost` BETWEEN 0 AND 5000);\n--> statement-breakpoint\nCREATE TABLE `order_line_component` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`order_line_id` text NOT NULL REFERENCES `order_line`(`id`),\n\t`inventory_item_id` text NOT NULL REFERENCES `inventory_item`(`id`),\n\t`tracking` text NOT NULL CHECK (`tracking` IN ('count', 'dated', 'none')),\n\t`current_version` integer DEFAULT 1 NOT NULL CHECK (typeof(`current_version`) = 'integer' AND `current_version` >= 1),\n\t`source` text NOT NULL CHECK (`source` IN ('catalogue', 'reservation')),\n\tUNIQUE (`order_line_id`, `inventory_item_id`)\n);\n--> statement-breakpoint\nCREATE TABLE `order_line_component_version` (\n\t`component_id` text NOT NULL REFERENCES `order_line_component`(`id`),\n\t`version` integer NOT NULL CHECK (typeof(`version`) = 'integer' AND `version` >= 1),\n\t`line_qty` integer NOT NULL CHECK (typeof(`line_qty`) = 'integer' AND `line_qty` BETWEEN 1 AND 100000),\n\t`qty_per_unit` integer CHECK (`qty_per_unit` IS NULL OR (typeof(`qty_per_unit`) = 'integer' AND `qty_per_unit` BETWEEN 1 AND 100000)),\n\t`ordered_qty` integer NOT NULL CHECK (typeof(`ordered_qty`) = 'integer' AND `ordered_qty` BETWEEN 1 AND 10000000000),\n\tPRIMARY KEY (`component_id`, `version`)\n) WITHOUT ROWID;\n--> statement-breakpoint\nCREATE TABLE `order_line_component_requirement` (\n\t`id` integer PRIMARY KEY AUTOINCREMENT,\n\t`component_id` text NOT NULL,\n\t`version` integer NOT NULL,\n\t`location_id` text NOT NULL REFERENCES `stock_location`(`id`),\n\t`pool_id` text REFERENCES `capacity_pool`(`id`),\n\t`qty` integer NOT NULL CHECK (typeof(`qty`) = 'integer' AND `qty` BETWEEN 1 AND 10000000000),\n\t`required_first_bucket` integer CHECK (`required_first_bucket` IS NULL OR typeof(`required_first_bucket`) = 'integer'),\n\t`required_last_bucket` integer CHECK (`required_last_bucket` IS NULL OR typeof(`required_last_bucket`) = 'integer'),\n\t`first_source` text CHECK (`first_source` IS NULL OR `first_source` IN ('scheduled', 'physical')),\n\t`last_source` text CHECK (`last_source` IS NULL OR `last_source` IN ('scheduled', 'physical')),\n\tFOREIGN KEY (`component_id`, `version`) REFERENCES `order_line_component_version`(`component_id`, `version`)\n);\n--> statement-breakpoint\nCREATE INDEX `order_line_component_requirement_component` ON `order_line_component_requirement` (`component_id`, `version`);\n--> statement-breakpoint\nCREATE INDEX `order_line_component_line` ON `order_line_component` (`order_line_id`);\n--> statement-breakpoint\nCREATE TABLE `order_place_by_source` (\n\t`order_id` text NOT NULL REFERENCES `order`(`id`),\n\t`seq` integer NOT NULL CHECK (typeof(`seq`) = 'integer' AND `seq` >= 0),\n\t`hold_id` text REFERENCES `capacity_hold`(`id`),\n\t`line_id` text REFERENCES `order_line`(`id`),\n\t`anchor` text NOT NULL CHECK (`anchor` IN ('line_period', 'hold_start', 'bucket_start', 'bucket_end', 'absolute')),\n\t`offset_minutes` integer CHECK (`offset_minutes` IS NULL OR typeof(`offset_minutes`) = 'integer'),\n\t`at` integer NOT NULL CHECK (typeof(`at`) = 'integer'),\n\t`source` text NOT NULL,\n\t`tz_rules_version` text NOT NULL,\n\tPRIMARY KEY (`order_id`, `seq`),\n\tCHECK (`anchor` NOT IN ('hold_start', 'bucket_start', 'bucket_end') OR `hold_id` IS NOT NULL),\n\tCHECK (`anchor` != 'line_period' OR (`line_id` IS NOT NULL AND `offset_minutes` IS NOT NULL))\n) WITHOUT ROWID;\n--> statement-breakpoint\nCREATE INDEX `order_place_by_source_hold` ON `order_place_by_source` (`hold_id`);\n--> statement-breakpoint\n-- The kind's cutoff on each line, so the seal can require a period line's deadline row (p0.4 §2.2).\nALTER TABLE `order_line` ADD `cutoff_minutes` integer CONSTRAINT \"order_line_cutoff_minutes\" CHECK(`cutoff_minutes` IS NULL OR (typeof(`cutoff_minutes`) = 'integer' AND `cutoff_minutes` BETWEEN 0 AND 1000000));\n--> statement-breakpoint\n-- Each transition row records the cause its batch's marker carried (K1's attribution, docs/04).\nALTER TABLE `transition` ADD `batch_cause` text;\n--> statement-breakpoint\n-- Capacity commands' retry identity (p0.4 §6), docs/06's idempotency scope.\nCREATE TABLE `capacity_command` (\n\t`principal` text NOT NULL,\n\t`operation` text NOT NULL,\n\t`key` text NOT NULL CHECK (length(`key`) BETWEEN 1 AND 255),\n\t`payload_sha256` text NOT NULL,\n\t`result` text NOT NULL,\n\t`created_at` integer NOT NULL,\n\tPRIMARY KEY (`principal`, `operation`, `key`)\n) WITHOUT ROWID;\n--> statement-breakpoint\nCREATE TABLE `order_line_period_provenance` (\n\t`order_line_id` text PRIMARY KEY NOT NULL REFERENCES `order_line`(`id`),\n\t`tz_rules_version` text NOT NULL,\n\t`source` text NOT NULL CHECK (`source` IN ('wall_time', 'legacy_instant'))\n);\n--> statement-breakpoint\n-- Maintenance cursors: retirement, compaction, adjustment ending and shortage scans\n-- page through their driving index and persist where they stopped (§4.3).\nCREATE TABLE `maintenance_cursor` (\n\t`name` text PRIMARY KEY NOT NULL,\n\t`cursor` text NOT NULL,\n\t`updated_at` integer NOT NULL\n);\n--> statement-breakpoint\n-- The shortage consumer's queue (§5): one row per availability-lowering event, in\n-- insertion order. AUTOINCREMENT, because events come from two clocks (the Worker's\n-- and D1's) and some ids are random, so neither created_at nor id orders them, and a\n-- cursor on either could pass an event for good. A declared INTEGER PRIMARY KEY\n-- survives VACUUM, unlike an implicit rowid.\nCREATE TABLE `shortage_queue` (\n\t`seq` integer PRIMARY KEY AUTOINCREMENT,\n\t`event_id` text NOT NULL\n);\n--> statement-breakpoint\nCREATE TABLE `shortage_seen` (\n\t`change_id` text NOT NULL,\n\t`booking_id` text NOT NULL,\n\tPRIMARY KEY (`change_id`, `booking_id`)\n) WITHOUT ROWID;\n--> statement-breakpoint\n-- A conversion's or replacement's guard (dated-capacity.md §2.1): its final\n-- batch's first statement inserts one row of counts, each bounded by its own\n-- index, and a CHECK refuses the whole batch before any write; the batch then\n-- deletes the row. Constraint names map to the API's codes.\nCREATE TABLE `capacity_guard` (\n\t`locations` integer NOT NULL CONSTRAINT \"conversion_locations\" CHECK (`locations` <= 32),\n\t`queue` integer NOT NULL CONSTRAINT \"conversion_cleanup\" CHECK (`queue` = 0),\n\t`holds` integer NOT NULL CONSTRAINT \"conversion_holds\" CHECK (`holds` = 0),\n\t`reservations` integer NOT NULL CONSTRAINT \"conversion_reservations\" CHECK (`reservations` = 0),\n\t`dated_facts` integer NOT NULL CONSTRAINT \"conversion_dated_facts\" CHECK (`dated_facts` = 0),\n\t`negative_base` integer NOT NULL CONSTRAINT \"conversion_negative_base\" CHECK (`negative_base` = 0),\n\t`history` integer NOT NULL CONSTRAINT \"conversion_history\" CHECK (`history` = 0),\n\t`unplanned` integer NOT NULL CONSTRAINT \"conversion_unplanned\" CHECK (`unplanned` = 0)\n);\n--> statement-breakpoint\nDELETE FROM `batch_marker`;\n","migrations/0023_dated_capacity_triggers.sql":"-- P0.4's backfills and invariants in D1 (docs/proposals/dated-capacity.md\n-- §2.2–§3.6a, p0.4-dated-capacity.md §2–§3). Rendered from\n-- packages/db/sql/0023_dated_capacity_triggers.sql by `pnpm render-sql`, so\n-- effective capacity, labels and both costs are one expression everywhere\n-- (packages/db/src/capacity-sql.ts); a test re-renders and compares. Each\n-- RAISE code is in @kedge/db's CORE_RAISE_CODES.\n--\n-- The fence (0022) refuses core writes without this release's marker, so the\n-- backfills write it first; the guards come after them, so history isn't\n-- held to rules it was written before.\nINSERT INTO batch_marker (id, release, cause) VALUES (1, 'p0.4', 'migration:0023');\n--> statement-breakpoint\n\n-- ---- Backfills (§2.2, p0.4 §3.3): each derived state's rebuild query, each recorded fact once ----\n-- Part 1 had no holds, so hold_count stays 0; both costs from their rebuild queries.\n-- Qualified: inside the cost subqueries a bare id would be the reservation's or the hold's.\nUPDATE \"order\" SET release_cost = ((SELECT count(*) FROM inventory_reservation ir WHERE ir.order_id = \"order\".id) + (SELECT coalesce(sum(ch.last_bucket - ch.first_bucket + 1), 0) FROM capacity_hold ch WHERE ch.order_id = \"order\".id)), current_alloc_cost = ((SELECT count(*) FROM inventory_reservation ir WHERE ir.order_id = \"order\".id AND ir.released_at IS NULL) + (SELECT coalesce(sum(ch.last_bucket - ch.first_bucket + 1), 0) FROM capacity_hold ch WHERE ch.order_id = \"order\".id AND ch.released_at IS NULL));\n--> statement-breakpoint\n-- Every item any reservation ever named, and, conservatively, every item of a\n-- variant on any order line: over-marking only blocks a conversion (§2).\nUPDATE inventory_item SET order_history = 1\n WHERE id IN (SELECT inventory_item_id FROM inventory_reservation)\n OR id IN (SELECT vi.inventory_item_id FROM variant_inventory_item vi WHERE vi.variant_id IN (SELECT variant_id FROM order_line));\n--> statement-breakpoint\n-- Legacy components (§3.6a): each line's reservations grouped by item, one\n-- requirement per reservation, never from today's recipe.\nINSERT INTO order_line_component (id, order_line_id, inventory_item_id, tracking, current_version, source)\nSELECT 'olc_' || substr(r.order_line_id, 6) || '.' || r.inventory_item_id, r.order_line_id, r.inventory_item_id, i.tracking, 1, 'reservation'\n FROM (SELECT DISTINCT order_line_id, inventory_item_id FROM inventory_reservation) r\n JOIN inventory_item i ON i.id = r.inventory_item_id;\n--> statement-breakpoint\nINSERT INTO order_line_component_version (component_id, version, line_qty, qty_per_unit, ordered_qty)\nSELECT c.id, 1, l.quantity,\n CASE WHEN s.q % l.quantity = 0 THEN s.q / l.quantity END, s.q\n FROM order_line_component c JOIN order_line l ON l.id = c.order_line_id\n JOIN (SELECT order_line_id, inventory_item_id, sum(quantity) AS q FROM inventory_reservation GROUP BY 1, 2) s\n ON s.order_line_id = c.order_line_id AND s.inventory_item_id = c.inventory_item_id;\n--> statement-breakpoint\nINSERT INTO order_line_component_requirement (component_id, version, location_id, pool_id, qty)\nSELECT c.id, 1, r.location_id, NULL, r.quantity\n FROM inventory_reservation r JOIN order_line_component c\n ON c.order_line_id = r.order_line_id AND c.inventory_item_id = r.inventory_item_id\n ORDER BY r.id;\n--> statement-breakpoint\nINSERT INTO pending_expiry (resource_kind, resource_id, expires_at, order_id)\nSELECT DISTINCT 'item', r.inventory_item_id, o.expires_at, o.id\n FROM inventory_reservation r JOIN \"order\" o ON o.id = r.order_id\n WHERE o.status = 'pending_payment' AND r.released_at IS NULL;\n--> statement-breakpoint\n-- Legacy bookings are classified, not reconstructed (p0.4 §2.2): a sold\n-- deadline stays a fixed instant, and a period stays the instants it was sold as.\nINSERT INTO order_place_by_source (order_id, seq, hold_id, line_id, anchor, offset_minutes, at, source, tz_rules_version)\nSELECT id, 0, NULL, NULL, 'absolute', NULL, place_by, 'legacy:p0.3', (SELECT tz_rules_version FROM store_time WHERE id = 1)\n FROM \"order\" WHERE place_by IS NOT NULL;\n--> statement-breakpoint\nINSERT INTO order_line_period_provenance (order_line_id, tz_rules_version, source)\nSELECT id, (SELECT tz_rules_version FROM store_time WHERE id = 1), 'legacy_instant'\n FROM order_line WHERE period_start IS NOT NULL;\n--> statement-breakpoint\n\n-- ---- Holds (§3.1) ----\nCREATE TRIGGER capacity_hold_insert BEFORE INSERT ON capacity_hold BEGIN\n SELECT RAISE(ABORT, 'KEDGE_HOLD_INSERTED_RELEASED') WHERE NEW.released_at IS NOT NULL;\n SELECT RAISE(ABORT, 'KEDGE_TZ_VERSION') WHERE NEW.tz_rules_version IS NOT (SELECT tz_rules_version FROM store_time WHERE id = 1);\n SELECT RAISE(ABORT, 'KEDGE_POOL_RETIRED') WHERE (SELECT state FROM capacity_pool WHERE id = NEW.pool_id) IS NOT 'active';\n SELECT RAISE(ABORT, 'KEDGE_HOLD_SPAN') WHERE NEW.last_bucket - NEW.first_bucket >= (SELECT max_span FROM capacity_pool WHERE id = NEW.pool_id);\n -- Owner rule: an order hold belongs to a pending order (and its line); an orderless hold names neither.\n SELECT RAISE(ABORT, 'KEDGE_HOLD_OWNER') WHERE\n (NEW.kind = 'order' AND (NEW.order_id IS NULL\n OR (SELECT status FROM \"order\" WHERE id = NEW.order_id) IS NOT 'pending_payment'\n OR (NEW.order_line_id IS NOT NULL AND (SELECT order_id FROM order_line WHERE id = NEW.order_line_id) IS NOT NEW.order_id)))\n OR (NEW.kind IN ('block', 'maintenance', 'import') AND (NEW.order_id IS NOT NULL OR NEW.order_line_id IS NOT NULL))\n OR NEW.kind NOT IN ('order', 'block', 'maintenance', 'import');\n SELECT RAISE(ABORT, 'KEDGE_ORDER_SEALED') WHERE NEW.order_id IS NOT NULL\n AND EXISTS (SELECT 1 FROM contribution_check WHERE order_id = NEW.order_id);\n -- Mode: an item pool's item is tracked by date (§2.1).\n SELECT RAISE(ABORT, 'KEDGE_TRACKING_MODE:' || p.inventory_item_id) FROM capacity_pool p JOIN inventory_item i ON i.id = p.inventory_item_id\n WHERE p.id = NEW.pool_id AND p.kind = 'item' AND i.tracking != 'dated';\n -- Materialise the range with a targeted conflict clause, never OR IGNORE.\n INSERT INTO capacity_bucket (pool_id, bucket, held)\n WITH RECURSIVE r(b) AS (SELECT NEW.first_bucket UNION ALL SELECT b + 1 FROM r WHERE b < NEW.last_bucket)\n SELECT NEW.pool_id, b, 0 FROM r WHERE true\n ON CONFLICT (pool_id, bucket) DO NOTHING;\n SELECT RAISE(ABORT, 'KEDGE_HOLD_RANGE') WHERE (SELECT count(*) FROM capacity_bucket\n WHERE pool_id = NEW.pool_id AND bucket BETWEEN NEW.first_bucket AND NEW.last_bucket) != NEW.last_bucket - NEW.first_bucket + 1;\n SELECT RAISE(ABORT, 'KEDGE_UNAVAILABLE:' || NEW.pool_id || ':' || b.bucket) FROM capacity_bucket b\n WHERE b.pool_id = NEW.pool_id AND b.bucket BETWEEN NEW.first_bucket AND NEW.last_bucket\n AND b.held + NEW.qty > min(1000000000000, max(0, min((SELECT cb.base FROM capacity_base cb WHERE cb.pool_id = b.pool_id AND cb.from_bucket <= b.bucket ORDER BY cb.from_bucket DESC LIMIT 1), coalesce((SELECT cpt.ceiling FROM capacity_pattern cpt WHERE cpt.pool_id = b.pool_id AND cpt.slot_of_week = ((((CAST((b.bucket) AS INTEGER)) / (1440 / (SELECT pp.bucket_minutes FROM capacity_pool pp WHERE pp.id = b.pool_id))) + 3) % 7) * (1440 / (SELECT pp.bucket_minutes FROM capacity_pool pp WHERE pp.id = b.pool_id)) + ((CAST((b.bucket) AS INTEGER)) % (1440 / (SELECT pp.bucket_minutes FROM capacity_pool pp WHERE pp.id = b.pool_id)))), (SELECT cb.base FROM capacity_base cb WHERE cb.pool_id = b.pool_id AND cb.from_bucket <= b.bucket ORDER BY cb.from_bucket DESC LIMIT 1))) + coalesce((SELECT sum(ca.delta) FROM capacity_adjustment ca WHERE ca.pool_id = b.pool_id AND ca.state = 'active' AND ca.first_bucket BETWEEN (b.bucket) - (SELECT pp.max_span FROM capacity_pool pp WHERE pp.id = b.pool_id) AND (b.bucket) AND ca.last_bucket >= (b.bucket)), 0)))\n ORDER BY b.bucket LIMIT 1;\nEND;\n--> statement-breakpoint\nCREATE TRIGGER capacity_hold_count AFTER INSERT ON capacity_hold BEGIN\n UPDATE capacity_bucket SET held = held + NEW.qty WHERE pool_id = NEW.pool_id AND bucket BETWEEN NEW.first_bucket AND NEW.last_bucket;\n -- Recorded facts and derived queues (§2.2), kept by the fact's own trigger.\n UPDATE inventory_item SET order_history = 1 WHERE order_history = 0 AND NEW.order_id IS NOT NULL\n AND id = (SELECT inventory_item_id FROM capacity_pool WHERE id = NEW.pool_id);\n INSERT INTO pending_expiry (resource_kind, resource_id, expires_at, order_id)\n SELECT 'pool', NEW.pool_id, o.expires_at, o.id FROM \"order\" o WHERE o.id = NEW.order_id AND o.status = 'pending_payment'\n ON CONFLICT (resource_kind, resource_id, expires_at, order_id) DO NOTHING;\n INSERT INTO hold_retirement_due (hold_id, pool_id, retire_after)\n SELECT NEW.id, NEW.pool_id, NEW.retire_after WHERE NEW.order_id IS NULL;\nEND;\n--> statement-breakpoint\nCREATE TRIGGER capacity_hold_immutable BEFORE UPDATE OF id, pool_id, kind, order_id, order_line_id, first_bucket, last_bucket,\n starts_at, ends_at, retire_after, qty, buffer_before, buffer_after, start_source, end_source, tz_rules_version, created_at\n ON capacity_hold BEGIN\n SELECT RAISE(ABORT, 'KEDGE_HOLD_IMMUTABLE');\nEND;\n--> statement-breakpoint\n-- A hold frees capacity only through its lifecycle (p0.4 §2.2; codex code r14): an order's when\n-- the order has left the states that hold it (expire, cancel), or by retirement (a completed\n-- order's or an orderless hold, due in the queue, in the cron's batch); an orderless hold also\n-- by the staff command. A batch without core's marker has no cause and is refused, so a stray\n-- write can't free capacity under a pending or open order whose cost and coverage still count it.\nCREATE TRIGGER capacity_hold_release_check BEFORE UPDATE OF released_at ON capacity_hold\n WHEN OLD.released_at IS NULL AND NEW.released_at IS NOT NULL BEGIN\n SELECT RAISE(ABORT, 'KEDGE_HOLD_RELEASE') WHERE NOT (\n (NEW.order_id IS NOT NULL AND (SELECT status FROM \"order\" WHERE id = NEW.order_id) IN ('expired', 'canceled'))\n OR ((SELECT cause FROM batch_marker WHERE id = 1) IS 'cron.retire'\n AND EXISTS (SELECT 1 FROM hold_retirement_due WHERE hold_id = NEW.id AND retire_after <= NEW.released_at))\n OR (NEW.order_id IS NULL AND (SELECT cause FROM batch_marker WHERE id = 1) IS 'capacity.hold.release'));\nEND;\n--> statement-breakpoint\nCREATE TRIGGER capacity_hold_release AFTER UPDATE OF released_at ON capacity_hold\n WHEN OLD.released_at IS NULL AND NEW.released_at IS NOT NULL BEGIN\n UPDATE capacity_bucket SET held = held - NEW.qty WHERE pool_id = NEW.pool_id AND bucket BETWEEN NEW.first_bucket AND NEW.last_bucket;\n DELETE FROM hold_retirement_due WHERE hold_id = NEW.id;\nEND;\n--> statement-breakpoint\nCREATE TRIGGER capacity_hold_reactivate_check BEFORE UPDATE OF released_at ON capacity_hold\n WHEN OLD.released_at IS NOT NULL AND NEW.released_at IS NULL BEGIN\n SELECT RAISE(ABORT, 'KEDGE_POOL_RETIRED') WHERE (SELECT state FROM capacity_pool WHERE id = NEW.pool_id) IS NOT 'active';\n SELECT RAISE(ABORT, 'KEDGE_TRACKING_MODE:' || p.inventory_item_id) FROM capacity_pool p JOIN inventory_item i ON i.id = p.inventory_item_id\n WHERE p.id = NEW.pool_id AND p.kind = 'item' AND i.tracking != 'dated';\n -- An order hold comes back only through reopen, which opens the order first.\n SELECT RAISE(ABORT, 'KEDGE_HOLD_OWNER') WHERE NEW.order_id IS NOT NULL\n AND (SELECT status FROM \"order\" WHERE id = NEW.order_id) IS NOT 'open';\n INSERT INTO capacity_bucket (pool_id, bucket, held)\n WITH RECURSIVE r(b) AS (SELECT NEW.first_bucket UNION ALL SELECT b + 1 FROM r WHERE b < NEW.last_bucket)\n SELECT NEW.pool_id, b, 0 FROM r WHERE true\n ON CONFLICT (pool_id, bucket) DO NOTHING;\n SELECT RAISE(ABORT, 'KEDGE_UNAVAILABLE:' || NEW.pool_id || ':' || b.bucket) FROM capacity_bucket b\n WHERE b.pool_id = NEW.pool_id AND b.bucket BETWEEN NEW.first_bucket AND NEW.last_bucket\n AND b.held + NEW.qty > min(1000000000000, max(0, min((SELECT cb.base FROM capacity_base cb WHERE cb.pool_id = b.pool_id AND cb.from_bucket <= b.bucket ORDER BY cb.from_bucket DESC LIMIT 1), coalesce((SELECT cpt.ceiling FROM capacity_pattern cpt WHERE cpt.pool_id = b.pool_id AND cpt.slot_of_week = ((((CAST((b.bucket) AS INTEGER)) / (1440 / (SELECT pp.bucket_minutes FROM capacity_pool pp WHERE pp.id = b.pool_id))) + 3) % 7) * (1440 / (SELECT pp.bucket_minutes FROM capacity_pool pp WHERE pp.id = b.pool_id)) + ((CAST((b.bucket) AS INTEGER)) % (1440 / (SELECT pp.bucket_minutes FROM capacity_pool pp WHERE pp.id = b.pool_id)))), (SELECT cb.base FROM capacity_base cb WHERE cb.pool_id = b.pool_id AND cb.from_bucket <= b.bucket ORDER BY cb.from_bucket DESC LIMIT 1))) + coalesce((SELECT sum(ca.delta) FROM capacity_adjustment ca WHERE ca.pool_id = b.pool_id AND ca.state = 'active' AND ca.first_bucket BETWEEN (b.bucket) - (SELECT pp.max_span FROM capacity_pool pp WHERE pp.id = b.pool_id) AND (b.bucket) AND ca.last_bucket >= (b.bucket)), 0)))\n ORDER BY b.bucket LIMIT 1;\nEND;\n--> statement-breakpoint\nCREATE TRIGGER capacity_hold_reactivate AFTER UPDATE OF released_at ON capacity_hold\n WHEN OLD.released_at IS NOT NULL AND NEW.released_at IS NULL BEGIN\n UPDATE capacity_bucket SET held = held + NEW.qty WHERE pool_id = NEW.pool_id AND bucket BETWEEN NEW.first_bucket AND NEW.last_bucket;\n INSERT INTO hold_retirement_due (hold_id, pool_id, retire_after)\n SELECT NEW.id, NEW.pool_id, NEW.retire_after WHERE NEW.order_id IS NULL\n ON CONFLICT (hold_id) DO NOTHING;\nEND;\n--> statement-breakpoint\n-- Archiving only: never while active, or while its order can still reopen.\nCREATE TRIGGER capacity_hold_delete BEFORE DELETE ON capacity_hold\n WHEN OLD.released_at IS NULL OR (OLD.order_id IS NOT NULL AND NOT EXISTS (\n SELECT 1 FROM \"order\" o WHERE o.id = OLD.order_id AND (o.status IN ('canceled', 'completed') OR (o.status = 'expired' AND o.reopenable = 0)))) BEGIN\n SELECT RAISE(ABORT, 'KEDGE_HOLD_IN_USE');\nEND;\n--> statement-breakpoint\n\n-- ---- Buckets: the new-demand backstop (§3.2) ----\nCREATE TRIGGER capacity_bucket_backstop BEFORE UPDATE OF held ON capacity_bucket\n WHEN NEW.held > OLD.held AND NEW.held > min(1000000000000, max(0, min((SELECT cb.base FROM capacity_base cb WHERE cb.pool_id = NEW.pool_id AND cb.from_bucket <= NEW.bucket ORDER BY cb.from_bucket DESC LIMIT 1), coalesce((SELECT cpt.ceiling FROM capacity_pattern cpt WHERE cpt.pool_id = NEW.pool_id AND cpt.slot_of_week = ((((CAST((NEW.bucket) AS INTEGER)) / (1440 / (SELECT pp.bucket_minutes FROM capacity_pool pp WHERE pp.id = NEW.pool_id))) + 3) % 7) * (1440 / (SELECT pp.bucket_minutes FROM capacity_pool pp WHERE pp.id = NEW.pool_id)) + ((CAST((NEW.bucket) AS INTEGER)) % (1440 / (SELECT pp.bucket_minutes FROM capacity_pool pp WHERE pp.id = NEW.pool_id)))), (SELECT cb.base FROM capacity_base cb WHERE cb.pool_id = NEW.pool_id AND cb.from_bucket <= NEW.bucket ORDER BY cb.from_bucket DESC LIMIT 1))) + coalesce((SELECT sum(ca.delta) FROM capacity_adjustment ca WHERE ca.pool_id = NEW.pool_id AND ca.state = 'active' AND ca.first_bucket BETWEEN (NEW.bucket) - (SELECT pp.max_span FROM capacity_pool pp WHERE pp.id = NEW.pool_id) AND (NEW.bucket) AND ca.last_bucket >= (NEW.bucket)), 0))) BEGIN\n SELECT RAISE(ABORT, 'KEDGE_CAPACITY_BACKSTOP');\nEND;\n--> statement-breakpoint\n\n-- ---- Pools and capacity changes (§2, §2.1, §3.3) ----\n-- An item pool is a dated item's, at one location; every pool records the pinned rules.\nCREATE TRIGGER capacity_pool_check BEFORE INSERT ON capacity_pool BEGIN\n SELECT RAISE(ABORT, 'KEDGE_TZ_VERSION') WHERE NEW.tz_rules_version IS NOT (SELECT tz_rules_version FROM store_time WHERE id = 1);\n SELECT RAISE(ABORT, 'KEDGE_POOL_RETIRED') WHERE NEW.state != 'active' OR NEW.compacted_before != 0;\n SELECT RAISE(ABORT, 'KEDGE_TRACKING_MODE:' || NEW.inventory_item_id) WHERE NEW.kind = 'item'\n AND (SELECT tracking FROM inventory_item WHERE id = NEW.inventory_item_id) IS NOT 'dated';\nEND;\n--> statement-breakpoint\n-- A pool starts with one step at bucket 0 carrying its initial base.\nCREATE TRIGGER capacity_pool_insert AFTER INSERT ON capacity_pool BEGIN\n INSERT INTO capacity_base (pool_id, from_bucket, base) VALUES (NEW.id, 0, NEW.initial_base);\nEND;\n--> statement-breakpoint\nCREATE TRIGGER capacity_pool_guard BEFORE UPDATE ON capacity_pool BEGIN\n SELECT RAISE(ABORT, 'KEDGE_POOL_IMMUTABLE') WHERE NEW.id IS NOT OLD.id OR NEW.kind IS NOT OLD.kind\n OR NEW.inventory_item_id IS NOT OLD.inventory_item_id OR NEW.location_id IS NOT OLD.location_id\n OR NEW.bucket_minutes IS NOT OLD.bucket_minutes OR NEW.max_span IS NOT OLD.max_span\n OR NEW.initial_base IS NOT OLD.initial_base OR NEW.tz_rules_version IS NOT OLD.tz_rules_version\n OR NEW.created_at IS NOT OLD.created_at\n OR NEW.compacted_before < OLD.compacted_before\n OR (OLD.state = 'retired' AND NEW.state = 'active');\n -- Retiring is a guarded operation (§2.1): nothing live may stay on the pool.\n SELECT RAISE(ABORT, 'KEDGE_POOL_RETIRE_BLOCKED') WHERE OLD.state = 'active' AND NEW.state = 'retired' AND (\n EXISTS (SELECT 1 FROM capacity_hold WHERE pool_id = OLD.id AND released_at IS NULL)\n OR EXISTS (SELECT 1 FROM capacity_pattern WHERE pool_id = OLD.id)\n OR EXISTS (SELECT 1 FROM capacity_adjustment WHERE pool_id = OLD.id AND state = 'active'\n AND retire_after > CAST(unixepoch('subsec') * 1000 AS INTEGER))\n OR EXISTS (SELECT 1 FROM capacity_base_change WHERE pool_id = OLD.id AND state = 'active' AND origin = 'scheduled'\n AND from_bucket > ((((CAST(unixepoch('subsec') * 1000 AS INTEGER)) + 1000 * (SELECT tt.offset_seconds FROM tz_transition tt WHERE tt.version = (SELECT st.tz_rules_version FROM store_time st WHERE st.id = 1) AND tt.at_utc <= (CAST(unixepoch('subsec') * 1000 AS INTEGER)) ORDER BY tt.at_utc DESC LIMIT 1)) / 86400000) * (1440 / (SELECT pp.bucket_minutes FROM capacity_pool pp WHERE pp.id = OLD.id))))\n OR (SELECT base FROM capacity_base WHERE pool_id = OLD.id AND from_bucket <= ((((CAST(unixepoch('subsec') * 1000 AS INTEGER)) + 1000 * (SELECT tt.offset_seconds FROM tz_transition tt WHERE tt.version = (SELECT st.tz_rules_version FROM store_time st WHERE st.id = 1) AND tt.at_utc <= (CAST(unixepoch('subsec') * 1000 AS INTEGER)) ORDER BY tt.at_utc DESC LIMIT 1)) / 86400000) * (1440 / (SELECT pp.bucket_minutes FROM capacity_pool pp WHERE pp.id = OLD.id)))\n ORDER BY from_bucket DESC LIMIT 1) != 0\n -- A same-mode replacement: a late reopen wouldn't be stopped by the mode check.\n OR ((SELECT tracking FROM inventory_item WHERE id = OLD.inventory_item_id) = 'dated'\n AND EXISTS (SELECT 1 FROM reopenable_hold_pool WHERE pool_id = OLD.id)));\nEND;\n--> statement-breakpoint\n-- A retired pool keeps no mappings, so a key's scope reads live pools only and\n-- never retired history (dated-capacity.md §4.1); a replacement moves them first.\nCREATE TRIGGER capacity_pool_retired_unmapped AFTER UPDATE OF state ON capacity_pool\n WHEN OLD.state = 'active' AND NEW.state = 'retired' BEGIN\n DELETE FROM capacity_scope_map WHERE pool_id = NEW.id;\nEND;\n--> statement-breakpoint\n-- A mapping names an active plugin pool: item capacity is held only by core, for lines.\nCREATE TRIGGER capacity_scope_map_insert BEFORE INSERT ON capacity_scope_map BEGIN\n SELECT RAISE(ABORT, 'KEDGE_POOL_RETIRED') WHERE (SELECT state FROM capacity_pool WHERE id = NEW.pool_id) IS NOT 'active';\n SELECT RAISE(ABORT, 'KEDGE_SCOPE_MAP_ITEM') WHERE (SELECT kind FROM capacity_pool WHERE id = NEW.pool_id) = 'item';\nEND;\n--> statement-breakpoint\nCREATE TRIGGER capacity_scope_map_update BEFORE UPDATE ON capacity_scope_map BEGIN\n SELECT RAISE(ABORT, 'KEDGE_POOL_RETIRED') WHERE (SELECT state FROM capacity_pool WHERE id = NEW.pool_id) IS NOT 'active';\n SELECT RAISE(ABORT, 'KEDGE_SCOPE_MAP_ITEM') WHERE (SELECT kind FROM capacity_pool WHERE id = NEW.pool_id) = 'item';\nEND;\n--> statement-breakpoint\nCREATE TRIGGER capacity_pool_no_delete BEFORE DELETE ON capacity_pool BEGIN\n SELECT RAISE(ABORT, 'KEDGE_POOL_IMMUTABLE');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER capacity_base_change_insert BEFORE INSERT ON capacity_base_change BEGIN\n SELECT RAISE(ABORT, 'KEDGE_TZ_VERSION') WHERE NEW.tz_rules_version IS NOT (SELECT tz_rules_version FROM store_time WHERE id = 1);\n SELECT RAISE(ABORT, 'KEDGE_BASE_CHANGE_STATE') WHERE NEW.state != 'active' OR NEW.canceled_at IS NOT NULL;\n -- A retired pool takes only the conversion's change to 0, written before it retires.\n SELECT RAISE(ABORT, 'KEDGE_POOL_RETIRED') WHERE (SELECT state FROM capacity_pool WHERE id = NEW.pool_id) IS NOT 'active';\n -- A scheduled change is for a future local date (a decrease may start a day earlier, §3.3).\n SELECT RAISE(ABORT, 'KEDGE_SCHEDULED_LIMIT') WHERE NEW.origin = 'scheduled'\n AND (NEW.intended_date <= (((CAST(unixepoch('subsec') * 1000 AS INTEGER)) + 1000 * (SELECT tt.offset_seconds FROM tz_transition tt WHERE tt.version = (SELECT st.tz_rules_version FROM store_time st WHERE st.id = 1) AND tt.at_utc <= (CAST(unixepoch('subsec') * 1000 AS INTEGER)) ORDER BY tt.at_utc DESC LIMIT 1)) / 86400000)\n OR (SELECT count(*) FROM capacity_base_change WHERE pool_id = NEW.pool_id AND origin = 'scheduled' AND state = 'active'\n AND from_bucket > ((((CAST(unixepoch('subsec') * 1000 AS INTEGER)) + 1000 * (SELECT tt.offset_seconds FROM tz_transition tt WHERE tt.version = (SELECT st.tz_rules_version FROM store_time st WHERE st.id = 1) AND tt.at_utc <= (CAST(unixepoch('subsec') * 1000 AS INTEGER)) ORDER BY tt.at_utc DESC LIMIT 1)) / 86400000) * (1440 / (SELECT pp.bucket_minutes FROM capacity_pool pp WHERE pp.id = NEW.pool_id)))) >= 50);\nEND;\n--> statement-breakpoint\n-- The steps follow the active changes (D-027's pattern); no bucket is touched.\n-- A change before the compaction watermark folds into the bucket-0 baseline (§3.3).\nCREATE TRIGGER capacity_base_change_apply AFTER INSERT ON capacity_base_change BEGIN\n INSERT INTO capacity_base (pool_id, from_bucket, base)\n SELECT NEW.pool_id, NEW.from_bucket,\n (SELECT base FROM capacity_base WHERE pool_id = NEW.pool_id AND from_bucket <= NEW.from_bucket ORDER BY from_bucket DESC LIMIT 1)\n WHERE NEW.from_bucket >= (SELECT compacted_before FROM capacity_pool WHERE id = NEW.pool_id)\n ON CONFLICT (pool_id, from_bucket) DO NOTHING;\n UPDATE capacity_base SET base = base + NEW.delta WHERE pool_id = NEW.pool_id\n AND from_bucket >= CASE WHEN NEW.from_bucket < (SELECT compacted_before FROM capacity_pool WHERE id = NEW.pool_id) THEN 0 ELSE NEW.from_bucket END;\n SELECT RAISE(ABORT, 'KEDGE_BASE_STEPS') WHERE (SELECT count(*) FROM capacity_base WHERE pool_id = NEW.pool_id) > 500;\n INSERT INTO outbox_event (id, aggregate_type, aggregate_id, type, payload, created_at)\n SELECT 'evt_' || NEW.id || '.base_negative', 'capacity_pool', NEW.pool_id, 'capacity.base_negative',\n json_object('pool_id', NEW.pool_id, 'change_id', NEW.id), NEW.created_at\n WHERE EXISTS (SELECT 1 FROM capacity_base WHERE pool_id = NEW.pool_id AND base < 0 AND from_bucket >= NEW.from_bucket);\n INSERT INTO outbox_event (id, aggregate_type, aggregate_id, type, payload, created_at)\n SELECT 'evt_' || NEW.id || '.capacity_changed', 'capacity_pool', NEW.pool_id, 'pool.capacity_changed',\n json_object('pool_id', NEW.pool_id, 'change_id', NEW.id, 'from_bucket', NEW.from_bucket), NEW.created_at\n WHERE NEW.delta < 0;\nEND;\n--> statement-breakpoint\n-- Only a scheduled change's cancellation, before it takes effect; every other field is immutable.\nCREATE TRIGGER capacity_base_change_guard BEFORE UPDATE ON capacity_base_change BEGIN\n SELECT RAISE(ABORT, 'KEDGE_BASE_CHANGE_IMMUTABLE') WHERE NEW.id IS NOT OLD.id OR NEW.pool_id IS NOT OLD.pool_id\n OR NEW.from_bucket IS NOT OLD.from_bucket OR NEW.delta IS NOT OLD.delta OR NEW.origin IS NOT OLD.origin\n OR NEW.intended_date IS NOT OLD.intended_date OR NEW.reason IS NOT OLD.reason OR NEW.actor IS NOT OLD.actor\n OR NEW.tz_rules_version IS NOT OLD.tz_rules_version OR NEW.created_at IS NOT OLD.created_at\n OR NOT (OLD.state = 'active' AND NEW.state = 'canceled' AND NEW.canceled_at IS NOT NULL AND OLD.origin = 'scheduled'\n AND OLD.from_bucket > ((((CAST(unixepoch('subsec') * 1000 AS INTEGER)) + 1000 * (SELECT tt.offset_seconds FROM tz_transition tt WHERE tt.version = (SELECT st.tz_rules_version FROM store_time st WHERE st.id = 1) AND tt.at_utc <= (CAST(unixepoch('subsec') * 1000 AS INTEGER)) ORDER BY tt.at_utc DESC LIMIT 1)) / 86400000) * (1440 / (SELECT pp.bucket_minutes FROM capacity_pool pp WHERE pp.id = OLD.pool_id))));\nEND;\n--> statement-breakpoint\nCREATE TRIGGER capacity_base_change_cancel AFTER UPDATE OF state ON capacity_base_change\n WHEN OLD.state = 'active' AND NEW.state = 'canceled' BEGIN\n UPDATE capacity_base SET base = base - OLD.delta WHERE pool_id = OLD.pool_id\n AND from_bucket >= CASE WHEN OLD.from_bucket < (SELECT compacted_before FROM capacity_pool WHERE id = OLD.pool_id) THEN 0 ELSE OLD.from_bucket END;\n -- The canonical form keeps one step per distinct active from_bucket (§2.2).\n DELETE FROM capacity_base WHERE pool_id = OLD.pool_id AND from_bucket = OLD.from_bucket AND from_bucket != 0\n AND from_bucket >= (SELECT compacted_before FROM capacity_pool WHERE id = OLD.pool_id)\n AND NOT EXISTS (SELECT 1 FROM capacity_base_change WHERE pool_id = OLD.pool_id AND from_bucket = OLD.from_bucket AND state = 'active');\n INSERT INTO outbox_event (id, aggregate_type, aggregate_id, type, payload, created_at)\n SELECT 'evt_' || OLD.id || '.cancel_changed', 'capacity_pool', OLD.pool_id, 'pool.capacity_changed',\n json_object('pool_id', OLD.pool_id, 'change_id', OLD.id, 'from_bucket', OLD.from_bucket), NEW.canceled_at\n WHERE OLD.delta > 0;\nEND;\n--> statement-breakpoint\nCREATE TRIGGER capacity_base_change_no_delete BEFORE DELETE ON capacity_base_change BEGIN\n SELECT RAISE(ABORT, 'KEDGE_BASE_CHANGE_IMMUTABLE');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER capacity_adjustment_insert BEFORE INSERT ON capacity_adjustment BEGIN\n SELECT RAISE(ABORT, 'KEDGE_TZ_VERSION') WHERE NEW.tz_rules_version IS NOT (SELECT tz_rules_version FROM store_time WHERE id = 1);\n SELECT RAISE(ABORT, 'KEDGE_POOL_RETIRED') WHERE (SELECT state FROM capacity_pool WHERE id = NEW.pool_id) IS NOT 'active';\n SELECT RAISE(ABORT, 'KEDGE_ADJUSTMENT_STATE') WHERE NEW.state != 'active' OR NEW.state_changed_at IS NOT NULL;\n SELECT RAISE(ABORT, 'KEDGE_HOLD_SPAN') WHERE NEW.last_bucket - NEW.first_bucket >= (SELECT max_span FROM capacity_pool WHERE id = NEW.pool_id);\n SELECT RAISE(ABORT, 'KEDGE_ADJUSTMENT_LIMIT') WHERE (SELECT count(*) FROM capacity_adjustment WHERE pool_id = NEW.pool_id AND state = 'active') >= 200;\nEND;\n--> statement-breakpoint\nCREATE TRIGGER capacity_adjustment_lowered AFTER INSERT ON capacity_adjustment WHEN NEW.delta < 0 BEGIN\n INSERT INTO outbox_event (id, aggregate_type, aggregate_id, type, payload, created_at)\n VALUES ('evt_' || NEW.id || '.capacity_changed', 'capacity_pool', NEW.pool_id, 'pool.capacity_changed',\n json_object('pool_id', NEW.pool_id, 'adjustment_id', NEW.id, 'from_bucket', NEW.first_bucket), NEW.created_at);\nEND;\n--> statement-breakpoint\n-- Ended by the cron once its last label can't recur; cancelled by staff. Both final.\nCREATE TRIGGER capacity_adjustment_guard BEFORE UPDATE ON capacity_adjustment BEGIN\n SELECT RAISE(ABORT, 'KEDGE_ADJUSTMENT_IMMUTABLE') WHERE NEW.id IS NOT OLD.id OR NEW.pool_id IS NOT OLD.pool_id\n OR NEW.first_bucket IS NOT OLD.first_bucket OR NEW.last_bucket IS NOT OLD.last_bucket OR NEW.delta IS NOT OLD.delta\n OR NEW.retire_after IS NOT OLD.retire_after OR NEW.reason IS NOT OLD.reason OR NEW.actor IS NOT OLD.actor\n OR NEW.tz_rules_version IS NOT OLD.tz_rules_version OR NEW.created_at IS NOT OLD.created_at\n OR NOT (OLD.state = 'active' AND NEW.state_changed_at IS NOT NULL AND (NEW.state = 'canceled'\n OR (NEW.state = 'ended' AND OLD.retire_after <= CAST(unixepoch('subsec') * 1000 AS INTEGER))));\nEND;\n--> statement-breakpoint\nCREATE TRIGGER capacity_adjustment_canceled AFTER UPDATE OF state ON capacity_adjustment\n WHEN NEW.state = 'canceled' AND OLD.delta > 0 BEGIN\n INSERT INTO outbox_event (id, aggregate_type, aggregate_id, type, payload, created_at)\n VALUES ('evt_' || NEW.id || '.cancel_changed', 'capacity_pool', NEW.pool_id, 'pool.capacity_changed',\n json_object('pool_id', NEW.pool_id, 'adjustment_id', NEW.id, 'from_bucket', NEW.first_bucket), NEW.state_changed_at);\nEND;\n--> statement-breakpoint\nCREATE TRIGGER capacity_adjustment_no_delete BEFORE DELETE ON capacity_adjustment BEGIN\n SELECT RAISE(ABORT, 'KEDGE_ADJUSTMENT_IMMUTABLE');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER capacity_pattern_insert BEFORE INSERT ON capacity_pattern BEGIN\n SELECT RAISE(ABORT, 'KEDGE_PATTERN_SLOT') WHERE NEW.slot_of_week >= 7 * 1440 / (SELECT bucket_minutes FROM capacity_pool WHERE id = NEW.pool_id);\n SELECT RAISE(ABORT, 'KEDGE_POOL_RETIRED') WHERE (SELECT state FROM capacity_pool WHERE id = NEW.pool_id) IS NOT 'active';\nEND;\n--> statement-breakpoint\nCREATE TRIGGER capacity_pattern_update BEFORE UPDATE ON capacity_pattern\n WHEN NEW.pool_id IS NOT OLD.pool_id OR NEW.slot_of_week IS NOT OLD.slot_of_week BEGIN\n SELECT RAISE(ABORT, 'KEDGE_PATTERN_SLOT');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER capacity_pattern_lowered AFTER INSERT ON capacity_pattern BEGIN\n INSERT INTO outbox_event (id, aggregate_type, aggregate_id, type, payload, created_at)\n -- A random id: two commands on one slot within a millisecond must not collide.\n VALUES ('evt_' || lower(hex(randomblob(12))) || '.ceiling',\n 'capacity_pool', NEW.pool_id, 'pool.capacity_changed', json_object('pool_id', NEW.pool_id, 'slot_of_week', NEW.slot_of_week),\n CAST(unixepoch('subsec') * 1000 AS INTEGER));\nEND;\n--> statement-breakpoint\nCREATE TRIGGER capacity_pattern_lowered_update AFTER UPDATE OF ceiling ON capacity_pattern WHEN NEW.ceiling < OLD.ceiling BEGIN\n INSERT INTO outbox_event (id, aggregate_type, aggregate_id, type, payload, created_at)\n -- A random id: two commands on one slot within a millisecond must not collide.\n VALUES ('evt_' || lower(hex(randomblob(12))) || '.ceiling',\n 'capacity_pool', NEW.pool_id, 'pool.capacity_changed', json_object('pool_id', NEW.pool_id, 'slot_of_week', NEW.slot_of_week),\n CAST(unixepoch('subsec') * 1000 AS INTEGER));\nEND;\n--> statement-breakpoint\n\n-- ---- Shortage discovery's queue (§5): every availability-lowering event, in order ----\nCREATE TRIGGER outbox_shortage_queue AFTER INSERT ON outbox_event\n WHEN NEW.type IN ('pool.capacity_changed', 'level.availability_changed') BEGIN\n INSERT INTO shortage_queue (event_id) VALUES (NEW.id);\nEND;\n--> statement-breakpoint\n\n-- ---- Counted stock joins the same bookkeeping (§2.1, §2.2, §4.2) ----\n-- The reservation mode checks are part of the reservation checks, recreated in 0024.\nCREATE TRIGGER reservation_bookkeeping AFTER INSERT ON inventory_reservation BEGIN\n UPDATE inventory_item SET order_history = 1 WHERE id = NEW.inventory_item_id AND order_history = 0;\n INSERT INTO pending_expiry (resource_kind, resource_id, expires_at, order_id)\n SELECT 'item', NEW.inventory_item_id, o.expires_at, o.id FROM \"order\" o WHERE o.id = NEW.order_id AND o.status = 'pending_payment'\n ON CONFLICT (resource_kind, resource_id, expires_at, order_id) DO NOTHING;\nEND;\n--> statement-breakpoint\nCREATE TRIGGER order_line_component_history AFTER INSERT ON order_line_component BEGIN\n UPDATE inventory_item SET order_history = 1 WHERE id = NEW.inventory_item_id AND order_history = 0;\nEND;\n--> statement-breakpoint\nCREATE TRIGGER inventory_item_history_guard BEFORE UPDATE OF order_history ON inventory_item\n WHEN OLD.order_history = 1 AND NEW.order_history = 0 BEGIN\n SELECT RAISE(ABORT, 'KEDGE_ORDER_HISTORY');\nEND;\n--> statement-breakpoint\n\n-- ---- Order transitions, recreated with holds (§3.4) ----\n-- One trigger per transition, consequences before the transition row, so\n-- plugin acquire triggers on that row see core's effects (P0.2).\nDROP TRIGGER order_expire;\n--> statement-breakpoint\nCREATE TRIGGER order_expire AFTER UPDATE OF status ON \"order\"\n WHEN OLD.status = 'pending_payment' AND NEW.status = 'expired' BEGIN\n UPDATE inventory_reservation SET released_at = CAST(unixepoch('subsec') * 1000 AS INTEGER)\n WHERE order_id = NEW.id AND released_at IS NULL;\n -- Pools the released holds touched, for the same-mode replacement guard.\n INSERT INTO reopenable_hold_pool (pool_id, order_id)\n SELECT DISTINCT pool_id, NEW.id FROM capacity_hold WHERE order_id = NEW.id AND released_at IS NULL\n ON CONFLICT (pool_id, order_id) DO NOTHING;\n UPDATE capacity_hold SET released_at = CAST(unixepoch('subsec') * 1000 AS INTEGER)\n WHERE order_id = NEW.id AND released_at IS NULL;\n DELETE FROM pending_expiry WHERE order_id = NEW.id;\n UPDATE \"order\" SET current_alloc_cost = ((SELECT count(*) FROM inventory_reservation ir WHERE ir.order_id = NEW.id AND ir.released_at IS NULL) + (SELECT coalesce(sum(ch.last_bucket - ch.first_bucket + 1), 0) FROM capacity_hold ch WHERE ch.order_id = NEW.id AND ch.released_at IS NULL)) WHERE id = NEW.id;\n INSERT INTO transition (uid, aggregate, aggregate_id, machine, from_state, to_state, at, batch_cause)\n VALUES (NEW.id || ':expired', 'order', NEW.id, 'status', OLD.status, NEW.status, CAST(unixepoch('subsec') * 1000 AS INTEGER),\n (SELECT cause FROM batch_marker WHERE id = 1));\nEND;\n--> statement-breakpoint\nDROP TRIGGER order_cancel;\n--> statement-breakpoint\nCREATE TRIGGER order_cancel AFTER UPDATE OF status ON \"order\"\n WHEN OLD.status IN ('pending_payment', 'open') AND NEW.status = 'canceled' BEGIN\n UPDATE inventory_reservation SET released_at = CAST(unixepoch('subsec') * 1000 AS INTEGER)\n WHERE order_id = NEW.id AND released_at IS NULL;\n UPDATE capacity_hold SET released_at = CAST(unixepoch('subsec') * 1000 AS INTEGER)\n WHERE order_id = NEW.id AND released_at IS NULL;\n DELETE FROM pending_expiry WHERE order_id = NEW.id;\n UPDATE \"order\" SET current_alloc_cost = ((SELECT count(*) FROM inventory_reservation ir WHERE ir.order_id = NEW.id AND ir.released_at IS NULL) + (SELECT coalesce(sum(ch.last_bucket - ch.first_bucket + 1), 0) FROM capacity_hold ch WHERE ch.order_id = NEW.id AND ch.released_at IS NULL)) WHERE id = NEW.id;\n INSERT INTO transition (uid, aggregate, aggregate_id, machine, from_state, to_state, at, batch_cause)\n VALUES (NEW.id || ':canceled', 'order', NEW.id, 'status', OLD.status, NEW.status, CAST(unixepoch('subsec') * 1000 AS INTEGER),\n (SELECT cause FROM batch_marker WHERE id = 1));\nEND;\n--> statement-breakpoint\nDROP TRIGGER order_place;\n--> statement-breakpoint\nCREATE TRIGGER order_place AFTER UPDATE OF status ON \"order\"\n WHEN OLD.status = 'pending_payment' AND NEW.status = 'open' BEGIN\n DELETE FROM pending_expiry WHERE order_id = NEW.id;\n UPDATE sequence SET value = value + 1 WHERE name = 'order_number';\n UPDATE \"order\" SET number = (SELECT value FROM sequence WHERE name = 'order_number'),\n placed_at = CAST(unixepoch('subsec') * 1000 AS INTEGER) WHERE id = NEW.id;\n INSERT INTO outbox_event (id, aggregate_type, aggregate_id, type, payload, created_at)\n SELECT 'evt_' || substr(o.id, 5) || '.placed', 'order', o.id, 'order.placed',\n json_object('id', o.id, 'number', o.number, 'status', o.status, 'currency_code', o.currency_code, 'email', o.email,\n 'subtotal', o.subtotal, 'total', o.total, 'revenue_total', o.revenue_total, 'deposit_total', o.deposit_total,\n 'placed_at', o.placed_at,\n 'lines', (SELECT json_group_array(json_object('id', l.id, 'variant_id', l.variant_id, 'sku', l.sku, 'title', l.title,\n 'unit_price', l.unit_price, 'quantity', l.quantity))\n FROM order_line l WHERE l.order_id = o.id)),\n o.placed_at\n FROM \"order\" o WHERE o.id = NEW.id;\n INSERT INTO transition (uid, aggregate, aggregate_id, machine, from_state, to_state, at, batch_cause)\n VALUES (NEW.id || ':placed', 'order', NEW.id, 'status', OLD.status, NEW.status, CAST(unixepoch('subsec') * 1000 AS INTEGER),\n (SELECT cause FROM batch_marker WHERE id = 1));\nEND;\n--> statement-breakpoint\nDROP TRIGGER order_reopen;\n--> statement-breakpoint\nCREATE TRIGGER order_reopen AFTER UPDATE OF status ON \"order\"\n WHEN OLD.status = 'expired' AND NEW.status = 'open' BEGIN\n UPDATE inventory_reservation SET released_at = NULL WHERE order_id = NEW.id AND released_at IS NOT NULL;\n SELECT RAISE(ABORT, 'KEDGE_RESERVATIONS_MISSING')\n WHERE (SELECT count(*) FROM inventory_reservation WHERE order_id = NEW.id AND released_at IS NULL) != NEW.reservation_count;\n -- Each hold is re-checked as it comes back: a resold day refuses the reopen (§3.4).\n UPDATE capacity_hold SET released_at = NULL WHERE order_id = NEW.id AND released_at IS NOT NULL;\n SELECT RAISE(ABORT, 'KEDGE_RESERVATIONS_MISSING')\n WHERE (SELECT count(*) FROM capacity_hold WHERE order_id = NEW.id AND released_at IS NULL) != NEW.hold_count;\n DELETE FROM reopenable_hold_pool WHERE order_id = NEW.id;\n UPDATE \"order\" SET current_alloc_cost = ((SELECT count(*) FROM inventory_reservation ir WHERE ir.order_id = NEW.id AND ir.released_at IS NULL) + (SELECT coalesce(sum(ch.last_bucket - ch.first_bucket + 1), 0) FROM capacity_hold ch WHERE ch.order_id = NEW.id AND ch.released_at IS NULL)) WHERE id = NEW.id;\n UPDATE sequence SET value = value + 1 WHERE name = 'order_number';\n UPDATE \"order\" SET number = (SELECT value FROM sequence WHERE name = 'order_number'),\n placed_at = CAST(unixepoch('subsec') * 1000 AS INTEGER) WHERE id = NEW.id;\n INSERT INTO outbox_event (id, aggregate_type, aggregate_id, type, payload, created_at)\n SELECT 'evt_' || substr(o.id, 5) || '.placed', 'order', o.id, 'order.placed',\n json_object('id', o.id, 'number', o.number, 'status', o.status, 'currency_code', o.currency_code, 'email', o.email,\n 'subtotal', o.subtotal, 'total', o.total, 'revenue_total', o.revenue_total, 'deposit_total', o.deposit_total,\n 'placed_at', o.placed_at,\n 'lines', (SELECT json_group_array(json_object('id', l.id, 'variant_id', l.variant_id, 'sku', l.sku, 'title', l.title,\n 'unit_price', l.unit_price, 'quantity', l.quantity))\n FROM order_line l WHERE l.order_id = o.id)),\n o.placed_at\n FROM \"order\" o WHERE o.id = NEW.id;\n INSERT INTO transition (uid, aggregate, aggregate_id, machine, from_state, to_state, at, batch_cause)\n VALUES (NEW.id || ':placed', 'order', NEW.id, 'status', OLD.status, NEW.status, CAST(unixepoch('subsec') * 1000 AS INTEGER),\n (SELECT cause FROM batch_marker WHERE id = 1));\nEND;\n--> statement-breakpoint\nDROP TRIGGER order_complete;\n--> statement-breakpoint\n-- Completion doesn't release dated holds; it makes them retirable (§3.5).\nCREATE TRIGGER order_complete AFTER UPDATE OF status ON \"order\"\n WHEN OLD.status = 'open' AND NEW.status = 'completed' BEGIN\n INSERT INTO hold_retirement_due (hold_id, pool_id, retire_after)\n SELECT id, pool_id, retire_after FROM capacity_hold WHERE order_id = NEW.id AND released_at IS NULL\n ON CONFLICT (hold_id) DO NOTHING;\n INSERT INTO transition (uid, aggregate, aggregate_id, machine, from_state, to_state, at, batch_cause)\n VALUES (NEW.id || ':completed', 'order', NEW.id, 'status', OLD.status, NEW.status, CAST(unixepoch('subsec') * 1000 AS INTEGER),\n (SELECT cause FROM batch_marker WHERE id = 1));\nEND;\n--> statement-breakpoint\n\n-- ---- Write-once order rows, with P0.4's columns (D-041, p0.4 §2.2) ----\nDROP TRIGGER order_write_once;\n--> statement-breakpoint\nCREATE TRIGGER order_write_once BEFORE UPDATE ON \"order\" BEGIN\n SELECT RAISE(ABORT, 'KEDGE_ORDER_IMMUTABLE') WHERE\n NEW.id IS NOT OLD.id OR NEW.cart_id IS NOT OLD.cart_id OR NEW.attempt_id IS NOT OLD.attempt_id\n OR NEW.currency_code IS NOT OLD.currency_code OR NEW.email IS NOT OLD.email\n OR NEW.subtotal IS NOT OLD.subtotal OR NEW.discount_total IS NOT OLD.discount_total\n OR NEW.shipping_total IS NOT OLD.shipping_total OR NEW.fee_total IS NOT OLD.fee_total\n OR NEW.tax_total IS NOT OLD.tax_total OR NEW.total IS NOT OLD.total\n OR NEW.payment_status IS NOT OLD.payment_status OR NEW.reservation_count IS NOT OLD.reservation_count\n OR NEW.expires_at IS NOT OLD.expires_at OR NEW.created_at IS NOT OLD.created_at\n OR NEW.tax_inclusive IS NOT OLD.tax_inclusive OR NEW.tax_rate_revenue_ppm IS NOT OLD.tax_rate_revenue_ppm\n OR NEW.tax_rate_deposit_ppm IS NOT OLD.tax_rate_deposit_ppm OR NEW.locale IS NOT OLD.locale\n OR NEW.place_by IS NOT OLD.place_by OR NEW.custom IS NOT OLD.custom\n OR NEW.hold_count IS NOT OLD.hold_count\n OR ((NEW.revenue_total IS NOT OLD.revenue_total OR NEW.deposit_total IS NOT OLD.deposit_total)\n AND (OLD.revenue_total IS NOT NULL OR OLD.deposit_total IS NOT NULL\n OR EXISTS (SELECT 1 FROM contribution_check WHERE order_id = OLD.id)))\n OR ((NEW.number IS NOT OLD.number OR NEW.placed_at IS NOT OLD.placed_at)\n AND (OLD.number IS NOT NULL OR OLD.placed_at IS NOT NULL OR NEW.status IS NOT 'open'\n OR NEW.number IS NULL OR NEW.placed_at IS NULL))\n OR (NEW.expired_at IS NOT OLD.expired_at AND (OLD.expired_at IS NOT NULL OR NEW.status IS NOT 'expired'))\n -- reopenable: only 1 → 0, only while expired.\n OR (NEW.reopenable IS NOT OLD.reopenable AND NOT (OLD.reopenable = 1 AND NEW.reopenable = 0 AND OLD.status = 'expired'))\n -- release_cost: only before the seal, only to its rebuild value.\n OR (NEW.release_cost IS NOT OLD.release_cost AND (EXISTS (SELECT 1 FROM contribution_check WHERE order_id = OLD.id)\n OR NEW.release_cost IS NOT ((SELECT count(*) FROM inventory_reservation ir WHERE ir.order_id = OLD.id) + (SELECT coalesce(sum(ch.last_bucket - ch.first_bucket + 1), 0) FROM capacity_hold ch WHERE ch.order_id = OLD.id))))\n -- current_alloc_cost: only to its rebuild value.\n OR (NEW.current_alloc_cost IS NOT OLD.current_alloc_cost AND NEW.current_alloc_cost IS NOT ((SELECT count(*) FROM inventory_reservation ir WHERE ir.order_id = OLD.id AND ir.released_at IS NULL) + (SELECT coalesce(sum(ch.last_bucket - ch.first_bucket + 1), 0) FROM capacity_hold ch WHERE ch.order_id = OLD.id AND ch.released_at IS NULL)));\n SELECT RAISE(ABORT, 'KEDGE_ORDER_UNSEALED') WHERE NEW.status IS NOT OLD.status\n AND NOT EXISTS (SELECT 1 FROM contribution_check WHERE order_id = OLD.id);\nEND;\n--> statement-breakpoint\n-- Marking an order not reopenable ends its claim on its released holds' pools.\nCREATE TRIGGER order_not_reopenable AFTER UPDATE OF reopenable ON \"order\"\n WHEN OLD.reopenable = 1 AND NEW.reopenable = 0 BEGIN\n DELETE FROM reopenable_hold_pool WHERE order_id = NEW.id;\nEND;\n--> statement-breakpoint\n\n-- ---- Order rows P0.4 adds: written in the commit, closed by the seal, write-once (p0.4 §2.2) ----\nCREATE TRIGGER order_line_component_insert BEFORE INSERT ON order_line_component BEGIN\n SELECT RAISE(ABORT, 'KEDGE_ORDER_SEALED') WHERE EXISTS (\n SELECT 1 FROM contribution_check c JOIN order_line l ON l.order_id = c.order_id WHERE l.id = NEW.order_line_id);\n SELECT RAISE(ABORT, 'KEDGE_COMPONENT_INITIAL') WHERE NEW.current_version != 1 OR NEW.source != 'catalogue';\nEND;\n--> statement-breakpoint\nCREATE TRIGGER order_line_component_version_insert BEFORE INSERT ON order_line_component_version BEGIN\n SELECT RAISE(ABORT, 'KEDGE_ORDER_SEALED') WHERE EXISTS (\n SELECT 1 FROM contribution_check c JOIN order_line l ON l.order_id = c.order_id\n JOIN order_line_component oc ON oc.order_line_id = l.id WHERE oc.id = NEW.component_id);\nEND;\n--> statement-breakpoint\nCREATE TRIGGER order_line_component_requirement_insert BEFORE INSERT ON order_line_component_requirement BEGIN\n SELECT RAISE(ABORT, 'KEDGE_ORDER_SEALED') WHERE EXISTS (\n SELECT 1 FROM contribution_check c JOIN order_line l ON l.order_id = c.order_id\n JOIN order_line_component oc ON oc.order_line_id = l.id WHERE oc.id = NEW.component_id);\nEND;\n--> statement-breakpoint\nCREATE TRIGGER order_place_by_source_insert BEFORE INSERT ON order_place_by_source BEGIN\n SELECT RAISE(ABORT, 'KEDGE_ORDER_SEALED') WHERE EXISTS (SELECT 1 FROM contribution_check WHERE order_id = NEW.order_id);\n SELECT RAISE(ABORT, 'KEDGE_TZ_VERSION') WHERE NEW.tz_rules_version IS NOT (SELECT tz_rules_version FROM store_time WHERE id = 1);\nEND;\n--> statement-breakpoint\nCREATE TRIGGER order_line_period_provenance_insert BEFORE INSERT ON order_line_period_provenance BEGIN\n SELECT RAISE(ABORT, 'KEDGE_ORDER_SEALED') WHERE EXISTS (\n SELECT 1 FROM contribution_check c JOIN order_line l ON l.order_id = c.order_id WHERE l.id = NEW.order_line_id);\n SELECT RAISE(ABORT, 'KEDGE_PERIOD_PROVENANCE') WHERE NEW.source != 'wall_time'\n OR NEW.tz_rules_version IS NOT (SELECT tz_rules_version FROM store_time WHERE id = 1);\nEND;\n--> statement-breakpoint\nCREATE TRIGGER order_line_component_write_once BEFORE UPDATE ON order_line_component BEGIN\n SELECT RAISE(ABORT, 'KEDGE_ORDER_IMMUTABLE');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER order_line_component_no_delete BEFORE DELETE ON order_line_component BEGIN\n SELECT RAISE(ABORT, 'KEDGE_ORDER_IMMUTABLE');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER order_line_component_version_write_once BEFORE UPDATE ON order_line_component_version BEGIN\n SELECT RAISE(ABORT, 'KEDGE_ORDER_IMMUTABLE');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER order_line_component_version_no_delete BEFORE DELETE ON order_line_component_version BEGIN\n SELECT RAISE(ABORT, 'KEDGE_ORDER_IMMUTABLE');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER order_line_component_requirement_write_once BEFORE UPDATE ON order_line_component_requirement BEGIN\n SELECT RAISE(ABORT, 'KEDGE_ORDER_IMMUTABLE');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER order_line_component_requirement_no_delete BEFORE DELETE ON order_line_component_requirement BEGIN\n SELECT RAISE(ABORT, 'KEDGE_ORDER_IMMUTABLE');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER order_place_by_source_write_once BEFORE UPDATE ON order_place_by_source BEGIN\n SELECT RAISE(ABORT, 'KEDGE_ORDER_IMMUTABLE');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER order_place_by_source_no_delete BEFORE DELETE ON order_place_by_source BEGIN\n SELECT RAISE(ABORT, 'KEDGE_ORDER_IMMUTABLE');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER order_line_period_provenance_write_once BEFORE UPDATE ON order_line_period_provenance BEGIN\n SELECT RAISE(ABORT, 'KEDGE_ORDER_IMMUTABLE');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER order_line_period_provenance_no_delete BEFORE DELETE ON order_line_period_provenance BEGIN\n SELECT RAISE(ABORT, 'KEDGE_ORDER_IMMUTABLE');\nEND;\n--> statement-breakpoint\n-- A package has at most COMPONENTS_MAX parts, so the claim's component seek is bounded (§4.1).\nCREATE TRIGGER variant_component_limit BEFORE INSERT ON variant_inventory_item\n WHEN (SELECT count(*) FROM variant_inventory_item WHERE variant_id = NEW.variant_id) >= 16 BEGIN\n SELECT RAISE(ABORT, 'KEDGE_COMPONENTS_LIMIT');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER variant_component_move_limit BEFORE UPDATE OF variant_id ON variant_inventory_item\n WHEN NEW.variant_id IS NOT OLD.variant_id\n AND (SELECT count(*) FROM variant_inventory_item WHERE variant_id = NEW.variant_id) >= 16 BEGIN\n SELECT RAISE(ABORT, 'KEDGE_COMPONENTS_LIMIT');\nEND;\n--> statement-breakpoint\n-- tracking changes only through a conversion (§2.1), which writes this release's marker with its cause.\nCREATE TRIGGER inventory_item_tracking_guard BEFORE UPDATE OF tracking ON inventory_item\n WHEN NEW.tracking IS NOT OLD.tracking\n AND (SELECT cause FROM batch_marker WHERE id = 1) IS NOT 'capacity.conversion' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_TRACKING_MODE:' || OLD.id);\nEND;\n--> statement-breakpoint\nDROP TRIGGER contribution_check_assert;\n--> statement-breakpoint\n-- The seal (D-041) proves allocation too (p0.4 §2.1): P0.3's checks, then coverage.\nCREATE TRIGGER contribution_check_assert BEFORE INSERT ON contribution_check BEGIN\n -- Names the plugin: a failed batch doesn't say which statement failed (research/15).\n SELECT RAISE(ABORT, 'KEDGE_CONTRIBUTION_UNBACKED:' || plugin) FROM (\n SELECT plugin FROM contribution_fact WHERE order_id = NEW.order_id AND backed_count != cardinality LIMIT 1);\n SELECT RAISE(ABORT, 'KEDGE_ADJUSTMENT_UNALLOCATED') WHERE EXISTS (\n SELECT 1 FROM order_adjustment a WHERE a.order_id = NEW.order_id\n AND a.amount != (SELECT COALESCE(sum(amount), 0) FROM order_line_adjustment WHERE adjustment_id = a.id));\n -- Discounts land on revenue lines whose kind is discountable only (K4).\n SELECT RAISE(ABORT, 'KEDGE_ALLOCATION_NOT_DISCOUNTABLE') WHERE EXISTS (\n SELECT 1 FROM order_adjustment a JOIN order_line_adjustment s ON s.adjustment_id = a.id\n JOIN order_line l ON l.id = s.order_line_id\n WHERE a.order_id = NEW.order_id AND (l.discountable = 0 OR l.charge_class != 'revenue'));\n -- No line is discounted below zero.\n SELECT RAISE(ABORT, 'KEDGE_LINE_NEGATIVE') WHERE EXISTS (\n SELECT 1 FROM order_line l WHERE l.order_id = NEW.order_id\n AND l.unit_price * l.quantity + (SELECT COALESCE(sum(amount), 0) FROM order_line_adjustment WHERE order_line_id = l.id) < 0);\n -- A per_parent line's quantity is proven, not trusted from the plan (§1.3).\n SELECT RAISE(ABORT, 'KEDGE_LINE_PER_PARENT') WHERE EXISTS (\n SELECT 1 FROM order_line c JOIN order_line p ON p.id = c.parent_line_id\n WHERE c.order_id = NEW.order_id AND c.quantity_mode = 'per_parent' AND c.quantity != p.quantity * c.quantity_per_parent);\n -- The insert-time checks hold at the moment of sealing too (§1.7).\n SELECT RAISE(ABORT, 'KEDGE_KIND_NOT_ALLOWED') WHERE EXISTS (\n SELECT 1 FROM order_line l WHERE l.order_id = NEW.order_id AND NOT EXISTS (\n SELECT 1 FROM variant v, json_each(v.allowed_kinds) k WHERE v.id = l.variant_id AND k.value = l.kind));\n SELECT RAISE(ABORT, 'KEDGE_TAX_CONTEXT') FROM \"order\" o WHERE o.id = NEW.order_id AND NOT EXISTS (\n SELECT 1 FROM tax_setting t WHERE t.id = 1 AND t.inclusive = o.tax_inclusive\n AND t.rate_revenue_ppm = o.tax_rate_revenue_ppm AND t.rate_deposit_ppm = o.tax_rate_deposit_ppm);\n -- Every tax owed has its row, and only those, with the formula's amount\n -- (§3.3). base = q·d + r, so tax = q·rate + round(r·rate / d): no product\n -- exceeds about 10^13. Rounded half up; bases are never negative here.\n SELECT RAISE(ABORT, 'KEDGE_TAX_LINES') FROM (\n SELECT x.*, t.id AS tid, t.rate_ppm AS trate, t.inclusive AS tincl, t.charge_class AS tclass, t.amount AS tamount\n FROM (\n SELECT l.id, l.taxable, l.charge_class, o.tax_inclusive AS incl,\n CASE l.charge_class WHEN 'revenue' THEN o.tax_rate_revenue_ppm ELSE o.tax_rate_deposit_ppm END AS rate,\n l.unit_price * l.quantity + (SELECT COALESCE(sum(amount), 0) FROM order_line_adjustment WHERE order_line_id = l.id) AS base\n FROM order_line l JOIN \"order\" o ON o.id = l.order_id WHERE l.order_id = NEW.order_id) x\n LEFT JOIN order_tax_line t ON t.order_line_id = x.id\n UNION ALL\n SELECT y.*, t.id, t.rate_ppm, t.inclusive, t.charge_class, t.amount\n FROM (\n SELECT f.id, f.taxable, f.charge_class, o.tax_inclusive,\n CASE f.charge_class WHEN 'revenue' THEN o.tax_rate_revenue_ppm ELSE o.tax_rate_deposit_ppm END,\n f.amount\n FROM order_fee f JOIN \"order\" o ON o.id = f.order_id WHERE f.order_id = NEW.order_id) y\n LEFT JOIN order_tax_line t ON t.order_fee_id = y.id\n ) z WHERE CASE WHEN z.taxable = 1 AND z.rate > 0 THEN\n z.tid IS NULL OR z.trate != z.rate OR z.tincl != z.incl OR z.tclass != z.charge_class\n OR z.tamount != (z.base / (1000000 + z.incl * z.rate)) * z.rate\n + ((z.base % (1000000 + z.incl * z.rate)) * z.rate * 2 + (1000000 + z.incl * z.rate))\n / (2 * (1000000 + z.incl * z.rate))\n ELSE z.tid IS NOT NULL END\n LIMIT 1;\n -- The stored breakdown (§3.2): amounts as priced, tax_total every tax line,\n -- the total adding only exclusive tax, and the total split by class (K4).\n SELECT RAISE(ABORT, 'KEDGE_ORDER_TOTALS') FROM \"order\" o WHERE o.id = NEW.order_id AND (\n o.fee_total != (SELECT COALESCE(sum(amount), 0) FROM order_fee WHERE order_id = o.id)\n OR o.discount_total != -(SELECT COALESCE(sum(amount), 0) FROM order_adjustment WHERE order_id = o.id)\n OR o.subtotal != (SELECT COALESCE(sum(unit_price * quantity), 0) FROM order_line WHERE order_id = o.id)\n OR o.tax_total != (SELECT COALESCE(sum(amount), 0) FROM order_tax_line WHERE order_id = o.id)\n OR o.total != o.subtotal - o.discount_total + o.shipping_total + o.fee_total\n + (SELECT COALESCE(sum(amount), 0) FROM order_tax_line WHERE order_id = o.id AND inclusive = 0)\n OR o.revenue_total IS NULL OR o.deposit_total IS NULL\n OR o.total != o.revenue_total + o.deposit_total\n OR o.deposit_total != (SELECT COALESCE(sum(unit_price * quantity), 0) FROM order_line WHERE order_id = o.id AND charge_class = 'deposit')\n + (SELECT COALESCE(sum(amount), 0) FROM order_fee WHERE order_id = o.id AND charge_class = 'deposit')\n + (SELECT COALESCE(sum(amount), 0) FROM order_tax_line WHERE order_id = o.id AND inclusive = 0 AND charge_class = 'deposit'));\n -- Consent completeness in the order's locale (§5.1): exactly one\n -- acceptance of each required policy's current revision, whoever wrote the\n -- order, the previous Worker included.\n SELECT RAISE(ABORT, 'KEDGE_POLICY_MISSING:' || cp.policy) FROM checkout_policy cp JOIN \"order\" o ON o.id = NEW.order_id\n WHERE (SELECT count(*) FROM policy_acceptance a JOIN policy_revision r ON r.id = a.revision_id\n WHERE a.order_id = o.id AND r.policy = cp.policy AND r.locale = o.locale\n AND r.version = (SELECT max(version) FROM policy_revision WHERE policy = cp.policy AND locale = o.locale)) != 1\n LIMIT 1;\n -- ---- P0.4: the seal proves allocation (p0.4 §2.1, dated-capacity.md §3.6a) ----\n -- Costs from their rebuild queries, once per commit (§2.2); the order's CHECKs bound them.\n UPDATE \"order\" SET release_cost = ((SELECT count(*) FROM inventory_reservation ir WHERE ir.order_id = NEW.order_id) + (SELECT coalesce(sum(ch.last_bucket - ch.first_bucket + 1), 0) FROM capacity_hold ch WHERE ch.order_id = NEW.order_id)), current_alloc_cost = ((SELECT count(*) FROM inventory_reservation ir WHERE ir.order_id = NEW.order_id AND ir.released_at IS NULL) + (SELECT coalesce(sum(ch.last_bucket - ch.first_bucket + 1), 0) FROM capacity_hold ch WHERE ch.order_id = NEW.order_id AND ch.released_at IS NULL))\n WHERE id = NEW.order_id;\n SELECT RAISE(ABORT, 'KEDGE_HOLD_COUNT') FROM \"order\" o WHERE o.id = NEW.order_id\n AND o.hold_count != (SELECT count(*) FROM capacity_hold WHERE order_id = o.id);\n -- Every line has exactly its catalogue components, as the database has them now.\n SELECT RAISE(ABORT, 'KEDGE_COVERAGE:' || l.id || ':' || vi.inventory_item_id)\n FROM order_line l JOIN variant_inventory_item vi ON vi.variant_id = l.variant_id\n LEFT JOIN order_line_component c ON c.order_line_id = l.id AND c.inventory_item_id = vi.inventory_item_id\n LEFT JOIN order_line_component_version cv ON cv.component_id = c.id AND cv.version = c.current_version\n WHERE l.order_id = NEW.order_id AND (c.id IS NULL\n OR c.tracking IS NOT (SELECT tracking FROM inventory_item WHERE id = vi.inventory_item_id)\n OR cv.line_qty IS NOT l.quantity OR cv.qty_per_unit IS NOT vi.quantity OR cv.ordered_qty IS NOT l.quantity * vi.quantity\n OR cv.ordered_qty IS NOT (SELECT sum(qty) FROM order_line_component_requirement r WHERE r.component_id = c.id AND r.version = c.current_version))\n LIMIT 1;\n SELECT RAISE(ABORT, 'KEDGE_COVERAGE:' || c.order_line_id || ':' || c.inventory_item_id)\n FROM order_line_component c JOIN order_line l ON l.id = c.order_line_id\n WHERE l.order_id = NEW.order_id\n AND NOT EXISTS (SELECT 1 FROM variant_inventory_item vi WHERE vi.variant_id = l.variant_id AND vi.inventory_item_id = c.inventory_item_id)\n LIMIT 1;\n -- Whole line units per location (rental-operations.md §7): at each location a\n -- line uses, every component is there, each at the same whole k times its ratio.\n SELECT RAISE(ABORT, 'KEDGE_COVERAGE:' || a.line || ':kit')\n FROM (SELECT l.id AS line, r.location_id AS loc, c.id AS comp, r.qty AS qty, cv.qty_per_unit AS per\n FROM order_line l JOIN order_line_component c ON c.order_line_id = l.id\n JOIN order_line_component_version cv ON cv.component_id = c.id AND cv.version = c.current_version\n JOIN order_line_component_requirement r ON r.component_id = c.id AND r.version = c.current_version\n WHERE l.order_id = NEW.order_id AND c.tracking != 'none') a\n WHERE a.qty % a.per != 0\n OR EXISTS (SELECT 1 FROM order_line_component c2 WHERE c2.order_line_id = a.line AND c2.tracking != 'none'\n AND COALESCE((SELECT sum(r2.qty) FROM order_line_component_requirement r2 JOIN order_line_component_version v2\n ON v2.component_id = r2.component_id AND v2.version = r2.version\n WHERE r2.component_id = c2.id AND r2.version = c2.current_version AND r2.location_id = a.loc), 0)\n != (a.qty / a.per) * (SELECT v3.qty_per_unit FROM order_line_component_version v3 WHERE v3.component_id = c2.id AND v3.version = c2.current_version))\n LIMIT 1;\n -- Counted demand is covered by active reservations at each location.\n SELECT RAISE(ABORT, 'KEDGE_COVERAGE:' || l.id || ':' || c.inventory_item_id)\n FROM order_line l JOIN order_line_component c ON c.order_line_id = l.id\n JOIN order_line_component_requirement r ON r.component_id = c.id AND r.version = c.current_version\n WHERE l.order_id = NEW.order_id AND c.tracking = 'count'\n -- Through the order's active-reservation index: the unary + keeps the planner off the\n -- item-and-location index, whose other orders' rows would grow the read with history.\n AND r.qty > (SELECT COALESCE(sum(x.quantity), 0) FROM inventory_reservation x\n WHERE x.order_id = NEW.order_id AND x.released_at IS NULL AND +x.order_line_id = l.id\n AND +x.inventory_item_id = c.inventory_item_id AND +x.location_id = r.location_id)\n LIMIT 1;\n -- Dated requirements are well-formed, on the component's own pool at that location, and contain the line's period.\n SELECT RAISE(ABORT, 'KEDGE_COVERAGE:' || l.id || ':' || c.inventory_item_id)\n FROM order_line l JOIN order_line_component c ON c.order_line_id = l.id\n JOIN order_line_component_requirement r ON r.component_id = c.id AND r.version = c.current_version\n LEFT JOIN capacity_pool p ON p.id = r.pool_id\n WHERE l.order_id = NEW.order_id AND c.tracking = 'dated' AND (\n p.id IS NULL OR p.kind != 'item' OR p.inventory_item_id != c.inventory_item_id OR p.location_id != r.location_id\n OR r.required_first_bucket IS NULL OR r.required_last_bucket IS NULL OR r.required_first_bucket > r.required_last_bucket\n OR l.period_start IS NULL OR l.period_end IS NULL\n -- The period's labels as rangeOf computes them, transitions inside it included (a fold moves the clock back).\n OR r.required_first_bucket > min(((CAST((l.period_start) AS INTEGER) + 1000 * (SELECT tt.offset_seconds FROM tz_transition tt WHERE tt.version = (SELECT st.tz_rules_version FROM store_time st WHERE st.id = 1) AND tt.at_utc <= (l.period_start) ORDER BY tt.at_utc DESC LIMIT 1)) / (60000 * (SELECT pp.bucket_minutes FROM capacity_pool pp WHERE pp.id = p.id))), coalesce((SELECT min((tt.at_utc + 1000 * tt.offset_seconds) / (60000 * (SELECT pp.bucket_minutes FROM capacity_pool pp WHERE pp.id = p.id))) FROM tz_transition tt\n WHERE tt.version = (SELECT st.tz_rules_version FROM store_time st WHERE st.id = 1) AND tt.at_utc > (l.period_start) AND tt.at_utc < (l.period_end)), ((CAST((l.period_start) AS INTEGER) + 1000 * (SELECT tt.offset_seconds FROM tz_transition tt WHERE tt.version = (SELECT st.tz_rules_version FROM store_time st WHERE st.id = 1) AND tt.at_utc <= (l.period_start) ORDER BY tt.at_utc DESC LIMIT 1)) / (60000 * (SELECT pp.bucket_minutes FROM capacity_pool pp WHERE pp.id = p.id)))))\n OR r.required_last_bucket < max(((CAST(((l.period_end) - 1) AS INTEGER) + 1000 * (SELECT tt.offset_seconds FROM tz_transition tt WHERE tt.version = (SELECT st.tz_rules_version FROM store_time st WHERE st.id = 1) AND tt.at_utc <= ((l.period_end) - 1) ORDER BY tt.at_utc DESC LIMIT 1)) / (60000 * (SELECT pp.bucket_minutes FROM capacity_pool pp WHERE pp.id = p.id))), coalesce((SELECT max((tt.at_utc - 1 + 1000 * (SELECT pt.offset_seconds FROM tz_transition pt WHERE pt.version = tt.version AND pt.at_utc < tt.at_utc\n ORDER BY pt.at_utc DESC LIMIT 1)) / (60000 * (SELECT pp.bucket_minutes FROM capacity_pool pp WHERE pp.id = p.id))) FROM tz_transition tt\n WHERE tt.version = (SELECT st.tz_rules_version FROM store_time st WHERE st.id = 1) AND tt.at_utc > (l.period_start) AND tt.at_utc < (l.period_end)), ((CAST(((l.period_end) - 1) AS INTEGER) + 1000 * (SELECT tt.offset_seconds FROM tz_transition tt WHERE tt.version = (SELECT st.tz_rules_version FROM store_time st WHERE st.id = 1) AND tt.at_utc <= ((l.period_end) - 1) ORDER BY tt.at_utc DESC LIMIT 1)) / (60000 * (SELECT pp.bucket_minutes FROM capacity_pool pp WHERE pp.id = p.id))))))\n LIMIT 1;\n -- Dated demand per pool and bucket is covered by this line's own active order holds (a grouped comparison).\n -- The buckets come from each requirement's own range, so the work is the order's held buckets.\n SELECT RAISE(ABORT, 'KEDGE_COVERAGE:' || d.line || ':' || d.item) FROM (\n WITH RECURSIVE d(line, item, pool, b, last) AS (\n SELECT l.id, c.inventory_item_id, r.pool_id, r.required_first_bucket, r.required_last_bucket\n FROM order_line l JOIN order_line_component c ON c.order_line_id = l.id\n JOIN order_line_component_requirement r ON r.component_id = c.id AND r.version = c.current_version\n WHERE l.order_id = NEW.order_id AND c.tracking = 'dated'\n UNION\n SELECT line, item, pool, b + 1, last FROM d WHERE b < last)\n SELECT line, item, pool, b FROM d) d\n WHERE (SELECT sum(r2.qty) FROM order_line_component c2 JOIN order_line_component_requirement r2\n ON r2.component_id = c2.id AND r2.version = c2.current_version\n WHERE c2.order_line_id = d.line AND r2.pool_id = d.pool AND r2.required_first_bucket <= d.b AND r2.required_last_bucket >= d.b)\n -- The order's own holds through its index; the unary + keeps the planner off the pool's active-hold index.\n > (SELECT COALESCE(sum(h.qty), 0) FROM capacity_hold h WHERE h.order_id = NEW.order_id AND h.released_at IS NULL\n AND +h.order_line_id = d.line AND h.kind = 'order' AND +h.pool_id = d.pool\n AND +h.first_bucket <= d.b AND +h.last_bucket >= d.b)\n LIMIT 1;\n -- No allocation of a line sits on another item's level or pool.\n SELECT RAISE(ABORT, 'KEDGE_COVERAGE:' || x.order_line_id || ':misassigned') FROM inventory_reservation x\n WHERE x.order_id = NEW.order_id AND x.released_at IS NULL AND NOT EXISTS (\n SELECT 1 FROM order_line_component c WHERE c.order_line_id = x.order_line_id AND c.inventory_item_id = x.inventory_item_id AND c.tracking = 'count')\n LIMIT 1;\n SELECT RAISE(ABORT, 'KEDGE_COVERAGE:' || h.order_line_id || ':misassigned') FROM capacity_hold h JOIN capacity_pool p ON p.id = h.pool_id\n WHERE h.order_id = NEW.order_id AND h.order_line_id IS NOT NULL AND h.released_at IS NULL AND (p.kind != 'item' OR NOT EXISTS (\n SELECT 1 FROM order_line_component c WHERE c.order_line_id = h.order_line_id AND c.inventory_item_id = p.inventory_item_id AND c.tracking = 'dated'))\n LIMIT 1;\n -- A line-less order hold is a plugin's, on its own pool: item capacity is held only for lines.\n SELECT RAISE(ABORT, 'KEDGE_COVERAGE:' || h.id || ':misassigned') FROM capacity_hold h JOIN capacity_pool p ON p.id = h.pool_id\n WHERE h.order_id = NEW.order_id AND h.order_line_id IS NULL AND h.kind = 'order' AND p.kind = 'item'\n LIMIT 1;\n -- Every hold contributes its cutoff and its bucket_end bound; every period line without holds whose kind\n -- has a cutoff contributes its own; nothing names another order's hold or line; place_by is their minimum.\n SELECT RAISE(ABORT, 'KEDGE_PLACE_BY') FROM capacity_hold h WHERE h.order_id = NEW.order_id AND h.kind = 'order' AND (\n (SELECT count(*) FROM order_place_by_source s WHERE s.order_id = NEW.order_id AND s.hold_id = h.id AND s.anchor != 'bucket_end') != 1\n OR (SELECT count(*) FROM order_place_by_source s WHERE s.order_id = NEW.order_id AND s.hold_id = h.id AND s.anchor = 'bucket_end') != 1)\n LIMIT 1;\n SELECT RAISE(ABORT, 'KEDGE_PLACE_BY') FROM order_place_by_source s WHERE s.order_id = NEW.order_id AND (\n (s.hold_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM capacity_hold h WHERE h.id = s.hold_id AND h.order_id = NEW.order_id))\n OR (s.line_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM order_line l WHERE l.id = s.line_id AND l.order_id = NEW.order_id)))\n LIMIT 1;\n SELECT RAISE(ABORT, 'KEDGE_PLACE_BY') FROM order_line l WHERE l.order_id = NEW.order_id\n AND l.period_start IS NOT NULL AND l.cutoff_minutes IS NOT NULL\n AND NOT EXISTS (SELECT 1 FROM capacity_hold h WHERE h.order_id = NEW.order_id AND h.order_line_id = l.id)\n AND (SELECT count(*) FROM order_place_by_source s WHERE s.order_id = NEW.order_id AND s.line_id = l.id\n AND s.hold_id IS NULL AND s.anchor = 'line_period') != 1\n LIMIT 1;\n -- A hold's rows resolve from the hold itself (§2.2). A boundary resolves to an instant at which\n -- the clock crosses into its label or past it: the label's first occurrence across a fold, the\n -- transition across a gap (labelStart in @kedge/core). So bucket_start is such a crossing into\n -- the first bucket, no later than the hold's start; bucket_end such a crossing out of it;\n -- hold_start the hold's own start plus its offset.\n SELECT RAISE(ABORT, 'KEDGE_PLACE_BY') FROM order_place_by_source s JOIN capacity_hold h ON h.id = s.hold_id\n WHERE s.order_id = NEW.order_id AND (\n (s.anchor = 'bucket_start' AND (s.at > h.starts_at OR ((CAST((s.at) AS INTEGER) + 1000 * (SELECT tt.offset_seconds FROM tz_transition tt WHERE tt.version = (SELECT st.tz_rules_version FROM store_time st WHERE st.id = 1) AND tt.at_utc <= (s.at) ORDER BY tt.at_utc DESC LIMIT 1)) / (60000 * (SELECT pp.bucket_minutes FROM capacity_pool pp WHERE pp.id = h.pool_id))) < h.first_bucket\n OR ((CAST((s.at - 1) AS INTEGER) + 1000 * (SELECT tt.offset_seconds FROM tz_transition tt WHERE tt.version = (SELECT st.tz_rules_version FROM store_time st WHERE st.id = 1) AND tt.at_utc <= (s.at - 1) ORDER BY tt.at_utc DESC LIMIT 1)) / (60000 * (SELECT pp.bucket_minutes FROM capacity_pool pp WHERE pp.id = h.pool_id))) >= h.first_bucket\n OR EXISTS (SELECT 1 FROM tz_transition tt WHERE tt.version = (SELECT st.tz_rules_version FROM store_time st WHERE st.id = 1)\n AND tt.at_utc < (s.at) AND tt.at_utc > (s.at) - 172800000\n AND (tt.at_utc - 1 + 1000 * (SELECT pt.offset_seconds FROM tz_transition pt WHERE pt.version = tt.version AND pt.at_utc < tt.at_utc\n ORDER BY pt.at_utc DESC LIMIT 1)) / (60000 * (SELECT pp.bucket_minutes FROM capacity_pool pp WHERE pp.id = h.pool_id)) >= (h.first_bucket))))\n OR (s.anchor = 'hold_start' AND (s.offset_minutes IS NULL OR s.at != h.starts_at + s.offset_minutes * 60000))\n OR (s.anchor = 'bucket_end' AND (((CAST((s.at) AS INTEGER) + 1000 * (SELECT tt.offset_seconds FROM tz_transition tt WHERE tt.version = (SELECT st.tz_rules_version FROM store_time st WHERE st.id = 1) AND tt.at_utc <= (s.at) ORDER BY tt.at_utc DESC LIMIT 1)) / (60000 * (SELECT pp.bucket_minutes FROM capacity_pool pp WHERE pp.id = h.pool_id))) <= h.first_bucket\n OR ((CAST((s.at - 1) AS INTEGER) + 1000 * (SELECT tt.offset_seconds FROM tz_transition tt WHERE tt.version = (SELECT st.tz_rules_version FROM store_time st WHERE st.id = 1) AND tt.at_utc <= (s.at - 1) ORDER BY tt.at_utc DESC LIMIT 1)) / (60000 * (SELECT pp.bucket_minutes FROM capacity_pool pp WHERE pp.id = h.pool_id))) > h.first_bucket\n OR EXISTS (SELECT 1 FROM tz_transition tt WHERE tt.version = (SELECT st.tz_rules_version FROM store_time st WHERE st.id = 1)\n AND tt.at_utc < (s.at) AND tt.at_utc > (s.at) - 172800000\n AND (tt.at_utc - 1 + 1000 * (SELECT pt.offset_seconds FROM tz_transition pt WHERE pt.version = tt.version AND pt.at_utc < tt.at_utc\n ORDER BY pt.at_utc DESC LIMIT 1)) / (60000 * (SELECT pp.bucket_minutes FROM capacity_pool pp WHERE pp.id = h.pool_id)) >= (h.first_bucket + 1)))))\n LIMIT 1;\n -- A line_period row is its line's start less the kind's stored cutoff: its offset is the\n -- cutoff's, and its instant follows from both.\n SELECT RAISE(ABORT, 'KEDGE_PLACE_BY') FROM order_place_by_source s JOIN order_line l ON l.id = s.line_id\n WHERE s.order_id = NEW.order_id AND s.anchor = 'line_period'\n AND (l.period_start IS NULL OR l.cutoff_minutes IS NULL OR s.offset_minutes IS NOT -l.cutoff_minutes\n OR s.at != l.period_start + s.offset_minutes * 60000)\n LIMIT 1;\n -- A line's hold takes its line's kind cutoff (line_period, naming that line) when the kind has one,\n -- and the default bucket_start when it doesn't (§2.2).\n SELECT RAISE(ABORT, 'KEDGE_PLACE_BY') FROM capacity_hold h JOIN order_line l ON l.id = h.order_line_id\n WHERE h.order_id = NEW.order_id AND h.kind = 'order' AND (SELECT count(*) FROM order_place_by_source s\n WHERE s.order_id = NEW.order_id AND s.hold_id = h.id\n AND CASE WHEN l.cutoff_minutes IS NULL THEN s.anchor = 'bucket_start'\n ELSE s.anchor = 'line_period' AND s.line_id IS l.id END) != 1\n LIMIT 1;\n SELECT RAISE(ABORT, 'KEDGE_PLACE_BY') FROM \"order\" o WHERE o.id = NEW.order_id\n AND o.place_by IS NOT (SELECT min(at) FROM order_place_by_source WHERE order_id = o.id);\n -- Period provenance: one row per line with a period.\n SELECT RAISE(ABORT, 'KEDGE_PERIOD_PROVENANCE') FROM order_line l WHERE l.order_id = NEW.order_id AND (\n (l.period_start IS NOT NULL AND NOT EXISTS (SELECT 1 FROM order_line_period_provenance pp WHERE pp.order_line_id = l.id\n AND pp.source = 'wall_time' AND pp.tz_rules_version = (SELECT tz_rules_version FROM store_time WHERE id = 1)))\n OR (l.period_start IS NULL AND EXISTS (SELECT 1 FROM order_line_period_provenance pp WHERE pp.order_line_id = l.id)))\n LIMIT 1;\nEND;\n--> statement-breakpoint\nDELETE FROM batch_marker;\n","migrations/0024_inventory_level_rebuild.sql":"-- The inventory_level rebuild (docs/proposals/dated-capacity.md §5,\n-- p0.4-dated-capacity.md §3.2): physical loss always commits, so the\n-- inv_no_oversell CHECK becomes inv_reserved_nonneg plus a backstop on\n-- increases of `reserved`, and a lower on_hand or higher safety stock writes\n-- `level.availability_changed` for the shortage consumer. SQLite can't drop a\n-- CHECK, so the table is rebuilt in this one transaction (the migration is\n-- one batch): deferred foreign keys, dependents dropped, rows held aside,\n-- drop, rename, rows back, dependents recreated, and an orphan guard that\n-- fails the batch.\n--\n-- The reservation checks are recreated with P0.4's mode check as their first\n-- statement, so its refusal wins over a stock refusal deterministically.\n--\n-- This is core's version, with core's dependents only. A store's composed\n-- migration (`kedge generate`) adds each installed plugin's dependents of\n-- inventory_level around the same steps and refuses a non-NO ACTION foreign key.\nPRAGMA defer_foreign_keys = on;\n--> statement-breakpoint\nINSERT INTO batch_marker (id, release, cause) VALUES (1, 'p0.4', 'migration:0024');\n--> statement-breakpoint\nDROP TRIGGER reservation_insert_check;\n--> statement-breakpoint\nDROP TRIGGER reservation_insert_count;\n--> statement-breakpoint\nDROP TRIGGER reservation_reactivate_check;\n--> statement-breakpoint\nDROP TRIGGER reservation_reactivate;\n--> statement-breakpoint\nDROP TRIGGER reservation_release;\n--> statement-breakpoint\nDROP TRIGGER inventory_level_bounds_insert;\n--> statement-breakpoint\nDROP TRIGGER inventory_level_bounds_update;\n--> statement-breakpoint\nDROP TRIGGER fence_inventory_level_insert;\n--> statement-breakpoint\nDROP TRIGGER fence_inventory_level_update;\n--> statement-breakpoint\nDROP TRIGGER fence_inventory_level_delete;\n--> statement-breakpoint\nCREATE TABLE `inventory_level_new` (\n\t`inventory_item_id` text NOT NULL,\n\t`location_id` text NOT NULL,\n\t`on_hand` integer NOT NULL,\n\t`reserved` integer DEFAULT 0 NOT NULL,\n\t`safety_stock` integer DEFAULT 0 NOT NULL,\n\t`allow_backorder` integer DEFAULT 0 NOT NULL,\n\tPRIMARY KEY(`inventory_item_id`, `location_id`),\n\tFOREIGN KEY (`inventory_item_id`) REFERENCES `inventory_item`(`id`) ON UPDATE no action ON DELETE no action,\n\tFOREIGN KEY (`location_id`) REFERENCES `stock_location`(`id`) ON UPDATE no action ON DELETE no action,\n\tCONSTRAINT \"inventory_level_on_hand_int\" CHECK(typeof(\"on_hand\") = 'integer' AND \"on_hand\" >= 0),\n\tCONSTRAINT \"inventory_level_reserved_int\" CHECK(typeof(\"reserved\") = 'integer'),\n\tCONSTRAINT \"inventory_level_safety_stock_int\" CHECK(typeof(\"safety_stock\") = 'integer' AND \"safety_stock\" >= 0),\n\tCONSTRAINT \"inventory_level_allow_backorder_bool\" CHECK(\"allow_backorder\" IN (0, 1)),\n\tCONSTRAINT \"inv_reserved_nonneg\" CHECK(\"reserved\" >= 0)\n);\n--> statement-breakpoint\n-- The rows wait aside, and go into the table only after it has its final\n-- name. Dropping the old table counts each reservation as a deferred\n-- violation of its key, and SQLite clears that count only as parent rows are\n-- inserted under the name the key names: copying into the new table before\n-- the rename would leave the count up, and the batch would fail at commit\n-- (reproduced; research/18). A row that really went missing still fails it.\nCREATE TABLE _kedge_inventory_level_hold AS\n SELECT inventory_item_id, location_id, on_hand, reserved, safety_stock, allow_backorder FROM inventory_level;\n--> statement-breakpoint\nDROP TABLE inventory_level;\n--> statement-breakpoint\nALTER TABLE inventory_level_new RENAME TO inventory_level;\n--> statement-breakpoint\nINSERT INTO inventory_level (inventory_item_id, location_id, on_hand, reserved, safety_stock, allow_backorder)\n SELECT inventory_item_id, location_id, on_hand, reserved, safety_stock, allow_backorder FROM _kedge_inventory_level_hold;\n--> statement-breakpoint\nDROP TABLE _kedge_inventory_level_hold;\n--> statement-breakpoint\nCREATE TRIGGER reservation_insert_check BEFORE INSERT ON inventory_reservation BEGIN\n -- The mode first (dated-capacity.md §2.1): a late payment after a conversion is\n -- TRACKING_MODE_CHANGED, a registered refusal, whatever the stock now says.\n SELECT RAISE(ABORT, 'KEDGE_TRACKING_MODE:' || NEW.inventory_item_id)\n WHERE (SELECT tracking FROM inventory_item WHERE id = NEW.inventory_item_id) IS NOT 'count';\n SELECT RAISE(ABORT, 'KEDGE_RESERVATION_INSERTED_RELEASED') WHERE NEW.released_at IS NOT NULL;\n SELECT RAISE(ABORT, 'KEDGE_RESERVATION_ORDER_STATE')\n WHERE (SELECT status FROM \"order\" WHERE id = NEW.order_id) IS NOT 'pending_payment';\n SELECT RAISE(ABORT, 'KEDGE_OOS:' || NEW.inventory_item_id || ':' || NEW.location_id) FROM inventory_level\n WHERE inventory_item_id = NEW.inventory_item_id AND location_id = NEW.location_id\n AND allow_backorder = 0 AND reserved + NEW.quantity > on_hand - safety_stock;\nEND;\n--> statement-breakpoint\nCREATE TRIGGER reservation_insert_count AFTER INSERT ON inventory_reservation BEGIN\n UPDATE inventory_level SET reserved = reserved + NEW.quantity\n WHERE inventory_item_id = NEW.inventory_item_id AND location_id = NEW.location_id;\nEND;\n--> statement-breakpoint\nCREATE TRIGGER reservation_reactivate_check BEFORE UPDATE OF released_at ON inventory_reservation\n WHEN OLD.released_at IS NOT NULL AND NEW.released_at IS NULL BEGIN\n -- The mode first (dated-capacity.md §2.1): a late payment after a conversion is\n -- TRACKING_MODE_CHANGED, a registered refusal, whatever the stock now says.\n SELECT RAISE(ABORT, 'KEDGE_TRACKING_MODE:' || NEW.inventory_item_id)\n WHERE (SELECT tracking FROM inventory_item WHERE id = NEW.inventory_item_id) IS NOT 'count';\n -- Only the reopen transition re-activates (the order is already 'open' inside its trigger).\n SELECT RAISE(ABORT, 'KEDGE_RESERVATION_ORDER_STATE')\n WHERE (SELECT status FROM \"order\" WHERE id = NEW.order_id) IS NOT 'open';\n SELECT RAISE(ABORT, 'KEDGE_OOS:' || NEW.inventory_item_id || ':' || NEW.location_id) FROM inventory_level\n WHERE inventory_item_id = NEW.inventory_item_id AND location_id = NEW.location_id\n AND allow_backorder = 0 AND reserved + NEW.quantity > on_hand - safety_stock;\nEND;\n--> statement-breakpoint\nCREATE TRIGGER reservation_reactivate AFTER UPDATE OF released_at ON inventory_reservation\n WHEN OLD.released_at IS NOT NULL AND NEW.released_at IS NULL BEGIN\n UPDATE inventory_level SET reserved = reserved + NEW.quantity\n WHERE inventory_item_id = NEW.inventory_item_id AND location_id = NEW.location_id;\nEND;\n--> statement-breakpoint\nCREATE TRIGGER reservation_release AFTER UPDATE OF released_at ON inventory_reservation\n WHEN OLD.released_at IS NULL AND NEW.released_at IS NOT NULL BEGIN\n UPDATE inventory_level SET reserved = reserved - NEW.quantity\n WHERE inventory_item_id = NEW.inventory_item_id AND location_id = NEW.location_id;\nEND;\n--> statement-breakpoint\nCREATE TRIGGER inventory_level_bounds_insert BEFORE INSERT ON inventory_level\n WHEN NEW.on_hand > 1000000000000 OR NEW.reserved > 1000000000000 OR NEW.safety_stock > 1000000000000 BEGIN\n SELECT RAISE(ABORT, 'KEDGE_STOCK_OUT_OF_RANGE');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER inventory_level_bounds_update BEFORE UPDATE OF on_hand, reserved, safety_stock ON inventory_level\n WHEN NEW.on_hand > 1000000000000 OR NEW.reserved > 1000000000000 OR NEW.safety_stock > 1000000000000 BEGIN\n SELECT RAISE(ABORT, 'KEDGE_STOCK_OUT_OF_RANGE');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_inventory_level_insert BEFORE INSERT ON \"inventory_level\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.4' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_inventory_level_update BEFORE UPDATE ON \"inventory_level\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.4' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_inventory_level_delete BEFORE DELETE ON \"inventory_level\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.4' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\n-- New demand never overbooks (§5): an increase of `reserved` past what is\n-- available (on_hand less safety stock), without backorder, is refused: the\n-- reservation trigger's own predicate, for any other writer. Decreases, and\n-- a higher safety stock, always pass.\nCREATE TRIGGER inventory_level_reserved_backstop BEFORE UPDATE OF reserved ON inventory_level\n WHEN NEW.reserved > OLD.reserved AND NEW.allow_backorder = 0 AND NEW.reserved > NEW.on_hand - NEW.safety_stock BEGIN\n SELECT RAISE(ABORT, 'KEDGE_STOCK_BACKSTOP:' || NEW.inventory_item_id || ':' || NEW.location_id);\nEND;\n--> statement-breakpoint\n-- A loss is recorded, and the shortage consumer is told (§5).\nCREATE TRIGGER inventory_level_lowered AFTER UPDATE OF on_hand, safety_stock ON inventory_level\n WHEN NEW.on_hand < OLD.on_hand OR NEW.safety_stock > OLD.safety_stock BEGIN\n INSERT INTO outbox_event (id, aggregate_type, aggregate_id, type, payload, created_at)\n VALUES ('evt_' || lower(hex(randomblob(12))) || '.level', 'inventory_level', NEW.inventory_item_id || ':' || NEW.location_id,\n 'level.availability_changed',\n json_object('inventory_item_id', NEW.inventory_item_id, 'location_id', NEW.location_id,\n 'on_hand', NEW.on_hand, 'safety_stock', NEW.safety_stock),\n CAST(unixepoch('subsec') * 1000 AS INTEGER));\nEND;\n--> statement-breakpoint\n-- Every inbound foreign key still resolves, as a constraint failure inside\n-- the batch: a pragma returning violation rows is a successful statement.\nCREATE TABLE `_kedge_migration_guard` (`violations` integer NOT NULL CHECK (`violations` = 0));\n--> statement-breakpoint\nINSERT INTO _kedge_migration_guard (violations)\n SELECT count(*) FROM inventory_reservation r\n WHERE NOT EXISTS (SELECT 1 FROM inventory_level l WHERE l.inventory_item_id = r.inventory_item_id AND l.location_id = r.location_id);\n--> statement-breakpoint\nDROP TABLE `_kedge_migration_guard`;\n--> statement-breakpoint\nDELETE FROM batch_marker;\n","migrations/0025_auth_access.sql":"-- P0.5 auth and access (docs/proposals/p0.5-auth-and-access.md, D-053): the\n-- cutover behind the fence (§8a). Rendered from packages/db/sql by\n-- src/access-sql.ts and src/capacity-sql.ts; render.test.ts compares.\n--\n-- (1) The old fence goes first, so nothing below is refused, and the new one\n-- (release p0.5, every core table) is installed before any backfill; from\n-- then on a P0.4 Worker's batch (old release, no actor) is refused whole.\nDROP TRIGGER fence_order_insert;\n--> statement-breakpoint\nDROP TRIGGER fence_order_update;\n--> statement-breakpoint\nDROP TRIGGER fence_order_delete;\n--> statement-breakpoint\nDROP TRIGGER fence_order_line_insert;\n--> statement-breakpoint\nDROP TRIGGER fence_order_line_update;\n--> statement-breakpoint\nDROP TRIGGER fence_order_line_delete;\n--> statement-breakpoint\nDROP TRIGGER fence_inventory_reservation_insert;\n--> statement-breakpoint\nDROP TRIGGER fence_inventory_reservation_update;\n--> statement-breakpoint\nDROP TRIGGER fence_inventory_reservation_delete;\n--> statement-breakpoint\nDROP TRIGGER fence_inventory_level_insert;\n--> statement-breakpoint\nDROP TRIGGER fence_inventory_level_update;\n--> statement-breakpoint\nDROP TRIGGER fence_inventory_level_delete;\n--> statement-breakpoint\nDROP TRIGGER fence_outbox_event_insert;\n--> statement-breakpoint\nDROP TRIGGER fence_outbox_event_update;\n--> statement-breakpoint\nDROP TRIGGER fence_outbox_event_delete;\n--> statement-breakpoint\n\n-- (2) The commit obligation (§7.1): a permanently empty table that deferred\n-- keys point at. A marker or an audit row still referencing it at the end of\n-- a batch fails the commit (research/16: D1 checks declared deferred keys at\n-- the end of a batch), so a marker can't outlive its batch and an audit row\n-- can't stand without one.\nCREATE TABLE `commit_obligation` (`id` integer PRIMARY KEY NOT NULL);\n--> statement-breakpoint\nCREATE TRIGGER commit_obligation_empty BEFORE INSERT ON commit_obligation BEGIN\n SELECT RAISE(ABORT, 'KEDGE_COMMIT_OBLIGATION');\nEND;\n--> statement-breakpoint\nDROP TABLE `batch_marker`;\n--> statement-breakpoint\nCREATE TABLE `batch_marker` (\n\t`id` integer PRIMARY KEY NOT NULL CHECK (`id` = 1),\n\t`release` text NOT NULL,\n\t`cause` text NOT NULL,\n\t`batch_id` text NOT NULL,\n\t`actor` text NOT NULL,\n\t-- NOT NULL and 1: a NULL would satisfy the deferred key and let the\n\t-- marker outlive its batch, opening the fence to later writes (codex code r12).\n\t`must_clear` integer NOT NULL DEFAULT 1 CHECK (`must_clear` = 1) REFERENCES `commit_obligation`(`id`) DEFERRABLE INITIALLY DEFERRED\n);\n--> statement-breakpoint\n\n-- (3) The audit log, written first in every staff, key, invite or CLI batch.\nCREATE TABLE `audit_log` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`batch_id` text NOT NULL UNIQUE,\n\t`actor` text NOT NULL,\n\t`action` text NOT NULL,\n\t`entity_type` text,\n\t`entity_id` text,\n\t`changes` text NOT NULL DEFAULT '{}' CHECK (json_valid(`changes`)),\n\t`ip` text,\n\t`user_agent` text,\n\t`created_at` integer NOT NULL,\n\t`pending` integer DEFAULT 1 REFERENCES `commit_obligation`(`id`) DEFERRABLE INITIALLY DEFERRED\n);\n--> statement-breakpoint\nCREATE INDEX `audit_log_entity` ON `audit_log` (`entity_type`, `entity_id`, `created_at`, `id`);\n--> statement-breakpoint\nCREATE INDEX `audit_log_actor` ON `audit_log` (`actor`, `created_at`, `id`);\n--> statement-breakpoint\n-- The unfiltered log, newest first, keyset by (created_at, id) (codex code r13, K11).\nCREATE INDEX `audit_log_created` ON `audit_log` (`created_at`, `id`);\n--> statement-breakpoint\n-- Written before the marker, so it can't require one: instead it must be the\n-- batch's first statement (no marker yet) and start pending.\nCREATE TRIGGER audit_log_insert BEFORE INSERT ON audit_log BEGIN\n SELECT RAISE(ABORT, 'KEDGE_AUDIT_ORDER') WHERE EXISTS (SELECT 1 FROM batch_marker);\n SELECT RAISE(ABORT, 'KEDGE_AUDIT_PENDING') WHERE NEW.pending IS NOT 1;\nEND;\n--> statement-breakpoint\n-- The only change ever made to an audit row: its obligation discharged, by\n-- the marker that names it, nothing else touched (codex r10).\nCREATE TRIGGER audit_log_update BEFORE UPDATE ON audit_log BEGIN\n SELECT RAISE(ABORT, 'KEDGE_AUDIT_IMMUTABLE') WHERE NOT (\n OLD.pending = 1 AND NEW.pending IS NULL\n AND NEW.id = OLD.id AND NEW.batch_id = OLD.batch_id AND NEW.actor = OLD.actor AND NEW.action = OLD.action\n AND NEW.entity_type IS OLD.entity_type AND NEW.entity_id IS OLD.entity_id AND NEW.changes = OLD.changes\n AND NEW.ip IS OLD.ip AND NEW.user_agent IS OLD.user_agent AND NEW.created_at = OLD.created_at\n AND EXISTS (SELECT 1 FROM batch_marker m WHERE m.batch_id = OLD.batch_id AND m.actor = OLD.actor));\nEND;\n--> statement-breakpoint\nCREATE TRIGGER audit_log_delete BEFORE DELETE ON audit_log BEGIN\n SELECT RAISE(ABORT, 'KEDGE_AUDIT_IMMUTABLE');\nEND;\n--> statement-breakpoint\n-- An actor that must be audited (staff, key, invite, CLI) gets no marker\n-- without its batch's pending audit row, so its writes never commit unaudited.\nCREATE TRIGGER batch_marker_audit BEFORE INSERT ON batch_marker\n WHEN NEW.actor LIKE 'staff:%' OR NEW.actor LIKE 'key:%' OR NEW.actor LIKE 'invite:%' OR NEW.actor = 'system:cli' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_AUDIT_MISSING') WHERE NOT EXISTS (\n SELECT 1 FROM audit_log WHERE batch_id = NEW.batch_id AND actor = NEW.actor AND pending = 1);\nEND;\n--> statement-breakpoint\nCREATE TRIGGER batch_marker_discharge AFTER INSERT ON batch_marker BEGIN\n UPDATE audit_log SET pending = NULL WHERE batch_id = NEW.batch_id AND actor = NEW.actor AND pending = 1;\nEND;\n--> statement-breakpoint\nCREATE TRIGGER batch_marker_immutable BEFORE UPDATE ON batch_marker BEGIN\n SELECT RAISE(ABORT, 'KEDGE_MARKER_IMMUTABLE');\nEND;\n--> statement-breakpoint\n\n-- (4) Storefronts and API keys (§4).\nCREATE TABLE `storefront` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`name` text NOT NULL,\n\t`allowed_origins` text NOT NULL DEFAULT '[]' CHECK (json_valid(`allowed_origins`) AND json_type(`allowed_origins`) = 'array'),\n\t`created_at` integer NOT NULL\n);\n--> statement-breakpoint\nCREATE TABLE `api_key` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`type` text NOT NULL CHECK (`type` IN ('publishable', 'secret')),\n\t`lookup_id` text NOT NULL UNIQUE,\n\t`secret_digest` text NOT NULL,\n\t`pepper_version` integer NOT NULL,\n\t`token_prefix` text NOT NULL,\n\t`name` text NOT NULL,\n\t`storefront_id` text REFERENCES `storefront`(`id`),\n\t`created_by` text NOT NULL,\n\t`created_at` integer NOT NULL,\n\t`created_batch` text NOT NULL,\n\t`revoked_at` integer,\n\t`last_used_at` integer,\n\tCHECK ((`type` = 'publishable') = (`storefront_id` IS NOT NULL))\n);\n--> statement-breakpoint\nCREATE INDEX `api_key_storefront` ON `api_key` (`storefront_id`, `revoked_at`);\n--> statement-breakpoint\nCREATE INDEX `api_key_pepper` ON `api_key` (`pepper_version`) WHERE `revoked_at` IS NULL;\n--> statement-breakpoint\n-- A key's identity and grants never change; its digest changes only by the\n-- pepper step (active version + 1 from its own); revocation only ever moves\n-- earlier and never back (codex r11), so no batch resurrects a key.\nCREATE TRIGGER api_key_guard BEFORE UPDATE ON api_key BEGIN\n SELECT RAISE(ABORT, 'KEDGE_KEY_IMMUTABLE') WHERE NEW.id IS NOT OLD.id OR NEW.type IS NOT OLD.type\n OR NEW.lookup_id IS NOT OLD.lookup_id OR NEW.token_prefix IS NOT OLD.token_prefix\n OR NEW.storefront_id IS NOT OLD.storefront_id OR NEW.created_by IS NOT OLD.created_by\n OR NEW.created_at IS NOT OLD.created_at OR NEW.created_batch IS NOT OLD.created_batch\n OR ((NEW.secret_digest IS NOT OLD.secret_digest OR NEW.pepper_version IS NOT OLD.pepper_version)\n AND NOT (NEW.pepper_version = OLD.pepper_version + 1\n AND NEW.pepper_version = (SELECT active FROM keyring_setting WHERE name = 'pepper')));\n SELECT RAISE(ABORT, 'KEDGE_KEY_REVOKED') WHERE OLD.revoked_at IS NOT NULL\n AND (NEW.revoked_at IS NULL OR NEW.revoked_at > OLD.revoked_at);\nEND;\n--> statement-breakpoint\nCREATE TRIGGER api_key_delete BEFORE DELETE ON api_key BEGIN\n SELECT RAISE(ABORT, 'KEDGE_KEY_IMMUTABLE');\nEND;\n--> statement-breakpoint\n-- A key whose revocation is scheduled still works until then, so it counts\n-- (codex code r3): rotations with overlaps can't grow past four live keys.\nCREATE TRIGGER api_key_storefront_bound BEFORE INSERT ON api_key WHEN NEW.storefront_id IS NOT NULL BEGIN\n SELECT RAISE(ABORT, 'KEDGE_BOUND:storefront_keys') WHERE (\n SELECT count(*) FROM api_key WHERE storefront_id = NEW.storefront_id\n AND (revoked_at IS NULL OR revoked_at > NEW.created_at)) >= 4;\nEND;\n--> statement-breakpoint\nCREATE TABLE `api_key_grant` (\n\t`api_key_id` text NOT NULL REFERENCES `api_key`(`id`),\n\t`permission` text NOT NULL,\n\t`row_scope` text NOT NULL CHECK (`row_scope` IN ('all', 'self', 'location')),\n\tPRIMARY KEY (`api_key_id`, `permission`, `row_scope`)\n) WITHOUT ROWID;\n--> statement-breakpoint\n-- Grants are written with the key, in its batch, then never again.\nCREATE TRIGGER api_key_grant_insert BEFORE INSERT ON api_key_grant BEGIN\n SELECT RAISE(ABORT, 'KEDGE_KEY_IMMUTABLE') WHERE (SELECT created_batch FROM api_key WHERE id = NEW.api_key_id)\n IS NOT (SELECT batch_id FROM batch_marker WHERE id = 1)\n OR EXISTS (SELECT 1 FROM api_key WHERE id = NEW.api_key_id AND type = 'publishable');\n SELECT RAISE(ABORT, 'KEDGE_BOUND:key_grants') WHERE (\n SELECT count(*) FROM api_key_grant WHERE api_key_id = NEW.api_key_id) >= 128;\nEND;\n--> statement-breakpoint\nCREATE TRIGGER api_key_grant_immutable BEFORE UPDATE ON api_key_grant BEGIN\n SELECT RAISE(ABORT, 'KEDGE_KEY_IMMUTABLE');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER api_key_grant_delete BEFORE DELETE ON api_key_grant BEGIN\n SELECT RAISE(ABORT, 'KEDGE_KEY_IMMUTABLE');\nEND;\n--> statement-breakpoint\nCREATE TABLE `api_key_location` (\n\t`api_key_id` text NOT NULL REFERENCES `api_key`(`id`),\n\t`location_id` text NOT NULL REFERENCES `stock_location`(`id`),\n\tPRIMARY KEY (`api_key_id`, `location_id`)\n) WITHOUT ROWID;\n--> statement-breakpoint\nCREATE TRIGGER api_key_location_bound BEFORE INSERT ON api_key_location BEGIN\n SELECT RAISE(ABORT, 'KEDGE_KEY_IMMUTABLE') WHERE (SELECT created_batch FROM api_key WHERE id = NEW.api_key_id)\n IS NOT (SELECT batch_id FROM batch_marker WHERE id = 1);\n SELECT RAISE(ABORT, 'KEDGE_BOUND:locations') WHERE (\n SELECT count(*) FROM api_key_location WHERE api_key_id = NEW.api_key_id) >= 64;\nEND;\n--> statement-breakpoint\nCREATE TRIGGER api_key_location_immutable BEFORE UPDATE ON api_key_location BEGIN\n SELECT RAISE(ABORT, 'KEDGE_KEY_IMMUTABLE');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER api_key_location_delete BEFORE DELETE ON api_key_location BEGIN\n SELECT RAISE(ABORT, 'KEDGE_KEY_IMMUTABLE');\nEND;\n--> statement-breakpoint\n\n-- (5) The keyrings (§9): one row per secret family, its active and retiring\n-- versions. Each batch that writes material under a version asserts it.\nCREATE TABLE `keyring_setting` (\n\t`name` text PRIMARY KEY NOT NULL CHECK (`name` IN ('pepper', 'totp', 'auth')),\n\t`active` integer NOT NULL CHECK (`active` >= 1),\n\t`retiring` integer CHECK (`retiring` IS NULL OR `retiring` <> `active`),\n\t`activated_at` integer NOT NULL\n) WITHOUT ROWID;\n--> statement-breakpoint\n\n-- (6) Staff, roles, invites (§3).\nCREATE TABLE `staff` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`email` text NOT NULL UNIQUE,\n\t`password_hash` text NOT NULL,\n\t`status` text NOT NULL DEFAULT 'active' CHECK (`status` IN ('active', 'deactivated')),\n\t`credential_state` text NOT NULL DEFAULT 'active' CHECK (`credential_state` IN ('active', 'recovering')),\n\t`generation` integer NOT NULL DEFAULT 1,\n\t`epoch` integer NOT NULL DEFAULT 0,\n\t`totp_locked_until` integer,\n\t`totp_failures` integer NOT NULL DEFAULT 0,\n\t`created_at` integer NOT NULL\n);\n--> statement-breakpoint\nCREATE TRIGGER staff_guard BEFORE UPDATE ON staff BEGIN\n SELECT RAISE(ABORT, 'KEDGE_STAFF_IMMUTABLE') WHERE NEW.id IS NOT OLD.id OR NEW.email IS NOT OLD.email\n OR NEW.created_at IS NOT OLD.created_at OR NEW.generation < OLD.generation OR NEW.epoch < OLD.epoch;\n -- At least one active staff member holds Owner (§3.1).\n SELECT RAISE(ABORT, 'KEDGE_LAST_OWNER') WHERE OLD.status = 'active' AND NEW.status = 'deactivated'\n AND EXISTS (SELECT 1 FROM staff_role WHERE staff_id = OLD.id AND role_id = 'role_owner')\n AND NOT EXISTS (SELECT 1 FROM staff_role sr JOIN staff s ON s.id = sr.staff_id\n WHERE sr.role_id = 'role_owner' AND s.status = 'active' AND s.id <> OLD.id);\nEND;\n--> statement-breakpoint\nCREATE TRIGGER staff_delete BEFORE DELETE ON staff BEGIN\n SELECT RAISE(ABORT, 'KEDGE_STAFF_IMMUTABLE');\nEND;\n--> statement-breakpoint\nCREATE TABLE `role` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`code` text NOT NULL UNIQUE,\n\t`name` text NOT NULL,\n\t`builtin` integer NOT NULL DEFAULT 0 CHECK (`builtin` IN (0, 1)),\n\t`strong_auth` integer NOT NULL DEFAULT 0 CHECK (`strong_auth` IN (0, 1)),\n\t`version` integer NOT NULL DEFAULT 1,\n\t`created_at` integer NOT NULL\n);\n--> statement-breakpoint\nCREATE TABLE `role_grant` (\n\t`role_id` text NOT NULL REFERENCES `role`(`id`),\n\t`permission` text NOT NULL,\n\t`row_scope` text NOT NULL CHECK (`row_scope` IN ('all', 'self', 'location')),\n\tPRIMARY KEY (`role_id`, `permission`, `row_scope`)\n) WITHOUT ROWID;\n--> statement-breakpoint\n-- A built-in role never changes (§3.2); any change to a role's grants moves\n-- its version, which invites and authority guards compare (§3.3).\nCREATE TRIGGER role_guard BEFORE UPDATE ON role BEGIN\n SELECT RAISE(ABORT, 'KEDGE_ROLE_BUILTIN') WHERE OLD.builtin = 1;\n SELECT RAISE(ABORT, 'KEDGE_ROLE_IMMUTABLE') WHERE NEW.id IS NOT OLD.id OR NEW.builtin IS NOT OLD.builtin\n OR NEW.version < OLD.version OR NEW.created_at IS NOT OLD.created_at;\nEND;\n--> statement-breakpoint\nCREATE TRIGGER role_delete BEFORE DELETE ON role BEGIN\n SELECT RAISE(ABORT, 'KEDGE_ROLE_BUILTIN') WHERE OLD.builtin = 1;\n SELECT RAISE(ABORT, 'KEDGE_ROLE_IN_USE') WHERE EXISTS (SELECT 1 FROM staff_role WHERE role_id = OLD.id);\nEND;\n--> statement-breakpoint\nCREATE TRIGGER role_grant_insert BEFORE INSERT ON role_grant BEGIN\n SELECT RAISE(ABORT, 'KEDGE_ROLE_BUILTIN') WHERE (SELECT builtin FROM role WHERE id = NEW.role_id) = 1\n AND (SELECT cause FROM batch_marker WHERE id = 1) IS NOT 'migration:0025';\n -- Wildcards belong to built-in roles only (§3.2).\n SELECT RAISE(ABORT, 'KEDGE_ROLE_WILDCARD') WHERE NEW.permission LIKE '*%'\n AND (SELECT builtin FROM role WHERE id = NEW.role_id) = 0;\n SELECT RAISE(ABORT, 'KEDGE_BOUND:role_grants') WHERE (\n SELECT count(*) FROM role_grant WHERE role_id = NEW.role_id) >= 128;\nEND;\n--> statement-breakpoint\nCREATE TRIGGER role_grant_version_insert AFTER INSERT ON role_grant\n WHEN (SELECT builtin FROM role WHERE id = NEW.role_id) = 0 BEGIN\n UPDATE role SET version = version + 1 WHERE id = NEW.role_id;\nEND;\n--> statement-breakpoint\nCREATE TRIGGER role_grant_delete BEFORE DELETE ON role_grant BEGIN\n SELECT RAISE(ABORT, 'KEDGE_ROLE_BUILTIN') WHERE (SELECT builtin FROM role WHERE id = OLD.role_id) = 1;\nEND;\n--> statement-breakpoint\nCREATE TRIGGER role_grant_version_delete AFTER DELETE ON role_grant BEGIN\n UPDATE role SET version = version + 1 WHERE id = OLD.role_id;\nEND;\n--> statement-breakpoint\nCREATE TRIGGER role_grant_immutable BEFORE UPDATE ON role_grant BEGIN\n SELECT RAISE(ABORT, 'KEDGE_ROLE_IMMUTABLE');\nEND;\n--> statement-breakpoint\nCREATE TABLE `staff_role` (\n\t`staff_id` text NOT NULL REFERENCES `staff`(`id`),\n\t`role_id` text NOT NULL REFERENCES `role`(`id`),\n\tPRIMARY KEY (`staff_id`, `role_id`)\n) WITHOUT ROWID;\n--> statement-breakpoint\nCREATE INDEX `staff_role_role` ON `staff_role` (`role_id`, `staff_id`);\n--> statement-breakpoint\nCREATE TRIGGER staff_role_bound BEFORE INSERT ON staff_role BEGIN\n SELECT RAISE(ABORT, 'KEDGE_BOUND:staff_roles') WHERE (\n SELECT count(*) FROM staff_role WHERE staff_id = NEW.staff_id) >= 16;\nEND;\n--> statement-breakpoint\nCREATE TRIGGER staff_role_last_owner BEFORE DELETE ON staff_role WHEN OLD.role_id = 'role_owner' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_LAST_OWNER') WHERE NOT EXISTS (\n SELECT 1 FROM staff_role sr JOIN staff s ON s.id = sr.staff_id\n WHERE sr.role_id = 'role_owner' AND s.status = 'active' AND sr.staff_id <> OLD.staff_id);\nEND;\n--> statement-breakpoint\nCREATE TRIGGER staff_role_immutable BEFORE UPDATE ON staff_role BEGIN\n SELECT RAISE(ABORT, 'KEDGE_ROLE_IMMUTABLE');\nEND;\n--> statement-breakpoint\nCREATE TABLE `staff_location` (\n\t`staff_id` text NOT NULL REFERENCES `staff`(`id`),\n\t`location_id` text NOT NULL REFERENCES `stock_location`(`id`),\n\tPRIMARY KEY (`staff_id`, `location_id`)\n) WITHOUT ROWID;\n--> statement-breakpoint\nCREATE TRIGGER staff_location_bound BEFORE INSERT ON staff_location BEGIN\n SELECT RAISE(ABORT, 'KEDGE_BOUND:locations') WHERE (\n SELECT count(*) FROM staff_location WHERE staff_id = NEW.staff_id) >= 64;\nEND;\n--> statement-breakpoint\nCREATE TABLE `staff_invite` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`purpose` text NOT NULL CHECK (`purpose` IN ('bootstrap', 'join', 'reenrol')),\n\t`email` text NOT NULL,\n\t`token_digest` text NOT NULL UNIQUE,\n\t`roles` text NOT NULL DEFAULT '[]' CHECK (json_valid(`roles`) AND json_array_length(`roles`) <= 16),\n\t`staff_id` text REFERENCES `staff`(`id`),\n\t`staff_generation` integer,\n\t`staff_epoch` integer,\n\t`created_by` text NOT NULL,\n\t`issuer_generation` integer,\n\t`issued_strong` integer NOT NULL DEFAULT 0 CHECK (`issued_strong` IN (0, 1)),\n\t`created_at` integer NOT NULL,\n\t`expires_at` integer NOT NULL,\n\t`accepted_at` integer,\n\t`accepted_batch` text,\n\t`voided_at` integer,\n\tCHECK ((`purpose` = 'reenrol') <= (`staff_id` IS NOT NULL))\n);\n--> statement-breakpoint\nCREATE INDEX `staff_invite_open` ON `staff_invite` (`email`, `expires_at`) WHERE `accepted_at` IS NULL AND `voided_at` IS NULL;\n--> statement-breakpoint\nCREATE INDEX `staff_invite_expiry` ON `staff_invite` (`expires_at`);\n--> statement-breakpoint\nCREATE TRIGGER staff_invite_bound BEFORE INSERT ON staff_invite BEGIN\n SELECT RAISE(ABORT, 'KEDGE_BOUND:invites') WHERE (\n SELECT count(*) FROM staff_invite WHERE email = NEW.email AND accepted_at IS NULL AND voided_at IS NULL\n AND expires_at > NEW.created_at) >= 5;\nEND;\n--> statement-breakpoint\n-- Accepted once; nothing else about an invite changes after it is issued.\nCREATE TRIGGER staff_invite_guard BEFORE UPDATE ON staff_invite BEGIN\n SELECT RAISE(ABORT, 'KEDGE_INVITE_IMMUTABLE') WHERE NEW.id IS NOT OLD.id OR NEW.purpose IS NOT OLD.purpose\n OR NEW.email IS NOT OLD.email OR NEW.token_digest IS NOT OLD.token_digest OR NEW.roles IS NOT OLD.roles\n OR NEW.staff_id IS NOT OLD.staff_id OR NEW.staff_generation IS NOT OLD.staff_generation\n OR NEW.staff_epoch IS NOT OLD.staff_epoch OR NEW.created_by IS NOT OLD.created_by\n OR NEW.issuer_generation IS NOT OLD.issuer_generation OR NEW.issued_strong IS NOT OLD.issued_strong\n OR NEW.created_at IS NOT OLD.created_at OR NEW.expires_at IS NOT OLD.expires_at\n OR (OLD.accepted_at IS NOT NULL AND (NEW.accepted_at IS NOT OLD.accepted_at OR NEW.accepted_batch IS NOT OLD.accepted_batch))\n OR (OLD.voided_at IS NOT NULL AND NEW.voided_at IS NOT OLD.voided_at);\nEND;\n--> statement-breakpoint\n\n-- (7) Customers (§6.1): the email is the credential, so it never changes.\nCREATE TABLE `customer` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`email` text NOT NULL UNIQUE,\n\t`status` text NOT NULL DEFAULT 'active' CHECK (`status` IN ('active', 'blocked')),\n\t`epoch` integer NOT NULL DEFAULT 0,\n\t`created_at` integer NOT NULL\n);\n--> statement-breakpoint\nCREATE TRIGGER customer_guard BEFORE UPDATE ON customer BEGIN\n SELECT RAISE(ABORT, 'KEDGE_CUSTOMER_IMMUTABLE') WHERE NEW.id IS NOT OLD.id OR NEW.email IS NOT OLD.email\n OR NEW.created_at IS NOT OLD.created_at OR NEW.epoch < OLD.epoch;\nEND;\n--> statement-breakpoint\nCREATE TRIGGER customer_delete BEFORE DELETE ON customer BEGIN\n SELECT RAISE(ABORT, 'KEDGE_CUSTOMER_IMMUTABLE');\nEND;\n--> statement-breakpoint\n-- The owning customer of an order (§6.2): the commit copies the attached\n-- cart's customer onto it, and owner-scoped reads (`/store/customers/me/orders`,\n-- §5.1's `owner` scope column) page it newest first through its own index\n-- rather than scanning every order. No reference to `customer`: erasure\n-- unlinks the person, and the id stays on the order (customer-erasure.md).\nALTER TABLE `order` ADD COLUMN `customer_id` text;\n--> statement-breakpoint\nCREATE INDEX `order_customer_created` ON `order` (`customer_id`, `created_at`);\n--> statement-breakpoint\n-- The owner is sealed with the order: the write-once guard names it like\n-- every column but status (P0.3 §1.7), so no later batch can re-own an order.\nDROP TRIGGER order_write_once;\n--> statement-breakpoint\nCREATE TRIGGER order_write_once BEFORE UPDATE ON \"order\" BEGIN\n SELECT RAISE(ABORT, 'KEDGE_ORDER_IMMUTABLE') WHERE\n NEW.id IS NOT OLD.id OR NEW.cart_id IS NOT OLD.cart_id OR NEW.attempt_id IS NOT OLD.attempt_id\n OR NEW.currency_code IS NOT OLD.currency_code OR NEW.email IS NOT OLD.email\n OR NEW.subtotal IS NOT OLD.subtotal OR NEW.discount_total IS NOT OLD.discount_total\n OR NEW.shipping_total IS NOT OLD.shipping_total OR NEW.fee_total IS NOT OLD.fee_total\n OR NEW.tax_total IS NOT OLD.tax_total OR NEW.total IS NOT OLD.total\n OR NEW.payment_status IS NOT OLD.payment_status OR NEW.reservation_count IS NOT OLD.reservation_count\n OR NEW.expires_at IS NOT OLD.expires_at OR NEW.created_at IS NOT OLD.created_at\n OR NEW.tax_inclusive IS NOT OLD.tax_inclusive OR NEW.tax_rate_revenue_ppm IS NOT OLD.tax_rate_revenue_ppm\n OR NEW.tax_rate_deposit_ppm IS NOT OLD.tax_rate_deposit_ppm OR NEW.locale IS NOT OLD.locale\n OR NEW.place_by IS NOT OLD.place_by OR NEW.custom IS NOT OLD.custom\n OR NEW.hold_count IS NOT OLD.hold_count\n -- P0.5: the owning customer, copied from the attached cart at commit (§6.2).\n OR NEW.customer_id IS NOT OLD.customer_id\n OR ((NEW.revenue_total IS NOT OLD.revenue_total OR NEW.deposit_total IS NOT OLD.deposit_total)\n AND (OLD.revenue_total IS NOT NULL OR OLD.deposit_total IS NOT NULL\n OR EXISTS (SELECT 1 FROM contribution_check WHERE order_id = OLD.id)))\n OR ((NEW.number IS NOT OLD.number OR NEW.placed_at IS NOT OLD.placed_at)\n AND (OLD.number IS NOT NULL OR OLD.placed_at IS NOT NULL OR NEW.status IS NOT 'open'\n OR NEW.number IS NULL OR NEW.placed_at IS NULL))\n OR (NEW.expired_at IS NOT OLD.expired_at AND (OLD.expired_at IS NOT NULL OR NEW.status IS NOT 'expired'))\n -- reopenable: only 1 → 0, only while expired.\n OR (NEW.reopenable IS NOT OLD.reopenable AND NOT (OLD.reopenable = 1 AND NEW.reopenable = 0 AND OLD.status = 'expired'))\n -- release_cost: only before the seal, only to its rebuild value.\n OR (NEW.release_cost IS NOT OLD.release_cost AND (EXISTS (SELECT 1 FROM contribution_check WHERE order_id = OLD.id)\n OR NEW.release_cost IS NOT ((SELECT count(*) FROM inventory_reservation ir WHERE ir.order_id = OLD.id) + (SELECT coalesce(sum(ch.last_bucket - ch.first_bucket + 1), 0) FROM capacity_hold ch WHERE ch.order_id = OLD.id))))\n -- current_alloc_cost: only to its rebuild value.\n OR (NEW.current_alloc_cost IS NOT OLD.current_alloc_cost AND NEW.current_alloc_cost IS NOT ((SELECT count(*) FROM inventory_reservation ir WHERE ir.order_id = OLD.id AND ir.released_at IS NULL) + (SELECT coalesce(sum(ch.last_bucket - ch.first_bucket + 1), 0) FROM capacity_hold ch WHERE ch.order_id = OLD.id AND ch.released_at IS NULL)));\n SELECT RAISE(ABORT, 'KEDGE_ORDER_UNSEALED') WHERE NEW.status IS NOT OLD.status\n AND NOT EXISTS (SELECT 1 FROM contribution_check WHERE order_id = OLD.id);\nEND;\n--> statement-breakpoint\n\n-- (8) Sessions, codes, TOTP, challenges, attempts, devices, counters (§2).\nCREATE TABLE `auth_session` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`pool` text NOT NULL CHECK (`pool` IN ('staff', 'customer')),\n\t`principal_id` text NOT NULL,\n\t`token_digest` text NOT NULL UNIQUE,\n\t`method` text NOT NULL,\n\t`epoch` integer NOT NULL,\n\t`authenticated_at` integer NOT NULL,\n\t`last_seen_at` integer NOT NULL,\n\t`expires_at` integer NOT NULL,\n\t`enrol_until` integer,\n\t`resume_digest` text,\n\t`created_at` integer NOT NULL\n);\n--> statement-breakpoint\nCREATE INDEX `auth_session_principal` ON `auth_session` (`pool`, `principal_id`);\n--> statement-breakpoint\nCREATE INDEX `auth_session_expiry` ON `auth_session` (`expires_at`);\n--> statement-breakpoint\n-- A session's identity, method and authentication time never change; its\n-- epoch moves only to its principal's current one (a ceremony that keeps\n-- its caller signed in re-stamps it, §2.3).\nCREATE TRIGGER auth_session_guard BEFORE UPDATE ON auth_session BEGIN\n SELECT RAISE(ABORT, 'KEDGE_SESSION_IMMUTABLE') WHERE NEW.id IS NOT OLD.id OR NEW.pool IS NOT OLD.pool\n OR NEW.principal_id IS NOT OLD.principal_id OR NEW.token_digest IS NOT OLD.token_digest\n OR NEW.method IS NOT OLD.method OR NEW.authenticated_at IS NOT OLD.authenticated_at\n OR NEW.created_at IS NOT OLD.created_at OR NEW.resume_digest IS NOT OLD.resume_digest\n OR NEW.enrol_until IS NOT OLD.enrol_until\n OR (NEW.epoch IS NOT OLD.epoch AND NEW.epoch IS NOT (CASE NEW.pool\n WHEN 'staff' THEN (SELECT epoch FROM staff WHERE id = NEW.principal_id)\n ELSE (SELECT epoch FROM customer WHERE id = NEW.principal_id) END));\nEND;\n--> statement-breakpoint\nCREATE TABLE `auth_code` (\n\t`pool` text NOT NULL,\n\t`purpose` text NOT NULL,\n\t`email` text NOT NULL,\n\t`code_mac` text NOT NULL,\n\t`mac_version` integer NOT NULL,\n\t`expires_at` integer NOT NULL,\n\t`epoch` integer,\n\t`attempts` integer NOT NULL DEFAULT 0 CHECK (`attempts` BETWEEN 0 AND 3),\n\t`last_batch` text,\n\t`consumed_batch` text,\n\tPRIMARY KEY (`pool`, `purpose`, `email`)\n) WITHOUT ROWID;\n--> statement-breakpoint\nCREATE INDEX `auth_code_expiry` ON `auth_code` (`expires_at`);\n--> statement-breakpoint\nCREATE TABLE `auth_totp` (\n\t`staff_id` text PRIMARY KEY NOT NULL REFERENCES `staff`(`id`),\n\t`secret_enc` text NOT NULL,\n\t`key_version` integer NOT NULL,\n\t`last_step` integer NOT NULL,\n\t`step_batch` text,\n\t`verified_at` integer NOT NULL\n) WITHOUT ROWID;\n--> statement-breakpoint\nCREATE INDEX `auth_totp_key` ON `auth_totp` (`key_version`);\n--> statement-breakpoint\n-- A code is accepted only for a later step than the last (RFC 6238 §5.2).\n-- Steps only rise within one factor; re-encrypting it keeps its verified_at\n-- and its counter, while confirming a replacement factor (a new verified_at)\n-- starts its own (codex code r11).\nCREATE TRIGGER auth_totp_step BEFORE UPDATE ON auth_totp BEGIN\n SELECT RAISE(ABORT, 'KEDGE_TOTP_REPLAY') WHERE NEW.verified_at IS OLD.verified_at AND (NEW.last_step < OLD.last_step\n OR (NEW.last_step = OLD.last_step AND NEW.secret_enc IS OLD.secret_enc AND NEW.key_version IS OLD.key_version));\nEND;\n--> statement-breakpoint\nCREATE TABLE `auth_totp_pending` (\n\t`staff_id` text PRIMARY KEY NOT NULL REFERENCES `staff`(`id`),\n\t`secret_enc` text NOT NULL,\n\t`key_version` integer NOT NULL,\n\t`session_id` text NOT NULL,\n\t`epoch` integer NOT NULL,\n\t`attempts` integer NOT NULL DEFAULT 0 CHECK (`attempts` BETWEEN 0 AND 5),\n\t`expires_at` integer NOT NULL\n) WITHOUT ROWID;\n--> statement-breakpoint\nCREATE TABLE `auth_challenge` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`token_digest` text NOT NULL UNIQUE,\n\t`staff_id` text NOT NULL REFERENCES `staff`(`id`),\n\t`epoch` integer NOT NULL,\n\t`attempts` integer NOT NULL DEFAULT 0 CHECK (`attempts` BETWEEN 0 AND 5),\n\t`expires_at` integer NOT NULL,\n\t`last_batch` text,\n\t`consumed_batch` text\n);\n--> statement-breakpoint\nCREATE INDEX `auth_challenge_expiry` ON `auth_challenge` (`expires_at`);\n--> statement-breakpoint\nCREATE TABLE `auth_attempt` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`kind` text NOT NULL,\n\t`pool` text NOT NULL,\n\t`subject` text,\n\t`state` text NOT NULL DEFAULT 'admitted' CHECK (`state` IN ('admitted', 'succeeded', 'failed')),\n\t`created_at` integer NOT NULL,\n\t`completed_batch` text\n);\n--> statement-breakpoint\nCREATE INDEX `auth_attempt_age` ON `auth_attempt` (`created_at`);\n--> statement-breakpoint\nCREATE TRIGGER auth_attempt_once BEFORE UPDATE ON auth_attempt BEGIN\n SELECT RAISE(ABORT, 'KEDGE_ATTEMPT_DONE') WHERE OLD.state <> 'admitted'\n OR NEW.id IS NOT OLD.id OR NEW.kind IS NOT OLD.kind OR NEW.subject IS NOT OLD.subject;\nEND;\n--> statement-breakpoint\nCREATE TABLE `auth_device` (\n\t`digest` text PRIMARY KEY NOT NULL,\n\t`staff_id` text NOT NULL REFERENCES `staff`(`id`),\n\t`created_at` integer NOT NULL,\n\t`last_used_at` integer NOT NULL,\n\t`expires_at` integer NOT NULL\n) WITHOUT ROWID;\n--> statement-breakpoint\nCREATE INDEX `auth_device_expiry` ON `auth_device` (`expires_at`);\n--> statement-breakpoint\nCREATE TABLE `auth_rate_counter` (\n\t`name` text PRIMARY KEY NOT NULL,\n\t`window_start` integer NOT NULL,\n\t`count` integer NOT NULL,\n\t`expires_at` integer NOT NULL\n) WITHOUT ROWID;\n--> statement-breakpoint\nCREATE INDEX `auth_rate_counter_expiry` ON `auth_rate_counter` (`expires_at`);\n--> statement-breakpoint\nCREATE TABLE `auth_event` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`pool` text NOT NULL,\n\t`kind` text NOT NULL,\n\t`principal_id` text,\n\t`email` text,\n\t`attempt_id` text,\n\t`outcome` text NOT NULL,\n\t`ip` text,\n\t`created_at` integer NOT NULL\n);\n--> statement-breakpoint\nCREATE INDEX `auth_event_principal` ON `auth_event` (`principal_id`, `created_at`);\n--> statement-breakpoint\n\n-- (9) Idempotency (§8): the generic command envelope, P0.4's rows kept.\nCREATE TABLE `idempotency_key` (\n\t`principal` text NOT NULL,\n\t`operation` text NOT NULL,\n\t`key` text NOT NULL CHECK (length(`key`) BETWEEN 1 AND 255),\n\t`fingerprint_scheme` text NOT NULL,\n\t`fingerprint` text NOT NULL,\n\t`result` text NOT NULL CHECK (json_valid(`result`)),\n\t`sealed_result` text,\n\t`seal_version` integer,\n\t`sealed_until` integer,\n\t`created_at` integer NOT NULL,\n\t`expires_at` integer NOT NULL,\n\tPRIMARY KEY (`principal`, `operation`, `key`)\n) WITHOUT ROWID;\n--> statement-breakpoint\nCREATE INDEX `idempotency_key_expiry` ON `idempotency_key` (`expires_at`);\n--> statement-breakpoint\nCREATE INDEX `idempotency_key_sealed` ON `idempotency_key` (`sealed_until`) WHERE `sealed_result` IS NOT NULL;\n--> statement-breakpoint\nCREATE TRIGGER idempotency_key_guard BEFORE UPDATE ON idempotency_key BEGIN\n SELECT RAISE(ABORT, 'KEDGE_IDEMPOTENCY_IMMUTABLE') WHERE NEW.principal IS NOT OLD.principal\n OR NEW.operation IS NOT OLD.operation OR NEW.key IS NOT OLD.key\n OR NEW.fingerprint_scheme IS NOT OLD.fingerprint_scheme OR NEW.fingerprint IS NOT OLD.fingerprint\n OR NEW.result IS NOT OLD.result OR NEW.created_at IS NOT OLD.created_at OR NEW.expires_at IS NOT OLD.expires_at\n OR (NEW.sealed_result IS NOT OLD.sealed_result AND NEW.sealed_result IS NOT NULL);\nEND;\n--> statement-breakpoint\nCREATE TABLE `principal_alias` (\n\t`alias` text PRIMARY KEY NOT NULL,\n\t`principal` text NOT NULL,\n\t`created_at` integer NOT NULL\n) WITHOUT ROWID;\n--> statement-breakpoint\nCREATE INDEX `principal_alias_principal` ON `principal_alias` (`principal`);\n--> statement-breakpoint\n\n-- (9a) One assertion primitive for P0.5's in-batch guards (the authority\n-- guard, keyring versions, claims asserted by batch id): a statement inserts\n-- `(code, predicate)`, the insert raises `code` unless the predicate held,\n-- and the row never stays, so a guard costs one statement and no state.\nCREATE TABLE `kedge_assert` (\n\t`code` text NOT NULL,\n\t`ok` integer\n);\n--> statement-breakpoint\nCREATE TRIGGER kedge_assert_check BEFORE INSERT ON kedge_assert WHEN NEW.ok IS NOT 1 BEGIN\n SELECT RAISE(ABORT, NEW.code);\nEND;\n--> statement-breakpoint\nCREATE TRIGGER kedge_assert_clear AFTER INSERT ON kedge_assert BEGIN\n DELETE FROM kedge_assert;\nEND;\n--> statement-breakpoint\n\n-- (10) The new fence, before any backfill (§8a step 1).\nCREATE TRIGGER fence_api_key_insert BEFORE INSERT ON \"api_key\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_api_key_update BEFORE UPDATE ON \"api_key\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_api_key_delete BEFORE DELETE ON \"api_key\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_api_key_grant_insert BEFORE INSERT ON \"api_key_grant\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_api_key_grant_update BEFORE UPDATE ON \"api_key_grant\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_api_key_grant_delete BEFORE DELETE ON \"api_key_grant\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_api_key_location_insert BEFORE INSERT ON \"api_key_location\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_api_key_location_update BEFORE UPDATE ON \"api_key_location\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_api_key_location_delete BEFORE DELETE ON \"api_key_location\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_auth_attempt_insert BEFORE INSERT ON \"auth_attempt\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_auth_attempt_update BEFORE UPDATE ON \"auth_attempt\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_auth_attempt_delete BEFORE DELETE ON \"auth_attempt\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_auth_challenge_insert BEFORE INSERT ON \"auth_challenge\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_auth_challenge_update BEFORE UPDATE ON \"auth_challenge\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_auth_challenge_delete BEFORE DELETE ON \"auth_challenge\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_auth_code_insert BEFORE INSERT ON \"auth_code\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_auth_code_update BEFORE UPDATE ON \"auth_code\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_auth_code_delete BEFORE DELETE ON \"auth_code\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_auth_device_insert BEFORE INSERT ON \"auth_device\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_auth_device_update BEFORE UPDATE ON \"auth_device\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_auth_device_delete BEFORE DELETE ON \"auth_device\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_auth_event_insert BEFORE INSERT ON \"auth_event\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_auth_event_update BEFORE UPDATE ON \"auth_event\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_auth_event_delete BEFORE DELETE ON \"auth_event\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_auth_rate_counter_insert BEFORE INSERT ON \"auth_rate_counter\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_auth_rate_counter_update BEFORE UPDATE ON \"auth_rate_counter\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_auth_rate_counter_delete BEFORE DELETE ON \"auth_rate_counter\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_auth_session_insert BEFORE INSERT ON \"auth_session\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_auth_session_update BEFORE UPDATE ON \"auth_session\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_auth_session_delete BEFORE DELETE ON \"auth_session\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_auth_totp_insert BEFORE INSERT ON \"auth_totp\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_auth_totp_update BEFORE UPDATE ON \"auth_totp\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_auth_totp_delete BEFORE DELETE ON \"auth_totp\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_auth_totp_pending_insert BEFORE INSERT ON \"auth_totp_pending\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_auth_totp_pending_update BEFORE UPDATE ON \"auth_totp_pending\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_auth_totp_pending_delete BEFORE DELETE ON \"auth_totp_pending\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_capacity_adjustment_insert BEFORE INSERT ON \"capacity_adjustment\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_capacity_adjustment_update BEFORE UPDATE ON \"capacity_adjustment\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_capacity_adjustment_delete BEFORE DELETE ON \"capacity_adjustment\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_capacity_base_insert BEFORE INSERT ON \"capacity_base\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_capacity_base_update BEFORE UPDATE ON \"capacity_base\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_capacity_base_delete BEFORE DELETE ON \"capacity_base\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_capacity_base_change_insert BEFORE INSERT ON \"capacity_base_change\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_capacity_base_change_update BEFORE UPDATE ON \"capacity_base_change\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_capacity_base_change_delete BEFORE DELETE ON \"capacity_base_change\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_capacity_bucket_insert BEFORE INSERT ON \"capacity_bucket\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_capacity_bucket_update BEFORE UPDATE ON \"capacity_bucket\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_capacity_bucket_delete BEFORE DELETE ON \"capacity_bucket\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_capacity_buffer_insert BEFORE INSERT ON \"capacity_buffer\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_capacity_buffer_update BEFORE UPDATE ON \"capacity_buffer\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_capacity_buffer_delete BEFORE DELETE ON \"capacity_buffer\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_capacity_guard_insert BEFORE INSERT ON \"capacity_guard\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_capacity_guard_update BEFORE UPDATE ON \"capacity_guard\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_capacity_guard_delete BEFORE DELETE ON \"capacity_guard\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_capacity_hold_insert BEFORE INSERT ON \"capacity_hold\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_capacity_hold_update BEFORE UPDATE ON \"capacity_hold\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_capacity_hold_delete BEFORE DELETE ON \"capacity_hold\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_capacity_pattern_insert BEFORE INSERT ON \"capacity_pattern\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_capacity_pattern_update BEFORE UPDATE ON \"capacity_pattern\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_capacity_pattern_delete BEFORE DELETE ON \"capacity_pattern\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_capacity_pool_insert BEFORE INSERT ON \"capacity_pool\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_capacity_pool_update BEFORE UPDATE ON \"capacity_pool\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_capacity_pool_delete BEFORE DELETE ON \"capacity_pool\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_capacity_scope_map_insert BEFORE INSERT ON \"capacity_scope_map\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_capacity_scope_map_update BEFORE UPDATE ON \"capacity_scope_map\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_capacity_scope_map_delete BEFORE DELETE ON \"capacity_scope_map\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_checkout_fence_insert BEFORE INSERT ON \"checkout_fence\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_checkout_fence_update BEFORE UPDATE ON \"checkout_fence\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_checkout_fence_delete BEFORE DELETE ON \"checkout_fence\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_checkout_policy_insert BEFORE INSERT ON \"checkout_policy\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_checkout_policy_update BEFORE UPDATE ON \"checkout_policy\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_checkout_policy_delete BEFORE DELETE ON \"checkout_policy\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_contribution_check_insert BEFORE INSERT ON \"contribution_check\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_contribution_check_update BEFORE UPDATE ON \"contribution_check\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_contribution_check_delete BEFORE DELETE ON \"contribution_check\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_contribution_fact_insert BEFORE INSERT ON \"contribution_fact\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_contribution_fact_update BEFORE UPDATE ON \"contribution_fact\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_contribution_fact_delete BEFORE DELETE ON \"contribution_fact\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_customer_insert BEFORE INSERT ON \"customer\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_customer_update BEFORE UPDATE ON \"customer\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_customer_delete BEFORE DELETE ON \"customer\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_hold_retirement_due_insert BEFORE INSERT ON \"hold_retirement_due\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_hold_retirement_due_update BEFORE UPDATE ON \"hold_retirement_due\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_hold_retirement_due_delete BEFORE DELETE ON \"hold_retirement_due\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_idempotency_key_insert BEFORE INSERT ON \"idempotency_key\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_idempotency_key_update BEFORE UPDATE ON \"idempotency_key\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_idempotency_key_delete BEFORE DELETE ON \"idempotency_key\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_inventory_item_insert BEFORE INSERT ON \"inventory_item\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_inventory_item_update BEFORE UPDATE ON \"inventory_item\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_inventory_item_delete BEFORE DELETE ON \"inventory_item\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_inventory_level_insert BEFORE INSERT ON \"inventory_level\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_inventory_level_update BEFORE UPDATE ON \"inventory_level\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_inventory_level_delete BEFORE DELETE ON \"inventory_level\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_inventory_reservation_insert BEFORE INSERT ON \"inventory_reservation\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_inventory_reservation_update BEFORE UPDATE ON \"inventory_reservation\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_inventory_reservation_delete BEFORE DELETE ON \"inventory_reservation\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_kedge_assert_insert BEFORE INSERT ON \"kedge_assert\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_kedge_assert_update BEFORE UPDATE ON \"kedge_assert\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_kedge_assert_delete BEFORE DELETE ON \"kedge_assert\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_keyring_setting_insert BEFORE INSERT ON \"keyring_setting\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_keyring_setting_update BEFORE UPDATE ON \"keyring_setting\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_keyring_setting_delete BEFORE DELETE ON \"keyring_setting\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_maintenance_cursor_insert BEFORE INSERT ON \"maintenance_cursor\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_maintenance_cursor_update BEFORE UPDATE ON \"maintenance_cursor\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_maintenance_cursor_delete BEFORE DELETE ON \"maintenance_cursor\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_order_insert BEFORE INSERT ON \"order\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_order_update BEFORE UPDATE ON \"order\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_order_delete BEFORE DELETE ON \"order\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_order_adjustment_insert BEFORE INSERT ON \"order_adjustment\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_order_adjustment_update BEFORE UPDATE ON \"order_adjustment\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_order_adjustment_delete BEFORE DELETE ON \"order_adjustment\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_order_event_insert BEFORE INSERT ON \"order_event\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_order_event_update BEFORE UPDATE ON \"order_event\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_order_event_delete BEFORE DELETE ON \"order_event\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_order_fee_insert BEFORE INSERT ON \"order_fee\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_order_fee_update BEFORE UPDATE ON \"order_fee\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_order_fee_delete BEFORE DELETE ON \"order_fee\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_order_index_insert BEFORE INSERT ON \"order_index\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_order_index_update BEFORE UPDATE ON \"order_index\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_order_index_delete BEFORE DELETE ON \"order_index\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_order_line_insert BEFORE INSERT ON \"order_line\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_order_line_update BEFORE UPDATE ON \"order_line\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_order_line_delete BEFORE DELETE ON \"order_line\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_order_line_adjustment_insert BEFORE INSERT ON \"order_line_adjustment\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_order_line_adjustment_update BEFORE UPDATE ON \"order_line_adjustment\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_order_line_adjustment_delete BEFORE DELETE ON \"order_line_adjustment\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_order_line_component_insert BEFORE INSERT ON \"order_line_component\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_order_line_component_update BEFORE UPDATE ON \"order_line_component\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_order_line_component_delete BEFORE DELETE ON \"order_line_component\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_order_line_component_requirement_insert BEFORE INSERT ON \"order_line_component_requirement\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_order_line_component_requirement_update BEFORE UPDATE ON \"order_line_component_requirement\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_order_line_component_requirement_delete BEFORE DELETE ON \"order_line_component_requirement\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_order_line_component_version_insert BEFORE INSERT ON \"order_line_component_version\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_order_line_component_version_update BEFORE UPDATE ON \"order_line_component_version\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_order_line_component_version_delete BEFORE DELETE ON \"order_line_component_version\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_order_line_period_provenance_insert BEFORE INSERT ON \"order_line_period_provenance\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_order_line_period_provenance_update BEFORE UPDATE ON \"order_line_period_provenance\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_order_line_period_provenance_delete BEFORE DELETE ON \"order_line_period_provenance\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_order_place_by_source_insert BEFORE INSERT ON \"order_place_by_source\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_order_place_by_source_update BEFORE UPDATE ON \"order_place_by_source\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_order_place_by_source_delete BEFORE DELETE ON \"order_place_by_source\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_order_seal_due_insert BEFORE INSERT ON \"order_seal_due\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_order_seal_due_update BEFORE UPDATE ON \"order_seal_due\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_order_seal_due_delete BEFORE DELETE ON \"order_seal_due\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_order_tax_line_insert BEFORE INSERT ON \"order_tax_line\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_order_tax_line_update BEFORE UPDATE ON \"order_tax_line\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_order_tax_line_delete BEFORE DELETE ON \"order_tax_line\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_outbox_event_insert BEFORE INSERT ON \"outbox_event\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_outbox_event_update BEFORE UPDATE ON \"outbox_event\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_outbox_event_delete BEFORE DELETE ON \"outbox_event\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_pending_expiry_insert BEFORE INSERT ON \"pending_expiry\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_pending_expiry_update BEFORE UPDATE ON \"pending_expiry\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_pending_expiry_delete BEFORE DELETE ON \"pending_expiry\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_plugin_participation_insert BEFORE INSERT ON \"plugin_participation\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_plugin_participation_update BEFORE UPDATE ON \"plugin_participation\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_plugin_participation_delete BEFORE DELETE ON \"plugin_participation\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_plugin_state_insert BEFORE INSERT ON \"plugin_state\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_plugin_state_update BEFORE UPDATE ON \"plugin_state\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_plugin_state_delete BEFORE DELETE ON \"plugin_state\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_policy_acceptance_insert BEFORE INSERT ON \"policy_acceptance\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_policy_acceptance_update BEFORE UPDATE ON \"policy_acceptance\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_policy_acceptance_delete BEFORE DELETE ON \"policy_acceptance\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_policy_revision_insert BEFORE INSERT ON \"policy_revision\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_policy_revision_update BEFORE UPDATE ON \"policy_revision\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_policy_revision_delete BEFORE DELETE ON \"policy_revision\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_price_insert BEFORE INSERT ON \"price\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_price_update BEFORE UPDATE ON \"price\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_price_delete BEFORE DELETE ON \"price\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_price_list_insert BEFORE INSERT ON \"price_list\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_price_list_update BEFORE UPDATE ON \"price_list\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_price_list_delete BEFORE DELETE ON \"price_list\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_principal_alias_insert BEFORE INSERT ON \"principal_alias\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_principal_alias_update BEFORE UPDATE ON \"principal_alias\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_principal_alias_delete BEFORE DELETE ON \"principal_alias\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_product_insert BEFORE INSERT ON \"product\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_product_update BEFORE UPDATE ON \"product\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_product_delete BEFORE DELETE ON \"product\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_release_delivery_insert BEFORE INSERT ON \"release_delivery\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_release_delivery_update BEFORE UPDATE ON \"release_delivery\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_release_delivery_delete BEFORE DELETE ON \"release_delivery\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_release_obligation_insert BEFORE INSERT ON \"release_obligation\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_release_obligation_update BEFORE UPDATE ON \"release_obligation\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_release_obligation_delete BEFORE DELETE ON \"release_obligation\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_reopenable_hold_pool_insert BEFORE INSERT ON \"reopenable_hold_pool\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_reopenable_hold_pool_update BEFORE UPDATE ON \"reopenable_hold_pool\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_reopenable_hold_pool_delete BEFORE DELETE ON \"reopenable_hold_pool\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_role_insert BEFORE INSERT ON \"role\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_role_update BEFORE UPDATE ON \"role\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_role_delete BEFORE DELETE ON \"role\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_role_grant_insert BEFORE INSERT ON \"role_grant\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_role_grant_update BEFORE UPDATE ON \"role_grant\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_role_grant_delete BEFORE DELETE ON \"role_grant\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_sequence_insert BEFORE INSERT ON \"sequence\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_sequence_update BEFORE UPDATE ON \"sequence\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_sequence_delete BEFORE DELETE ON \"sequence\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_shortage_queue_insert BEFORE INSERT ON \"shortage_queue\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_shortage_queue_update BEFORE UPDATE ON \"shortage_queue\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_shortage_queue_delete BEFORE DELETE ON \"shortage_queue\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_shortage_seen_insert BEFORE INSERT ON \"shortage_seen\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_shortage_seen_update BEFORE UPDATE ON \"shortage_seen\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_shortage_seen_delete BEFORE DELETE ON \"shortage_seen\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_staff_insert BEFORE INSERT ON \"staff\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_staff_update BEFORE UPDATE ON \"staff\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_staff_delete BEFORE DELETE ON \"staff\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_staff_invite_insert BEFORE INSERT ON \"staff_invite\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_staff_invite_update BEFORE UPDATE ON \"staff_invite\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_staff_invite_delete BEFORE DELETE ON \"staff_invite\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_staff_location_insert BEFORE INSERT ON \"staff_location\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_staff_location_update BEFORE UPDATE ON \"staff_location\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_staff_location_delete BEFORE DELETE ON \"staff_location\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_staff_role_insert BEFORE INSERT ON \"staff_role\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_staff_role_update BEFORE UPDATE ON \"staff_role\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_staff_role_delete BEFORE DELETE ON \"staff_role\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_stock_location_insert BEFORE INSERT ON \"stock_location\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_stock_location_update BEFORE UPDATE ON \"stock_location\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_stock_location_delete BEFORE DELETE ON \"stock_location\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_store_time_insert BEFORE INSERT ON \"store_time\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_store_time_update BEFORE UPDATE ON \"store_time\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_store_time_delete BEFORE DELETE ON \"store_time\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_storefront_insert BEFORE INSERT ON \"storefront\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_storefront_update BEFORE UPDATE ON \"storefront\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_storefront_delete BEFORE DELETE ON \"storefront\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_tax_setting_insert BEFORE INSERT ON \"tax_setting\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_tax_setting_update BEFORE UPDATE ON \"tax_setting\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_tax_setting_delete BEFORE DELETE ON \"tax_setting\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_transition_insert BEFORE INSERT ON \"transition\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_transition_update BEFORE UPDATE ON \"transition\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_transition_delete BEFORE DELETE ON \"transition\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_tz_transition_insert BEFORE INSERT ON \"tz_transition\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_tz_transition_update BEFORE UPDATE ON \"tz_transition\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_tz_transition_delete BEFORE DELETE ON \"tz_transition\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_variant_insert BEFORE INSERT ON \"variant\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_variant_update BEFORE UPDATE ON \"variant\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_variant_delete BEFORE DELETE ON \"variant\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_variant_inventory_item_insert BEFORE INSERT ON \"variant_inventory_item\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_variant_inventory_item_update BEFORE UPDATE ON \"variant_inventory_item\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_variant_inventory_item_delete BEFORE DELETE ON \"variant_inventory_item\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\n\n-- (11) The migration's own writes go under its marker (it is the first\n-- writer of release p0.5) and clear it at the end.\nINSERT INTO `batch_marker` (`id`, `release`, `cause`, `batch_id`, `actor`) VALUES (1, 'p0.5', 'migration:0025', 'bat_migration_0025', 'system');\n--> statement-breakpoint\nINSERT INTO `keyring_setting` (`name`, `active`, `retiring`, `activated_at`) VALUES\n ('pepper', 1, NULL, 0), ('totp', 1, NULL, 0), ('auth', 1, NULL, 0);\n--> statement-breakpoint\n-- Built-in roles (§3.2): Owner and Admin by wildcards, Read-only by `*:read`,\n-- the rest by explicit core grants.\nINSERT INTO `role` (`id`, `code`, `name`, `builtin`, `strong_auth`, `version`, `created_at`) VALUES\n ('role_owner', 'owner', 'Owner', 1, 1, 1, 0),\n ('role_admin', 'admin', 'Admin', 1, 1, 1, 0),\n ('role_operations', 'operations', 'Operations', 1, 0, 1, 0),\n ('role_catalog', 'catalog', 'Catalog', 1, 0, 1, 0),\n ('role_support', 'support', 'Support', 1, 0, 1, 0),\n ('role_read_only', 'read_only', 'Read-only', 1, 0, 1, 0);\n--> statement-breakpoint\nINSERT INTO `role_grant` (`role_id`, `permission`, `row_scope`) VALUES\n ('role_owner', '*', 'all'),\n ('role_admin', '*!owner:manage', 'all'),\n ('role_read_only', '*:read', 'all'),\n ('role_operations', 'order:read', 'all'), ('role_operations', 'order:write', 'all'), ('role_operations', 'order:place', 'all'),\n ('role_operations', 'inventory:read', 'all'), ('role_operations', 'inventory:write', 'all'),\n ('role_operations', 'capacity:read', 'all'), ('role_operations', 'capacity:write', 'all'),\n ('role_operations', 'product:read', 'all'),\n ('role_catalog', 'product:read', 'all'), ('role_catalog', 'product:write', 'all'), ('role_catalog', 'inventory:read', 'all'),\n ('role_support', 'order:read', 'all'), ('role_support', 'field:pii', 'all'), ('role_support', 'product:read', 'all');\n--> statement-breakpoint\n-- P0.4's capacity commands keep their retry identity (§8): the shared\n-- secret's principal becomes `legacy:admin`, which the first Owner's\n-- bootstrap aliases; fingerprints stay P0.4's SHA-256, marked as such.\nINSERT INTO `idempotency_key` (`principal`, `operation`, `key`, `fingerprint_scheme`, `fingerprint`, `result`, `created_at`, `expires_at`)\n SELECT CASE `principal` WHEN 'stf_admin_secret' THEN 'legacy:admin' ELSE `principal` END, `operation`, `key`,\n 'legacy-sha256', `payload_sha256`, `result`, `created_at`,\n CAST(unixepoch('subsec') * 1000 AS INTEGER) + 86400000\n FROM `capacity_command`;\n--> statement-breakpoint\nDROP TABLE `capacity_command`;\n--> statement-breakpoint\nDELETE FROM `batch_marker`;\n","migrations/0026_relay_timers.sql":"-- P0.6a: the relay, subscribers, timers and the effect journal's D1 side\n-- (docs/proposals/p0.6a-relay-timers-effects.md, D-061). Rendered from\n-- packages/db/sql by src/access-sql.ts and src/capacity-sql.ts; render.test.ts\n-- compares. N−1 compatible (§6.1): the fence stays at release p0.5, and every\n-- trigger here fires inside the previous Worker's batches too.\n\n-- (1) The marker learns who wrote a batch (§6.1). Every batch of this\n-- release's code sets `writer`; the previous Worker's leave it NULL, which is\n-- how its release drain is fenced off below.\nALTER TABLE `batch_marker` ADD `writer` text;\n--> statement-breakpoint\n\n-- (2) Subscriptions are rows (§2.2), written by `kedge generate` per plugin\n-- and here for core's own; retired, never deleted.\nCREATE TABLE `event_subscription` (\n\t`plugin` text NOT NULL,\n\t`registration` text NOT NULL,\n\t`event_type` text NOT NULL,\n\t`durable` integer NOT NULL DEFAULT 0 CHECK (`durable` IN (0, 1)),\n\t`state` text NOT NULL CHECK (`state` IN ('live', 'retired')),\n\t`generation` integer NOT NULL,\n\t`created_at` integer NOT NULL,\n\tPRIMARY KEY (`plugin`, `registration`, `event_type`)\n);\n--> statement-breakpoint\nCREATE INDEX `event_subscription_live` ON `event_subscription` (`event_type`, `state`);\n--> statement-breakpoint\n-- The obligation trigger reads every live row of an event type, so the cap is\n-- enforced where a row becomes live: a 33rd fails `kedge migrate`, before any\n-- business event meets it (codex r1).\nCREATE TRIGGER event_subscription_cap BEFORE INSERT ON event_subscription WHEN NEW.state = 'live' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_SUBSCRIPTION_CAP')\n WHERE (SELECT count(*) FROM event_subscription WHERE event_type = NEW.event_type AND state = 'live') >= 32;\nEND;\n--> statement-breakpoint\n-- Identity is immutable, so moving to another event type is a retire and an\n-- insert, which the cap counts (codex r2).\nCREATE TRIGGER event_subscription_guard BEFORE UPDATE ON event_subscription BEGIN\n SELECT RAISE(ABORT, 'KEDGE_SUBSCRIPTION_IMMUTABLE')\n WHERE NEW.plugin IS NOT OLD.plugin OR NEW.registration IS NOT OLD.registration\n OR NEW.event_type IS NOT OLD.event_type OR NEW.durable IS NOT OLD.durable OR NEW.created_at IS NOT OLD.created_at;\n SELECT RAISE(ABORT, 'KEDGE_SUBSCRIPTION_CAP')\n WHERE NEW.state = 'live' AND OLD.state = 'retired'\n AND (SELECT count(*) FROM event_subscription WHERE event_type = NEW.event_type AND state = 'live') >= 32;\nEND;\n--> statement-breakpoint\nCREATE TRIGGER event_subscription_no_delete BEFORE DELETE ON event_subscription BEGIN\n SELECT RAISE(ABORT, 'KEDGE_SUBSCRIPTION_IMMUTABLE');\nEND;\n--> statement-breakpoint\n\n-- (3) Subscriber deliveries (§2.3): written with their event, delivered by the\n-- relay, owned by one run at a time, dead-lettered in place.\nCREATE TABLE `subscriber_obligation` (\n\t`seq` integer PRIMARY KEY AUTOINCREMENT NOT NULL,\n\t`event_id` text NOT NULL REFERENCES `outbox_event`(`id`),\n\t`plugin` text NOT NULL,\n\t`registration` text NOT NULL,\n\t`order_id` text REFERENCES `order_index`(`id`),\n\t`provider_installation` text,\n\t`state` text NOT NULL CHECK (`state` IN ('pending', 'blocked', 'finalizing', 'done', 'failed')),\n\t`attempts` integer NOT NULL DEFAULT 0 CHECK (typeof(`attempts`) = 'integer' AND `attempts` >= 0),\n\t`next_attempt_at` integer NOT NULL,\n\t`lease_token` text,\n\t`run_token` text,\n\t`page` integer NOT NULL DEFAULT 0,\n\t`cursor` text,\n\t`blocked_on` text,\n\t`blocked_revision` integer,\n\t-- A blocked delivery's re-checks so far, for BLOCKED_RECHECK's steps (§2.3).\n\t`rechecks` integer NOT NULL DEFAULT 0,\n\t`final_reason` text CHECK (`final_reason` IS NULL OR `final_reason` IN ('completed', 'superseded', 'discarded')),\n\t`last_code` text,\n\t`created_at` integer NOT NULL,\n\t`updated_at` integer NOT NULL,\n\tUNIQUE (`event_id`, `plugin`, `registration`)\n);\n--> statement-breakpoint\nCREATE INDEX `subscriber_obligation_due` ON `subscriber_obligation` (`state`, `next_attempt_at`);\n--> statement-breakpoint\nCREATE INDEX `subscriber_obligation_plugin` ON `subscriber_obligation` (`plugin`, `state`, `seq`);\n--> statement-breakpoint\nCREATE INDEX `subscriber_obligation_order` ON `subscriber_obligation` (`order_id`);\n--> statement-breakpoint\nCREATE INDEX `subscriber_obligation_blocked` ON `subscriber_obligation` (`blocked_on`) WHERE `state` = 'blocked';\n--> statement-breakpoint\nCREATE TRIGGER subscriber_obligation_guard BEFORE UPDATE ON subscriber_obligation BEGIN\n SELECT RAISE(ABORT, 'KEDGE_OBLIGATION_IMMUTABLE')\n WHERE NEW.seq IS NOT OLD.seq OR NEW.event_id IS NOT OLD.event_id OR NEW.plugin IS NOT OLD.plugin\n OR NEW.registration IS NOT OLD.registration OR NEW.order_id IS NOT OLD.order_id OR NEW.created_at IS NOT OLD.created_at;\n SELECT RAISE(ABORT, 'KEDGE_OBLIGATION_STATE')\n WHERE NEW.state IS NOT OLD.state AND NOT (\n (OLD.state = 'pending' AND NEW.state IN ('done', 'finalizing', 'failed', 'blocked'))\n OR (OLD.state = 'blocked' AND NEW.state IN ('pending', 'finalizing'))\n OR (OLD.state = 'failed' AND NEW.state IN ('pending', 'finalizing', 'done'))\n OR (OLD.state = 'finalizing' AND NEW.state = 'done'));\nEND;\n--> statement-breakpoint\nCREATE TRIGGER subscriber_obligation_no_delete BEFORE DELETE ON subscriber_obligation BEGIN\n SELECT RAISE(ABORT, 'KEDGE_OBLIGATION_IMMUTABLE');\nEND;\n--> statement-breakpoint\n-- A delivery's receipt, first in its completion batch: a second copy of one\n-- delivery fails here and writes nothing.\nCREATE TABLE `subscriber_delivery` (\n\t`seq` integer PRIMARY KEY NOT NULL REFERENCES `subscriber_obligation`(`seq`),\n\t`at` integer NOT NULL\n);\n--> statement-breakpoint\nCREATE TRIGGER subscriber_delivery_needs_open BEFORE INSERT ON subscriber_delivery BEGIN\n SELECT RAISE(ABORT, 'KEDGE_OBLIGATION_NOT_PENDING')\n WHERE (SELECT state FROM subscriber_obligation WHERE seq = NEW.seq) NOT IN ('pending', 'blocked', 'failed');\nEND;\n--> statement-breakpoint\n\n-- (4) Release deliveries join the relay (§2.6): its run columns, and staff's\n-- discard. Existing pending rows are due at once.\nALTER TABLE `release_obligation` ADD `next_attempt_at` integer NOT NULL DEFAULT 0;\n--> statement-breakpoint\nALTER TABLE `release_obligation` ADD `lease_token` text;\n--> statement-breakpoint\nALTER TABLE `release_obligation` ADD `run_token` text;\n--> statement-breakpoint\nALTER TABLE `release_obligation` ADD `last_code` text;\n--> statement-breakpoint\nCREATE INDEX `release_obligation_due` ON `release_obligation` (`state`, `next_attempt_at`);\n--> statement-breakpoint\nDROP TRIGGER release_obligation_guard;\n--> statement-breakpoint\nCREATE TRIGGER release_obligation_guard BEFORE UPDATE ON release_obligation BEGIN\n SELECT RAISE(ABORT, 'KEDGE_OBLIGATION_IMMUTABLE')\n WHERE NEW.plugin IS NOT OLD.plugin OR NEW.transition_uid IS NOT OLD.transition_uid OR NEW.order_id IS NOT OLD.order_id\n OR NEW.from_state IS NOT OLD.from_state OR NEW.to_state IS NOT OLD.to_state OR NEW.created_at IS NOT OLD.created_at;\n -- done is final; failed goes back to pending by a staff retry, or to done by a staff discard.\n SELECT RAISE(ABORT, 'KEDGE_OBLIGATION_STATE')\n WHERE NEW.state IS NOT OLD.state AND NOT (\n (OLD.state = 'pending' AND NEW.state IN ('done', 'failed')) OR (OLD.state = 'failed' AND NEW.state IN ('pending', 'done')));\nEND;\n--> statement-breakpoint\n-- The previous Worker's drain knows nothing of run tokens, so a delayed\n-- failure from it could fail a run a staff retry started since (codex r25).\n-- Its receipts are refused, so its success batch rolls back whole; its\n-- failure writes are skipped, so they change nothing and throw nothing.\nCREATE TRIGGER release_delivery_writer BEFORE INSERT ON release_delivery\n WHEN (SELECT writer FROM batch_marker WHERE id = 1) IS NULL BEGIN\n SELECT RAISE(ABORT, 'KEDGE_OBLIGATION_RELAYED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER release_obligation_writer BEFORE UPDATE OF state, attempts ON release_obligation\n WHEN (SELECT writer FROM batch_marker WHERE id = 1) IS NULL BEGIN\n SELECT RAISE(IGNORE);\nEND;\n--> statement-breakpoint\n\n-- (5) Staff resolutions of unresolved effects are deliveries too (§4.9), so a\n-- crash between D1 and the journal shard can't lose one.\nCREATE TABLE `effect_resolution` (\n\t`seq` integer PRIMARY KEY AUTOINCREMENT NOT NULL,\n\t`decision_id` text NOT NULL UNIQUE,\n\t`effect_key` text NOT NULL,\n\t`expected_revision` integer NOT NULL,\n\t`form` text NOT NULL CHECK (`form` IN ('observe', 'succeeded', 'failed_final')),\n\t`payload` text NOT NULL CHECK (json_valid(`payload`)),\n\t`mode` text NOT NULL DEFAULT 'apply' CHECK (`mode` IN ('apply', 'cancel')),\n\t`staff_id` text NOT NULL,\n\t`state` text NOT NULL CHECK (`state` IN ('pending', 'done', 'failed')),\n\t`attempts` integer NOT NULL DEFAULT 0 CHECK (typeof(`attempts`) = 'integer' AND `attempts` >= 0),\n\t`next_attempt_at` integer NOT NULL,\n\t`lease_token` text,\n\t`run_token` text,\n\t`last_code` text,\n\t`created_at` integer NOT NULL,\n\t`updated_at` integer NOT NULL\n);\n--> statement-breakpoint\nCREATE INDEX `effect_resolution_due` ON `effect_resolution` (`state`, `next_attempt_at`);\n--> statement-breakpoint\nCREATE TRIGGER effect_resolution_guard BEFORE UPDATE ON effect_resolution BEGIN\n SELECT RAISE(ABORT, 'KEDGE_OBLIGATION_IMMUTABLE')\n WHERE NEW.seq IS NOT OLD.seq OR NEW.decision_id IS NOT OLD.decision_id OR NEW.effect_key IS NOT OLD.effect_key\n OR NEW.expected_revision IS NOT OLD.expected_revision OR NEW.form IS NOT OLD.form OR NEW.payload IS NOT OLD.payload\n OR NEW.staff_id IS NOT OLD.staff_id OR NEW.created_at IS NOT OLD.created_at\n -- a discard turns a failed apply into a cancel, never back.\n OR (NEW.mode IS NOT OLD.mode AND NOT (OLD.mode = 'apply' AND NEW.mode = 'cancel' AND OLD.state = 'failed'));\n SELECT RAISE(ABORT, 'KEDGE_OBLIGATION_STATE')\n WHERE NEW.state IS NOT OLD.state AND NOT (\n (OLD.state = 'pending' AND NEW.state IN ('done', 'failed')) OR (OLD.state = 'failed' AND NEW.state = 'pending'));\nEND;\n--> statement-breakpoint\nCREATE TRIGGER effect_resolution_no_delete BEFORE DELETE ON effect_resolution BEGIN\n SELECT RAISE(ABORT, 'KEDGE_OBLIGATION_IMMUTABLE');\nEND;\n--> statement-breakpoint\nCREATE TABLE `resolution_delivery` (\n\t`seq` integer PRIMARY KEY NOT NULL REFERENCES `effect_resolution`(`seq`),\n\t`at` integer NOT NULL\n);\n--> statement-breakpoint\nCREATE TRIGGER resolution_delivery_needs_pending BEFORE INSERT ON resolution_delivery BEGIN\n SELECT RAISE(ABORT, 'KEDGE_OBLIGATION_NOT_PENDING')\n WHERE (SELECT state FROM effect_resolution WHERE seq = NEW.seq) IS NOT 'pending';\nEND;\n--> statement-breakpoint\n\n-- (6) Timers (§3). Rows are permanent: the generation never resets, and it is\n-- the timer's mutation version, raised by every change of state or schedule.\nCREATE TABLE `timer_owner` (\n\t`order_id` text PRIMARY KEY NOT NULL REFERENCES `order_index`(`id`),\n\t`timer_epoch` integer NOT NULL DEFAULT 0,\n\t`cancel_epoch` integer NOT NULL DEFAULT 0,\n\t`ordinary_n` integer NOT NULL DEFAULT 0,\n\t`ordinary_at` integer NOT NULL DEFAULT 0,\n\t`surviving_n` integer NOT NULL DEFAULT 0,\n\t`surviving_at` integer NOT NULL DEFAULT 0\n);\n--> statement-breakpoint\nCREATE TABLE `timer` (\n\t`seq` integer PRIMARY KEY AUTOINCREMENT NOT NULL,\n\t`id` text NOT NULL UNIQUE,\n\t`key` text NOT NULL UNIQUE,\n\t`plugin` text NOT NULL,\n\t`registration` text NOT NULL,\n\t`owner_kind` text NOT NULL CHECK (`owner_kind` = 'order'),\n\t`owner_id` text NOT NULL,\n\t`owning_order_id` text NOT NULL REFERENCES `timer_owner`(`order_id`),\n\t`generation` integer NOT NULL CHECK (typeof(`generation`) = 'integer' AND `generation` >= 1),\n\t`revision_id` text NOT NULL UNIQUE,\n\t`continued_from` text,\n\t`due_at` integer NOT NULL,\n\t`eligible_at` integer NOT NULL,\n\t`anchor_kind` text CHECK (`anchor_kind` IS NULL OR `anchor_kind` IN ('line_start', 'line_end')),\n\t`anchor_ref` text,\n\t`offset_ms` integer,\n\t`after_terminal` text CHECK (`after_terminal` IS NULL OR `after_terminal` = 'completed'),\n\t`epoch` integer NOT NULL,\n\t`state` text NOT NULL CHECK (`state` IN ('waiting', 'scheduled', 'fired', 'canceled')),\n\t`payload` text NOT NULL CHECK (json_valid(`payload`) AND length(`payload`) <= 1024),\n\t`fired_at` integer,\n\t`created_at` integer NOT NULL,\n\t`updated_at` integer NOT NULL,\n\tCHECK ((`anchor_kind` IS NULL) = (`anchor_ref` IS NULL) AND (`anchor_kind` IS NULL) = (`offset_ms` IS NULL))\n);\n--> statement-breakpoint\nCREATE INDEX `timer_due` ON `timer` (`state`, `due_at`);\n--> statement-breakpoint\nCREATE INDEX `timer_order` ON `timer` (`owning_order_id`, `state`);\n--> statement-breakpoint\nCREATE TABLE `timer_firing` (\n\t`revision_id` text PRIMARY KEY NOT NULL,\n\t`timer_seq` integer NOT NULL REFERENCES `timer`(`seq`),\n\t`owning_order_id` text NOT NULL REFERENCES `timer_owner`(`order_id`),\n\t`kind` text NOT NULL CHECK (`kind` IN ('ordinary', 'surviving')),\n\t`epoch` integer NOT NULL,\n\t`due_at` integer NOT NULL,\n\t`anchor_kind` text,\n\t`anchor_ref` text,\n\t`offset_ms` integer,\n\t`payload` text NOT NULL,\n\t`state` text NOT NULL CHECK (`state` IN ('open', 'delivered', 'obsolete', 'discarded')),\n\t`fired_at` integer NOT NULL,\n\t`closed_at` integer\n);\n--> statement-breakpoint\nCREATE INDEX `timer_firing_order` ON `timer_firing` (`owning_order_id`, `state`);\n--> statement-breakpoint\nCREATE TRIGGER timer_no_delete BEFORE DELETE ON timer BEGIN\n SELECT RAISE(ABORT, 'KEDGE_TIMER_IMMUTABLE');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER timer_firing_no_delete BEFORE DELETE ON timer_firing BEGIN\n SELECT RAISE(ABORT, 'KEDGE_TIMER_IMMUTABLE');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER timer_owner_no_delete BEFORE DELETE ON timer_owner BEGIN\n SELECT RAISE(ABORT, 'KEDGE_TIMER_IMMUTABLE');\nEND;\n--> statement-breakpoint\n-- A firing's snapshot is what its handler reads (codex r7): only its state\n-- moves, open to one closed state, once.\nCREATE TRIGGER timer_firing_guard BEFORE UPDATE ON timer_firing BEGIN\n SELECT RAISE(ABORT, 'KEDGE_TIMER_IMMUTABLE')\n WHERE NEW.revision_id IS NOT OLD.revision_id OR NEW.timer_seq IS NOT OLD.timer_seq\n OR NEW.owning_order_id IS NOT OLD.owning_order_id OR NEW.kind IS NOT OLD.kind OR NEW.epoch IS NOT OLD.epoch\n OR NEW.due_at IS NOT OLD.due_at OR NEW.anchor_kind IS NOT OLD.anchor_kind OR NEW.anchor_ref IS NOT OLD.anchor_ref\n OR NEW.offset_ms IS NOT OLD.offset_ms OR NEW.payload IS NOT OLD.payload OR NEW.fired_at IS NOT OLD.fired_at;\n SELECT RAISE(ABORT, 'KEDGE_TIMER_STATE')\n WHERE NEW.state IS NOT OLD.state AND NOT (OLD.state = 'open' AND NEW.state IN ('delivered', 'obsolete', 'discarded'));\nEND;\n--> statement-breakpoint\n-- Identity is permanent, and the owning order never changes (K10).\nCREATE TRIGGER timer_identity BEFORE UPDATE ON timer BEGIN\n SELECT RAISE(ABORT, 'KEDGE_TIMER_IMMUTABLE')\n WHERE NEW.seq IS NOT OLD.seq OR NEW.id IS NOT OLD.id OR NEW.key IS NOT OLD.key OR NEW.plugin IS NOT OLD.plugin\n OR NEW.registration IS NOT OLD.registration OR NEW.owner_kind IS NOT OLD.owner_kind OR NEW.owner_id IS NOT OLD.owner_id\n OR NEW.owning_order_id IS NOT OLD.owning_order_id OR NEW.after_terminal IS NOT OLD.after_terminal\n OR NEW.created_at IS NOT OLD.created_at;\n -- The mutation version only rises, and rises with every change but a firing.\n SELECT RAISE(ABORT, 'KEDGE_TIMER_GENERATION')\n WHERE NEW.generation < OLD.generation\n OR (NEW.generation = OLD.generation AND NOT (OLD.state = 'scheduled' AND NEW.state = 'fired')\n AND (NEW.state IS NOT OLD.state OR NEW.due_at IS NOT OLD.due_at OR NEW.revision_id IS NOT OLD.revision_id\n OR NEW.payload IS NOT OLD.payload)\n AND NOT (OLD.state = 'waiting' AND NEW.state = 'scheduled'));\nEND;\n--> statement-breakpoint\n-- Scheduling checks the owner and the plugin inside its own batch (§3.3): a\n-- cancel or a disable that committed first is seen. A `canceled` row is a\n-- cancel of a timer never scheduled (code r8), which is always allowed: it\n-- only records the generation a racing schedule's \"none\" must now miss.\nCREATE TRIGGER timer_insert_check BEFORE INSERT ON timer WHEN NEW.state IS NOT 'canceled' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_TIMER_OWNER_TERMINAL')\n WHERE (SELECT status FROM \"order\" WHERE id = NEW.owning_order_id) = 'canceled'\n OR ((SELECT status FROM \"order\" WHERE id = NEW.owning_order_id) = 'completed' AND NEW.after_terminal IS NULL);\n SELECT RAISE(ABORT, 'KEDGE_PLUGIN_INACTIVE')\n WHERE NEW.plugin IS NOT 'core'\n AND (SELECT state FROM plugin_state WHERE plugin = NEW.plugin) IS NOT 'active'\n AND NOT EXISTS (SELECT 1 FROM plugin_participation WHERE plugin = NEW.plugin AND order_id = NEW.owning_order_id);\n SELECT RAISE(ABORT, 'KEDGE_TIMER_STATE')\n WHERE NEW.state NOT IN ('waiting', 'scheduled')\n OR (NEW.state = 'waiting') IS NOT (NEW.after_terminal IS NOT NULL\n AND (SELECT status FROM \"order\" WHERE id = NEW.owning_order_id) IS NOT 'completed');\n SELECT RAISE(ABORT, 'KEDGE_TIMER_EPOCH')\n WHERE NEW.epoch IS NOT (SELECT CASE WHEN NEW.after_terminal IS NULL THEN timer_epoch ELSE cancel_epoch END\n FROM timer_owner WHERE order_id = NEW.owning_order_id);\nEND;\n--> statement-breakpoint\n-- A reschedule faces the owner's checks too; a revival of finished work (a\n-- canceled timer, or a fired one whose firing is closed) also needs an active\n-- or participating plugin, unless it continues the firing being delivered\n-- (codex r11, r12). A fired timer whose firing is still open is outstanding\n-- work, which a disabled plugin may still reschedule (code r7).\nCREATE TRIGGER timer_reschedule_check BEFORE UPDATE OF state, due_at, revision_id ON timer\n WHEN NEW.state IN ('scheduled', 'waiting') AND NOT (OLD.state = 'waiting' AND NEW.state = 'scheduled' AND NEW.revision_id IS OLD.revision_id) BEGIN\n SELECT RAISE(ABORT, 'KEDGE_TIMER_OWNER_TERMINAL')\n WHERE (SELECT status FROM \"order\" WHERE id = NEW.owning_order_id) = 'canceled'\n OR ((SELECT status FROM \"order\" WHERE id = NEW.owning_order_id) = 'completed' AND NEW.after_terminal IS NULL);\n SELECT RAISE(ABORT, 'KEDGE_PLUGIN_INACTIVE')\n WHERE NEW.plugin IS NOT 'core'\n AND (OLD.state = 'canceled'\n OR (OLD.state = 'fired' AND NEW.continued_from IS NOT OLD.revision_id\n AND NOT EXISTS (SELECT 1 FROM timer_firing f WHERE f.revision_id = OLD.revision_id AND f.state = 'open')))\n AND (SELECT state FROM plugin_state WHERE plugin = NEW.plugin) IS NOT 'active'\n AND NOT EXISTS (SELECT 1 FROM plugin_participation WHERE plugin = NEW.plugin AND order_id = NEW.owning_order_id);\n SELECT RAISE(ABORT, 'KEDGE_TIMER_EPOCH')\n WHERE NEW.epoch IS NOT (SELECT CASE WHEN NEW.after_terminal IS NULL THEN timer_epoch ELSE cancel_epoch END\n FROM timer_owner WHERE order_id = NEW.owning_order_id);\nEND;\n--> statement-breakpoint\nCREATE TRIGGER timer_insert_canceled BEFORE INSERT ON timer WHEN NEW.state = 'canceled' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_TIMER_STATE') WHERE NEW.generation IS NOT 1;\nEND;\n--> statement-breakpoint\n-- The per-order cap (§3.3): each counter is valid only for the epoch it\n-- records, so an epoch's rise resets it in the same one-row update. A\n-- canceled row isn't outstanding; its revival counts it (below).\nCREATE TRIGGER timer_count_insert AFTER INSERT ON timer WHEN NEW.state IS NOT 'canceled' BEGIN\n UPDATE timer_owner SET\n ordinary_n = CASE WHEN NEW.after_terminal IS NULL\n THEN (CASE WHEN ordinary_at = timer_epoch THEN ordinary_n ELSE 0 END) + 1 ELSE ordinary_n END,\n ordinary_at = CASE WHEN NEW.after_terminal IS NULL THEN timer_epoch ELSE ordinary_at END,\n surviving_n = CASE WHEN NEW.after_terminal IS NOT NULL\n THEN (CASE WHEN surviving_at = cancel_epoch THEN surviving_n ELSE 0 END) + 1 ELSE surviving_n END,\n surviving_at = CASE WHEN NEW.after_terminal IS NOT NULL THEN cancel_epoch ELSE surviving_at END\n WHERE order_id = NEW.owning_order_id;\n SELECT RAISE(ABORT, 'KEDGE_TIMER_CAP') FROM timer_owner\n WHERE order_id = NEW.owning_order_id\n AND (CASE WHEN ordinary_at = timer_epoch THEN ordinary_n ELSE 0 END)\n + (CASE WHEN surviving_at = cancel_epoch THEN surviving_n ELSE 0 END) > 64;\nEND;\n--> statement-breakpoint\n-- Back into the outstanding set: a reschedule after firing (the earlier\n-- firing stays open until its delivery ends) or a revival, as `scheduled`\n-- or, for a surviving timer before completion, `waiting`.\nCREATE TRIGGER timer_count_reschedule AFTER UPDATE OF state ON timer\n WHEN NEW.state IN ('scheduled', 'waiting') AND OLD.state IN ('fired', 'canceled') BEGIN\n UPDATE timer_owner SET\n ordinary_n = CASE WHEN NEW.after_terminal IS NULL\n THEN (CASE WHEN ordinary_at = timer_epoch THEN ordinary_n ELSE 0 END) + 1 ELSE ordinary_n END,\n ordinary_at = CASE WHEN NEW.after_terminal IS NULL THEN timer_epoch ELSE ordinary_at END,\n surviving_n = CASE WHEN NEW.after_terminal IS NOT NULL\n THEN (CASE WHEN surviving_at = cancel_epoch THEN surviving_n ELSE 0 END) + 1 ELSE surviving_n END,\n surviving_at = CASE WHEN NEW.after_terminal IS NOT NULL THEN cancel_epoch ELSE surviving_at END\n WHERE order_id = NEW.owning_order_id;\n SELECT RAISE(ABORT, 'KEDGE_TIMER_CAP') FROM timer_owner\n WHERE order_id = NEW.owning_order_id\n AND (CASE WHEN ordinary_at = timer_epoch THEN ordinary_n ELSE 0 END)\n + (CASE WHEN surviving_at = cancel_epoch THEN surviving_n ELSE 0 END) > 64;\nEND;\n--> statement-breakpoint\n-- Out of it: a cancel, or a dead timer's cleanup, lowers the counter only\n-- while the timer's epoch is still the counter's (a risen epoch already reset it).\nCREATE TRIGGER timer_count_cancel AFTER UPDATE OF state ON timer\n WHEN NEW.state = 'canceled' AND OLD.state IN ('waiting', 'scheduled') BEGIN\n UPDATE timer_owner SET ordinary_n = ordinary_n - 1\n WHERE order_id = NEW.owning_order_id AND OLD.after_terminal IS NULL\n AND ordinary_at = timer_epoch AND OLD.epoch = timer_epoch;\n UPDATE timer_owner SET surviving_n = surviving_n - 1\n WHERE order_id = NEW.owning_order_id AND OLD.after_terminal IS NOT NULL\n AND surviving_at = cancel_epoch AND OLD.epoch = cancel_epoch;\nEND;\n--> statement-breakpoint\nCREATE TRIGGER timer_count_firing_closed AFTER UPDATE OF state ON timer_firing\n WHEN OLD.state = 'open' AND NEW.state IN ('delivered', 'obsolete', 'discarded') BEGIN\n UPDATE timer_firing SET closed_at = CAST((julianday('now') - 2440587.5) * 86400000 AS INTEGER) WHERE revision_id = NEW.revision_id AND closed_at IS NULL;\n UPDATE timer_owner SET ordinary_n = ordinary_n - 1\n WHERE order_id = NEW.owning_order_id AND NEW.kind = 'ordinary' AND ordinary_at = timer_epoch AND NEW.epoch = timer_epoch;\n UPDATE timer_owner SET surviving_n = surviving_n - 1\n WHERE order_id = NEW.owning_order_id AND NEW.kind = 'surviving' AND surviving_at = cancel_epoch AND NEW.epoch = cancel_epoch;\nEND;\n--> statement-breakpoint\n-- Firing turns the revision into its event and its one delivery, atomically\n-- (K10, D-028): the firing's snapshot, `timer:<revision_id>`, and the\n-- obligation of the registration that scheduled it.\nCREATE TRIGGER timer_fired AFTER UPDATE OF state ON timer WHEN OLD.state = 'scheduled' AND NEW.state = 'fired' BEGIN\n INSERT INTO timer_firing (revision_id, timer_seq, owning_order_id, kind, epoch, due_at, anchor_kind, anchor_ref, offset_ms,\n payload, state, fired_at)\n VALUES (NEW.revision_id, NEW.seq, NEW.owning_order_id, CASE WHEN NEW.after_terminal IS NULL THEN 'ordinary' ELSE 'surviving' END,\n NEW.epoch, NEW.due_at, NEW.anchor_kind, NEW.anchor_ref, NEW.offset_ms, NEW.payload, 'open', NEW.fired_at);\n INSERT INTO outbox_event (id, aggregate_type, aggregate_id, type, payload, created_at)\n VALUES ('timer:' || NEW.revision_id, 'timer', NEW.id, 'timer.fired',\n json_object('timer_id', NEW.id, 'key', NEW.key, 'generation', NEW.generation, 'revision_id', NEW.revision_id),\n NEW.fired_at);\n INSERT INTO subscriber_obligation (event_id, plugin, registration, order_id, state, attempts, next_attempt_at, created_at, updated_at)\n VALUES ('timer:' || NEW.revision_id, NEW.plugin, NEW.registration, NEW.owning_order_id, 'pending', 0, NEW.fired_at,\n NEW.fired_at, NEW.fired_at);\nEND;\n--> statement-breakpoint\n\n-- (7) The order's terminal transitions (§3.3): epochs rise in one row with no\n-- scan, and the order's `waiting` timers (at most the cap) move with it.\nCREATE TRIGGER order_timers_canceled AFTER UPDATE OF status ON \"order\"\n WHEN NEW.status = 'canceled' AND OLD.status IS NOT 'canceled' BEGIN\n UPDATE timer_owner SET timer_epoch = timer_epoch + 1, cancel_epoch = cancel_epoch + 1 WHERE order_id = NEW.id;\n UPDATE timer SET state = 'canceled', generation = generation + 1, updated_at = CAST((julianday('now') - 2440587.5) * 86400000 AS INTEGER)\n WHERE owning_order_id = NEW.id AND state = 'waiting';\nEND;\n--> statement-breakpoint\nCREATE TRIGGER order_timers_completed AFTER UPDATE OF status ON \"order\"\n WHEN OLD.status = 'open' AND NEW.status = 'completed' BEGIN\n UPDATE timer_owner SET timer_epoch = timer_epoch + 1 WHERE order_id = NEW.id;\n UPDATE timer SET state = 'scheduled', updated_at = CAST((julianday('now') - 2440587.5) * 86400000 AS INTEGER)\n WHERE owning_order_id = NEW.id AND state = 'waiting';\nEND;\n--> statement-breakpoint\n\n-- (8) The order's other transitions get their events (§2.1), ids from the\n-- transition (each at most once per order, K1), payloads ids only.\nCREATE TRIGGER order_event_expired AFTER UPDATE OF status ON \"order\"\n WHEN OLD.status = 'pending_payment' AND NEW.status = 'expired' BEGIN\n INSERT INTO outbox_event (id, aggregate_type, aggregate_id, type, payload, created_at)\n VALUES ('evt_' || substr(NEW.id, 5) || '.expired', 'order', NEW.id, 'order.expired',\n json_object('order_id', NEW.id, 'transition_uid', NEW.id || ':expired'), CAST((julianday('now') - 2440587.5) * 86400000 AS INTEGER));\nEND;\n--> statement-breakpoint\nCREATE TRIGGER order_event_canceled AFTER UPDATE OF status ON \"order\"\n WHEN OLD.status IN ('pending_payment', 'open') AND NEW.status = 'canceled' BEGIN\n INSERT INTO outbox_event (id, aggregate_type, aggregate_id, type, payload, created_at)\n VALUES ('evt_' || substr(NEW.id, 5) || '.canceled', 'order', NEW.id, 'order.canceled',\n json_object('order_id', NEW.id, 'transition_uid', NEW.id || ':canceled'), CAST((julianday('now') - 2440587.5) * 86400000 AS INTEGER));\nEND;\n--> statement-breakpoint\nCREATE TRIGGER order_event_completed AFTER UPDATE OF status ON \"order\"\n WHEN OLD.status = 'open' AND NEW.status = 'completed' BEGIN\n INSERT INTO outbox_event (id, aggregate_type, aggregate_id, type, payload, created_at)\n VALUES ('evt_' || substr(NEW.id, 5) || '.completed', 'order', NEW.id, 'order.completed',\n json_object('order_id', NEW.id, 'transition_uid', NEW.id || ':completed'), CAST((julianday('now') - 2440587.5) * 86400000 AS INTEGER));\nEND;\n--> statement-breakpoint\nDROP INDEX `outbox_event_one_shot`;\n--> statement-breakpoint\nCREATE UNIQUE INDEX `outbox_event_one_shot` ON `outbox_event` (`aggregate_id`, `type`)\n WHERE `type` IN ('order.created', 'order.placed', 'order.expired', 'order.canceled', 'order.completed');\n--> statement-breakpoint\n\n-- (9) Every event's deliveries are written with it (§2.2): one per live\n-- subscription whose plugin is active, or durable, or took part in the\n-- event's order. A firing has its own (above). The event's order is its\n-- aggregate, or for a `booking` (a shortage) the order that booking is: a\n-- booking is its hold's order when it has one, else the hold (code r6), so\n-- a disabled plugin still hears of a shortage on an order it shaped, from\n-- this Worker's scan or the previous one's.\nCREATE TRIGGER outbox_obligations AFTER INSERT ON outbox_event WHEN NEW.type IS NOT 'timer.fired' BEGIN\n INSERT INTO subscriber_obligation (event_id, plugin, registration, order_id, state, attempts, next_attempt_at, created_at, updated_at)\n SELECT NEW.id, s.plugin, s.registration, owner.id, 'pending', 0, NEW.created_at, NEW.created_at, NEW.created_at\n FROM event_subscription s\n LEFT JOIN (SELECT o.id FROM \"order\" o\n WHERE o.id = NEW.aggregate_id AND NEW.aggregate_type IN ('order', 'booking')) owner\n WHERE s.event_type = NEW.type AND s.state = 'live'\n AND (s.plugin = 'core' OR s.durable = 1\n OR (SELECT state FROM plugin_state WHERE plugin = s.plugin) = 'active'\n OR EXISTS (SELECT 1 FROM plugin_participation pp WHERE pp.plugin = s.plugin AND pp.order_id = owner.id));\nEND;\n--> statement-breakpoint\n\n-- (10) Capacity events take batch-scoped ids (§2.1): unique per batch, one per\n-- row and batch. Not restore-stable; decision 11 says what that allows.\nDROP TRIGGER capacity_pattern_lowered;\n--> statement-breakpoint\nCREATE TRIGGER capacity_pattern_lowered AFTER INSERT ON capacity_pattern BEGIN\n INSERT OR IGNORE INTO outbox_event (id, aggregate_type, aggregate_id, type, payload, created_at)\n VALUES ('evt_' || (SELECT batch_id FROM batch_marker WHERE id = 1) || '.' || NEW.pool_id || ':' || NEW.slot_of_week || '.ceiling',\n 'capacity_pool', NEW.pool_id, 'pool.capacity_changed', json_object('pool_id', NEW.pool_id, 'slot_of_week', NEW.slot_of_week),\n CAST(unixepoch('subsec') * 1000 AS INTEGER));\nEND;\n--> statement-breakpoint\nDROP TRIGGER capacity_pattern_lowered_update;\n--> statement-breakpoint\nCREATE TRIGGER capacity_pattern_lowered_update AFTER UPDATE OF ceiling ON capacity_pattern WHEN NEW.ceiling < OLD.ceiling BEGIN\n INSERT OR IGNORE INTO outbox_event (id, aggregate_type, aggregate_id, type, payload, created_at)\n VALUES ('evt_' || (SELECT batch_id FROM batch_marker WHERE id = 1) || '.' || NEW.pool_id || ':' || NEW.slot_of_week || '.ceiling',\n 'capacity_pool', NEW.pool_id, 'pool.capacity_changed', json_object('pool_id', NEW.pool_id, 'slot_of_week', NEW.slot_of_week),\n CAST(unixepoch('subsec') * 1000 AS INTEGER));\nEND;\n--> statement-breakpoint\nDROP TRIGGER inventory_level_lowered;\n--> statement-breakpoint\nCREATE TRIGGER inventory_level_lowered AFTER UPDATE OF on_hand, safety_stock ON inventory_level\n WHEN NEW.on_hand < OLD.on_hand OR NEW.safety_stock > OLD.safety_stock BEGIN\n INSERT OR IGNORE INTO outbox_event (id, aggregate_type, aggregate_id, type, payload, created_at)\n VALUES ('evt_' || (SELECT batch_id FROM batch_marker WHERE id = 1) || '.' || NEW.inventory_item_id || ':' || NEW.location_id || '.level',\n 'inventory_level', NEW.inventory_item_id || ':' || NEW.location_id,\n 'level.availability_changed',\n json_object('inventory_item_id', NEW.inventory_item_id, 'location_id', NEW.location_id,\n 'on_hand', NEW.on_hand, 'safety_stock', NEW.safety_stock),\n CAST(unixepoch('subsec') * 1000 AS INTEGER));\nEND;\n--> statement-breakpoint\n\n-- (11) The journal shards are fenced stores from their first write (§4.4):\n-- the registry and the store-wide generation, `(r0, 0)` until P1.18 issues\n-- recovery ids; the ordered log exists, empty until P2.10 writes to it.\nCREATE TABLE `privacy_store` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`kind` text NOT NULL,\n\t`state` text NOT NULL CHECK (`state` IN ('bootstrapping', 'active', 'retired')),\n\t`acked_recovery_id` text,\n\t`acked_generation` integer,\n\t`created_at` integer NOT NULL\n);\n--> statement-breakpoint\nCREATE TABLE `privacy_generation` (\n\t`id` integer PRIMARY KEY NOT NULL CHECK (`id` = 1),\n\t`recovery_id` text NOT NULL,\n\t`n` integer NOT NULL\n);\n--> statement-breakpoint\nCREATE TABLE `privacy_change` (\n\t`recovery_id` text NOT NULL,\n\t`generation` integer NOT NULL,\n\t`kind` text NOT NULL,\n\t`subject_id` text NOT NULL,\n\t`scope` text,\n\t`fields` text,\n\t`created_at` integer NOT NULL,\n\tPRIMARY KEY (`recovery_id`, `generation`)\n);\n--> statement-breakpoint\n-- Activation asserts the store copied the current generation (customer-erasure.md §3.0).\nCREATE TRIGGER privacy_store_activate BEFORE UPDATE OF state ON privacy_store\n WHEN NEW.state = 'active' AND OLD.state = 'bootstrapping' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_PRIVACY_STALE')\n WHERE NOT EXISTS (SELECT 1 FROM privacy_generation g\n WHERE g.id = 1 AND g.recovery_id = NEW.acked_recovery_id AND g.n = NEW.acked_generation);\nEND;\n--> statement-breakpoint\nCREATE TRIGGER privacy_store_no_delete BEFORE DELETE ON privacy_store BEGIN\n SELECT RAISE(ABORT, 'KEDGE_PRIVACY_STORE');\nEND;\n--> statement-breakpoint\n\n-- (12) Fences for the new tables (release p0.5, as every core table).\nCREATE TRIGGER fence_event_subscription_insert BEFORE INSERT ON \"event_subscription\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_event_subscription_update BEFORE UPDATE ON \"event_subscription\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_event_subscription_delete BEFORE DELETE ON \"event_subscription\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_subscriber_obligation_insert BEFORE INSERT ON \"subscriber_obligation\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_subscriber_obligation_update BEFORE UPDATE ON \"subscriber_obligation\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_subscriber_obligation_delete BEFORE DELETE ON \"subscriber_obligation\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_subscriber_delivery_insert BEFORE INSERT ON \"subscriber_delivery\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_subscriber_delivery_update BEFORE UPDATE ON \"subscriber_delivery\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_subscriber_delivery_delete BEFORE DELETE ON \"subscriber_delivery\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_effect_resolution_insert BEFORE INSERT ON \"effect_resolution\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_effect_resolution_update BEFORE UPDATE ON \"effect_resolution\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_effect_resolution_delete BEFORE DELETE ON \"effect_resolution\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_resolution_delivery_insert BEFORE INSERT ON \"resolution_delivery\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_resolution_delivery_update BEFORE UPDATE ON \"resolution_delivery\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_resolution_delivery_delete BEFORE DELETE ON \"resolution_delivery\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_timer_owner_insert BEFORE INSERT ON \"timer_owner\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_timer_owner_update BEFORE UPDATE ON \"timer_owner\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_timer_owner_delete BEFORE DELETE ON \"timer_owner\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_timer_insert BEFORE INSERT ON \"timer\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_timer_update BEFORE UPDATE ON \"timer\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_timer_delete BEFORE DELETE ON \"timer\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_timer_firing_insert BEFORE INSERT ON \"timer_firing\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_timer_firing_update BEFORE UPDATE ON \"timer_firing\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_timer_firing_delete BEFORE DELETE ON \"timer_firing\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_privacy_store_insert BEFORE INSERT ON \"privacy_store\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_privacy_store_update BEFORE UPDATE ON \"privacy_store\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_privacy_store_delete BEFORE DELETE ON \"privacy_store\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_privacy_generation_insert BEFORE INSERT ON \"privacy_generation\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_privacy_generation_update BEFORE UPDATE ON \"privacy_generation\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_privacy_generation_delete BEFORE DELETE ON \"privacy_generation\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_privacy_change_insert BEFORE INSERT ON \"privacy_change\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_privacy_change_update BEFORE UPDATE ON \"privacy_change\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_privacy_change_delete BEFORE DELETE ON \"privacy_change\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\n\n-- (13) The migration's own writes, under its marker.\nINSERT INTO `batch_marker` (`id`, `release`, `cause`, `batch_id`, `actor`, `writer`)\n VALUES (1, 'p0.5', 'migration:0026', 'bat_migration_0026', 'system', 'p0.6a');\n--> statement-breakpoint\nINSERT INTO `privacy_generation` (`id`, `recovery_id`, `n`) VALUES (1, 'r0', 0);\n--> statement-breakpoint\nINSERT INTO `privacy_store` (`id`, `kind`, `state`, `created_at`)\n WITH RECURSIVE s(n) AS (SELECT 0 UNION ALL SELECT n + 1 FROM s WHERE n < 16 - 1)\n SELECT 'journal:' || n, 'journal_shard', 'bootstrapping', CAST((julianday('now') - 2440587.5) * 86400000 AS INTEGER) FROM s;\n--> statement-breakpoint\n-- Core's own subscription (§2.7): shortage discovery, per change.\nINSERT INTO `event_subscription` (`plugin`, `registration`, `event_type`, `durable`, `state`, `generation`, `created_at`) VALUES\n ('core', 'core.shortage', 'pool.capacity_changed', 1, 'live', 1, CAST((julianday('now') - 2440587.5) * 86400000 AS INTEGER)),\n ('core', 'core.shortage', 'level.availability_changed', 1, 'live', 1, CAST((julianday('now') - 2440587.5) * 86400000 AS INTEGER));\n--> statement-breakpoint\n-- The shortage handoff (§2.7, codex r15): constant work here; the clock\n-- backfills the cron's unfinished backlog from this cursor in bounded pages.\nINSERT INTO `maintenance_cursor` (`name`, `cursor`, `updated_at`)\n SELECT 'shortage_handoff',\n json_object('next', coalesce((SELECT json_extract(cursor, '$.seq') FROM maintenance_cursor WHERE name = 'shortage'), 0),\n 'page', (SELECT json_extract(cursor, '$.page') FROM maintenance_cursor WHERE name = 'shortage'),\n 'end', coalesce((SELECT max(seq) FROM shortage_queue), 0)),\n CAST((julianday('now') - 2440587.5) * 86400000 AS INTEGER);\n--> statement-breakpoint\nDELETE FROM `batch_marker`;\n","migrations/0027_webhooks_calendar.sql":"-- P0.6b: webhooks out, the business calendar and preview isolation's D1 side\n-- (docs/proposals/p0.6b-webhooks-calendar-preview.md, D-062). Rendered from\n-- packages/db/sql like 0026; render.test.ts compares. N−1 compatible (§5): the\n-- P0.6a Worker touches none of the new tables, and the fence stays at p0.5.\n\n-- (1) Endpoints (§2.1). A deleted endpoint is kept for its log, and is final.\nCREATE TABLE `webhook_endpoint` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`url` text NOT NULL CHECK (`url` LIKE 'https://%' AND length(`url`) <= 2048),\n\t`url_hash` text NOT NULL,\n\t`description` text CHECK (`description` IS NULL OR length(`description`) <= 200),\n\t`status` text NOT NULL CHECK (`status` IN ('active', 'disabled', 'deleted')),\n\t`generation` integer NOT NULL DEFAULT 1 CHECK (typeof(`generation`) = 'integer' AND `generation` >= 1),\n\t`disabled_reason` text CHECK (`disabled_reason` IS NULL OR `disabled_reason` IN ('failing', 'staff')),\n\t`disabled_at` integer,\n\t`deleted_at` integer,\n\t`failure_streak` integer NOT NULL DEFAULT 0 CHECK (typeof(`failure_streak`) = 'integer' AND `failure_streak` >= 0),\n\t`api_version` text NOT NULL,\n\t`created_by` text NOT NULL,\n\t`created_at` integer NOT NULL,\n\t`updated_at` integer NOT NULL\n);\n--> statement-breakpoint\n-- The active-endpoint bound counts through this index, so it reads at most\n-- the bound's rows however many disabled and deleted endpoints are kept (codex r10).\nCREATE INDEX `webhook_endpoint_active` ON `webhook_endpoint` (`id`) WHERE `status` = 'active';\n--> statement-breakpoint\n-- The staff list pages one status by id through this index (codex code r1).\nCREATE INDEX `webhook_endpoint_status` ON `webhook_endpoint` (`status`, `id`);\n--> statement-breakpoint\nCREATE TRIGGER webhook_endpoint_cap BEFORE INSERT ON webhook_endpoint WHEN NEW.status = 'active' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_WEBHOOK_ENDPOINTS_MAX')\n WHERE (SELECT count(*) FROM webhook_endpoint WHERE status = 'active') >= 16;\nEND;\n--> statement-breakpoint\nCREATE TRIGGER webhook_endpoint_guard BEFORE UPDATE ON webhook_endpoint BEGIN\n SELECT RAISE(ABORT, 'KEDGE_WEBHOOK_IMMUTABLE')\n WHERE NEW.id IS NOT OLD.id OR NEW.created_by IS NOT OLD.created_by OR NEW.created_at IS NOT OLD.created_at\n OR NEW.generation < OLD.generation;\n -- Deleted is final (§2.5): nothing about a deleted endpoint changes again.\n SELECT RAISE(ABORT, 'KEDGE_WEBHOOK_DELETED') WHERE OLD.status = 'deleted';\n SELECT RAISE(ABORT, 'KEDGE_WEBHOOK_ENDPOINTS_MAX')\n WHERE NEW.status = 'active' AND OLD.status IS NOT 'active'\n AND (SELECT count(*) FROM webhook_endpoint WHERE status = 'active') >= 16;\n -- A disable, an enable, a delete or a URL change starts a new generation (§2.5).\n SELECT RAISE(ABORT, 'KEDGE_WEBHOOK_GENERATION')\n WHERE (NEW.status IS NOT OLD.status OR NEW.url IS NOT OLD.url) AND NEW.generation <= OLD.generation;\nEND;\n--> statement-breakpoint\nCREATE TRIGGER webhook_endpoint_no_delete BEFORE DELETE ON webhook_endpoint BEGIN\n SELECT RAISE(ABORT, 'KEDGE_WEBHOOK_IMMUTABLE');\nEND;\n--> statement-breakpoint\n\n-- (2) Grants: the endpoint's own, unscoped only (§2.1).\nCREATE TABLE `webhook_endpoint_grant` (\n\t`endpoint_id` text NOT NULL REFERENCES `webhook_endpoint`(`id`),\n\t`permission` text NOT NULL,\n\tPRIMARY KEY (`endpoint_id`, `permission`)\n) WITHOUT ROWID;\n--> statement-breakpoint\n\n-- (3) Selections (§2.1, §2.6): bounded per type and per endpoint, never for a\n-- deleted endpoint, whose selections its delete batch removes (codex r9).\nCREATE TABLE `webhook_endpoint_event` (\n\t`endpoint_id` text NOT NULL REFERENCES `webhook_endpoint`(`id`),\n\t`event_type` text NOT NULL CHECK (`event_type` IN ('order.placed', 'order.expired', 'order.canceled', 'order.completed', 'stock.shortage', 'capacity.base_negative', 'webhook_endpoint.disabled')),\n\tPRIMARY KEY (`endpoint_id`, `event_type`)\n) WITHOUT ROWID;\n--> statement-breakpoint\nCREATE INDEX `webhook_endpoint_event_type` ON `webhook_endpoint_event` (`event_type`);\n--> statement-breakpoint\n-- A selection the endpoint already holds adds no row, so the caps don't\n-- count it: re-saving unchanged selections at a cap succeeds (codex code r1).\nCREATE TRIGGER webhook_endpoint_event_insert BEFORE INSERT ON webhook_endpoint_event BEGIN\n SELECT RAISE(ABORT, 'KEDGE_WEBHOOK_DELETED')\n WHERE coalesce((SELECT status FROM webhook_endpoint WHERE id = NEW.endpoint_id), 'deleted') = 'deleted';\n SELECT RAISE(ABORT, 'KEDGE_WEBHOOK_ENDPOINTS_PER_EVENT')\n WHERE NOT EXISTS (SELECT 1 FROM webhook_endpoint_event WHERE endpoint_id = NEW.endpoint_id AND event_type = NEW.event_type)\n AND (SELECT count(*) FROM webhook_endpoint_event WHERE event_type = NEW.event_type) >= 16;\n SELECT RAISE(ABORT, 'KEDGE_WEBHOOK_EVENTS_PER_ENDPOINT')\n WHERE NOT EXISTS (SELECT 1 FROM webhook_endpoint_event WHERE endpoint_id = NEW.endpoint_id AND event_type = NEW.event_type)\n AND (SELECT count(*) FROM webhook_endpoint_event WHERE endpoint_id = NEW.endpoint_id) >= 64;\nEND;\n--> statement-breakpoint\nCREATE TRIGGER webhook_endpoint_event_no_update BEFORE UPDATE ON webhook_endpoint_event BEGIN\n SELECT RAISE(ABORT, 'KEDGE_WEBHOOK_IMMUTABLE');\nEND;\n--> statement-breakpoint\n\n-- (4) Signing secrets (§2.7): one row each, sealed under the keyring's active\n-- `auth` version; a superseded one lives its 24 hours; at most three live.\nCREATE TABLE `webhook_secret` (\n\t`endpoint_id` text NOT NULL REFERENCES `webhook_endpoint`(`id`),\n\t`n` integer NOT NULL CHECK (typeof(`n`) = 'integer' AND `n` >= 1),\n\t`sealed` text NOT NULL,\n\t`seal_version` integer NOT NULL CHECK (typeof(`seal_version`) = 'integer' AND `seal_version` >= 1),\n\t`created_at` integer NOT NULL,\n\t`retires_at` integer,\n\tPRIMARY KEY (`endpoint_id`, `n`)\n) WITHOUT ROWID;\n--> statement-breakpoint\nCREATE INDEX `webhook_secret_retiring` ON `webhook_secret` (`retires_at`) WHERE `retires_at` IS NOT NULL;\n--> statement-breakpoint\nCREATE INDEX `webhook_secret_seal` ON `webhook_secret` (`seal_version`);\n--> statement-breakpoint\nCREATE TRIGGER webhook_secret_insert BEFORE INSERT ON webhook_secret BEGIN\n SELECT RAISE(ABORT, 'KEDGE_KEYRING_VERSION')\n WHERE NEW.seal_version IS NOT (SELECT active FROM keyring_setting WHERE name = 'auth');\n SELECT RAISE(ABORT, 'KEDGE_ROTATION_LIMIT')\n WHERE (SELECT count(*) FROM webhook_secret\n WHERE endpoint_id = NEW.endpoint_id AND (retires_at IS NULL OR retires_at > CAST((julianday('now') - 2440587.5) * 86400000 AS INTEGER))) >= 3;\nEND;\n--> statement-breakpoint\nCREATE TRIGGER webhook_secret_guard BEFORE UPDATE ON webhook_secret BEGIN\n SELECT RAISE(ABORT, 'KEDGE_WEBHOOK_IMMUTABLE')\n WHERE NEW.endpoint_id IS NOT OLD.endpoint_id OR NEW.n IS NOT OLD.n OR NEW.created_at IS NOT OLD.created_at\n OR (OLD.retires_at IS NOT NULL AND NEW.retires_at IS NOT OLD.retires_at);\n -- A re-seal moves a secret to the active version only (§2.7).\n SELECT RAISE(ABORT, 'KEDGE_KEYRING_VERSION')\n WHERE (NEW.sealed IS NOT OLD.sealed OR NEW.seal_version IS NOT OLD.seal_version)\n AND NEW.seal_version IS NOT (SELECT active FROM keyring_setting WHERE name = 'auth');\nEND;\n--> statement-breakpoint\n-- The database's guard, so an old Worker's retirement can't complete while a\n-- webhook secret still needs the retiring version (codex r2).\nCREATE TRIGGER keyring_auth_webhook_secrets BEFORE UPDATE OF retiring ON keyring_setting\n WHEN NEW.name = 'auth' AND OLD.retiring IS NOT NULL AND NEW.retiring IS NULL BEGIN\n SELECT RAISE(ABORT, 'KEDGE_KEYRING_IN_USE')\n WHERE EXISTS (SELECT 1 FROM webhook_secret WHERE seal_version = OLD.retiring);\nEND;\n--> statement-breakpoint\n\n-- (5) Deliveries (§2.2–§2.5): written with their event, one per endpoint, a\n-- relay delivery of kind `whk`. Replays and tests sit beside the fan-out.\nCREATE TABLE `webhook_delivery` (\n\t`seq` integer PRIMARY KEY AUTOINCREMENT NOT NULL,\n\t`id` text NOT NULL UNIQUE,\n\t`origin` text NOT NULL CHECK (`origin` IN ('fanout', 'replay', 'test')),\n\t`event_id` text NOT NULL REFERENCES `outbox_event`(`id`),\n\t`event_type` text NOT NULL,\n\t`endpoint_id` text NOT NULL REFERENCES `webhook_endpoint`(`id`),\n\t`endpoint_generation` integer NOT NULL CHECK (typeof(`endpoint_generation`) = 'integer' AND `endpoint_generation` >= 1),\n\t`state` text NOT NULL CHECK (`state` IN ('pending', 'finalizing', 'done', 'failed')),\n\t`attempts` integer NOT NULL DEFAULT 0 CHECK (typeof(`attempts`) = 'integer' AND `attempts` >= 0),\n\t`next_attempt_at` integer NOT NULL,\n\t`lease_token` text,\n\t`run_token` text,\n\t`last_code` text,\n\t`last_status` integer CHECK (`last_status` IS NULL OR (typeof(`last_status`) = 'integer' AND `last_status` BETWEEN 100 AND 599)),\n\t`final_reason` text CHECK (`final_reason` IS NULL OR `final_reason` IN ('completed', 'superseded', 'discarded')),\n\t`end_code` text,\n\t`replaced_by` text,\n\t`waits_for` text,\n\t`created_at` integer NOT NULL,\n\t`updated_at` integer NOT NULL\n);\n--> statement-breakpoint\nCREATE UNIQUE INDEX `webhook_delivery_fanout` ON `webhook_delivery` (`event_id`, `endpoint_id`) WHERE `origin` = 'fanout';\n--> statement-breakpoint\n-- Every query over deliveries has a driving index and a row bound (§2.2, codex r6–r8, r11).\nCREATE INDEX `webhook_delivery_due` ON `webhook_delivery` (`state`, `next_attempt_at`);\n--> statement-breakpoint\nCREATE INDEX `webhook_delivery_done` ON `webhook_delivery` (`updated_at`) WHERE `state` = 'done';\n--> statement-breakpoint\nCREATE INDEX `webhook_delivery_endpoint` ON `webhook_delivery` (`endpoint_id`, `event_type`, `seq`);\n--> statement-breakpoint\nCREATE INDEX `webhook_delivery_failed` ON `webhook_delivery` (`state`, `event_type`, `seq`);\n--> statement-breakpoint\n-- The conflict list's seeks (codex code r3): failed rows only, by code then\n-- seq, so a page never reads the failures that match no code.\nCREATE INDEX `webhook_delivery_failed_code` ON `webhook_delivery` (`event_type`, `last_code`, `seq`) WHERE `state` = 'failed';\n--> statement-breakpoint\nCREATE INDEX `subscriber_obligation_failed_code` ON `subscriber_obligation` (`last_code`, `seq`) WHERE `state` = 'failed';\n--> statement-breakpoint\nCREATE INDEX `release_obligation_failed_code` ON `release_obligation` (`last_code`, `seq`) WHERE `state` = 'failed';\n--> statement-breakpoint\nCREATE INDEX `effect_resolution_failed_code` ON `effect_resolution` (`last_code`, `seq`) WHERE `state` = 'failed';\n--> statement-breakpoint\nCREATE INDEX `webhook_delivery_endpoint_state` ON `webhook_delivery` (`endpoint_id`, `state`, `event_type`, `seq`);\n--> statement-breakpoint\nCREATE INDEX `webhook_delivery_latest` ON `webhook_delivery` (`endpoint_id`, `seq`);\n--> statement-breakpoint\nCREATE TRIGGER webhook_delivery_guard BEFORE UPDATE ON webhook_delivery BEGIN\n SELECT RAISE(ABORT, 'KEDGE_OBLIGATION_IMMUTABLE')\n WHERE NEW.seq IS NOT OLD.seq OR NEW.id IS NOT OLD.id OR NEW.origin IS NOT OLD.origin OR NEW.event_id IS NOT OLD.event_id\n OR NEW.event_type IS NOT OLD.event_type OR NEW.endpoint_id IS NOT OLD.endpoint_id\n OR NEW.endpoint_generation IS NOT OLD.endpoint_generation OR NEW.created_at IS NOT OLD.created_at\n OR (OLD.replaced_by IS NOT NULL AND NEW.replaced_by IS NOT OLD.replaced_by)\n OR (NEW.waits_for IS NOT NULL AND NEW.waits_for IS NOT OLD.waits_for);\n SELECT RAISE(ABORT, 'KEDGE_OBLIGATION_STATE')\n WHERE NEW.state IS NOT OLD.state AND NOT (\n (OLD.state = 'pending' AND NEW.state IN ('finalizing', 'failed'))\n OR (OLD.state = 'failed' AND NEW.state = 'finalizing')\n OR (OLD.state = 'finalizing' AND NEW.state = 'done'));\nEND;\n--> statement-breakpoint\nCREATE TRIGGER webhook_delivery_no_delete BEFORE DELETE ON webhook_delivery\n WHEN OLD.state IS NOT 'done' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_OBLIGATION_IMMUTABLE');\nEND;\n--> statement-breakpoint\n-- The fan-out (§2.2): one delivery per active endpoint selecting the type, at\n-- its generation, in the event's own batch. `webhook.test` is never fanned\n-- out, and a disabled endpoint's own notice goes to the others only.\nCREATE TRIGGER outbox_webhooks AFTER INSERT ON outbox_event WHEN NEW.type IN ('order.placed', 'order.expired', 'order.canceled', 'order.completed', 'stock.shortage', 'capacity.base_negative', 'webhook_endpoint.disabled') BEGIN\n INSERT INTO webhook_delivery (id, origin, event_id, event_type, endpoint_id, endpoint_generation, state, attempts,\n next_attempt_at, created_at, updated_at)\n SELECT 'whd_' || NEW.id || '.' || e.id, 'fanout', NEW.id, NEW.type, e.id, e.generation, 'pending', 0,\n NEW.created_at, NEW.created_at, NEW.created_at\n FROM webhook_endpoint_event s JOIN webhook_endpoint e ON e.id = s.endpoint_id\n WHERE s.event_type = NEW.type AND e.status = 'active'\n AND NOT (NEW.type = 'webhook_endpoint.disabled' AND e.id = NEW.aggregate_id);\nEND;\n--> statement-breakpoint\n-- Health follows from the delivery's own transitions, whichever path made\n-- them, fenced by the endpoint's generation (§2.5, codex r2, r5). A failure\n-- counts unless its code is Kedge's own; the 15th disables, in one statement.\nCREATE TRIGGER webhook_delivery_failed AFTER UPDATE OF state ON webhook_delivery\n WHEN OLD.state = 'pending' AND NEW.state = 'failed'\n AND coalesce(NEW.last_code, '') NOT IN ('body_too_large', 'effect_key_conflict', 'effect_key_completed_conflict')\n AND coalesce(NEW.last_code, '') NOT LIKE 'local\\_%' ESCAPE '\\' BEGIN\n UPDATE webhook_endpoint\n SET failure_streak = failure_streak + 1,\n status = CASE WHEN failure_streak + 1 >= 15 THEN 'disabled' ELSE status END,\n disabled_reason = CASE WHEN failure_streak + 1 >= 15 THEN 'failing' ELSE disabled_reason END,\n disabled_at = CASE WHEN failure_streak + 1 >= 15 THEN CAST((julianday('now') - 2440587.5) * 86400000 AS INTEGER) ELSE disabled_at END,\n generation = CASE WHEN failure_streak + 1 >= 15 THEN generation + 1 ELSE generation END,\n updated_at = CAST((julianday('now') - 2440587.5) * 86400000 AS INTEGER)\n WHERE id = NEW.endpoint_id AND status = 'active' AND generation = NEW.endpoint_generation;\nEND;\n--> statement-breakpoint\nCREATE TRIGGER webhook_delivery_completed AFTER UPDATE OF state ON webhook_delivery\n WHEN OLD.state = 'pending' AND NEW.state = 'finalizing' AND NEW.final_reason = 'completed' BEGIN\n UPDATE webhook_endpoint SET failure_streak = 0, updated_at = CAST((julianday('now') - 2440587.5) * 86400000 AS INTEGER)\n WHERE id = NEW.endpoint_id AND status = 'active' AND generation = NEW.endpoint_generation AND failure_streak <> 0;\nEND;\n--> statement-breakpoint\n-- The notice and the event come from the winning transition only (§2.5).\nCREATE TRIGGER webhook_endpoint_disabled AFTER UPDATE OF status ON webhook_endpoint\n WHEN OLD.status = 'active' AND NEW.status = 'disabled' AND NEW.disabled_reason = 'failing' BEGIN\n INSERT INTO admin_notice (kind, subject_id, created_at) VALUES ('webhook_disabled', NEW.id, CAST((julianday('now') - 2440587.5) * 86400000 AS INTEGER));\n INSERT OR IGNORE INTO outbox_event (id, aggregate_type, aggregate_id, type, payload, created_at)\n VALUES ('evt_' || (SELECT batch_id FROM batch_marker WHERE id = 1) || '.' || NEW.id || '.disabled',\n 'webhook_endpoint', NEW.id, 'webhook_endpoint.disabled',\n json_object('endpoint_id', NEW.id, 'reason', NEW.disabled_reason, 'disabled_at', NEW.disabled_at,\n 'generation', NEW.generation),\n CAST((julianday('now') - 2440587.5) * 86400000 AS INTEGER));\nEND;\n--> statement-breakpoint\n\n-- (6) The attempt log (§2.4): one row per journal dispatch, written by the\n-- port before its send and completed after. Evidence for staff, never state.\nCREATE TABLE `webhook_attempt` (\n\t`delivery_seq` integer NOT NULL REFERENCES `webhook_delivery`(`seq`),\n\t`dispatch` integer NOT NULL CHECK (typeof(`dispatch`) = 'integer' AND `dispatch` >= 1),\n\t`started_at` integer NOT NULL,\n\t`body_hash` text NOT NULL,\n\t`body_bytes` integer NOT NULL CHECK (typeof(`body_bytes`) = 'integer' AND `body_bytes` >= 0),\n\t`outcome` text NOT NULL CHECK (`outcome` IN ('sending', 'answered', 'transport_error')),\n\t`status` integer CHECK (`status` IS NULL OR (typeof(`status`) = 'integer' AND `status` BETWEEN 100 AND 599)),\n\t`code` text,\n\t`duration_ms` integer CHECK (`duration_ms` IS NULL OR (typeof(`duration_ms`) = 'integer' AND `duration_ms` >= 0)),\n\tPRIMARY KEY (`delivery_seq`, `dispatch`)\n) WITHOUT ROWID;\n--> statement-breakpoint\nCREATE INDEX `webhook_attempt_age` ON `webhook_attempt` (`started_at`);\n--> statement-breakpoint\nCREATE TRIGGER webhook_attempt_guard BEFORE UPDATE ON webhook_attempt BEGIN\n SELECT RAISE(ABORT, 'KEDGE_WEBHOOK_IMMUTABLE')\n WHERE NEW.delivery_seq IS NOT OLD.delivery_seq OR NEW.dispatch IS NOT OLD.dispatch OR NEW.started_at IS NOT OLD.started_at\n OR NEW.body_hash IS NOT OLD.body_hash OR NEW.body_bytes IS NOT OLD.body_bytes\n OR NOT (OLD.outcome = 'sending' AND NEW.outcome IN ('answered', 'transport_error'));\nEND;\n--> statement-breakpoint\n\n-- (7) Staff notices (§2.5): the admin Home's list.\nCREATE TABLE `admin_notice` (\n\t`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,\n\t`kind` text NOT NULL CHECK (`kind` IN ('webhook_disabled')),\n\t`subject_id` text NOT NULL,\n\t`created_at` integer NOT NULL,\n\t`dismissed_at` integer\n);\n--> statement-breakpoint\nCREATE INDEX `admin_notice_open` ON `admin_notice` (`created_at`) WHERE `dismissed_at` IS NULL;\n--> statement-breakpoint\n\n-- (8) The business calendar (§3.1): working days and a cutoff, and holidays in\n-- a window. The window's check is on UTC days with a day's slack each side;\n-- the store's own code holds the exact local window.\nCREATE TABLE `store_calendar` (\n\t`id` integer PRIMARY KEY NOT NULL CHECK (`id` = 1),\n\t`working_days` integer NOT NULL DEFAULT 31\n\t\tCHECK (typeof(`working_days`) = 'integer' AND `working_days` BETWEEN 1 AND 127),\n\t`cutoff_minutes` integer\n\t\tCHECK (`cutoff_minutes` IS NULL OR (typeof(`cutoff_minutes`) = 'integer' AND `cutoff_minutes` BETWEEN 0 AND 1439)),\n\t`version` integer NOT NULL DEFAULT 1 CHECK (typeof(`version`) = 'integer' AND `version` >= 1),\n\t`updated_at` integer NOT NULL\n);\n--> statement-breakpoint\nCREATE TABLE `store_holiday` (\n\t`day` text PRIMARY KEY NOT NULL CHECK (date(`day`) IS `day`),\n\t`name` text NOT NULL CHECK (length(`name`) BETWEEN 1 AND 80)\n) WITHOUT ROWID;\n--> statement-breakpoint\nCREATE TRIGGER store_holiday_insert BEFORE INSERT ON store_holiday BEGIN\n SELECT RAISE(ABORT, 'KEDGE_CALENDAR_DAY_OUT_OF_RANGE')\n WHERE NEW.day < date('now', '-30 days', '-1 day')\n OR NEW.day > date('now', '+1095 days', '+1 day');\n -- A rename (the upsert's conflict) adds no row, so a full calendar allows it (codex code r1).\n SELECT RAISE(ABORT, 'KEDGE_CALENDAR_HOLIDAYS_MAX')\n WHERE NOT EXISTS (SELECT 1 FROM store_holiday WHERE day = NEW.day)\n AND (SELECT count(*) FROM store_holiday) >= 500;\nEND;\n--> statement-breakpoint\nCREATE TRIGGER store_holiday_day BEFORE UPDATE OF day ON store_holiday BEGIN\n SELECT RAISE(ABORT, 'KEDGE_CALENDAR_IMMUTABLE');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER store_calendar_guard BEFORE UPDATE ON store_calendar BEGIN\n SELECT RAISE(ABORT, 'KEDGE_CALENDAR_IMMUTABLE') WHERE NEW.version <= OLD.version;\nEND;\n--> statement-breakpoint\n-- A batch-scoped id, never the version a restore rewinds (codex r8); one event per batch.\nCREATE TRIGGER store_calendar_changed AFTER UPDATE OF version ON store_calendar BEGIN\n INSERT OR IGNORE INTO outbox_event (id, aggregate_type, aggregate_id, type, payload, created_at)\n VALUES ('evt_' || (SELECT batch_id FROM batch_marker WHERE id = 1) || '.calendar',\n 'store_calendar', '1', 'store_calendar.changed', json_object('version', NEW.version), CAST((julianday('now') - 2440587.5) * 86400000 AS INTEGER));\nEND;\n--> statement-breakpoint\n\n-- The calendar's read surface (§3.2): a plugin reads it with its claim\n-- through `calendarReads()`, so a hook needs no D1 request of its own.\nCREATE VIEW kedge_v1_calendar AS SELECT working_days, cutoff_minutes, version FROM store_calendar WHERE id = 1;\n--> statement-breakpoint\nCREATE VIEW kedge_v1_holiday AS SELECT day, name FROM store_holiday;\n--> statement-breakpoint\n\n-- (9) `order.placed` carries no email (§2.6): it is read from the order.\n-- The two triggers are 0023's, with `email` left out of the payload.\nDROP TRIGGER order_place;\n--> statement-breakpoint\nCREATE TRIGGER order_place AFTER UPDATE OF status ON \"order\"\n WHEN OLD.status = 'pending_payment' AND NEW.status = 'open' BEGIN\n DELETE FROM pending_expiry WHERE order_id = NEW.id;\n UPDATE sequence SET value = value + 1 WHERE name = 'order_number';\n UPDATE \"order\" SET number = (SELECT value FROM sequence WHERE name = 'order_number'),\n placed_at = CAST(unixepoch('subsec') * 1000 AS INTEGER) WHERE id = NEW.id;\n INSERT INTO outbox_event (id, aggregate_type, aggregate_id, type, payload, created_at)\n SELECT 'evt_' || substr(o.id, 5) || '.placed', 'order', o.id, 'order.placed',\n json_object('id', o.id, 'number', o.number, 'status', o.status, 'currency_code', o.currency_code,\n 'subtotal', o.subtotal, 'total', o.total, 'revenue_total', o.revenue_total, 'deposit_total', o.deposit_total,\n 'placed_at', o.placed_at,\n 'lines', (SELECT json_group_array(json_object('id', l.id, 'variant_id', l.variant_id, 'sku', l.sku, 'title', l.title,\n 'unit_price', l.unit_price, 'quantity', l.quantity))\n FROM order_line l WHERE l.order_id = o.id)),\n o.placed_at\n FROM \"order\" o WHERE o.id = NEW.id;\n INSERT INTO transition (uid, aggregate, aggregate_id, machine, from_state, to_state, at, batch_cause)\n VALUES (NEW.id || ':placed', 'order', NEW.id, 'status', OLD.status, NEW.status, CAST(unixepoch('subsec') * 1000 AS INTEGER),\n (SELECT cause FROM batch_marker WHERE id = 1));\nEND;\n--> statement-breakpoint\nDROP TRIGGER order_reopen;\n--> statement-breakpoint\nCREATE TRIGGER order_reopen AFTER UPDATE OF status ON \"order\"\n WHEN OLD.status = 'expired' AND NEW.status = 'open' BEGIN\n UPDATE inventory_reservation SET released_at = NULL WHERE order_id = NEW.id AND released_at IS NOT NULL;\n SELECT RAISE(ABORT, 'KEDGE_RESERVATIONS_MISSING')\n WHERE (SELECT count(*) FROM inventory_reservation WHERE order_id = NEW.id AND released_at IS NULL) != NEW.reservation_count;\n -- Each hold is re-checked as it comes back: a resold day refuses the reopen (§3.4).\n UPDATE capacity_hold SET released_at = NULL WHERE order_id = NEW.id AND released_at IS NOT NULL;\n SELECT RAISE(ABORT, 'KEDGE_RESERVATIONS_MISSING')\n WHERE (SELECT count(*) FROM capacity_hold WHERE order_id = NEW.id AND released_at IS NULL) != NEW.hold_count;\n DELETE FROM reopenable_hold_pool WHERE order_id = NEW.id;\n UPDATE \"order\" SET current_alloc_cost = ((SELECT count(*) FROM inventory_reservation ir WHERE ir.order_id = NEW.id AND ir.released_at IS NULL) + (SELECT coalesce(sum(ch.last_bucket - ch.first_bucket + 1), 0) FROM capacity_hold ch WHERE ch.order_id = NEW.id AND ch.released_at IS NULL)) WHERE id = NEW.id;\n UPDATE sequence SET value = value + 1 WHERE name = 'order_number';\n UPDATE \"order\" SET number = (SELECT value FROM sequence WHERE name = 'order_number'),\n placed_at = CAST(unixepoch('subsec') * 1000 AS INTEGER) WHERE id = NEW.id;\n INSERT INTO outbox_event (id, aggregate_type, aggregate_id, type, payload, created_at)\n SELECT 'evt_' || substr(o.id, 5) || '.placed', 'order', o.id, 'order.placed',\n json_object('id', o.id, 'number', o.number, 'status', o.status, 'currency_code', o.currency_code,\n 'subtotal', o.subtotal, 'total', o.total, 'revenue_total', o.revenue_total, 'deposit_total', o.deposit_total,\n 'placed_at', o.placed_at,\n 'lines', (SELECT json_group_array(json_object('id', l.id, 'variant_id', l.variant_id, 'sku', l.sku, 'title', l.title,\n 'unit_price', l.unit_price, 'quantity', l.quantity))\n FROM order_line l WHERE l.order_id = o.id)),\n o.placed_at\n FROM \"order\" o WHERE o.id = NEW.id;\n INSERT INTO transition (uid, aggregate, aggregate_id, machine, from_state, to_state, at, batch_cause)\n VALUES (NEW.id || ':placed', 'order', NEW.id, 'status', OLD.status, NEW.status, CAST(unixepoch('subsec') * 1000 AS INTEGER),\n (SELECT cause FROM batch_marker WHERE id = 1));\nEND;\n--> statement-breakpoint\n\n-- (10) Fences for the new tables (release p0.5, as every core table).\nCREATE TRIGGER fence_webhook_endpoint_insert BEFORE INSERT ON \"webhook_endpoint\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_webhook_endpoint_update BEFORE UPDATE ON \"webhook_endpoint\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_webhook_endpoint_delete BEFORE DELETE ON \"webhook_endpoint\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_webhook_endpoint_grant_insert BEFORE INSERT ON \"webhook_endpoint_grant\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_webhook_endpoint_grant_update BEFORE UPDATE ON \"webhook_endpoint_grant\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_webhook_endpoint_grant_delete BEFORE DELETE ON \"webhook_endpoint_grant\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_webhook_endpoint_event_insert BEFORE INSERT ON \"webhook_endpoint_event\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_webhook_endpoint_event_update BEFORE UPDATE ON \"webhook_endpoint_event\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_webhook_endpoint_event_delete BEFORE DELETE ON \"webhook_endpoint_event\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_webhook_secret_insert BEFORE INSERT ON \"webhook_secret\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_webhook_secret_update BEFORE UPDATE ON \"webhook_secret\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_webhook_secret_delete BEFORE DELETE ON \"webhook_secret\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_webhook_delivery_insert BEFORE INSERT ON \"webhook_delivery\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_webhook_delivery_update BEFORE UPDATE ON \"webhook_delivery\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_webhook_delivery_delete BEFORE DELETE ON \"webhook_delivery\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_webhook_attempt_insert BEFORE INSERT ON \"webhook_attempt\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_webhook_attempt_update BEFORE UPDATE ON \"webhook_attempt\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_webhook_attempt_delete BEFORE DELETE ON \"webhook_attempt\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_admin_notice_insert BEFORE INSERT ON \"admin_notice\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_admin_notice_update BEFORE UPDATE ON \"admin_notice\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_admin_notice_delete BEFORE DELETE ON \"admin_notice\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_store_calendar_insert BEFORE INSERT ON \"store_calendar\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_store_calendar_update BEFORE UPDATE ON \"store_calendar\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_store_calendar_delete BEFORE DELETE ON \"store_calendar\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_store_holiday_insert BEFORE INSERT ON \"store_holiday\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_store_holiday_update BEFORE UPDATE ON \"store_holiday\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_store_holiday_delete BEFORE DELETE ON \"store_holiday\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\n\n-- (11) The migration's own writes, under its marker.\nINSERT INTO `batch_marker` (`id`, `release`, `cause`, `batch_id`, `actor`, `writer`)\n VALUES (1, 'p0.5', 'migration:0027', 'bat_migration_0027', 'system', 'p0.6a');\n--> statement-breakpoint\nINSERT INTO `store_calendar` (`id`, `working_days`, `cutoff_minutes`, `version`, `updated_at`) VALUES (1, 31, NULL, 1, CAST((julianday('now') - 2440587.5) * 86400000 AS INTEGER));\n--> statement-breakpoint\nDELETE FROM `batch_marker`;\n","migrations/0028_tracks_entities.sql":"-- P0.7: tracks and the plugin lifecycle's D1 side\n-- (docs/proposals/p0.7-tracks-entities.md, D-063). Rendered from\n-- packages/db/sql like 0027; render.test.ts compares. N−1 compatible (§6):\n-- the P0.6b Worker writes none of the new tables, its order rows leave the\n-- new columns NULL or 0, which pass while no plugin declares checkout tracks,\n-- and the fence stays at p0.5. Plugin entities' tables are composed by\n-- `kedge generate` per plugin (§4.3), not here.\n\n-- (1) History: a track move's actor, and a seek per subject and machine (§3.2, §3.8).\nALTER TABLE `transition` ADD `actor` text;\n--> statement-breakpoint\nCREATE INDEX `transition_machine` ON `transition` (`aggregate`, `aggregate_id`, `machine`, `id`);\n--> statement-breakpoint\n\n-- (2) A move's comment, classed pii, apart from the immutable transition so\n-- erasure (P2.10) can reach it (§3.2, decision 8).\nCREATE TABLE `transition_note` (\n\t`transition_id` integer PRIMARY KEY NOT NULL REFERENCES `transition`(`id`),\n\t`body` text NOT NULL CHECK (length(`body`) BETWEEN 1 AND 1000)\n);\n--> statement-breakpoint\nCREATE TRIGGER transition_note_immutable BEFORE UPDATE ON transition_note BEGIN\n SELECT RAISE(ABORT, 'KEDGE_TRANSITION_IMMUTABLE');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER transition_note_no_delete BEFORE DELETE ON transition_note BEGIN\n SELECT RAISE(ABORT, 'KEDGE_TRANSITION_IMMUTABLE');\nEND;\n--> statement-breakpoint\n\n-- (3) Declaration fingerprints (§3.3): the plugin's tracks, and the store's\n-- checkout-started set, both set by `kedge generate`'s composed migrations.\nALTER TABLE `plugin_state` ADD `tracks_sha` text;\n--> statement-breakpoint\nCREATE TABLE `checkout_tracks` (\n\t`id` integer PRIMARY KEY NOT NULL CHECK (`id` = 1),\n\t`sha` text\n);\n--> statement-breakpoint\nALTER TABLE `order` ADD `tracks_sha` text;\n--> statement-breakpoint\nALTER TABLE `order` ADD `blocking_tracks` integer NOT NULL DEFAULT 0;\n--> statement-breakpoint\n-- A checkout planned by a Worker that doesn't know the store's current\n-- checkout-track set commits no order: it would skip a track for good. An\n-- older Worker writes NULL, which matches only while no plugin has one.\nCREATE TRIGGER order_tracks_generation BEFORE INSERT ON \"order\"\n WHEN NEW.tracks_sha IS NOT (SELECT sha FROM checkout_tracks WHERE id = 1) BEGIN\n SELECT RAISE(ABORT, 'KEDGE_TRACKS_GENERATION');\nEND;\n--> statement-breakpoint\n-- Sealed with the order: the write-once guard names every column but\n-- status (P0.3 §1.7), the two new ones included.\nDROP TRIGGER order_write_once;\n--> statement-breakpoint\nCREATE TRIGGER order_write_once BEFORE UPDATE ON \"order\" BEGIN\n SELECT RAISE(ABORT, 'KEDGE_ORDER_IMMUTABLE') WHERE\n NEW.id IS NOT OLD.id OR NEW.cart_id IS NOT OLD.cart_id OR NEW.attempt_id IS NOT OLD.attempt_id\n OR NEW.currency_code IS NOT OLD.currency_code OR NEW.email IS NOT OLD.email\n OR NEW.subtotal IS NOT OLD.subtotal OR NEW.discount_total IS NOT OLD.discount_total\n OR NEW.shipping_total IS NOT OLD.shipping_total OR NEW.fee_total IS NOT OLD.fee_total\n OR NEW.tax_total IS NOT OLD.tax_total OR NEW.total IS NOT OLD.total\n OR NEW.payment_status IS NOT OLD.payment_status OR NEW.reservation_count IS NOT OLD.reservation_count\n OR NEW.expires_at IS NOT OLD.expires_at OR NEW.created_at IS NOT OLD.created_at\n OR NEW.tax_inclusive IS NOT OLD.tax_inclusive OR NEW.tax_rate_revenue_ppm IS NOT OLD.tax_rate_revenue_ppm\n OR NEW.tax_rate_deposit_ppm IS NOT OLD.tax_rate_deposit_ppm OR NEW.locale IS NOT OLD.locale\n OR NEW.place_by IS NOT OLD.place_by OR NEW.custom IS NOT OLD.custom\n OR NEW.hold_count IS NOT OLD.hold_count\n -- P0.5: the owning customer, copied from the attached cart at commit (§6.2).\n OR NEW.customer_id IS NOT OLD.customer_id\n -- P0.7: the checkout-track set and blocking count the commit planned with (§3.3, §3.7).\n OR NEW.tracks_sha IS NOT OLD.tracks_sha OR NEW.blocking_tracks IS NOT OLD.blocking_tracks\n OR ((NEW.revenue_total IS NOT OLD.revenue_total OR NEW.deposit_total IS NOT OLD.deposit_total)\n AND (OLD.revenue_total IS NOT NULL OR OLD.deposit_total IS NOT NULL\n OR EXISTS (SELECT 1 FROM contribution_check WHERE order_id = OLD.id)))\n OR ((NEW.number IS NOT OLD.number OR NEW.placed_at IS NOT OLD.placed_at)\n AND (OLD.number IS NOT NULL OR OLD.placed_at IS NOT NULL OR NEW.status IS NOT 'open'\n OR NEW.number IS NULL OR NEW.placed_at IS NULL))\n OR (NEW.expired_at IS NOT OLD.expired_at AND (OLD.expired_at IS NOT NULL OR NEW.status IS NOT 'expired'))\n -- reopenable: only 1 → 0, only while expired.\n OR (NEW.reopenable IS NOT OLD.reopenable AND NOT (OLD.reopenable = 1 AND NEW.reopenable = 0 AND OLD.status = 'expired'))\n -- release_cost: only before the seal, only to its rebuild value.\n OR (NEW.release_cost IS NOT OLD.release_cost AND (EXISTS (SELECT 1 FROM contribution_check WHERE order_id = OLD.id)\n OR NEW.release_cost IS NOT ((SELECT count(*) FROM inventory_reservation ir WHERE ir.order_id = OLD.id) + (SELECT coalesce(sum(ch.last_bucket - ch.first_bucket + 1), 0) FROM capacity_hold ch WHERE ch.order_id = OLD.id))))\n -- current_alloc_cost: only to its rebuild value.\n OR (NEW.current_alloc_cost IS NOT OLD.current_alloc_cost AND NEW.current_alloc_cost IS NOT ((SELECT count(*) FROM inventory_reservation ir WHERE ir.order_id = OLD.id AND ir.released_at IS NULL) + (SELECT coalesce(sum(ch.last_bucket - ch.first_bucket + 1), 0) FROM capacity_hold ch WHERE ch.order_id = OLD.id AND ch.released_at IS NULL)));\n SELECT RAISE(ABORT, 'KEDGE_ORDER_UNSEALED') WHERE NEW.status IS NOT OLD.status\n AND NOT EXISTS (SELECT 1 FROM contribution_check WHERE order_id = OLD.id);\nEND;\n--> statement-breakpoint\n\n-- (4) Track state (§3.2): one row per subject and track, never deleted.\nCREATE TABLE `track_state` (\n\t`seq` integer PRIMARY KEY AUTOINCREMENT NOT NULL,\n\t`id` text NOT NULL UNIQUE,\n\t`plugin` text NOT NULL,\n\t`track` text NOT NULL,\n\t`aggregate` text NOT NULL,\n\t`aggregate_id` text NOT NULL,\n\t`order_id` text REFERENCES `order_index`(`id`),\n\t`state` text NOT NULL,\n\t`version` integer NOT NULL CHECK (typeof(`version`) = 'integer' AND `version` >= 1),\n\t`terminal` integer NOT NULL CHECK (`terminal` IN (0, 1)),\n\t`blocks_completion` integer NOT NULL CHECK (`blocks_completion` IN (0, 1)),\n\t`decl_sha` text NOT NULL,\n\t`started_by` text NOT NULL,\n\t`created_at` integer NOT NULL,\n\t`updated_at` integer NOT NULL,\n\tCHECK (`blocks_completion` = 0 OR `order_id` IS NOT NULL),\n\tUNIQUE (`aggregate`, `aggregate_id`, `plugin`, `track`)\n);\n--> statement-breakpoint\nCREATE INDEX `track_state_order` ON `track_state` (`order_id`, `plugin`);\n--> statement-breakpoint\n-- Completion's check reads only unfinished blocking rows (§3.7).\nCREATE INDEX `track_state_blocking` ON `track_state` (`order_id`) WHERE `blocks_completion` = 1 AND `terminal` = 0;\n--> statement-breakpoint\nCREATE TRIGGER track_state_insert BEFORE INSERT ON track_state BEGIN\n SELECT RAISE(ABORT, 'KEDGE_TRACK_VERSION_STEP') WHERE NEW.version IS NOT 1;\n -- Written by a Worker that knows the plugin's current tracks (§3.3).\n SELECT RAISE(ABORT, 'KEDGE_TRACKS_GENERATION')\n WHERE NEW.decl_sha IS NOT (SELECT tracks_sha FROM plugin_state WHERE plugin = NEW.plugin);\n -- A disabled plugin starts no new roots, except on an order it took part in (§5.2).\n SELECT RAISE(ABORT, 'KEDGE_PLUGIN_INACTIVE:' || NEW.plugin)\n WHERE (SELECT state FROM plugin_state WHERE plugin = NEW.plugin) IS NOT 'active'\n AND (NEW.order_id IS NULL\n OR NOT EXISTS (SELECT 1 FROM plugin_participation WHERE plugin = NEW.plugin AND order_id = NEW.order_id));\n SELECT RAISE(ABORT, 'KEDGE_TRACK_ORDER_COMPLETED')\n WHERE NEW.blocks_completion = 1 AND (SELECT status FROM \"order\" WHERE id = NEW.order_id) = 'completed';\nEND;\n--> statement-breakpoint\nCREATE TRIGGER track_state_update BEFORE UPDATE ON track_state BEGIN\n SELECT RAISE(ABORT, 'KEDGE_TRACK_IMMUTABLE')\n WHERE NEW.seq IS NOT OLD.seq OR NEW.id IS NOT OLD.id OR NEW.plugin IS NOT OLD.plugin OR NEW.track IS NOT OLD.track\n OR NEW.aggregate IS NOT OLD.aggregate OR NEW.aggregate_id IS NOT OLD.aggregate_id OR NEW.order_id IS NOT OLD.order_id\n OR NEW.blocks_completion IS NOT OLD.blocks_completion OR NEW.started_by IS NOT OLD.started_by\n OR NEW.created_at IS NOT OLD.created_at;\n SELECT RAISE(ABORT, 'KEDGE_TRACK_VERSION_STEP') WHERE NEW.version IS NOT OLD.version + 1;\n SELECT RAISE(ABORT, 'KEDGE_TRACKS_GENERATION')\n WHERE NEW.decl_sha IS NOT (SELECT tracks_sha FROM plugin_state WHERE plugin = NEW.plugin);\n SELECT RAISE(ABORT, 'KEDGE_TRACK_ORDER_COMPLETED')\n WHERE NEW.blocks_completion = 1 AND NEW.terminal = 0\n AND (SELECT status FROM \"order\" WHERE id = NEW.order_id) = 'completed';\nEND;\n--> statement-breakpoint\nCREATE TRIGGER track_state_no_delete BEFORE DELETE ON track_state BEGIN\n SELECT RAISE(ABORT, 'KEDGE_TRACK_IMMUTABLE');\nEND;\n--> statement-breakpoint\n\n-- (5) Completion waits for every blocking track to be terminal (§3.7), and\n-- the commit's seal proves it wrote every blocking row it planned.\nCREATE TRIGGER order_completion_tracks BEFORE UPDATE OF status ON \"order\"\n WHEN OLD.status = 'open' AND NEW.status = 'completed' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_ORDER_TRACKS')\n WHERE EXISTS (SELECT 1 FROM track_state WHERE order_id = NEW.id AND blocks_completion = 1 AND terminal = 0);\nEND;\n--> statement-breakpoint\nCREATE TRIGGER contribution_check_tracks BEFORE INSERT ON contribution_check BEGIN\n SELECT RAISE(ABORT, 'KEDGE_TRACKS_MISSING')\n WHERE (SELECT blocking_tracks FROM \"order\" WHERE id = NEW.order_id)\n IS NOT (SELECT count(*) FROM track_state WHERE order_id = NEW.order_id AND blocks_completion = 1);\nEND;\n--> statement-breakpoint\n\n-- (6) The read view guards and plugin reads use (§3.4).\nCREATE VIEW kedge_v1_track AS\n SELECT plugin, track, aggregate, aggregate_id, order_id, state, version, terminal FROM track_state;\n--> statement-breakpoint\n\n-- (7) A delivery's committed mints, written with its receipt (§3.5): what\n-- finalization closes the obligation with. NULL on rows from before P0.7.\nALTER TABLE `subscriber_delivery` ADD `mints` text CHECK (`mints` IS NULL OR json_valid(`mints`));\n--> statement-breakpoint\n\n-- (8) The owning plugin always hears of its own tracks' moves (§3.9): a\n-- standalone track's continuation is existing work a disable must not drop.\nDROP TRIGGER outbox_obligations;\n--> statement-breakpoint\nCREATE TRIGGER outbox_obligations AFTER INSERT ON outbox_event WHEN NEW.type IS NOT 'timer.fired' BEGIN\n INSERT INTO subscriber_obligation (event_id, plugin, registration, order_id, state, attempts, next_attempt_at, created_at, updated_at)\n SELECT NEW.id, s.plugin, s.registration, owner.id, 'pending', 0, NEW.created_at, NEW.created_at, NEW.created_at\n FROM event_subscription s\n LEFT JOIN (SELECT o.id FROM \"order\" o\n WHERE o.id = NEW.aggregate_id AND NEW.aggregate_type IN ('order', 'booking')) owner\n WHERE s.event_type = NEW.type AND s.state = 'live'\n AND (s.plugin = 'core' OR s.durable = 1\n OR (SELECT state FROM plugin_state WHERE plugin = s.plugin) = 'active'\n OR EXISTS (SELECT 1 FROM plugin_participation pp WHERE pp.plugin = s.plugin AND pp.order_id = owner.id)\n OR (NEW.type = 'track.transitioned' AND s.plugin = json_extract(NEW.payload, '$.plugin')));\nEND;\n--> statement-breakpoint\n\n-- (9) Fences for the new tables (release p0.5, as every core table).\nCREATE TRIGGER fence_transition_note_insert BEFORE INSERT ON \"transition_note\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_transition_note_update BEFORE UPDATE ON \"transition_note\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_transition_note_delete BEFORE DELETE ON \"transition_note\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_checkout_tracks_insert BEFORE INSERT ON \"checkout_tracks\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_checkout_tracks_update BEFORE UPDATE ON \"checkout_tracks\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_checkout_tracks_delete BEFORE DELETE ON \"checkout_tracks\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_track_state_insert BEFORE INSERT ON \"track_state\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_track_state_update BEFORE UPDATE ON \"track_state\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\nCREATE TRIGGER fence_track_state_delete BEFORE DELETE ON \"track_state\"\n WHEN (SELECT release FROM batch_marker WHERE id = 1) IS NOT 'p0.5' BEGIN\n SELECT RAISE(ABORT, 'KEDGE_FENCED');\nEND;\n--> statement-breakpoint\n\n-- (10) The migration's own writes, under its marker.\nINSERT INTO `batch_marker` (`id`, `release`, `cause`, `batch_id`, `actor`, `writer`)\n VALUES (1, 'p0.5', 'migration:0028', 'bat_migration_0028', 'system', 'p0.6a');\n--> statement-breakpoint\nINSERT INTO `checkout_tracks` (`id`, `sha`) VALUES (1, NULL);\n--> statement-breakpoint\nDELETE FROM `batch_marker`;\n","package.json":"{\n \"name\": \"@kedge/starter\",\n \"version\": \"0.0.0\",\n \"private\": true,\n \"type\": \"module\",\n \"packageManager\": \"pnpm@11.11.0\",\n \"description\": \"The thin app a merchant deploys: config, entry point, migrations, tests and the rules for coding agents.\",\n \"scripts\": {\n \"check\": \"kedge check\",\n \"dev\": \"kedge dev\",\n \"generate\": \"kedge generate\",\n \"test\": \"vitest run\",\n \"typecheck\": \"tsc --noEmit -p .\"\n },\n \"dependencies\": {\n \"@kedge/admin\": \"workspace:*\",\n \"@kedge/plugin-sdk\": \"workspace:*\",\n \"@kedge/runtime-cf\": \"workspace:*\"\n },\n \"devDependencies\": {\n \"@cloudflare/vitest-plugin\": \"1.4.0\",\n \"@cloudflare/workers-types\": \"5.20261024.1\",\n \"@kedge/cli\": \"workspace:*\",\n \"@kedge/docs\": \"workspace:*\",\n \"@kedge/testing\": \"workspace:*\",\n \"@types/node\": \"24.19.2\",\n \"typescript\": \"7.0.2\",\n \"vitest\": \"4.1.12\",\n \"wrangler\": \"4.152.0\"\n }\n}\n","renovate.json":"{\n \"$schema\": \"https://docs.renovatebot.com/renovate-schema.json\",\n \"extends\": [\"config:recommended\"],\n \"packageRules\": [\n {\n \"description\": \"Kedge's packages move together; kedge upgrade does the rest.\",\n \"matchPackageNames\": [\"@kedge/**\"],\n \"groupName\": \"kedge\",\n \"rangeStrategy\": \"pin\"\n }\n ]\n}\n","src/index.ts":"// The merchant's entry point (docs/02): Kedge comes in as packages, so\n// upgrading is a version bump. The Durable Object classes must be exported here.\nimport { createKedge } from \"@kedge/runtime-cf\";\nimport config from \"../kedge.config\";\n// The store's pinned timezone rules, written by `kedge generate` (p0.4 §4).\nimport { tz } from \"./kedge.tz\";\n\nconst kedge = createKedge(config, { tz });\nexport const { CartDO, ClockDO, JournalDO } = kedge;\n// A preview binds it for its capture sinks (p0.8 §5.2); production doesn't, so here it's inert.\nexport { SyntheticProviderDO } from \"@kedge/runtime-cf\";\nexport default kedge.handler;\n","src/kedge.tz.ts":"// Generated by kedge generate: UTC, the store's default until a zone is configured, pinned (p0.4 §4).\n// Never edit: the database holds the same rows, and the checkout claim compares versions.\nimport type { TzTable } from \"@kedge/runtime-cf\";\n\nexport const tz: TzTable = {\n zone: \"UTC\",\n version: \"utc\",\n initialOffset: 0,\n at: [],\n offsets: [],\n};\n","src/overrides/.gitkeep":"","src/plugins/.gitkeep":"","test/apply-migrations.ts":"import { applyD1Migrations, env } from \"cloudflare:test\";\n\nawait applyD1Migrations(env.DB, env.TEST_MIGRATIONS);\n","test/env.d.ts":"declare namespace Cloudflare {\n interface Env {\n DB: D1Database;\n TEST_MIGRATIONS: import(\"cloudflare:test\").D1Migration[];\n }\n}\n","test/scenarios/checkout.http.ts":"// Runs against this store's preview (`kedge preview`), over HTTP, with the\n// preview's real relay and cron (p0.8 §5.4). Add one per requirement.\nimport { concurrently, credentialsFromEnv, httpScenario } from \"@kedge/testing/http\";\nimport { it } from \"vitest\";\n\nconst s = httpScenario(credentialsFromEnv());\n\nit(\"sells the last unit once\", async () => {\n const p = await s.product({ prices: { USD: 1500 }, stock: 1 });\n const results = await concurrently(\n () => s.buyer().checkout([{ variantId: p.variantId }]),\n () => s.buyer().checkout([{ variantId: p.variantId }]),\n );\n s.expectExactlyOne(results, \"placed\");\n s.expectError(results, \"OUT_OF_STOCK\");\n await s.expectInvariants();\n});\n","test/store.test.ts":"// This store's own test (docs/proposals/p0.8-cli-agent-kit.md §4.4): real\n// checkouts through this store's composed migrations and kedge.config.ts, its\n// plugins included, with every invariant checked after each test. Add a\n// scenario here for each requirement you build; this one fails a broken\n// composition from the first commit.\nimport { env } from \"cloudflare:test\";\nimport { describeKedgeInvariants, scenario } from \"@kedge/testing\";\nimport { describe, expect, it } from \"vitest\";\nimport config from \"../kedge.config\";\nimport { tz } from \"../src/kedge.tz\";\n\ndescribeKedgeInvariants(() => env.DB);\n\ndescribe(\"this store\", () => {\n it(\"places an order, holds its stock, and refuses the last unit twice\", async () => {\n const s = scenario({ db: env.DB, config, tz });\n const shirt = await s.product({ price: 2500, stock: 3 });\n const out = await s.checkout(\"shopper@example.com\", [{ variantId: shirt, quantity: 2 }]);\n expect(out.order.total).toBe(5000);\n expect(out.requests).toBe(2);\n const last = await s.checkout(\"next@example.com\", [{ variantId: shirt, quantity: 1 }]);\n expect(last.order.total).toBe(2500);\n await expect(s.checkout(\"late@example.com\", [{ variantId: shirt, quantity: 1 }])).rejects.toMatchObject({\n code: \"OUT_OF_STOCK\",\n });\n });\n\n it(\"returns an expired order's stock\", async () => {\n const s = scenario({ db: env.DB, config, tz });\n const mug = await s.product({ price: 900, stock: 1 });\n const out = await s.checkout(\"shopper@example.com\", [{ variantId: mug }]);\n await s.expire(out.order.id);\n const again = await s.checkout(\"second@example.com\", [{ variantId: mug }]);\n expect(again.order.total).toBe(900);\n });\n});\n","tsconfig.json":"{\n \"compilerOptions\": {\n \"target\": \"es2023\",\n \"module\": \"es2022\",\n \"moduleResolution\": \"bundler\",\n \"lib\": [\"es2023\"],\n \"strict\": true,\n \"noUncheckedIndexedAccess\": true,\n \"noImplicitOverride\": true,\n \"verbatimModuleSyntax\": true,\n \"isolatedModules\": true,\n \"skipLibCheck\": true,\n \"noEmit\": true,\n \"types\": [\"@cloudflare/workers-types\", \"@cloudflare/vitest-plugin/types\", \"node\"]\n },\n \"include\": [\"src\", \"test\", \"kedge.config.ts\", \"vitest.config.ts\"]\n}\n","vitest.config.ts":"// The store's tests run in workerd, over this store's own composed migrations,\n// so they see every trigger the deployed store has (docs/proposals/p0.8-cli-agent-kit.md §4.4).\nimport path from \"node:path\";\nimport { cloudflareTest, readD1Migrations } from \"@cloudflare/vitest-plugin\";\nimport { defineConfig } from \"vitest/config\";\n\nexport default defineConfig({\n plugins: [\n cloudflareTest(async () => ({\n miniflare: {\n compatibilityDate: \"2026-10-01\",\n compatibilityFlags: [\"nodejs_compat\"],\n d1Databases: [\"DB\"],\n bindings: { TEST_MIGRATIONS: await readD1Migrations(path.join(import.meta.dirname, \"migrations\")) },\n },\n })),\n ],\n test: { include: [\"test/**/*.test.ts\"], setupFiles: [\"./test/apply-migrations.ts\"], testTimeout: 30_000 },\n});\n","wrangler.jsonc":"// A merchant's deployment. Secrets (CART_TOKEN_SECRET, ORDER_TOKEN_SECRET,\n// KEDGE_AUTH_SECRET_V1, KEDGE_KEY_PEPPER_V1, KEDGE_TOTP_KEY_V1, each at least\n// 32 bytes) are set with `wrangler secret put`, never here. Then\n// `npx kedge staff invite-owner <email> --remote` makes the first Owner and\n// `npx kedge keys create --publishable --name <name> --origin <url> --remote`\n// the storefront's key (docs/09).\n{\n \"name\": \"my-store\",\n \"main\": \"src/index.ts\",\n \"compatibility_date\": \"2026-10-01\",\n \"workers_dev\": true,\n \"d1_databases\": [\n {\n \"binding\": \"DB\",\n \"database_name\": \"my-store-db\",\n \"database_id\": \"00000000-0000-0000-0000-000000000000\",\n \"migrations_dir\": \"migrations\"\n }\n ],\n // CART: one object per cart. CLOCK: the relay's one clock. JOURNAL: the\n // effect journal's 16 shards (p0.6a §2.4, §4.3).\n \"durable_objects\": {\n \"bindings\": [\n { \"name\": \"CART\", \"class_name\": \"CartDO\" },\n { \"name\": \"CLOCK\", \"class_name\": \"ClockDO\" },\n { \"name\": \"JOURNAL\", \"class_name\": \"JournalDO\" }\n ]\n },\n \"migrations\": [\n { \"tag\": \"v1\", \"new_sqlite_classes\": [\"CartDO\"] },\n { \"tag\": \"v2\", \"new_sqlite_classes\": [\"ClockDO\", \"JournalDO\"] }\n ],\n // The relay's queue, made once with `wrangler queues create my-store-events`\n // (docs/10). It carries only `{ k, seq, lease }`; retries, backoff and the\n // dead letter are D1's, so the queue's own retries are a backstop and no\n // dead-letter queue is configured. A batch is up to 10 messages or 1 s,\n // whichever comes first (range 1-100 and 0-60 s, research/21 §Batching,\n // developers.cloudflare.com/queues/configuration/batching-retries/).\n \"queues\": {\n \"producers\": [{ \"binding\": \"EVENTS\", \"queue\": \"my-store-events\" }],\n \"consumers\": [{ \"queue\": \"my-store-events\", \"max_batch_size\": 10, \"max_batch_timeout\": 1, \"max_retries\": 3 }]\n },\n \"triggers\": { \"crons\": [\"* * * * *\"] },\n \"vars\": { \"HOLD_MINUTES\": \"15\", \"ADMIN_ORIGINS\": \"\" },\n // scrypt for staff passwords and the request-scoped audit context (p0.5 §2.4, §7.1).\n \"compatibility_flags\": [\"nodejs_compat\", \"global_fetch_strictly_public\"],\n // Invocation logs are off: Cloudflare writes one per request with its URL,\n // where a rejected request can carry a customer's data that erasure can't\n // reach (customer-erasure.md §3, docs/10). `kedge doctor` checks it.\n \"observability\": { \"enabled\": true, \"logs\": { \"invocation_logs\": false } },\n // The admin at /app/ (p0.9 §2): its files are assets, served with no SPA\n // fallback, so no API path is ever answered with HTML (research/27). Remove\n // this block to serve no admin; `kedge doctor` checks its shape.\n \"assets\": { \"directory\": \"./node_modules/@kedge/admin/dist\", \"binding\": \"ASSETS\" }\n}\n"}}
|