<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
	<channel>
		<title>WEOUR Build — Build in Public Log</title>
		<link>https://weour.com/build/</link>
		<description>The public build log of WEOUR OS: multi-store commerce, inventory and warehouse systems, SKU mapping and AI agents — built in production.</description>
		<language>en</language>
		<atom:link href="https://weour.com/build/feed/" rel="self" type="application/rss+xml" />
		<atom:link href="https://weour.com/build/feed/" rel="alternate" hreflang="en" type="application/rss+xml" />
		<atom:link href="https://weour.com/ru/build/feed/" rel="alternate" hreflang="ru" type="application/rss+xml" />
		<atom:link href="https://weour.com/zh/build/feed/" rel="alternate" hreflang="zh-CN" type="application/rss+xml" />
		<lastBuildDate>Wed, 14 Oct 2026 00:00:00 +0000</lastBuildDate>
		<item>
			<title>Why Did One 80×80mm Label Print as Two Pages?</title>
			<link>https://weour.com/build/why-an-80x80mm-label-printed-two-pages/</link>
			<guid isPermaLink="true">https://weour.com/build/why-an-80x80mm-label-printed-two-pages/</guid>
			<pubDate>Wed, 14 Oct 2026 00:00:00 +0000</pubDate>
			<description><![CDATA[A download button exposed one connected engineering problem spanning business identity, filenames, browser pagination and physical dimensions; the fix was to isolate the print document, not shrink the label.]]></description>
			<content:encoded><![CDATA[<p>The request began with a small interaction: make “Download PNG” in the label editor open a real Save As flow. After clicking, a user should see a useful suggested filename, choose where to save it and end up with a file that can be identified without opening it.</p>
<p>The first filename implementation immediately exposed a deeper problem. It used an internal SKU such as <code class="weour-build-inline-code">SKU-001</code>, while the identifier people actually saw and used on the label was <code class="weour-build-inline-code">Batch No: 30267-0914</code>. The download worked, but the data semantics did not. The system had selected an identifier that existed technically and was wrong operationally.</p>
<dl class="weour-build-facts"><div class="weour-build-fact"><dt>Production route</dt><dd>/tools/label/</dd></div><div class="weour-build-fact"><dt>Business identifier for filenames</dt><dd>Visible Batch No, when present</dd></div><div class="weour-build-fact"><dt>Example</dt><dd>30267-0914-BQ.png · 30267-0914-BQ.pdf</dd></div><div class="weour-build-fact"><dt>Physical size</dt><dd>No scaling; 80×80mm remains 80×80mm</dd></div><div class="weour-build-fact"><dt>Pagination verification</dt><dd>Six sizes produced one page each in Chromium</dd></div></dl>
<h2 id="a-filename-is-part-of-the-data-model">A filename is part of the data model</h2>
<p>The answer was not to invent another piece of download-only naming logic that merely looked right. The filename and the visible <code class="weour-build-inline-code">Batch No</code> now resolve from the same canonical data source. If the label shows <code class="weour-build-inline-code">30267-0914</code>, the PNG is suggested as <code class="weour-build-inline-code">30267-0914-BQ.png</code> and the PDF as <code class="weour-build-inline-code">30267-0914-BQ.pdf</code>. If those values diverge, at least one of them is no longer describing the batch in front of the user.</p>
<p>The same principle applies to PDF, although the browser interface is different. On macOS, when a browser print flow continues to Print → Save as PDF, the suggested PDF name comes from the print document title. The print path therefore replaces <code class="weour-build-inline-code">document.title</code> briefly with the Batch-derived name before printing, then reliably restores the normal page title when printing ends, is cancelled or the window regains control. This is not a change to the page&#039;s SEO title. It is scoped state with an explicit entry and exit around one print session.</p>
<h2 id="exactly-one-page-became-one-page-plus-a-blank-one">Exactly one page became one page plus a blank one</h2>
<p>Once the naming path was coherent, the more interesting failure appeared: an 80×80mm label produced two pages in print preview, with the second page blank.</p>
<p>It feels obvious that an 80×80mm sheet should fit on an 80×80mm page. Browser layout, however, does not compare two integer millimetre values directly. CSS millimetres are converted to CSS pixels, and the print pipeline maps those pixels again into device and pagination units. At the page boundary, subpixel rounding, a default line box or a tiny contribution from the document body can place an edge that is numerically “exact” into the next pagination fragment.</p>
<p>The original print sheet was set to the full page dimensions, but it still participated in normal document pagination. Hiding only <code class="weour-build-inline-code">#root</code> was weaker than it appeared, because it assumed that no other body child could contribute to print layout. That assumption is too broad when the output contract depends on an exact page boundary.</p>
<h2 id="fix-the-pagination-not-the-label-dimensions">Fix the pagination, not the label dimensions</h2>
<p>The correct fix was to isolate the print sheet from normal pagination flow. The print stylesheet strictly removes every non-print body child, zeroes margins, padding and line-box contributions on <code class="weour-build-inline-code">html</code> and <code class="weour-build-inline-code">body</code>, and positions the one print sheet absolutely at the page origin. It still retains the exact dynamic rule derived from the current label:</p>
<pre class="weour-build-code"><code class="language-css">@page { size: &lt;width-mm&gt; &lt;height-mm&gt;; margin: 0; }</code></pre>
<p>There is deliberately no 99% scale, no subtraction of 1mm from either edge and no A4 “fit to page” workaround. Those techniques might remove the blank page, but they would also change the output contract that matters most. An 80×80mm label must remain physically 80×80mm.</p>
<p>The editor and export preview can be enlarged to 150% to make the work surface easier to use, but that is presentation only. Canonical geometry and output pixels stay fixed: 100×100mm still exports as 1000×1000px, and 70×20mm still exports as 700×200px. Looking larger on screen is not the same as printing larger on paper.</p>
<h2 id="proving-it-was-a-pagination-fix-not-a-lucky-exception">Proving it was a pagination fix, not a lucky exception</h2>
<p>Verification could not stop at 80×80mm. A special case for one size can create a convincing but fragile “fix.” Chromium pagination checks covered 70×20, 70×40, 80×60, 80×80 and 100×100mm, plus a custom 93×57mm label. All six produced exactly one page.</p>
<p>The automated environment could not directly read the page counter in the native macOS print-preview UI. The evidence boundary matters: the automation proved single-page Chromium print documents across that six-size matrix; it did not pretend to inspect a native UI value that was unavailable to it.</p>
<p>The last step happened outside the browser. After the print hotfix was validated in Production, the production-validated branch was reconciled back to GitHub <code class="weour-build-inline-code">main</code>, leaving the source tree and the Production tree identical. That closure prevents a solved production bug from returning the next time an incomplete source branch is deployed.</p>
<blockquote class="weour-build-quote">“It downloads” is not done, and “it prints” is not done either. It is done when the data source, filename, physical dimensions, pagination behaviour and production source all agree.</blockquote>]]></content:encoded>
			<category>Engineering</category>
			<category>Data Modeling</category>
			<category>Build in Public</category>
		</item>
		<item>
			<title>We Stopped Building Another ERP. Here&#039;s What WEOUR OS Is Becoming.</title>
			<link>https://weour.com/build/we-stopped-building-another-erp/</link>
			<guid isPermaLink="true">https://weour.com/build/we-stopped-building-another-erp/</guid>
			<pubDate>Thu, 08 Oct 2026 00:00:00 +0000</pubDate>
			<description><![CDATA[WEOUR OS is no longer trying to become a complete cross-border ERP. It is becoming the layer above ERP and marketplace systems that turns their data into trustworthy, explainable and actionable operating decisions.]]></description>
			<content:encoded><![CDATA[<p>In September we wrote that we had not bought another ERP and were building a commerce operating system instead. This note records what happened next: on 2026-10-03 we decided to stop building the parts of that system that an ERP already does well.</p>
<p>This is a change in architecture and product strategy. It is not the abandonment of WEOUR OS.</p>
<dl class="weour-build-facts"><div class="weour-build-fact"><dt>Decision date</dt><dd>2026-10-03</dd></div><div class="weour-build-fact"><dt>What stops</dt><dd>Rebuilding order, inventory, purchasing, warehouse and finance functions that mature ERP systems already provide</dd></div><div class="weour-build-fact"><dt>Systems of record</dt><dd>ERP and marketplace systems, where they already own the data</dd></div><div class="weour-build-fact"><dt>What WEOUR OS becomes</dt><dd>Multi-platform operating data · trustworthy analytics · decision intelligence · control tower · AI agents</dd></div><div class="weour-build-fact"><dt>What this note is</dt><dd>A strategy decision, not a release announcement</dd></div></dl>
<h2 id="where-we-started">Where we started</h2>
<p>WEOUR OS began with a broad ambition. We wanted one system that understood stores, listings, products, warehouses and stock positions the way our business actually works across several marketplaces. Earlier notes in this log describe that work: stores that share a catalogue without sharing inventory, real warehouse locations, an inventory ledger, and the rule that an unknown stock value must never quietly become zero.</p>
<p>That work was not wasted. It taught us where the hard problems are. It also showed us, more clearly than any planning document could, how much of a full Commerce OS is not hard in a new way.</p>
<h2 id="what-changed">What changed</h2>
<p>Once we were building it rather than describing it, a pattern became obvious. Orders, inventory movements, purchasing, warehousing and finance are large, careful and well-understood domains. Mature ERP systems, Lingxing among them, already cover a large share of the established core capabilities in these domains, along with a great deal of edge-case handling learned from real businesses.</p>
<p>Every week spent re-implementing those functions was a week spent catching up with software that already existed. At best we would arrive at a second copy of something we could already use. At worst we would create a second source of truth for the same order or the same stock position, which is exactly the kind of ambiguity WEOUR OS exists to remove.</p>
<p>So we stopped. WEOUR OS will not rebuild commodity ERP functionality unless there is a documented reason the existing system cannot meet a specific need.</p>
<h2 id="why-this-is-the-right-trade">Why this is the right trade</h2>
<p>Engineering time is the scarcest thing we have. The useful question is not what we could build, but where building creates something nobody else gives us.</p>
<p>An ERP is very good at recording what happened inside its own scope. A marketplace is very good at reporting what happened on its own platform. Neither is designed to answer the questions that run a multi-platform business day to day: which numbers can be trusted right now, where the platforms disagree, which exception deserves attention first, and what we should do about it.</p>
<p>Those questions sit between systems. That is where WEOUR OS can be genuinely different.</p>
<h2 id="what-weour-os-is-becoming">What WEOUR OS is becoming</h2>
<p>The shape of the system changes from one more all-in-one commerce system to a layer on top of the systems we already rely on:</p>
<pre class="weour-build-code"><code>ERP + marketplaces  →  WEOUR OS intelligence and control layer  →  human and agent decisions</code></pre>
<p>In practice, this defines five directions for what we build next. They are directions, not five capabilities that are already finished:</p>
<ul class="weour-build-list"><li><strong>Multi-platform operating data.</strong> Data from the ERP and from each marketplace is brought into one normalised operating view, while keeping the original record and where it came from.</li><li><strong>Trustworthy analytics.</strong> Every figure carries its freshness and completeness. Partial data is shown as partial, and unknown is never presented as zero.</li><li><strong>Decision intelligence.</strong> Profitability, exceptions and forecasts are framed as decisions to make, not as more reports to read.</li><li><strong>Control tower.</strong> One place to see the state of the business across platforms, and what needs attention first.</li><li><strong>AI agents.</strong> Agents work on verified data and act within explicit permission and audit boundaries. High-risk operations and key business decisions remain subject to human authorization.</li></ul>
<p>ERP and marketplace systems remain systems of record where that is appropriate. For now, our connection to the ERP starts as read-only: WEOUR OS reads, explains and cross-checks the data, and does not write back into the system that owns the record. This is where the integration begins, not a sign that it is complete.</p>
<h2 id="what-this-does-not-mean">What this does not mean</h2>
<p>It does not mean WEOUR OS is starting over. The work on identities, inventory semantics, provenance and release safety carries forward. It is the foundation that makes cross-platform data trustworthy in the first place.</p>
<p>It also does not mean the new layer is finished. Much of the decision intelligence, control tower and agent capability described here is direction, not a production-ready feature. When those pieces ship, they will be recorded in this log with the same evidence standard as everything else.</p>
<blockquote class="weour-build-quote">The value of WEOUR OS is not owning the most features. It is turning data from different systems into decisions that can be trusted, explained and acted on.</blockquote>
<aside class="weour-build-callout"><p>Building less of what already exists is how we get to build more of what does not.</p></aside>
<p>WEOUR OS does not need to replace the ERP. It needs to become the trustworthy intelligence layer between the ERP, the platforms and the people who run the business.</p>]]></content:encoded>
			<category>WEOUR OS</category>
			<category>Architecture</category>
			<category>Data Quality</category>
			<category>Build in Public</category>
		</item>
		<item>
			<title>Connecting Yandex orders to unified business analytics: an evidence-first build note</title>
			<link>https://weour.com/build/weour-os-program-milestone-yandex-ozon-yandex/</link>
			<guid isPermaLink="true">https://weour.com/build/weour-os-program-milestone-yandex-ozon-yandex/</guid>
			<pubDate>Fri, 02 Oct 2026 00:00:00 +0000</pubDate>
			<description><![CDATA[This event connected the order facts of two Yandex stores to the business analytics definitions shared with Ozon, and clarified the scheduler boundary as "code deployed, but not enabled." This note records only evidence that can be traced back, and what it does not mean.]]></description>
			<content:encoded><![CDATA[<h2 id="why">Why</h2>
<p>Before this, multi-platform integration and catalog evidence, Yandex data ingestion, and unified business analytics definitions were advanced separately. The goal of this event was to connect them onto the same chain: to have Ozon and Yandex share the same order facts, rather than each maintaining its own definitions.</p>
<p>Another motivation was to make the boundary clear. The scheduler-related code has entered the deployed artifact, but &quot;code deployed&quot; and &quot;running in production as planned&quot; are two different things. This record deliberately leaves the latter blank, to avoid writing a not-yet-enabled capability as a completed running state.</p>
<h2 id="what-changed">What changed</h2>
<p>The changes centered on channel integration, business analytics, and the Yandex platform adaptation: the console&#039;s channel and insights pages, the analytics library, the channel library, the Yandex platform adaptation layer, and the server-side logic.</p>
<p>On the data side, a foreign key constraint was added to the catalog, to protect observation evidence while not locking down the task rows themselves — this constraint has a corresponding test assertion. On the channel side, a round of RC fixes was made, covering store onboarding-related acceptance, time zones, shared secrets, and the PII allowlist.</p>
<p>On the documentation side, two deployments were recorded: one is the code deployment and P5 production observation of Yandex scheduler V1, and the other is the Yandex analytics main-chain integration. Both deployments are recorded in the overview document.</p>
<p>For specific versions, commits, deployed artifacts and digests, test counts, runtime state, and corpus counts, see the facts above.</p>
<dl class="weour-build-facts"><div class="weour-build-fact"><dt>Version</dt><dd>20260926-yandex-sched-add2ba4 / 20260926-yaxan-1771e0c</dd></div><div class="weour-build-fact"><dt>Main release commit</dt><dd>025b05e54469a5e8ab636915322cbe478f73131e</dd></div><div class="weour-build-fact"><dt>Production provenance commit</dt><dd>025b05e54469a5e8ab636915322cbe478f73131e</dd></div><div class="weour-build-fact"><dt>Deployed artifact and digest</dt><dd>20260926-yandex-sched-add2ba4 (recorded in docs/00-overview/CURRENT_STATE.md); 20260926-yaxan-1771e0c (recorded in docs/00-overview/CURRENT_STATE.md)</dd></div><div class="weour-build-fact"><dt>Test counts</dt><dd>see the catalog foreign key assertion in commit df4bb40802d4f0ee6af67d3bf7e09d74d39e7b5e</dd></div><div class="weour-build-fact"><dt>Runtime state</dt><dd>scheduler code deployed, not enabled</dd></div><div class="weour-build-fact"><dt>Current authoring corpus</dt><dd>8 notes / 24 variants (of which 1 is still unpublished)</dd></div></dl>
<h2 id="how-we-verified-it">How we verified it</h2>
<p>Verification had three layers. The first layer is the commit itself: the main release commit and the production provenance commit point to the same SHA, showing that the record and the deployed artifact match up.</p>
<p>The second layer is tests: the catalog foreign key change carries an assertion, verifying that it protects observation evidence while not trapping the task rows.</p>
<p>The third layer is documentation: the two deployments each have a release id and a recorded date, and land in the overview document; the architecture document separately describes the design of Yandex order scheduler V1.</p>
<h2 id="what-it-does-not-mean">What it does not mean</h2>
<p>It does not mean the scheduler is already running in production as planned. Code entering the deployed artifact, and the scheduler being enabled, scheduled, and observed to run stably, are conclusions at different levels.</p>
<p>It also does not mean multi-platform integration has been wrapped up. This event connected the order facts of Ozon and Yandex to the same definitions, but the scope of integration, subsequent platforms, and coverage of operational processes are not within this evidence.</p>
<p>It likewise does not mean the corpus is complete. As of the current repository there are still unpublished notes; the corpus count is the repository state captured at the time of writing, and later repository growth will not change the meaning of this number.</p>]]></content:encoded>
			<category>Build in Public</category>
		</item>
		<item>
			<title>We paused new features to give WEOUR OS a production safety net</title>
			<link>https://weour.com/build/weour-os-building-the-safety-net/</link>
			<guid isPermaLink="true">https://weour.com/build/weour-os-building-the-safety-net/</guid>
			<pubDate>Wed, 23 Sep 2026 00:00:00 +0000</pubDate>
			<description><![CDATA[This round of WEOUR OS added no features; instead it closed four gaps: release retention, a backup loop, the scheduler's time budget, and a canonical release pipeline. This note records what changed, what the evidence was at the time, and what it does not mean.]]></description>
			<content:encoded><![CDATA[<p>This is not a feature release. It records why WEOUR OS paused new features to add a layer of production safety net: backup, scheduling, release, rollback.</p>
<dl class="weour-build-facts"><div class="weour-build-fact"><dt>Scope of this round</dt><dd>Production infrastructure (record of this round of work): backup · scheduling · release · rollback</dd></div><div class="weour-build-fact"><dt>Backup cadence</dt><dd>Every 6 hours (launchd timer)</dd></div><div class="weour-build-fact"><dt>Backup success contract</dt><dd>Four gates: stream / decrypt / structure / durable</dd></div><div class="weour-build-fact"><dt>Backup health tiers</dt><dd>HEALTHY ≤ 7 hours · DEGRADED ≤ 12 hours · STALE &gt; 12 hours (FAILED means a single attempt failed, not a tier)</dd></div><div class="weour-build-fact"><dt>Scheduler stagger budget</dt><dd>Default 30 seconds (floor 0 / ceiling 60), decoupled from the tick interval</dd></div><div class="weour-build-fact"><dt>Scheduler tick P50 (4 stores)</dt><dd>119.5 s → 31.9 s</dd></div><div class="weour-build-fact"><dt>Scheduler tick P95 (4 stores)</dt><dd>369.9 s → 55.3 s</dd></div><div class="weour-build-fact"><dt>Scheduler duty cycle</dt><dd>83.7% → 17.1%</dd></div><div class="weour-build-fact"><dt>Scheduler acceptance sample</dt><dd>11 ticks (4 stores)</dd></div><div class="weour-build-fact"><dt>First canonical deployment Trial</dt><dd>Failed on 2026-09-23, rollback executed by the pipeline</dd></div><div class="weour-build-fact"><dt>Production validation for this round</dt><dd>release 20260923-rpg13-7f685ca (CANONICAL, validation / smoke PASS)</dd></div><div class="weour-build-fact"><dt>Release governance test cases</dt><dd>123 → 157</dd></div><div class="weour-build-fact"><dt>Retention state at this round&#039;s observation point</dt><dd>release 19 · deletion candidates 1 · unknown provenance 6 · protected 5 (retention dry-run)</dd></div><div class="weour-build-fact"><dt>Scheduler correction commit</dt><dd>Anxin360/weour-os@e92b059</dd></div><div class="weour-build-fact"><dt>readiness / iowait correction commit</dt><dd>Anxin360/weour-os@d4cbe01</dd></div><div class="weour-build-fact"><dt>Foundation V1.1 trial branch tip</dt><dd>Anxin360/weour-os@b8cc817</dd></div><div class="weour-build-fact"><dt>Governance V1.3 commit</dt><dd>Anxin360/weour-os@7f685ca</dd></div></dl>
<h2 id="why-we-paused-new-features-first">Why we paused new features first</h2>
<p>WEOUR OS is already running real data in production: real stores, a real scheduling loop, real release records. The more features there are, the higher the cost of a mistake; but before this, several things had never really been closed out:</p>
<ul class="weour-build-list"><li>Immutable releases only accumulate, and the disk does not clean itself;</li><li>Backups existed, but &quot;did this backup actually succeed&quot; was never automatically checked, and there was no health signal that could be consumed automatically;</li><li>The scheduler looked busy, but most of that busyness was not business work.</li></ul>
<p>None of these are feature problems; they are problems with how the system runs. Fixing them first makes every feature added afterwards cheaper.</p>
<h2 id="what-changed">What changed</h2>
<h3 id="1-release-retention-immutable-releases-need-governed-cleanup">1. Release retention: immutable releases need governed cleanup</h3>
<p>Releases are immutable whole builds: switching means pointing a symlink at the new release directory, and rollback means pointing it back at the old one. This design makes rollback cheap, at the cost of a disk that keeps growing — at audit time, release directories accounted for about 74% of the host&#039;s used disk (44.12 GB / 59.44 GB used), while the same disk also had to leave headroom for builds.</p>
<p>So cleanup was written as a classification priority, not &quot;delete the oldest&quot;:</p>
<pre class="weour-build-code"><code>CURRENT &gt; IMMEDIATE_ROLLBACK &gt; DIAGNOSTIC &gt; FAILED_TRIAL &gt; VALIDATED_ANCHOR
      &gt; MIGRATION_BOUNDARY &gt; MANUAL_PROTECT &gt; UNKNOWN_PROVENANCE &gt; ROUTINE</code></pre>
<p>Only <code class="weour-build-inline-code">ROUTINE</code> enters routine quota cleanup; no other category is ever swept away in passing. One rule matters especially: the release of a failed Trial is not downgraded to <code class="weour-build-inline-code">ROUTINE</code> just because its commit later landed on <code class="weour-build-inline-code">main</code>. Classification rests only on lifecycle evidence, not on directory names, timestamps, or chat logs.</p>
<h3 id="2-backup-from-there-is-a-backup-to-a-backup-that-can-be-checked">2. Backup: from &quot;there is a backup&quot; to &quot;a backup that can be checked&quot;</h3>
<p>The shape of backup was not rebuilt from scratch: the backup host pulls, production only exposes a restricted forced-command stream, and the decryption private key never enters the production host. The changes happened on the pull side:</p>
<ul class="weour-build-list"><li><strong>Cadence and RPO</strong>: every 6 hours (launchd timer), with a target RPO of 6 hours. The cadence is explainable and does not depend on a drifting interval timer.</li><li><strong>Four gates</strong>: a run counts as successful only after all four gates — stream, decrypt, structure, durable — pass. A <code class="weour-build-inline-code">pg_dump</code> exit code of 0 was never success by itself.</li><li><strong>Failure is failure</strong>: if any gate fails, the run is <code class="weour-build-inline-code">FAILED</code>, <code class="weour-build-inline-code">last-success</code> is not updated, and cleanup does not happen.</li><li><strong>Health tiers</strong>: <code class="weour-build-inline-code">HEALTHY</code> (≤ 7 hours) / <code class="weour-build-inline-code">DEGRADED</code> (≤ 12 hours) / <code class="weour-build-inline-code">STALE</code> (&gt; 12 hours) are derived from hours since last success and can be consumed directly by monitoring as an exit code; <code class="weour-build-inline-code">FAILED</code> describes a single attempt, not a tier.</li><li><strong>Failure-injection tests</strong>: covering remote failure, undersized artifact, decryption failure, invalid structure, overlapping runs, stale lock, retention policy, and corrupted health state.</li></ul>
<p>The last step was wiring the health signal back into production: after the four gates pass, the backup host pushes the snapshot to production through a restricted identity that can write only one fixed file — it cannot switch releases, cannot restart services, and cannot read any key.</p>
<h3 id="3-scheduler-separating-the-semantics-of-tick-and-stagger">3. Scheduler: separating the semantics of tick and stagger</h3>
<p>Diagnosis first produced an uncomfortable conclusion: over the run window with 4 stores, 96.8% of tick wall-clock time was spent in deterministic stagger sleeps — 84 seconds of real work out of 2610 seconds. Counted per action, the sleeps alone came to 4150 seconds/hour, close to 115% of wall clock.</p>
<p>The root cause was mixed semantics: the stagger window was bound to the tick interval (180 seconds), and the fixed sleeps of multiple actions on the same serial runner were accumulated serially.</p>
<p>The correction separated the two: the tick interval is used only for reporting cadence and no longer participates in stagger computation; stagger has its own budget, default 30 seconds, selectable 0 / 30 / 60, with invalid values falling back to the default. Measured results after deployment:</p>
<ul class="weour-build-list"><li>tick P50 119.5 s → 31.9 s, P95 369.9 s → 55.3 s;</li><li>duty cycle 83.7% → 17.1%;</li><li>planned sleep matches actual sleep, and the remaining time is explainable API and DB work;</li><li>timer, cadence, store switches, and concurrency budget were not changed.</li></ul>
<h3 id="4-one-canonical-release-pipeline">4. One canonical release pipeline</h3>
<p>Release is no longer a string of manual commands but an ordered, reproducible pipeline:</p>
<pre class="weour-build-code"><code>provenance → preflight → release lock → scheduler quiet window → source
  → build backend → monitored build → immutable release + manifest
  → candidate validation → rollback readiness → atomic switch → restart
  → readiness → functional smoke → state write → history → retention dry-run</code></pre>
<p>Several parts are deliberate: builds happen only inside the scheduler&#039;s quiet window; the switch is atomic; the rollback target is verified once before and once after the switch; and the exit code of <code class="weour-build-inline-code">systemctl restart</code> is explicitly recorded as &quot;unit started&quot;, not &quot;application ready&quot;.</p>
<h3 id="5-the-first-trial-failed-and-was-preserved-in-full">5. The first Trial failed, and was preserved in full</h3>
<p>The first canonical deployment Trial ran through its own success path, then failed at the smoke stage, and the pipeline executed its own canonical rollback.</p>
<p>The cause was not a problem with the application: <code class="weour-build-inline-code">systemctl restart</code> returning only means the unit started, not that the port is bound. The smoke at the time was an immediate HTTP check with no readiness wait, so it observed connection refused and gateway errors.</p>
<p>The handling is the point: nobody bypassed the pipeline to fix this step by hand. The pipeline stopped according to its own failure contract and executed the rollback, that release was preserved for diagnosis, and the cleanup stage performed not a single deletion. Production kept serving normally on the pre-switch release.</p>
<p>The correction released afterwards turned readiness into bounded polling and made build monitoring actually measure iowait. On the re-run, the earlier failure mode was correctly absorbed by the new logic as transient unreadiness rather than a deployment failure.</p>
<h3 id="6-governance-leaving-evidence-for-both-who-may-release-and-what-was-released">6. Governance: leaving evidence for both &quot;who may release&quot; and &quot;what was released&quot;</h3>
<p>The rules added this round are for our future selves:</p>
<ul class="weour-build-list"><li><strong>canonical deploy only</strong>: day-to-day releases go only through the pipeline; exceptions must be explicitly marked.</li><li><strong><code class="weour-build-inline-code">deploymentMethod</code></strong>: every new lifecycle record carries a method enum, history is appended rather than rewritten, and records that cannot be determined are marked <code class="weour-build-inline-code">UNKNOWN_LEGACY</code>, never guessed as <code class="weour-build-inline-code">CANONICAL</code>.</li><li><strong>Failed Trial classification</strong>: <code class="weour-build-inline-code">FAILED_TRIAL</code> is determined by lifecycle evidence and does not consume routine cleanup quota.</li><li><strong>Diagnostic evidence first</strong>: before deletion, a KB-level diagnostic record containing no keys must be written back and verified; if it cannot be written, deletion is refused.</li></ul>
<p>At this round&#039;s observation point (production release <code class="weour-build-inline-code">20260923-rpg13-7f685ca</code>), the retention policy was presented as a dry-run: the record showed 19 releases, of which 1 deletion candidate, 6 unknown provenance, and 5 protected. Cleanup happens only when the evidence is complete (the target is confirmed unreferenced, and the diagnostic record has been written back and verified).</p>
<h2 id="how-we-verified-it">How we verified it</h2>
<p>At every step, verification was anchored as far as possible in reviewable evidence rather than &quot;it looks fine&quot;:</p>
<ul class="weour-build-list"><li>Backup was verified by failure-injection tests and real four-gate runs, and the health signal is consumed by monitoring;</li><li>The scheduler correction was first simulated in an isolated environment with measured offsets, then compared before and after deployment using 11 real ticks;</li><li>The release pipeline was verified by letting it run one real release end to end: readiness passed, functional smoke passed on six routes, state was written by the pipeline, and only then was the retention dry-run performed;</li><li>Governance rules are covered by test cases, from &quot;a failed Trial is not downgraded&quot; to &quot;the releaser rejects anomalous payloads&quot;.</li></ul>
<h2 id="what-it-does-not-mean">What it does not mean</h2>
<p>It is not a feature release, and it does not mean the infrastructure is finished.</p>
<p>In this round&#039;s record, the limitations of backup are written down explicitly: there is only one decryption key and it sits on the same machine as the ciphertext; there is no second off-site copy; there is no PITR, so the RPO is the 6-hour cadence; media backups have no retention policy; and roles and global objects are not in the dump. These are known trade-offs, not overlooked details.</p>
<p>The scheduler correction only addresses where time is spent; it is not a concurrency or performance optimization.</p>
<p>Governance does not cover all history either: at this round&#039;s observation point there were still old releases whose provenance could not be determined, and they were conservatively retained.</p>
<p>This note belongs to the round of work from 2026-09-22 to 2026-09-23. The production states in it are facts <strong>at this round&#039;s observation point</strong>, not the state at any later moment; subsequent production changes are outside the scope of this note.</p>
<p>Finally, it is not a promise about future versions, timelines, or capabilities. What is recorded here is an infrastructure upgrade that already happened, and the evidence supporting it.</p>
<h2 id="where-the-evidence-is">Where the evidence is</h2>
<p>Every factual statement in this note points to an object that can be looked up: the correspondence between commits, deployment release identifiers, and test case counts is registered in the facts table above and in this note&#039;s evidence entries. The body of this note only explains meaning and does not repeat the identifiers.</p>
<aside class="weour-build-callout"><p>The value of a safety net is not that it was built, but that when it actually stops a failure, nobody has to decide on the spot what to do.</p></aside>]]></content:encoded>
			<category>WEOUR OS</category>
			<category>Engineering</category>
			<category>Architecture</category>
			<category>Build in Public</category>
		</item>
		<item>
			<title>We turned Build content publishing into a verifiable pipeline</title>
			<link>https://weour.com/build/weour-core-1-3-5-release/</link>
			<guid isPermaLink="true">https://weour.com/build/weour-core-1-3-5-release/</guid>
			<pubDate>Tue, 22 Sep 2026 00:00:00 +0000</pubDate>
			<description><![CDATA[This event narrowed the Build content publishing path from manual operation into a pipeline with preview, CI gates, and a content contract, validated in production. Below is what it changed, what the evidence is, and what it does not mean.]]></description>
			<content:encoded><![CDATA[<h2 id="why">Why</h2>
<p>Before this, publishing Build content relied on manual judgment: whether content was compliant, whether its structure was complete, and whether the runtime could read it correctly after publishing all lacked a unified checkpoint. Once the content format diverged from runtime expectations, problems often only surfaced after publishing.</p>
<p>The goal of this change was not to add features, but to turn &quot;can this content be published&quot; into a question that can be answered before publishing: content first passes contract validation, then CI gates, and only then enters the publishing flow.</p>
<h2 id="what-changed">What changed</h2>
<p>The changes centered on several interlocking parts: workflows, the content directory, documentation, the runtime content module, scripts, and tests.</p>
<p>On the content side, schema and topics definitions were introduced, making the relationship between notes, variants, and topics explicit; the script side validates content against the contract; the test side covers both the PHP and Node check paths; the workflow side strings preview and gates together so that checks must pass before publishing.</p>
<p>The release itself concluded with a production deployment; the deployment record was written into the repository documentation and tagged with the corresponding validation tag. For the specific version, commit, artifact digest, test counts, and corpus counts, see the facts above.</p>
<h2 id="how-we-verified-it">How we verified it</h2>
<p>Verification had two layers. The first layer is automated checks: the PHP and Node check suites run in CI, with several gates, and only when all pass is it allowed to continue. The second layer is production verification: after the artifact was deployed to production, the runtime reported its own state and the scale of content it loaded.</p>
<p>Two corpus figures from different points in time need to be distinguished:</p>
<ul class="weour-build-list"><li>Production corpus at the time of the 1.3.5 deployment | 5 notes / 15 variants / 13 topics</li><li>Current authoring corpus as of the current repository state | 6 notes / 18 variants (1 of which is still unpublished)</li></ul>
<p>The former is the runtime snapshot at deployment time; the latter is the repository&#039;s current content state. It is normal for the two to differ: the repository can contain drafts that have not yet been published.</p>
<dl class="weour-build-facts"><div class="weour-build-fact"><dt>Version</dt><dd>1.3.5</dd></div><div class="weour-build-fact"><dt>Main release commit</dt><dd>57bf785a0bcd6e886397de776cbe7bd7156fd64c</dd></div><div class="weour-build-fact"><dt>Production provenance</dt><dd>4b4ec32a3aac70a8222ca7255f0db38fb6683200</dd></div><div class="weour-build-fact"><dt>Deployed artifact</dt><dd>weour-core-1.3.5.zip</dd></div><div class="weour-build-fact"><dt>Artifact SHA-256</dt><dd>f660c0d9b48cbe774a3ba10d380eedfbf9a4fee5cf1a49c97c024ee7c1ee28ab</dd></div><div class="weour-build-fact"><dt>Plugin manifest SHA-256</dt><dd>952f570124787702fc8c71ce6ebd5410dca3099730b212d805189f3764d2a703</dd></div><div class="weour-build-fact"><dt>Deployment environment</dt><dd>production</dd></div><div class="weour-build-fact"><dt>Deployment state</dt><dd>deployed</dd></div><div class="weour-build-fact"><dt>Runtime state</dt><dd>ready</dd></div><div class="weour-build-fact"><dt>Runtime version</dt><dd>1.3.5</dd></div><div class="weour-build-fact"><dt>Runtime module count</dt><dd>12</dd></div><div class="weour-build-fact"><dt>Test counts</dt><dd>PHP 1100 checks / Node 97 checks</dd></div><div class="weour-build-fact"><dt>Gate count</dt><dd>5</dd></div><div class="weour-build-fact"><dt>Production corpus at deploy time</dt><dd>5 notes / 15 variants / 13 topics</dd></div><div class="weour-build-fact"><dt>Current authoring corpus</dt><dd>6 notes / 18 variants (1 of which is still unpublished)</dd></div></dl>
<h2 id="what-it-does-not-mean">What it does not mean</h2>
<p>It does not mean content publishing no longer needs manual judgment. The pipeline checks the contract and structure; it does not check whether the content itself is appropriate or worth publishing.</p>
<p>It does not mean everything in the repository is live. The repository&#039;s current content count is greater than the runtime count at deployment time; the difference comes from drafts that have not yet been published.</p>
<p>It also does not mean this flow already covers all future content forms. This validation targeted the current content contract and runtime read path; forms outside the contract are not within the scope of this validation.</p>
<p>Finally, it is not a commitment to future versions, timelines, or capabilities. What is recorded here is a release and validation that already happened, and the evidence supporting it.</p>]]></content:encoded>
			<category>Build in Public</category>
		</item>
		<item>
			<title>Ledger #1 — When WEOUR OS Started Knowing What We Actually Had</title>
			<link>https://weour.com/build/ledger-1-when-weour-os-started-knowing-what-we-had/</link>
			<guid isPermaLink="true">https://weour.com/build/ledger-1-when-weour-os-started-knowing-what-we-had/</guid>
			<pubDate>Mon, 21 Sep 2026 00:00:00 +0000</pubDate>
			<description><![CDATA[The first real physical count entered the central inventory ledger, and what that changed about the difference between knowing, observing and deciding.]]></description>
			<content:encoded><![CDATA[<p>For months the honest answer to &quot;what do we actually have?&quot; was a set of numbers that came from somewhere, in formats that did not agree, at times that did not line up. We knew what channels reported. We did not have a ledger.</p>
<p>This note is the moment that changed. Ledger #1 is the first entry in WEOUR OS where a real physical count became recorded inventory, in a form that can be audited later.</p>
<h2 id="what-existed-before-the-ledger">What existed before the ledger</h2>
<p>Two real warehouses were already modelled, and their active storage locations had been replaced with verified physical ones — the 1,306 locations described in the previous note. Marketplaces and channels were reporting what they could see. None of that is the same as WEOUR knowing.</p>
<p>Observed inventory is evidence. It is what a source told us, at a time, from its own point of view. A central ledger is different: it is where WEOUR decides what it holds, and it only earns that status by being traceable back to a physical fact.</p>
<h2 id="what-a-first-physical-count-actually-changes">What a first physical count actually changes</h2>
<p>A count is not a sync. It is a person standing in front of a location and confirming what is there. When that result is written into the ledger, three things become possible for the first time:</p>
<ul class="weour-build-list"><li>a number exists because someone counted it, with a scope, a time and a location behind it;</li><li>the difference between what the system previously showed and what was physically found becomes a recordable movement rather than a mystery;</li><li>later observations can be compared against a decision instead of against each other.</li></ul>
<dl class="weour-build-facts"><div class="weour-build-fact"><dt>Real warehouses in scope</dt><dd>2</dd></div><div class="weour-build-fact"><dt>Active storage locations</dt><dd>1,306</dd></div><div class="weour-build-fact"><dt>First entry</dt><dd>Opening balance from a physical count</dd></div><div class="weour-build-fact"><dt>Ledger movement</dt><dd><code class="weour-build-inline-code">OPENING_BALANCE +55</code></dd></div><div class="weour-build-fact"><dt>Automatic inventory writes</dt><dd>None</dd></div><div class="weour-build-fact"><dt>Approval requirement</dt><dd>Explicit, per movement class</dd></div></dl>
<p><code class="weour-build-inline-code">OPENING_BALANCE +55</code> is a bookkeeping movement, not a demand signal. It records that the counted reality and the ledger now agree on a first number in that scope. It is published here because it is exactly the kind of movement a central inventory system has to be able to explain months later — and because inventing a cleaner-looking number would have defeated the point.</p>
<h2 id="the-rules-that-made-it-possible">The rules that made it possible</h2>
<p>The count could only be entered because four rules already existed in the system:</p>
<ol class="weour-build-list"><li><code class="weour-build-inline-code">NOT_INITIALIZED ≠ 0</code>. A scope that has never been counted is not empty; it is unknown, and it stays unknown until someone counts it.</li><li><code class="weour-build-inline-code">NOT_REPORTED ≠ ZERO</code>. A channel that failed to report has not told us anything, and a failed report never becomes a shortage.</li><li><code class="weour-build-inline-code">Observed Inventory ≠ Central Inventory</code>. Channels keep reporting what they see. The ledger records what WEOUR has decided, after reconciliation.</li><li><code class="weour-build-inline-code">Candidate ≠ Approved</code>. A proposed movement is a proposal. It becomes inventory only through an explicit approval step, and never by automation filling in a gap.</li></ol>
<aside class="weour-build-callout"><p>The first ledger entry is not valuable because of its number. It is valuable because everything around it — scope, location, time, and approval — can be reconstructed later.</p></aside>
<h2 id="what-this-does-not-mean">What this does not mean</h2>
<p>It does not mean inventory is now &quot;solved&quot;. Two counted warehouses are two counted warehouses. The ledger is only as good as the next count, and the next scope is the one that has not been initialised yet.</p>
<p>It also does not mean the numbers are final. A physical count is a statement about a moment. The system has to keep the moment attached, so that a later difference is a difference between two known states rather than an argument about who is right.</p>
<h2 id="why-this-is-the-first-ledger-note">Why this is the first ledger note</h2>
<p>We are publishing the ledger series because it is the least glamorous and most load-bearing part of a commerce operating system. If the system cannot say what it knows, what it observed and what it decided — separately — then every other number above it is decoration.</p>
<p>Ledger #1 is the first entry where those three things stopped being the same thing.</p>]]></content:encoded>
			<category>Inventory</category>
			<category>Central Inventory</category>
			<category>Warehouse</category>
			<category>WEOUR OS</category>
			<category>Build in Public</category>
		</item>
		<item>
			<title>Unknown Inventory Is Not Zero</title>
			<link>https://weour.com/build/unknown-inventory-is-not-zero/</link>
			<guid isPermaLink="true">https://weour.com/build/unknown-inventory-is-not-zero/</guid>
			<pubDate>Sun, 20 Sep 2026 00:00:00 +0000</pubDate>
			<description><![CDATA[The WEOUR OS principle that uninitialised and unreported inventory is not the same as zero, and why observed inventory is never central inventory.]]></description>
			<content:encoded><![CDATA[<p>This is the rule that shapes more of WEOUR OS than any other, so it gets its own note.</p>
<dl class="weour-build-facts"><div class="weour-build-fact"><dt>NOT_INITIALIZED</dt><dd>The system has never been told what is here</dd></div><div class="weour-build-fact"><dt>NOT_REPORTED</dt><dd>A source that normally reports has not reported</dd></div><div class="weour-build-fact"><dt>ZERO</dt><dd>We know, from a specific source at a specific time, that the amount is none</dd></div><div class="weour-build-fact"><dt>Observed inventory</dt><dd>What a channel or warehouse reported, kept as evidence</dd></div><div class="weour-build-fact"><dt>Central inventory</dt><dd>What WEOUR has decided the truth is, after reconciliation</dd></div></dl>
<p>These are five different things. Systems that collapse them into one column are not simpler — they are just unable to tell you why a number is what it is.</p>
<h2 id="why-unknown-must-not-become-zero">Why &quot;unknown&quot; must not become &quot;zero&quot;</h2>
<p>Zero is a claim. It says a countable quantity is none. When the system has no information, writing zero makes an unknown fact look like a known one, and every downstream decision inherits the lie:</p>
<ul class="weour-build-list"><li>A storefront marks an item unavailable because a report failed, not because the item is gone.</li><li>A replenishment rule fires on a phantom shortage.</li><li>A reconciliation report shows a difference that is really a missing source, and the team spends a day chasing nothing.</li></ul>
<p>None of this looks like a bug. It looks like normal data.</p>
<h2 id="how-we-represent-it">How we represent it</h2>
<p>An unset inventory state is stored as an explicit state, not as a number. States are part of the model:</p>
<ul class="weour-build-list"><li><code class="weour-build-inline-code">NOT_INITIALIZED</code> — the record exists, and no source has ever provided a value.</li><li><code class="weour-build-inline-code">NOT_REPORTED</code> — a source is expected to report and has not yet, or has failed.</li><li>A numeric value — an actual claim, always attached to its source and its observed time.</li></ul>
<p>The display layer is allowed to be friendly. The storage layer is not allowed to guess.</p>
<h2 id="observed-inventory-is-not-central-inventory">Observed inventory is not central inventory</h2>
<p>The second half of the principle is that observations never become the ledger by accident.</p>
<p>Every marketplace and every warehouse reports something, and those reports answer different questions at different moments. Observed inventory is what we were told. Central inventory is what WEOUR has decided, after reconciling the observations, the physical counts, and the known gaps.</p>
<aside class="weour-build-callout"><p>The system may show you an observation. It will not pretend the observation is a decision.</p></aside>
<p>Keeping that boundary explicit is what makes multi-store selling honest: five stores can each report what they see without any of them silently rewriting what WEOUR owns.</p>
<h2 id="what-it-costs-and-why-we-pay-it">What it costs, and why we pay it</h2>
<p>This principle is inconvenient. It means more states, more explicit handling, and fewer places where a default value can quietly fill a hole. It means dashboards have to say &quot;unknown&quot; instead of showing a reassuring zero.</p>
<p>We pay that cost because the alternative is an operating system that is confidently wrong. A commerce OS earns trust by knowing what it does not know — and by saying so in the database, not only in the interface.</p>
<blockquote class="weour-build-quote"><code class="weour-build-inline-code">NOT_INITIALIZED ≠ 0</code>. <code class="weour-build-inline-code">NOT_REPORTED ≠ ZERO</code>. Observed inventory is not central inventory.</blockquote>]]></content:encoded>
			<category>Inventory</category>
			<category>Data Modeling</category>
			<category>WEOUR OS</category>
			<category>Engineering</category>
			<category>Build in Public</category>
		</item>
		<item>
			<title>1,306 Real Warehouse Locations</title>
			<link>https://weour.com/build/1306-real-warehouse-locations/</link>
			<guid isPermaLink="true">https://weour.com/build/1306-real-warehouse-locations/</guid>
			<pubDate>Tue, 15 Sep 2026 00:00:00 +0000</pubDate>
			<description><![CDATA[How placeholder storage locations were replaced by a verified warehouse master for the Suzhou and Shenzhen operations.]]></description>
			<content:encoded><![CDATA[<p>For a while, our warehouse data looked healthy. Every item had a storage location. Every location existed in the system. The numbers were consistent, and nothing failed.</p>
<p>The locations were placeholders.</p>
<h2 id="the-problem-with-numbers-that-are-merely-consistent">The problem with numbers that are merely consistent</h2>
<p>Placeholder locations are easy to generate and easy to trust. They fit the schema, they pass validation, and they produce charts. The failure only appears when someone has to physically pick something up, or when two different people describe the same shelf in two different ways and the system insists they are different places.</p>
<p>That is the trap: placeholder data does not look like missing data. It looks like data.</p>
<h2 id="replacing-the-placeholder-set-with-the-real-one">Replacing the placeholder set with the real one</h2>
<p>We rebuilt the warehouse master from how the facilities actually operate, then loaded it as the authoritative set of storage locations.</p>
<dl class="weour-build-facts"><div class="weour-build-fact"><dt>Verified storage locations</dt><dd>1,306</dd></div><div class="weour-build-fact"><dt>Facilities modelled</dt><dd>Suzhou, Shenzhen</dd></div><div class="weour-build-fact"><dt>Approach</dt><dd>Physical location first, system record second</dd></div><div class="weour-build-fact"><dt>Placeholder locations</dt><dd>Retired, not repaired</dd></div><div class="weour-build-fact"><dt>Unknown mapping</dt><dd>Kept unknown until a real location is confirmed</dd></div></dl>
<p>The count is not the point. The point is that every one of those 1,306 locations corresponds to a place a person can walk to, and that the two facilities are modelled on the same rules rather than two local conventions.</p>
<h2 id="what-the-change-surfaced">What the change surfaced</h2>
<p>Replacing placeholders with reality exposes everything that had been quietly leaning on the fake data:</p>
<ul class="weour-build-list"><li>Items that had a placeholder location and no real home needed a decision, not a default.</li><li>Legacy codes that meant different things at each facility had to be resolved instead of merged.</li><li>Any process that assumed every item is always somewhere had to tolerate &quot;we have not confirmed this yet&quot; as a legitimate state.</li></ul>
<p>That last point is the one that changed the system most, and it is the subject of the next note in this log.</p>
<h2 id="why-we-publish-this">Why we publish this</h2>
<p>A warehouse master is the least glamorous part of a commerce operation and one of the few parts that everything else depends on. Publishing it as a build note is a way of stating the standard we now hold ourselves to: storage locations are physical facts, and a system that invents them is worse than a system that admits it does not know.</p>
<blockquote class="weour-build-quote">A location record that nobody can walk to is not inventory data. It is decoration.</blockquote>]]></content:encoded>
			<category>Warehouse</category>
			<category>Inventory</category>
			<category>Data Quality</category>
			<category>WEOUR OS</category>
			<category>Engineering</category>
		</item>
		<item>
			<title>From 2 Ozon Stores to 5</title>
			<link>https://weour.com/build/from-2-ozon-stores-to-5/</link>
			<guid isPermaLink="true">https://weour.com/build/from-2-ozon-stores-to-5/</guid>
			<pubDate>Fri, 11 Sep 2026 00:00:00 +0000</pubDate>
			<description><![CDATA[Multi-store architecture, store isolation, and why opening a new storefront must never create a second copy of the same inventory.]]></description>
			<content:encoded><![CDATA[<p>We started with two Ozon stores. We now operate five. The interesting part of that change was not the number. It was discovering how many assumptions two stores let us keep.</p>
<p>With two stores, it is possible to hold the whole picture in one person&#039;s head. Which listing belongs to which store, which price belongs to which channel, and what happens when both stores try to sell the last unit are all answerable by memory. That stops working at three, and it fails loudly at five.</p>
<h2 id="store-isolation-is-a-boundary-not-a-copy">Store isolation is a boundary, not a copy</h2>
<p>The first instinct when adding a store is to duplicate. Copy the catalogue, copy the stock numbers, copy the mapping table, change the store identifier. It looks fast, and it creates a second version of the truth that will disagree with the first one within a week.</p>
<p>We took the opposite position:</p>
<dl class="weour-build-facts"><div class="weour-build-fact"><dt>Principle</dt><dd>What it means in practice</dd></div><div class="weour-build-fact"><dt>Store is an identity, not a stock bucket</dt><dd>A store owns listings, prices, and channel rules — not its own inventory ledger</dd></div><div class="weour-build-fact"><dt>Listing is a channel artefact</dt><dd>Two stores may list the same physical product under different listing identities</dd></div><div class="weour-build-fact"><dt>Product identity is shared</dt><dd>The thing being sold is described once and referenced everywhere</dd></div><div class="weour-build-fact"><dt>Stock is observed, never copied</dt><dd>A store reports what it sees; the system records the observation instead of inventing a balance</dd></div></dl>
<p>Adding a store now adds a channel identity. It does not add inventory.</p>
<h2 id="why-observed-inventory-instead-of-a-per-store-balance">Why &quot;observed inventory&quot; instead of a per-store balance</h2>
<p>Marketplaces tell you things at their own speed and in their own vocabulary. One channel reports sellable quantity after its own reservations. Another reports what it was told last night. Neither number is wrong, and neither number is the whole truth about the physical goods.</p>
<p>So we stopped asking each channel to be the source of a balance. A channel reports an observation: this is what I can see, from this source, at this time. Observations are recorded, compared, and reconciled — they are not silently merged into a single number that nobody can audit.</p>
<p>That one modelling decision is what makes five stores tractable instead of five times the maintenance.</p>
<h2 id="what-we-had-to-build-to-make-it-hold">What we had to build to make it hold</h2>
<p>Running five storefronts under one operating model required three things that were not obvious at two:</p>
<ul class="weour-build-list"><li>A single product identity that listings point at, so the same physical item is not re-described per channel.</li><li>A mapping layer between marketplace listing identities and our own SKUs, because a seller article and an internal identifier are not the same thing.</li><li>A resolver that answers &quot;which channels can sell this, and at what price&quot; without letting any single channel overwrite the shared record.</li></ul>
<p>None of this is exotic. All of it has to be enforced, because the failure mode is silent: a duplicated stock number still looks like a valid number.</p>
<blockquote class="weour-build-quote">The cost of multi-store is not the fifth store. It is the second copy of the truth that you did not notice you created.</blockquote>
<h2 id="what-changes-as-stores-are-added">What changes as stores are added</h2>
<p>With isolation held at the channel boundary, adding a store becomes an operational task instead of a data-migration project. The next store can go live with its own listing set, its own pricing rules, and its own reporting — while the products and the physical stock they reference stay where they were.</p>
<p>That is the property we were actually buying when we built the model. Not more stores. Fewer surprises per store.</p>]]></content:encoded>
			<category>Multi-Store</category>
			<category>Ozon</category>
			<category>Marketplace</category>
			<category>Architecture</category>
			<category>Inventory</category>
		</item>
		<item>
			<title>We Didn&#039;t Buy Another ERP. We&#039;re Building a Commerce OS.</title>
			<link>https://weour.com/build/we-didnt-buy-another-erp/</link>
			<guid isPermaLink="true">https://weour.com/build/we-didnt-buy-another-erp/</guid>
			<pubDate>Sun, 06 Sep 2026 00:00:00 +0000</pubDate>
			<description><![CDATA[Why WEOUR stopped treating multi-channel commerce as a software purchasing problem and started building a commerce operating system instead.]]></description>
			<content:encoded><![CDATA[<p>For a long time the question in front of us looked like a purchasing decision. Sales channels were multiplying, stock was spread across marketplaces and warehouses, and the honest answer to &quot;how much of this do we actually have?&quot; depended on who you asked. The obvious move was to buy a bigger system.</p>
<p>We tried to look at it that way. We compared platforms that could hold more SKUs, more stores, and more integrations. Every one of them answered a version of the question. None of them answered ours.</p>
<h2 id="the-problem-was-never-the-software-category">The problem was never the software category</h2>
<p>An ERP is built around a company that already knows what it owns. It assumes a clean master record, one place where the truth lives, and processes that mostly run inside one organisation. That model works well when your business is one catalogue sold through one or two channels.</p>
<p>Our reality is different. The same physical product lives in several marketplaces under different listing identities, arrives from different supply routes, and reports stock at different times in different formats. A purchase order, a marketplace listing, and a warehouse location are three different objects with three different lifecycles. Software that flattens them into one row is not wrong — it is just answering a simpler question than the one we have.</p>
<h2 id="what-we-are-building-instead">What we are building instead</h2>
<p>WEOUR OS is a commerce operating system: a set of systems that describe how goods, listings, warehouses, and decisions relate to each other, and that keep those relationships honest while the business runs.</p>
<dl class="weour-build-facts"><div class="weour-build-fact"><dt>Layer</dt><dd>Responsibility</dd></div><div class="weour-build-fact"><dt>Channel</dt><dd>Each store keeps its own identity, pricing rules, and listing lifecycle</dd></div><div class="weour-build-fact"><dt>Inventory</dt><dd>Observations are recorded with source and time, not silently merged</dd></div><div class="weour-build-fact"><dt>Warehouse</dt><dd>Physical locations are modelled as first-class records</dd></div><div class="weour-build-fact"><dt>Mapping</dt><dd>SKU, listing, and product identities are linked, never assumed identical</dd></div><div class="weour-build-fact"><dt>Agents</dt><dd>Automation proposes and executes against explicit system rules</dd></div></dl>
<p>Each layer can be replaced. The relationships cannot.</p>
<h2 id="built-in-production-not-in-a-slide-deck">Built in production, not in a slide deck</h2>
<p>The reason this is published as a build log rather than an announcement is that we are not starting from a whiteboard. We are changing systems that already carry real stores, real warehouses, and real listings. Every design decision has to survive contact with a live operation on the same day it is made.</p>
<p>That constraint is uncomfortable and useful. It rules out elegant architectures that cannot be deployed incrementally, and it forces us to write down the rules that the business already follows implicitly but has never had to state.</p>
<h2 id="what-this-means-for-the-storefront-you-are-looking-at">What this means for the storefront you are looking at</h2>
<p>weour.com is not being replaced by a new website. It is one of the systems. The marketplace storefront, the Russian and Chinese market channels, the supplier workflows, and the internal operating layer are being brought under the same model of what a store, a product, and a stock position actually are.</p>
<blockquote class="weour-build-quote">An operating system for global commerce is not a bigger database. It is a smaller number of rules that everything else can be trusted to follow.</blockquote>
<h2 id="what-comes-next">What comes next</h2>
<p>The next notes in this log go into the parts that were hardest to get right: how stores stay isolated while sharing one catalogue, why adding a store must not duplicate inventory, what happened when placeholder warehouse locations met real ones, and why an unknown stock value is never allowed to quietly become zero.</p>
<p>That is the build. We will keep publishing it as it happens.</p>]]></content:encoded>
			<category>WEOUR OS</category>
			<category>Global Commerce</category>
			<category>Architecture</category>
			<category>Build in Public</category>
		</item>
	</channel>
</rss>
