@klok-integration/cli 0.7.248 → 0.7.279

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
@@ -25,8 +25,8 @@ variable: `KLOK_AGENT_URL`, `KLOK_TENANT`, `KLOK_DOMAIN`, `KLOK_DEV_URL`, `KLOK_
25
25
  `KLOK_KEYCLOAK_CLIENT_ID`, `KLOK_GIT_URL`, `KLOK_GIT_LOCAL_WORKSPACE`, `KLOK_GIT_APIKEY`. Run
26
26
  `klok config list` to see what is set and where it comes from.
27
27
 
28
- The GitLab token can be stored with `klok config set klokApiKey` (a hidden prompt, so it stays out of the shell
29
- history). It is shown as `****` everywhere unless you run `klok config get klokApiKey --reveal`, and the file
28
+ The GitLab token can be stored with `klok config set klokApiKey`: it asks `Enter API key:` and shows a `*` for
29
+ each character, so the token stays off the screen and out of the shell history. It is shown as `****` everywhere unless you run `klok config get klokApiKey --reveal`, and the file
30
30
  is kept at mode 0600.
31
31
 
32
32
  Credentials are written to `~/.config/klok/credentials.json` with mode 0600.
@@ -53,7 +53,9 @@ Credentials are written to `~/.config/klok/credentials.json` with mode 0600.
53
53
  | `klok docs id [path]` | The doc id of a repository path. |
54
54
  | `klok docs publish` | Build this repository's znai documentation and upload it to the docs portal. |
55
55
  | `klok docs build` | Build this repository's `klok-docs/` with znai the way its documentation project will, uploading nothing. |
56
- | `klok solutions validate` | Check the descriptors of an integration solution registry against the descriptor contract. |
56
+ | `klok solutions next` | The next free solution id of the tenant, from the registry, its open merge requests and the route classes on every branch of the workspace. |
57
+ | `klok solutions new "<name>"` | Reserve the next solution id by a merge request in the registry, from a task worktree. |
58
+ | `klok solutions validate` | Check the descriptors of an integration solution registry against the descriptor contract and, in a merge request, that no solution is removed and no solution id reused or taken twice. |
57
59
  | `klok ci env` | Show what klok-cli detected about the CI job it runs in. |
58
60
  | `klok fleet status\|rebuild` | Every deployed service: what it was built from, whether that is the latest commit and the catalog's broker client, and which clients the broker sees; rebuild the stale ones one pipeline at a time. |
59
61
  | `klok manifest update` | Set a container's image in a deploy manifest of your GitOps repository, then commit and push. |
@@ -8,19 +8,25 @@ dependency catalog. Read `klok docs read klok-framework-klok-common` for naming
8
8
  ### Rules
9
9
 
10
10
  * One root element `<Name>_vN` that references every complex type; a single `xs:` prefix; `targetNamespace` and
11
- `xmlns:tns` set; `elementFormDefault="qualified"`. Validate with the XSD validation of the CI template before
12
- pushing (`python3 scripts/validate-xsd.py` from build-ci-templates, or the pipeline's `generate_avro_from_xsd`).
11
+ `xmlns:tns` set; `elementFormDefault="qualified"`. Validate with `klok datamodel validate` before pushing; it
12
+ applies the rules of the pipeline's `generate_avro_from_xsd`.
13
13
  * One name, in kebab case, from the repository down: `customer-v1` is the repository, the XSD file
14
14
  (`customer-v1.xsd`), the Maven artifact and the npm and Python packages, and `customer_v1` ends the Java package.
15
15
  Inside the XSD the model keeps its PascalCase form: root element `Customer_v1`, root type and class `CustomerV1`.
16
16
  * The major version is part of the model name; a breaking change is a new major version and a new topic, never a
17
- silent change of an existing one.
18
- * `meta.json` uses the flat form with `targetNamespace`; it drives the generated project's group, name and
19
- documentation.
20
- * Consumers reference the artifact only through the catalog alias (`klokLibs.<key>`), never by literal version.
17
+ silent change of an existing one. The pipeline's `klok_datamodel_compatibility` fails a merge request whose change
18
+ breaks a service built with any earlier release, in either direction: read its `breaking:` lines, then make the
19
+ change compatible or create the next major version (the `klok-datamodel` skill).
20
+ * `klok-datamodel-project.json` at the root is the model's metadata (name, description, class, model type,
21
+ `targetNamespace`, template version), rendered from `klok/environment/project-template-datamodel`; klok.dev and
22
+ the documentation payload read it. The pipeline takes the artifact and the Java package from the repository path
23
+ and the XSD file name, never from metadata.
24
+ * Consumers reference the artifact only through the catalog alias (`klokLibs.<key>` for tenant klok's models,
25
+ `tenantLibs.<key>` for another tenant's), never by literal version.
21
26
  * Datamodel documentation lives in `klok-docs/` (purpose, fields that need explanation, producers and consumers,
22
- design decisions). The datamodel pipeline's `klok_docs_publish` job sends it with the XSD to docs.klok.dev, where
23
- the model is documented from the XSD (a structure page, then AI sections from the complex types up) under
27
+ design decisions); `klok-docs/README.md` says which page holds which, and is never published. The datamodel
28
+ pipeline's `klok_docs_publish` job sends it with the XSD to docs.klok.dev, where the model is documented from the
29
+ XSD (a structure page, then AI sections from the complex types up) under
24
30
  `<tenant>/documentation/datamodel/<same path>`. Put `xs:documentation` on types and fields; that is what the
25
31
  generated text is written from.
26
32
  * The repository's path is its class: `public` (A), `common` (B), `domain/<d>` (C) or `system/<s>` (X). The wider
@@ -13,8 +13,8 @@ content arrives here. There are two kinds, and they are edited differently:
13
13
  * `klok-docs/` in the source repository, in a merge request there: the hand written pages, their order and
14
14
  chapter titles (`toc-manifest.json`), and where an AI section appears (`<!-- KLOK:GENERATED:START <id> -->` …
15
15
  `<!-- KLOK:GENERATED:END <id> -->`);
16
- * what is generated and how (chapters, sections, prompts, languages, model): klok.dev, Tenant Administration,
17
- Documentation generation, which tenant admins change.
16
+ * what is generated and how (chapters, sections, prompts, languages, model): klok.dev, Run → Documentation,
17
+ tab Generation, which tenant admins change.
18
18
  * **A standalone documentation project** (directly under `<tenant>/documentation/`, such as
19
19
  `klok-integration-strategy-and-tactics`) is written by hand in this repository, and the egress never touches it.
20
20
  Keep `docs/toc` and `docs/toc-manifest.json` in sync when adding a page.
@@ -24,6 +24,10 @@ content arrives here. There are two kinds, and they are edited differently:
24
24
  * The doc id must start with the tenant prefix (`<tenant>-...`); the pipeline refuses others. Do not set the
25
25
  tenant, the Vault path or upload tokens in the project CI. This project's `.gitlab-ci.yml` is the include of
26
26
  `klok/environment/build-ci-templates:/klok-docs-publish.yml` and nothing else; the job is `klok docs publish`.
27
+ * `klok-documented-project.json` at the root names the source project and the doc ids, and KlokDocumentationEgress
28
+ writes it. `"state": "retired"` there makes `klok docs publish` take every doc id it names (`docId`,
29
+ `previousDocIds`) off the portal instead of uploading; `--dry-run` lists them. Do not edit it to keep a project
30
+ published: its state follows the source project.
27
31
  * Visibility follows `meta.json` `scope` (`global`, `tenant`, `domain`, `system`); a page that must not be seen by
28
32
  another domain belongs in a project with that scope, not behind a note.
29
33
  * Preview locally with `znai preview --source=docs`, and check what a publish would do with
@@ -18,7 +18,8 @@ needs. Read `klok docs read klok-framework-klok-quarkus-extension` before writin
18
18
 
19
19
  ### Rules
20
20
 
21
- * Route class name = route id `<TENANTSHORT><NNN><LEG><seq>_<Description>`; one solution id per business flow.
21
+ * Route class name = route id `<TENANTSHORT><NNN><LEG><seq>_<Description>`; one solution id per business flow, and
22
+ its prefix is a solution registered in `<tenant>/integration-solutions` (a new one: `klok solutions new`).
22
23
  Configuration keys are `<RouteId>.<key>` with fallback to `<SolutionId>.<key>` and `klok.route-defaults.<key>`.
23
24
  * Datamodels are the type parameters of the pattern class and come from the catalog bundles; never hand write DTOs
24
25
  for something a datamodel project should own.
@@ -50,7 +51,9 @@ needs. Read `klok docs read klok-framework-klok-quarkus-extension` before writin
50
51
  reached with `<RouteId>.topic.tenant-override`. Never reuse another service's credentials.
51
52
  * `application.properties` keeps every `klok.*` key explicit; secrets come from environment variables that the
52
53
  Vault env secret provides in Kubernetes (`KLOK_BROKER_AUTH_CLIENT_ID`, ...).
53
- * Update `klok-docs/` (overview, design decisions, routes, operations) in the same merge request as the change.
54
+ * Update `klok-docs/` (overview, design decisions, routes, usage, operations) in the same merge request as the
55
+ change. `klok-docs/README.md` says which page answers which question and how the folder reaches docs.klok.dev;
56
+ it is the folder's guide and is never published.
54
57
  * On `main` the pipeline builds the image, updates the deployment manifest and publishes the documentation payload
55
58
  that KlokDocumentationEgress turns into the documentation project; check it.
56
59
  * The `Dockerfile` copies `build/quarkus-app/lib/` in a `COPY` of its own before `quarkus-run.jar`, `quarkus/`
@@ -14,7 +14,8 @@ what went wrong (no token, no network); say so instead of guessing.
14
14
 
15
15
  On a workstation, `klok sync pull` then brings the workspace current before you read it: it fast-forwards every
16
16
  canonical checkout under the workspace root, never merges or resets one, and skips `<root>/worktrees`. It is the one
17
- sanctioned update of a canonical checkout. A service never clones a repository just to read it: klok.dev and the agent
17
+ sanctioned update of a canonical checkout. A checkout whose GitLab project moved, was archived or is gone is reported
18
+ with the commands that fix it, and those are the person's to run. A service never clones a repository just to read it: klok.dev and the agent
18
19
  read through the GitLab API, docs.klok.dev and the integration context. The `klok-cli` skill says which klok command
19
20
  does what.
20
21
 
@@ -300,6 +301,11 @@ solution it belongs to, and new development and maintenance follow the same proc
300
301
  * Route ids: `<TENANTSHORT><NNN><LEG><seq>_<Description>` (`KLK002I1_OdlScheduledQuery`); legs `I` ingress,
301
302
  `P` processing, `E` egress, `A` API, `S` sub route, `T` timer. The class name is the route id, the config prefix
302
303
  and the solution key.
304
+ * `<TENANTSHORT>` is the tenant's `shortname` in tenants.yml of `klok/environment/klok-environment-setup`: the first
305
+ three consonants of the tenant slug in upper case (`klok` KLK, `degerfors` DGR, `konvoj` KNV), decided by the
306
+ platform. A solution id `<TENANTSHORT><NNN>` is allocated by the merge request that adds its folder to
307
+ `<tenant>/integration-solutions` (`klok solutions new`) and never reused; a solution that stops running is retired
308
+ with `"status": "retired"`.
303
309
  * Topics: `persistent://<tenant>[_<env>]/<domain>/<Model>[_<purpose>]`, request/reply
304
310
  `ReqRes_<Req>_<Res>_Request|_Response`, dead letters `<topic>_DLQ`. The datamodel major version is in the class
305
311
  name (`CustomerV1`). The purpose is optional and the publisher's: the solution requires it so that one datamodel
@@ -327,7 +333,7 @@ access, Keycloak broker clients or a service that serves several tenants.
327
333
  * **Write narrow, consume wide.** A tenant publishes only in its own namespace, `<tenant>[_<env>]/<domain>`; nobody
328
334
  holds `publish` in another tenant's namespace, not even on one topic. Reading elsewhere is a `consume` grant on a
329
335
  named topic, approved by the owner and recorded in Git in the owning tenant. The topic keeps its own name; the
330
- reader is known by its subscription (its route id, `DEG012E1_...`). When the content belongs to one reader, the
336
+ reader is known by its subscription (its route id, `DGR012E1_...`). When the content belongs to one reader, the
331
337
  publisher may choose that reader's tenant slug as the purpose
332
338
  (`persistent://konvoj/hr/PersonnelScheduleExportV1_degerfors`); that is the publisher's choice, not a condition
333
339
  for reading across tenants.
@@ -392,6 +398,9 @@ Klok documentation has three levels, and a change is finished only when all thre
392
398
  Write both **as the work happens**, not at the end of the branch. By the end nobody remembers why a choice was
393
399
  made, and a guide written from memory describes the product somebody meant to build.
394
400
 
401
+ `klok-docs/README.md` is the folder's own guide for the people and agents who write it: a project made from a
402
+ Klok template has one that says which page answers which question. It is neither published nor read by Kloker.
403
+
395
404
  Keep `klok-docs/` as the single copy. Do not mirror a page into a second `docs/` folder: two copies of one page
396
405
  are two pages that quietly stop agreeing. `docs/` may hold what is genuinely not product documentation, such as
397
406
  a strategy document that predates the code.
@@ -16,7 +16,8 @@ it as the source does.
16
16
  | Domain | A business area inside a tenant. `common` is shared by the tenant's domains, `public` is open to anyone |
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
- | Solution | The business flow a set of routes implements; its id is the route id's prefix, for example `KLK007` |
19
+ | Tenant short name | `TENANTSHORT`, three upper case letters that start every solution and route id of a tenant: the first three consonants of the tenant's name (`KLK` for klok, `DGR` for degerfors), decided by the platform |
20
+ | Solution | The business flow a set of routes implements; its id is the route id's prefix, for example `KLK007`. The id is allocated once, by a merge request in the tenant's `integration-solutions` registry, and never reused; a solution is reserved, active or retired |
20
21
  | 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 |
21
22
  | Correlation id | `klok-correlation-id`: follows one business flow across routes and messages |
22
23
  | 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 |
@@ -29,9 +29,11 @@ documentation or the dependency catalog can give you.
29
29
  ## Routes and naming
30
30
 
31
31
  * One Camel route per Java class. The route id is the class name, the configuration prefix and the solution key:
32
- `<TENANTSHORT><NNN><LEG><seq>_<Description>` (`KLK002I1_OdlScheduledQuery`, `ACME001E1_FortnoxInvoiceOfframp`).
33
- * `TENANTSHORT`: the tenant short name, upper case. `NNN`: the solution number from the klok.dev registry, at
34
- least three digits. The solution id is `<TENANTSHORT><NNN>` (`KLK002`).
32
+ `<TENANTSHORT><NNN><LEG><seq>_<Description>` (`KLK002I1_OdlScheduledQuery`, `ACM001E1_FortnoxInvoiceOfframp`).
33
+ * `TENANTSHORT`: the tenant's short name, three upper case letters: the first three consonants of the tenant
34
+ slug (`klok` KLK, `degerfors` DGR, `konvoj` KNV), decided by the platform and recorded as `shortname` in
35
+ tenants.yml of `klok/environment/klok-environment-setup`. `NNN`: the solution number, three digits; 900–999
36
+ are the framework's. The solution id is `<TENANTSHORT><NNN>` (`KLK002`).
35
37
  * Legs: `I` ingress, `P` processing, `E` egress, `A` API (REST, SOAP, MCP), `S` sub route or custom route,
36
38
  `T` timer. `seq` numbers the routes of one leg in a solution.
37
39
  * Every route extends exactly one pattern base class; never write a bare Camel `RouteBuilder`:
@@ -65,7 +67,10 @@ documentation or the dependency catalog can give you.
65
67
 
66
68
  * Every route belongs to a solution, and every solution has a document: `README.md` and `solution.meta.json` in
67
69
  `<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.
70
+ its first route. The id is allocated by the merge request that adds the folder (`klok solutions new`; `klok
71
+ solutions next` shows the next free number and why), and a number is never reused, not even a removed
72
+ solution's. A solution is `reserved`, `active` or `retired` (`status` in `solution.meta.json`); one that stops
73
+ running is retired, not deleted. A leg and sequence (`KLK002A11`) names one route.
69
74
  * The document classifies every service the solution needs: new project, new routes in an existing project, reused
70
75
  with a change, reused as is, client change. It maps the legs (route id, project, pattern, topics with purpose,
71
76
  datamodels) and links each project's `klok-docs/` for the details.
@@ -87,7 +92,7 @@ documentation or the dependency catalog can give you.
87
92
  several flows, the publisher sets it and the reader consumes the purpose the publisher chose. A topic name has at
88
93
  most one purpose, never a second suffix.
89
94
  * 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.
95
+ it), so the reading tenant's short name is in it (`DGR012E1_...`). A reader never appears in the topic name.
91
96
  When the content belongs to one reader, the publisher may choose that reader's tenant slug as the purpose
92
97
  (`persistent://konvoj/hr/PersonnelScheduleExportV1_degerfors`); that is the publisher's choice, not a condition
93
98
  for reading across tenants.
@@ -136,9 +141,11 @@ whether a model may travel on a topic; it decides how widely the model is shared
136
141
  * The major version is part of the name: repository `<model>-v<n>` in kebab case, XSD root element `<Model>_v<n>`,
137
142
  class and topic `<Model>V<n>`. A compatible change (a new optional field) stays in the major version; consumers
138
143
  ignore unknown fields. A breaking change (remove, rename or retype a field, change its meaning, make an optional
139
- field required) is a new major version in a new repository, run in parallel with the old one, bridged by
140
- `KlokSchemaUpgradeBridge` / `KlokSchemaDowngradeBridge` while readers move. A published version is never edited
141
- into a new shape.
144
+ field required, add or remove an enumeration value) is a new major version in a new repository, run in parallel
145
+ with the old one, bridged by `KlokSchemaUpgradeBridge` / `KlokSchemaDowngradeBridge` while readers move. A
146
+ published version is never edited into a new shape. The datamodel pipeline enforces it: every merge request is
147
+ compared with every earlier release of the datamodel, both ways, because producers and consumers deploy in any
148
+ order, and a change that breaks one fails (`klokDatamodelCompatibilityCheck`).
142
149
  * A, B and C (and X with its own repository) generate Java, Python and TypeScript classes that are compatible on the
143
150
  wire, and their versions are written into the dependency catalog.
144
151
 
@@ -17,6 +17,12 @@
17
17
  # Route ids are <TENANTSHORT><NNN><LEG><seq>_<Description>, legs I P E A S T.
18
18
  \bS<(NNNN|ID|SolutionID|Solution|NNN)> # old S-prefixed integration ids; use <TENANTSHORT><NNN><LEG><seq>_<Description>
19
19
  \bS[0-9]{4}[IEP][0-9] # old S-prefixed route id such as S0001I1; use KLK002I1_... style ids
20
+ # Solution ids are allocated by a merge request in <tenant>/integration-solutions and never reused (2026-10-08); the
21
+ # tenant short name is the first three consonants of the tenant slug, shortname in tenants.yml.
22
+ (?i)\bsolution (number|id)s?\s+(is\s+|are\s+)?(taken\s+)?from\s+the\s+(klok\.dev|portal|klok\.dev\s+solution|portal\s+solution)\s+registry # a solution id is allocated by a merge request in <tenant>/integration-solutions (klok solutions new), never reused
23
+ (?i)\bid is taken from the code # a solution id is allocated by a merge request in <tenant>/integration-solutions (klok solutions new); the code on every branch only counts as used
24
+ (?i)\bfirst\s+(three|3)\s+letters\s+of\s+the\s+tenant # the tenant short name is the first three consonants of the tenant slug, its shortname in tenants.yml
25
+ (?i)\bshort\s+(name|code)s?\s+(is|are)\s+the\s+(tenant'?s?\s+)?(name'?s?\s+|slug'?s?\s+)?first\s+(three|3)\s+letters # the tenant short name is the first three consonants of the tenant slug, its shortname in tenants.yml
20
26
  #
21
27
  # Topics are persistent://<tenant>[_<env>]/<domain>/<Model>[_<purpose>].
22
28
  pulsar:// # topics are persistent://<tenant>[_<env>]/<domain>/<Model>[_<purpose>]
@@ -56,3 +62,8 @@ klok-(ingress|egress)-< # integration repositories are named <tenant>-<system>-<
56
62
  (^|[^<`\\])/think\b # a prompt token of an old Qwen model; the model name is the only switch (a quoted mention in a rule is fine)
57
63
  (^|[^`])/no_think\b # a prompt token of an old Qwen model; the model name is the only switch
58
64
  Kloker-Long # not a gateway model name; use Kloker-nothink or Kloker
65
+ #
66
+ # A datamodel's metadata is klok-datamodel-project.json, rendered from project-template-datamodel; the pipeline reads
67
+ # no metadata, and klok.dev does not create datamodel repositories yet (frontend-klok-dev#33, 2026-10-10).
68
+ (?i)meta\.json`? (uses|in) the flat form # a datamodel's metadata is klok-datamodel-project.json; *.meta.json is only klok.dev's fallback for an older repository
69
+ (?i)create it with klok\.dev's datamodel wizard # klok.dev's wizard edits existing models; it does not create datamodel repositories yet (frontend-klok-dev#33), they are made by hand from project-template-datamodel
@@ -13,7 +13,8 @@ name the parts, and say what is wrong when it is wrong.
13
13
  1. Name the datamodel class of every payload you mention (A, B, C or X) and who reads it.
14
14
  2. Name the legs: ingress, processing, egress, API, or a solution of several legs.
15
15
  3. Choose one pattern base class per leg, as a design decision.
16
- 4. Propose the solution id, the route ids and the topic names, following the platform rules.
16
+ 4. Propose the route ids and the topic names, following the platform rules. A new solution's id is never made up:
17
+ it is the next free number, reserved by a merge request in the tenant's registry (`klok solutions new`).
17
18
  5. Flag every tactical deviation with `[TACTICAL]`, its cost and its unwind condition, or say the design is
18
19
  strategy-pure.
19
20
 
@@ -21,7 +22,7 @@ A correct answer looks like this:
21
22
 
22
23
  "Visma exports invoices in its own format, a class X model. Finance and sales both read invoices, so the ingress
23
24
  maps each invoice to the class B model `InvoiceV1` before it reaches the broker. Use `KlokOnrampRoute` as
24
- `ACME001I1_VismaInvoiceOnramp`, split the nightly file into one fact event per invoice, and publish on
25
+ `ACM001I1_VismaInvoiceOnramp`, split the nightly file into one fact event per invoice, and publish on
25
26
  `persistent://acme/finance/InvoiceV1`. If a consumer later needs Visma's format, that mapping belongs in an egress."
26
27
 
27
28
  A rejection names the problem and gives the broker-shaped replacement:
@@ -164,10 +165,10 @@ direct database or file share only for a system older than the team).
164
165
  Imitate the density of these answers, with the user's names.
165
166
 
166
167
  **Visma nightly invoice file.** The export is class X. Invoices are read by more than one domain, so the target is
167
- the class B model `InvoiceV1`. Ingress: `KlokOnrampRoute` as `ACME001I1_VismaInvoiceOnramp`, splitting the file
168
+ the class B model `InvoiceV1`. Ingress: `KlokOnrampRoute` as `ACM001I1_VismaInvoiceOnramp`, splitting the file
168
169
  into one fact event per invoice (a processing splitter if the mapping is heavy), published on
169
170
  `persistent://acme/finance/InvoiceV1`. Do not publish the file as one large message unless the claim check carries
170
- it and the event holds only the reference. A later Visma-shaped export is an egress, `ACME001E1_...`, mapping B to
171
+ it and the event holds only the reference. A later Visma-shaped export is an egress, `ACM001E1_...`, mapping B to
171
172
  X. Strategy-pure while each message stays small.
172
173
 
173
174
  **A front end that wants a JSON body now.** Ingress that cannot be fire and forget: `KlokOnrampRouteSync` as a
@@ -62,7 +62,7 @@ as the CloudEvent's data.
62
62
 
63
63
  ```java
64
64
  @ApplicationScoped
65
- public class ACME001I1_OrderOnramp extends KlokOnrampRoute<ShopOrder, OrderV1> {
65
+ public class ACM001I1_OrderOnramp extends KlokOnrampRoute<ShopOrder, OrderV1> {
66
66
  @Override
67
67
  protected String fromUri() {
68
68
  return "platform-http:/orders";
@@ -84,7 +84,7 @@ system's reply type; the framework handles the request/reply topics and the time
84
84
 
85
85
  ```java
86
86
  @ApplicationScoped
87
- public class ACME002I1_CustomerLookup
87
+ public class ACM002I1_CustomerLookup
88
88
  extends KlokOnrampRouteSync<CustomerRequest, CustomerResponse, CustomerLookupV1, CustomerV1> {
89
89
  @Override
90
90
  protected String fromUri() {
@@ -118,7 +118,7 @@ dead letters. Override `toUri()` and `transformBody(DOMAIN_MODEL body, Message m
118
118
 
119
119
  ```java
120
120
  @ApplicationScoped
121
- public class ACME001E1_InvoiceOfframp extends KlokOfframpRoute<InvoiceV1, LegacyInvoice> {
121
+ public class ACM001E1_InvoiceOfframp extends KlokOfframpRoute<InvoiceV1, LegacyInvoice> {
122
122
  @Override
123
123
  protected String toUri() {
124
124
  return "https://legacy-invoice-system/invoices";
@@ -198,7 +198,8 @@ blindly. `klok-mock-*` headers are for test and staging and are stripped in prod
198
198
  choose between a splitter and the claim check.
199
199
  2. **Reuse.** Before a new model, search the existing class B and C models, topics and routes, in klok.dev's
200
200
  context and on docs.klok.dev. A new topic only for a new contract.
201
- 3. **Solution design.** Solution id from the klok.dev registry, the legs and their route ids, the models per leg,
201
+ 3. **Solution design.** The solution id, reserved by a merge request in `<tenant>/integration-solutions` (`klok
202
+ solutions new`), the legs and their route ids, the models per leg,
202
203
  `[TACTICAL]` for any deviation, written in the solution document before the code and kept current by every change
203
204
  of a leg (platform rules, *Solutions*).
204
205
  4. **Design per leg.** Base class, exchange pattern, URI, authentication (a Vault path reference, never a secret),
@@ -237,11 +238,11 @@ blindly. `klok-mock-*` headers are for test and staging and are stripped in prod
237
238
 
238
239
  ## Worked answers
239
240
 
240
- **CRM over HTTP to Visma over JMS.** `ACME001I1_CrmCustomerOnramp extends KlokOnrampRoute<CrmCustomer, CustomerV1>`
241
+ **CRM over HTTP to Visma over JMS.** `ACM001I1_CrmCustomerOnramp extends KlokOnrampRoute<CrmCustomer, CustomerV1>`
241
242
  with `fromUri` on the CRM webhook and a `transformBody` that sets `customerId`, `fullName` and `email`; it publishes
242
- `persistent://acme/sales/CustomerV1`. `ACME001E1_VismaCustomerOfframp extends KlokOfframpRoute<CustomerV1,
243
+ `persistent://acme/sales/CustomerV1`. `ACM001E1_VismaCustomerOfframp extends KlokOfframpRoute<CustomerV1,
243
244
  VismaCustomer>` with `toUri` on the JMS queue and a `transformBody` into Visma's model. Validation that is a business
244
- rule is `ACME001P1_CustomerValidation`, a `KlokProcessingRoute` between two topics, not extra code in the onramp.
245
+ rule is `ACM001P1_CustomerValidation`, a `KlokProcessingRoute` between two topics, not extra code in the onramp.
245
246
 
246
247
  **Synchronous lookup.** First choice: `KlokOnrampRoute` answering 202 with a task reference and a status the caller
247
248
  can poll. If the UI must have a synchronous answer: `KlokOnrampRouteSync` as above, a timeout of 10 to 20 seconds, the
@@ -18,11 +18,12 @@ CLI version, generated from the program itself; when it is missing (an installer
18
18
  | to know the agent instructions and skills are current, at the start of a task | `klok agents update --check` (exit 5: run `klok agents update`) |
19
19
  | the local workspace current before you read it | `klok sync pull` |
20
20
  | a repository you can see in GitLab but do not have locally | `klok sync clone --dry-run`, then `klok sync clone` |
21
- | what is dirty, unpushed or behind in the workspace | `klok sync status` (`--fetch` for a current "behind") |
21
+ | what is dirty, unpushed or behind in the workspace, or moved, archived or gone in GitLab | `klok sync status` (`--fetch` for a current "behind") |
22
22
  | the documentation projects you may read | `klok docs list` |
23
23
  | one project's documentation | `klok docs read <doc-id>`; `klok docs id <tenant/group/.../repo>` derives a doc id |
24
24
  | an answer about the integration landscape, with links | `klok agent ask "<question>"`; `--session <first task id>` for a follow-up |
25
25
  | the impact of a datamodel change | `klok agent impact <datamodel> --change minor\|major\|remove` |
26
+ | the next free solution id of the tenant, with why, or a new solution's id reserved by a merge request | `klok solutions next`, `klok solutions new "<name>" --dry-run`, then without `--dry-run` |
26
27
  | the solution registry checked against its contract, or against the code | `klok solutions validate`, `klok solutions check` |
27
28
  | whether the deployed services run what `main` and the catalog say | `klok fleet status` |
28
29
  | the platform catalog's agent assets or its dependents | `klok catalog publish-agents`, `klok catalog release-dependents` (both run in CI) |
@@ -40,17 +41,30 @@ CLI version, generated from the program itself; when it is missing (an installer
40
41
  key `workspace`, `KLOK_GIT_LOCAL_WORKSPACE`, default `~/devklok`) is fetched and fast-forwarded (`git pull
41
42
  --ff-only`). It never merges, rebases or resets, so a checkout with local commits or a diverged branch is reported,
42
43
  not changed, and `<root>/worktrees` (the task worktrees) is skipped. It is the one sanctioned update of a canonical
43
- checkout, made on a workstation. Edits still happen in a task worktree (the `klok-worktree` skill). A service never
44
+ checkout, made on a workstation. `pull` and `status` also ask GitLab about each checkout and report one whose project
45
+ moved, was archived or is gone, with the commands that fix it (`remote set-url`, `mv`, `git worktree repair`); they
46
+ move nothing, and those commands are the person's to run. `klok sync clone` does not clone a project that is already
47
+ checked out under its old path. Edits still happen in a task worktree (the `klok-worktree` skill). A service never
44
48
  clones a repository just to read it: klok.dev and the agent read through the GitLab API, docs.klok.dev and the
45
49
  integration context.
46
50
 
47
51
  ## Reading
48
52
 
49
53
  `klok docs list`, then `klok docs read <doc-id>`, is how to read the platform's documentation; the `klok-docs` skill
50
- has the doc ids that matter most and what a 401, 403 or 404 means. `klok agent ask` asks Kloker, which reads the
54
+ has the doc ids that matter most and what a 401, 403 or 404 means. A doc id that moved with its renamed project is
55
+ followed to the new one, and stderr says `<old-id> moved to <new-id>`: use the new id from then on. `klok agent ask` asks Kloker, which reads the
51
56
  solution registry, the integration context and the documentation you may read, and answers with links; it is the
52
57
  quickest way to find which project, leg or topic handles something.
53
58
 
59
+ ## klok.dev
60
+
61
+ The CLI is meant to do everything klok.dev does, as the person, but only part of the portal has its command yet
62
+ (`klok whoami`, the agent commands, `klok solutions`). When a task needs something klok.dev shows and no klok command
63
+ prints it, such as a broker topic's publishers and subscriptions, an ODL record or a task's events, say so and ask the
64
+ person to look it up in klok.dev or to send it. Do not scrape the portal, and do not reach for credentials the task
65
+ was not given (a service token, a secret read out of Vault) to get around the gap. `klok fleet status --broker` is an
66
+ operator command for the person to run, not a way in.
67
+
54
68
  ## Exit codes
55
69
 
56
70
  `0` ok, `1` error, `2` a task waits for input, `3` denied by policy, `4` not authenticated, `5` outdated (instructions,
@@ -118,7 +118,7 @@ Print one effective configuration value; secrets show as **** unless --reveal
118
118
  klok config set [options] <key> [value]
119
119
  ```
120
120
 
121
- Set a value. Keys: agentUrl, tenant, klokDevUrl, keycloakIssuer, keycloakClientId, protocol, domain, gitUrl, workspace, klokerBasePath, klokerUrl, klokerModel, summaryLanguage, klokApiKey. Leave out the value of a secret (klokApiKey) to type it at a hidden prompt or pipe it in, so it stays out of the shell history
121
+ Set a value. Keys: agentUrl, tenant, klokDevUrl, keycloakIssuer, keycloakClientId, protocol, domain, gitUrl, workspace, klokerBasePath, klokerUrl, klokerModel, summaryLanguage, klokApiKey. Leave out the value of a secret (klokApiKey) to type it at a prompt that shows * for each character, or pipe it in, so it stays off the screen and out of the shell history
122
122
 
123
123
  | Argument | Description |
124
124
  |---|---|
@@ -661,7 +661,7 @@ Read the platform documentation on docs.klok.dev, and publish this repository’
661
661
  klok docs read [options] <doc-id>
662
662
  ```
663
663
 
664
- Print the llm.txt of a documentation project on docs.klok.dev, the one markdown file with its whole content, read with your GitLab token. Cached under ~/.cache/klok/docs with the ETag, so a second read is one conditional request; without network the cache is served with a warning. With --json the document carries the content and where it came from; otherwise the content goes to stdout and one status line to stderr.
664
+ Print the llm.txt of a documentation project on docs.klok.dev, the one markdown file with its whole content, read with your GitLab token. Cached under ~/.cache/klok/docs with the ETag, so a second read is one conditional request; without network the cache is served with a warning. A doc id that moved (its project was renamed) is followed once to the new one, said on stderr and cached under the new id; with --json, docId is the id read, requestedDocId the one asked for and movedTo is set. With --json the document carries the content and where it came from; otherwise the content goes to stdout and one status line to stderr.
665
665
 
666
666
  | Argument | Description |
667
667
  |---|---|
@@ -756,7 +756,7 @@ Publish this repository’s klok-docs/ to the documentation chain (projects not
756
756
  klok docs publish [options]
757
757
  ```
758
758
 
759
- Build the znai project and upload it under this project’s doc id
759
+ Build the znai project and upload it under this project’s doc id. When klok-documented-project.json at the repository root says state "retired", unpublish its docId and every previousDocIds entry instead (DELETE with the upload token; 404 counts as done) and upload nothing.
760
760
 
761
761
  | Option | Description |
762
762
  |---|---|
@@ -764,7 +764,7 @@ Build the znai project and upload it under this project’s doc id
764
764
  | `--doc-id <id>` | Publish under this doc id instead of the derived one |
765
765
  | `--repo-path <path>` | Repository path to derive from (default: CI_PROJECT_PATH or the git remote) |
766
766
  | `--znai <path>` | The znai executable (default: znai on PATH) |
767
- | `-n, --dry-run` | Resolve what would be published and build it with znai, upload nothing |
767
+ | `-n, --dry-run` | Resolve what would be published and build it with znai, upload nothing; for a retired project, say what would be unpublished |
768
768
  | `--no-build` | With --dry-run: only resolve the plan, do not run znai |
769
769
 
770
770
  ## klok fleet
@@ -897,7 +897,7 @@ Deployment manifests in the GitOps repository that Argo CD applies
897
897
  klok manifest update [options]
898
898
  ```
899
899
 
900
- Set the image of one container in one manifest file, then commit and push it
900
+ Set the image of one container in one manifest file and in the service's per-tenant instances, then commit and push
901
901
 
902
902
  | Option | Description |
903
903
  |---|---|
@@ -909,6 +909,7 @@ Set the image of one container in one manifest file, then commit and push it
909
909
  | `--container <name>` | Container to update (default: KLOK_MANIFEST_CONTAINER, else the only one in the file) |
910
910
  | `--workload <name>` | [<kind>/]<name> of the workload, when the container name repeats (default: KLOK_MANIFEST_WORKLOAD) |
911
911
  | `--branch <name>` | Branch to push to (default: KLOK_MANIFEST_BRANCH, then main) |
912
+ | `--no-instances` | Leave the per-tenant instances <tenant>/platform/<name>/<name>-crds.yaml alone (default: update them; KLOK_MANIFEST_INSTANCES=false turns it off too) |
912
913
  | `-n, --dry-run` | Clone and edit, print the diff, commit and push nothing |
913
914
 
914
915
  ```
@@ -918,6 +919,12 @@ catalog) is then a new reference that Argo CD rolls out, while an image that cam
918
919
  the same digest and changes nothing. Without a digest the manifest says only the tag, and a new image under an old tag is
919
920
  never rolled out: nodes keep the copy they cached.
920
921
 
922
+ Instances: a platform service can run once per tenant from the same image, each instance in its own manifest
923
+ <tenant>/platform/<name>/<name>-crds.yaml, where <name> is the repository name of <project path>/<name>-crds.yaml.
924
+ Every such file in another tenant's folder whose containers run the same image repository (any tag or digest) gets
925
+ the new image in the same commit; only those image lines change. A file of another image is left as it is, and so is
926
+ the project's own tenant folder. --no-instances updates the project's own manifest alone.
927
+
921
928
  Credentials: in CI the job logs in to Vault with its identity token (VAULT_ID_TOKEN) and the role
922
929
  <tenant>-ci-protected (KLOK_VAULT_DEPLOY_KEY_ROLE overrides it), which only pipelines of a protected branch or tag
923
930
  can use, and reads the deploy key and the pinned host key from secret/<tenant>/CICD_KEY and
@@ -1017,11 +1024,86 @@ The integration solution registry (<tenant>/integration-solutions)
1017
1024
  klok solutions validate [options]
1018
1025
  ```
1019
1026
 
1020
- Check every solutions/<SOLUTIONID>_<slug>/solution.meta.json against the descriptor contract
1027
+ Check every solutions/<SOLUTIONID>_<slug>/solution.meta.json against the descriptor contract and, against a base, that no solution is removed, no number reused, no retired solution revived and no id taken by an earlier merge request
1021
1028
 
1022
1029
  | Option | Description |
1023
1030
  |---|---|
1024
1031
  | `--root <dir>` | Checkout of the registry (default: the current directory) |
1032
+ | `--base <ref>` | Compare the change with this commit (default: the diff base of a merge request pipeline; none elsewhere) |
1033
+ | `--token <token>` | GitLab token that lists the registry's open merge requests (default: KLOK_GIT_APIKEY, then config klokApiKey, then the CI job token) |
1034
+ | `--offline` | Do not ask GitLab for the open merge requests (skips solution-id-claimed) |
1035
+
1036
+ ```
1037
+ In a merge request pipeline the base is CI_MERGE_REQUEST_DIFF_BASE_SHA and the job needs the whole history
1038
+ (GIT_DEPTH: "0"). On a branch or main pipeline there is no base, so removing a solution is possible there: that is
1039
+ how a platform admin removes one, on main. A number stays spent either way.
1040
+
1041
+ Examples:
1042
+ klok solutions validate # in a checkout of the registry
1043
+ klok solutions validate --base origin/main # a change on a branch, before pushing it
1044
+ ```
1045
+
1046
+ ### klok solutions next
1047
+
1048
+ ```
1049
+ klok solutions next [options]
1050
+ ```
1051
+
1052
+ The next free solution id of the tenant: one more than the highest number in the registry, its history, its branches and open merge requests, and the route classes on every branch of the tenant's repositories
1053
+
1054
+ | Option | Description |
1055
+ |---|---|
1056
+ | `--root <dir>` | Checkout of the registry (default: <workspace>/<tenant>/integration-solutions) |
1057
+ | `--workspace <dir>` | Workspace root that `klok sync` mirrors into (default: the configured workspace) |
1058
+ | `--tenant-short <short>` | The tenant's short name, three upper case letters (default: the one the registry's solution ids share) |
1059
+ | `--token <token>` | GitLab token that reads the registry's open merge requests (default: KLOK_GIT_APIKEY, then config klokApiKey) |
1060
+ | `--offline` | Neither fetch the registry nor ask GitLab for its open merge requests |
1061
+
1062
+ ```
1063
+ Fetches the registry first (git fetch --prune origin). The repositories are read as last fetched; when one was
1064
+ fetched more than a day ago it says so, and `klok sync pull` refreshes them. Numbers 900-999 are the framework's and
1065
+ a gap is never filled. Nothing is written: reserve the id with `klok solutions new`.
1066
+
1067
+ Examples:
1068
+ klok solutions next
1069
+ klok solutions next --root . --json # in a checkout or worktree of the registry
1070
+ ```
1071
+
1072
+ ### klok solutions new
1073
+
1074
+ ```
1075
+ klok solutions new [options] <name>
1076
+ ```
1077
+
1078
+ Reserve the next solution id: a task worktree of the registry, a folder with a reserved descriptor and a stub document, one commit, and a push that opens the merge request, which is the allocation
1079
+
1080
+ | Argument | Description |
1081
+ |---|---|
1082
+ | `<name>` | required |
1083
+
1084
+ | Option | Description |
1085
+ |---|---|
1086
+ | `--root <dir>` | Checkout of the registry (default: <workspace>/<tenant>/integration-solutions) |
1087
+ | `--workspace <dir>` | Workspace root that `klok sync` mirrors into (default: the configured workspace) |
1088
+ | `--tenant-short <short>` | The tenant's short name, three upper case letters (default: the one the registry's solution ids share) |
1089
+ | `--token <token>` | GitLab token that reads the registry's open merge requests (default: KLOK_GIT_APIKEY, then config klokApiKey) |
1090
+ | `--offline` | Neither fetch the registry nor ask GitLab for its open merge requests |
1091
+ | `--slug <slug>` | kebab-case name of the folder and the url (default: from the name) |
1092
+ | `--domain <domain>` | Information domain (Finance, HR, AI, ...) |
1093
+ | `--description <text>` | One or two sentences: what the solution keeps in step, in business terms |
1094
+ | `--dry-run` | Print the id, the worktree and the files, and create nothing |
1095
+
1096
+ ```
1097
+ The id is the one `klok solutions next` gives, after fetching the registry. The worktree is
1098
+ <workspace>/worktrees/<tenant>/integration-solutions/<YYYYMMDD>-solution-<id>-<8 hex> on the branch agent/<task id>,
1099
+ based on the registry's default branch; the canonical checkout is not touched. The push uses your own git setup and
1100
+ asks GitLab, with push options, to open the merge request against the default branch. The commit is
1101
+ docs(<ID>): reserve <ID> for <name>.
1102
+
1103
+ Examples:
1104
+ klok solutions new "Payroll export to Personec" --domain HR --dry-run
1105
+ klok solutions new "Payroll export to Personec" --domain HR
1106
+ ```
1025
1107
 
1026
1108
  ### klok solutions check
1027
1109
 
@@ -1070,10 +1152,11 @@ Discover every repository you can see and clone the missing ones
1070
1152
  klok sync pull [options]
1071
1153
  ```
1072
1154
 
1073
- Fast-forward every repository in the workspace
1155
+ Fast-forward every repository in the workspace, and report checkouts whose GitLab project moved, was archived or is gone
1074
1156
 
1075
1157
  | Option | Description |
1076
1158
  |---|---|
1159
+ | `--no-remote-check` | Do not ask GitLab whether the checkouts’ projects moved, were archived or are gone |
1077
1160
  | `-w, --workspace <path>` | Workspace directory (default: config workspace) |
1078
1161
  | `--token <token>` | GitLab token (default: KLOK_GIT_APIKEY, then config klokApiKey) |
1079
1162
  | `-j, --jobs <n>` | Parallel git operations (default: `6`) |
@@ -1086,7 +1169,7 @@ Fast-forward every repository in the workspace
1086
1169
  klok sync status [options]
1087
1170
  ```
1088
1171
 
1089
- Show dirty trees, unpushed commits and branches behind their remote (as of the last fetch)
1172
+ Show dirty trees, unpushed commits and branches behind their remote (as of the last fetch), and checkouts whose GitLab project moved, was archived or is gone
1090
1173
 
1091
1174
  | Option | Description |
1092
1175
  |---|---|
@@ -1094,3 +1177,5 @@ Show dirty trees, unpushed commits and branches behind their remote (as of the l
1094
1177
  | `-j, --jobs <n>` | Parallel git operations (default: `12`) |
1095
1178
  | `-a, --all` | List every repository, not only the ones needing attention |
1096
1179
  | `--fetch` | Fetch every repository first, so "behind" is current |
1180
+ | `--token <token>` | GitLab token for the GitLab check (default: KLOK_GIT_APIKEY, then config klokApiKey) |
1181
+ | `--no-remote-check` | Do not ask GitLab whether the checkouts’ projects moved, were archived or are gone |