@turri-dev/cli 0.0.0-stage → 0.1.1

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 (315) hide show
  1. package/.pi/themes/turri-dark.json +80 -0
  2. package/.pi/themes/turri-light.json +82 -0
  3. package/LICENSE +21 -0
  4. package/README.md +272 -2
  5. package/assets/jellyfish/dark-00.png +0 -0
  6. package/assets/jellyfish/dark-01.png +0 -0
  7. package/assets/jellyfish/dark-02.png +0 -0
  8. package/assets/jellyfish/dark-03.png +0 -0
  9. package/assets/jellyfish/dark-04.png +0 -0
  10. package/assets/jellyfish/dark-05.png +0 -0
  11. package/assets/jellyfish/dark-06.png +0 -0
  12. package/assets/jellyfish/dark-07.png +0 -0
  13. package/assets/jellyfish/dark-08.png +0 -0
  14. package/assets/jellyfish/dark-09.png +0 -0
  15. package/assets/jellyfish/dark-10.png +0 -0
  16. package/assets/jellyfish/dark-11.png +0 -0
  17. package/assets/jellyfish/dark-12.png +0 -0
  18. package/assets/jellyfish/dark-13.png +0 -0
  19. package/assets/jellyfish/dark-14.png +0 -0
  20. package/assets/jellyfish/fallback.txt +252 -0
  21. package/assets/jellyfish/light-00.png +0 -0
  22. package/assets/jellyfish/light-01.png +0 -0
  23. package/assets/jellyfish/light-02.png +0 -0
  24. package/assets/jellyfish/light-03.png +0 -0
  25. package/assets/jellyfish/light-04.png +0 -0
  26. package/assets/jellyfish/light-05.png +0 -0
  27. package/assets/jellyfish/light-06.png +0 -0
  28. package/assets/jellyfish/light-07.png +0 -0
  29. package/assets/jellyfish/light-08.png +0 -0
  30. package/assets/jellyfish/light-09.png +0 -0
  31. package/assets/jellyfish/light-10.png +0 -0
  32. package/assets/jellyfish/light-11.png +0 -0
  33. package/assets/jellyfish/light-12.png +0 -0
  34. package/assets/jellyfish/light-13.png +0 -0
  35. package/assets/jellyfish/light-14.png +0 -0
  36. package/bin/turri.mjs +29 -0
  37. package/dist/ask.js +149 -0
  38. package/dist/brand.js +50 -0
  39. package/dist/btw.js +174 -0
  40. package/dist/chat-models.js +37 -0
  41. package/dist/chat-skills.js +18 -0
  42. package/dist/cli.js +178 -0
  43. package/dist/config-command.js +197 -0
  44. package/dist/config-edit.js +367 -0
  45. package/dist/config.js +489 -0
  46. package/dist/conform-cli.js +29 -0
  47. package/dist/cultivate-cli.js +110 -0
  48. package/dist/drift-cli.js +25 -0
  49. package/dist/environment.js +70 -0
  50. package/dist/extension.js +2372 -0
  51. package/dist/genome.js +301 -0
  52. package/dist/graph.js +246 -0
  53. package/dist/identity.js +35 -0
  54. package/dist/init-config.js +511 -0
  55. package/dist/judge.js +62 -0
  56. package/dist/lineage-cli.js +13 -0
  57. package/dist/loop-cli.js +132 -0
  58. package/dist/loop-tools.js +225 -0
  59. package/dist/mature-tools.js +193 -0
  60. package/dist/model-check.js +117 -0
  61. package/dist/module-report.js +9 -0
  62. package/dist/pi-launch.js +31 -0
  63. package/dist/pi.js +813 -0
  64. package/dist/plain/check.js +76 -0
  65. package/dist/plain/concepts.js +213 -0
  66. package/dist/plain/errors.js +43 -0
  67. package/dist/plain/files.js +57 -0
  68. package/dist/plain/highlight.js +195 -0
  69. package/dist/plain/index.js +15 -0
  70. package/dist/plain/liquid.js +129 -0
  71. package/dist/plain/markdown.js +208 -0
  72. package/dist/plain/parser.js +752 -0
  73. package/dist/plain/plain.tmLanguage.json +124 -0
  74. package/dist/plain/spec.js +291 -0
  75. package/dist/plain/stdlib.js +44 -0
  76. package/dist/question-dialog.js +125 -0
  77. package/dist/questioning.js +109 -0
  78. package/dist/quiet.js +125 -0
  79. package/dist/regenerate/activity.js +187 -0
  80. package/dist/regenerate/agent-env.js +190 -0
  81. package/dist/regenerate/agent-shell.js +114 -0
  82. package/dist/regenerate/arbiter.js +133 -0
  83. package/dist/regenerate/audit.js +203 -0
  84. package/dist/regenerate/cases.js +83 -0
  85. package/dist/regenerate/change-view.js +102 -0
  86. package/dist/regenerate/choose-conformance.js +117 -0
  87. package/dist/regenerate/chunk-plan.js +625 -0
  88. package/dist/regenerate/codebase.js +75 -0
  89. package/dist/regenerate/command-check.js +312 -0
  90. package/dist/regenerate/compaction.js +20 -0
  91. package/dist/regenerate/confine.js +310 -0
  92. package/dist/regenerate/conform.js +133 -0
  93. package/dist/regenerate/conformance-agents.js +955 -0
  94. package/dist/regenerate/conformance.js +936 -0
  95. package/dist/regenerate/conversation-writes.js +100 -0
  96. package/dist/regenerate/credentials.js +88 -0
  97. package/dist/regenerate/debug-log.js +228 -0
  98. package/dist/regenerate/debugger.js +90 -0
  99. package/dist/regenerate/delivery.js +19 -0
  100. package/dist/regenerate/diagnose-job.js +40 -0
  101. package/dist/regenerate/diagnose.js +81 -0
  102. package/dist/regenerate/diff-view.js +240 -0
  103. package/dist/regenerate/drift.js +194 -0
  104. package/dist/regenerate/engine.js +118 -0
  105. package/dist/regenerate/environment-error.js +220 -0
  106. package/dist/regenerate/job-log.js +102 -0
  107. package/dist/regenerate/job-panel.js +336 -0
  108. package/dist/regenerate/jobs-tree.js +300 -0
  109. package/dist/regenerate/jobs.js +427 -0
  110. package/dist/regenerate/lanes.js +32 -0
  111. package/dist/regenerate/lineage.js +384 -0
  112. package/dist/regenerate/loop.js +1435 -0
  113. package/dist/regenerate/masking.js +95 -0
  114. package/dist/regenerate/notes.js +397 -0
  115. package/dist/regenerate/os-sandbox.js +243 -0
  116. package/dist/regenerate/paths.js +165 -0
  117. package/dist/regenerate/plain-palette.js +83 -0
  118. package/dist/regenerate/prompt.js +704 -0
  119. package/dist/regenerate/records.js +294 -0
  120. package/dist/regenerate/regenerate.js +598 -0
  121. package/dist/regenerate/regression.js +181 -0
  122. package/dist/regenerate/release.js +262 -0
  123. package/dist/regenerate/requires.js +241 -0
  124. package/dist/regenerate/resources.js +164 -0
  125. package/dist/regenerate/self-check.js +111 -0
  126. package/dist/regenerate/services.js +155 -0
  127. package/dist/regenerate/shell.js +319 -0
  128. package/dist/regenerate/side-panel.js +278 -0
  129. package/dist/regenerate/spec-map-view.js +574 -0
  130. package/dist/regenerate/spec-map.js +561 -0
  131. package/dist/regenerate/spec-steps.js +527 -0
  132. package/dist/regenerate/state.js +10 -0
  133. package/dist/regenerate/steer.js +91 -0
  134. package/dist/regenerate/steering.js +119 -0
  135. package/dist/regenerate/stop-report.js +161 -0
  136. package/dist/regenerate/submit-work.js +71 -0
  137. package/dist/regenerate/test-kinds.js +124 -0
  138. package/dist/regenerate/test-scripts.js +695 -0
  139. package/dist/regenerate/testing.js +406 -0
  140. package/dist/regenerate/transcript-view.js +1 -0
  141. package/dist/regenerate/unit-gate.js +20 -0
  142. package/dist/regenerate/unit-review.js +347 -0
  143. package/dist/regenerate/web.js +256 -0
  144. package/dist/regenerate/workspace.js +99 -0
  145. package/dist/regenerate-cli.js +68 -0
  146. package/dist/release-agent.js +114 -0
  147. package/dist/release-cli.js +131 -0
  148. package/dist/release-tools.js +148 -0
  149. package/dist/review/live.js +165 -0
  150. package/dist/rules.js +47 -0
  151. package/dist/self-invocation.js +110 -0
  152. package/dist/setup.js +245 -0
  153. package/dist/skill-progress.js +78 -0
  154. package/dist/skill.js +149 -0
  155. package/dist/skills.js +74 -0
  156. package/dist/spec-diff-cli.js +37 -0
  157. package/dist/spec-tools.js +155 -0
  158. package/dist/stack.js +158 -0
  159. package/dist/stacks.js +97 -0
  160. package/dist/stdlib.js +100 -0
  161. package/dist/terminal-images.js +46 -0
  162. package/dist/tools.js +242 -0
  163. package/dist/validate-cli.js +52 -0
  164. package/docs/README.md +58 -0
  165. package/docs/authoring-plain.md +245 -0
  166. package/docs/config.md +353 -0
  167. package/docs/deploy.md +109 -0
  168. package/docs/development.md +75 -0
  169. package/docs/headless.md +148 -0
  170. package/docs/images/how-turri-works.svg +50 -0
  171. package/docs/images/turri-banner.png +0 -0
  172. package/docs/installation.md +118 -0
  173. package/docs/parser.md +108 -0
  174. package/docs/regenerating.md +1116 -0
  175. package/docs/sandbox.md +233 -0
  176. package/docs/skills-and-gates.md +254 -0
  177. package/docs/theming.md +76 -0
  178. package/docs/using.md +454 -0
  179. package/genome/rules/bullet-continuation.md +69 -0
  180. package/genome/rules/concise-specs-examples.md +431 -0
  181. package/genome/rules/concise-specs-integration-examples.md +270 -0
  182. package/genome/rules/concise-specs.md +80 -0
  183. package/genome/rules/definitions.md +107 -0
  184. package/genome/rules/examples/integration-embedded-testing/run_conformance_tests_java.sh +163 -0
  185. package/genome/rules/examples/integration-embedded-testing/run_unittests_java.sh +51 -0
  186. package/genome/rules/exported-concepts.md +39 -0
  187. package/genome/rules/func-specs.md +129 -0
  188. package/genome/rules/impl-reqs.md +68 -0
  189. package/genome/rules/import-modules.md +48 -0
  190. package/genome/rules/integration-embedded-testing.md +276 -0
  191. package/genome/rules/integration-embedded.md +171 -0
  192. package/genome/rules/integration-standalone.md +135 -0
  193. package/genome/rules/integrations.md +137 -0
  194. package/genome/rules/linked-resources.md +69 -0
  195. package/genome/rules/module-structure.md +105 -0
  196. package/genome/rules/project-model.md +88 -0
  197. package/genome/rules/required-concepts.md +44 -0
  198. package/genome/rules/requires-modules.md +80 -0
  199. package/genome/rules/stdlib.md +73 -0
  200. package/genome/rules/test-reqs.md +57 -0
  201. package/genome/skills/add-acceptance-test/SKILL.md +100 -0
  202. package/genome/skills/add-concept/SKILL.md +71 -0
  203. package/genome/skills/add-functional-spec/SKILL.md +84 -0
  204. package/genome/skills/add-functional-specs/SKILL.md +118 -0
  205. package/genome/skills/add-implementation-requirement/SKILL.md +86 -0
  206. package/genome/skills/add-resource/SKILL.md +105 -0
  207. package/genome/skills/add-template/SKILL.md +64 -0
  208. package/genome/skills/add-test-requirement/SKILL.md +73 -0
  209. package/genome/skills/analyze-2-func-specs/SKILL.md +101 -0
  210. package/genome/skills/analyze-func-specs/SKILL.md +123 -0
  211. package/genome/skills/analyze-if-func-spec-too-complex/SKILL.md +211 -0
  212. package/genome/skills/break-down-func-spec/SKILL.md +205 -0
  213. package/genome/skills/brood/SKILL.md +229 -0
  214. package/genome/skills/brood/references/intent.md +35 -0
  215. package/genome/skills/brood/references/product.md +101 -0
  216. package/genome/skills/brood/references/stack.md +61 -0
  217. package/genome/skills/brood/references/testing.md +56 -0
  218. package/genome/skills/bud/SKILL.md +148 -0
  219. package/genome/skills/bud/references/checklist.md +44 -0
  220. package/genome/skills/bud/references/intent.md +30 -0
  221. package/genome/skills/check-plain-env/SKILL.md +285 -0
  222. package/genome/skills/consolidate-concepts/SKILL.md +192 -0
  223. package/genome/skills/create-import-module/SKILL.md +104 -0
  224. package/genome/skills/create-requires-module/SKILL.md +109 -0
  225. package/genome/skills/debug-specs/SKILL.md +188 -0
  226. package/genome/skills/implement-conformance-testing-script/SKILL.md +238 -0
  227. package/genome/skills/implement-conformance-testing-script/assets/run_conformance_tests_cypress.ps1 +337 -0
  228. package/genome/skills/implement-conformance-testing-script/assets/run_conformance_tests_golang.ps1 +111 -0
  229. package/genome/skills/implement-conformance-testing-script/assets/run_conformance_tests_java.sh +136 -0
  230. package/genome/skills/implement-conformance-testing-script/assets/run_conformance_tests_python.ps1 +103 -0
  231. package/genome/skills/implement-conformance-testing-script/assets/run_conformance_tests_python.sh +150 -0
  232. package/genome/skills/implement-unit-testing-script/SKILL.md +160 -0
  233. package/genome/skills/implement-unit-testing-script/assets/run_unittests_flutter.ps1 +87 -0
  234. package/genome/skills/implement-unit-testing-script/assets/run_unittests_golang.ps1 +73 -0
  235. package/genome/skills/implement-unit-testing-script/assets/run_unittests_java.sh +72 -0
  236. package/genome/skills/implement-unit-testing-script/assets/run_unittests_python.ps1 +81 -0
  237. package/genome/skills/implement-unit-testing-script/assets/run_unittests_python.sh +98 -0
  238. package/genome/skills/implement-unit-testing-script/assets/run_unittests_react.ps1 +87 -0
  239. package/genome/skills/init-plain-project/SKILL.md +117 -0
  240. package/genome/skills/init-plain-project/references/checklist.md +30 -0
  241. package/genome/skills/mature/SKILL.md +126 -0
  242. package/genome/skills/refactor-module/SKILL.md +196 -0
  243. package/genome/skills/resolve-section-ownership/SKILL.md +64 -0
  244. package/genome/skills/resolve-spec-conflict/SKILL.md +96 -0
  245. package/genome/skills/spawn/SKILL.md +163 -0
  246. package/package.json +68 -4
  247. package/stdlib/backend/common.plain +25 -0
  248. package/stdlib/backend/conformance/common.plain +12 -0
  249. package/stdlib/backend/conformance/go-gin.plain +9 -0
  250. package/stdlib/backend/conformance/go-nethttp.plain +9 -0
  251. package/stdlib/backend/conformance/java-springboot.plain +9 -0
  252. package/stdlib/backend/conformance/python-django.plain +9 -0
  253. package/stdlib/backend/conformance/python-fastapi.plain +9 -0
  254. package/stdlib/backend/conformance/python-flask.plain +9 -0
  255. package/stdlib/backend/conformance/rust-axum.plain +9 -0
  256. package/stdlib/backend/conformance/typescript-express.plain +9 -0
  257. package/stdlib/backend/conformance/typescript-fastify.plain +9 -0
  258. package/stdlib/backend/conformance/typescript-nestjs.plain +9 -0
  259. package/stdlib/backend/go-gin.plain +18 -0
  260. package/stdlib/backend/go-nethttp.plain +18 -0
  261. package/stdlib/backend/java-springboot.plain +19 -0
  262. package/stdlib/backend/python-django.plain +19 -0
  263. package/stdlib/backend/python-fastapi.plain +23 -0
  264. package/stdlib/backend/python-flask.plain +23 -0
  265. package/stdlib/backend/rust-axum.plain +17 -0
  266. package/stdlib/backend/typescript-express.plain +19 -0
  267. package/stdlib/backend/typescript-fastify.plain +19 -0
  268. package/stdlib/backend/typescript-nestjs.plain +19 -0
  269. package/stdlib/cli/common.plain +17 -0
  270. package/stdlib/cli/conformance/common.plain +11 -0
  271. package/stdlib/cli/conformance/go-cobra.plain +9 -0
  272. package/stdlib/cli/conformance/go-flag.plain +9 -0
  273. package/stdlib/cli/conformance/java-picocli.plain +9 -0
  274. package/stdlib/cli/conformance/python-argparse.plain +9 -0
  275. package/stdlib/cli/conformance/python-click.plain +9 -0
  276. package/stdlib/cli/conformance/python-typer.plain +9 -0
  277. package/stdlib/cli/conformance/rust-clap.plain +9 -0
  278. package/stdlib/cli/conformance/typescript-commander.plain +9 -0
  279. package/stdlib/cli/go-cobra.plain +14 -0
  280. package/stdlib/cli/go-flag.plain +14 -0
  281. package/stdlib/cli/java-picocli.plain +15 -0
  282. package/stdlib/cli/python-argparse.plain +18 -0
  283. package/stdlib/cli/python-click.plain +16 -0
  284. package/stdlib/cli/python-typer.plain +16 -0
  285. package/stdlib/cli/rust-clap.plain +14 -0
  286. package/stdlib/cli/typescript-commander.plain +17 -0
  287. package/stdlib/frontend/angular.plain +17 -0
  288. package/stdlib/frontend/common.plain +17 -0
  289. package/stdlib/frontend/conformance/angular.plain +5 -0
  290. package/stdlib/frontend/conformance/common.plain +11 -0
  291. package/stdlib/frontend/conformance/react-vite.plain +5 -0
  292. package/stdlib/frontend/conformance/sveltekit-spa.plain +5 -0
  293. package/stdlib/frontend/conformance/vue-vite.plain +5 -0
  294. package/stdlib/frontend/react-vite.plain +16 -0
  295. package/stdlib/frontend/sveltekit-spa.plain +16 -0
  296. package/stdlib/frontend/vue-vite.plain +16 -0
  297. package/stdlib/lang/go.plain +13 -0
  298. package/stdlib/lang/java.plain +12 -0
  299. package/stdlib/lang/python.plain +18 -0
  300. package/stdlib/lang/rust.plain +12 -0
  301. package/stdlib/lang/typescript.plain +13 -0
  302. package/stdlib/library/common.plain +15 -0
  303. package/stdlib/library/conformance/common.plain +9 -0
  304. package/stdlib/library/conformance/go.plain +9 -0
  305. package/stdlib/library/conformance/java-gradle.plain +9 -0
  306. package/stdlib/library/conformance/java-maven.plain +9 -0
  307. package/stdlib/library/conformance/python.plain +9 -0
  308. package/stdlib/library/conformance/rust.plain +9 -0
  309. package/stdlib/library/conformance/typescript.plain +9 -0
  310. package/stdlib/library/go.plain +12 -0
  311. package/stdlib/library/java-gradle.plain +13 -0
  312. package/stdlib/library/java-maven.plain +13 -0
  313. package/stdlib/library/python.plain +13 -0
  314. package/stdlib/library/rust.plain +12 -0
  315. package/stdlib/library/typescript.plain +15 -0
@@ -0,0 +1,431 @@
1
+ ---
2
+ description: Worked BAD to GOOD examples of concise .plain specs, one pair per conciseness failure mode
3
+ ---
4
+
5
+ # Worked examples of concise `.plain` specs
6
+
7
+ `concise-specs.md` is the rule; this file is the example bank behind it. Every entry names one failure mode, shows the padded version, and shows the rewrite that carries the same information.
8
+
9
+ Read the pairs, not the prose: in each one, the GOOD block says everything the BAD block said, and usually pins down an interface detail the BAD block left open. Conciseness is what makes room for that detail — it is never a licence to drop it. The last section, *Do not over-cut*, shows the failure in the other direction.
10
+
11
+ The BAD blocks intentionally demonstrate the mistakes they name, including invalid syntax. Copy only from GOOD blocks.
12
+
13
+ Integration specs fail in their own ways — over-precision, inlined contract data, and edge cases the provider's documentation invites. Those examples are in `concise-specs-integration-examples.md`.
14
+
15
+ ## Padding around one fact
16
+
17
+ BAD:
18
+
19
+ ```plain
20
+ ***functional specs***
21
+
22
+ - In order to provide users with a robust and seamless experience, :TaskService: exposes a comprehensive endpoint for the creation of new :Task: items.
23
+
24
+ - The endpoint accepts a request and, provided that the incoming payload is valid and well-formed, proceeds to persist the :Task: appropriately.
25
+
26
+ - If the payload is not valid, an appropriate error is returned to the caller so that the user is informed of the problem.
27
+ ```
28
+
29
+ GOOD:
30
+
31
+ ```plain
32
+ ***functional specs***
33
+
34
+ - A :User: can create a :Task: via `POST /tasks`.
35
+ - The request body is a :Task: without `id`.
36
+ - The response is `201` with the created :Task:.
37
+ - A request without a name returns `400`.
38
+ ```
39
+
40
+ Three bullets became one, and the rewrite names the method, the path, the request shape, and both status codes — none of which the padded version pinned down.
41
+
42
+ ## Rationale, motivation, and benefits
43
+
44
+ BAD:
45
+
46
+ ```plain
47
+ ***functional specs***
48
+
49
+ - Because support staff frequently need to find customers quickly, and because scanning the full list is slow and error-prone, :CustomerDirectory: offers a search capability that greatly improves their daily workflow.
50
+ ```
51
+
52
+ GOOD:
53
+
54
+ ```plain
55
+ ***functional specs***
56
+
57
+ - :CustomerDirectory: returns :Customer: items whose name or email contains a search term, case-insensitively.
58
+ ```
59
+
60
+ Why the feature exists changes nothing about what is built. What "search" matches does.
61
+
62
+ ## Restating a concept's own name
63
+
64
+ BAD:
65
+
66
+ ```plain
67
+ ***definitions***
68
+
69
+ - :InvoiceNumber: is the invoice number of an :Invoice:. It is used to identify invoices.
70
+ ```
71
+
72
+ GOOD:
73
+
74
+ ```plain
75
+ ***definitions***
76
+
77
+ - :InvoiceNumber: identifies an :Invoice: within a :BillingPeriod: and has the form `INV-<year>-<6 digits>`.
78
+ ```
79
+
80
+ A definition that only expands the token into a sentence carries no information. State the scope of uniqueness and the format instead.
81
+
82
+ ## Stating the obvious
83
+
84
+ BAD:
85
+
86
+ ```plain
87
+ ***functional specs***
88
+
89
+ - :OrderApi: returns a list of :Order: items over HTTP.
90
+ - The list contains :Order: items.
91
+ - If there are no :Order: items, the list is empty.
92
+ - Each :Order: has an `id`, which is unique.
93
+ - The caller receives the response after the request is sent.
94
+ ```
95
+
96
+ GOOD:
97
+
98
+ ```plain
99
+ ***functional specs***
100
+
101
+ - :OrderApi: returns the :Order: items of the authenticated :User: via `GET /orders`, newest first.
102
+ - The response is `200` with a JSON array, empty when the :User: has no :Order: items.
103
+ ```
104
+
105
+ Every cut line was already implied: a list of :Order: items contains :Order: items, an identifier identifies, and a response follows a request. What survives is what a reader could *not* have guessed — the ownership filter, the sort order, and the response shape.
106
+
107
+ Apply the deletion test line by line: *if this line were missing, would anything be built differently?* "The list contains :Order: items" fails it. "Newest first" passes it.
108
+
109
+ ## Universal software defaults
110
+
111
+ BAD:
112
+
113
+ ```plain
114
+ ***functional specs***
115
+
116
+ - :OrderService: validates all incoming data before processing it.
117
+
118
+ - :OrderService: handles errors and returns meaningful error messages.
119
+
120
+ - :OrderService: saves the :Order: to storage so that it is not lost.
121
+ ```
122
+
123
+ GOOD:
124
+
125
+ ```plain
126
+ ***functional specs***
127
+
128
+ - :OrderService: rejects an :Order: whose `total` does not equal the sum of its :OrderLine: amounts, with `422`.
129
+ ```
130
+
131
+ Validating input, reporting errors, and persisting on a save operation belong in the `***implementation reqs***` and a baseline is assumed. If there is a specific use case for a particular operation, it should be documented there, not in the `***functional specs***`. The one fact worth stating is *which* rule is enforced and what the caller sees when it fails.
132
+
133
+ ## Standard behavior of a named technology
134
+
135
+ BAD:
136
+
137
+ ```plain
138
+ ***implementation reqs***
139
+
140
+ - :Implementation: uses FastAPI.
141
+
142
+ - FastAPI parses incoming HTTP requests and routes them to the correct handler function.
143
+
144
+ - Request and response bodies are serialized to and from JSON.
145
+
146
+ - :Implementation: uses SQLAlchemy, which opens connections to the database and maps rows to objects.
147
+ ```
148
+
149
+ GOOD:
150
+
151
+ ```plain
152
+ ***implementation reqs***
153
+
154
+ - :Implementation: is written in Python 3.12 and uses FastAPI.
155
+
156
+ - :Implementation: uses SQLAlchemy 2.x with a single session per request.
157
+ ```
158
+
159
+ Once a technology is named, its documented behavior comes with it. Keep only the choices a reader could not derive from the name — here, the version and the session scope.
160
+
161
+ ## Uncheckable adjectives and adverbs
162
+
163
+ BAD:
164
+
165
+ ```plain
166
+ ***implementation reqs***
167
+
168
+ - :Implementation: handles large files efficiently and gracefully, following modern best practices.
169
+
170
+ - :UnitTests: provide comprehensive coverage of all important functionality.
171
+ ```
172
+
173
+ GOOD:
174
+
175
+ ```plain
176
+ ***implementation reqs***
177
+
178
+ - :FileDownloader: streams a file larger than 10 MB to disk instead of holding it in memory.
179
+
180
+ - :UnitTests: of :FileDownloader: serve fixture files from a local HTTP server, never a live host.
181
+ ```
182
+
183
+ `modern`, `best practices`, `efficiently`, `gracefully`, and `comprehensive` fail the same test: no test can distinguish a build that has them from one that does not. Either name the checkable threshold or delete the word.
184
+
185
+ Note the subject too. `:Implementation:` is the whole codebase — right for a fact that holds everywhere, too coarse for a rule about one behavior, and a requirement no reader can locate is one no reviewer can check. Name the component the requirement constrains.
186
+
187
+ The coverage line has no checkable version worth writing: testing every branch is what `:UnitTests:` are for, so "comprehensive coverage" converts to nothing. Not every uncheckable line becomes a checkable one — some are cut and replaced by the fact the section was missing, here the one thing nobody can derive: where the bytes come from when the tests run.
188
+
189
+ ## Hedging
190
+
191
+ BAD:
192
+
193
+ ```plain
194
+ ***test reqs***
195
+
196
+ - :ConformanceTests: should ideally use pytest, or another suitable framework if that is more appropriate.
197
+
198
+ - :ConformanceTests: may optionally mock external HTTP calls where possible.
199
+ ```
200
+
201
+ GOOD:
202
+
203
+ ```plain
204
+ ***test reqs***
205
+
206
+ - :ConformanceTests: are implemented with pytest.
207
+
208
+ - :ConformanceTests: mock every external HTTP call.
209
+ ```
210
+
211
+ Every hedge hands the renderer a decision the spec was supposed to make. A spec states one
212
+ behavior.
213
+
214
+ ## Ornate vocabulary
215
+
216
+ BAD:
217
+
218
+ ```plain
219
+ ***functional specs***
220
+
221
+ - :ReportGenerator: is responsible for facilitating the utilization of a plurality of :DataSource: instances in order to initiate the generation of a :Report: prior to the performance of the validation of the resulting output.
222
+ ```
223
+
224
+ GOOD:
225
+
226
+ ```plain
227
+ ***functional specs***
228
+
229
+ - :ReportGenerator: builds a :Report: from one or more :DataSource: items, then validates it.
230
+ ```
231
+
232
+ Same sequence, plain words. See the substitution table in `concise-specs.md`.
233
+
234
+ ## Sub-bullets that elaborate instead of disambiguating
235
+
236
+ BAD:
237
+
238
+ ```plain
239
+ ***functional specs***
240
+
241
+ - A :User: can archive a :Project:.
242
+ - Archiving is an action the user performs on a project.
243
+ - The archive action is available to users who have access to the project.
244
+ - Once archived, the project is in the archived state.
245
+ ```
246
+
247
+ GOOD:
248
+
249
+ ```plain
250
+ ***functional specs***
251
+
252
+ - A :User: can archive a :Project: they own.
253
+ - An archived :Project: is excluded from :ProjectList:.
254
+ - Archiving a :Project: that is already archived returns `409`.
255
+ ```
256
+
257
+ A sub-bullet earns its line by removing an ambiguity. If it only says the parent again in more words, it is filler.
258
+
259
+ ## Restating the parent bullet
260
+
261
+ BAD:
262
+
263
+ ```plain
264
+ ***functional specs***
265
+
266
+ - :SessionStore: expires a :Session: 30 minutes after its last use.
267
+ - Sessions do not live forever; they time out.
268
+ - The timeout is 30 minutes.
269
+ ```
270
+
271
+ GOOD:
272
+
273
+ ```plain
274
+ ***functional specs***
275
+
276
+ - :SessionStore: expires a :Session: 30 minutes after its last use.
277
+ - A request carrying an expired :Session: returns `401`.
278
+ ```
279
+
280
+ ## Synonym drift
281
+
282
+ BAD:
283
+
284
+ ```plain
285
+ ***functional specs***
286
+
287
+ - A :User: can upload a :Document:.
288
+
289
+ - The system stores the submitted file in :DocumentStore:.
290
+
291
+ - The uploaded asset is retrievable by its identifier.
292
+ ```
293
+
294
+ Three names — `:Document:`, "file", "asset" — read as three things. A reader cannot tell whether "asset" is a fourth concept, and the renderer cannot either.
295
+
296
+ GOOD:
297
+
298
+ ```plain
299
+ ***functional specs***
300
+
301
+ - A :User: can upload a :Document:.
302
+ - :DocumentStore: stores the :Document: and assigns it a :DocumentId:.
303
+ - A :User: can retrieve a :Document: by its :DocumentId:.
304
+ ```
305
+
306
+ ## Meta-commentary and scope disclaimers
307
+
308
+ BAD:
309
+
310
+ ```plain
311
+ ***functional specs***
312
+
313
+ - This spec is intentionally kept small so that it stays within the complexity limit.
314
+
315
+ - As described above, :ExportService: writes a :CsvFile:.
316
+
317
+ - No changes are made to the existing :ImportService: or to the user interface.
318
+
319
+ - Future specs will extend this behavior with additional formats.
320
+ ```
321
+
322
+ GOOD:
323
+
324
+ ```plain
325
+ ***functional specs***
326
+
327
+ - :ExportService: writes the :Order: items of a :BillingPeriod: to a :CsvFile:.
328
+ - The header row is `id,customer,total,currency`.
329
+ - An empty :BillingPeriod: produces a :CsvFile: with only the header row.
330
+ ```
331
+
332
+ A spec describes the software, not itself. What the spec does *not* change is everything it does not mention, and what later specs will add belongs to those specs.
333
+
334
+ ## Prose and tables inside a section
335
+
336
+ BAD:
337
+
338
+ ```plain
339
+ ***functional specs***
340
+
341
+ - :PricingEngine: applies discounts.
342
+ The discount matrix is as follows:
343
+ | Tier | Volume | Discount |
344
+ |---|---|---|
345
+ | Bronze | 0-99 | 0% |
346
+ | Silver | 100-499 | 5% |
347
+ | Gold | 500+ | 12% |
348
+ ```
349
+
350
+ GOOD:
351
+
352
+ ```plain
353
+ ***functional specs***
354
+
355
+ - :PricingEngine: applies the :DiscountTier: matrix in [resources/discount-tiers.json](resources/discount-tiers.json) to an :Order: total.
356
+ - An :Order: below the lowest tier threshold gets no discount.
357
+ ```
358
+
359
+ A section holds `- ` list items. Matrices, schemas, and payloads are external artifacts and are linked from `resources/` — see `linked-resources.md`.
360
+
361
+ ## Split one crowded bullet across its owning sections
362
+
363
+ One bullet that mixes behavior, technology, and test policy is hard to review and partly ignored: the renderer reads each concept only from its owning section, so the unit-test and conformance-test facts below never reach the generators that need them.
364
+
365
+ BAD — everything in one place:
366
+
367
+ ```plain
368
+ ***definitions***
369
+
370
+ - :ImageResizer: resizes images.
371
+
372
+ - :Image: is an image file uploaded by the user.
373
+
374
+ ***functional specs***
375
+
376
+ - :ImageResizer:, implemented as a Python class using Pillow with a thread pool of 4 workers, resizes an uploaded :Image: to a thumbnail, where a thumbnail is defined as a JPEG at most 256 pixels on its longest edge.
377
+ ```
378
+
379
+ GOOD — each fact in the section that owns it:
380
+
381
+ ```plain
382
+ ***definitions***
383
+
384
+ - :ImageResizer: resizes images.
385
+
386
+ - :Image: is an image file uploaded by the user.
387
+
388
+ - :Thumbnail: is a JPEG derived from an :Image:, at most 256 pixels on its longest edge, with the aspect ratio preserved.
389
+
390
+ - :UnsupportedImageError: is raised when an :Image: is not a supported format.
391
+
392
+ ***implementation reqs***
393
+
394
+ - :ImageResizer: resizes images with Pillow, on a thread pool of 4 workers.
395
+
396
+ ***functional specs***
397
+
398
+ - :ImageResizer: produces a :Thumbnail: from an :Image:.
399
+ - An :Image: already within the :Thumbnail: bounds is returned unchanged.
400
+ - An :Image: that is not a supported format raises :UnsupportedImageError:.
401
+ ```
402
+
403
+ Each section now answers one question, and the definition, the mechanism, and the behavior are each one line to check instead of clauses buried in a paragraph about resizing. Use the split whenever a bullet answers more than one of *what is it*, *what does it do*, *how is it built*, and *how is it tested* — see `module-structure.md` for section ownership and canonical section order.
404
+
405
+ Use `resolve-section-ownership` to perform the split. It is not the same as splitting a functional spec that is simply too large — for that, run `analyze-if-func-spec-too-complex`, then `break-down-func-spec`.
406
+
407
+ The most common single case of this is a functional spec that states a mechanism rather than a result — a retry schedule, a cache, a token refresh. `impl-reqs.md` owns HOW; a functional spec keeps what a caller observes. `concise-specs-integration-examples.md` works through several.
408
+
409
+ ## Narrated acceptance tests
410
+
411
+ BAD:
412
+
413
+ ```plain
414
+ ***acceptance tests***
415
+
416
+ - The tester should first open the application and log in with a valid account, then navigate to the projects area where they will be able to see the list of projects, and then they should create a new project by filling in the form, after which they should verify that everything worked as expected and the project shows up correctly.
417
+ ```
418
+
419
+ GOOD:
420
+
421
+ ```plain
422
+ ***acceptance tests***
423
+
424
+ - A :User: creates a :Project: and finds it in :ProjectList:.
425
+ - Sign in as a :User: with no :Project: items.
426
+ - Create a :Project: named `Apollo`.
427
+ - :ProjectList: contains exactly one :Project:, named `Apollo`.
428
+ ```
429
+
430
+ One narrated paragraph becomes an ordered set of checkable steps, and "everything worked as
431
+ expected" becomes an assertion.
@@ -0,0 +1,270 @@
1
+ ---
2
+ description: Worked BAD to GOOD examples of concise integration specs, where over-precision and obvious edge cases dominate
3
+ ---
4
+
5
+ # Worked examples of concise integration specs
6
+
7
+ `concise-specs.md` is the rule and `concise-specs-examples.md` is the general example bank. This file covers integration specs only, because they fail differently.
8
+
9
+ An integration is technically demanding, the provider's contract is large, and the author has the provider's documentation open while writing. The result is a spec that is *too precise*: status codes, field lists, retry arithmetic, and every edge case the API can produce, written out as prose bullets. It reads as rigorous and is unreviewable.
10
+
11
+ Conciseness here is almost never about covering less. `integrations.md` makes edge-case coverage a hard floor — auth, environments, versioning, pagination, rate limits, the error model, retries, idempotency, webhooks, data mapping, compliance, observability. Every one of those must be covered. The lever is **where** the coverage lives:
12
+
13
+ - **Shapes and enumerations go to `resources/`** — schemas, error maps, retry policy, rate-limit headers. A spec bullet links to the file; it never restates its contents.
14
+ - **The spec keeps behavior** — what the integration does with the shape, and what a caller observes.
15
+ - **Mechanism goes to `***implementation reqs***`** — retry logic, exception hierarchies, token caching, connection handling. Integration precision is mostly HOW, and `impl-reqs.md` owns HOW. `***functional specs***` keeps only what a caller can observe.
16
+
17
+ That last lever is easy to miss, so several examples below move a bullet between sections rather than shortening it. A fact in the wrong section is worse than a verbose one: the renderer reads each concept only from its owning section, so a misplaced fact is silently ignored.
18
+
19
+ Applied consistently, a spec gets shorter as coverage goes *up*, because each new corner case adds a line to a resource file rather than a paragraph to the spec.
20
+
21
+ The BAD blocks intentionally demonstrate the mistakes they name, including invalid syntax. Copy only from GOOD blocks.
22
+
23
+ ## Status codes and error categories enumerated inline
24
+
25
+ BAD:
26
+
27
+ ```plain
28
+ ***definitions***
29
+
30
+ - :ProviderApi: is the REST surface of the provider, defined by [resources/provider.openapi.yaml](resources/provider.openapi.yaml).
31
+
32
+ ***functional specs***
33
+
34
+ - :ProviderClient: retries the request when :ProviderApi: returns 408, 425, 429, 500, 502, 503, or 504, and also on connection errors, DNS failures, socket timeouts, TLS handshake failures, and connection resets.
35
+
36
+ - :ProviderClient: does not retry on 400, 401, 403, 404, 405, 409, 410, 415, or 422, and raises immediately.
37
+
38
+ - On 429, :ProviderClient: waits and tries again, unless the retry budget is exhausted.
39
+ ```
40
+
41
+ GOOD:
42
+
43
+ ```plain
44
+ ***definitions***
45
+
46
+ - :ProviderApi: is the REST surface of the provider, defined by [resources/provider.openapi.yaml](resources/provider.openapi.yaml).
47
+
48
+ ***implementation reqs***
49
+
50
+ - :ProviderClient: classifies every failure of :ProviderApi: through [resources/error-map.yaml](resources/error-map.yaml) and retries the transient ones under [resources/retry-policy.yaml](resources/retry-policy.yaml).
51
+ ```
52
+
53
+ The status-code lists move into `error-map.yaml`, which the renderer already reads to generate the exception hierarchy and the transient predicate. Coverage is unchanged and the classification now has one source of truth instead of two that can disagree — see *Transient vs. permanent exception classification* in `integrations.md`.
54
+
55
+ Note the section too. Classifying and retrying is mechanism, so it is an implementation req; what belongs in `***functional specs***` is only the part a caller observes — which exception each error category surfaces as.
56
+
57
+ ## Restating a schema the resource already carries
58
+
59
+ BAD:
60
+
61
+ ```plain
62
+ ***definitions***
63
+
64
+ - :ProviderApi: is the REST surface of the provider, defined by [resources/provider.openapi.yaml](resources/provider.openapi.yaml).
65
+
66
+ - :Customer: is a customer record returned by :ProviderApi:. It has an `id` (string), an `email` (string), a `created_at` (ISO 8601 timestamp), a `status` (one of `active`, `pending`, `churned`), an optional `phone` (string or null), and a `metadata` object of string keys to string values.
67
+ ```
68
+
69
+ GOOD:
70
+
71
+ ```plain
72
+ ***definitions***
73
+
74
+ - :ProviderApi: is the REST surface of the provider, defined by [resources/provider.openapi.yaml](resources/provider.openapi.yaml).
75
+
76
+ - :Customer: is a :ProviderApi: record, defined by [resources/provider.openapi.yaml](resources/provider.openapi.yaml) under `components.schemas.Customer`.
77
+ ```
78
+
79
+ Field lists rot the moment the provider adds a field, and the renderer generates types from the schema, not from the sentence. Keep in the definition only what the schema cannot say — a stability or uniqueness guarantee, a unit, a lifecycle rule.
80
+
81
+ ## Specifying the provider instead of the integration
82
+
83
+ BAD:
84
+
85
+ ```plain
86
+ ***definitions***
87
+
88
+ - :ProviderApi: is the REST surface of the provider, defined by [resources/provider.openapi.yaml](resources/provider.openapi.yaml).
89
+
90
+ - :Customer: is a :ProviderApi: record, at `components.schemas.Customer`.
91
+
92
+ ***functional specs***
93
+
94
+ - :ProviderApi: returns `200` with a JSON body when a request succeeds.
95
+
96
+ - :ProviderApi: returns `404` when the requested :Customer: does not exist.
97
+
98
+ - :ProviderApi: rate-limits clients and includes the remaining quota in the response headers.
99
+
100
+ - :ProviderApi: requires the `Authorization` header on every request.
101
+ ```
102
+
103
+ GOOD:
104
+
105
+ ```plain
106
+ ***definitions***
107
+
108
+ - :ProviderApi: is the REST surface of the provider, defined by [resources/provider.openapi.yaml](resources/provider.openapi.yaml).
109
+
110
+ - :ProviderClient: is the component every call to :ProviderApi: goes through.
111
+
112
+ - :Customer: is a :ProviderApi: record, at `components.schemas.Customer`.
113
+
114
+ - :CustomerId: identifies a :Customer: and is stable across updates.
115
+
116
+ - :CustomerNotFoundError: is raised when :ProviderApi: has no :Customer: for a :CustomerId:.
117
+
118
+ ***implementation reqs***
119
+
120
+ - :ProviderClient: authenticates to :ProviderApi: with the credential in the environment variable `PROVIDER_API_KEY`.
121
+
122
+ - :ProviderClient: raises :CustomerNotFoundError: when :ProviderApi: has no :Customer: for the requested :CustomerId:.
123
+
124
+ ***functional specs***
125
+
126
+ - :ProviderClient: fetches a :Customer: from :ProviderApi: by :CustomerId:.
127
+ ```
128
+
129
+ Every BAD bullet is true, and none of them is deleted — they are moved. The success shape, the `404`, and the `Authorization` requirement are what an OpenAPI file exists to state; the rate-limit model has its own conventional files. Provider behavior with no machine-readable home goes in `resources/<provider>-notes.md`. `integrations.md` carries the full artifact table.
130
+
131
+ What stays in the sections is only what gets built. A bullet stating provider behavior renders into nothing — the renderer cannot build the provider, and no conformance test can assert a sentence that makes no claim about the integration. State instead what the integration does when the provider behaves that way.
132
+
133
+ Note where the links hang: both artifacts attach to the :ProviderApi: concept, and everything else references the token. `linked-resources.md` requires each resource to be linked from exactly one place, so a second spec needing the contract cannot introduce a second, divergent reference to it.
134
+
135
+ ## One operation fragmented into several specs
136
+
137
+ Integration work invites this: the author walks through the request lifecycle — credential check, discovery call, request shape, paging — and each step becomes its own spec.
138
+
139
+ BAD — four specs, one operation:
140
+
141
+ ```plain
142
+ ***definitions***
143
+
144
+ - :ProviderApi: is the REST surface of the provider, defined by [resources/provider.openapi.yaml](resources/provider.openapi.yaml).
145
+
146
+ - :Record: is an account record of :ProviderApi:.
147
+
148
+ ***functional specs***
149
+
150
+ - A call to `fetch` fails before any request to :ProviderApi: is sent when `PROVIDER_API_KEY` is unset or empty.
151
+ - The failure names `PROVIDER_API_KEY`.
152
+
153
+ - A call to `fetch` reads the field catalogue from :ProviderApi: once, before requesting any :Record:.
154
+ - The catalogue yields the name of every field the account defines.
155
+ - A call to `fetch` whose catalogue read fails requests no :Record: items.
156
+
157
+ - A request for :Record: items from :ProviderApi: names every field in the catalogue and asks for at most 100 records.
158
+ - Archived :Record: items are not requested.
159
+
160
+ - A call to `fetch` requests pages until :ProviderApi: returns a response carrying no next-page cursor.
161
+ - Each page after the first is requested with the cursor from the previous response.
162
+ - The :Record: items of every page are carried forward together.
163
+ ```
164
+
165
+ GOOD — one spec, with the result stated first:
166
+
167
+ ```plain
168
+ ***definitions***
169
+
170
+ - :ProviderApi: is the REST surface of the provider, defined by [resources/provider.openapi.yaml](resources/provider.openapi.yaml).
171
+
172
+ - :Record: is an account record of :ProviderApi:.
173
+
174
+ - :RecordSource: is the component through which the integration reads :Record: items from :ProviderApi:.
175
+
176
+ ***functional specs***
177
+
178
+ - :RecordSource: `fetch()` returns every non-archived :Record: with every field :ProviderApi: defines, paging under [resources/pagination.yaml](resources/pagination.yaml).
179
+ - :RecordSource: reads the field catalogue once per `fetch()`.
180
+ - `fetch()` fails before any request when `PROVIDER_API_KEY` is unset or empty, naming it.
181
+ - A failed catalogue read fails `fetch()` before any :Record: is requested.
182
+ ```
183
+
184
+ Merging is not in tension with the complexity limit. Split a spec when it implies more than 200 changed lines of code (`analyze-if-func-spec-too-complex`, then `break-down-func-spec`); merge when several specs implement one operation that fits inside that budget. Fragment count is not a measure of size.
185
+
186
+ ## The auth flow narrated step by step
187
+
188
+ BAD:
189
+
190
+ ```plain
191
+ ***functional specs***
192
+
193
+ - :ProviderAuth: first reads the client id and the client secret, then builds a form-encoded body containing `grant_type=client_credentials`, the client id, and the client secret, then posts that body to the token endpoint, then parses the JSON response, then extracts the `access_token` and the `expires_in` fields, then stores the token in memory together with the moment it expires, and then attaches it to subsequent requests as a bearer token.
194
+
195
+ - Before each request, :ProviderAuth: compares the current time to the stored expiry and, if the token has expired or is about to expire, repeats the whole procedure described above to obtain a new token.
196
+ ```
197
+
198
+ GOOD:
199
+
200
+ ```plain
201
+ ***definitions***
202
+
203
+ - :ProviderApi: is the REST surface of the provider, defined by [resources/provider.openapi.yaml](resources/provider.openapi.yaml).
204
+
205
+ - :ProviderClient: is the component every call to :ProviderApi: goes through.
206
+
207
+ - :AccessToken: is the bearer credential :ProviderApi: accepts.
208
+
209
+ - :AuthTokenManager: manages the lifecycle of all :AccessToken:
210
+
211
+ ***implementation reqs***
212
+
213
+ - :ProviderAuth: obtains an :AccessToken: by the grant in [resources/provider.openapi.yaml](resources/provider.openapi.yaml) under `components.securitySchemes.oauth2`.
214
+
215
+ - :ProviderAuth: reads `PROVIDER_CLIENT_ID` and `PROVIDER_CLIENT_SECRET` from the environment.
216
+
217
+ - :AuthTokenManager: refreshes an :AccessToken: 60 seconds before it expires, and once on a `401` from :ProviderApi:.
218
+
219
+ - Concurrent requests finding the :AccessToken: expired cause one refresh.
220
+
221
+ - :ProviderClient: uses :AuthTokenManager: to cache and refresh :AccessToken:.
222
+ ```
223
+
224
+ The grant type, endpoint, body encoding, and response fields are in the security scheme. What
225
+ survives is the policy the scheme does not describe: the env-var names, the refresh margin, the
226
+ retry-once-on-401 rule, and the concurrency behavior.
227
+
228
+ Almost all of it is implementation reqs — acquisition, caching, and refresh are machinery a caller never sees. Not all of it, though, and the rewrite has to name a component to say the rest. :ProviderAuth: performs the grant and has no observable surface; :AuthTokenManager: is what consumes it and hands tokens to :ProviderClient:, so it is the concept a functional spec can take as its subject. Without that consumer the section is left with a subject-less bullet, which `func-specs.md` rules out — and that one spec is the only line here a live conformance test can assert.
229
+
230
+ ## Per-endpoint test reqs
231
+
232
+ BAD:
233
+
234
+ ```plain
235
+ ***definitions***
236
+
237
+ - :ProviderApi: is the REST surface of the provider, defined by [resources/provider.openapi.yaml](resources/provider.openapi.yaml).
238
+
239
+ ***test reqs***
240
+
241
+ - :ConformanceTests: for the customer list endpoint run against the live :ProviderApi: sandbox.
242
+
243
+ - :ConformanceTests: for the customer retrieval endpoint run against the live :ProviderApi: sandbox.
244
+
245
+ - :ConformanceTests: for the invoice list endpoint run against the live :ProviderApi: sandbox.
246
+
247
+ - :ConformanceTests: for the webhook endpoint run against the live :ProviderApi: sandbox.
248
+
249
+ - :ConformanceTests: for the customer list endpoint use the credentials from the environment.
250
+
251
+ - :ConformanceTests: for the invoice list endpoint use the credentials from the environment.
252
+ ```
253
+
254
+ GOOD:
255
+
256
+ ```plain
257
+ ***definitions***
258
+
259
+ - :ProviderApi: is the REST surface of the provider, defined by [resources/provider.openapi.yaml](resources/provider.openapi.yaml).
260
+
261
+ ***test reqs***
262
+
263
+ - :ConformanceTests: run against the live :ProviderApi: sandbox — no recorded fixtures and no mocking of provider calls.
264
+ - Credentials come from `PROVIDER_CLIENT_ID` and `PROVIDER_CLIENT_SECRET` in the environment.
265
+ - The `429` and forced-`5xx` paths mock that endpoint alone; every other path is live.
266
+ ```
267
+
268
+ A policy that holds for every endpoint is stated once. Enumerate an endpoint only where it departs
269
+ from the policy — here, the two paths that cannot be exercised live safely, which
270
+ `integrations.md` requires to be documented explicitly.