Strict Mode gives a design system's rules authority over a pull request: a versioned artifact a build refuses to proceed without, and a check whose exit code decides whether a change ships. What follows is the month between its first commit and the day it left the repository it was built in, and the failures along the way that decided what the artifact had to guarantee.
The name is older than the product. Strict Mode was a desktop tool for making AI coding agents behave, and it is shelved. What carried over is the conviction inside it, that output has to be constrained by something structural instead of corrected after the fact. This study is about the second thing I built on that idea, which is the one that works.
I directed the build. AI agents wrote the code under that direction, and separate AI agents reviewed it.
Form Logic has its own study and it asks the earlier question: what a visual ruling can become, and what has to stay with the person at the render. This one picks up after that and asks what the resulting rule costs to enforce.
A design system can only ask
The tokens are documented. The components are in a catalog. The rules are in the design file, in the instructions the coding agents read, in the lint config, in the review checklist. All of that describes the system. None of it has authority over a pull request, so drift arrives there anyway, and the person who knows the system spends their attention settling decisions the system already settled once.
Agents changed the arithmetic. Interface code is cheap to produce now and the rules did not become any more enforceable, so the volume of code arriving against an advisory standard went up while the standard stayed advisory. That is not a documentation problem. Writing the rules down again in a better place does not fix it.
The boundary was drawn on the first day
Form Logic's repository was scaffolded on July 23, 2026. Its first substantive commit contains packages/governance, and the same commit carries an architecture decision record separating baseline governance from consumer policy. Both are dated that day, before the package had a second consumer or a reason to need the distinction.
The reasoning in that record is the load-bearing part. A rule belonging to one consuming product, embedded in the shared baseline, becomes an accidental contract for every other product. A scanner embedded separately in each consumer forks the engine and lets rule behavior drift between them. So the engine owns scanning, file classification, the rule and violation interfaces, allowances, the ledger, and the CLI. Each consumer owns its own vocabulary, brand semantics, and workflow policy.
I want to be accurate about what I knew. I did not write that boundary because I planned to extract the package. I wrote it because the design system was going to have more than one consumer and I did not want one product's opinions leaking into everyone else's build. The extraction a month later was possible because of a decision made for a smaller reason.
186 findings, and every one of them wrong
A consumer's first governance run exited 1 with 186 raw-color findings. Every one was a hex value in a theme file. The shipped CLI had generated that file minutes earlier, and none of the findings could be fixed, because a token file is literal color. That is what a token file is for.
The scanner was working exactly as specified and the result was useless. Worse than useless: the first thing the package ever said to that consumer was 186 sentences of nonsense, at the moment it was asking to be trusted with a merge gate.
A preset that cries wolf on correct code gets switched off, and a preset that is off governs nothing.
The fix was to classify generated output by content rather than by filename. A .generated. naming convention only protects someone who already knew to use it, and not knowing is the whole problem. So a file is treated as generated when it carries a banner naming a generator that somebody owns, because the consumer picks the output path and the generator's author is the one who knows what it writes.
That decision has a tail I did not see at the time. The banner the engine matched was hardcoded and named the design system, which meant the engine knew something about one specific consumer. It came out during the extraction and became configuration the pack supplies. A test now asserts that a banner file with no markers supplied gets scanned rather than exempted, so the exemption cannot be inherited by accident.
What it caught
The rule that governs repair selectors, the ones where a consumer reaches past a component's public surface to patch its internals, found eleven of them in the catalog on its first run. None was silently allowed. Each went into the ledger with a named fix.
The argument for a required check is better told as an event than as a claim. One of the five consuming products spent its entire build filing defects against the design system by hand, thirty of them, written up as prose after careful reading, with no scanner installed anywhere in its pipeline. The day the governance package landed in that repository, the first run found drift the hand review had missed. Careful reading by someone who knows the system is not the same instrument as a check that cannot be skipped, and it took a scan against an already-reviewed codebase to show me the size of the gap.
Why it left
By August the package was running against five consuming products and none of them needed Form Logic's opinions about color in order to get the enforcement. The engine knew nothing about tokens. The rules did. That split had been written down on day one and was now visible in the file list.
On August 22 it came out by git subtree split, carrying 34 commits of its own history from July 23 through August 20. Form Logic's main branch was not modified. A rewrite would have been faster and would have thrown away the month of false-positive discipline that made the engine worth extracting, so the history came along. It lives in the working repository; what goes to npm is a clean export of the engine at a release.
What stayed behind was the rule pack, about 2,100 lines of Form Logic's specific opinions, which now depends on the engine instead of containing it. What moved was the engine, about 500 lines at that point: types, file classification, the scanner, the CLI. It has zero runtime dependencies and imports nothing outside node: builtins, enforced by a test that fails on any dependency entry or any non-builtin import. I falsified that guard in both directions before trusting it, which is a habit I would not have had before the 186 findings. The engine is MIT and its source is public, so that one is checkable rather than a claim.
The lock, not the scanner
The scanner underneath is ordinary. I checked that belief against two established tools on the same corpus before letting myself think otherwise, and the honest read is that finding a raw hex in a stylesheet is commodity work. What a team would actually adopt is the lifecycle above it.
strictmode compile resolves the policy into a lock. Every rule carries an id, a severity, an owner, and a source. Every allowance carries a reason. Every tolerated defect carries a fix. strictmode check refuses a missing or stale lock before it scans anything, and exit codes are the authority.
Three properties took the most thought. The hash covers the policy's identity, the config text and the metadata of every rule it pulls in, so a changed rule or allowance stales the lock. It deliberately excludes the engine version: staling every consumer's lock on every engine release would teach people to recompile without reading the diff, which is the opposite of what a lock is for. Allowances and ledger entries expire on a calendar date, and the date named is the first day the exemption is gone. And the ledger fails in both directions, so quietly fixing a tolerated defect without removing its entry breaks the build, because a ledger that only catches new problems drifts out of date the moment somebody does the right thing.
The property I am most attached to is the one that admits failure. A rule the scanner cannot statically evaluate, one that needs a running browser or a person's eyes, is declared as such and listed as uncovered on every single run. It is never quietly counted as passing. The gap between the policy and what the machine can enforce gets printed rather than hidden.
Putting this site's rules under it
The site for the product runs on the product. Three rules over 34 stylesheets: no raw color outside the token and theme directories, no raw length on spatial properties, no monospace outside a code block. The first run produced thirty findings, and the useful part is how each one was resolved, because the tempting move at that moment is to loosen a rule.
Four raw colors were in the two logo modules. A mark's colors are immutable by definition, so those files are classified as brand identity and the color rule honors that class. That is not a deviation, it is the correct classification. One monospace finding was the compiled rule-set diagram on the site, where monospace is functionally required for the indentation to line up, so it became a permanent allowance carrying that reason. The remaining twenty-five findings, twenty unique, became ledger entries with a reason, a named fix, an owner, and an expiry.
None of the twenty was fixed in CSS that day. Every one of them changes rendering, and the person who has to verify the rendering is me. An expiry date on a tolerated defect is a commitment with a deadline rather than a permanent exception, which is the only version of tolerated debt I trust. Then I falsified both mechanisms: fixing one ledgered value made the scan exit 1 with a stale-ledger error, and deleting the allowance made the monospace rule fire.
What it cannot do
Rules written as code are opaque to the lock, which can record that they were in force but not what they check. So the rules became data: declarative rows built from six primitives, recorded verbatim in the lock, evaluable without the code that wrote them. Read against the design system's fourteen rules, ten of them lowered into rows. Two of those ten lose specificity on the way.
Three will not lower at all, and they fail for one shared reason: each needs to consider a selector and the declarations inside its block together, and every primitive I have matches one or the other. That missing shape is the seventh primitive and it is the next one worth building. It is also the uncomfortable result, because those three rules are the ones that caught real defects. One of them exists only because the design system shipped that exact defect itself.
There is a live defect in the hash, too. It covers rule metadata, so a rule whose behavior changes while its description stays the same does not stale anybody's lock. A reviewer proposed folding each rule's implementation text into the hash, which is the obvious fix. I checked it against the only real rule pack before agreeing and it does not work: every check body in that pack is a three-line delegation, and the pattern, the exemption list, and the shared matching helper all live at module scope. Change the shared helper and six rules change behavior while their text stays byte-identical. The mechanism that does work already existed in the design system's build, a content hash over the package source, and it is not wired into the lock yet.
One more, smaller and more embarrassing. The design log claimed seven primitives directly above a list of six. Six is right; the seventh had folded into another during implementation and three documents still said otherwise. A review caught it. The correction is recorded as its own entry rather than edited into the original, because a log that gets tidied is not evidence of anything.
Nothing had been blocked yet
The engine shipped inside the design system as its governance package and ran on the product's own site. Five products ran it and every one of them was mine, so there was no external customer and no adoption to point at.
The honest limit is narrower than that and worth naming exactly. The site compiled a lock, checked against it, and uploaded an evidence record on every run, but nobody had made it a required status check on a branch. Nothing had been blocked that a person actually wanted to ship, and being blocked when you are in a hurry is the only real test of whether a rule set is tolerable to live with.
