@klok-integration/cli 0.5.0 → 0.6.203

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/README.md CHANGED
@@ -45,7 +45,7 @@ Credentials are written to `~/.config/klok/credentials.json` with mode 0600.
45
45
  | `klok agent answer <taskId> --question <qid> --text "..."` | Answer a clarification and continue. |
46
46
  | `klok agent serve [--port 3091]` | Loopback MCP and A2A proxy for IDEs and other agents. |
47
47
  | `klok agents init` | Write the Klok agent instructions, the platform skills and your tenant's skills into a repository, with a Claude Code hook that keeps them fresh. |
48
- | `klok agents update [--check] [--global]` | Fetch what moved in the platform and tenant catalogs; `--check` exits 5 when something is outdated. |
48
+ | `klok agents update [--check] [--global]` | Fetch what moved in the platform and tenant catalogs; `--check` writes nothing and exits 5 when something is outdated. |
49
49
  | `klok agents status` | Per file and per catalog: current, outdated, missing or unreachable. |
50
50
  | `klok bead list\|show\|report` | Progress beads in the current repository. |
51
51
  | `klok docs read <doc-id>` | Print a documentation project's `llm.txt` from docs.klok.dev, read with your GitLab token and cached. |
@@ -53,6 +53,18 @@ The above copyright notice and this permission notice shall be included in all c
53
53
  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
54
54
  ```
55
55
 
56
+ ## @klok-integration/brokerclient@0.3.114
57
+
58
+ Licence: Apache-2.0 — https://git.klok.dev/klok/framework/klok-brokerclient-java/-/tree/main/typescript/klok-brokerclient-typescript
59
+
60
+ _No licence file shipped with the package; see the project homepage._
61
+
62
+ ## @klok-integration/datamodel-klok-project-documentation-v1@1.0.0-build.260930043552
63
+
64
+ Licence: Apache-2.0 — https://git.klok.dev/klok/datamodel/public/klok-project-documentation-v1
65
+
66
+ _No licence file shipped with the package; see the project homepage._
67
+
56
68
  ## @modelcontextprotocol/sdk@1.30.0
57
69
 
58
70
  Licence: MIT — https://modelcontextprotocol.io
@@ -201,6 +213,32 @@ The above copyright notice and this permission notice shall be included in all c
201
213
  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
202
214
  ```
203
215
 
216
+ ## avsc@5.7.9
217
+
218
+ Licence: MIT — https://github.com/mtth/avsc
219
+
220
+ ```
221
+ Copyright (c) 2015-2017, Matthieu Monsch.
222
+
223
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
224
+ this software and associated documentation files (the "Software"), to deal in
225
+ the Software without restriction, including without limitation the rights to
226
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
227
+ of the Software, and to permit persons to whom the Software is furnished to do
228
+ so, subject to the following conditions:
229
+
230
+ The above copyright notice and this permission notice shall be included in all
231
+ copies or substantial portions of the Software.
232
+
233
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
234
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
235
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
236
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
237
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
238
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
239
+ SOFTWARE.
240
+ ```
241
+
204
242
  ## bundle-name@4.1.0
205
243
 
206
244
  Licence: MIT
@@ -7,8 +7,9 @@ content arrives here. There are two kinds, and they are edited differently:
7
7
  * **The documentation project of a source repository** (`<tenant>/documentation/<group>/.../<repo>`, mirroring the
8
8
  source path) is output. KlokDocumentationEgress (`klok/integration/klok-documentation-egress`, KLK007) builds it
9
9
  from the `KlokProjectDocumentationV1` payload the source repository's `main` pipeline publishes, replaces
10
- everything under `docs/` on every run and commits the AI sections under `docs/_ai/`. Never edit it: a change made
11
- here is overwritten by the next run. There are exactly two places to change it:
10
+ everything under `docs/` on every run and commits the AI sections under `docs/_ai/`. It reads only tenant klok's
11
+ payloads today (klok/integration/klok-documentation-egress#2). Never edit it: a change made here is overwritten by
12
+ the next run. There are exactly two places to change it:
12
13
  * `klok-docs/` in the source repository, in a merge request there: the hand written pages, their order and
13
14
  chapter titles (`toc-manifest.json`), and where an AI section appears (`<!-- KLOK:GENERATED:START <id> -->` …
14
15
  `<!-- KLOK:GENERATED:END <id> -->`);
@@ -21,9 +21,10 @@ needs. Read `klok docs read klok-framework-klok-quarkus-extension` before writin
21
21
  * Datamodels are the type parameters of the pattern class and come from the catalog bundles; never hand write DTOs
22
22
  for something a datamodel project should own.
23
23
  * A datamodel used in more than one flow gets a **topic purpose** per flow (`<Model>_<purpose>`, set with
24
- `topicPurpose()` or `<RouteId>.topic.purpose`), agreed with the project on the other end and written in both
25
- projects' `klok-docs/`. Never consume a shared topic and discard the other flows' messages. The `klok-route` skill
26
- has the details.
24
+ `topicPurpose()` or `<RouteId>.topic.purpose`). The publisher sets it as the solution requires, the consumer
25
+ matches it, and both projects' `klok-docs/` write it down. A topic has at most one purpose; a reader is its
26
+ subscription (its route id), never a suffix. Never consume a shared topic and discard the other flows' messages.
27
+ The `klok-route` skill has the details.
27
28
  * Errors: throw `Klok4XXException` for caller errors (no retry) and `Klok5XXException` or plain exceptions for
28
29
  retryable failures; the framework retries, republishes to `_DLQ` and emits the failure event. Do not add
29
30
  `onException` blocks unless the route owns the failure (`failureHandled()`).
@@ -32,10 +33,12 @@ needs. Read `klok docs read klok-framework-klok-quarkus-extension` before writin
32
33
  * Tests: `@QuarkusTest` with the recording broker client factory; assert routes started and published messages,
33
34
  never talk to a real broker or OpenSearch in unit tests.
34
35
  * A route that serves several tenants (a shared platform service) consumes each tenant's own request topic,
35
- `<tenant>[_<env>]/common/<Model>`, from the subscription list klok.dev keeps in the klok ODL; it takes the tenant
36
- from the namespace it received on and rejects a message whose payload or properties name another tenant. It never
37
- picks an index, repository or account from a field in the message, and what it writes back for a tenant goes to
38
- klok's namespace, one topic per tenant.
36
+ `<tenant>[_<env>]/common/<Model>`; it takes the tenant from the namespace it received on and rejects a message
37
+ whose payload or properties name another tenant. It never picks an index, repository or account from a field in
38
+ the message, and what it writes back for a tenant goes to klok's namespace, one topic per tenant. Today every
39
+ platform service is configured for tenant klok only, and the broker client has no topic list that can grow while
40
+ a service runs (klok/framework/klok-brokerclient-java#31, klok/application/frontend-klok-dev#53); say so in the
41
+ design instead of assuming one.
39
42
  * The service connects to the broker with a client of its own (`KLOK_BROKER_AUTH_CLIENT_ID`), granted `publish` and
40
43
  `consume` on its own tenant's namespaces only; a topic of another tenant is a `consume` grant on that one topic,
41
44
  reached with `<RouteId>.topic.tenant-override`. Never reuse another service's credentials.
@@ -23,13 +23,13 @@ klok docs id [tenant/group/.../repo] # the doc id of a repository, this one by
23
23
 
24
24
  | Topic | Read |
25
25
  |---|---|
26
- | Platform principles, event driven design, naming, onboarding | `klok docs read klok-integration-strategy-and-tactics` |
26
+ | Platform principles, event driven design, naming | `klok docs read klok-integration-strategy-and-tactics` |
27
27
  | Route framework (patterns, config, logging, errors, topics, tasks) | `klok docs read klok-framework-klok-quarkus-extension` |
28
28
  | Naming conventions, message properties, CloudEvents | `klok docs read klok-framework-klok-common` |
29
29
  | Broker client | `klok docs read klok-framework-klok-brokerclient-java` |
30
30
  | Gradle plugin, versions, catalog | `klok docs read klok-framework-klok-gradle-plugin` |
31
31
  | CI templates, release process, Vault roles | `klok docs read klok-environment-build-ci-templates` |
32
- | Developer rules: identity and access, Keycloak, Vault paths, certificates, infrastructure as code | `klok docs read klok-framework-development-guide` |
32
+ | Developer rules: identity and access, Keycloak, Vault paths, certificates, infrastructure as code; onboarding a tenant | `klok docs read klok-framework-development-guide` |
33
33
  | How people use the platform: concepts, roles and access, workflow, operations | `klok docs read klok-user-guide` |
34
34
  | Infrastructure: stateful services, certificates, Ansible, Keycloak clients | `klok docs read klok-environment-klok-environment-setup` |
35
35
 
@@ -46,9 +46,10 @@ The `klok-docs` skill describes the lookup in detail. Cite the doc id you relied
46
46
 
47
47
  ## Tenant skills
48
48
 
49
- Next to the platform skills (`klok-docs`, `klok-route`, `klok-datamodel`, `klok-release`, `klok-worktree`) the
50
- skill directories hold your tenant's own skills, generated by KlokDocumentationEgress from every documentation
51
- publish in the tenant and served from the tenant's private catalog:
49
+ Next to the platform skills (`klok-docs`, `klok-solution`, `klok-route`, `klok-datamodel`, `klok-release`,
50
+ `klok-worktree`) the skill directories hold your tenant's own skills, generated by KlokDocumentationEgress from the
51
+ tenant's documentation publishes and served from the tenant's private catalog. The egress reads only tenant klok's
52
+ publishes today, so another tenant has no tenant skills yet:
52
53
 
53
54
  * `<tenant>-context` is the router: what exists in the tenant (repositories, systems, datamodels, topics,
54
55
  solutions), where it lives, and which skill to open for it. Start there. Its `data/` holds `projects.jsonl`,
@@ -79,7 +80,7 @@ it when a task touches the platform:
79
80
 
80
81
  * Klok knowledge is edited **only** in klok-integration-agent, `packages/cli/assets/agents/knowledge/`. Every other
81
82
  copy is generated and never edited: `knowledge/` next to an installed `KLOK-AGENTS.md`, the catalog's
82
- `agents/knowledge/`, `build/generated/klok-knowledge/`, `generated/klok-knowledge/` and the copies in images.
83
+ `agents/knowledge/`, `build/generated/klok-knowledge/` and the copies in images.
83
84
  * A prompt, persona or agent instruction that needs the glossary or the platform rules loads the knowledge block.
84
85
  It never restates it.
85
86
  * Text that matches a line of `retired-patterns.txt` teaches a rule the platform no longer has; fix it at its source.
@@ -144,6 +145,32 @@ step by step procedure; the contract every agent follows is:
144
145
  * Repository discovery and sync (`klok sync`, workspace generators) exclude `<root>/worktrees`; a task tree is
145
146
  never a canonical repository.
146
147
 
148
+ ## Commits follow Conventional Commits
149
+
150
+ Every commit in a Klok repository follows [Conventional Commits 1.0.0](https://www.conventionalcommits.org/en/v1.0.0/),
151
+ and so does every merge request title. The type says what kind of change it is, and the pipelines act on it: the
152
+ Klok templates choose which jobs run from the type of the change.
153
+
154
+ * The header is `<type>[(<scope>)][!]: <description>`: a lower case type, an optional scope naming the part that
155
+ changed, `!` for a breaking change, and a short imperative description (`feat(odl): add a scheduled query leg`,
156
+ `fix: take the tenant from the topic`, `docs(ci): explain the docs lane`). The body follows a blank line and says
157
+ why. Footers follow another blank line: `BREAKING CHANGE: <what breaks>`, `Refs: #12`, `Closes #7`.
158
+ * Types: `feat` a new capability, `fix` a bug fix, `docs` documentation only, `refactor`, `perf`, `test`, `build`
159
+ (build system and dependencies, the catalog copy included), `ci` (pipeline configuration), `style`, `revert`, and
160
+ `chore` for anything else that changes no behaviour.
161
+ * The merge request title is what `main` sees. Merge requests land as merge commits, and GitLab's default merge
162
+ commit message carries the title on its third line, where the template rules look. Give the merge request the
163
+ type of its whole change, not of its last commit.
164
+ * `docs` must be true. It covers `klok-docs/`, the Markdown files at the repository root and the bead files under
165
+ `.beads/`, nothing else. A `docs` merge request in a project built from a Klok template skips the build, the
166
+ image, the release and the deployment and only builds and publishes the documentation; `klok ci docs-only` fails
167
+ its pipeline when any other file changed. A merge request that mixes documentation with anything else takes the
168
+ type of the other change.
169
+ * A commit with no type, such as one made in the GitLab web editor, gets the full pipeline. So does a pipeline
170
+ started by hand (*Run pipeline*), by a schedule or through the API: that is how a change is built in full when it
171
+ was typed `docs` by mistake. The templates and the check are described on the *Change types* page of
172
+ `klok docs read klok-environment-build-ci-templates`.
173
+
147
174
  ## Versions come from the dependency catalog
148
175
 
149
176
  * Framework libraries, the gradle plugin and datamodels are released with timestamped versions from `main`
@@ -156,7 +183,7 @@ step by step procedure; the contract every agent follows is:
156
183
 
157
184
  ## Pipelines, secrets and infrastructure
158
185
 
159
- * After every push to git.klok.dev, check the pipeline (`glab api "projects/<id>/pipelines?per_page=3"`, job traces)
186
+ * After every push to git.klok.dev, check the pipeline (`glab api --hostname git.klok.dev "projects/<id>/pipelines?per_page=3"`, job traces)
160
187
  and fix failures before moving on. Publishing, image pushes, manifest updates and catalog updates only happen on
161
188
  `main`; merge request pipelines build and test.
162
189
  * Container images are built without privileges, always. Every image job extends `.klok_image_build` from
@@ -166,16 +193,18 @@ step by step procedure; the contract every agent follows is:
166
193
  `COPY` layers and sets owners with `COPY --chown`, never `RUN chown -R`, so that a build stores only what
167
194
  changed. Read the *Container images* page of `klok docs read klok-environment-build-ci-templates`
168
195
  before changing a Dockerfile or an image job.
169
- * Vault (`vault.ipaas.se`) is changed by a human only. Agents may read to diagnose and must then propose the exact
170
- command, including every existing field of the object.
171
- * New projects are created through klok.dev, or through the agent at https://agent.klok.dev authenticated as the
172
- tenant with the right to create projects in the domain. That is the supported path and it provisions what the
173
- project needs to build. Creating a project by hand is a legitimate fallback under *Git är sanningen*, but it is
174
- not the normal path: say up front what a human will still have to do, rather than letting it surface as a red
175
- pipeline. Vault needs nothing per project: the tenant CI role `<tenant>-ci` accepts every project under the
176
- tenant's root group. A tenant without it fails every job at `auth/jwt/login` with
177
- `role "<tenant>-ci" could not be found`; setting a tenant up is described in `klok-docs/ci/tenant-onboarding.md`
178
- of `klok/environment/build-ci-templates`.
196
+ * **Agents never write to Vault** (`vault.ipaas.se`). People write it, by hand or with the playbooks they run. The
197
+ only automated writer allowed is klok.dev's own provisioning identity, within its policy, and that identity does
198
+ not exist yet (klok/environment/klok-environment-setup#34). An agent may read to diagnose, and then proposes the
199
+ exact command, including every existing field of the object.
200
+ * New projects are created through klok.dev (the agent at https://agent.klok.dev asks klok.dev), or by hand, which
201
+ is a legitimate fallback under *Git är sanningen*. Neither path provisions a pipeline schedule, a deployment
202
+ manifest, a Vault Kubernetes role, a Keycloak client or broker grants yet: say up front what a human still has to
203
+ do (the `klok-release` skill lists it), rather than letting it surface as a red pipeline (tracked in
204
+ klok/application/frontend-klok-dev#41). Vault CI needs nothing
205
+ per project: the tenant CI role `<tenant>-ci` accepts every project under the tenant's root group. A tenant
206
+ without it fails every job at `auth/jwt/login` with `role "<tenant>-ci" could not be found`; setting a tenant up
207
+ is the *Onboarding a tenant* runbook in `klok docs read klok-framework-development-guide`.
179
208
  * Never push to `main` of an existing repository, never force push, never merge. Work on a branch and open a
180
209
  merge request that is ready for review; only a brand new repository gets its first commit on `main`. A draft
181
210
  merge request is not the default, it is what you open when the work is knowingly unfinished, and you say why.
@@ -188,6 +217,56 @@ step by step procedure; the contract every agent follows is:
188
217
  for Maven Central or npm can legitimately take half an hour. On 2026-09-29 an agent cancelled the Gradle
189
218
  plugin's release while it correctly waited for Maven Central, because it misread the time.
190
219
 
220
+ ## Manual steps are handed over as a guide
221
+
222
+ Whatever a task leaves for a human (merging merge requests, a Vault write, an Ansible run, a Keycloak or cluster
223
+ change, a decision) is handed over as a numbered step-by-step guide at the end of every answer that has such steps.
224
+ Someone who did not follow the work must be able to carry it out without working anything out:
225
+
226
+ * **What, why and how.** Each step says what to do and why, gives the exact commands in copyable code blocks (one
227
+ command per block, a dry run first where there is one, such as `ansible-playbook … --check --diff`), and says what
228
+ "done" looks like before the next step. Repeat the whole guide in the final answer, even when the steps were
229
+ mentioned earlier.
230
+ * **Commands run as pasted, from any directory.** The person pastes a command into a terminal that is not in your
231
+ worktree, so a command never relies on the working directory to tell it where it is: `glab` takes its host from
232
+ the git remote of the current directory and otherwise falls back to gitlab.com, where every Klok project is a 404,
233
+ so every `glab api` command carries `--hostname git.klok.dev`. Run a command from a neutral directory such as
234
+ `/tmp` before you hand it over.
235
+ * **Merging is a manual step.** List every merge request with its link and say whether the merge order matters and
236
+ why (the catalog before its consumers; klok-common, then the broker client, then the Quarkus extension; a
237
+ datamodel before the code that uses it), or say plainly that it does not.
238
+ * **Wait for the result, not for the merge.** Pipelines on `main` may run in parallel, so never assume that one job
239
+ runs at a time or that a merge means a release. When a step needs what an earlier pipeline produces (a datamodel
240
+ or framework version in the dependency catalog, an image in the deploy manifest, the agent files in the catalog, a
241
+ staged npm package, a published documentation project), the guide says to wait for that result and gives the
242
+ command that shows it. The catalog records every release as a commit on its `main`
243
+ (`chore(catalog): update <name> to the version published by <repository>@<sha>`,
244
+ `chore(catalog): update klok-agents to <version>`; releases before 2026-10-01 have the same titles without the
245
+ `chore(catalog): ` prefix and with a capital `Update`):
246
+
247
+ ```
248
+ glab api --hostname git.klok.dev "projects/klok%2Fframework%2Fklok-gradle-dependency-catalog/repository/commits?ref_name=main&per_page=10" | jq -r '.[].title'
249
+ ```
250
+ * **Hold points.** A plan that mixes agent work with such steps stops at numbered hold points, says exactly what the
251
+ human does there, and continues when they say so.
252
+
253
+ ## Design and maintenance start from the solution
254
+
255
+ Every task that designs, builds, changes or fixes a service, an integration, an API or a route starts from the
256
+ solution it belongs to, and new development and maintenance follow the same process (the `klok-solution` skill):
257
+
258
+ * Find the solution from the route id prefix and read its document in `<tenant>/integration-solutions` on `main`;
259
+ `klok solutions check` says whether the document still matches the code. A new solution gets its id and its
260
+ document before its first route.
261
+ * The document classifies every service (new project, new routes in an existing project, reused with a change,
262
+ reused as is, client change) and maps the legs: route ids, patterns, topics with purpose, datamodels.
263
+ * Integration logic is Java Klok routes. Doing integration work with the Python or TypeScript broker clients, or
264
+ inside an application or an external engine, is a `[TACTICAL]` choice whose motivation, cost and removal condition
265
+ the solution document states. An external engine (a GPU model server, a vendor service) stays a simple REST
266
+ service; priority, retries, claim checks and task state live in a Java leg.
267
+ * Every merge request that adds, changes or removes a leg brings the solution document to the new steady state, so
268
+ the registry describes the landscape that is built and deployed.
269
+
191
270
  ## Logic lives in the Klok tools, not in the pipelines
192
271
 
193
272
  * Klok functionality belongs in the Klok tools: **klok-cli** (`@klok/cli`), **the Klok Gradle plugin** and **the
@@ -212,8 +291,10 @@ step by step procedure; the contract every agent follows is:
212
291
  and the solution key.
213
292
  * Topics: `persistent://<tenant>[_<env>]/<domain>/<Model>[_<purpose>]`, request/reply
214
293
  `ReqRes_<Req>_<Res>_Request|_Response`, dead letters `<topic>_DLQ`. The datamodel major version is in the class
215
- name (`CustomerV1`). The purpose is a flow that producer and consumer agree on, so one datamodel serves several
216
- flows and each consumer reads only its own; it never contains `_`.
294
+ name (`CustomerV1`). The purpose is optional and the publisher's: the solution requires it so that one datamodel
295
+ serves several flows, and each consumer reads only the purpose of its flow. A topic has at most one purpose, and
296
+ the purpose never contains `_`. A reader is identified by its subscription, named after its route id, and never
297
+ appears in the topic name.
217
298
  * Context properties: `klok-correlation-id`, `klok-invocation-id`, `klok-parent-invocation-id`, `klok-task-id`,
218
299
  `klok-solution-id`, `klok-route-id`, `klok-tenant`, `klok-domain`, `klok-environment`; same spelling as HTTP
219
300
  header, Pulsar property and CloudEvents extension, `klok.`-prefixed as MDC key.
@@ -234,32 +315,45 @@ access, Keycloak broker clients or a service that serves several tenants.
234
315
  name and rejects a payload that claims another one.
235
316
  * **Write narrow, consume wide.** A tenant publishes only in its own namespace, `<tenant>[_<env>]/<domain>`; nobody
236
317
  holds `publish` in another tenant's namespace, not even on one topic. Reading elsewhere is a `consume` grant on a
237
- named topic, approved by the owner and recorded in Git in the owning tenant. A topic read across tenants carries
238
- its reader as the last purpose segment (`persistent://konvoj/hr/PersonnelScheduleExportV1_degerfors`).
239
- * **Platform services** (logs, the ODL, AI, mail, SMS, documentation) are shared klok workers that follow the same
240
- rule: a tenant's requests live in the tenant's namespace (`persistent://<tenant>[_<env>]/common/KlokLogEventV5`)
241
- and the worker is granted `consume` on exactly those topics, taking the tenant from the namespace; what the
242
- platform writes back lives in klok's namespace, one topic per tenant (`klok[_<env>]/<domain>/<Model>_<tenant>`).
243
- The worker subscribes to a list of topics kept in the klok ODL, never to a pattern. The ODL runs as one instance
244
- per tenant.
318
+ named topic, approved by the owner and recorded in Git in the owning tenant. The topic keeps its own name; the
319
+ reader is known by its subscription (its route id, `DEG012E1_...`). When the content belongs to one reader, the
320
+ publisher may choose that reader's tenant slug as the purpose
321
+ (`persistent://konvoj/hr/PersonnelScheduleExportV1_degerfors`); that is the publisher's choice, not a condition
322
+ for reading across tenants.
323
+ * **Platform services** (logs, the ODL, tasks, AI, documentation) are klok services, and today each one is
324
+ configured for tenant klok only (`klok.tenant=klok`; other tenants: klok/application/frontend-klok-dev#51). A
325
+ platform service that serves several tenants follows the same rule: a tenant's requests live in the tenant's
326
+ namespace (`persistent://<tenant>[_<env>]/common/KlokProjectDocumentationV1_prepared`), the service is granted
327
+ `consume` on exactly those topics and takes the tenant from the namespace, and what it writes back lives in klok's
328
+ namespace, one topic per tenant (`klok[_<env>]/<domain>/<Model>_<tenant>`).
245
329
  * **Identities:** one Keycloak client per running deployment, never shared, never used by a person; its Pulsar role
246
330
  is `service-account-<client id>`; one CI broker client per tenant; every broker client attaches the client scope
247
331
  `pulsar` (audience and the `nbf` claim Pulsar requires) instead of copying mappers.
248
332
  * **Grants:** `publish` and `consume` on the service's own domain and its tenant's `common`, as namespace grants;
249
333
  anything in another domain or tenant is `consume` on one topic, never `publish`, approved by its owner and
250
334
  recorded in Git in the owning tenant. Pulsar's `produce`/`consume` appear only inside a grant command.
251
- * **Who applies it:** Ansible (`klok/environment/klok-environment-setup`) for the platform and tenant klok's
252
- services, klok.dev for tenant projects. A grant made only in the broker's admin API is drift.
335
+ * **Who applies it:** Ansible (`klok/environment/klok-environment-setup`, `keycloak.yml` and `pulsar.yml`) applies
336
+ what those files declare: today the platform and tenant klok's services, plus any entries a person adds there for
337
+ another tenant's service (a playbook per tenant: klok/environment/klok-environment-setup#35). Broker access (a
338
+ tenant, a namespace, a topic created in advance, a grant) changes only through a merge request to those Pulsar
339
+ files, applied by Ansible run by a person. klok.dev provisions no broker clients or grants yet
340
+ (klok/application/frontend-klok-dev#51). A grant made only in the broker's admin API is drift.
253
341
 
254
342
  ## Documentation is part of every change
255
343
 
256
344
  Klok documentation has three levels, and a change is finished only when all three have been considered:
257
345
 
258
- * **The repository's own `klok-docs/`** (znai markdown, `toc-manifest.json`). On every `main` pipeline
259
- `./gradlew publishKlokDocs` publishes it, together with the scan of the code, as `KlokProjectDocumentationV1` on
260
- `persistent://<tenant>[_<env>]/common/KlokProjectDocumentationV1_prepared`. KlokDocumentationEgress
346
+ * **The repository's own `klok-docs/`** (znai markdown, `toc-manifest.json`). On every `main` pipeline it is
347
+ published as `KlokProjectDocumentationV1` on
348
+ `persistent://<tenant>[_<env>]/common/KlokProjectDocumentationV1_prepared`: by `./gradlew publishKlokDocs`, with
349
+ the scan of the code, in a Gradle project, and by `klok docs send` in any other (the Klok templates do this; a
350
+ project outside them includes `klok/environment/build-ci-templates:/klok-docs-send.yml`).
351
+ `klok docs send --dry-run` shows what would be sent. KlokDocumentationEgress
261
352
  (`klok/integration/klok-documentation-egress`, KLK007) builds the repository's documentation project from it. That
262
- documentation project is output, and nobody edits it. There are two places to change a project's documentation:
353
+ documentation project is output, and nobody edits it. This runs for tenant klok only: another tenant has no
354
+ documentation broker client, so its `klok_docs_publish` job fails and, being `allow_failure`, only warns
355
+ (klok/environment/klok-environment-setup#33), and the egress reads only klok's topic
356
+ (klok/integration/klok-documentation-egress#2). There are two places to change a project's documentation:
263
357
  `klok-docs/` in the source repository (including the `KLOK:GENERATED` markers that place AI sections in a page),
264
358
  and the documentation generation settings in klok.dev, which tenant admins own. A change that adds or alters a
265
359
  route, a datamodel, a configuration key, a CI job, a command or a decision updates `klok-docs/` in the same merge
@@ -290,6 +384,12 @@ Klok documentation has three levels, and a change is finished only when all thre
290
384
  sections that fill `KLOK:GENERATED` regions. They are never edited by hand. A wrong generated page is fixed in
291
385
  the egress or in the documentation generation settings in klok.dev.
292
386
 
387
+ **Documentation describes only what exists, in one place.** A cross-cutting topic lives in the framework
388
+ documentation projects under `klok/documentation/`, everything else in the owning project's `klok-docs/`; other
389
+ places link there instead of repeating it. A wrong statement is removed or corrected, never marked as outdated. A
390
+ wanted feature written as if it existed becomes an issue in the repository that would build it, and the text is
391
+ removed. Never use the present tense for something that is not built.
392
+
293
393
  **Prompts and agent instructions carry the same knowledge.** System prompts, section prompts, personas, tool
294
394
  descriptions, these agent instructions and the skills teach platform rules to a model, and they load the shared
295
395
  blocks from *Klok knowledge* above instead of restating them. The *Prompt Catalogue* in
@@ -324,8 +424,9 @@ The pipeline on `main` publishes the project's documentation payload (`KlokProje
324
424
  sources, configuration keys and the whole `klok-docs/` folder with a hash per file). KlokDocumentationEgress
325
425
  (`klok/integration/klok-documentation-egress`) turns it into the project's published documentation: your
326
426
  `klok-docs/` pages as written, with `KLOK:GENERATED` regions filled, next to generated pages for routes,
327
- datamodels and configuration. Nothing behind you updates the central platform documentation, so the steps above
328
- are the ones that matter.
427
+ datamodels and configuration. The egress reads only tenant klok's topic today
428
+ (klok/integration/klok-documentation-egress#2), so another tenant's projects get no generated documentation yet.
429
+ Nothing behind you updates the central platform documentation, so the steps above are the ones that matter.
329
430
 
330
431
  ## How to work
331
432
 
@@ -333,9 +434,12 @@ are the ones that matter.
333
434
  2. Create the task worktree and branch before the first edit (see *One task, one worktree, one branch*) and say
334
435
  which repository, worktree and branch you work in.
335
436
  3. Build and test locally with the repository's standard commands before pushing; keep every commit green.
336
- 4. Small, reviewable merge requests with a description that states what changed, why, and which documentation
337
- it affects.
437
+ 4. Small, reviewable merge requests with a Conventional Commits title (see *Commits follow Conventional Commits*)
438
+ and a description that states what changed, why, and which documentation it affects.
338
439
  5. A new project cloned from a template refreshes the catalog copy and the agent instructions before the first
339
- push: `./gradlew updateKlokLibsToml updateKlokAgents`. klok.dev does the same HTTP fetch when it creates the
340
- project. Re-run the tasks later to pick up a new catalog or a new skill set.
440
+ push: `./gradlew updateKlokLibsToml updateKlokAgents`. klok.dev fetches only the versions catalog when it creates
441
+ a project; the agent instructions come as committed in the template. Re-run the tasks later to pick up a new
442
+ catalog or a new skill set.
341
443
  6. Report outcomes faithfully: failing tests, skipped steps and open questions are stated, not hidden.
444
+ 7. End with the step-by-step guide of everything left for a human, merges included (see *Manual steps are handed
445
+ over as a guide*).
@@ -10,8 +10,8 @@ text of its own.
10
10
  * Klok knowledge is edited **only** in klok-integration-agent, `packages/cli/assets/agents/knowledge/`, through a
11
11
  merge request like any other change.
12
12
  * Everything else is a generated copy and is never edited: `knowledge/` next to an installed `KLOK-AGENTS.md`, the
13
- catalog's `agents/knowledge/` and `klok-agents.zip`, `build/generated/klok-knowledge/` in a Java build,
14
- `generated/klok-knowledge/` in klok.dev, and the copies inside images. A wrong copy is fixed here and refreshed.
13
+ catalog's `agents/knowledge/` and `klok-agents.zip`, `build/generated/klok-knowledge/` in a Java build, and the
14
+ copies inside images. A wrong copy is fixed here and refreshed.
15
15
  * A prompt that needs the glossary or the platform rules loads the block. It never restates it, and it never
16
16
  paraphrases a rule "to keep the prompt short".
17
17
  * A change here is also a prompt change: check the *Prompt Catalogue* in the framework development guide
@@ -21,8 +21,8 @@ text of its own.
21
21
 
22
22
  | File | What it is | Composed into |
23
23
  |---|---|---|
24
- | `glossary.md` | the Klok terms and their meanings, with the rule never to guess a term | every Kloker prompt that explains or documents Klok, the documentation egress's sections |
25
- | `platform-rules.md` | the current platform rules: principles, naming, route ids and legs, topics, datamodel classes, write narrow and consume wide, claim check, tasks, the ODL | the agent runner's doctrine, the role models, klok.dev's datamodel and planning helpers |
24
+ | `glossary.md` | the Klok terms and their meanings, with the rule never to guess a term | the role models `Kloker-Integration-Architect` and `Kloker-Integration-Developer` |
25
+ | `platform-rules.md` | the current platform rules: principles, naming, route ids and legs, topics, datamodel classes, write narrow and consume wide, claim check, tasks, the ODL | the agent runner's doctrine, the role models |
26
26
  | `kloker-identity.md` | who Kloker is, and the Unslop writing style | every request to the models `Kloker` and `Kloker-nothink` |
27
27
  | `roles/integration-architect.md`, `roles/integration-developer.md` | only what is specific to the role; the rules come from `platform-rules.md` | the role models `Kloker-Integration-Architect` and `Kloker-Integration-Developer` |
28
28
  | `retired-patterns.txt` | conventions the platform no longer has, one pattern per line with the reason | tests that fail when a prompt teaches a retired rule |
@@ -47,15 +47,14 @@ can add to the knowledge but not drop it.
47
47
  | Coding agents in a home folder | `klok agents init --global` writes `~/.klok/agents/knowledge/` | same |
48
48
  | The agent runner (agent.klok.dev) | the image copies `packages/cli/assets/agents` to `/app/assets/agents` (`KLOK_RUNNER_AGENT_ASSETS_DIR`) | `platform-rules.md` once, on first use, as the doctrine of every task; bead-implementation also seeds `knowledge/` into the sandbox |
49
49
  | The dependency catalog | the `update_klok_agents_catalog` job on `main` copies the whole asset tree to `agents/knowledge/` and into `klok-agents.zip` | — |
50
- | Java services: the Kloker gateway (klok-api-ai), klok-egress-ai, the documentation egress | planned: the Gradle plugin extracts `agents/knowledge/**` from the catalog into `build/generated/klok-knowledge/` on the classpath with a `klok-knowledge.properties` (catalog commit, `blocks=`), and `KlokKnowledge` in klok-common reads the blocks once at start | once, at start; never per request |
51
- | klok.dev | planned: a prebuild step extracts the catalog's `agents/knowledge/**` into `generated/klok-knowledge/`, read once at module load | once, at start |
50
+ | Java services | the Gradle plugin's `extractKlokKnowledge` bundles the catalog's `agents/knowledge/**` into `build/generated/klok-knowledge/` on the classpath with a `klok-knowledge.properties` (catalog commit, `blocks=`), and `KlokKnowledge` in klok-common reads the blocks. No service reads them that way yet: the Kloker gateway (klok-api-ai) composes the role models from its own copies under `/kloker/prompts/` (klok/integration/klok-api-ai#13) | once, at start; never per request |
52
51
 
53
52
  An installer replaces the files it ships wholesale and leaves any other file in the installed `knowledge/` alone. It
54
53
  does not remove a file this folder no longer ships, so a renamed or retired block is deleted from an installed copy
55
54
  by hand (or by the merge request that refreshes it).
56
55
 
57
56
  No consumer fetches the knowledge at run time, and none reads a database for it, so an AI request costs no extra I/O.
58
- Each build records the catalog commit it bundled.
57
+ A build that bundles it with `extractKlokKnowledge` records the catalog commit it bundled.
59
58
 
60
59
  ## How an update spreads
61
60
 
@@ -63,8 +62,8 @@ Each build records the catalog commit it bundled.
63
62
  `retired-patterns.txt`.
64
63
  2. After the merge, the `main` pipeline copies the assets into the dependency catalog (`agents/knowledge/`,
65
64
  `klok-agents.zip`).
66
- 3. Consumers that build from the catalog pick up the new text on their next build. Starting those builds
67
- automatically when this folder changes is planned (`klok knowledge propagate`).
65
+ 3. A project that builds with `extractKlokKnowledge` bundles the new text on its next build; nothing starts those
66
+ builds when this folder changes (klok/application/klok-integration-agent#37).
68
67
  4. The agent runner gets it with its next image, which is built from the same commit.
69
68
  5. Developers get it with the next klok-cli release (`klok agents init`), or right away from the catalog with
70
69
  `./gradlew updateKlokAgents`.
@@ -17,6 +17,7 @@ it as the source does.
17
17
  | Route, route id | One Camel route per Java class. Its id is `<TENANTSHORT><NNN><LEG><seq>_<Description>`, for example `KLK007T1_PublishKlokStandard`; the class name is the id |
18
18
  | Leg | The letter in a route id: I ingress (onramp, brings data in), P processing, E egress (offramp, delivers data out), A API, S sub route, T timer |
19
19
  | Solution | The business flow a set of routes implements; its id is the route id's prefix, for example `KLK007` |
20
+ | Solution document | The `README.md` of a solution in the tenant's `integration-solutions` registry: its services, legs and flows, kept true to what is built |
20
21
  | Correlation id | `klok-correlation-id`: follows one business flow across routes and messages |
21
22
  | Task | Long-running work tracked by the platform (`KlokTaskV1` events, visible in klok.dev and the task API), for example an `ai.chat` task that asks Kloker for text |
22
23
  | Datamodel | A versioned data contract; the major version is in the name (`CustomerV1`). Classes: public (A), common (B) and domain (C) are XSD in a Git project of their own with generated Java, Python and TypeScript; system (X) models one external system and may be plain Java classes. All four classes may travel on the broker |
@@ -61,6 +61,21 @@ documentation or the dependency catalog can give you.
61
61
  * documentation is mirrored under `<tenant>/documentation/<same path>`; a documentation id is
62
62
  `slug(<tenant>-<group>-...-<repo>)`.
63
63
 
64
+ ## Solutions
65
+
66
+ * Every route belongs to a solution, and every solution has a document: `README.md` and `solution.meta.json` in
67
+ `<tenant>/integration-solutions/solutions/<SOLUTIONID>_<slug>/`. A new solution gets its id and its document before
68
+ its first route; the id is taken from the code, never reused, and a leg and sequence (`KLK002A11`) names one route.
69
+ * The document classifies every service the solution needs: new project, new routes in an existing project, reused
70
+ with a change, reused as is, client change. It maps the legs (route id, project, pattern, topics with purpose,
71
+ datamodels) and links each project's `klok-docs/` for the details.
72
+ * Integration logic is Java Klok routes on the Quarkus extension. Integration work done with the Python or TypeScript
73
+ broker clients, or inside an application or an external engine, is `[TACTICAL]`. An external engine (a GPU model
74
+ server, a vendor service) stays a simple REST service; priority, retries, claim checks and task state live in a
75
+ Java leg.
76
+ * New development and maintenance are one process: a change to a leg brings the solution document to the new steady
77
+ state in the same change set, and `klok solutions check` compares the documents with the code on `main`.
78
+
64
79
  ## Topics and CloudEvents
65
80
 
66
81
  * `persistent://<tenant>[_<env>]/<domain>/<Model>[_<purpose>]`. Production has no environment suffix
@@ -68,6 +83,14 @@ documentation or the dependency catalog can give you.
68
83
  * `<Model>` is the datamodel's class name with its major version (`InvoiceV1`). The purpose has no underscore of its
69
84
  own and is set with `topicPurpose()` or `<RouteId>.topic.purpose`; the framework computes the whole topic name.
70
85
  Never write topic strings by hand.
86
+ * The purpose is optional, and it is the publisher's: the solution requires it so that one general datamodel serves
87
+ several flows, the publisher sets it and the reader consumes the purpose the publisher chose. A topic name has at
88
+ most one purpose, never a second suffix.
89
+ * A reader is identified by its subscription, whose name is its route id (`<RouteId>.subscription.name` overrides
90
+ it), so the reading tenant's short name is in it (`DEG012E1_...`). A reader never appears in the topic name.
91
+ When the content belongs to one reader, the publisher may choose that reader's tenant slug as the purpose
92
+ (`persistent://konvoj/hr/PersonnelScheduleExportV1_degerfors`); that is the publisher's choice, not a condition
93
+ for reading across tenants.
71
94
  * Request/reply: `ReqRes_<Request>_<Reply>[_<purpose>]_Request` and `_Response`. Dead letters: `<topic>_DLQ`.
72
95
  * One datamodel per topic. A breaking change is a new major version and therefore a new topic.
73
96
  * Every broker message is a CloudEvent in binary content mode: the attributes are message properties, the payload
@@ -106,12 +129,15 @@ whether a model may travel on a topic; it decides how widely the model is shared
106
129
  a payload or property that claims another tenant.
107
130
  * A tenant publishes only in its own namespace, `<tenant>[_<env>]/<domain>`. Nobody holds `publish` in another
108
131
  tenant's namespace. Reading elsewhere is a `consume` grant on one named topic, approved by the owner and recorded in
109
- Git in the owning tenant. A topic read across tenants carries its reader as the last purpose segment
110
- (`persistent://konvoj/hr/PersonnelScheduleExportV1_degerfors`).
111
- * Platform services (logs, the ODL, AI, mail, documentation) are shared klok workers under the same rule: a tenant's
112
- requests live in the tenant's namespace (`persistent://<tenant>[_<env>]/common/<Model>`), the worker is granted
113
- `consume` on exactly those topics, and what the platform writes back lives in klok's namespace, one topic per
114
- tenant (`persistent://klok[_<env>]/<domain>/<Model>_<tenant>`).
132
+ Git in the owning tenant. The topic keeps its own name; the reader is known by its subscription.
133
+ * Platform services (logs, the ODL, tasks, AI, documentation) are klok services, today configured for tenant klok
134
+ only. One that serves several tenants follows the same rule: a tenant's requests live in the tenant's namespace
135
+ (`persistent://<tenant>[_<env>]/common/<Model>`), the service is granted `consume` on exactly those topics, and
136
+ what the platform writes back lives in klok's namespace, one topic per tenant
137
+ (`persistent://klok[_<env>]/<domain>/<Model>_<tenant>`).
138
+ * Broker access (a tenant, a namespace, a topic created in advance, a grant) changes only through a merge request to
139
+ the Pulsar variables in `klok/environment/klok-environment-setup`, applied by Ansible run by a person. A grant made
140
+ only in the broker's admin API is drift.
115
141
  * One Keycloak client per running deployment, never shared and never used by a person. Broker rights are called
116
142
  `publish` and `consume`; *ingress* and *egress* say what a service is and are never permissions.
117
143
 
@@ -137,8 +163,8 @@ asynchronously through events, never inline in a request.
137
163
  The ODL stores any datamodel object per tenant, keyed by its type (the fully qualified Java class name) and an id,
138
164
  backed by OpenSearch (index `klok-odl-<tenant>-<slug(type)>`). Every write emits a change event
139
165
  `KlokOdlObjectEventV1_<Type>`. A route stores its body with `persistToOdl()` or `odlPersist(...)` (fire and forget)
140
- and reads, searches and writes synchronously through `odl()` (`get`, `search`, `persistSync`, `delete`). The ODL
141
- topics are in the `common` domain. Tenant-tunable behaviour of a service (prompts, sections, languages) is a Klok
166
+ and reads, searches and writes synchronously through `odl()` (`get`, `search`, `persistSync`, `delete`). The ODL's
167
+ request topics are in the `common` domain; a change event goes to the domain of the object. Tenant-tunable behaviour of a service (prompts, sections, languages) is a Klok
142
168
  standard shipped with the service plus a tenant override stored in the ODL.
143
169
 
144
170
  ## Versions and delivery
@@ -148,5 +174,9 @@ standard shipped with the service plus a tenant override stored in the ODL.
148
174
  `./gradlew updateKlokLibsToml` refreshes `gradle/klok-libs.versions.toml`.
149
175
  * Work on a branch and open a merge request; never push to `main` of an existing repository. Publishing, images,
150
176
  manifests and catalog updates happen on `main` only.
177
+ * What only a person may do (merge a merge request, write Vault, run Ansible, change Keycloak) is handed over as a
178
+ numbered guide with the exact commands, the merge order and why. The commands run as pasted from any directory,
179
+ so a `glab api` command names the host (`--hostname git.klok.dev`). A step that needs an earlier release waits for
180
+ its result (the version in the dependency catalog), not for the merge: `main` pipelines may run in parallel.
151
181
  * Every change that adds or alters a route, a datamodel, a configuration key or a decision updates the repository's
152
182
  `klok-docs/` in the same merge request.
@@ -23,6 +23,10 @@ pulsar:// # topics are persistent://<tenant>[_<env>]/<domain>/<Model>[_<purpose>
23
23
  <tenant>_<env>/ # the environment suffix is optional; production has none: <tenant>[_<env>]
24
24
  topic-dlq # dead letters are <topic>_DLQ
25
25
  (?i)owner hosts the topics # write narrow, consume wide: the writer's namespace holds the topic
26
+ # A reader is never part of the topic name (2026-10-06): one optional purpose, set by the publisher.
27
+ (?i)last purpose segment # a topic has one optional purpose set by the publisher; a reader is its subscription (route id)
28
+ (?i)_<reader> # a topic has one optional purpose set by the publisher; a reader is its subscription (route id)
29
+ (?i)\breader (as|after) the (last )?purpose # a topic has one optional purpose set by the publisher; a reader is its subscription
26
30
  #
27
31
  # Framework classes that do not exist in the Quarkus extension 2.0 and later.
28
32
  KlokSoapApi # removed in 2.0; SOAP is a KlokCustomRoute with camel-cxf
@@ -57,6 +57,9 @@ visible.
57
57
  * **Egress** adapts the broker to an external protocol, transforms into the target's format, and owns retries,
58
58
  backpressure and the delivery guarantee.
59
59
 
60
+ Every design names its solution and lists its services by kind and its legs, as the platform rules' *Solutions*
61
+ describe; a design that moves integration logic out of Java legs says why, as a tactic.
62
+
60
63
  A design that says "one service that polls CRM, maps and posts to Visma" hides three legs. Split it and name each
61
64
  leg. Onramps and offramps stay thin (protocol and format); business enrichment, splitting a nightly file and
62
65
  aggregating for a chatty vendor belong in processing or in a framework capability.
@@ -166,7 +166,12 @@ custom route for a plain mapping, a plain protocol bridge or an API.
166
166
 
167
167
  `KlokSubRoute` splits a route into reusable parts through `configureSubroute(ProcessorDefinition<?>)`; its URI is
168
168
  `direct:<fully qualified class name>`. A sub route that only transforms and is called from several onramps is
169
- right; a sub route that opens an SFTP connection is in the wrong place.
169
+ right; a sub route that opens an SFTP connection is in the wrong place. A sub route has no error handling of its
170
+ own (klok-quarkus-extension 3.4 and later): every exception it throws reaches the route that called it, and that
171
+ route's retries, dead letter queue and reply decide what happens to the message. In a sub route, catch only what is
172
+ not an error (a lookup that finds nothing when "absent" is a valid answer) and handle it, or catch an error to throw
173
+ it again as a more specific `Klok4XXException` / `Klok5XXException` or with a better message. Never give a sub route
174
+ its own `retry.*`, `dlq.*` or `failure.*` keys; configure its caller.
170
175
 
171
176
  ## Channel adapters and APIs
172
177
 
@@ -194,7 +199,8 @@ blindly. `klok-mock-*` headers are for test and staging and are stripped in prod
194
199
  2. **Reuse.** Before a new model, search the existing class B and C models, topics and routes, in klok.dev's
195
200
  context and on docs.klok.dev. A new topic only for a new contract.
196
201
  3. **Solution design.** Solution id from the klok.dev registry, the legs and their route ids, the models per leg,
197
- `[TACTICAL]` for any deviation.
202
+ `[TACTICAL]` for any deviation, written in the solution document before the code and kept current by every change
203
+ of a leg (platform rules, *Solutions*).
198
204
  4. **Design per leg.** Base class, exchange pattern, URI, authentication (a Vault path reference, never a secret),
199
205
  the `transformBody` sketch, the CloudEvent type, the failure matrix (timeout: retry; invalid: dead letter;
200
206
  authentication: alert and dead letter), the tests.
@@ -215,6 +221,9 @@ blindly. `klok-mock-*` headers are for test and staging and are stripped in prod
215
221
  routes start and what they publish; never a real broker or OpenSearch in unit tests.
216
222
  * Delivery: the `main` pipeline builds the image, updates the deployment manifest that Argo CD applies, and
217
223
  publishes the documentation. Never commit hand-made production YAML beside it.
224
+ * Commit messages and merge request titles follow Conventional Commits (`feat(odl): …`, `fix: …`, `docs: …`). The
225
+ pipeline acts on the type: a `docs` merge request only publishes the documentation, so it is `docs` only when
226
+ nothing but `klok-docs/` and root Markdown files changed.
218
227
  * Troubleshooting: follow `klok-correlation-id` from ingress through processing to egress; operations follow the
219
228
  failure events and the dead letter topics, not pod logs.
220
229