@nurix/etna 0.18.22 → 0.18.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/data/harness-index.json +1 -5
  2. package/data/harnesses/code/CLAUDE.md +16 -27
  3. package/data/harnesses/code/harness.json +2 -2
  4. package/data/harnesses/code/rules/testing.md +1 -1
  5. package/data/harnesses/etna/CLAUDE.md +5 -14
  6. package/data/harnesses/etna/harness.json +2 -2
  7. package/data/harnesses/etna/rules/testing.md +1 -1
  8. package/package.json +1 -1
  9. package/data/harnesses/mobile/.prettierignore +0 -12
  10. package/data/harnesses/mobile/.prettierrc.json +0 -3
  11. package/data/harnesses/mobile/.vscode/settings.json +0 -10
  12. package/data/harnesses/mobile/CLAUDE.md +0 -151
  13. package/data/harnesses/mobile/agents/brainstorm.md +0 -35
  14. package/data/harnesses/mobile/agents/change-review.md +0 -251
  15. package/data/harnesses/mobile/agents/lld-mode.md +0 -56
  16. package/data/harnesses/mobile/agents/maintenance.md +0 -216
  17. package/data/harnesses/mobile/agents/performance-review.md +0 -223
  18. package/data/harnesses/mobile/harness.json +0 -11
  19. package/data/harnesses/mobile/hooks/generate-ownership-index.mjs +0 -148
  20. package/data/harnesses/mobile/hooks/hooks.json +0 -16
  21. package/data/harnesses/mobile/rules/database.md +0 -38
  22. package/data/harnesses/mobile/rules/general.md +0 -49
  23. package/data/harnesses/mobile/rules/nomenclature.md +0 -34
  24. package/data/harnesses/mobile/rules/nustack.md +0 -150
  25. package/data/harnesses/mobile/rules/react-native.md +0 -132
  26. package/data/harnesses/mobile/rules/testing.md +0 -25
  27. package/data/harnesses/mobile/rules/typescript.md +0 -40
  28. package/data/harnesses/mobile/skills/boundary/SKILL.md +0 -69
  29. package/data/harnesses/mobile/skills/boundary/references/index.md +0 -16
  30. package/data/harnesses/mobile/skills/boundary/references/mechanisms.md +0 -99
  31. package/data/harnesses/mobile/skills/boundary/references/packs/async-messaging.md +0 -81
  32. package/data/harnesses/mobile/skills/boundary/references/packs/auth.md +0 -93
  33. package/data/harnesses/mobile/skills/boundary/references/packs/build-config-runtime.md +0 -93
  34. package/data/harnesses/mobile/skills/boundary/references/packs/cloudflare.md +0 -93
  35. package/data/harnesses/mobile/skills/boundary/references/packs/frontend.md +0 -93
  36. package/data/harnesses/mobile/skills/boundary/references/packs/generic-tail.md +0 -55
  37. package/data/harnesses/mobile/skills/boundary/references/packs/persistence.md +0 -93
  38. package/data/harnesses/mobile/skills/boundary/references/packs/wire-api.md +0 -87
  39. package/data/harnesses/mobile/skills/brainstorm/README.md +0 -162
  40. package/data/harnesses/mobile/skills/brainstorm/SKILL.md +0 -152
  41. package/data/harnesses/mobile/skills/brainstorm/exit-artifacts.md +0 -66
  42. package/data/harnesses/mobile/skills/brainstorm/templates/backlog.md +0 -39
  43. package/data/harnesses/mobile/skills/brainstorm/templates/build.md +0 -37
  44. package/data/harnesses/mobile/skills/brainstorm/templates/decisions.md +0 -29
  45. package/data/harnesses/mobile/skills/brainstorm/templates/doctrine.md +0 -31
  46. package/data/harnesses/mobile/skills/brainstorm/templates/domain-model.md +0 -25
  47. package/data/harnesses/mobile/skills/brainstorm/templates/evidence.md +0 -24
  48. package/data/harnesses/mobile/skills/brainstorm/templates/glossary.md +0 -20
  49. package/data/harnesses/mobile/skills/brainstorm/templates/milestones.md +0 -43
  50. package/data/harnesses/mobile/skills/brainstorm/templates/non-goals.md +0 -17
  51. package/data/harnesses/mobile/skills/brainstorm/templates/plan-doc.md +0 -61
  52. package/data/harnesses/mobile/skills/brainstorm/templates/questions.md +0 -36
  53. package/data/harnesses/mobile/skills/brainstorm/templates/references.md +0 -28
  54. package/data/harnesses/mobile/skills/brainstorm/templates/rejected.md +0 -33
  55. package/data/harnesses/mobile/skills/brainstorm/templates/source-of-truth.md +0 -49
  56. package/data/harnesses/mobile/skills/brainstorm/templates/state.md +0 -1
  57. package/data/harnesses/mobile/skills/brainstorm/templates/tech.md +0 -23
  58. package/data/harnesses/mobile/skills/brainstorm/templates/thesis.md +0 -21
  59. package/data/harnesses/mobile/skills/documentation/SKILL.md +0 -172
  60. package/data/harnesses/mobile/skills/documentation/references/examples.md +0 -203
  61. package/data/harnesses/mobile/skills/documentation/references/style-guide.md +0 -315
  62. package/data/harnesses/mobile/skills/documentation/references/templates.md +0 -1091
  63. package/data/harnesses/mobile/skills/workflow-mapper/SKILL.md +0 -451
  64. package/data/harnesses/mobile/skills/workflow-mapper/references/patterns.md +0 -214
@@ -1,150 +0,0 @@
1
- # Nustack — `@nurix/*` Packages: naming, monorepo layout, and publishing
2
-
3
- How a `@nurix/*` multi-package repo is **named**, **structured**, and **shipped**. For consuming an already-published `@nurix/*` package (auth, install, imports), see the Consuming section of [`typescript.md`](typescript.md).
4
-
5
- ---
6
-
7
- ## 1. Package naming
8
-
9
- Every workspace package is scoped to **`@nurix/`**. The segment after the scope encodes **intent** — the dot vs. hyphen is semantic, not cosmetic:
10
-
11
- | Form | Meaning | Examples |
12
- | ----------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
13
- | `@nurix/workflows-sdk` (hyphen, single segment) | The **published, externally-destined** surface. A clean `@nurix/*` name is a published _contract_ — changing an export or peer dep is a breaking change for consumers outside this repo. | `@nurix/workflows-sdk` |
14
- | `@nurix/workflows.<name>` (dot separator) | **Internal** to the workflows product — `private: true`, refactor freely, no external consumers. | `@nurix/workflows.agent-core`, `.worker`, `.gateway`, `.secrets`, `.connector`, `.playground`, `.playground-tests` |
15
- | `@nurix/<name>` (plain, no `workflows` prefix) | Cross-repo **shared libs** owned _elsewhere_ and merely consumed here — never renamed. | `@nurix/contracts`, `@nurix/components` |
16
-
17
- - The `<name>` segment is always **kebab-case** (`agent-core`, `playground-tests`).
18
- - The dot is **not** a path separator, so subpath exports still resolve: `@nurix/workflows.agent-core/nodes`.
19
- - The dot tracks "internal product package," not the `private` flag exactly — `@nurix/workflows.secrets` is `private: false` because it's extraction-bound (slated to move to a sibling repo/namespace), yet still wears the dotted internal name. Today `-sdk` is the only clean published form.
20
-
21
- **Naming a new package:** internal to this repo? → `@nurix/workflows.<thing>`, `private: true`, kebab-case `<thing>` (the default). Externally consumed contract? → clean `@nurix/<thing>` (rare). Shared lib from another repo? → keep its existing `@nurix/<name>`.
22
-
23
- ---
24
-
25
- ## 2. Monorepo & package boundaries
26
-
27
- One `tsconfig.base.json` at the repo root is the **single owner of TS policy** — the strict set (see [`typescript.md`](typescript.md)), `target`, and any option uniform across packages (here, `module`/`moduleResolution`). Each package's `tsconfig.json` is thin: it `extends` the base, then adds only its own *coordinates* (`rootDir`/`outDir`/`include`) and genuine *environment* overrides (`lib`/`types`/`jsx`, plus `module`/`moduleResolution` when a package's module system genuinely differs — a Worker, browser, or NodeNext package). That is environment, not policy. Never re-declare or weaken a *policy* option locally (no per-package `strict: false`, no widening `noUncheckedIndexedAccess`). A framework that owns its own base (e.g. an Astro app on `astro/tsconfigs/strict`) is the one allowed exception.
28
-
29
- - **Internal packages are source-first.** A `private: true` workspace package exposes its API through the `exports` field pointing at TypeScript source (`"types"`/`"default"` → `./src/index.ts`) — consumed across the workspace with **no build step**. Prefer the `exports` field; don't add a bare `main`/`module` to new packages. Add one typed subpath per public surface (`"./nodes": { "types": "./src/nodes/index.ts", "default": "./src/nodes/index.ts" }`); consumers import the subpath, **never** a deep relative path into another package's `src/`.
30
- - **Published packages build to `dist` — never ship raw `src`.** The moment a package crosses a publish boundary (`private: false`), it compiles with `tsc` (`declaration: true`, `declarationMap: true` from the base), ships `files: ["dist"]`, and points `exports`/`types` at `./dist/*` (use `publishConfig.exports` to swap source→dist on publish if the dev entry stays on source). `prepublishOnly` runs the build.
31
- - **Declare host singletons as `peerDependencies`** on published libraries (`react`, `react-dom`, the framework client) so consumers keep one copy; never bundle them. Apps depend on them directly.
32
- - **Single-source shared dep versions with pnpm `catalog:`** — `typescript`, `zod`, `vitest`, `react`, type packages. One version per dependency across the workspace so every package's types resolve identically; no per-manifest version drift.
33
- - **Reach for TS project references only when it hurts** — adopt `references` once the typecheck/build graph is slow enough to feel. Until then, `pnpm -r typecheck` over independent `extends` configs is the correct default.
34
-
35
- ---
36
-
37
- ## 3. Publishing & the release pipeline
38
-
39
- `@nurix/*` packages publish to **npmjs.org with `restricted` access** — only npm users with read rights to the `nurix` org can install them. Reference implementations: `~/dev/components` (`@nurix/components`, library) and `~/dev/etna/packages/cli` (`@nurix/etna`, CLI).
40
-
41
- ### 3.1 Prerequisites (one-time, org-wide)
42
-
43
- 1. **npm org `nurix`** exists and you're a member with publish rights — <https://www.npmjs.com/settings/nurix/packages>.
44
- 2. **An npm automation token** with `Read and write` for the `nurix` scope — npm → Generate New Token → Automation.
45
- 3. **The token added as a GitHub Actions secret** named `NPM_PUBLISH_TOKEN` on every publishing repo. `NPM_PUBLISH_TOKEN` is the convention; pick another name only with a strong reason.
46
-
47
- ### 3.2 `package.json` — required fields
48
-
49
- ```jsonc
50
- {
51
- "name": "@nurix/<package-name>", // The scope makes it discoverable to org members.
52
- "version": "0.1.0", // Start at 0.1.0; the workflow bumps from here.
53
- "private": false, // MUST be false (or omitted). `npm publish` refuses if true.
54
- "publishConfig": { "access": "restricted" }, // Scoped + private: only org members can install.
55
- "files": ["dist"] // Whitelist of what ships. Anything outside is excluded.
56
- }
57
- ```
58
-
59
- - **`publishConfig.access`** — `"restricted"` (recommended): visible only to `nurix`-org readers. `"public"`: world-readable (free, but only if you mean it). Omitting it on a scoped package defaults to `restricted`; be explicit anyway.
60
- - **`files`** — without it, npm publishes everything not in `.gitignore`, **including source**. Library → `["dist"]`; CLI (etna) → `["bin/", "data/", "README.md"]`. Don't list `package.json`/`README.md` — npm always includes them.
61
- - **Build hooks** — `prepack` for build steps that must produce files in `files` (also runs on install-as-git-dep); `prepublishOnly` for publish-only steps. Either alone is fine; `@nurix/components` runs both.
62
- - **Other useful fields** — `type: "module"`, `engines.node`, `bin` (CLIs), `main`/`module`/`types`/`exports` (libraries), `sideEffects: ["**/*.css"]`, `repository` (with `directory` for monorepos), `license: "UNLICENSED"`.
63
-
64
- ### 3.3 The publish workflow (generic template)
65
-
66
- The dual-trigger pattern: a push to the default branch auto-ships a patch; a manual dispatch picks minor/major.
67
-
68
- ```yaml
69
- name: Publish to npm
70
- on:
71
- push:
72
- branches: [dev] # your default branch
73
- paths: # ONLY paths that change what ships — else every README edit publishes
74
- - 'src/**'
75
- - 'scripts/**'
76
- - 'package.json'
77
- - 'package-lock.json'
78
- - '.github/workflows/publish.yml'
79
- workflow_dispatch:
80
- inputs:
81
- bump: { description: 'Version bump', type: choice, options: [patch, minor, major], default: patch }
82
- permissions: { contents: write } # push the version-bump commit + tag
83
- concurrency: { group: publish, cancel-in-progress: false } # monorepo: group: publish-<pkg>
84
- jobs:
85
- publish:
86
- runs-on: ubuntu-latest
87
- steps:
88
- - uses: actions/checkout@v5
89
- with: { fetch-depth: 0, token: '${{ secrets.GITHUB_TOKEN }}' }
90
- - uses: actions/setup-node@v5
91
- with: { node-version: 22, registry-url: 'https://registry.npmjs.org' } # writes .npmrc using $NODE_AUTH_TOKEN
92
- - run: npm ci # skip ONLY if the build is pure-Node with no deps (e.g. @nurix/etna)
93
- - run: |
94
- git config user.name "github-actions[bot]"
95
- git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
96
- - run: npm version ${{ inputs.bump || 'patch' }} -m "chore: release v%s [skip ci]"
97
- - run: npm publish # prepublishOnly / prepack runs the build
98
- env: { NODE_AUTH_TOKEN: '${{ secrets.NPM_PUBLISH_TOKEN }}' }
99
- - run: git push --follow-tags # pushes the bump commit AND the v<version> tag npm created
100
- ```
101
-
102
- **Why dual-trigger is safe** — three safeguards prevent an infinite publish loop: (1) `paths:` scopes the push trigger to artifact-affecting files; (2) `[skip ci]` in the bump commit makes GitHub skip the workflow that bump would otherwise re-trigger; (3) `concurrency: cancel-in-progress: false` queues near-simultaneous merges instead of racing for a version number.
103
-
104
- **`registry-url` is load-bearing** — without `registry-url: https://registry.npmjs.org` on `setup-node`, it never writes the `.npmrc` line that uses `$NODE_AUTH_TOKEN`, and the publish falls back to anonymous auth and fails.
105
-
106
- ### 3.4 The three ways to ship
107
-
108
- - **A. Auto on merge (common).** Land on the default branch; if a `paths:`-filtered file changed, the workflow fires and ships a patch.
109
- - **B. Manual dispatch (minor/major).** **Actions → Publish to npm → Run workflow**, pick `bump = minor|major`.
110
- - **C. Local (CI down / one-off):** `npm version <patch|minor|major>` → `npm publish` → `git push --follow-tags`.
111
-
112
- Local auth uses two tokens, two homes: a **read** token in `~/.npmrc` (`//registry.npmjs.org/:_authToken=…`) for `npm install`, and `NPM_PUBLISH_TOKEN` as an env var (sourced from `~/.zsh_secrets` or equivalent, **not** in `.npmrc` or the repo) for `npm publish`.
113
-
114
- > **Agent guard — if `NPM_PUBLISH_TOKEN` isn't in the environment, do not work around it.** No `npm login` to swap the user's auth, no rewriting `~/.npmrc`, no substituting the read token (it lacks publish rights → `403`), no `--registry` overrides, no temp-file token. Instead **stop and report**: "This repo can't publish locally — `NPM_PUBLISH_TOKEN` isn't set. Source `~/.zsh_secrets` (or wherever you keep it) and re-run, or trigger the CI workflow." Some users publish exclusively from CI — that's the default path for a reason.
115
-
116
- Verify after any flow: `https://www.npmjs.com/package/@nurix/<name>` and a clean-dir `npx @nurix/<name>@latest`.
117
-
118
- ### 3.5 Monorepo deviations
119
-
120
- When the package lives in `packages/<pkg>/` of a multi-package repo:
121
-
122
- - **`working-directory: packages/<pkg>`** on the bump and publish steps so they operate on the right `package.json`.
123
- - **Scoped concurrency** — `group: publish-<pkg>` lets sibling packages publish in parallel.
124
- - **Tag prefix — only with multiple tagging packages.** `npm version --tag-version-prefix="<pkg>-v"` (→ `<pkg>-v0.2.0`) is worth it only when more than one package cuts release tags, so they don't collide. A single publishable package (etna's case, even though it's laid out as a monorepo) uses plain `v0.2.0`.
125
-
126
- A repo with one publishable package only needs `working-directory`; skip the prefix, and concurrency scoping is optional.
127
-
128
- ### 3.6 Common pitfalls
129
-
130
- - **402 Payment Required** — `access: "public"` on an org without a plan for free public scoped packages. Use `"restricted"` (or upgrade the plan).
131
- - **403 Forbidden** — `NPM_PUBLISH_TOKEN` missing/wrong, or its user lacks `nurix` write access, or it's a read-only (not Automation) token.
132
- - **"Cannot publish over previously published version x.y.z"** — a bump ran but publish failed and was re-run without rebasing. `git reset` the bump and let CI re-bump, or `npm version patch && git push --follow-tags && npm publish`.
133
- - **Publish succeeds but `npx @nurix/foo` is "command not found"** — no `bin` field, or `bin` points outside `files`, or the script is missing its `#!/usr/bin/env node` shebang.
134
- - **"Git working directory not clean" on `npm version`** — the checkout has untracked/modified files; build only into `.gitignore`d paths (the components repo builds into `dist/`).
135
- - **Published tarball contains source** — missing `files` in `package.json`. Add `["dist"]` and republish.
136
- - **Auto-publish fires too often** — no `paths:` filter or one that's too broad. Tighten it to artifact-affecting files, or drop the push trigger for `workflow_dispatch`-only.
137
- - **Workflow loops forever** — the bump commit lacks `[skip ci]`. Invoke `npm version` with `-m "chore: release v%s [skip ci]"`.
138
- - **A normal commit silently skips its own push** — the inverse trap. GitHub scans the head commit's **whole message** (subject _and_ body) for skip directives (`[skip ci]`, `[ci skip]`, `[no ci]`, `[skip actions]`, `***NO_CI***`). If any hand-written commit merely _mentions_ the literal token in prose, the entire push is skipped and the publish never fires (symptom: branch moved, paths matched, Actions on — yet zero runs for the SHA). Never write the literal token in a commit message or PR body unless you mean it; to ship a push that carried it by accident, re-trigger via **Run workflow** / `gh workflow run … --ref dev`.
139
-
140
- ---
141
-
142
- ## 4. Quick checklist for a new `@nurix/*` package
143
-
144
- - [ ] `package.json`: `name: "@nurix/X"`, `version: "0.1.0"`, `private: false`, `publishConfig.access: "restricted"`, `files: [...]`
145
- - [ ] Build step wired into `prepack` (or `prepublishOnly`)
146
- - [ ] Name follows §1; if it's a workspace member, boundaries follow §2
147
- - [ ] `.github/workflows/publish.yml` from §3.3
148
- - [ ] `NPM_PUBLISH_TOKEN` secret added to the repo
149
- - [ ] First publish from local (`npm publish`) to verify the pipeline, then a no-op commit to verify CI
150
- - [ ] README notes the `@nurix` scope so consumers know they need org access
@@ -1,132 +0,0 @@
1
- # React Native + Expo
2
-
3
- How mobile apps are built. Distilled from a hand-crafted reference app — the gold standard for *style*. This rule is **scale-aware**: a few-screen app uses the small-app form, a feature/auth-heavy app grows into the full form. Where a section offers both, the trigger to graduate is stated.
4
-
5
- ## 1. Stack & packages
6
-
7
- - **Expo SDK** managed workflow, **config-as-code** (`app.config.ts`, never `app.json`), **expo-router** for navigation, **TypeScript strict**.
8
- - New Architecture **on** by default; turn it **off** only for a legacy native module that breaks under it — document *why* at the top of `app.config.ts`.
9
- - `react` / `react-dom` versions must match. Run `npx expo install <pkg>` (not a bare add) for any native/Expo module so the SDK-compatible version is picked.
10
-
11
- | Need | Use | Not |
12
- | --- | --- | --- |
13
- | HTTP | native `fetch` wrapped in a `request<T>()` util | axios |
14
- | Navigation | `expo-router` (file-based) | bare React Navigation at the root |
15
- | Storage (sensitive) | `expo-secure-store` | AsyncStorage |
16
- | Storage (prefs/cache) | `@react-native-async-storage/async-storage` | secure-store |
17
- | State (non-trivial) | `@reduxjs/toolkit` + `react-redux` | zustand, jotai |
18
- | Styling | `StyleSheet.create` + token object | nativewind, tamagui, styled |
19
- | Toasts | `react-native-toast-message` | custom |
20
- | Haptics | `expo-haptics` behind one dispatcher | scattered call sites |
21
-
22
- ## 2. Structure & naming
23
-
24
- - **Path alias `@/*` → `./src/*`** (in `tsconfig.json`). Import via `@/...`, never deep-relative (`../../..`). Sibling/child relative imports are fine.
25
- - **Every file starts with a path + one-line purpose comment.** One-line comment above each function.
26
- - **No barrel/index re-export files.** Import the source directly: `import { ThemedButton } from "@/components/ThemedButton"`.
27
- - **Naming:** components & screens `PascalCase.tsx`; hooks `useXxx.ts`; services/utils/reducers/types `camelCase.ts`; route files follow expo-router (`index.tsx`, `[id].tsx`, `_layout.tsx`).
28
-
29
- **Directory layout** — `app/` is routes only; everything else under `src/`:
30
-
31
- ```
32
- app/ # expo-router routes (screens) ONLY — see §3
33
- src/
34
- components/ # presentational primitives + shared UI (PascalCase)
35
- hooks/ # useXxx
36
- services/ # api client, auth storage, background tasks, notifications
37
- state/ | reducers/+store/ # see §4 (scale decides)
38
- types/ # shared TS contracts
39
- utils/ # pure helpers (haptics, deepLink, schemas, ...)
40
- constants/ # Colors.ts, routes.ts, toast_config.tsx
41
- navigation/ # custom navigators / BackButton (only if you outgrow Stack/Tabs)
42
- ```
43
-
44
- > **Lean form:** for a handful of screens, group by domain instead of by type — e.g. `src/{data,player,state,ui}` — keeping `app/` routes-only. Graduate to the by-type layout above once you have >~3 cross-cutting concerns (auth + tabs + notifications + ...).
45
-
46
- ## 3. Expo Router & navigation
47
-
48
- - **Routes live in `app/`** and contain screen logic; **presentational components live in `src/components/`**. A route file may hold its own data/business logic — strict container/presentational split is not required, but pure UI primitives stay pure.
49
- - `experiments.typedRoutes: true`. Navigate with `router.push()` / `router.replace()` (use `replace` for auth/redirect transitions); reserve `<Link>` for static in-content links. Read params with `useLocalSearchParams()`.
50
- - **Route groups** `(auth)` / `(tabs)` to partition without affecting the URL. `index.tsx` may be pure redirect logic, not a screen.
51
- - **Root `_layout.tsx` is the provider tree**, composed outside-in in this order (include only what the app needs):
52
-
53
- ```
54
- ErrorBoundary → Redux Provider → HydrationGate → GestureHandlerRootView
55
- → SafeAreaProvider → StatusBar → <Stack/Tabs> → Toast
56
- ```
57
-
58
- A lean app collapses this to `StatusBar → Stack` plus a one-time `useEffect` bootstrap.
59
- - Header styling via `screenOptions` on the `Stack`; per-screen title via `<Stack.Screen options={{ title }}>`.
60
- - For a bottom-tab app needing a custom header/drawer, `createBottomTabNavigator` inside a `(tabs)/_layout.tsx` is acceptable over expo-router `Tabs` when you need fine layout control.
61
-
62
- ## 4. State
63
-
64
- **Scale rule:**
65
-
66
- - **Trivial** (no auth, few screens) — **plain TS modules** under `src/state/`: a module owns one concern, exposes async get/set functions, persists to AsyncStorage with swallowed try/catch, and may cache in a module-level variable.
67
- - **Non-trivial** (auth, preloaded config, cross-screen data) — **Redux Toolkit**:
68
- - Slices in `src/reducers/`, store in `src/store/`. Combine slices in one root reducer.
69
- - **Persist with listener middleware** (debounced ~300ms), **not** `redux-persist`.
70
- - **Dual storage:** sensitive state (tokens) → `expo-secure-store`; UI prefs → AsyncStorage.
71
- - **Hydrate before first render:** async hydrate thunks dispatched by a `HydrationGate` that shows a spinner until rehydrated — prevents a flash of unauthenticated UI.
72
- - Store absolute expiries (`accessExpiresAt`), computed at login from the `expiresIn` the server returns.
73
-
74
- ## 5. Data fetching / networking
75
-
76
- - **Native `fetch`**, wrapped once: a `request<T>(method, endpoint, body?, opts?)` util in `src/services/api.ts`, and a `useRequest<T>()` hook returning `{ loading, response, error, makeRequest }`.
77
- - Base URL and auth token are read **at call time** (from Redux state or the settings module), not baked into a client instance. Inject `Authorization: Bearer <token>` only when `authorized: true`.
78
- - Per-call `onSuccess` / `onFail` callbacks; no global interceptor. Surface failures as toasts or inline error UI.
79
- - **Every fetched surface renders three states: loading, empty, error** (with a Retry affordance). Caching is in-memory at the data-module level for a lean app; Redux slices serve as the cache for the full app.
80
- - Types: a `RequestMethod` union and a generic `ApiResponse<T>`; endpoint-specific call wrappers live next to their screen (`app/<feature>/api.ts`).
81
-
82
- ## 6. Styling & theming
83
-
84
- - `StyleSheet.create` for static styles; inline only for dynamic (theme) values. **No** utility-CSS or styled-components libs.
85
- - **Color tokens in one place** (`src/constants/Colors.ts`) — components reference tokens, never raw hex.
86
- - **Theming is a hook, not a Context provider.** `useTheme()` returns `{ colors, colorScheme, isDark }`, resolving **user preference → device `useColorScheme()` → light fallback**. The preference lives in state (e.g. `customize.theme`: `"light" | "dark" | "system"`), so theme survives restart and there's no provider to thread.
87
-
88
- > **Lean form:** a flat single-mode token object is fine until dark mode is a requirement; adopt the light/dark `Colors` + `useTheme()` shape when it is.
89
-
90
- ## 7. Native modules & permissions
91
-
92
- - **Wrap each `expo-*` module behind a typed helper** — never sprinkle raw calls. Canonical example: one `triggerHaptic(type)` dispatcher with named exports (`triggerSuccessHaptic`, `triggerTabChangeHaptic`, ...) over `expo-haptics`.
93
- - **Platform-specific code via filename suffixes** — `Foo.native.tsx` / `Foo.web.tsx` / `Foo.tsx` — preferred over imperative `Platform.select()`. Use `Platform.OS` checks for small inline branches only.
94
- - Declare Android/iOS permissions and usage strings in `app.config.ts`. Request at point-of-use with a clear pre-prompt where the platform allows.
95
- - Background work (location, audio) runs through a registered task (`expo-task-manager` / a playback service) so it survives the UI being unmounted — register it in the **app entry** before the router mounts.
96
-
97
- ## 8. Reusable primitives
98
-
99
- - **`Themed{View,Text,Button}`** as the base layer: extend the native props and add `lightColor?` / `darkColor?`; `ThemedText` takes `type?: "default" | "title" | "subtitle" | "link"`; `ThemedButton` takes `variant?: "contained" | "outlined" | "text"`, `size?`, `fullWidth?`.
100
- - **`EdgeToEdgeScreen` / `EdgeToEdgeTabScreen` / `EdgeToEdgeTabBar`** own safe-area inset handling so screens never compute insets inline.
101
- - Loading/feedback primitives (`DotLoader`, `CircularProgress`, `PullToRefreshHint`) are shared, not re-implemented per screen.
102
-
103
- ## 9. TypeScript
104
-
105
- - **`interface`** for external contracts (API shapes, component props); **`type`** for unions and aliases (`type RequestMethod = "get" | ...`).
106
- - Shared domain types in `src/types/`; component prop types extend native prop types (`TextProps & { ... }`).
107
- - **Never weaken `strict` or `noUncheckedIndexedAccess`.** A framework base (`expo/tsconfig.base`) may be extended but not relaxed. Generics for `request<T>` / `useRequest<T>`; no implicit `any` on external data. (See the `typescript` rule for the workspace-wide policy.)
108
-
109
- ## 10. Config & environment
110
-
111
- - `app.config.ts` (TS) is the single source: scheme, deep-link intent filters, permissions, plugins, `userInterfaceStyle: "automatic"`, `experiments.typedRoutes: true`, splash/icon.
112
- - Client-readable env via **`EXPO_PUBLIC_*`** only, read through `expo-constants` or `process.env`. **Never commit `.env`**; ship `.env.example`. Secrets/keystore creds stay out of git.
113
- - Build profiles in `eas.json` (`development` / `preview` / `production`). Local config plugins (`./plugins/withXxx.js`) for native edits the SDK doesn't expose (e.g. Android Auto).
114
-
115
- ## 11. Errors, loading & UX
116
-
117
- - **`ErrorBoundary` class component wraps the whole app.** On crash it logs, clears *session* state but **preserves user preferences**, and shows an apology + recover affordance.
118
- - **Toasts** via `react-native-toast-message` with a custom `toast_config.tsx` (success/error/warning variants, `topOffset` below the header). Fire inline: `Toast.show({ type, text1, text2 })`.
119
- - Gate first paint: hide the splash only after fonts load + state hydrates (`expo-splash-screen`).
120
- - **Lists:** `FlatList` typed with `ListRenderItemInfo<T>`, `ListEmptyComponent` for empty, `onEndReached` + pagination for infinite scroll, `refreshing`/`onRefresh` for pull-to-refresh.
121
- - Safe-area through the `EdgeToEdge*` wrappers (§8), not ad-hoc insets.
122
-
123
- ## 12. Web target (`expo start --web`)
124
-
125
- - Web needs `react-dom`, `react-native-web`, `@expo/metro-runtime`. Add a `"web": "expo start --web"` script.
126
- - `react-native-track-player` runs on web only with its peer **`shaka-player`** installed; without it the bundle fails to resolve and the page is blank.
127
- - **Native-only features have no web equivalent** — background audio, lock-screen controls, Android Auto. Guard them behind `Platform.OS` so the web build renders, and treat web as a layout/navigation preview, not a playback/Auto test surface.
128
-
129
- ## 13. Formatting
130
-
131
- - Prettier: `tabWidth: 4`, `singleQuote: false` (double quotes), `bracketSameLine: true`, `printWidth: 120`, trailing commas.
132
- - No commented-out code; comments explain *why*, not *what*.
@@ -1,25 +0,0 @@
1
- # Testing
2
-
3
- Framework choice is never ad hoc and never the user's problem mid-task: detect the stack, use its row. If the repo already has a runner, that runner wins — never introduce a second framework beside it.
4
-
5
- ## Frameworks by stack
6
-
7
- No alternatives. Just use these.
8
-
9
- | Stack | Unit / integration | Component | E2E | HTTP mocking | Don't use |
10
- | --- | --- | --- | --- | --- | --- |
11
- | Node + Vite / React (web) | vitest | @testing-library/react (+ @testing-library/user-event, happy-dom) | @playwright/test | msw | jest, mocha, jasmine, cypress, enzyme |
12
- | Express APIs | vitest | — | supertest against the app instance | msw (outbound calls) | jest, chai-http |
13
- | React Native / Expo | jest via the jest-expo preset | @testing-library/react-native | maestro | msw | detox, enzyme |
14
- | Cloudflare Workers | vitest + @cloudflare/vitest-pool-workers | — | @playwright/test against `wrangler dev` | msw | jest |
15
- | Rust | `cargo test` (built-in) | — | — | — | — |
16
-
17
- All npm packages at `latest`. Fake data comes from `@ngneat/falso` (the repo standard — never faker/chance).
18
-
19
- ## Conventions
20
-
21
- - **Suite, not scripts.** Tests live in the canonical test tree (`tests/`, mirroring `src/`), named `<subject>.test.ts`. Shared fixtures, factories, and helpers live in `tests/helpers/`. The harness `CLAUDE.md` Tests guardrail carries the full suite-first policy; this rule owns only the technology choices.
22
- - **Wire the canonical command.** `"test"` in package.json runs the whole unit suite (`vitest run`); e2e runs under `"test:e2e"`. A repo where `pnpm test`/`npm test` doesn't run the suite is misconfigured.
23
- - **Unit and E2E trees stay separate.** Vitest/Jest suites in `tests/`; Playwright/Maestro suites in `e2e/` with their own config — never mixed into the unit runner's glob.
24
- - **No test doubles for what you own.** Prefer real instances — in-memory SQLite for drizzle, the actual Express app via supertest — over mocks; mock only true externals (network via msw, time via the runner's fake timers).
25
- - **Determinism.** No real network, no real time, no shared mutable state between tests; every test must pass alone and in any order.
@@ -1,40 +0,0 @@
1
- # TypeScript
2
-
3
- TypeScript conventions. Compiles under `strict: true` and `noUncheckedIndexedAccess: true`.
4
-
5
- ## Type Safety
6
-
7
- - Never `any` to silence the checker. Unknown value → type `unknown`, then narrow (type guard / `zod` parse).
8
- - Never weaken strictness. No `// @ts-nocheck`; `// @ts-expect-error` only with a same-line reason.
9
- - Infer locals & return types; annotate at boundaries (exports, props, contracts).
10
-
11
- ## Generics
12
-
13
- Shared libraries (`@nurix/components`, etc.) are generic on purpose — that's what types `cell(row)`, checks `slug` against `keyof TData`, and ties `data`/`columns` together. Keep the safety; never widen to `any` to dodge it. State the row type **once per file**, then let inference carry it:
14
-
15
- - **Bind a repeated factory once** (preferred): `const col = defineColumn<BriefRow>;` → `col({...})`. No `<T>` per call; `row` stays typed. Canonical: `modules/briefs/BriefsListPage.tsx`.
16
- - **Infer JSX type args from props:** `<Table data={rows} columns={columns} />`, not `<Table<BriefRow> …>`.
17
- - **Anchor with a collection annotation** when nothing else can: `const columns: ColumnDefinition<BriefRow>[] = [...]`.
18
-
19
- Don't repeat `fn<Type>(…)` / `<Comp<Type>>` at every call site — cosmetic only (same safety, same emitted JS).
20
-
21
- > Monorepo TS policy (`tsconfig.base.json` + thin per-package `extends`, source-first internal packages, `peerDependencies`, pnpm `catalog:`) lives in [`nustack.md`](nustack.md) §2 — it travels with package boundaries and publishing.
22
-
23
- ## Consuming `@nurix/*` packages
24
-
25
- `@nurix/*` packages publish to npm with **`restricted`** access — installing needs read rights to the `nurix` org. (Publishing one is [`nustack.md`](nustack.md) §3.)
26
-
27
- - **Auth** — `npm login` once (the npm user must be a `nurix` member), **or** drop a read token in `.npmrc` (the only CI option; with it you don't need `npm login`):
28
- ```ini
29
- @nurix:registry=https://registry.npmjs.org/
30
- //registry.npmjs.org/:_authToken=${NPM_READ_TOKEN}
31
- ```
32
- A `403` on install means auth failed — check `npm whoami` (local) or the `NPM_READ_TOKEN` secret (CI). The read token only needs `Read` on the `nurix` scope.
33
- - **Install** — `npm install @nurix/<name>` (or `pnpm add` / `yarn add`).
34
- - **Import by subpath, never the barrel** — bundlers tree-shake subpaths reliably; root-barrel imports less so. Available subpaths are the package's `exports` keys.
35
- ```tsx
36
- import { Button } from "@nurix/components/button";
37
- import { useMobile } from "@nurix/components/hooks/use-mobile";
38
- ```
39
- - **Styles** — packages that ship CSS need their stylesheet imported **once** at the app entry: `import "@nurix/components/styles.css";`. Unstyled components are almost always a missing styles import. Tailwind v4 doesn't scan `node_modules`, but the shipped stylesheet is precompiled — no `@source` directive needed unless you consume the source via a workspace link.
40
- - **CLIs** ship a `bin` — run directly: `npx @nurix/etna@latest` (or `npm install -g`).
@@ -1,69 +0,0 @@
1
- ---
2
- name: boundary
3
- description: Boundary diagnostic for implementation plans. Given a plan file, reads the source the plan touches, identifies the cross-cutting contracts it participates in — db schema, wire envelope, auth, events, env/config, … — where editing one side silently breaks the other, and reports whether the planned changes would violate any of them, severity-ranked, with suggested amendments and a pass/block verdict. Read-only; the report lands in chat. Also runs against a diff or named code area. Triggers on "boundary check", "check this plan", "what could break", "will this break anything", "is it safe to rename this column/endpoint/event", "who uses this", "impact analysis", "/boundary".
4
- ---
5
-
6
- # Boundary Diagnostic
7
-
8
- You diagnose whether a plan's changes would break an application boundary.
9
-
10
- A boundary is a contract between two pieces of code (or code and infra) that share an assumption with **no static link** between them: the column a raw SQL string names, the JSON envelope a client unwraps, the event string a dispatcher parses, the env var a deploy script writes. Changing one side ships green — compiles, lints, tests pass — and breaks the other at runtime. Your job is to find those edges in the plan's path _before_ execution.
11
-
12
- **The litmus is "would it ship green?"** An edge the compiler or an existing test already guards is not a boundary — a change that fails the build loudly needs no diagnostic. Spend your reading on the edges with no static link.
13
-
14
- ## Contract sources
15
-
16
- Three layers tell you what to look for; the code tells you what exists.
17
-
18
- - **The mechanisms** — [`references/mechanisms.md`](references/mechanisms.md) defines the seven failure mechanisms every boundary instantiates (rendezvous-string, serialized-shape, generated-artifact, dual-authority, config-elsewhere, trust-invariant, lifecycle-protocol), each with its trace strategy, universal safe-change pattern, and severity heuristic, plus the two stack-independent domain contracts (entity-model, tenancy). Load it on every check.
19
- - **The technology packs** — [`references/index.md`](references/index.md) routes to the packs under `references/packs/` by the signals in the touched code. Each pack carries the non-derivable payload for one technology area: exact detect patterns, expensive-to-learn gotchas, and safe-change sequences. Load **only the packs the touch list exhibits**; a technology no pack covers is still in scope — diagnose it from its mechanism.
20
- - **Repo doctrine** — `.claude/rules/`, `docs/architecture/` (including `adr/`), and any contracts package name the repo's bespoke contracts (a documented response envelope, a schema-parity rule, an entity model). A documented contract is a real side of a boundary: a plan that contradicts one is a finding **even when no code consumer exists yet** — this is how greenfield plans, whose modules have no code to trace, still get checked.
21
-
22
- The packs are a floor, not a fence: report any contract that passes the ships-green litmus, pack entry or not.
23
-
24
- ## Run
25
-
26
- **Input:** a plan path (given, or the newest in `docs/history/plans/`) — or a diff / named code area for non-plan work. Everything below is identical either way; a diff's hunks stand in for the plan's steps.
27
-
28
- 1. **Build the touch list.** Parse the plan's Proposed Changes: files created or modified, entities/state added or changed, API changes, anything the author flags as a cross-module contract. Note missing subsections.
29
-
30
- 2. **Identify the contracts.** Read each touched file (and its immediate imports). Against the mechanisms, the routed packs, and the repo doctrine, list the contracts this code participates in — the schema it queries, the envelope it emits, the events it publishes or parses, the auth checks it sits behind, the env it reads.
31
-
32
- 3. **Trace the other side.** A boundary has two sides; the danger lives on the one the plan _doesn't_ mention. For each contract, grep the shared name or shape across the repo — the column name in other SQL strings, the response field in client unwrappers, the event kind in subscribers, the route in fetch calls and scripts, the env key in deploy tooling. Record consumers as `file:line`; record doctrine as the doc path. When the touch list is large (≳10 contracts), fan the tracing out to parallel sub-agents — one per pack area, each with a self-contained prompt naming its contracts and grep targets — and judge the returned evidence yourself; a small diff traces inline.
33
-
34
- 4. **Judge each intersection.** Does the planned change alter a shape a traced consumer depends on, break an invariant the code or doctrine establishes (append-only vocabularies, idempotency keys, fail-closed checks, ordering guarantees), or introduce a new cross-module edge? Severity:
35
- - **CRITICAL** — can corrupt persisted data, or open an auth/money path.
36
- - **HIGH** — silent cross-module breakage that would ship green.
37
- - **MEDIUM** — drift-prone coupling (string-matched, duplicated constants).
38
- - **LOW** — smell. Loud, immediately-visible runtime breakage caps at HIGH.
39
-
40
- 5. **Report in chat:**
41
-
42
- ```
43
- # Boundary check — {plan}
44
-
45
- **Verdict: PASS | PASS WITH AMENDMENTS | BLOCK**
46
-
47
- Touched files read: {N} · Contracts identified: {N} · Consumers traced: {N}
48
-
49
- ## Violations
50
- | # | Boundary | Mechanism | Planned change | Other side (file:line or doc) | Severity |
51
-
52
- ## New edges the plan introduces
53
- | # | Contract | Sides | Hardening to fold into the plan |
54
-
55
- ## Coverage
56
- | Area touched | Contracts checked | Result |
57
-
58
- ## Suggested plan amendments
59
- ## Missing plan subsections
60
- ```
61
-
62
- **BLOCK** = any CRITICAL. HIGH findings yield **PASS WITH AMENDMENTS** — fold the amendments into the plan before coding. Order amendments sequencing-first: many violations dissolve by reordering steps (expand-then-contract, migrate-before-code) rather than changing what ships — prefer that fix, drawn from the entry's safe-change sequence.
63
-
64
- ## Rules
65
-
66
- - **Read-only.** You never edit the plan, the code, or any doc. The user folds your amendments into the plan.
67
- - **Verify in source.** Never flag from plan text alone — read both sides of every finding and cite them: code as `file:line`, doctrine as the doc path.
68
- - **No other side, no violation.** An intersection with no traced consumer and no doctrine behind it isn't a violation — it's a **new edge**: report it in its own section with the hardening its mechanism prescribes (version the cache key now, make the enum append-only now), so the fragile edge is born safe.
69
- - **A clean report is valid.** Zero violations means say so — the coverage table is the evidence it was checked, not skipped. Don't pad findings.
@@ -1,16 +0,0 @@
1
- # Pack routing index
2
-
3
- Load when: every boundary check, right after `mechanisms.md`. Match the touch list against the signals below and load **only** the packs whose signals appear in the touched files or the plan's Proposed Changes. Several packs matching is normal (a full-stack feature typically routes 2–4). A technology matching nothing here is still in scope — diagnose it from its mechanism, and check `packs/generic-tail.md` for a thin entry.
4
-
5
- | Pack | Load when the touch list shows |
6
- | --- | --- |
7
- | `packs/persistence.md` | `schema.ts`, `init.sql`, `sqliteTable(`, `drizzle-kit`, `pgTable`, migrations, `*.seed.sql`, seed generators, JSON columns, `pgEnum`, pgvector, Redis `get`/`set` caching |
8
- | `packs/wire-api.md` | Express routes/middleware (`app.use(`, `router.`), `res.json({ status`, zod schemas on requests, axios clients/interceptors, `cors(`, inbound webhooks, `rateLimit(` |
9
- | `packs/frontend.md` | `createSlice`/`configureStore`, RTK Query (`tagTypes`), react-router tables/`useParams`, Tailwind CSS (`@theme`, `personality.css`, `globals.css`), `vite.config.*` (alias, env), `@nurix/components`, localforage/redux-persist, date formatting |
10
- | `packs/auth.md` | `jwtVerify(`/jose, `betterAuth(`/`auth.api.`, session cookies, CSRF, OAuth callbacks/`redirect_uri`, `tenantId`/`organizationId` scoping, password hashing, auth middleware mounts |
11
- | `packs/cloudflare.md` | `wrangler.jsonc`/`wrangler.toml`, `env.X` bindings, `extends DurableObject`/`[[migrations]]`, KV/R2/D1/Queues, `scheduled(`/crons, `WorkerEntrypoint`, `compatibility_date`, static assets config |
12
- | `packs/async-messaging.md` | BullMQ (`new Queue(`/`new Worker(`), Redis pub/sub or Streams, WebSockets/socket.io, SSE (`text/event-stream`), channel-name templates, outbound webhook senders |
13
- | `packs/build-config-runtime.md` | `package.json` (deps/`exports`/scripts/`packageManager`), lockfiles, `.env*`, tsconfig `paths`, Dockerfiles/compose, `.github/workflows/`, pino config, `trust proxy`, ports/healthchecks |
14
- | `packs/generic-tail.md` | GraphQL, gRPC/protobuf, tRPC, OpenAPI codegen, Kafka/schema registry/CDC, workflow engines, event sourcing, Postgres RLS/pooling/collation, SSR/hydration, module federation, i18n, analytics plans, hosted feature flags, K8s probes/shutdown/limits, service mesh, IaC outputs, SAML, Lambda, IPC |
15
-
16
- Two files never route — they always load: `mechanisms.md` (the seven failure mechanisms + domain contracts) and this index.
@@ -1,99 +0,0 @@
1
- # Boundary Mechanisms
2
-
3
- Load when: every boundary check — this is the core reference. The technology packs under `packs/` instantiate these mechanisms with exact detect signals and gotchas; `index.md` routes to the packs the touched code exhibits.
4
-
5
- A boundary is a contract between two pieces of code (or code and infra) that share an assumption with **no static link** between them. The litmus is **"would it ship green?"** — change one side, everything compiles, lints, and tests pass, and the other side breaks at runtime. An edge the compiler or an existing test already guards is not a boundary.
6
-
7
- Every boundary instantiates one of seven mechanisms. The mechanism tells you what to grep for, what the universal fix is, and how bad a break is — including for technologies no pack covers. When touched code exhibits a contract that matches no pack entry, diagnose it from its mechanism.
8
-
9
- ---
10
-
11
- ## 1 · rendezvous-string
12
-
13
- Two sides meet on a literal identifier: a column name in raw SQL, an event/job/topic name, a route path, an env key, a cache key, a channel template, a DI token, a flag key, a metric name, a port number, a binding name. The compiler sees two unrelated literals.
14
-
15
- - **Ask:** where else does this exact string (or its template) appear — including config, IaC, CI, dashboards, and other repos?
16
- - **Failure:** the sides silently stop meeting — publishes into the void, reads `undefined`, routes 404, handler never fires, alert goes quiet.
17
- - **Trace:** grep the literal and its prefix/template variants repo-wide; the other side is often not code (a deploy manifest, a saved dashboard query, a provider allowlist).
18
- - **Universal fix:** centralize the identifier in one shared constant or module. On rename, serve both old and new until the old side drains — in-flight jobs, cached entries, sent emails, deployed clients.
19
- - **Severity:** HIGH by default; CRITICAL when the string scopes trust (a tenant segment in a channel name, a redirect-URI allowlist entry).
20
-
21
- ## 2 · serialized-shape
22
-
23
- A payload crosses a gap in space or time — a process boundary, a deploy window, persistence — and the reader is not the writer. Old data and mixed-version peers must still parse. The defining sub-case is the **mixed-version window**: rolling deploys, in-flight queue items, long-lived clients, persisted events, browser storage — the old shape exists somewhere and outlives the change.
24
-
25
- - **Ask:** who reads this shape that doesn't build from this commit — old rows, queued messages, deployed clients, replayed histories?
26
- - **Failure:** the reader crashes or misreads with no error at the writer; persisted old shapes fail **forever**, not just during the deploy.
27
- - **Trace:** find every deserializer of the shape (parsers, `JSON.parse` + property reads, ORM rehydration, client unwrappers) and every store where old instances persist.
28
- - **Universal fix:** additive-only changes (add optional fields, never rename/retype/remove); carry an explicit version and read all live versions; readers tolerate unknown fields and ignore unknown discriminators.
29
- - **Severity:** CRITICAL when the shape is persisted or replayed (the break is historical, not just live); HIGH for live-traffic shapes.
30
-
31
- ## 3 · generated-artifact
32
-
33
- A derived artifact must stay in sync with its source: ORM client from schema, types from codegen, lockfile from package.json, bundler aliases from tsconfig, seed SQL from a CSV. The artifact is trusted as if it were the source — when they drift, the artifact **lies** to everything that consumes it.
34
-
35
- - **Ask:** what generates this, what does it generate, and would CI notice if they drifted?
36
- - **Failure:** code compiles against stale types and mismatches at runtime; installs resolve a tree nobody tested; hand-edits are clobbered on the next generation.
37
- - **Trace:** find the generator invocation (scripts, `prepack`, CI steps) and every consumer of the output; check whether generation is enforced or manual.
38
- - **Universal fix:** regenerate in the same commit as the source change; CI-gate drift (`git diff --exit-code` after generate); never hand-edit generated output.
39
- - **Severity:** HIGH by default; CRITICAL when the artifact governs installs or schema (lockfile, migrations).
40
-
41
- ## 4 · dual-authority
42
-
43
- Two independent writers own the same state: two migration tools targeting one database, a framework that owns its own tables, an app route table and an ingress rule, hand-written DDL and an ORM mirror. Each authority is internally consistent; the state they share is not.
44
-
45
- - **Ask:** who else writes this — and does either writer know the other exists?
46
- - **Failure:** one authority clobbers or drifts from the other; the loser's consumers break with no signal on the winner's side (a schema push drops the auth library's tables; login breaks).
47
- - **Trace:** list every tool/config with write authority over the state (migration runners, CLIs, IaC, admin dashboards) and what each believes the state is.
48
- - **Universal fix:** designate one canonical authority and make the others read-only mirrors reconciled from it — or byte-align them and CI-check the parity.
49
- - **Severity:** CRITICAL when the shared state is persisted data or auth; HIGH otherwise.
50
-
51
- ## 5 · config-elsewhere
52
-
53
- A value is declared in one place and consumed in another — env vars, platform bindings, secret paths, ports, build-time defines. Reader and provider never reference each other statically, and the provider is often per-environment, so the break can exist in exactly one environment.
54
-
55
- - **Ask:** for each value this code reads, where is it declared — in **every** environment, not just the one you can see?
56
- - **Failure:** `undefined` at request time, boot failure in one env only, a stale build-time literal where a runtime value was expected, a secret baked into a public artifact.
57
- - **Trace:** map each read (`process.env.X`, `env.BINDING`, `import.meta.env.Y`) to its declaration in every env file, deploy manifest, CI secret store, and dashboard.
58
- - **Universal fix:** validate the full key set at boot and fail fast; keep a tracked example/contract file (`.env.example`); change reader and every provider in one commit.
59
- - **Severity:** HIGH by default; CRITICAL when the flow direction is wrong (a secret reaching a client bundle) — that is also a trust-invariant break.
60
-
61
- ## 6 · trust-invariant
62
-
63
- An auth or isolation guarantee that **fails open**: tenancy scoping, RLS policies, cookie flags, CSRF defenses, redirect-URI allowlists, JWT audience checks, public-env prefixes. Unlike the other mechanisms, breaking it doesn't stop the system — everything keeps working, plus an attacker path or a cross-tenant leak.
64
-
65
- - **Ask:** if this check were deleted, would anything visibly fail — or would the system happily serve data it shouldn't?
66
- - **Failure:** silent grant or disclosure. There is no error to observe; tests that only exercise the happy path stay green by construction.
67
- - **Trace:** find every enforcement point of the invariant and every path that bypasses it (new endpoints, new tables, raw queries, `SECURITY DEFINER`-style escape hatches, spoofable inputs like `req.body.tenantId`).
68
- - **Universal fix:** enforce centrally (middleware, policy, schema default) so a new code path inherits the invariant instead of having to remember it; default-deny on any missing input.
69
- - **Severity:** CRITICAL, categorically. Loud auth failures (everyone locked out) cap at HIGH; it's the quiet successes that are CRITICAL.
70
-
71
- ## 7 · lifecycle-protocol
72
-
73
- An ordering or sequencing agreement with a platform or across deploys: expand-then-contract migrations, deploy order between producer and consumer, graceful-shutdown draining, replay determinism, retry/idempotency budgets, timezone/DST assumptions, append-only version tags.
74
-
75
- - **Ask:** does correctness depend on *when* or *in what order* this runs — and who else assumes that order?
76
- - **Failure:** correct code applied in the wrong order: a migration that strands a rolling deploy, a retry that double-charges, a rename that orphans live stateful objects, a cron that fires on the wrong day twice a year.
77
- - **Trace:** reconstruct the sequence the change requires (what must exist before what) and compare against how deploys actually roll out — including crash/retry paths.
78
- - **Universal fix:** make each step independently safe (idempotent handlers, additive-first migrations, append-only version markers); many boundary violations dissolve by **reordering the plan**, not changing what ships.
79
- - **Severity:** CRITICAL when a wrong order corrupts persisted state or duplicates money-adjacent side effects; HIGH otherwise.
80
-
81
- ---
82
-
83
- ## Domain contracts — stack-independent, always checked
84
-
85
- Two contracts come from the product's information architecture, not from any technology, and are checked on every run:
86
-
87
- - **entity-model — CRITICAL.** The entity vocabulary and topology (what the entities are, how they relate) that every module, migration, wire type, and doc agrees on. Re-relating or renaming an entity is a change to *every* mechanism at once. Sourced from `docs/architecture/` (domain model, ADRs), a contracts package, and the schema — never from a single module's view of it. Safe change: map every consumer before re-relating; coordinate schema + wire + client in one planned sequence.
88
- - **tenancy — CRITICAL · trust-invariant.** The invariant that every request carries a non-spoofable tenant identity and all data access is scoped by it, at every layer. Its enforcement points are technology-specific (a JWT claim, a channel-name segment, an RLS policy, a scoped query helper) but the invariant is one contract — a single unscoped path anywhere breaks it.
89
-
90
- ---
91
-
92
- ## Severity recap
93
-
94
- - **CRITICAL** — can corrupt persisted data, or open an auth/money path. Trust-invariants and persisted/replayed shapes live here.
95
- - **HIGH** — silent cross-module breakage that ships green.
96
- - **MEDIUM** — drift-prone coupling (string-matched, duplicated constants) not yet broken.
97
- - **LOW** — smell. Loud, immediately-visible runtime breakage caps at HIGH; statically-guarded edges are out of scope entirely.
98
-
99
- Severity is intrinsic to the finding, not curved: three genuine CRITICALs in a small repo is correct signal, not inflation.