@tenkara/afm-linux-arm64 0.0.0-stage → 4.0.0-alpha.11
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.
- package/bin/afm +4 -0
- package/content/.coupling-baseline.tsv +1 -0
- package/content/.pending-commands.txt +2 -0
- package/content/CHANGELOG-archive.md +806 -0
- package/content/CHANGELOG.md +4330 -0
- package/content/PRINCIPLES.md +119 -0
- package/content/README-light.md +158 -0
- package/content/README.md +150 -0
- package/content/code-style/_any/file-size.md +62 -0
- package/content/code-style/_any/imports.md +68 -0
- package/content/code-style/_any/naming.md +72 -0
- package/content/code-style/_any/test-placement.md +57 -0
- package/content/code-style/nextjs-app-router/imports.md +50 -0
- package/content/code-style/nextjs-app-router/naming.md +50 -0
- package/content/code-style/prisma-postgres/error-boundary.md +55 -0
- package/content/code-style/prisma-postgres/imports.md +61 -0
- package/content/code-style/shadcn-ui/naming.md +48 -0
- package/content/code-style/stripe-billing/naming.md +76 -0
- package/content/code-style/trigger-dev-v4/imports.md +58 -0
- package/content/code-style/trigger-dev-v4/naming.md +49 -0
- package/content/code-style/trigger-dev-v4/test-placement.md +61 -0
- package/content/code-style/trpc-v11/error-boundary.md +51 -0
- package/content/code-style/trpc-v11/file-size.md +44 -0
- package/content/code-style/trpc-v11/naming.md +60 -0
- package/content/code-style/trpc-v11/test-placement.md +52 -0
- package/content/core/ach.md +260 -0
- package/content/core/afm.md +514 -0
- package/content/core/gotchas.md +18 -0
- package/content/core/prd.md +141 -0
- package/content/core/protocol.md +158 -0
- package/content/core/ust.md +65 -0
- package/content/guardrails/README.md +55 -0
- package/content/guardrails/dsl-com-ordem-obrigatoria.md +47 -0
- package/content/guardrails/exaustividade-de-union.md +48 -0
- package/content/guardrails/fronteira-de-import.md +57 -0
- package/content/hooks/README.md +70 -0
- package/content/interview/bootstrap.md +417 -0
- package/content/interview/promotion.md +115 -0
- package/content/interview/refactor.md +379 -0
- package/content/interview/triggers.md +107 -0
- package/content/modules/nextjs-app-router/ach.patch.md +44 -0
- package/content/modules/nextjs-app-router/gotchas.seed.md +78 -0
- package/content/modules/nextjs-app-router/rules.patch.md +35 -0
- package/content/modules/prisma-postgres/ach.patch.md +65 -0
- package/content/modules/prisma-postgres/rules.patch.md +28 -0
- package/content/modules/shadcn-ui/ach.patch.md +42 -0
- package/content/modules/stripe-billing/ach.patch.md +46 -0
- package/content/modules/stripe-billing/gotchas.seed.md +73 -0
- package/content/modules/stripe-billing/rules.patch.md +30 -0
- package/content/modules/trigger-dev-v4/ach.patch.md +64 -0
- package/content/modules/trigger-dev-v4/gotchas.seed.md +53 -0
- package/content/modules/trigger-dev-v4/rules.patch.md +35 -0
- package/content/modules/trpc-v11/ach.patch.md +56 -0
- package/content/modules/trpc-v11/rules.patch.md +34 -0
- package/content/modules/vireya-ui/ach.patch.md +85 -0
- package/content/modules/vireya-ui/gotchas.seed.md +98 -0
- package/content/modules/vireya-ui/rules.patch.md +36 -0
- package/content/ops/adr.md +54 -0
- package/content/ops/analyze.md +130 -0
- package/content/ops/clarify.md +86 -0
- package/content/ops/deliver.md +209 -0
- package/content/ops/diagnose.md +143 -0
- package/content/ops/evolve.md +366 -0
- package/content/ops/export-template.md +97 -0
- package/content/ops/generalize.md +117 -0
- package/content/ops/gotcha.md +46 -0
- package/content/ops/obligations.md +145 -0
- package/content/ops/plan.md +61 -0
- package/content/ops/policy.md +61 -0
- package/content/ops/promote.md +72 -0
- package/content/ops/protocol.md +51 -0
- package/content/ops/reconcile.md +44 -0
- package/content/ops/reflect.md +182 -0
- package/content/ops/remediate.md +143 -0
- package/content/ops/restructure.md +142 -0
- package/content/ops/rule.md +170 -0
- package/content/ops/specify.md +62 -0
- package/content/ops/tasks.md +67 -0
- package/content/ops/update.md +252 -0
- package/content/ops/verify.md +67 -0
- package/content/patterns/adapter.md +46 -0
- package/content/patterns/facade.md +47 -0
- package/content/patterns/factory.md +46 -0
- package/content/patterns/null-object.md +55 -0
- package/content/patterns/result-vs-exception.md +52 -0
- package/content/patterns/strategy.md +45 -0
- package/content/patterns/template-method-vs-composition.md +50 -0
- package/content/questions/adr.questions.yaml +14 -0
- package/content/questions/analyze.questions.yaml +12 -0
- package/content/questions/bootstrap-mapping.questions.yaml +14 -0
- package/content/questions/bootstrap-rules.questions.yaml +10 -0
- package/content/questions/bootstrap-stack.questions.yaml +23 -0
- package/content/questions/clarify.questions.yaml +10 -0
- package/content/questions/code-style.questions.yaml +12 -0
- package/content/questions/confirm.questions.yaml +12 -0
- package/content/questions/deliver.questions.yaml +23 -0
- package/content/questions/evolve.questions.yaml +15 -0
- package/content/questions/export-template-apply.questions.yaml +11 -0
- package/content/questions/export-template.questions.yaml +23 -0
- package/content/questions/gates.questions.yaml +27 -0
- package/content/questions/generalize.questions.yaml +13 -0
- package/content/questions/gotcha.questions.yaml +11 -0
- package/content/questions/init-template.questions.yaml +10 -0
- package/content/questions/init.questions.yaml +41 -0
- package/content/questions/obligations.questions.yaml +17 -0
- package/content/questions/plan.questions.yaml +14 -0
- package/content/questions/refactor.questions.yaml +23 -0
- package/content/questions/reflect.questions.yaml +13 -0
- package/content/questions/remediate.questions.yaml +27 -0
- package/content/questions/restructure.questions.yaml +15 -0
- package/content/questions/rf-mapping.questions.yaml +12 -0
- package/content/questions/rule-scope.questions.yaml +12 -0
- package/content/questions/rule.questions.yaml +12 -0
- package/content/questions/specify.questions.yaml +23 -0
- package/content/questions/update-artifacts.questions.yaml +10 -0
- package/content/questions/update-strings.questions.yaml +14 -0
- package/content/questions/update.questions.yaml +10 -0
- package/content/questions/verify.questions.yaml +9 -0
- package/content/roles/executor.md +18 -0
- package/content/roles/explorer.md +22 -0
- package/content/roles/reviewer.md +23 -0
- package/content/rubrics/enum-vs-union-vs-branded.md +40 -0
- package/content/rubrics/episodic-vs-semantic-boundary.md +37 -0
- package/content/rubrics/error-classification.md +98 -0
- package/content/rubrics/failure-classification.md +111 -0
- package/content/rubrics/negative-filters.md +38 -0
- package/content/rubrics/solid-triggers.md +119 -0
- package/content/rubrics/template-vs-streaming-precedence.md +34 -0
- package/content/rubrics/validation-boundary.md +94 -0
- package/content/rubrics/when-to-create-dsl.md +65 -0
- package/content/rubrics/when-to-create-lib.md +49 -0
- package/content/rubrics/when-to-create-module.md +39 -0
- package/content/rubrics/when-to-evolve-methodology.md +62 -0
- package/content/skills/adr.md +42 -0
- package/content/skills/analyze.md +28 -0
- package/content/skills/clarify.md +28 -0
- package/content/skills/deliver.md +26 -0
- package/content/skills/diagnose.md +42 -0
- package/content/skills/evolve.md +43 -0
- package/content/skills/export-template.md +37 -0
- package/content/skills/generalize.md +31 -0
- package/content/skills/gotcha.md +40 -0
- package/content/skills/init.md +77 -0
- package/content/skills/obligations.md +27 -0
- package/content/skills/plan.md +28 -0
- package/content/skills/policy.md +25 -0
- package/content/skills/promote.md +39 -0
- package/content/skills/protocol.md +41 -0
- package/content/skills/reconcile.md +45 -0
- package/content/skills/refactor.md +79 -0
- package/content/skills/reflect.md +43 -0
- package/content/skills/remediate.md +33 -0
- package/content/skills/restructure.md +46 -0
- package/content/skills/rule.md +43 -0
- package/content/skills/specify.md +28 -0
- package/content/skills/tasks.md +24 -0
- package/content/skills/update.md +58 -0
- package/content/skills/verify.md +29 -0
- package/content/templates/adr.template.md +51 -0
- package/content/templates/evolution-report.template.md +132 -0
- package/content/templates/feature-plan.template.md +84 -0
- package/content/templates/feature-research.template.md +56 -0
- package/content/templates/feature-spec.template.md +86 -0
- package/content/templates/feature-tasks.induced.template.md +91 -0
- package/content/templates/feature-tasks.template.md +82 -0
- package/content/templates/focus.template.md +30 -0
- package/content/templates/gotcha.template.md +31 -0
- package/content/templates/guardrail-runner.template.md +97 -0
- package/content/templates/guardrail.template.md +63 -0
- package/content/templates/learning.template.md +40 -0
- package/content/templates/procedure.template.md +47 -0
- package/content/templates/protocols/p1.md +29 -0
- package/content/templates/protocols/p2.md +28 -0
- package/content/templates/protocols/p3.md +47 -0
- package/content/templates/research.template.md +50 -0
- package/content/templates/session-handoff.template.md +58 -0
- package/content/templates/us.template.md +39 -0
- package/content/tools/gate/core-rules/PLUGIN-13-secrets.sh +36 -0
- package/content/tools/gate/core-rules/PLUGIN-21-code-language.sh +27 -0
- package/content/tools/gate/core-rules/PLUGIN-35-gate-complete.sh +19 -0
- package/content/tools/gate/core-rules/PLUGIN-36-absolute-path.sh +38 -0
- package/content/tools/gate/core-rules/PLUGIN-37-prose-language.sh +56 -0
- package/content/tools/gate/core-rules/PLUGIN-38-parser-marker.sh +82 -0
- package/content/tools/gate/core-rules/PLUGIN-39-frontmatter-ops.sh +325 -0
- package/content/tools/gate/core-rules/PLUGIN-40-dead-reference.sh +74 -0
- package/content/tools/gate/core-rules/README.md +55 -0
- package/content/tools/gate/guardrails.sh +740 -0
- package/content/tools/gate/lib/ratchet.sh +119 -0
- package/content/tools/gate/lib/scope.sh +85 -0
- package/content/tools/gate/ratchet.seed.tsv +1 -0
- package/content/tools/gate/rules/README.md +50 -0
- package/package.json +19 -5
- package/plugin/agy/hooks.json +29 -0
- package/plugin/agy/mcp_config.json +7 -0
- package/plugin/agy/plugin.json +6 -0
- package/plugin/agy/skills/afm-adr/SKILL.md +48 -0
- package/plugin/agy/skills/afm-analyze/SKILL.md +34 -0
- package/plugin/agy/skills/afm-clarify/SKILL.md +34 -0
- package/plugin/agy/skills/afm-deliver/SKILL.md +30 -0
- package/plugin/agy/skills/afm-diagnose/SKILL.md +46 -0
- package/plugin/agy/skills/afm-evolve/SKILL.md +49 -0
- package/plugin/agy/skills/afm-export-template/SKILL.md +43 -0
- package/plugin/agy/skills/afm-generalize/SKILL.md +37 -0
- package/plugin/agy/skills/afm-gotcha/SKILL.md +46 -0
- package/plugin/agy/skills/afm-init/SKILL.md +81 -0
- package/plugin/agy/skills/afm-obligations/SKILL.md +31 -0
- package/plugin/agy/skills/afm-plan/SKILL.md +34 -0
- package/plugin/agy/skills/afm-policy/SKILL.md +29 -0
- package/plugin/agy/skills/afm-promote/SKILL.md +43 -0
- package/plugin/agy/skills/afm-protocol/SKILL.md +45 -0
- package/plugin/agy/skills/afm-reconcile/SKILL.md +49 -0
- package/plugin/agy/skills/afm-refactor/SKILL.md +83 -0
- package/plugin/agy/skills/afm-reflect/SKILL.md +49 -0
- package/plugin/agy/skills/afm-remediate/SKILL.md +39 -0
- package/plugin/agy/skills/afm-restructure/SKILL.md +52 -0
- package/plugin/agy/skills/afm-rule/SKILL.md +49 -0
- package/plugin/agy/skills/afm-specify/SKILL.md +34 -0
- package/plugin/agy/skills/afm-tasks/SKILL.md +28 -0
- package/plugin/agy/skills/afm-update/SKILL.md +64 -0
- package/plugin/agy/skills/afm-verify/SKILL.md +35 -0
- package/plugin/claude-code/.claude-plugin/marketplace.json +15 -0
- package/plugin/claude-code/.claude-plugin/plugin.json +9 -0
- package/plugin/claude-code/.mcp.json +7 -0
- package/plugin/claude-code/agents/afm-executor.md +18 -0
- package/plugin/claude-code/agents/afm-explorer.md +20 -0
- package/plugin/claude-code/agents/afm-reviewer.md +21 -0
- package/plugin/claude-code/hooks/hooks.json +85 -0
- package/plugin/claude-code/skills/adr/SKILL.md +48 -0
- package/plugin/claude-code/skills/analyze/SKILL.md +34 -0
- package/plugin/claude-code/skills/clarify/SKILL.md +34 -0
- package/plugin/claude-code/skills/deliver/SKILL.md +30 -0
- package/plugin/claude-code/skills/diagnose/SKILL.md +46 -0
- package/plugin/claude-code/skills/evolve/SKILL.md +49 -0
- package/plugin/claude-code/skills/export-template/SKILL.md +43 -0
- package/plugin/claude-code/skills/generalize/SKILL.md +37 -0
- package/plugin/claude-code/skills/gotcha/SKILL.md +46 -0
- package/plugin/claude-code/skills/init/SKILL.md +81 -0
- package/plugin/claude-code/skills/obligations/SKILL.md +31 -0
- package/plugin/claude-code/skills/plan/SKILL.md +34 -0
- package/plugin/claude-code/skills/policy/SKILL.md +29 -0
- package/plugin/claude-code/skills/promote/SKILL.md +43 -0
- package/plugin/claude-code/skills/protocol/SKILL.md +45 -0
- package/plugin/claude-code/skills/reconcile/SKILL.md +49 -0
- package/plugin/claude-code/skills/refactor/SKILL.md +83 -0
- package/plugin/claude-code/skills/reflect/SKILL.md +49 -0
- package/plugin/claude-code/skills/remediate/SKILL.md +39 -0
- package/plugin/claude-code/skills/restructure/SKILL.md +52 -0
- package/plugin/claude-code/skills/rule/SKILL.md +49 -0
- package/plugin/claude-code/skills/specify/SKILL.md +34 -0
- package/plugin/claude-code/skills/tasks/SKILL.md +28 -0
- package/plugin/claude-code/skills/update/SKILL.md +64 -0
- package/plugin/claude-code/skills/verify/SKILL.md +35 -0
- package/plugin/opencode/afm/index.js +245 -0
- package/plugin/opencode/afm/tui.js +21 -0
- package/plugin/opencode/agents/afm-executor.md +39 -0
- package/plugin/opencode/agents/afm-explorer.md +92 -0
- package/plugin/opencode/agents/afm-reviewer.md +93 -0
- package/plugin/opencode/skills/afm-adr/SKILL.md +48 -0
- package/plugin/opencode/skills/afm-analyze/SKILL.md +34 -0
- package/plugin/opencode/skills/afm-clarify/SKILL.md +34 -0
- package/plugin/opencode/skills/afm-deliver/SKILL.md +30 -0
- package/plugin/opencode/skills/afm-diagnose/SKILL.md +46 -0
- package/plugin/opencode/skills/afm-evolve/SKILL.md +49 -0
- package/plugin/opencode/skills/afm-export-template/SKILL.md +43 -0
- package/plugin/opencode/skills/afm-generalize/SKILL.md +37 -0
- package/plugin/opencode/skills/afm-gotcha/SKILL.md +46 -0
- package/plugin/opencode/skills/afm-init/SKILL.md +81 -0
- package/plugin/opencode/skills/afm-obligations/SKILL.md +31 -0
- package/plugin/opencode/skills/afm-plan/SKILL.md +34 -0
- package/plugin/opencode/skills/afm-policy/SKILL.md +29 -0
- package/plugin/opencode/skills/afm-promote/SKILL.md +43 -0
- package/plugin/opencode/skills/afm-protocol/SKILL.md +45 -0
- package/plugin/opencode/skills/afm-reconcile/SKILL.md +49 -0
- package/plugin/opencode/skills/afm-refactor/SKILL.md +83 -0
- package/plugin/opencode/skills/afm-reflect/SKILL.md +49 -0
- package/plugin/opencode/skills/afm-remediate/SKILL.md +39 -0
- package/plugin/opencode/skills/afm-restructure/SKILL.md +52 -0
- package/plugin/opencode/skills/afm-rule/SKILL.md +49 -0
- package/plugin/opencode/skills/afm-specify/SKILL.md +34 -0
- package/plugin/opencode/skills/afm-tasks/SKILL.md +28 -0
- package/plugin/opencode/skills/afm-update/SKILL.md +64 -0
- package/plugin/opencode/skills/afm-verify/SKILL.md +35 -0
- package/README.md +0 -3
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: module
|
|
3
|
+
description: 'Hard rules for App Router projects — Server Component by default, every "use client" carries a stated reason, and <html lang> renders in the localized segment. The route-handler-thinness rule this module used to state as a fixed 80-line trigger is an opt-in seed now.'
|
|
4
|
+
load: cold
|
|
5
|
+
stack: nextjs-app-router
|
|
6
|
+
patches: 'docs/rules/*.md'
|
|
7
|
+
detect: |
|
|
8
|
+
if command -v jq >/dev/null 2>&1; then jq -e '(.dependencies//{})+(.devDependencies//{})+(.peerDependencies//{})|has("next")' package.json >/dev/null 2>&1; else grep -q '"next"' package.json 2>/dev/null; fi
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Patch — Hard rules for Next.js App Router projects
|
|
12
|
+
|
|
13
|
+
> Insert after the last existing hard rule in `/docs/afm.md` §3.
|
|
14
|
+
|
|
15
|
+
- **Server Component is the default.** Use `"use client"` only where the code needs real interactivity (state, DOM events, browser hooks). Each `"use client"` needs a clear reason.
|
|
16
|
+
*Verification:* `grep -rn '"use client"' src/app app 2>/dev/null` — each match has a clear reason in the diff.
|
|
17
|
+
|
|
18
|
+
> This module used to add a hard rule here that route handlers stay thin, verified by a fixed
|
|
19
|
+
> "all files under 80 lines" check — an arbitrary constant with no measurement behind it and no
|
|
20
|
+
> placeholder. The qualitative convention ("a route handler delegates to `server/`, it does not
|
|
21
|
+
> embed business logic") is not a Next.js fact either; it now lives as an opt-in seed at
|
|
22
|
+
> `code-style/nextjs-app-router/imports.md` (concern: imports). A project that wants a numeric
|
|
23
|
+
> line-count backstop should adopt `code-style/_any/file-size.md` instead, with a threshold it
|
|
24
|
+
> measured against its own files — not this module's unmeasured 80.
|
|
25
|
+
|
|
26
|
+
- **A page Server Component does not call raw `fetch()` when a typed client exists.** Use the stack's server-side client instead. For example, use a tRPC server caller to keep the types.
|
|
27
|
+
*Verification:* `grep -rn "fetch(" --include='page.tsx' src/app app 2>/dev/null`.
|
|
28
|
+
- **The system renders `<html lang>` in the `[lang]` segment, never with a fixed default in the root layout**. Localized routes (`app/[lang]/...`) need the correct locale in `<html lang>` at SSR/SSG time. Crawlers and screen readers read this value before hydration. A fixed root render (`<html lang="pt-BR">`) plus a client-side fix serves the wrong `lang` on `/en/*`. The client-side fix is a `useEffect` that sets `document.documentElement.lang`.
|
|
29
|
+
|
|
30
|
+
Next.js supports a root layout under a dynamic segment. **Delete `app/layout.tsx`.** Make `app/[lang]/layout.tsx` render `<html lang={...}>`. Take `lang` from `params.lang` (SSG-safe, no `headers()` call).
|
|
31
|
+
|
|
32
|
+
This has a required consequence: without a root layout, the `_not-found` route and non-locale paths become orphans. Add `app/global-not-found.tsx` (its own HTML document). Set `experimental.globalNotFound: true` in the Next.js config.
|
|
33
|
+
|
|
34
|
+
Move the styled `not-found.tsx` to `app/[lang]/not-found.tsx`.
|
|
35
|
+
*Verification:* `grep -rn 'html lang' src/app app 2>/dev/null` — the only dynamic-lang match is in `app/[lang]/layout.tsx` (the fixed default is allowed only in `app/global-not-found.tsx`). `grep -rn 'documentElement.lang' src/app app 2>/dev/null` returns empty (no client-side hack). `ls src/app/layout.tsx app/layout.tsx 2>/dev/null` shows the file does not exist.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: module
|
|
3
|
+
description: 'Adds the always-run-generate rule and the optimistic-locking pattern to the materialized ACH. Schema layout, generated-client path, and soft-delete are opt-in seeds — see the pointers below.'
|
|
4
|
+
load: cold
|
|
5
|
+
stack: prisma-postgres
|
|
6
|
+
patches: 'docs/ach.md'
|
|
7
|
+
detect: |
|
|
8
|
+
test -f prisma/schema.prisma
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Patch — ACH for Prisma + Postgres projects
|
|
12
|
+
|
|
13
|
+
> Apply this to `/docs/ach.md`. It adds the generate-after-edit rule and the multi-actor locking
|
|
14
|
+
> pattern.
|
|
15
|
+
|
|
16
|
+
## §2 — Add to the tree
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
prisma/
|
|
20
|
+
schema.prisma # config + datasource
|
|
21
|
+
seed.ts # seed script (also wired as the Prisma seed cmd)
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
> Prisma 5.15+ also supports splitting the schema across several files, merged by a
|
|
25
|
+
> `prisma.config.ts`, and the generated client's output path is configurable per project. Where
|
|
26
|
+
> those files live and where the client lands — and the three layer-direction import rules this
|
|
27
|
+
> patch used to state as fact alongside them — are house convention, not a Prisma requirement.
|
|
28
|
+
> They now live as an opt-in seed at `code-style/prisma-postgres/imports.md` (concern: imports).
|
|
29
|
+
> Materialize it only if the operator accepts it.
|
|
30
|
+
|
|
31
|
+
## §6 — Add schema conventions
|
|
32
|
+
|
|
33
|
+
```md
|
|
34
|
+
### Schema conventions
|
|
35
|
+
|
|
36
|
+
- **Always run `npx prisma generate`** after you (a) clone the repo, (b) edit any schema file, or
|
|
37
|
+
(c) update the Prisma version. Without this step, the generated-client imports do not resolve.
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
> The soft-delete convention this patch used to state as fact — `deletedAt`, a hard-delete SLA
|
|
41
|
+
> comment, the default query filter, and a scheduled cleanup job — is a design choice, not a
|
|
42
|
+
> Prisma fact: Prisma has no opinion on whether a row ever leaves the table. It now lives as an
|
|
43
|
+
> opt-in seed at `code-style/prisma-postgres/error-boundary.md` (concern: error-boundary).
|
|
44
|
+
> Materialize it only if the project actually needs soft delete.
|
|
45
|
+
|
|
46
|
+
## §6 — Multi-actor concurrency
|
|
47
|
+
|
|
48
|
+
```md
|
|
49
|
+
### Optimistic locking for multi-actor entities
|
|
50
|
+
|
|
51
|
+
An entity that more than one actor can edit concurrently (example: a record with a status or
|
|
52
|
+
version field that a background job and a user action can both update) needs a **conditional
|
|
53
|
+
update**, not a plain `update`:
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
const result = await db.record.updateMany({
|
|
57
|
+
where: { id, version: expectedVersion },
|
|
58
|
+
data: { version: nextVersion, ...changes },
|
|
59
|
+
});
|
|
60
|
+
if (result.count === 0) throw new DomainError({ code: "STALE_WRITE" });
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Without this pattern, the last write wins silently. For a side-effect-once endpoint (anything that
|
|
64
|
+
should not re-run if a stale client retries), use the same pattern and add a race-condition test.
|
|
65
|
+
```
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: module
|
|
3
|
+
description: 'Principle for Prisma and Postgres projects: migrations must stay forward-compatible for a rolling deploy. Ships no hard rule of its own — the two this module used to state are opt-in seeds now.'
|
|
4
|
+
load: cold
|
|
5
|
+
stack: prisma-postgres
|
|
6
|
+
patches: 'docs/rules/*.md'
|
|
7
|
+
detect: |
|
|
8
|
+
test -f prisma/schema.prisma
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Patch — Hard rules for Prisma + Postgres projects
|
|
12
|
+
|
|
13
|
+
> Hard rules (with an executable trigger) go after the last hard rule in `/docs/afm.md` §3.
|
|
14
|
+
> Principles (verification by review only) go in `/docs/afm.md` §1.3.
|
|
15
|
+
|
|
16
|
+
## Hard rules (executable trigger)
|
|
17
|
+
|
|
18
|
+
None. The two rules this module used to state as fact here — a domain function must receive `db`
|
|
19
|
+
through `ctx` instead of importing `PrismaClient` directly, and every query against a
|
|
20
|
+
soft-deletable model must filter `deletedAt: null` — are house conventions, not Prisma facts:
|
|
21
|
+
Prisma has no opinion on dependency injection or on whether a row is ever really deleted. Both are
|
|
22
|
+
now **opt-in seeds**, proposed rather than imposed, at `code-style/prisma-postgres/imports.md`
|
|
23
|
+
(concern: imports) and `code-style/prisma-postgres/error-boundary.md` (concern: error-boundary).
|
|
24
|
+
Materialize either only if the operator accepts it.
|
|
25
|
+
|
|
26
|
+
## Principles (insert in §1.3 — review, not a trigger)
|
|
27
|
+
|
|
28
|
+
- **Migrations must stay forward-compatible**. A destructive column drop or rename needs a dual-read phase. Without it, a rolling deploy across N nodes creates an error window. *(No grep can prove rolling-deploy safety — this is a review checklist: "is this migration safe for a rolling deploy?")*
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: module
|
|
3
|
+
description: 'Adds shadcn-style UI primitive conventions, the Tailwind layer, and the component boundary to the materialized ACH.'
|
|
4
|
+
load: cold
|
|
5
|
+
stack: shadcn-ui
|
|
6
|
+
patches: 'docs/ach.md'
|
|
7
|
+
detect: |
|
|
8
|
+
test -f components.json
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Patch — ACH for projects with shadcn-style UI primitives
|
|
12
|
+
|
|
13
|
+
> Apply this to `/docs/ach.md`. It adds conventions for components, Tailwind, and primitives.
|
|
14
|
+
|
|
15
|
+
## §3.2 — UI primitives
|
|
16
|
+
|
|
17
|
+
```md
|
|
18
|
+
#### UI primitives
|
|
19
|
+
|
|
20
|
+
- **What it is:** base components in the shadcn style (Button, Input, Dialog, Sheet, ...). The CLI copies them in through `components.json`. You then edit them in place in the project.
|
|
21
|
+
- **Responsibility:** pure primitives. No domain logic, no fetch, no global state.
|
|
22
|
+
- **Stack:** Tailwind v4 (`@tailwindcss/postcss`), `tw-animate-css`, Radix UI underneath.
|
|
23
|
+
- **Modification:** primitives are copied, not a dependency. Edits stay permanent. Use `npx shadcn add` for new components, then treat them as your own code.
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
> Where primitives and feature-composed components live (`src/components/ui/*` versus
|
|
27
|
+
> `src/components/<feature>/`) is house convention, not a shadcn fact — shadcn only prescribes
|
|
28
|
+
> where the CLI copies primitives *within* whatever `components.json` points at. That layout now
|
|
29
|
+
> lives as an **opt-in seed** at `code-style/shadcn-ui/naming.md`. Materialize it only if the
|
|
30
|
+
> operator accepts it. How a feature component fetches its data (tRPC, REST, GraphQL, a plain
|
|
31
|
+
> `fetch`) is a different stack's decision entirely — this module does not make it.
|
|
32
|
+
|
|
33
|
+
## §6 — Conventions
|
|
34
|
+
|
|
35
|
+
```md
|
|
36
|
+
### shadcn / Tailwind
|
|
37
|
+
|
|
38
|
+
- `components.json` defines paths and config. Do not edit it by hand. Use the CLI.
|
|
39
|
+
- Use `class-variance-authority` (cva) for variants, typed for autocomplete.
|
|
40
|
+
- Use the `cn()` helper (`clsx` + `tailwind-merge`) as the standard way to compose classes.
|
|
41
|
+
- Components export `Component` plus a `ComponentProps` type. This makes extension easier.
|
|
42
|
+
```
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: module
|
|
3
|
+
description: 'Adds the canonical webhook and the Stripe Subscription Schedule conventions for billing to the materialized ACH.'
|
|
4
|
+
load: cold
|
|
5
|
+
stack: stripe-billing
|
|
6
|
+
patches: 'docs/ach.md'
|
|
7
|
+
detect: |
|
|
8
|
+
if command -v jq >/dev/null 2>&1; then jq -e '(.dependencies//{})+(.devDependencies//{})+(.peerDependencies//{})|has("stripe")' package.json >/dev/null 2>&1; else grep -q '"stripe"' package.json 2>/dev/null; fi
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Patch — ACH for projects with Stripe billing
|
|
12
|
+
|
|
13
|
+
> Applies to `/docs/ach.md`. Adds conventions for the canonical webhook and Stripe Subscription
|
|
14
|
+
> Schedule handling. A file layout for the billing domain, an SDK-wrapper naming scheme, and a
|
|
15
|
+
> server-side gating accessor shape are proposed separately, as an opt-in code-style seed — see
|
|
16
|
+
> `code-style/stripe-billing/naming.md` — because none of the three is a fact about Stripe.
|
|
17
|
+
|
|
18
|
+
## §6 — Add Billing conventions
|
|
19
|
+
|
|
20
|
+
```md
|
|
21
|
+
### Billing (webhook-driven state)
|
|
22
|
+
|
|
23
|
+
### Hard rules for billing
|
|
24
|
+
|
|
25
|
+
- **The webhook is the canonical source** for subscription state, but a mutation procedure that
|
|
26
|
+
changes a subscription (a plan change, a cancellation, a checkout self-heal) also writes the
|
|
27
|
+
new state synchronously. That write is an idempotent upsert, so a webhook that arrives late
|
|
28
|
+
converges to the same state instead of creating a duplicate or a conflict.
|
|
29
|
+
|
|
30
|
+
### Stripe Subscription Schedules
|
|
31
|
+
|
|
32
|
+
A multi-plan subscription flow uses four Stripe operations:
|
|
33
|
+
|
|
34
|
+
- An immediate Price swap with proration, for an upgrade that should take effect now.
|
|
35
|
+
- Creating or updating a `SubscriptionSchedule` for a Price change that should take effect at the
|
|
36
|
+
end of the current billing cycle, for a downgrade.
|
|
37
|
+
- Releasing an attached `SubscriptionSchedule`. The release call is a no-op, not an error, when
|
|
38
|
+
the subscription carries no schedule, so it is safe to call unconditionally before a mutation.
|
|
39
|
+
- Setting `cancel_at_period_end: true` on the subscription, for a cancellation that takes effect
|
|
40
|
+
at cycle end rather than immediately.
|
|
41
|
+
|
|
42
|
+
**Critical gotcha:** a subscription with an attached `SubscriptionSchedule` rejects any direct
|
|
43
|
+
mutation through the Subscriptions API (`subscription is managed by subscription schedule`).
|
|
44
|
+
Release the schedule before you mutate a subscription that may be carrying a pending downgrade.
|
|
45
|
+
See the gotcha for the exact error and remedy, and ADR-0002 (if one exists).
|
|
46
|
+
```
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: module
|
|
3
|
+
description: 'Seed gotchas for Stripe billing, starting with a subscription that carries a SubscriptionSchedule being immutable.'
|
|
4
|
+
load: cold
|
|
5
|
+
stack: stripe-billing
|
|
6
|
+
patches: 'docs/gotchas/*.md'
|
|
7
|
+
detect: |
|
|
8
|
+
if command -v jq >/dev/null 2>&1; then jq -e '(.dependencies//{})+(.devDependencies//{})+(.peerDependencies//{})|has("stripe")' package.json >/dev/null 2>&1; else grep -q '"stripe"' package.json 2>/dev/null; fi
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Gotchas seed — Stripe billing
|
|
12
|
+
|
|
13
|
+
> Each entry below is a complete gotcha file. Copy one to `docs/gotchas/NNN-slug.md` (the first `---` of the entry as line 1) only when it bites in real use. Re-date `last_bite` on copy.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
type: gotcha
|
|
17
|
+
description: 'A subscription attached to a SubscriptionSchedule rejects every direct update, so release the schedule before you mutate it.'
|
|
18
|
+
load: warm
|
|
19
|
+
fires_on: event
|
|
20
|
+
fires_when: 'you edit a procedure that mutates a subscription (changePlan, cancelSubscription, createCheckout self-heal).'
|
|
21
|
+
bites: 1
|
|
22
|
+
last_bite: 2026-10
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Stripe — a sub with SubscriptionSchedule is immutable
|
|
26
|
+
|
|
27
|
+
## Behavior
|
|
28
|
+
|
|
29
|
+
A sub attached to a `SubscriptionSchedule` returns `subscription is managed by subscription schedule` on any direct `subscription.update`. This blocks immediate upgrade and cancel when the user has a pending downgrade.
|
|
30
|
+
|
|
31
|
+
## Solution
|
|
32
|
+
|
|
33
|
+
Call `stripe_releaseSubscriptionSchedule(subscriptionId)` before you mutate. The helper returns `null` when there is no schedule, so it is safe to call unconditionally. `changePlan({ planKey: currentPlan })` releases the schedule and clears `pendingPlan` (the "undo pending" pattern).
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
type: gotcha
|
|
37
|
+
description: 'Changing a Price lookupKey in Stripe forces a new Product and unanchors the existing subscription base.'
|
|
38
|
+
load: warm
|
|
39
|
+
fires_on: event
|
|
40
|
+
fires_when: 'you rename a plan, change a price, or edit planConfig.ts.'
|
|
41
|
+
bites: 1
|
|
42
|
+
last_bite: 2026-10
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Stripe — a Price `lookupKey` is immutable after publish
|
|
46
|
+
|
|
47
|
+
## Behavior
|
|
48
|
+
|
|
49
|
+
Changing a Price's `lookupKey` in Stripe forces creation of a **new Product**. Existing subs stay anchored to the old Product, so this unanchors the whole base. A `productName` rename in code is cosmetic only.
|
|
50
|
+
|
|
51
|
+
## Solution
|
|
52
|
+
|
|
53
|
+
Stripe requires a manual rename in the dashboard. Or use a new lookupKey only for **new** subs. Keep the old lookupKey valid for the current base.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
type: gotcha
|
|
57
|
+
description: 'Stripe delivers webhook events at least once, so a handler with a direct INSERT creates duplicates.'
|
|
58
|
+
load: warm
|
|
59
|
+
fires_on: event
|
|
60
|
+
fires_when: 'you implement a webhook handler, for example for customer.subscription.updated.'
|
|
61
|
+
bites: 1
|
|
62
|
+
last_bite: 2026-10
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Stripe — `customer.subscription.updated` can arrive more than once
|
|
66
|
+
|
|
67
|
+
## Behavior
|
|
68
|
+
|
|
69
|
+
Stripe can deliver the same event more than once (at-least-once delivery). A handler that runs a direct INSERT creates a duplicate.
|
|
70
|
+
|
|
71
|
+
## Solution
|
|
72
|
+
|
|
73
|
+
Use an idempotent UPSERT keyed on the unique `stripeSubscriptionId`. Detect a plan transition (`existing.plan !== incoming.plan`) and act only on the real transition.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: module
|
|
3
|
+
description: 'Hard rules for Stripe billing, split between executable triggers and review-only principles.'
|
|
4
|
+
load: cold
|
|
5
|
+
stack: stripe-billing
|
|
6
|
+
patches: 'docs/rules/*.md'
|
|
7
|
+
detect: |
|
|
8
|
+
if command -v jq >/dev/null 2>&1; then jq -e '(.dependencies//{})+(.devDependencies//{})+(.peerDependencies//{})|has("stripe")' package.json >/dev/null 2>&1; else grep -q '"stripe"' package.json 2>/dev/null; fi
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Patch — Hard rules for Stripe billing
|
|
12
|
+
|
|
13
|
+
> Hard rules (with an executable trigger) go in after the last hard rule in `/docs/afm.md` §3.
|
|
14
|
+
> Principles (verification by review only) go in `/docs/afm.md` §1.3.
|
|
15
|
+
|
|
16
|
+
## Hard rules (executable trigger)
|
|
17
|
+
|
|
18
|
+
- **Card data NEVER touches the backend.** Checkout and Portal run hosted on Stripe.
|
|
19
|
+
*Verification:* `grep -rnE "card.*number|cvc|cvv" {{src-path}}/` returns 0.
|
|
20
|
+
|
|
21
|
+
> A server-side gate on every billing-restricted procedure is a genuine requirement — a
|
|
22
|
+
> frontend-only check is UX, not a boundary — but the exact accessor shape and its grep
|
|
23
|
+
> verification are one codebase's convention, not a Stripe fact. See
|
|
24
|
+
> `code-style/stripe-billing/naming.md` for that as an opt-in proposal.
|
|
25
|
+
|
|
26
|
+
## Principles (insert in §1.3 — review, not a trigger)
|
|
27
|
+
|
|
28
|
+
- **A subscription-mutation procedure writes to the DB synchronously. It does not rely only on the webhook.** The webhook is canonical. The procedure is convergent. It uses upsert for idempotency. *(Review: each procedure that changes a subscription — a plan change, a cancellation — upserts the Subscription record after the Stripe call.)*
|
|
29
|
+
- **Before it mutates a subscription, the procedure checks whether a `SubscriptionSchedule` is attached and releases it first.** Stripe rejects a direct mutation on a subscription that has a schedule. *(Review: ask "can this procedure run on a subscription that carries a pending downgrade?".)*
|
|
30
|
+
- **The webhook verifies the signature BEFORE any lookup.** Without a valid signature, it does not touch the DB. *(Review: the Stripe SDK's signature-verification call is the first line of the route handler, before any DB read.)*
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: module
|
|
3
|
+
description: 'Materializes the Task-like component type with Trigger.dev v4 vocabulary in the ACH.'
|
|
4
|
+
load: cold
|
|
5
|
+
stack: trigger-dev-v4
|
|
6
|
+
patches: 'docs/ach.md'
|
|
7
|
+
detect: |
|
|
8
|
+
if command -v jq >/dev/null 2>&1; then jq -e '(.dependencies//{})+(.devDependencies//{})+(.peerDependencies//{})|has("@trigger.dev/sdk")' package.json >/dev/null 2>&1; else grep -q '"@trigger.dev/sdk"' package.json 2>/dev/null; fi
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Patch — ACH for Trigger.dev v4 projects
|
|
12
|
+
|
|
13
|
+
> Applies to `/docs/ach.md`. Materializes Task-like with Trigger.dev vocabulary.
|
|
14
|
+
|
|
15
|
+
## §3.1 — Task-like → Trigger.dev v4 task
|
|
16
|
+
|
|
17
|
+
```md
|
|
18
|
+
#### Task (Trigger.dev)
|
|
19
|
+
|
|
20
|
+
- **What it is:** an async, idempotent, retry-safe unit of work. The Trigger.dev v4 infrastructure runs it.
|
|
21
|
+
- **Single responsibility:** one workflow step, or a cron orchestrator. It reads canonical state from the DB. It returns early on terminal states. It updates the DB atomically. It triggers the next task when needed.
|
|
22
|
+
- **When to use it:**
|
|
23
|
+
- Cron (crawler's `schedules.task`).
|
|
24
|
+
- A long workflow that needs retry (build + publish).
|
|
25
|
+
- An external integration with variable latency or expected failure.
|
|
26
|
+
- **When NOT to use it:**
|
|
27
|
+
- The work fits in under 5 seconds with no retry. Use a procedure instead.
|
|
28
|
+
- The logic is pure with no I/O. Use domain instead.
|
|
29
|
+
- **Convention:** the task `id` matches the file name in kebab-case. Example: `start-deployment-workflow.ts` becomes `tasks.task("start-deployment-workflow", ...)`. This is Trigger.dev's own convention — the dashboard and CLI both key a run on it — so it stays here as fact, unlike the file-tree layout and import boundary below, which are one operator's choice.
|
|
30
|
+
|
|
31
|
+
##### Pattern: a thin parent calls subtasks through `triggerAndWait`
|
|
32
|
+
|
|
33
|
+
A multi-stage workflow is a thin parent that coordinates, plus subtasks that do the work. Each subtask has its own `runId`, its own retry, its own log stream, and its own machine size. The parent only calls `triggerAndWait`.
|
|
34
|
+
|
|
35
|
+
Example topology:
|
|
36
|
+
|
|
37
|
+
~~~
|
|
38
|
+
start-deployment-workflow (orchestrator — machine: small-1x)
|
|
39
|
+
├── triggerAndWait("build-sdk", { buildId }) — machine: large-1x
|
|
40
|
+
└── triggerAndWait("publish-sdk", { publishId }) — machine: medium-1x
|
|
41
|
+
~~~
|
|
42
|
+
|
|
43
|
+
Benefits: granular retry (publish alone, with no rebuild), isolated logs, a machine size tuned per stage, and composability (`retag-publish` reuses auth and the registry without a rebundle).
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
> The task directory tree (`src/trigger/` with `cron-<X>.ts` / `<stage>-<entity>.ts` /
|
|
47
|
+
> `start-<entity>-workflow.ts`), the import boundary between tasks and the rest of the codebase,
|
|
48
|
+
> and the `createTriggerContext()` DB-access helper are house convention, not Trigger.dev facts —
|
|
49
|
+
> nothing about Trigger.dev requires this layout or this helper. They now live as an **opt-in
|
|
50
|
+
> seed**, proposed rather than imposed, at `code-style/trigger-dev-v4/naming.md` and
|
|
51
|
+
> `code-style/trigger-dev-v4/imports.md`. Materialize either only if the operator accepts it.
|
|
52
|
+
|
|
53
|
+
## §6 — Trigger conventions
|
|
54
|
+
|
|
55
|
+
```md
|
|
56
|
+
### Trigger config (`trigger.config.ts`)
|
|
57
|
+
|
|
58
|
+
- `build.external` — deps that must NOT be bundled (for example, `tsdown` when used as a runtime subprocess).
|
|
59
|
+
- `machines` — sizing per task. The topology above sizes the orchestrator small, build large, and publish medium; size each stage against its own workload.
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
> Where the project ID lives — pinned in `trigger.config.ts` versus read from env — is an
|
|
63
|
+
> operator call this module does not make either way; state it in the project's own docs if it
|
|
64
|
+
> matters there.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: module
|
|
3
|
+
description: 'Seed gotchas for Trigger.dev v4, starting with the run id changing between attempts of the same run.'
|
|
4
|
+
load: cold
|
|
5
|
+
stack: trigger-dev-v4
|
|
6
|
+
patches: 'docs/gotchas/*.md'
|
|
7
|
+
detect: |
|
|
8
|
+
if command -v jq >/dev/null 2>&1; then jq -e '(.dependencies//{})+(.devDependencies//{})+(.peerDependencies//{})|has("@trigger.dev/sdk")' package.json >/dev/null 2>&1; else grep -q '"@trigger.dev/sdk"' package.json 2>/dev/null; fi
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Gotchas seed — Trigger.dev v4
|
|
12
|
+
|
|
13
|
+
> Each entry below is a complete gotcha file. Copy one to `docs/gotchas/NNN-slug.md` (the first `---` of the entry as line 1) only when it bites in real use. Re-date `last_bite` on copy.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
type: gotcha
|
|
17
|
+
description: 'ctx.run.id differs on each retry attempt, so a runId persisted to track one execution points at the last attempt instead.'
|
|
18
|
+
load: warm
|
|
19
|
+
fires_on: event
|
|
20
|
+
fires_when: 'you write a new Trigger.dev task, especially one that persists a runId in a row.'
|
|
21
|
+
bites: 1
|
|
22
|
+
last_bite: 2026-10
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Trigger.dev — `params.ctx.run.id` changes between attempts
|
|
26
|
+
|
|
27
|
+
## Behavior
|
|
28
|
+
|
|
29
|
+
`ctx.run.id` differs on each retry attempt. A task that persists runId to track one execution loses the link on retry. It ends up pointing to the last run instead.
|
|
30
|
+
|
|
31
|
+
## Solution
|
|
32
|
+
|
|
33
|
+
Make the runId that tracks a process **immutable after the first write**. Use `updateMany({ where: { id, runId: null }, data: { runId } })` for an atomic upsert. Keep canonical state in the DB, not in the run id.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
type: gotcha
|
|
37
|
+
description: 'Trigger.dev bundles everything a task imports, which breaks binaries the task runs as a subprocess unless they are listed in build.external.'
|
|
38
|
+
load: warm
|
|
39
|
+
fires_on: event
|
|
40
|
+
fires_when: 'you update Trigger.dev, edit trigger.config.ts, or remove a binary used as a subprocess at runtime.'
|
|
41
|
+
bites: 1
|
|
42
|
+
last_bite: 2026-10
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Trigger.dev — put `tsdown` (or similar) in `build.external`
|
|
46
|
+
|
|
47
|
+
## Behavior
|
|
48
|
+
|
|
49
|
+
Trigger.dev tries to bundle everything a task imports. Binaries invoked as a subprocess (`tsdown`, `esbuild`, `rollup`) break when bundled. The task expects a file but receives a module instead.
|
|
50
|
+
|
|
51
|
+
## Solution
|
|
52
|
+
|
|
53
|
+
Keep the binary listed in `trigger.config.ts` `build.external`. Document this in the config's comment.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: module
|
|
3
|
+
description: 'Hard rules for Trigger.dev v4 projects, split between executable triggers and review-only principles.'
|
|
4
|
+
load: cold
|
|
5
|
+
stack: trigger-dev-v4
|
|
6
|
+
patches: 'docs/rules/*.md'
|
|
7
|
+
detect: |
|
|
8
|
+
if command -v jq >/dev/null 2>&1; then jq -e '(.dependencies//{})+(.devDependencies//{})+(.peerDependencies//{})|has("@trigger.dev/sdk")' package.json >/dev/null 2>&1; else grep -q '"@trigger.dev/sdk"' package.json 2>/dev/null; fi
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Patch — Hard rules for Trigger.dev v4 projects
|
|
12
|
+
|
|
13
|
+
> Hard rules (with an executable trigger) go in after the last hard rule in `/docs/afm.md` §3.
|
|
14
|
+
> Principles (verified by review only) go in `/docs/afm.md` §1.3.
|
|
15
|
+
|
|
16
|
+
## Hard rules (executable trigger)
|
|
17
|
+
|
|
18
|
+
None. Both rules this module used to state as a hard, path-bound trigger — routing task DB
|
|
19
|
+
access through `createTriggerContext()` instead of `prismaClient` directly, and requiring an
|
|
20
|
+
idempotency test co-located with each task — are house convention, not Trigger.dev facts:
|
|
21
|
+
nothing about Trigger.dev requires either helper or that test layout. Worse, the first one gated
|
|
22
|
+
an invented helper at an invented path (`src/lib/trigger.ts`) as if it were framework law. Both
|
|
23
|
+
verifications also hardcoded one operator's `src/trigger/` directory, so they would fail
|
|
24
|
+
vacuously on any project laid out differently. They now live as an **opt-in seed**, proposed
|
|
25
|
+
rather than imposed, at `code-style/trigger-dev-v4/imports.md` (the context helper) and
|
|
26
|
+
`code-style/trigger-dev-v4/test-placement.md` (the idempotency test, plus the `runId`
|
|
27
|
+
atomic-upsert example that guards it). Materialize either only if the operator accepts it.
|
|
28
|
+
|
|
29
|
+
## Principles (insert in §1.3 — review, not a trigger)
|
|
30
|
+
|
|
31
|
+
None currently. The `runId`-immutability principle that lived here was tied entirely to the
|
|
32
|
+
idempotency-test hard rule above and had no separate grep trigger of its own — it moved with
|
|
33
|
+
that rule to `code-style/trigger-dev-v4/test-placement.md`, as the worked example of the atomic
|
|
34
|
+
upsert. Add a principle here only for a genuine Trigger.dev fact that mechanical review, not a
|
|
35
|
+
grep, has to catch.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: module
|
|
3
|
+
description: 'Materializes the Procedure-like and Domain-like component types with tRPC v11 vocabulary in the ACH.'
|
|
4
|
+
load: cold
|
|
5
|
+
stack: trpc-v11
|
|
6
|
+
patches: 'docs/ach.md'
|
|
7
|
+
detect: |
|
|
8
|
+
if command -v jq >/dev/null 2>&1; then jq -e '(.dependencies//{})+(.devDependencies//{})+(.peerDependencies//{})|has("@trpc/server")' package.json >/dev/null 2>&1; else grep -q '"@trpc/server"' package.json 2>/dev/null; fi
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Patch — ACH for tRPC v11 projects
|
|
12
|
+
|
|
13
|
+
> Apply this to `/docs/ach.md`. It materializes Procedure-like and Domain-like with tRPC vocabulary.
|
|
14
|
+
|
|
15
|
+
## §3.1 — Procedure-like → tRPC procedure
|
|
16
|
+
|
|
17
|
+
```md
|
|
18
|
+
#### Procedure (tRPC)
|
|
19
|
+
|
|
20
|
+
- **What it is:** a tRPC handler with Zod input, auth through middleware, and a typed return the frontend can consume.
|
|
21
|
+
- **Single responsibility:** orchestrate (< ~5s) — validate → authorize → call domain/lib → persist → respond.
|
|
22
|
+
- **When to use it:** every synchronous UI ↔ server interaction. REST `/api/v1/*` is derived through `trpc-to-openapi` when applicable.
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
> Naming (export and file), the neighbor-test rule, and the subrouter's location are house
|
|
26
|
+
> convention, not a tRPC fact — a project can name and place these however it wants and the
|
|
27
|
+
> handler shape above still holds. They now live as an opt-in seed at
|
|
28
|
+
> `code-style/trpc-v11/naming.md` (naming) and `code-style/trpc-v11/test-placement.md`
|
|
29
|
+
> (test placement). Materialize either only if the operator accepts it.
|
|
30
|
+
|
|
31
|
+
## §3.1 — Domain-like → function with `DomainInput<T>`
|
|
32
|
+
|
|
33
|
+
```md
|
|
34
|
+
#### Domain (per module)
|
|
35
|
+
|
|
36
|
+
- **What it is:** pure business-rule functions. Signature: `({ ctx, input }: DomainInput<T>) => …`. Always use `{ ctx, input }`, even when `ctx` is unused, for consistency.
|
|
37
|
+
- **Domain does NOT import `TRPCError`.** `TRPCError` is a transport type — importing it in domain code couples business logic to the tRPC boundary. Use `DomainError({ code: "..." as const })` or `Result<T, E>` instead; the procedure maps the code to `TRPCError` at the boundary.
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
> How many functions live in one file, and the file/export naming, are house convention — they
|
|
41
|
+
> now live as an opt-in seed at `code-style/trpc-v11/file-size.md` (one function per file) and
|
|
42
|
+
> `code-style/trpc-v11/naming.md` (export/file naming). A mechanical, path-bound check for the
|
|
43
|
+
> "no `TRPCError` in domain" principle above — which needs the project's actual domain-layer
|
|
44
|
+
> path — is also in the seed, at `code-style/trpc-v11/error-boundary.md`.
|
|
45
|
+
|
|
46
|
+
## §6 — ISP through `Pick<TRPCContext, ...>`
|
|
47
|
+
|
|
48
|
+
```md
|
|
49
|
+
- A domain function declares only the `ctx` fields it uses: `ctx: Pick<TRPCContext, "db" | "log">`. It does not receive the full `TRPCContext`. This keeps tests smaller and makes the dependency explicit.
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
> The remaining house conventions this module used to state as fact — the subrouter's exact
|
|
53
|
+
> directory (`modules/<f>/router.ts`) and a specific `makeTestCtx` test helper naming one file
|
|
54
|
+
> path that exists in exactly one repo — are not a tRPC fact either. See
|
|
55
|
+
> `code-style/trpc-v11/naming.md` and `code-style/trpc-v11/test-placement.md` for the proposals
|
|
56
|
+
> that replace them.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: module
|
|
3
|
+
description: 'Hard rules for tRPC v11 projects, including domain code that never raises a transport error.'
|
|
4
|
+
load: cold
|
|
5
|
+
stack: trpc-v11
|
|
6
|
+
patches: 'docs/rules/*.md'
|
|
7
|
+
detect: |
|
|
8
|
+
if command -v jq >/dev/null 2>&1; then jq -e '(.dependencies//{})+(.devDependencies//{})+(.peerDependencies//{})|has("@trpc/server")' package.json >/dev/null 2>&1; else grep -q '"@trpc/server"' package.json 2>/dev/null; fi
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Patch — Hard rules for tRPC v11 projects
|
|
12
|
+
|
|
13
|
+
> Hard rules (with an executable trigger) go after the last hard rule in `/docs/afm.md` §3.
|
|
14
|
+
> Principles (verified only by review) go in `/docs/afm.md` §1.3.
|
|
15
|
+
|
|
16
|
+
## Hard rules (executable trigger)
|
|
17
|
+
|
|
18
|
+
None. Every rule this module used to state as a hard, path-bound trigger — the
|
|
19
|
+
`procedure_<action><Module>` / `domain_<action><Module>` export naming, and the ban on a
|
|
20
|
+
`__tests__/` folder — is house convention, not a tRPC fact: nothing about tRPC requires a
|
|
21
|
+
particular export prefix or test-file location. They now live as an **opt-in seed**, proposed
|
|
22
|
+
rather than imposed, at `code-style/trpc-v11/naming.md` and
|
|
23
|
+
`code-style/trpc-v11/test-placement.md`. Materialize either only if the operator accepts it.
|
|
24
|
+
|
|
25
|
+
## Principles (insert into §1.3 — review, not a trigger)
|
|
26
|
+
|
|
27
|
+
- **A transport error type must not reach the domain layer.** `TRPCError` is tRPC-specific;
|
|
28
|
+
a domain function that imports it couples business logic to the transport boundary and makes
|
|
29
|
+
the domain layer harder to reuse outside tRPC (a queue worker, a CLI, a test). The procedure
|
|
30
|
+
maps a domain error code to `TRPCError` at the edge instead.
|
|
31
|
+
*(This is a genuine layering fact, but a mechanical check for it needs the project's actual
|
|
32
|
+
domain-layer path, which is house layout and varies by repo. A concrete, path-bound grep that
|
|
33
|
+
verifies it is proposed — not imposed — at `code-style/trpc-v11/error-boundary.md`.)*
|
|
34
|
+
- **A domain function uses `Pick<TRPCContext, ...>` instead of the full TRPCContext** when it needs only part of ctx. *(No grep can tell partial use from full use — this needs review. Flag a procedure that receives full ctx but uses only part of it.)*
|