Settings

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.

Phase 1 of 9 GATE-1 day 6 / 7 Flags 14 · 6 authoritative · 4 shadow · 4 disabled Gate-locked flags 4 Secrets overdue 1 Sections 1..9 · Search /
A flag bound to a gate cannot be promoted to authoritative before that gate passes, and the rule is a database constraint rather than a UI check. Four flags are currently refused for that reason, and each one names the gate and the day count instead of just greying out. Gate tracker →
Rollout
Feature flags 14 Phase gates 9
Registries
Stock types 18 Locations 10 Valuation basis 4
Access
Roles + personas 4 SSO status
Wiring
Secrets 7 Integrations 9 Typed config 22
Audit
Compliance
Destructive
Danger zone

Feature flags

Three states, not a boolean. A flag in shadow computes its answer and records disagreement without acting on it. Promotion to authoritative needs the mismatch rate under the bar and its gate to have passed.
35-day shadow window
SHOPIFY_DIRECT_INGEST authoritative

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.

Usage 7d 2,481,209 evaluations · 0 errors
ahmad@ promoted to authoritative 18-07 09:12 · shadow divergence 0.04% for 9 days · ahmad@ created 01-07
Core
OPS2_DISPATCH_AUTHORITATIVE authoritative

ops2 dispatch events own courier handoff. Other sources are shadow inputs for drift detection only.

Usage 7d 318,402 evaluations · 0 errors
ahmad@ promoted 14-07 14:02 · fatima@ tightened the AWB match window to 48h 21-07 11:28
Core
CARRIER_STATUS_TO_QUARANTINE authoritative

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.

Usage 7d 1,842 evaluations · 197 would-have-credited events refused
ahmad@ promoted 16-07 08:55 · created after 197 rows claimed "returned" about parcels the carrier had delivered
Reconciliation
CLOSE_FREEZE_REQUIRES_ZERO_RESIDUAL authoritative

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.

Usage 7d 3 evaluations · 3 freezes refused
ahmad@ created authoritative 02-07 12:08 · never been disabled
Safetykill switch disarmed: this flag is a correctness guard
CLEAN_COUNT_WRITES_VERIFICATION authoritative

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.

Usage 7d 1,204 verifications · 188 variances routed to the ledger
ahmad@ promoted 24-07 10:14 · 78% of counts are clean, so this is the majority path not the exception
Counts
SAMPLE_72H_AUTO_FALLBACK authoritative

Auto-route SAMPLE_QC to SAMPLE_POOL after 72h of inactivity, so an unclassified sample cannot sit in a QC bucket forever.

Usage 7d 88 evaluations · 2 in breach at 148h
fatima@ tuned the SLA 48h to 72h 20-07 10:14 · ahmad@ promoted 12-07 18:22
Programs
CSD_RESERVATION_PER_LINE shadow GATE-2

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.

Target authoritative on GATE-2 pass · gate streak 0 / 7 · shadow day 12 / 35
Usage 7d 4,182 evaluations · 312 shadow mismatches (7.5%, bar is 0.5%)
ahmad@ flipped disabled to shadow 18-07 09:00 · 312 mismatches are all cases with more than one SKU, which is the defect this flag exists to fix
Reversepromotion refused: GATE-2 streak 0/7 and mismatch 7.5% over the 0.5% bar
RFID_UNIT_LEDGER shadow GATE-6

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.

Target authoritative on GATE-6 pass · gate streak 0 / 7, best 4 · shadow day 18 / 35
Usage 7d 1,842,118 unit hops · no data for 3 days, gate G2 offline
ahmad@ flipped to shadow 10-07 07:30 · streak reset 4 to 0 on 27-07 when the metric returned NULL
RFIDpromotion refused: GATE-6 streak 0/7, metric NULL for 3 days
COST_LAYER_VALUATION shadow GATE-3

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.

Target authoritative on GATE-3 pass · needs cost confidence layered 95%, currently 91.4%
Usage 7d 184 valuation runs · AED 8.6% of value still estimated
ahmad@ flipped to shadow 08-07 22:14 · converging steadily, 74% to 91.4% in three weeks
Financepromotion refused: GATE-3 not started, confidence 91.4% under the 95% bar
PER_STORE_CUTOVER shadow MCC only

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.

Sequence MCC then YAS 05-08 · CCZ 12-08 · AJM 19-08 · BAS 26-08 · 1 / 5
Usage 7d 18,402 evaluations at MCC · 0.06% divergence
ahmad@ enabled MCC shadow 14-07 07:30 · ahmad@ set the rollout sequence 12-07 22:14
Rollout
IWMS_CARTON_WEBHOOK disabled GATE-5

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.

Blocked on a sibling PR · target shadow on the outbox landing
Usage 7d 0 evaluations
system registered the flag 16-07 21:40 · the subscription is registered before the topic is switched on, deliberately, so the first hour of events is not lost
Pending sibling
SHOPIFY_LEVELS_WRITEBACK disabled GATE-8

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.

Locked to GATE-8 · streak 0 / 7 · gate not started
Usage 7d 0 evaluations
system registered 01-07 00:00 · never enabled in any environment
Cutovershadow itself is locked: even a dry run needs GATE-8
LULWA_BRAND_SCOPE disabled

Bring the second brand into scope. Held until MCC has cut over, per the phased rollout decision.

Target after MCC cutover, before the full estate
Usage 7d 0 evaluations
system registered 01-07 00:00
Scope
DEV_CONSOLE_WRITE_REQUIRES_APPROVER authoritative

Any write from the developer console needs a second named engineer. Includes replay to production, flag promotion, secret rotation and reopening a filed close.

Usage 7d 41 evaluations · 2 pending approval, oldest 41m
ahmad@ created authoritative 02-07 12:08
Safetykill switch disarmed: this flag guards its own kill path

Phase gates

Read-only mirror of the tracker. Waiving a gate is a named human decision with a reason and an expiry, and a waived gate renders differently from a passed one everywhere it appears.
1 passed1 holding2 failingOpen tracker
GatePhaseStreakBestStateFlags bound to itWaiver
GATE-00 · Opening balance1818passednone-Open
GATE-11 · Sales and cancels66holdingSHOPIFY_DIRECT_INGEST-Open
GATE-22 · Reverse logistics01failingCSD_RESERVATION_PER_LINE-Open
GATE-33 · Inbound and cost00not startedCOST_LAYER_VALUATION-Open
GATE-44 · Transfers and retail00not startednone-Open
GATE-55 · IWMS physical00not startedIWMS_CARTON_WEBHOOK-Open
GATE-66 · RFID unit ledger04regressedRFID_UNIT_LEDGER-Open
GATE-77 · Programs00not startednoneinfluencer source unownedOpen
GATE-88 · Close reproducible00not startedSHOPIFY_LEVELS_WRITEBACK-Open
A metric that returns NULL counts as a failing day and a metric computed over zero rows never counts as a pass. GATE-6 lost a streak of 4 to a collector outage rather than to a real regression, and the tracker says which.

Stock-type registry · 18

Five flags per type. These flags drive every measure in the system, so the table is migration-only: there is no UI that can edit a flag, because a mistyped flag would silently move a number on every page at once.
read-onlyseed migration is the only writer
#CodeDimension physicalsellablefloor sell ownedpipelineNote
1STOCKconditionyesyesyesyesnothe ordinary case
2DISPLAY_GOODconditionyesnoyesyesnoon the floor, resellable
3DISPLAY_DAMAGEDconditionyesnonoyesnoowned, not resellable
4QUARANTINEconditionyesnonoyesnowhere a carrier-confirmed return lands
5WRITTEN_OFFconditionnononononovalue already gone · finance approves
6IN_TRANSIT_CUSTOMERcustodynononoyesnoowned, unpaid-returns risk
7IN_TRANSIT_RETURNcustodynononoyesnocoming back from a customer
8IN_TRANSIT_INTERNAL 17thcustodynononoyesnoa truck between HQ_319 and MCC is not a customer risk
9LOST_IN_TRANSITcustodynononononoterminal until an investigation resolves it
10RTS_PENDINGcustodyyesnonoyesnostaged for return to supplier
11RESERVEDcustodyyesnonoyesnoalready left STOCK, so never subtract it again
12RECEIVED_NOT_INVOICEDcustodyyesnonoyes · correctednoownership inbound is monotonic: title cannot un-pass at the dock
13INFLUENCER_POOLprogramyesnonoyesnoloan pool
14SAMPLE_POOLprogramyesnonoyesnoclassified samples
15SAMPLE_QCprogramyesnonoyesno72h SLA before auto-fallback
16ON_TRIAL 18thprogramnononoyesnoout with a customer or staff member · a restoration, not an invention
17IN_PRODUCTIONpipelinenonononoyesa real balance, not inventory · the deposit is a prepayment
18GIT_INBOUNDpipelinenononoyesyestitle passes at FOB, so goods on a ship are ours
18 types, not 14 and not 16. Any module or query hardcoding an older count is wrong, and the seed migration is the single place the list lives. Two of these flags carry the whole valuation: owned is the balance-sheet quantity, and physical is what a warehouse count should find.

Location registry · 10

The real codes, fetched from Shopify rather than guessed. Roll-ups are derived and never stored, so no row here can double-count the estate.
1 unmapped
CodeNameKindSellableShopify location idOn handOwned valueNote
HQ_319Warehouse 319warehouseyes1837039610038,204AED 5.84Mprimary · RFID gates G1 and G2
HQ_325Warehouse 325warehouseyes183703961209,118AED 1.20Moverflow and repack
MCCMirdif City Centrestoreyes183703961401,842AED 248Kfirst in the cutover sequence
YASYas Mallstoreyes183703961501,204AED 168Knext in the sequence, 05-08
CCZCity Centre Zahiastoreyes18370396160988AED 143K-
AJMAjman City Centrestoreyes18370396170742AED 98K-
BASBawabat Al Sharqstoreyes18370396180610AED 84K-
TRANSITIn transitvirtualnonone4,198AED 1.28Mholds the transit types with a counterpart location
SUPPLIER_HOLDSupplier holdvirtualnonone64AED 13Kstaged for RTS
3PL_ARAMEXAramex third-partythird_partyunmapped18370396195312AED 49Ka live Shopify location that appears in no plan and no NetSuite account. Needs a mapping decision
DISCONTINUEDDiscontinued poolvirtualnonone0AED 0terminal holding, currently empty
Sellable requires both the stock-type flag and the location flag. Stock sitting at TRANSIT in a sellable type is still not sellable, which is why the two flags are separate columns rather than one.

Valuation basis registry

Which cost basis i3 uses, and which basis the accounting system uses for the same items. A mismatch here is the single largest source of a reconciliation delta, so it is a declared fact rather than a discovery.
3 unconfirmedOpen recon
ScopeApplies toi3 basisNetSuite basisMatchConfirmed byEffect if mismatched
globalall SKUs, defaultfifo_layerunknownunconfirmednobody yeta permanent costing bucket that grows with price volatility
categoryLeather goodsfifo_layerfifoyesfinance@ · 12-07-
categoryFragrancefifo_layeraveragenofinance@ · 12-07AED 36.6K of the July costing bucket sits here
categoryJewelleryfifo_layerunknownunconfirmednobody yetunknown until somebody reads an item record
skuconsignment-sourced itemsfifo_layerunknownunconfirmednobody yet2 of 5 partners are not in the accounting system at all
i3 publishes a management valuation and reconciles to the accounting system, which owns the balance sheet. On a disagreement about units i3 wins, because it has the gates, the cartons and the counts. On a disagreement about AED after a vendor bill is posted the accounting system wins. On the reason a difference exists i3 wins, because it owns the movement history.

Roles and personas

The auth portal has exactly four roles. A persona is derived at login from role, feature flags and rota-effective locations, then cached in i3's own table. It is never granted, because the session token has a hard size ceiling across the whole estate and nothing per-persona can live in it.
4 roles · 9 personasSSO portal
PersonaMin roleRequired featuresLocations fromDerivation rule
Owneradminnoneallrole = admin
Financemanagerfinanceallrole ≥ manager AND feature:finance
Store managermanagerretailrotarole ≥ manager AND feature:retail AND rota_locations ≠ {}
WH operatorops_managerwarehouserotarole ≥ ops_manager AND feature:warehouse
Inboundmanagerinbound, suppliersallrole ≥ manager AND feature:inbound
CS agentviewerreversenonerole ≥ viewer AND feature:reverse
Plannermanagerplanningallrole ≥ manager AND feature:planning
Marketingviewerprogramsnonerole ≥ viewer AND feature:programs
Adminadmindev_consoleallrole = admin AND feature:dev_console
The phrase "requires the store_manager role" specifies something the auth system cannot express: there is no such role. The correct form is role ≥ manager plus a feature flag, and every permission rule in i3 is written that way. Where several personas match, the lowest priority wins and the page names which one it used.

Secrets and rotation

Rotation status only. The registry table has no value column, so there is nothing here to leak by accident: not a value, not a masked value, not a prefix and not a last-four.
1 overdue1 due soon60-min overlap
NameStored atOwnerCadenceLast rotatedStatusConsumers
I3_WEBHOOK_SECRET[see ~/.env.master]platform@minimalist.ae90d12-07 03:00currentall 9 inbound sources
I3_SHOPIFY_WEBHOOK_SECRET[see ~/.env.master]platform@minimalist.ae90d20-03 11:42overdue 131d12 Shopify topics
I3_INTERNAL_API_KEY[see ~/.env.master]platform@minimalist.ae90d05-07 22:17currentCSD · rep app · SKU intel
SSO_JWT_SECRET[see ~/.env.master]platform@minimalist.ae180d12-07 03:00currentshared across the whole estate
I3_OUTBOX_SIGNING_KEY[see ~/.env.master]platform@minimalist.ae90d30-06 04:00due in 6d5 fanout destinations
I3_RFID_DEVICE_TOKEN[see ~/.env.master]platform@minimalist.ae365d01-07 00:00current2 gates · 2 handhelds · 5 sleds
I3_TIGRIS_ARTIFACT_KEY[see ~/.env.master]platform@minimalist.ae180dnevernever rotatedreport artifact storage
Rotation is a two-phase change with a 60-minute overlap: the new secret is accepted alongside the old one, every consumer is confirmed to have moved, and only then is the old one revoked. Rotating without the overlap drops every in-flight webhook for the length of the deploy.

Integration config

A sibling endpoint URL has no default. If the variable is unset the process refuses to start, because a wrong destination that returns 200 is worse than a crash on boot.
6 enabled2 pending1 not emittableLag monitor
IntegrationDirectionBase URLAuthSigning secretRetryWhen downEnabled
shopifyinboundwebhooks directhmacI3_SHOPIFY_WEBHOOK_SECRETexp 5,30,120queueyes
ops2 inboundbothops.minimalist-app.comhmacI3_WEBHOOK_SECRETexp 5,30,120queueyes
ops2 reverseinboundops.minimalist-app.comhmacI3_WEBHOOK_SECRETexp 5,30,120queuenot emittable
csdbothcsd.minimalist-app.comhmacI3_WEBHOOK_SECRETexp 5,30,120fail closedyes
grninboundgrn.minimalist-app.comhmacI3_WEBHOOK_SECRETexp 5,30,120queueyes
iwmsinboundops.minimalist-app.comhmacI3_WEBHOOK_SECRETexp 5,30,120queueshadow
rfidinboundrfid.minimalist-app.comhmacI3_RFID_DEVICE_TOKENexp 5,30,120queueshadow
influencer hubinboundunset · refuses boothmacI3_WEBHOOK_SECRETexp 5,30,120queuepending
sku intelinboundsku.minimalist-app.combearerI3_INTERNAL_API_KEYdaily pullqueueyes
fanout outboxoutbound5 destinationshmacI3_OUTBOX_SIGNING_KEYexp 5,30,120 + DLQqueueyes
csd fails closed rather than queueing, deliberately: a stock reservation that silently queues while a customer is told the item is held is worse than an error the agent can see. The incumbent defaulted its endpoint to a dormant app, so an unset variable shipped live reservations into a dead system and returned 200 while doing it.

Typed configuration

Every setting declares a type, its allowed values and a default. The incumbent stored these as an untyped key and an opaque JSON value, which is why casting a plain string was a documented landmine.
22 settings
KeyTypeAllowedDefaultCurrentMin roleDescription
recon.tolerance_floor_filsint0 to 100000005000050000adminAED 500 floor on any reconciliation tolerance
recon.tolerance_pctdecimal0.0 to 5.00.100.10adminpercentage arm of the tolerance, whichever is greater
gate.window_daysint1 to 3077adminconsecutive passing days a gate needs
gate.null_counts_asenumfail, skipfailfailadmina NULL metric is a failing day, never a skipped one
flag.shadow_window_daysint7 to 903535admincovers two month-ends
flag.promotion_mismatch_bardecimal0.0 to 5.00.500.50adminshadow disagreement ceiling for promotion
valuation.confidence_bar_pctdecimal0.0 to 100.05.05.0adminabove this share of non-layered cost, a figure is labelled estimated
valuation.basisenumfifo_layer, weighted_average, standardfifo_layerfifo_layeradminthe i3 cost basis
close.residual_tolerance_unitsint0 to 000adminfixed at zero by constraint; a residual is a decomposition bug
close.file_requires_extractbooltrue, falsetruetrueadminfreezing needs no extract, filing does
cover.null_sortenumfirst, lastfirstfirstmanagerhow a SKU with no computable cover sorts
sample.qc_sla_hoursduration24 to 1687272managerbefore auto-fallback to the pool
count.dual_approval_varianceint1 to 10055managerunits of variance that trigger a second approver
drift.auto_accept_unitsint0 to 1022adminbelow this, a drift is auto-accepted with evidence
report.max_range_daysint30 to 730400400managerguards the runner from an unbounded scan
report.bounce_pause_afterint1 to 1033managerconsecutive bounces before a subscription pauses
dev.session_ttl_minutesduration15 to 4806060admindev console session lifetime
dev.read_only_defaultbooltrue, falsetruetrueadminthe console opens safe
dev.replay_per_minuteint1 to 6055adminper user
session.lifetime_daysint1 to 303030adminaligned with the SSO cookie and JWT
timezone.displayenumAsia/Dubai, UTCAsia/DubaiAsia/Dubaiadminevery timestamp on every page and in every export
currency.displayenumAEDAEDAEDadmininteger fils internally, AED on the surface
A setting change is an append to the change log with an actor and a reason, never an in-place overwrite, so a value can always be traced to the person who moved it and the day it moved.

SSO status

i3 has no login of its own.
connected
Portal
auth.minimalist-app.com 200
App code
i3
Roles path
payload.apps.i3.role
Roles
viewer · manager · ops_manager · admin
Auth methods
Google · password · passkey, all with TOTP
Session cookie
_mst_session · 30 days
Token budget
under the shared size ceiling across 38 apps
Secret
[see ~/.env.master]
Locations from
rota effective-store roster
Persona cache
i3's own table, rebuilt at login

Compliance

Four hash-chained journals.
Open audit log
JournalRowsLast passValidRetention
ledger_entries4,186,20430-07 04:00yesinfinite
cost_ledger118,44130-07 04:00yesinfinite
unit_ledger1,842,11830-07 04:00yesinfinite
audit_log248,90430-07 04:00yesinfinite
events_raw6,204,882n/anot chained5 years
webhook deliveries1,482,204n/anot chained2 years
29 of the last 30 nightly passes were valid. The one exception was an incomplete pass aborted at 78% by a worker redeploy, and an incomplete pass is recorded as not-a-pass rather than as a pass.

Danger zone

Each action needs 2FA step-up, a typed reason and a named approver. None of them is reversible by clicking the same button again.
admin only
ActionWhat it does, and what it does notRequires
Reopen a filed closeMoves 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 cacheRecomputes 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 batchWrites 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 flagBypasses 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 onceTwo-phase with the 60-minute overlap, all seven at once. Every consumer must confirm before the old values are revoked.2FA + approver
Export everythingFull 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
There is no "reset the ledger" action, and there never will be. The ledger is append-only, and every correction anywhere in i3 is a compensating row rather than an edit.

Registries not seeded

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.

Could not read the flag service

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.

Read-only settings

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.

Flag writes paused

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.

Four flags cannot be promoted yet

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 →

Connected to Ops health + gates Dev console Reports + close Cash + valuation Audit log Locations Rules Sibling events Opening stock Sign in (SSO) Help + glossary
1..9 jump section · / search · G gates · F flags · ⌘K palette No panel on this page renders a secret value, a masked value or a prefix. The registry has no value column, so this is enforced by the schema rather than by discipline.