From a44133aff259bc122006a2fa4df482ebe8b4eeb3 Mon Sep 17 00:00:00 2001 From: Michael Chus Date: Fri, 12 Jun 2026 10:00:02 +0300 Subject: [PATCH] Move inline code examples out of normative contracts identifier-normalization, no-hardcoded-vendors, vendor-installer-verification, and build-version-display follow the go-database split: rules in contract.md, snippets in README.md. Routed contract reads get cheaper; examples stay available on demand. Lint now also rejects stale kit/patterns references. Co-Authored-By: Claude Fable 5 --- .../patterns/build-version-display/README.md | 29 ++++++++ .../build-version-display/contract.md | 49 +----------- .../identifier-normalization/README.md | 54 ++++++++++++++ .../identifier-normalization/contract.md | 65 ++-------------- rules/patterns/no-hardcoded-vendors/README.md | 42 +++++++++++ .../patterns/no-hardcoded-vendors/contract.md | 57 ++------------ .../vendor-installer-verification/README.md | 46 ++++++++++++ .../vendor-installer-verification/contract.md | 74 +++---------------- scripts/lint.sh | 6 +- 9 files changed, 201 insertions(+), 221 deletions(-) create mode 100644 rules/patterns/build-version-display/README.md create mode 100644 rules/patterns/identifier-normalization/README.md create mode 100644 rules/patterns/no-hardcoded-vendors/README.md create mode 100644 rules/patterns/vendor-installer-verification/README.md diff --git a/rules/patterns/build-version-display/README.md b/rules/patterns/build-version-display/README.md new file mode 100644 index 0000000..738668f --- /dev/null +++ b/rules/patterns/build-version-display/README.md @@ -0,0 +1,29 @@ +# Build Version Display Pattern Notes + +This file keeps examples. The normative rules live in `contract.md`. + +## Frontend (JS/TS build tools) + +```ts +// vite.config.ts / webpack.config.js +define: { + __APP_VERSION__: JSON.stringify(process.env.APP_VERSION ?? "dev"), +} + +// Footer component +
v{__APP_VERSION__}
+``` + +## Go (server-rendered HTML) + +```go +// main.go +var Version = "dev" + +// Build: go build -ldflags "-X main.Version=1.4.2" +``` + +```html + +
v{{ .Version }}
+``` diff --git a/rules/patterns/build-version-display/contract.md b/rules/patterns/build-version-display/contract.md index 44daa68..7a34f22 100644 --- a/rules/patterns/build-version-display/contract.md +++ b/rules/patterns/build-version-display/contract.md @@ -1,18 +1,13 @@ # Contract: Build Version Display -Version: 1.0 - -## Purpose - -Every web application must display the current build version in the page footer so that users and support staff can identify exactly which version is running. - ---- +Version: 1.1 ## Rule -The build version **must** be visible in the footer on every page of the web application. +The build version **must** be visible in the footer on every page of the web application, +so users and support staff can identify exactly which version is running. ---- +See `README.md` for implementation snippets. ## Requirements @@ -22,42 +17,6 @@ The build version **must** be visible in the footer on every page of the web app - Format: any human-readable string that uniquely identifies the build — a semver tag, a git commit SHA, or a combination (e.g. `1.4.2`, `1.4.2-abc1234`, `abc1234`). - The version text must be legible but visually subordinate — use a muted color and small font size so it does not compete with page content. ---- - -## Recommended implementation - -**Frontend (JS/TS build tools)** - -Expose the version through an environment variable at build time and reference it in the footer component: - -```ts -// vite.config.ts / webpack.config.js -define: { - __APP_VERSION__: JSON.stringify(process.env.APP_VERSION ?? "dev"), -} - -// Footer component -
v{__APP_VERSION__}
-``` - -**Go (server-rendered HTML)** - -Inject via `-ldflags` at build time and pass to the template: - -```go -// main.go -var Version = "dev" - -// Build: go build -ldflags "-X main.Version=1.4.2" -``` - -```html - - -``` - ---- - ## What is NOT allowed - Omitting the version from any page, including error pages. diff --git a/rules/patterns/identifier-normalization/README.md b/rules/patterns/identifier-normalization/README.md new file mode 100644 index 0000000..c6aefb6 --- /dev/null +++ b/rules/patterns/identifier-normalization/README.md @@ -0,0 +1,54 @@ +# Identifier Normalization Pattern Notes + +This file keeps examples. The normative rules live in `contract.md`. + +## Go — сравнение + +```go +import "strings" + +func SameIdentifier(a, b string) bool { + return strings.EqualFold(a, b) +} +``` + +## Go — дедупликация + +```go +func deduplicateBySerial(items []Device) []Device { + seen := make(map[string]struct{}) + result := items[:0] + for _, item := range items { + key := strings.ToLower(item.SerialNumber) + if _, exists := seen[key]; !exists { + seen[key] = struct{}{} + result = append(result, item) + } + } + return result +} +``` + +## SQL — поиск и уникальность + +Поиск: + +```sql +SELECT * FROM devices WHERE LOWER(serial_number) = LOWER(?); +``` + +Уникальный индекс (MySQL / MariaDB): + +```sql +-- Collation ci обеспечивает case-insensitive уникальность +ALTER TABLE devices MODIFY serial_number VARCHAR(255) + CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +ALTER TABLE devices ADD UNIQUE INDEX uniq_serial (serial_number); +``` + +SQLite: + +```sql +CREATE UNIQUE INDEX uniq_serial ON devices (LOWER(serial_number)); +``` diff --git a/rules/patterns/identifier-normalization/contract.md b/rules/patterns/identifier-normalization/contract.md index 45ae460..34d36d8 100644 --- a/rules/patterns/identifier-normalization/contract.md +++ b/rules/patterns/identifier-normalization/contract.md @@ -1,13 +1,13 @@ # Contract: Identifier Normalization -Version: 1.0 +Version: 1.1 ## Purpose Правила хранения и сравнения идентификаторов оборудования: серийные номера, вендоры, версии прошивок, партномера, артикулы. ---- +See `README.md` for Go and SQL examples. ## Правило @@ -17,11 +17,8 @@ Version: 1.0 ``` Пришло: "SN-001-ABC" → хранится: "SN-001-ABC" Пришло: "sn-001-abc" → это тот же объект, не дубликат -Пришло: "Sn-001-Abc" → то же самое ``` ---- - ## Применяется к полям - Серийный номер (`serial_number`, `serial`) @@ -30,61 +27,13 @@ Version: 1.0 - Партномер (`part_number`, `part_no`) - Артикул (`article`, `sku`) ---- - ## Реализация -### Go — сравнение - -```go -import "strings" - -func SameIdentifier(a, b string) bool { - return strings.EqualFold(a, b) -} -``` - -### Go — дедупликация - -```go -func deduplicateBySerial(items []Device) []Device { - seen := make(map[string]struct{}) - result := items[:0] - for _, item := range items { - key := strings.ToLower(item.SerialNumber) - if _, exists := seen[key]; !exists { - seen[key] = struct{}{} - result = append(result, item) - } - } - return result -} -``` - -Ключ в map — всегда `strings.ToLower(value)`. Сам объект сохраняется с оригинальным значением. - -### SQL — поиск и уникальность - -Поиск: -```sql -SELECT * FROM devices WHERE LOWER(serial_number) = LOWER(?); -``` - -Уникальный индекс (MySQL / MariaDB): -```sql --- Collation ci обеспечивает case-insensitive уникальность -ALTER TABLE devices MODIFY serial_number VARCHAR(255) - CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; - -ALTER TABLE devices ADD UNIQUE INDEX uniq_serial (serial_number); -``` - -SQLite: -```sql -CREATE UNIQUE INDEX uniq_serial ON devices (LOWER(serial_number)); -``` - ---- +- Go: сравнение только через `strings.EqualFold`, никогда `==`. +- Go: ключ дедупликации в map — `strings.ToLower(value)`; сам объект хранит оригинал. +- SQL-поиск: `WHERE LOWER(col) = LOWER(?)`. +- Уникальность: MySQL/MariaDB — case-insensitive collation (`utf8mb4_unicode_ci`) + + unique index; SQLite — `CREATE UNIQUE INDEX ... ON t (LOWER(col))`. ## Что не делать diff --git a/rules/patterns/no-hardcoded-vendors/README.md b/rules/patterns/no-hardcoded-vendors/README.md new file mode 100644 index 0000000..efa1625 --- /dev/null +++ b/rules/patterns/no-hardcoded-vendors/README.md @@ -0,0 +1,42 @@ +# No Hardcoded Vendors Pattern Notes + +This file keeps examples. The normative rules live in `contract.md`. + +## Запрещено + +```go +if device.Vendor == "Dell" { ... } +if strings.Contains(model, "PowerEdge") { ... } +switch vendor { +case "HP", "HPE", "Hewlett Packard": ... +} +``` + +```go +// Запрещено — список вендоров в коде +var knownVendors = []string{"Dell", "HP", "Cisco", "Lenovo"} +``` + +## Правильно + +```go +// Смотрим на возможности объекта, не на имя вендора +if device.HasIPMI { ... } +if device.ParserType == "redfish" { ... } +``` + +Маппинг в конфиге: + +```yaml +# config.yaml +vendor_parsers: + dell: redfish + hp: ilo + cisco: ucs +``` + +Маппинг в БД: + +```sql +SELECT parser_type FROM vendor_registry WHERE LOWER(vendor) = LOWER(?); +``` diff --git a/rules/patterns/no-hardcoded-vendors/contract.md b/rules/patterns/no-hardcoded-vendors/contract.md index 06f0d35..0186cee 100644 --- a/rules/patterns/no-hardcoded-vendors/contract.md +++ b/rules/patterns/no-hardcoded-vendors/contract.md @@ -1,64 +1,23 @@ # Contract: No Hardcoded Vendors or Models -Version: 1.0 +Version: 1.1 ## Purpose Запрет на хардкод названий вендоров, моделей и партномеров в коде. ---- +See `README.md` for code examples. ## Правило Названия вендоров, моделей, серий оборудования и партномеров **не появляются в коде**. Они приходят из данных: БД, конфига, входного документа, справочника. ---- - -## Что запрещено - -```go -// Запрещено -if device.Vendor == "Dell" { ... } -if strings.Contains(model, "PowerEdge") { ... } -switch vendor { -case "HP", "HPE", "Hewlett Packard": ... -} -``` - -```go -// Запрещено — список вендоров в коде -var knownVendors = []string{"Dell", "HP", "Cisco", "Lenovo"} -``` - ---- - -## Что делать вместо - -Логика определяется по полям из данных, не по названию вендора: - -```go -// Правильно — смотрим на возможности объекта, не на имя вендора -if device.HasIPMI { ... } -if device.ParserType == "redfish" { ... } -``` - -Если нужен маппинг — он живёт в конфиге или справочной таблице БД, не в коде: - -```yaml -# config.yaml -vendor_parsers: - dell: redfish - hp: ilo - cisco: ucs -``` - -```sql --- справочник в БД -SELECT parser_type FROM vendor_registry WHERE LOWER(vendor) = LOWER(?); -``` - ---- +- Запрещены сравнения и switch по имени вендора (`if device.Vendor == "Dell"`) и + списки вендоров в коде (`var knownVendors = []string{...}`). +- Логика определяется по полям из данных (`device.HasIPMI`, `device.ParserType`), + не по названию вендора. +- Если нужен маппинг вендор → поведение, он живёт в конфиге или справочной таблице БД. ## Исключения @@ -70,8 +29,6 @@ SELECT parser_type FROM vendor_registry WHERE LOWER(vendor) = LOWER(?); В этих местах название вендора — идентификатор модуля, не условие в логике. ---- - ## Почему Хардкод вендора делает код хрупким: новый вендор требует правок в коде, а не в данных. diff --git a/rules/patterns/vendor-installer-verification/README.md b/rules/patterns/vendor-installer-verification/README.md new file mode 100644 index 0000000..a1cc18f --- /dev/null +++ b/rules/patterns/vendor-installer-verification/README.md @@ -0,0 +1,46 @@ +# Vendor Installer Verification Pattern Notes + +This file keeps examples. The normative rules live in `contract.md`. + +## Download Order + +```sh +BASE_URL="https://vendor.example.com/downloads/${VERSION}" +BIN_FILE="/var/cache/vendor-${VERSION}.run" +SHA_FILE="/var/cache/vendor-${VERSION}.run.sha256sum" + +# 1. Download checksum first +wget -q -O "$SHA_FILE" "${BASE_URL}/vendor-${VERSION}.run.sha256sum" + +# 2. Download installer +wget --show-progress -O "$BIN_FILE" "${BASE_URL}/vendor-${VERSION}.run" + +# 3. Verify +cd /var/cache +sha256sum -c "$SHA_FILE" || { echo "ERROR: sha256 mismatch"; rm -f "$BIN_FILE"; exit 1; } +``` + +## Cache with Verification + +```sh +verify_cached() { + [ -s "$SHA_FILE" ] || return 1 # sha256 file missing or empty + [ -s "$BIN_FILE" ] || return 1 # binary missing or empty + cd "$(dirname "$BIN_FILE")" + sha256sum -c "$SHA_FILE" --status 2>/dev/null +} + +if ! verify_cached; then + rm -f "$BIN_FILE" "$SHA_FILE" + # ... download and verify +else + echo "verified from cache" +fi +``` + +## Version Validation + +```sh +curl -sIL "https://vendor.example.com/downloads/${VERSION}/installer.run" \ + | grep -i 'http/\|content-length' +``` diff --git a/rules/patterns/vendor-installer-verification/contract.md b/rules/patterns/vendor-installer-verification/contract.md index 3ca3def..d352831 100644 --- a/rules/patterns/vendor-installer-verification/contract.md +++ b/rules/patterns/vendor-installer-verification/contract.md @@ -1,6 +1,6 @@ # Contract: Vendor Installer Verification -Version: 1.0 +Version: 1.1 ## Purpose @@ -8,75 +8,19 @@ Rules for downloading and verifying proprietary vendor installers (`.run`, `.exe where the vendor publishes a checksum alongside the binary. Applies to: NVIDIA drivers, vendor CLI tools, firmware packages. ---- - -## Download Order - -Always download the checksum file **before** the installer: - -```sh -BASE_URL="https://vendor.example.com/downloads/${VERSION}" -BIN_FILE="/var/cache/vendor-${VERSION}.run" -SHA_FILE="/var/cache/vendor-${VERSION}.run.sha256sum" - -# 1. Download checksum first -wget -q -O "$SHA_FILE" "${BASE_URL}/vendor-${VERSION}.run.sha256sum" - -# 2. Download installer -wget --show-progress -O "$BIN_FILE" "${BASE_URL}/vendor-${VERSION}.run" - -# 3. Verify -cd /var/cache -sha256sum -c "$SHA_FILE" || { echo "ERROR: sha256 mismatch"; rm -f "$BIN_FILE"; exit 1; } -``` - -Reason: if the download is interrupted, you have the expected checksum to verify against on retry. - ---- - -## Cache with Verification - -Never assume a cached file is valid — a previous download may have been interrupted (0-byte file): - -```sh -verify_cached() { - [ -s "$SHA_FILE" ] || return 1 # sha256 file missing or empty - [ -s "$BIN_FILE" ] || return 1 # binary missing or empty - cd "$(dirname "$BIN_FILE")" - sha256sum -c "$SHA_FILE" --status 2>/dev/null -} - -if ! verify_cached; then - rm -f "$BIN_FILE" "$SHA_FILE" - # ... download and verify -else - echo "verified from cache" -fi -``` - -**Never check only for file existence.** Check that the file is non-empty (`-s`) AND passes checksum. - ---- - -## Version Validation - -Before writing build scripts, verify the version URL actually exists: - -```sh -curl -sIL "https://vendor.example.com/downloads/${VERSION}/installer.run" \ - | grep -i 'http/\|content-length' -``` - -A `404` or `content-length: 0` means the version does not exist on that CDN. -Vendor version numbering may have gaps (e.g. NVIDIA skips minor versions on some CDNs). - ---- +See `README.md` for shell snippets. ## Rules -- Download checksum before installer — never after. +- Download the checksum file **before** the installer — never after. If the download is + interrupted, you still have the expected checksum to verify against on retry. - Verify checksum before extracting or executing. - On mismatch: delete the file, exit with error. Never proceed with a bad installer. +- Never assume a cached file is valid — a previous download may have been interrupted. + **Never check only for file existence**: the file must be non-empty (`-s`) AND pass checksum. - Cache by `version` + any secondary key (e.g. kernel version for compiled modules). +- Before writing build scripts, verify the version URL actually exists (`curl -sIL`). + A `404` or `content-length: 0` means the version is absent on that CDN; vendor version + numbering may have gaps. - Never commit installer files to git — always download at build time. - Log the expected hash when downloading so failures are diagnosable. diff --git a/scripts/lint.sh b/scripts/lint.sh index c72e6f4..3f13466 100755 --- a/scripts/lint.sh +++ b/scripts/lint.sh @@ -22,9 +22,9 @@ for ref in $(grep -o 'bible/rules/patterns/[a-z-]*/contract\.md' AGENT-BOOTSTRAP fi done -# 3. No machine-local absolute paths in committed markdown. -if grep -rn '/Users/' --include='*.md' . --exclude-dir=.git; then - echo "FAIL: machine-local absolute paths found (see above)" +# 3. No machine-local absolute paths or stale path prefixes in committed markdown. +if grep -rn '/Users/\|kit/patterns' --include='*.md' . --exclude-dir=.git; then + echo "FAIL: machine-local absolute paths or stale kit/patterns references found (see above)" fail=1 fi