Feature flags gated on the phase gates, typed configuration, the stock-type and location registries, secret rotation status, integration config and the role mapping. Every change is an append, never an overwrite.
Shopify order webhooks post directly to the quantity ledger. Keyed on variant.id, never on the SKU string, so a rename does not orphan the stream.
ops2 dispatch events own courier handoff. Other sources are shadow inputs for drift detection only.
A carrier-confirmed return moves units to QUARANTINE: present, owned, not sellable. A status string is structurally incapable of crediting sellable stock, and only a physical scan moves units out.
A close whose category decomposition does not add up to its independently computed closing balance cannot be frozen. Turning this off would let a residual be absorbed into next month's opening.
A zero-variance count writes a balance_verifications row, not a ledger row. A zero-quantity ledger row is forbidden, and without this a diligently counted location would drift to "never verified" precisely because it keeps passing.
Auto-route SAMPLE_QC to SAMPLE_POOL after 72h of inactivity, so an unclassified sample cannot sit in a QC bucket forever.
Hold every SKU on a case at its own location and quantity, keyed per line and per sequence. The incumbent sends only the first SKU of the case with quantity 1 and a hardcoded location, and its key has no line, so a case re-entering approval is deduplicated away and never re-held.
The unit ledger becomes a peer of the quantity ledger, keyed on sku_id with the SKU code kept only as a frozen display copy so a rename cannot orphan an encoded tag.
Value stock at the layer the units arrived on rather than at one company-wide current cost. Without it a single new receipt reprices every unit on the shelf, and a past valuation changes when somebody edits a cost.
Per-store gradual cutover, scoped by location rather than globally, so a rollback is one flag flip at one store and never a database restore.
Accept carton create, move, split and QC events from the warehouse layer. Registered but not enabled: the sibling emits six inbound-warehouse topics and i3 needs the carton set to be complete first.
Push i3 balances back into Shopify inventory levels. Locked until the close is reproducible and one full month is signed, because a wrong write-back is visible to customers.
Bring the second brand into scope. Held until MCC has cut over, per the phased rollout decision.
Any write from the developer console needs a second named engineer. Includes replay to production, flag promotion, secret rotation and reopening a filed close.
| Gate | Phase | Streak | Best | State | Flags bound to it | Waiver | |
|---|---|---|---|---|---|---|---|
| GATE-0 | 0 · Opening balance | 18 | 18 | passed | none | - | Open |
| GATE-1 | 1 · Sales and cancels | 6 | 6 | holding | SHOPIFY_DIRECT_INGEST | - | Open |
| GATE-2 | 2 · Reverse logistics | 0 | 1 | failing | CSD_RESERVATION_PER_LINE | - | Open |
| GATE-3 | 3 · Inbound and cost | 0 | 0 | not started | COST_LAYER_VALUATION | - | Open |
| GATE-4 | 4 · Transfers and retail | 0 | 0 | not started | none | - | Open |
| GATE-5 | 5 · IWMS physical | 0 | 0 | not started | IWMS_CARTON_WEBHOOK | - | Open |
| GATE-6 | 6 · RFID unit ledger | 0 | 4 | regressed | RFID_UNIT_LEDGER | - | Open |
| GATE-7 | 7 · Programs | 0 | 0 | not started | none | influencer source unowned | Open |
| GATE-8 | 8 · Close reproducible | 0 | 0 | not started | SHOPIFY_LEVELS_WRITEBACK | - | Open |
| # | Code | Dimension | physical | sellable | floor sell | owned | pipeline | Note |
|---|---|---|---|---|---|---|---|---|
| 1 | STOCK | condition | yes | yes | yes | yes | no | the ordinary case |
| 2 | DISPLAY_GOOD | condition | yes | no | yes | yes | no | on the floor, resellable |
| 3 | DISPLAY_DAMAGED | condition | yes | no | no | yes | no | owned, not resellable |
| 4 | QUARANTINE | condition | yes | no | no | yes | no | where a carrier-confirmed return lands |
| 5 | WRITTEN_OFF | condition | no | no | no | no | no | value already gone · finance approves |
| 6 | IN_TRANSIT_CUSTOMER | custody | no | no | no | yes | no | owned, unpaid-returns risk |
| 7 | IN_TRANSIT_RETURN | custody | no | no | no | yes | no | coming back from a customer |
| 8 | IN_TRANSIT_INTERNAL 17th | custody | no | no | no | yes | no | a truck between HQ_319 and MCC is not a customer risk |
| 9 | LOST_IN_TRANSIT | custody | no | no | no | no | no | terminal until an investigation resolves it |
| 10 | RTS_PENDING | custody | yes | no | no | yes | no | staged for return to supplier |
| 11 | RESERVED | custody | yes | no | no | yes | no | already left STOCK, so never subtract it again |
| 12 | RECEIVED_NOT_INVOICED | custody | yes | no | no | yes · corrected | no | ownership inbound is monotonic: title cannot un-pass at the dock |
| 13 | INFLUENCER_POOL | program | yes | no | no | yes | no | loan pool |
| 14 | SAMPLE_POOL | program | yes | no | no | yes | no | classified samples |
| 15 | SAMPLE_QC | program | yes | no | no | yes | no | 72h SLA before auto-fallback |
| 16 | ON_TRIAL 18th | program | no | no | no | yes | no | out with a customer or staff member · a restoration, not an invention |
| 17 | IN_PRODUCTION | pipeline | no | no | no | no | yes | a real balance, not inventory · the deposit is a prepayment |
| 18 | GIT_INBOUND | pipeline | no | no | no | yes | yes | title passes at FOB, so goods on a ship are ours |
| Code | Name | Kind | Sellable | Shopify location id | On hand | Owned value | Note |
|---|---|---|---|---|---|---|---|
| HQ_319 | Warehouse 319 | warehouse | yes | 18370396100 | 38,204 | AED 5.84M | primary · RFID gates G1 and G2 |
| HQ_325 | Warehouse 325 | warehouse | yes | 18370396120 | 9,118 | AED 1.20M | overflow and repack |
| MCC | Mirdif City Centre | store | yes | 18370396140 | 1,842 | AED 248K | first in the cutover sequence |
| YAS | Yas Mall | store | yes | 18370396150 | 1,204 | AED 168K | next in the sequence, 05-08 |
| CCZ | City Centre Zahia | store | yes | 18370396160 | 988 | AED 143K | - |
| AJM | Ajman City Centre | store | yes | 18370396170 | 742 | AED 98K | - |
| BAS | Bawabat Al Sharq | store | yes | 18370396180 | 610 | AED 84K | - |
| TRANSIT | In transit | virtual | no | none | 4,198 | AED 1.28M | holds the transit types with a counterpart location |
| SUPPLIER_HOLD | Supplier hold | virtual | no | none | 64 | AED 13K | staged for RTS |
| 3PL_ARAMEX | Aramex third-party | third_party | unmapped | 18370396195 | 312 | AED 49K | a live Shopify location that appears in no plan and no NetSuite account. Needs a mapping decision |
| DISCONTINUED | Discontinued pool | virtual | no | none | 0 | AED 0 | terminal holding, currently empty |
| Scope | Applies to | i3 basis | NetSuite basis | Match | Confirmed by | Effect if mismatched |
|---|---|---|---|---|---|---|
| global | all SKUs, default | fifo_layer | unknown | unconfirmed | nobody yet | a permanent costing bucket that grows with price volatility |
| category | Leather goods | fifo_layer | fifo | yes | finance@ · 12-07 | - |
| category | Fragrance | fifo_layer | average | no | finance@ · 12-07 | AED 36.6K of the July costing bucket sits here |
| category | Jewellery | fifo_layer | unknown | unconfirmed | nobody yet | unknown until somebody reads an item record |
| sku | consignment-sourced items | fifo_layer | unknown | unconfirmed | nobody yet | 2 of 5 partners are not in the accounting system at all |
| Persona | Min role | Required features | Locations from | Derivation rule |
|---|---|---|---|---|
| Owner | admin | none | all | role = admin |
| Finance | manager | finance | all | role ≥ manager AND feature:finance |
| Store manager | manager | retail | rota | role ≥ manager AND feature:retail AND rota_locations ≠ {} |
| WH operator | ops_manager | warehouse | rota | role ≥ ops_manager AND feature:warehouse |
| Inbound | manager | inbound, suppliers | all | role ≥ manager AND feature:inbound |
| CS agent | viewer | reverse | none | role ≥ viewer AND feature:reverse |
| Planner | manager | planning | all | role ≥ manager AND feature:planning |
| Marketing | viewer | programs | none | role ≥ viewer AND feature:programs |
| Admin | admin | dev_console | all | role = admin AND feature:dev_console |
| Name | Stored at | Owner | Cadence | Last rotated | Status | Consumers | |
|---|---|---|---|---|---|---|---|
| I3_WEBHOOK_SECRET | [see ~/.env.master] | platform@minimalist.ae | 90d | 12-07 03:00 | current | all 9 inbound sources | |
| I3_SHOPIFY_WEBHOOK_SECRET | [see ~/.env.master] | platform@minimalist.ae | 90d | 20-03 11:42 | overdue 131d | 12 Shopify topics | |
| I3_INTERNAL_API_KEY | [see ~/.env.master] | platform@minimalist.ae | 90d | 05-07 22:17 | current | CSD · rep app · SKU intel | |
| SSO_JWT_SECRET | [see ~/.env.master] | platform@minimalist.ae | 180d | 12-07 03:00 | current | shared across the whole estate | |
| I3_OUTBOX_SIGNING_KEY | [see ~/.env.master] | platform@minimalist.ae | 90d | 30-06 04:00 | due in 6d | 5 fanout destinations | |
| I3_RFID_DEVICE_TOKEN | [see ~/.env.master] | platform@minimalist.ae | 365d | 01-07 00:00 | current | 2 gates · 2 handhelds · 5 sleds | |
| I3_TIGRIS_ARTIFACT_KEY | [see ~/.env.master] | platform@minimalist.ae | 180d | never | never rotated | report artifact storage |
| Integration | Direction | Base URL | Auth | Signing secret | Retry | When down | Enabled |
|---|---|---|---|---|---|---|---|
| shopify | inbound | webhooks direct | hmac | I3_SHOPIFY_WEBHOOK_SECRET | exp 5,30,120 | queue | yes |
| ops2 inbound | both | ops.minimalist-app.com | hmac | I3_WEBHOOK_SECRET | exp 5,30,120 | queue | yes |
| ops2 reverse | inbound | ops.minimalist-app.com | hmac | I3_WEBHOOK_SECRET | exp 5,30,120 | queue | not emittable |
| csd | both | csd.minimalist-app.com | hmac | I3_WEBHOOK_SECRET | exp 5,30,120 | fail closed | yes |
| grn | inbound | grn.minimalist-app.com | hmac | I3_WEBHOOK_SECRET | exp 5,30,120 | queue | yes |
| iwms | inbound | ops.minimalist-app.com | hmac | I3_WEBHOOK_SECRET | exp 5,30,120 | queue | shadow |
| rfid | inbound | rfid.minimalist-app.com | hmac | I3_RFID_DEVICE_TOKEN | exp 5,30,120 | queue | shadow |
| influencer hub | inbound | unset · refuses boot | hmac | I3_WEBHOOK_SECRET | exp 5,30,120 | queue | pending |
| sku intel | inbound | sku.minimalist-app.com | bearer | I3_INTERNAL_API_KEY | daily pull | queue | yes |
| fanout outbox | outbound | 5 destinations | hmac | I3_OUTBOX_SIGNING_KEY | exp 5,30,120 + DLQ | queue | yes |
| Key | Type | Allowed | Default | Current | Min role | Description |
|---|---|---|---|---|---|---|
| recon.tolerance_floor_fils | int | 0 to 10000000 | 50000 | 50000 | admin | AED 500 floor on any reconciliation tolerance |
| recon.tolerance_pct | decimal | 0.0 to 5.0 | 0.10 | 0.10 | admin | percentage arm of the tolerance, whichever is greater |
| gate.window_days | int | 1 to 30 | 7 | 7 | admin | consecutive passing days a gate needs |
| gate.null_counts_as | enum | fail, skip | fail | fail | admin | a NULL metric is a failing day, never a skipped one |
| flag.shadow_window_days | int | 7 to 90 | 35 | 35 | admin | covers two month-ends |
| flag.promotion_mismatch_bar | decimal | 0.0 to 5.0 | 0.50 | 0.50 | admin | shadow disagreement ceiling for promotion |
| valuation.confidence_bar_pct | decimal | 0.0 to 100.0 | 5.0 | 5.0 | admin | above this share of non-layered cost, a figure is labelled estimated |
| valuation.basis | enum | fifo_layer, weighted_average, standard | fifo_layer | fifo_layer | admin | the i3 cost basis |
| close.residual_tolerance_units | int | 0 to 0 | 0 | 0 | admin | fixed at zero by constraint; a residual is a decomposition bug |
| close.file_requires_extract | bool | true, false | true | true | admin | freezing needs no extract, filing does |
| cover.null_sort | enum | first, last | first | first | manager | how a SKU with no computable cover sorts |
| sample.qc_sla_hours | duration | 24 to 168 | 72 | 72 | manager | before auto-fallback to the pool |
| count.dual_approval_variance | int | 1 to 100 | 5 | 5 | manager | units of variance that trigger a second approver |
| drift.auto_accept_units | int | 0 to 10 | 2 | 2 | admin | below this, a drift is auto-accepted with evidence |
| report.max_range_days | int | 30 to 730 | 400 | 400 | manager | guards the runner from an unbounded scan |
| report.bounce_pause_after | int | 1 to 10 | 3 | 3 | manager | consecutive bounces before a subscription pauses |
| dev.session_ttl_minutes | duration | 15 to 480 | 60 | 60 | admin | dev console session lifetime |
| dev.read_only_default | bool | true, false | true | true | admin | the console opens safe |
| dev.replay_per_minute | int | 1 to 60 | 5 | 5 | admin | per user |
| session.lifetime_days | int | 1 to 30 | 30 | 30 | admin | aligned with the SSO cookie and JWT |
| timezone.display | enum | Asia/Dubai, UTC | Asia/Dubai | Asia/Dubai | admin | every timestamp on every page and in every export |
| currency.display | enum | AED | AED | AED | admin | integer fils internally, AED on the surface |
| Journal | Rows | Last pass | Valid | Retention |
|---|---|---|---|---|
| ledger_entries | 4,186,204 | 30-07 04:00 | yes | infinite |
| cost_ledger | 118,441 | 30-07 04:00 | yes | infinite |
| unit_ledger | 1,842,118 | 30-07 04:00 | yes | infinite |
| audit_log | 248,904 | 30-07 04:00 | yes | infinite |
| events_raw | 6,204,882 | n/a | not chained | 5 years |
| webhook deliveries | 1,482,204 | n/a | not chained | 2 years |
| Action | What it does, and what it does not | Requires | |
|---|---|---|---|
| Reopen a filed close | Moves a filed period back to draft. The cut ids are retained, so the original pack stays reproducible and the reopening is itself an event. Finance is notified. | 2FA + approver + reason | |
| Rebuild the balance cache | Recomputes every cache row from the chain. This is not a repair. An unreconciled key stays unreconciled and stays excluded, because only a compensating pair closes it. | 2FA + reason | |
| Revert a fixture batch | Writes compensating rows for every event in the batch. Never deletes. Refuses if any row in the batch has already been compensated. | 2FA + reason | |
| Force-promote a gated flag | Bypasses the gate constraint for one flag with an expiring waiver. The waiver appears on the flag, on the gate tracker and on every report the flag touches. | 2FA + approver + expiry | |
| Rotate every secret at once | Two-phase with the 60-minute overlap, all seven at once. Every consumer must confirm before the old values are revoked. | 2FA + approver | |
| Export everything | Full dump of every table to object storage with a 30-day signed URL. Secret values are not included, because they are not stored here. | 2FA + reason |
The stock-type, location and valuation-basis registries are seeded by migration, so an empty page means the migration has not run rather than that nobody has configured anything. i3 will not start against an empty stock-type table: every measure in the system is a flag-driven sum over it, and a missing flag would silently move a number on every page at once.
GET /api/flags returned 503. The application is still running on its last known flag state, cached in process, and is not falling back to defaults: silently reverting a shadow flag to disabled mid-day would change what the ledger records.
Writing settings needs admin. You have manager, so every panel is readable and no control is live. That is deliberate: the registries and the gate state are things an operator needs to see to understand why a page is behaving as it is, and hiding them makes a gated feature look like a bug. Your derived persona is store manager · MCC.
Postgres minor upgrade, 02:00 to 02:40 GST. Flag reads continue from the in-process cache so behaviour does not change mid-window. Flips are queued rather than dropped, and each one keeps the timestamp it was requested at, not the timestamp it applied.
CSD_RESERVATION_PER_LINE needs GATE-2, RFID_UNIT_LEDGER needs GATE-6, COST_LAYER_VALUATION needs GATE-3 and SHOPIFY_LEVELS_WRITEBACK needs GATE-8. Each control shows the gate, its streak and its blocking metric rather than just greying out, because "why can I not turn this on" is the only question that matters here. Gate tracker →