Appearance
Code hygiene gates
What the pipeline enforces about dead, legacy and duplicated code — and, just as importantly, what it deliberately does not enforce. Written as part of ADR 0006 / US 7.2, whose premise is that "no junk code" has to be verifiable rather than a matter of discipline.
The banned identifier guard
scripts/check-banned-identifiers.sh runs before the build in both pipeline stages. It fails the build when an identifier this repository deliberately removed comes back.
| List | build/banned-identifiers.txt — versioned data, one pattern | reason per line |
| Scope | *.cs under src/ and tests/ |
| Cost | ~0.6 s |
Two properties matter more than the mechanism:
- The list is data, not code. It used to be a bash array inside the script. Adding or lifting a ban is a decision that has to stand on its own in a pull request, and a reviewer should read the reason next to the pattern rather than reverse-engineer it from a diff of shell.
- Matches inside comments are ignored. Every banned name survives in prose explaining why it was removed —
ICurrentTenantdocuments that itsChangescope went withGroupTenantBehavior, and that sentence is the reason nobody re-adds it. A guard that also banned the memory would push people to delete the explanation, which is exactly backwards.
A hit prints the pattern, the reason and every offending line. The failure tells the author what replaced the thing, not just that something matched.
The secret scanning gate
scripts/run-gitleaks.sh scans committed Git objects against .gitleaks.toml. PRs and branch pushes check their immutable base.sha..head.sha event range; a newly created tag and an interactive local run check one commit. An unrelated historical finding therefore cannot break every new PR or merge. The Husky pre-commit hook separately checks staged changes for fast feedback. A full-history scan is a dedicated maintenance audit when the ruleset or pinned scanner version changes.
Two escape hatches exist, and they are not interchangeable:
| When | |
|---|---|
paths / regexes in .gitleaks.toml | A whole class of content is fake by design — docs samples, test fixtures, ${ENV_VAR} placeholders. Blinds that path from then on. |
A fingerprint in .gitleaksignore | One finding, at one commit, reviewed and accepted. A new secret in the same file still fails. |
Prefer the fingerprint. An allowlisted path stops protecting the file forever, and the next real secret to land there is invisible.
Every entry in .gitleaksignore carries its reason inline — what the value is, why it is not live, and what stops a repeat. Same principle as the banned identifier list: an accepted finding with no reason decays into a permanent blind spot, which is precisely what these gates exist to prevent.
What is deliberately NOT enforced
Unused-symbol analyzers as errors
IDE0051, IDE0052, CA1823 and friends are not promoted to errors.
They would fail today, on types that exist without a consumer on purpose: ScopedQueryParameters, ScopeCapableAttribute, ScopeInfo, ConsolidatedResult and IScopedItemDto. These are the group-scope contract (ADR 0006, USs 3.1/3.3/3.4) — a mechanism delivered ahead of the first business screen that uses it, which was the explicit trade-off recorded when those stories shipped.
Turning the analyzers on would mean a suppression list naming those five types, and a suppression list is a worse artifact than this paragraph: it decays silently, whereas a decision with a reason gets revisited. When the reference domain lands (feature 931) and the contract types acquire real consumers, this is the paragraph to delete and the analyzers to enable.
Note what this does not excuse: those types are covered by tests, and the composition tests fail if the pipeline stops being able to activate them. "No consumer yet" is not "no verification".
Architecture sweep over the modules
The reflection-based architecture test from US 1.4 scans only the Gryd*.dll files in Gryd.Application.Tests's output — that is, the Core. It is not extended to the modules.
Extending it would produce a sweep with nothing to find: the rules it checks are Core rules about Core abstractions. A green test that could never go red is worse than no test, because it reads as coverage. The limitation is recorded here instead.
Related
scripts/check-format.sh— whitespace and default-severity style verificationscripts/check-analyzers.sh— zero warning/error policy and non-growing informational baseline- Coverlet/ReportGenerator — reporting-only coverage pending backlog F-19 through F-21
scripts/check-doc-contracts.sh— public package/template discovery and navigation linksscripts/check-vulnerable-packages.sh— dependency advisoriesscripts/run-gitleaks.sh— secret scanning over the event-specific committed revision rangedocs/adr/0006-group-scoped-reads.md— the decisions the banned list enforcesdocs/adr/0007-single-source-of-truth-for-cross-tenant-reach.md— the second batch of bans: the cross-tenant reach flag, the group-inheritance switch permission, the per-tenant conditional inside the role catalog, and the two startup data migrations that no longer have a database to run against