coding.sgit.ai / the rules / enforcement

Enforcement

There is no linter, no formatter and no type-checker anywhere in the estate. Four tests/ci/ structural guards are the entire automated enforcement surface, and one of them has never worked. This page is the day's work that converts a description into a standard, shipped as files rather than as prose.

A rule without a working test is a suggestion, and you cannot tell the difference from the outside.

The configs

Every file below is real, parses, and is released under CC BY 4.0 with the rest of this site. Copy them; they have no dependencies beyond the tool each one configures.

ruff.toml

Three import bans — pydantic, typing.Literal, boto3 — plus the pyflakes category that runtime type safety does not cover. Every stylistic rule that would reflow the alignment is explicitly off.

ruff.toml →
eslint.config.js

4-space indent, single quotes, no semicolons, trailing commas — and the two no-restricted-syntax selectors that catch real bugs: a connectedCallback override, and a new listener on the legacy sp-cli: namespace.

eslint.config.js →
stylelint.config.js

No literal colours outside the token file — the highest-value CSS rule available — as color-no-hex, color-named and a property-value allow-list, with the token file itself carved out.

stylelint.config.js →
test_house_style.py

The seven rules no linter can express, as one TestCase in the style of the four guards that already exist: banner, no docstrings, no _ methods, one class per file named for the file, empty __init__.py, no raw primitive attributes, aligned imports.

test_house_style.py →
test_rendered_shell.py

The highest-value missing check in the estate: render every Section__* and pipe the output through shellcheck, plus banner, breadcrumb and unescaped-brace checks. Skips cleanly when shellcheck is absent, per testing rule T3.

test_rendered_shell.py →
check_components.py

The two JavaScript rules ESLint cannot express: every component directory holds the full .js/.html/.css triplet, and every SgComponent subclass declares static jsUrl = import.meta.url.

check_components.py →
check_markup.py

The two HTML rules that catch defects rather than formatting — every interactive control has an accessible name, and behaviour never lives in an on* attribute — plus the fragment rule and the 2-space indent. Standard library only; it tolerates the unclosed markup an excerpt leaves behind.

check_markup.py →

What the tools genuinely cannot do

The survey document behind this site lists “no docstrings — trivial, ruff D-rules inverted” and “a ruff config block” covering four rules. That is optimistic, and getting it right matters more here than being brief.

RuleCan a linter express it?Where it ends up
Ban pydantic, Literal, boto3Yesruff flake8-tidy-imports banned-apiruff.toml
No docstringsNo. pydocstyle (D) requires docstrings; there is no inverse rule in ruff or flake8a custom AST check
No _-prefixed methodsNo. pep8-naming has no inverse of its private-name rulescustom AST check
__init__.py emptyNo. A file-emptiness check is not a lint rulecustom check
Banner present and well-formedNo. A file-header shape check; no rule existscustom check
Filename equals class nameNo. A cross-file property; ruff sees one file at a timecustom check
No raw primitives as attributesNo. Needs an AST walk over annotated class attributescustom AST check
Import alignmentNo, and no formatter will ever produce it. black and gofmt refuse alignment by designcustom check
CSS indent and value alignmentNo longer. stylelint deprecated every stylistic rule in v15 and removed them in v16, on the grounds that a formatter should own them — and no formatter produces this alignmentcustom check
Every interactive control has an accessible namePartly. htmlhint and eslint-plugin-jsx-a11y cover some of this; neither reads a detached component fragment, which is what this markup isa custom check
static jsUrl = import.meta.url presentNo. ESLint core has no “this class must declare X” rulea custom check

The pattern is not an accident. Almost every rule in this house style is a structural property — of a file, a filename, a directory, a column — and linters are built to check syntactic properties of one file's contents. That is why the estate's existing enforcement surface is a folder of TestCase classes rather than a config, and why the honest recommendation is to keep it that way and add a small linter alongside for the things linters are good at: undefined names, unused imports, unreachable code — the category Type_Safe genuinely does not cover.

Running it against the estate's own examples

The guard was run against the two Python files this site quotes verbatim. It is not a demonstration written to pass:

# Safe_Str__IP__Address.py — the constrained-primitive example
Ran 7 tests — OK

# Schema__Caller__IP.py — the eleven-line "everything in one file" example
FAILED (failures=1)
  test_import_keywords_are_aligned:
    Schema__Caller__IP.py: imports at 2 different columns [76, 77]

The canonical example of the house style fails the house style's own alignment rule, by one column. It may be a transcription artifact in the survey document rather than a miss in the real file — and that is the whole argument on this page. Today nobody can tell you which, because nothing checks. With the guard in place the question does not come up: either the file is aligned or the build is red.

The markup checker was run the same way, against the component fragment this site quotes on the HTML page. It reports nothing — and on a fragment with an onclick, a nameless <button>, a <template> wrapper and a 3-space indent it reports all four. A check that has only ever been seen to pass is the failure mode this whole page is about, so both directions are worth running once.

Rule 7 is documented as a banner with three content lines. Every verbatim example in the survey has a different number: Schema__Caller__IP has two — an identity line and a purpose line — and Section__Shutdown has four. A guard written to the documented rule fails every file in the estate, at a measured 100% compliance.

So the shipped check enforces the shape the code actually has: an identity line that names the file, then at least one purpose line. Writing the guard is what found the discrepancy, which is the second argument for writing guards.

The order to do this in

#FixEffortCatches a real defect today?
F1Delete [^_] from test_no_legacy_imports.py. The guard has never matched anything; there are 228 real imports across 69 files4 charactersYes — 69 files
F4shellcheck on rendered Section__* output. The generated shell is currently unlintable because it is never rendered outside productiona test — shipped aboveYes
F2The ruff config — three import bans plus pyflakesone block — shippedGuards against regression
F6stylelint: no literal colours outside the token filean hour — shippedGuards against regression
F7The banner check~20 lines — shippedFound the rule-7 discrepancy above
F8Filename equals class name, one class per file~20 lines — shipped90% today, so yes
F3Import alignment — the only formatting rule that is measurably inconsistent, at 39%a check — shippedYes — 817 files in scope
F5eslint, plus the component-triplet checkhalf a day — shippedGuards against regression

Turn any of these on and the build goes red. Rule 9 has 97 violations, rule 21 is at 90%, import alignment is at 39%, and the semicolon rule contradicts half the JavaScript. That is not an argument against turning them on — it is the measurement of what “high compliance, no enforcement” actually costs, and it is only visible once something counts. Start with F1 and F4, which catch defects rather than formatting.

How to grow a guard set

The four working guards in the estate have one thing in common that is worth copying: each encodes a rule that was violated at least once. The : object = None ban, the UI-in-wheel check, the two component snapshots. None of them came from a checklist; all of them came from an incident.

That is also the strongest argument for the two AWS naming rules being written down at all. They cite their own precedent, so a reader who disagrees is arguing with a rejected API call rather than with a preference. Rules 14 and 15 →