External writes
product.stock is an output. Stockly derives it from the shelves after every operation and
overwrites whatever it finds there. An integration that writes it is not talking to the warehouse;
it is racing the next recalculation, and losing.
This page is the mechanism. The setting, the register screen and the daily summary are described in external stock writes for the people who operate them.
How a foreign write is proven
Section titled “How a foreign write is proven”Every legitimate write updates product.stock and an availability shadow in the same
transaction. Because the pair moves atomically, a mismatch observed under the caller’s row lock
cannot be an intermediate state, because nothing can be halfway through. Somebody wrote the
field without telling the module.
The shadow is maintained whether or not detection is switched on, so turning detection on later starts from a current baseline rather than from a cliff of false positives.
Two detection points, with different timing:
| Source value | Written by | Caught |
|---|---|---|
dal | the Admin API, the admin UI, any DAL write | immediately, from the product write |
sql | raw SQL straight into the database | lazily, at the product’s next stock event — exactly the moment the module would otherwise silently overwrite it |
The threat model is a misconfigured integration, not sabotage from inside the database. A foreign system that also forges the shadow table is out of scope for any application-level mechanism.
The three reactions
Section titled “The three reactions”The policy is a setting, and its values reach the integration verbatim in policyApplied:
| Value | What happens to the number |
|---|---|
off | no detection and no record: a DAL edit is absorbed as an ordinary stock correction, a raw-SQL write is overwritten by the next recalculation |
correct | the external figure wins — the difference is booked into the warehouse and the incident recorded |
restore | the shelves win — nothing is booked, the field is rewritten from the shelves, and the rejected write is recorded |
Under correct the difference lands as a real movement, so the correction is in the product’s
history like any other. Under restore there is no movement, because nothing physically changed.
Being told
Section titled “Being told”p2lab_stockly.external_stock_write_detected fires once per product per unit of work, after the
commit:
| Key | |
|---|---|
productId | |
expectedValue | what Stockly held |
foundValue | what the writer left behind |
delta | the drift |
source | dal or sql |
policyApplied | off, correct or restore |
Unlike every other event in the catalogue, this one carries scalar values rather than the shared envelope, because it is written for the Flow Builder and for App System webhooks, which is also why a merchant can wire it to a mail without anybody writing code.
The register
Section titled “The register”p2lab_stockly_external_stock_incident, one row per detection:
| Field | |
|---|---|
productId | |
expectedAtp | the figure the shelves add up to |
foundValue | the figure somebody wrote |
delta | |
source | dal or sql |
policyApplied | correct or restore |
userId, integrationId | who wrote it, when the write came through the API |
integrationId is the useful one for an integration author: it names the API integration that made
the write, which turns a vague report of stock being overwritten into a specific credential.
Read it like any entity (/api/search/p2lab-stockly-external-stock-incident); a rising count
for one integrationId is worth an alert of its own.
Rows survive the product being deleted, on purpose: an audit trail that disappears with its subject is not an audit trail. They are purged after a year.
What to do instead
Section titled “What to do instead”| You want to | Call |
|---|---|
| book goods in | warehouse/stock/add |
| book goods out | warehouse/stock/remove |
| correct a figure to a counted one | warehouse/stock/count |
repair a product.stock somebody already overwrote | warehouse/stock/sync-product/{productId} |
Each of those leaves a ledger row, recalculates the published figure, and updates the shadow, which is the whole difference between a stock change and a stock change nobody can explain later.
If an external system genuinely owns the numbers, that is a legitimate architecture: set the policy
to correct and let it win explicitly, with every override recorded. What is never right is writing
the field with detection off and assuming it stuck.
- Putting stock in a bin — the supported way
- The stock model — why the field is derived in the first place
- Integrity check — cleaning up drift that already exists