Foundation

Same quality bar, regardless of author

The ProductOps Quality Model exists so that functional code meets the same quality bar non-functional-requirement (NFR) code is expected to meet — regardless of who authored it or which pipeline it came from.

Two pipelines, one bar

Within this Pipeline, the ProductOps and DevSecOps streams divide the work: ProductOps produces the code that implements functional requirements; DevSecOps produces the code that implements non-functional requirements — hardening, integration, reliability. This division follows the tripartite product structure — UX PM owns functional code, Tech PM owns NFR code. Both streams are increasingly authored through Claude Code.

The rest of this page uses functional code and NFR code as shorthand for what each stream produces:

  • Functional code — reference implementations scaffolded from PRDs, capability storyboards, virtual prototypes. Usually driven by UX; sometimes a Dev self-serving.
  • NFR code — implementation of non-functional requirements, hardening, integration. Usually driven by Dev.

Both are subjected to the same automated scan suite at PR time. Functional code and NFR code go through the DevSecOps pipeline's Quality Checks & Linters, SCA, SAST, Container Scanning, and DAST — applied identically regardless of stream or author. The Quality Model is the layered assurance approach that wraps that scan with three further layers — extending parity beyond what a scanner can codify, and making the handoff between the two streams a smooth collaboration rather than a throw-over-the-wall: functional code arrives on shared foundations (design system, typed contracts, agreed patterns) that Dev can layer NFR work onto in parallel, not sequentially.

The four layers

Each layer intervenes at a different point in the lifecycle. A concern that slips past one layer is still likely to be caught by another before it reaches production.

LayerWhenIntentMechanismCatches
L1 — PreventionPre-buildStart every generation from a shared baseline: consistent style, aligned architecture patterns, baseline security and accessibility hygiene. Reduces how often the later layers need to fire.CLAUDE.md NFR rules; PRIZM paved-road components; typed contract boundaries between functional and NFR surfacesViolations that never get a chance to occur — the cheapest layer
L2 — Outcome ValidationDesign → continuousValidate that the product concept will deliver its intended outcome before and as it is built. Catch misalignment before implementation effort is sunk into the wrong thing.Test plan generation, usability testing, product scorecard, OKR validation → DASH for outcome measurement and early-warningFunctional and product-outcome risk, before implementation effort is sunk into the wrong thing
L3 — Automated ScanAt PRMechanically verify that generated code meets codifiable quality and security bars, applied identically regardless of author.Software quality checks and linters + application security testing suite (SCA, SAST, container scanning, DAST) → Vulnerability ManagementKnown, codifiable NFR classes — code quality, dependency and code-level vulnerabilities, container misconfigurations, runtime-exposed flaws
L4 — Integration VerificationPost-mergeConfirm that once NFR implementation is layered on top of functional code, the combined artifact still meets the L3 bar. Catches interaction effects from the merge, not re-doing NFR work L1 should already have shaped.L3 scan suite re-run against the integrated build rather than isolated componentsInteraction effects invisible at the component level

Ownership. L1 and L4 span both pipelines directly. L2 sits within ProductOps. L3 sits within DevSecOps.

Where L1 does the heavy lifting

Parity is engineered at L1, not enforced at L3. L3 is the safety net; L1 is what keeps most code from ever needing it.

Three L1 mechanisms carry most of the load:

  • CLAUDE.md NFR rules — a per-project file that loads into Claude Code's context on every session. Names the exact patterns to use (shared API wrapper, existing state store, ErrorBoundary, PRIZM form components) rather than describing them in the abstract. The starter is below.
  • PRIZM paved-road componentsPRIZM primitives already conform to DSTA's accessibility, theming, and visual bars. Using them means those bars are met by construction. Whenever PRIZM ships a component for the job — layout (Frame, Stack), forms (Input, Select, Checkbox, Textarea), interactive surfaces (Button, Tabs, Sheet, Command), content (Card, Heading, Text) — reach for it before writing a bespoke one or dropping to raw HTML.
  • Typed contract boundaries — shared type definitions at the seam between functional and NFR surfaces, so Dev can layer NFR work on top of a UX-authored scaffold without redefining prop shapes or guessing at intent. Straightforward integration is the goal; typed contracts are how L1 delivers it.

Why four layers, not one

A single uniform scan catches syntactic and pattern-level issues but not architectural drift, integration-time failures, or repeat offences that a static rule set never learns from. The four layers exist because those failure modes intervene at different points in the lifecycle:

  • L1 shapes the code before it exists — cheapest to fix, before violations occur.
  • L2 checks that the thing being built is worth building.
  • L3 catches known issues that can be checked by a rule — hardcoded secrets, vulnerable dependencies, injection patterns, accessibility failures — at PR time.
  • L4 catches the interaction effects that only appear once functional and NFR code are integrated.

Any one layer is necessary but not sufficient. Together, they let functional code ship into the same DevSecOps pipeline as NFR code, with the same confidence.

Starter CLAUDE.md

Below is a ready-to-use starting file. Drop it into a project as-is, or edit it as a template.

Before use: the rules reference the project's actual patterns (shared API wrapper, error handling pattern, state management approach). These are placeholders in the starter template and must be verified — and the file endorsed — by the project's tech lead before it is enabled. A rule that names the wrong convention actively misleads Claude Code and recreates the integration risk this file exists to prevent.

Rules use MUST / SHOULD phrasing rather than a separate severity field, since directive language measurably improves how consistently Claude Code follows a rule. Enforcement-layer mapping lives in the four-layer table above, not repeated per rule — every line in CLAUDE.md loads into context on every session, so keep it lean.

Drop into a project

Save as CLAUDE.md at the repo root. Replace the placeholder patterns with the real ones from the project, then have the tech lead endorse the file before it's enabled.

# CLAUDE.md

## Project Context

- Name: [project name]
- Tech stack: [e.g. React + TypeScript, Node/Express, PostgreSQL]
- Last reviewed: [date]
- Endorsed by: [Tech Lead name] — [date]. Required before this file is
  enabled for a project; see note below.

<!-- Point to deeper docs instead of pasting them, e.g.:
See @README.md for project overview
See @docs/architecture.md for system design -->

> **Before use:** the rules below reference this project's actual patterns
> (`apiClient`, `ErrorBoundary`, state management, etc.). These are
> placeholders in the starter template and must be verified — and the file
> endorsed — by the project's tech lead before it's enabled. A rule that
> names the wrong convention is worse than no rule at all.

## Commands

- Install: `[e.g. pnpm install]`
- Dev: `[e.g. pnpm dev]`
- Build: `[e.g. pnpm build]`
- Type check: `[e.g. pnpm typecheck]`
- Lint: `[e.g. pnpm lint]`
- Test: `[e.g. pnpm test]`

## Working Principles

General session behavior, separate from the NFR rules below — these apply to
how Claude Code works, not to properties of the code it produces.

- **IMPORTANT: make minimal, targeted changes.** Do not refactor, reformat, or
  "clean up" code outside the scope of the current task.
- **IMPORTANT: when unsure between two reasonable approaches, explain the
  tradeoff and ask, rather than picking one silently.**
- Prefer reusing existing helpers, components, and utilities over writing new
  ones from scratch.
- Do not edit authentication, data classification, or deployment configuration
  without first explaining the risk and getting confirmation.
- Keep one logical change per PR; do not bundle unrelated changes together.

## Verification

Before opening a PR, run the relevant commands from above and report any that
could not be run:

- Type check and lint must both pass.
- Run tests for anything touching shared logic or state.
- For UI changes, do a visual check against the design in Figma/PRIZM before
  requesting review.

## Non-Functional Requirement Rules

These apply to all code generated in this repository, regardless of whether
the author is UX or Dev. Organised by category, not by author — functional
code inherits the same rules NFR code is expected to follow.

**MUST** = blocking, fails the PR. **SHOULD** = advisory, flagged but non-blocking.

**If a rule below says "use the existing X" and no clear existing pattern is
found, or more than one conflicting pattern exists in the codebase: STOP and
note the ambiguity in the PR description rather than picking one.** This is
the single most important instruction in this file — a flagged ambiguity
costs Dev two minutes to answer; a silent guess costs a rewrite.

### Architecture & Integration

- **[NFR-ARCH-01] MUST** separate presentational components (markup, styling)
  from logic and data-fetching (state, API calls). Keep them in different
  files/hooks, not interleaved in one component. (Lets NFR work be layered in
  without touching UI code.)
- **[NFR-ARCH-02] MUST** expose typed props/interfaces for every component,
  using shared type definitions where one already exists rather than
  redefining local shapes. (Mismatched types are the most common cause of
  integration breakage.)
- **[NFR-ARCH-03] MUST** follow the existing folder/file naming convention
  for the module being worked in, rather than introducing a new structure.
  (Inconsistent structure is what makes a component feel unsalvageable.)
- **[NFR-ARCH-04] MUST** compose PRIZM components whenever one exists for
  the job — layout, forms, interactive surfaces, content — before writing a
  bespoke component or dropping to raw HTML. If a PRIZM primitive is missing,
  flag it per the ambiguity rule rather than fabricating one. (Reaching for
  PRIZM by default is how L1 keeps accessibility, theming, and visual bars
  met by construction.)

### Code Convention

- **[NFR-API-01] MUST** call backend services through the shared `apiClient`
  wrapper; never call `fetch`/`axios` directly. (Centralises auth headers,
  retries, and error shape.)
- **[NFR-API-02] MUST NOT** introduce a new HTTP/data-fetching library; use
  the data layer already in use elsewhere in the app.

### Error Handling

- **[NFR-ERR-01] MUST** wrap async UI actions in the existing `ErrorBoundary` /
  `useAsyncState` pattern; no bare try/catch with silent swallow. (Silent
  failures stay invisible until a user reports them.)
- **[NFR-ERR-02] MUST** define loading, empty, and error states for any
  data-driven component, not just the happy path. (Missing states are usually
  retrofitted, not added — which is what triggers a rewrite.)

### Accessibility

- **[NFR-A11Y-01] MUST** give all interactive elements accessible names; use
  PRIZM form components rather than raw HTML inputs. (Screen-reader and
  keyboard navigation depend on this.)
- **[NFR-A11Y-02] MUST** use semantic HTML landmarks consistent with the rest
  of the app's layout structure.

### Performance

- **[NFR-PERF-01] SHOULD** use the shared virtualised list component for
  lists over 50 items, not a plain map render. (Unvirtualised long lists are
  the most common cause of jank reports.)
- **[NFR-PERF-02] SHOULD** avoid inline function/object literals in render
  paths where the rest of the app relies on memoisation.

### Security

- **[NFR-SEC-01] MUST** never inline secrets, tokens, or environment values;
  reference the config module only. (The single most common SAST finding.)
- **[NFR-SEC-02] MUST** use the shared auth/session hook to check
  permissions; never re-implement auth logic locally.
- **[NFR-SEC-03] MUST NOT** log, print, or expose personally identifiable
  information (PII) or classified/sensitive data in console output, error
  messages, or client-side storage.

### State & Data Management

- **[NFR-STATE-01] MUST** use the app's existing state management pattern
  (shared store/hook) for any state that needs to persist beyond a single
  component. No component-local copies of server state.
- **[NFR-STATE-02] MUST NOT** introduce a new state management library or
  pattern alongside the one already in use.

### Testing

- **[NFR-TEST-01] MUST** include a colocated test file for any new
  component, following the existing test file naming convention (e.g.
  `Component.test.tsx` next to `Component.tsx`). If no test convention is
  found, flag it per the ambiguity rule above rather than skipping tests.
- **[NFR-TEST-02] MUST** add `data-testid` attributes to primary
  interactive elements, using the existing naming convention, so Dev's
  automated test suite doesn't need to be retrofitted after handover.

### Dependencies

- **[NFR-DEP-01] MUST NOT** add a new npm package without first checking
  whether an existing library in the repo already covers the need, and
  flagging the addition in the PR description. (An unreviewed new dependency
  is one of the fastest ways to trigger a full rewrite.)

### Documentation

- **[NFR-DOC-01] SHOULD** add a brief comment explaining non-obvious
  business logic decisions — the "why," not the "what." (Restating the code
  in words isn't documentation.)
- **[NFR-DOC-02] MUST** update the relevant README or docs when introducing
  a new environment variable, script, or public-facing API.

### Version Control

- **[NFR-VC-01] MUST** write descriptive commit messages (what changed and
  why); no generic messages like "fix" or "update."
- **[NFR-VC-02] MUST NOT** commit commented-out code, debug console logs, or
  TODO comments without a linked ticket.

## Adding New Rules

- ID format: `NFR-<CATEGORY>-<NUMBER>`, stable once assigned.
- Write rules as actions, not vibes: name the exact pattern/component/wrapper.
  ("Handle errors gracefully" is not a rule.)
- Promote a rule here once the same PR review pushback recurs 3+ times in a
  sprint — don't pre-guess rules that haven't come up yet.
- Keep this file scannable. If a rule only matters for one part of the
  codebase, put it in a path-scoped rule under `.claude/rules/` instead.

Where to go next