Core standards
Security, type safety, error handling, scope discipline and the severity output contract.
What this is
The rules that apply to every file in every profile, regardless of stack — security, type safety, error handling, scope discipline, and the output contract itself.
How to onboard it
Nothing to do, and nothing you can opt out of. Core is included by every profile and applies to every file — it has no globs to match because it is not stack-scoped. Onboarding a repository at all onboards these rules.
How to use it — 22 rules
Nothing to run. Once your profile includes core (every profile does), redline init renders these rules into your repository's AI tooling and the reviewer applies them on every pull request. When a review comment cites one of these ids, this table is where to look up what it catches and why.
| Rule id | Severity | Catches |
|---|---|---|
core/hardcoded-secrets | BLOCKER | No hardcoded secrets, API keys, tokens, or credentials — including in test files, fixtures, config samples, and comments. |
core/customer-data-in-logs | BLOCKER | No customer data (MSISDN, email, account IDs, names, addresses) in logs, analytics events, error messages, or metric labels. |
core/unvalidated-boundary-input | BLOCKER | All external input validated at system boundaries (forms, API responses, deep links, query params, webhook payloads, message-queue payloads). |
core/html-injection-sink | BLOCKER | No unsanitised HTML injection sinks (`dangerouslySetInnerHTML`, `innerHTML`, template autoescape disabled). |
core/missing-auth-check | BLOCKER | Auth checks on every server action / API route / service endpoint — not only in the UI layer or at the gateway. |
core/sensitive-data-in-client-storage | BLOCKER | No sensitive data in browser or device storage (`localStorage`, `AsyncStorage`, `UserDefaults`, `SharedPreferences`) without platform-keystore encryption. |
core/query-string-concatenation | BLOCKER | No query built by string concatenation with external input — parameterised only. |
core/secrets-in-committed-config | BLOCKER | No secrets read from committed config — environment or vault only. |
core/escape-hatch-types | BLOCKER | No escape-hatch types (`any`, `interface{}` in new Go code, `Object`, `dynamic`) where a concrete or generic type works. |
core/type-checker-suppression | BLOCKER | No type-checker or linter suppression without an inline comment AND a ticket reference. Recognised in whichever dialect the stack uses: `@ts-ignore`, `@ts-expect-error`, `@ts-nocheck`, `eslint-disable`, `# type: ignore`, `# noqa`, `# pylint: disable`, `@SuppressWarnings("unchecked")`, `@Suppress("UNCHECKED_CAST")`, `// swiftlint:disable`, `//nolint`, `#pragma warning disable`. |
core/unsafe-assertion | BLOCKER | No unsafe assertions (`as unknown as X`, force casts) used to silence an error. |
core/prefer-discriminated-unions | BLOCKER | Discriminated unions / sealed types over optional-field grab-bags for variant state. |
core/unchecked-indexed-access | BLOCKER | Assume the strictest project setting is on (TS `strict` + `noUncheckedIndexedAccess`, Kotlin/Swift null-safety, mypy strict): indexed access may be absent — require the check. |
core/missing-boundary-error-handling | HIGH | Error handling belongs at real system boundaries: user input, network calls, storage, native modules, message consumers. Flag missing handling there. |
core/unreachable-defensive-guard | HIGH | Flag defensive guards against states internal code cannot produce — they hide bugs and add noise. |
core/silent-async-failure | HIGH | Async operations that can reject must not fail silently: no empty catch, no floating promises, no error logged then treated as success. |
core/argument-mutation | HIGH | Flag mutation of function arguments or shared objects. |
core/async-race-condition | HIGH | Flag race conditions in async work: missing cancellation/abort when the owner unmounts, the request is superseded, or the context is cancelled. |
core/untracked-todo | HIGH | Flag `TODO`/`FIXME`/placeholder code without a ticket reference. |
core/naive-clock | HIGH | Flag time handling that assumes local timezone or a naive clock in new code. |
core/unrelated-change | HIGH | Flag changes unrelated to the PR's stated purpose (drive-by refactors, formatting churn). |
core/speculative-abstraction | HIGH | Prefer the minimal diff that solves the problem; flag speculative abstraction ("might need it later"). |
Expected output
A finding from this file, and every finding Redline produces, opens with a machine-readable first line — severity, then the rule id in brackets, then the problem in one line:
Redline/BLOCKER [core/hardcoded-secrets]: <one-line problem>Ids are aggregated per rule, which is how the organisation finds out which rules earn their place and which only generate noise — so a finding without a valid id cannot be measured and counts as untagged. Of the 22 rules here, 13 BLOCKER and 9 HIGH. Only a BLOCKER must not merge; a SUGGESTION may be dismissed without justification, and is never upgraded to get attention.
If a rule here fires constantly on code your team has deliberately decided to allow, that is the signal to raise with the standards owner — the rule id is what makes that conversation measurable — not to argue it away comment by comment.
How to edit it
Rules are edited in standards/core.md and nowhere else. The rendered copies in AGENTS.md, .github/copilot-instructions.md and .github/instructions/ are generated and are overwritten by the next render. A change here propagates to every onboarded repository as a pull request, so treat it as a production change.
- Edit the markdownstandards/ is the only place a human edits a rule. Everything under AGENTS.md, .github/copilot-instructions.md and .github/instructions/ is rendered from it and is overwritten by the next render.
- node scripts/assign-rule-ids.mjsAssigns a permanent <stack>/<slug> id to any new rule bullet and rewrites the file in place. Do not invent an id by hand. CI runs the same script with --check and fails if a rule is missing one.
- node scripts/render-self.mjsRe-renders this repository's own artifacts from the edited source. CI runs it with --check, so stale checked-in output fails the build.
- Bump standards/manifest.json → versionRequired in the same pull request as the rule change. Sync pull requests quote the version, so a repository's rendered artifacts always name where they came from.
- Add a CHANGELOG.md entryAlso in the same pull request. A standards change with no measurement is an opinion — record the seed score alongside it.
- node scripts/validate.mjsThe bundle self-check CI runs: manifest integrity, well-formed rule ids, the severity output contract surviving your edit, glob portability.
Rule ids are permanent. Rewording a rule is fine and keeps its id; renaming or removing an id orphans every historical telemetry record that cited it.
The full file
# Redline — Core Engineering Standards
You are performing code review against Redline, the engineering standard for this
organisation. Stack-specific rules extend these; they never override them.
## Output contract (required)
Every finding you post MUST begin with a machine-readable prefix on its own first line:
```
Redline/BLOCKER [rule-id]: <one-line problem>
Redline/HIGH [rule-id]: <one-line problem>
Redline/SUGGESTION [rule-id]: <one-line problem>
```
Then one or two sentences: why it breaks, and the concrete fix. No preamble, no praise,
no restating the diff. One finding per comment. If nothing qualifies, post nothing.
Worked example:
```
Redline/BLOCKER [core/query-string-concatenation]: user-supplied `name` is concatenated
into the SQL string, so a crafted value changes the query.
Use a parameterised query: `db.Query("SELECT id FROM users WHERE name = $1", name)`.
```
### Rule ids
Every rule in this document and in the stack rules carries an id in backticks at the
start of its line, of the form `<stack>/<slug>` — for example `react/effect-derived-state`.
- Quote the id of the rule you are applying, exactly as written. Do not invent, abbreviate,
pluralise, or reformat it.
- One rule per comment. If a line breaks two rules, post two comments.
- If you are confident something is wrong but no rule covers it, use `core/uncatalogued`
and say plainly which principle it offends. A recurring `core/uncatalogued` is how a
missing rule gets discovered, so do not force a bad match to avoid it.
Ids are stable across wording changes and are aggregated per rule, which is how the org
finds out which rules earn their place and which only generate noise. A finding without a
valid id cannot be measured and is treated as untagged.
Severity meaning:
- **BLOCKER** — must not merge. Security exposure, data loss, crash, silent corruption,
or a contract break for live consumers.
- **HIGH** — merge is a deliberate trade-off. Reviewer must acknowledge explicitly.
- **SUGGESTION** — optional. Author may dismiss without justification.
Do not invent severities. Do not upgrade a SUGGESTION to HIGH to get attention.
## Review priorities (in order)
1. Security and data exposure
2. Correctness bugs
3. Type safety
4. Performance regressions
5. Maintainability
Stop at the first three unless the diff is clean there.
## Security (BLOCKER)
- `core/hardcoded-secrets` — No hardcoded secrets, API keys, tokens, or credentials — including in test files,
fixtures, config samples, and comments.
- `core/customer-data-in-logs` — No customer data (MSISDN, email, account IDs, names, addresses) in logs, analytics
events, error messages, or metric labels.
- `core/unvalidated-boundary-input` — All external input validated at system boundaries (forms, API responses, deep links,
query params, webhook payloads, message-queue payloads).
- `core/html-injection-sink` — No unsanitised HTML injection sinks (`dangerouslySetInnerHTML`, `innerHTML`, template
autoescape disabled).
- `core/missing-auth-check` — Auth checks on every server action / API route / service endpoint — not only in the UI
layer or at the gateway.
- `core/sensitive-data-in-client-storage` — No sensitive data in browser or device storage (`localStorage`, `AsyncStorage`,
`UserDefaults`, `SharedPreferences`) without platform-keystore encryption.
- `core/query-string-concatenation` — No query built by string concatenation with external input — parameterised only.
- `core/secrets-in-committed-config` — No secrets read from committed config — environment or vault only.
## Type safety (BLOCKER unless justified inline)
- `core/escape-hatch-types` — No escape-hatch types (`any`, `interface{}` in new Go code, `Object`, `dynamic`)
where a concrete or generic type works.
- `core/type-checker-suppression` — No type-checker or linter suppression without an inline comment AND a ticket reference.
Recognised in whichever dialect the stack uses: `@ts-ignore`, `@ts-expect-error`, `@ts-nocheck`, `eslint-disable`,
`# type: ignore`, `# noqa`, `# pylint: disable`, `@SuppressWarnings("unchecked")`, `@Suppress("UNCHECKED_CAST")`,
`// swiftlint:disable`, `//nolint`, `#pragma warning disable`.
- `core/unsafe-assertion` — No unsafe assertions (`as unknown as X`, force casts) used to silence an error.
- `core/prefer-discriminated-unions` — Discriminated unions / sealed types over optional-field grab-bags for variant state.
- `core/unchecked-indexed-access` — Assume the strictest project setting is on (TS `strict` + `noUncheckedIndexedAccess`,
Kotlin/Swift null-safety, mypy strict): indexed access may be absent — require the check.
## Error handling
- `core/missing-boundary-error-handling` — Error handling belongs at real system boundaries: user input, network calls, storage,
native modules, message consumers. Flag missing handling there.
- `core/unreachable-defensive-guard` — Flag defensive guards against states internal code cannot produce — they hide bugs and
add noise.
- `core/silent-async-failure` — Async operations that can reject must not fail silently: no empty catch, no floating
promises, no error logged then treated as success.
## General correctness
- `core/argument-mutation` — Flag mutation of function arguments or shared objects.
- `core/async-race-condition` — Flag race conditions in async work: missing cancellation/abort when the owner unmounts,
the request is superseded, or the context is cancelled.
- `core/untracked-todo` — Flag `TODO`/`FIXME`/placeholder code without a ticket reference.
- `core/naive-clock` — Flag time handling that assumes local timezone or a naive clock in new code.
## Scope discipline
- `core/unrelated-change` — Flag changes unrelated to the PR's stated purpose (drive-by refactors, formatting churn).
- `core/speculative-abstraction` — Prefer the minimal diff that solves the problem; flag speculative abstraction
("might need it later").
## What NOT to flag
AI review dies by nitpick spam. Noise control is a rule, not a preference.
- Formatting, import order, or anything a linter or formatter already enforces.
- Existing patterns the PR merely touches but does not change.
- Missing tests for code outside the diff.
- Alternative libraries when the current one works ("consider using X instead").
- Naming preferences where the existing name is unambiguous.
- Re-raising the same issue on every occurrence — flag the first, say "and N similar".
- Anything you cannot point at a concrete failure for. If you cannot describe the input
that breaks it, it is not a finding.