@llm4ts/shell 2.12.0 → 2.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/flows/epic-stories.js +9 -3
  2. package/flows/lib/epic-stories.js +24 -4
  3. package/kits/soap-ace/README.md +313 -0
  4. package/kits/soap-ace/fixtures/demo-bank-soap/RUNBOOK.md +102 -0
  5. package/kits/soap-ace/fixtures/demo-bank-soap/design/api-design.md +620 -0
  6. package/kits/soap-ace/fixtures/demo-bank-soap/responses/cercaConti.xml +3 -0
  7. package/kits/soap-ace/fixtures/demo-bank-soap/responses/cercaMovimenti.xml +3 -0
  8. package/kits/soap-ace/fixtures/demo-bank-soap/responses/confermaBonifico.xml +3 -0
  9. package/kits/soap-ace/fixtures/demo-bank-soap/responses/dettaglioConto.xml +3 -0
  10. package/kits/soap-ace/fixtures/demo-bank-soap/responses/inserisciBonifico.xml +3 -0
  11. package/kits/soap-ace/fixtures/demo-bank-soap/responses/revocaBonifico.xml +3 -0
  12. package/kits/soap-ace/fixtures/demo-bank-soap/soapui/DemoBank-soapui-project.xml +39 -0
  13. package/kits/soap-ace/fixtures/demo-bank-soap/wsdl/DemoBank.wsdl +186 -0
  14. package/kits/soap-ace/fixtures/demo-bank-soap/wsdl/LegacyRpc.wsdl +29 -0
  15. package/kits/soap-ace/fixtures/demo-bank-soap/wsdl/xsd/banking.xsd +10 -0
  16. package/kits/soap-ace/fixtures/demo-bank-soap/wsdl/xsd/bonifici.xsd +107 -0
  17. package/kits/soap-ace/fixtures/demo-bank-soap/wsdl/xsd/common.xsd +93 -0
  18. package/kits/soap-ace/fixtures/demo-bank-soap/wsdl/xsd/conti.xsd +71 -0
  19. package/kits/soap-ace/fixtures/demo-bank-soap/wsdl/xsd/movimenti.xsd +55 -0
  20. package/kits/soap-ace/flows/lib/soap/Analysis.js +508 -0
  21. package/kits/soap-ace/flows/lib/soap/Auth.js +179 -0
  22. package/kits/soap-ace/flows/lib/soap/Call.js +186 -0
  23. package/kits/soap-ace/flows/lib/soap/CallPolicy.js +50 -0
  24. package/kits/soap-ace/flows/lib/soap/Catalog.js +157 -0
  25. package/kits/soap-ace/flows/lib/soap/Classification.js +254 -0
  26. package/kits/soap-ace/flows/lib/soap/Design.js +497 -0
  27. package/kits/soap-ace/flows/lib/soap/DesignProposal.js +204 -0
  28. package/kits/soap-ace/flows/lib/soap/Discover.js +139 -0
  29. package/kits/soap-ace/flows/lib/soap/Envelope.js +132 -0
  30. package/kits/soap-ace/flows/lib/soap/Epic.js +239 -0
  31. package/kits/soap-ace/flows/lib/soap/Instance.js +675 -0
  32. package/kits/soap-ace/flows/lib/soap/Mapping.js +130 -0
  33. package/kits/soap-ace/flows/lib/soap/Masking.js +508 -0
  34. package/kits/soap-ace/flows/lib/soap/OpenApi.js +288 -0
  35. package/kits/soap-ace/flows/lib/soap/Samples.js +287 -0
  36. package/kits/soap-ace/flows/lib/soap/Scenarios.js +132 -0
  37. package/kits/soap-ace/flows/lib/soap/SoapUiImport.js +200 -0
  38. package/kits/soap-ace/flows/lib/soap/Stub.js +34 -0
  39. package/kits/soap-ace/flows/lib/soap/Transport.js +167 -0
  40. package/kits/soap-ace/flows/lib/soap/Wsdl.js +533 -0
  41. package/kits/soap-ace/flows/lib/soap/Xml.js +453 -0
  42. package/kits/soap-ace/flows/lib/soap/Xsd.js +471 -0
  43. package/kits/soap-ace/flows/lib/soap/Yaml.js +309 -0
  44. package/kits/soap-ace/flows/soap-design.js +167 -0
  45. package/kits/soap-ace/flows/soap-discover.js +85 -0
  46. package/kits/soap-ace/flows/soap-epic.js +204 -0
  47. package/kits/soap-ace/flows/soap-sample.js +265 -0
  48. package/kits/soap-ace/packs/ace12-rest/api-style.md +63 -0
  49. package/kits/soap-ace/packs/ace12-rest/pack.md +18 -0
  50. package/kits/soap-ace/packs/ace12-rest/prompts/implement.md +8 -0
  51. package/kits/soap-ace/packs/ace12-rest/reviewers/contract.md +5 -0
  52. package/kits/soap-ace/patterns/PAT-ACE-001-esito-to-problem.md +8 -0
  53. package/kits/soap-ace/patterns/PAT-ACE-002-fault-to-problem.md +8 -0
  54. package/kits/soap-ace/patterns/PAT-ACE-003-amounts-and-enums.md +8 -0
  55. package/kits/soap-ace/patterns/PAT-ACE-004-pagination.md +7 -0
  56. package/kits/soap-ace/patterns/PAT-ACE-005-nil-and-absence.md +7 -0
  57. package/kits/soap-ace/scaffolds/ace12-rest-api/CONTRIBUTING.md +56 -0
  58. package/kits/soap-ace/scaffolds/ace12-rest-api/README.md +16 -0
  59. package/kits/soap-ace/scaffolds/ace12-rest-api/scripts/ace-gates-container.sh +8 -0
  60. package/kits/soap-ace/scaffolds/ace12-rest-api/scripts/ace-gates.sh +41 -0
  61. package/package.json +4 -4
@@ -29,7 +29,7 @@ import { landEpic } from "@llm4ts/flow/Landing";
29
29
  import { implementStoriesFlow } from "@llm4ts/flow/Stories";
30
30
  import { makeStoryPlanStore, validateStoryPlan } from "@llm4ts/flow/StoryPlan";
31
31
  import * as Console from "effect/Console";
32
- import { awaitServer, chooseEpic, epicsDir, listEpics, renderEpicList, combineTotals, epicIdFor, flagsFromEnvironment, appDirFor, gateCommands, gatesIn, inAppDir, generateStoryPlan, httpProbe, judgeStory, localCoderServer, parseEpicArgs, reasonerFromEnvironment, serverHealthUrl, setupAgentEnabled, setupIn, storyCoderFromEnvironment, verifyBlockedOn, worktreeRootFor, worktreeSetupCommand } from "./lib/epic-stories.js";
32
+ import { awaitServer, chooseEpic, epicsDir, listEpics, renderEpicList, combineTotals, epicIdFor, flagsFromEnvironment, appDirFor, appScripts, gateCommands, gatesIn, inAppDir, generateStoryPlan, httpProbe, judgeStory, localCoderServer, parseEpicArgs, reasonerFromEnvironment, serverHealthUrl, setupAgentEnabled, setupIn, storyCoderFromEnvironment, verifyBlockedOn, worktreeRootFor, worktreeSetupCommand } from "./lib/epic-stories.js";
33
33
  /** `LLM4TS_CODER_MODEL` / `LLM4TS_REASONING_MODEL`: pi takes `provider/model` (e.g. `openai-codex/gpt-5.5`). */
34
34
  const withOptionalModel = (config, model) => {
35
35
  const trimmed = model?.trim();
@@ -100,6 +100,12 @@ const program = Effect.gen(function* () {
100
100
  if (appDir !== ".") {
101
101
  yield* events.publish(Info.make({ message: `app dir: ${appDir} (setup and gates run there; LLM4TS_APP_DIR)` }));
102
102
  }
103
+ const commands = gateCommands(process.env, yield* appScripts(files, join(input.workDir, appDir)));
104
+ yield* events.publish(Info.make({
105
+ message: commands.length === 0
106
+ ? "gates: none (the app defines none of typecheck, lint, test, build; set LLM4TS_GATES)"
107
+ : `gates: ${commands.map((command) => command.join(" ")).join(" · ")}`
108
+ }));
103
109
  if (flags.land !== undefined) {
104
110
  const landed = yield* landEpic(context, {
105
111
  plan,
@@ -107,7 +113,7 @@ const program = Effect.gen(function* () {
107
113
  stateDir,
108
114
  target: flags.land,
109
115
  keepWorktrees: flags.keepWorktrees,
110
- gates: inAppDir(appDir, gatesIn(nodeProcessExecutor, events, gateCommands(process.env))),
116
+ gates: inAppDir(appDir, gatesIn(nodeProcessExecutor, events, commands)),
111
117
  system: ["House rules of the target repository (CONTRIBUTING.md):", guidance].join("\n")
112
118
  });
113
119
  yield* events.publish(Info.make({
@@ -136,7 +142,7 @@ const program = Effect.gen(function* () {
136
142
  : [
137
143
  `The application lives in ${appDir}/ — its package.json, sources and tests; the gates run there.`
138
144
  ];
139
- const gates = inAppDir(appDir, gatesIn(nodeProcessExecutor, events, gateCommands(process.env)));
145
+ const gates = inAppDir(appDir, gatesIn(nodeProcessExecutor, events, commands));
140
146
  const setupCommand = worktreeSetupCommand(process.env);
141
147
  const healthUrl = serverHealthUrl(localServer, process.env);
142
148
  const report = yield* implementStoriesFlow({ ...context, reasoning: reasoningMeter.service }, {
@@ -5,6 +5,7 @@ import { readdir } from "node:fs/promises";
5
5
  import { isAbsolute, join, normalize } from "node:path";
6
6
  import * as Duration from "effect/Duration";
7
7
  import * as Effect from "effect/Effect";
8
+ import * as Schema from "effect/Schema";
8
9
  import { Dimension, Sample } from "@llm4ts/core/eval/Eval";
9
10
  import { judge } from "@llm4ts/core/eval/Judge";
10
11
  import { TokenUsage } from "@llm4ts/core/Models";
@@ -34,7 +35,8 @@ export const epicUsage = [
34
35
  "Seats: LLM4TS_REASONER (claude|gemini|…, default claude) splits, reviews, judges;",
35
36
  " LLM4TS_CODER (default pi) implements; LLM4TS_REASONING_MODEL / LLM4TS_CODER_MODEL",
36
37
  " pick their models (pi: provider/model); LLM4TS_CODER_FLAGS / LLM4TS_REASONING_FLAGS",
37
- " add CLI flags (key=value;key=value). LLM4TS_GATES overrides the gate commands;",
38
+ " add CLI flags (key=value;key=value). LLM4TS_GATES overrides the gate commands",
39
+ " (default: those of pnpm typecheck|lint|test|build the app's package.json defines);",
38
40
  " LLM4TS_WORKTREE_SETUP (default: pnpm install --offline) prepares each worktree;",
39
41
  " LLM4TS_WORKTREE_ROOT (default: <repo>.worktrees beside the repository) holds them;",
40
42
  " LLM4TS_APP_DIR (e.g. frontend) runs setup and gates in that subfolder; unset, a",
@@ -625,11 +627,29 @@ export const defaultGateCommands = [
625
627
  ["pnpm", "test"],
626
628
  ["pnpm", "build"]
627
629
  ];
628
- /** `LLM4TS_GATES="pnpm typecheck;pnpm test"` overrides the four defaults. */
629
- export const gateCommands = (environment) => {
630
+ /** The part of an application's package.json the gates care about. */
631
+ const PackageScripts = Schema.fromJsonString(Schema.Struct({
632
+ scripts: Schema.optional(Schema.Record(Schema.String, Schema.String))
633
+ }));
634
+ /**
635
+ * The script names in `<appRoot>/package.json`; undefined when it is missing
636
+ * or unreadable, which keeps every default gate (the failure then says why).
637
+ */
638
+ export const appScripts = (files, appRoot) => files.read(join(appRoot, "package.json")).pipe(Effect.flatMap((text) => text === undefined
639
+ ? Effect.succeed(undefined)
640
+ : Effect.map(Schema.decodeUnknownEffect(PackageScripts)(text), (manifest) => new Set(Object.keys(manifest.scripts ?? {})))), Effect.catch(() => Effect.succeed(undefined)));
641
+ /**
642
+ * `LLM4TS_GATES="pnpm typecheck;pnpm test"` overrides the four defaults.
643
+ * Without it, a default gate runs only when the application defines that
644
+ * script: a Next.js app has `lint` and `build` but seldom `typecheck`, and
645
+ * `pnpm typecheck` there fails with "Command not found", not a red gate.
646
+ */
647
+ export const gateCommands = (environment, scripts) => {
630
648
  const raw = environment.LLM4TS_GATES?.trim();
631
649
  if (raw === undefined || raw.length === 0) {
632
- return defaultGateCommands;
650
+ return scripts === undefined
651
+ ? defaultGateCommands
652
+ : defaultGateCommands.filter((command) => scripts.has(command[1] ?? ""));
633
653
  }
634
654
  return raw
635
655
  .split(";")
@@ -0,0 +1,313 @@
1
+ # Kit: soap-ace
2
+
3
+ SOAP services to REST APIs on IBM ACE 12: discover the WSDL, sample and analyse real responses, design the REST contract, and plan the ACE build as an epic.
4
+
5
+ In bank integration work most new REST APIs are exposed through IBM ACE on
6
+ top of an existing SOAP service. This kit turns a WSDL (a file or URL, with
7
+ auth when needed) into evidence first, then into a design, and only then into
8
+ an ACE implementation plan. The spec of record is
9
+ [`specs/pending/soap-ace-kit.md`](../../specs/pending/soap-ace-kit.md).
10
+
11
+ Direction: SOAP is the source and ACE the **target**. The
12
+ `mainframe-java/ace-integration` pack goes the other way (ACE as legacy
13
+ source) and shares nothing with this kit.
14
+
15
+ ## Status
16
+
17
+ | Piece | State |
18
+ | -------------------------------------------------- | -------- |
19
+ | Strict XML reader (`flows/lib/soap/Xml.ts`) | done |
20
+ | WSDL 1.1/2.0 + XSD catalog (`Wsdl.ts`, `Xsd.ts`) | done |
21
+ | `demo-bank-soap` fixture | WSDL/XSD |
22
+ | `soap-discover` flow and operation classification | pending |
23
+ | Masking, auth profile, transport, sample authoring | pending |
24
+ | Analysis, REST design, OpenAPI projection | pending |
25
+ | `ace12-rest` pack, scaffold, `soap-epic` | pending |
26
+
27
+ ## soap-discover
28
+
29
+ ```bash
30
+ llm4ts run soap-discover --repo ~/work/conti-api ./wsdl/DemoBank.wsdl
31
+ ```
32
+
33
+ Writes under `<repo>/.llm4ts/soap/<service>/`:
34
+
35
+ | File | What it is |
36
+ | --------------- | ------------------------------------------------------------------------ |
37
+ | `catalog.json` | the typed catalog every later step reads |
38
+ | `catalog.md` | human summary: endpoints, operations and their fields, open questions |
39
+ | `operations.md` | proposed `read`/`mutating` classes; review, then set `Status: confirmed` |
40
+
41
+ `operations.md` is the approval file: until its status is `confirmed` every
42
+ operation counts as unclassified, so nothing can be called and the design
43
+ cannot pick REST verbs. An existing file is never overwritten; rerunning
44
+ reports operations it does not mention and entries no longer in the WSDL.
45
+ Classes are proposed by a name heuristic (Italian and English verbs) and, when
46
+ `LLM4TS_JUDGMENT_PROVIDER` names a judgment seat, by a typed judgment
47
+ (ADR 0017); without one, discovery makes no model call at all.
48
+ `LLM4TS_SOAP_SERVICE` overrides the service directory name; for a WSDL URL it
49
+ also selects the auth profile whose `fetch` side authenticates every document
50
+ request.
51
+
52
+ ## Discovery guarantees
53
+
54
+ - The catalog is produced by code, never by a model. Every operation, type,
55
+ and field it lists is read from the WSDL and the schemas it imports.
56
+ - Constructs the reader does not model (`xs:any`, substitution groups,
57
+ model-group refs, complex restrictions, list/union types, dangling
58
+ references) are listed as **open questions** with their document and line.
59
+ - Only document/literal SOAP bindings (bare and wrapped, SOAP 1.1 and 1.2)
60
+ become operations. A WSDL whose every binding is RPC or encoded is refused
61
+ with an error naming the bindings; a WSDL that also offers a
62
+ document/literal binding keeps it and lists the others as open questions.
63
+ - `<!DOCTYPE` is refused outright: no external entities, no entity expansion.
64
+ Schemas in ISO-8859-1 and windows-1252 are decoded by their declaration.
65
+
66
+ ## soap-sample
67
+
68
+ Runs after `soap-discover` in the same repository; the task text is a
69
+ command.
70
+
71
+ ```bash
72
+ llm4ts run soap-sample --repo . "import ./DemoBank-soapui-project.xml" # requests + mock responses, masked
73
+ llm4ts run soap-sample --repo . "init cercaMovimenti fine-mese" # commented skeleton to edit
74
+ llm4ts run soap-sample --repo . "propose cercaMovimenti" # scenario set from the reasoning seat
75
+ llm4ts run soap-sample --repo . "call cercaMovimenti" # every request of the operation
76
+ llm4ts run soap-sample --repo . -- --allow-mutating revocaBonifico "call revocaBonifico/gia-eseguito"
77
+ llm4ts run soap-sample --repo . list
78
+ ```
79
+
80
+ A request is a YAML projection of the XSD, one file per scenario under
81
+ `samples/<operation>/<name>.request.yaml`:
82
+
83
+ ```yaml
84
+ # cercaMovimenti: fine mese
85
+ operation: cercaMovimenti
86
+ purpose: "fine mese"
87
+ body:
88
+ iban: IT12L0542811101309953525271 # IbanType, string, pattern …, length 27 [1]
89
+ dataDa: 2026-01-15 # date [1]
90
+ dataA: 2026-01-15 # date [1]
91
+ paginazione: # PaginazioneRichiesta [1]
92
+ numeroPagina: 1 # int [1]
93
+ dimensionePagina: 1 # int, 1..100 [1]
94
+ ```
95
+
96
+ Required fields come filled with values that satisfy the schema: identifiers
97
+ already seen in earlier (masked) samples first, then checksummed example
98
+ IBANs and codici fiscali, enumeration values, and strings built from the XSD
99
+ patterns. Optional fields and choice alternatives are commented out. Every
100
+ value is a string, sent exactly as written; `~` sends `xsi:nil`. A raw
101
+ `<name>.request.xml` (the body element or a whole envelope, with an optional
102
+ `<!-- purpose: … -->`) is accepted as a second form and validated the same way.
103
+
104
+ `call` never sends a request that does not validate; it lists what to fix.
105
+ It records `<name>.exchange.json` beside the request: the masked request
106
+ and response envelopes without security headers, safe HTTP headers, status,
107
+ latency, any SOAP fault, and every place the response breaks its schema
108
+ (values outside an enumeration, undeclared elements), which are findings for
109
+ the design rather than errors. `--keep-raw` also writes the unmasked response
110
+ to the gitignored `raw/`, never shown to a model.
111
+
112
+ `propose` asks the reasoning seat (`LLM4TS_REASONER`, default `claude`) for
113
+ three to six scenarios (happy path, empty result, pagination boundary, a
114
+ business error, a schema edge) and writes each as a request file, listing
115
+ at its top whatever does not validate. `import` reads the requests saved in a
116
+ SoapUI or ReadyAPI project (interface calls and test steps) and its mock
117
+ responses; stored credentials are counted and ignored, and everything is
118
+ masked before it is written. `LLM4TS_SOAP_STUB=<dir>` answers calls from
119
+ `<dir>/<operation>.xml` instead of the network, for rehearsals; the fixture's
120
+ `responses/` directory is one.
121
+
122
+ ## soap-design
123
+
124
+ ```bash
125
+ llm4ts run soap-design --repo . "analyse" # evidence per operation (no model)
126
+ llm4ts run soap-design --repo . mapping # the 1:1 XSD ↔ JSON mapping (no model)
127
+ llm4ts run soap-design --repo . design # the reasoning seat drafts design/api-design.md
128
+ llm4ts run soap-design --repo . check # deterministic review; rerun after every edit
129
+ llm4ts run soap-design --repo . revise # redraft against the check's findings
130
+ llm4ts run soap-design --repo . openapi # design/openapi.yaml from an approved design
131
+ ```
132
+
133
+ ### analyse
134
+
135
+ Reads the recorded exchanges (calls and SoapUI mocks) and writes
136
+ `analysis/<operation>.md` with a typed `analysis/<operation>.json` beside it.
137
+ No model is involved; the report is the evidence the REST design must cite:
138
+
139
+ - **observations**: values outside a declared enumeration, undeclared code
140
+ lists, required fields that are always empty, optional ones always present,
141
+ `xsi:nil` usage, business errors carried inside successful responses,
142
+ faults, and how many declared fields were never observed;
143
+ - **business outcome codes** (`esito.codice` and similar) with their
144
+ descriptions and the samples that produced them;
145
+ - **SOAP faults** by code, detail element, and reason;
146
+ - **pagination**: page-number, offset, or cursor style, from the request and
147
+ response field names, plus what the samples showed (last-page flags, most
148
+ items per response);
149
+ - **response fields**: presence, emptiness and nil counts, and the masked
150
+ values seen (most frequent first);
151
+ - **schema findings**: every place a response broke its WSDL;
152
+ - fields **never observed**, optional **request fields no sample used**,
153
+ and **latency** across real calls.
154
+
155
+ ### mapping, design, check, openapi
156
+
157
+ `mapping` writes `design/mapping.json` and `mapping.md`: every request and
158
+ response field with the JSON path and type a literal facade would use
159
+ (decimals stay strings). It is the traceability base: the only paths a
160
+ design may cite.
161
+
162
+ `design` asks the reasoning seat (`LLM4TS_REASONER`, default `claude`) for a
163
+ resource-oriented design, giving it the style guide as hard rules, the
164
+ confirmed classes, the mapping, the named XSD types, and the analysis
165
+ evidence. The draft is written to `design/api-design.md` under
166
+ `Status: proposed`, with the check's findings above the design of record, a
167
+ ` ```json apidesign ` block:
168
+
169
+ - **endpoints** name their SOAP `sources`, parameters cite request paths,
170
+ responses name a model and the response path it comes from (`list`/`paged`
171
+ for collections), **errors** list the outcome codes and fault elements they
172
+ map, and **evidence** cites the analysis;
173
+ - **models** bind to an XSD element or named type (`sourceType`); each
174
+ property cites a path within it (`saldo.valore`) or a `derivation`, and an
175
+ `enumMap` translates SOAP values to REST values;
176
+ - **excluded** lists operations deliberately not exposed, with reasons.
177
+
178
+ `check` is the deterministic reviewer, run on every draft and after every
179
+ edit. Errors: an operation neither exposed nor excluded, `GET` over a
180
+ mutating operation, a source path that is not in the XSD, a model rendered
181
+ from a type it is not bound to, an `enumMap` missing a declared value or a
182
+ value the samples showed (`SOSPESO`), and style violations (paths, names,
183
+ `esito` in payloads, problem codes). Warnings: REST promising a field the
184
+ XSD leaves optional, business errors or faults seen in samples but not
185
+ mapped, unused models. `revise` hands the findings back to the seat.
186
+
187
+ The file is the approval: edit the JSON block, rerun `check`, and set
188
+ `Status: approved`. `design` never overwrites it. `openapi` refuses a design
189
+ that is not approved or still has errors, then projects `design/openapi.yaml`
190
+ (OpenAPI 3.1): component schemas per model, `{ items, page }` wrappers for
191
+ collections, RFC 9457 problems per error status with their codes
192
+ (`x-problem-codes`), a `Location` header on creations, a default `502`, and
193
+ `x-soap-*` extensions tracing every operation, parameter, and property to the
194
+ SOAP service. Nobody edits it; the ACE pack implements it. The fixture's
195
+ [`design/api-design.md`](fixtures/demo-bank-soap/design/api-design.md) is a
196
+ reviewed example.
197
+
198
+ ## soap-epic
199
+
200
+ ```bash
201
+ llm4ts run soap-epic --repo . doctor # ACE 12 usable here?
202
+ llm4ts run soap-epic --repo . -- --target ~/work/demo-bank-ace plan # seed + story plan
203
+ ```
204
+
205
+ `plan` takes an approved design with no check errors and prepares the ACE
206
+ repository (`--target`, a git repository):
207
+
208
+ - **regenerated every run**: `contracts/` (the design, `openapi.yaml` 3.1,
209
+ `openapi-ace.yaml` 3.0.3 because ACE 12 imports OpenAPI 3.0, the 1:1
210
+ mapping, the WSDL and XSDs re-encoded as UTF-8), `test-data/` (the masked
211
+ exchanges the backend stubs are built from), `docs/analysis/`, and
212
+ `docs/patterns/` (the ESQL pattern cards);
213
+ - **created once, then the team's**: the
214
+ [`ace12-rest-api`](scaffolds/ace12-rest-api) scaffold: README,
215
+ `CONTRIBUTING.md` (house rules every coder reads), and
216
+ `scripts/ace-gates.sh build | test` (`ibmint package`; `ibmint deploy` to a
217
+ scratch work directory and `IntegrationServer --test-project`, after
218
+ sourcing `mqsiprofile`), plus a container variant.
219
+
220
+ It then derives the story plan, not generates it: `api-skeleton` (REST API
221
+ project descriptors and main flow), `policies` (dev/test/uat endpoints and
222
+ security), `shared-lib` (WSDL, backend call, error mapping), `backend-stub`
223
+ (test project and stubs), one `resource-<name>` story per resource owning its
224
+ broker schema folder and tests, and `contract-check`. The plan is saved where
225
+ the engine's `epic-stories` flow looks for it
226
+ (`.llm4ts/epics/<epic-id>/plan.md`, same epic id), so that flow executes it
227
+ unchanged: parallel stories in worktrees (concurrency 2), the ACE gates,
228
+ the judge, the board, branches only. An existing plan is kept; editing it is
229
+ the re-plan. `plan` prints the exact `epic-stories` command.
230
+
231
+ The [`ace12-rest`](packs/ace12-rest/pack.md) pack names the same gates and
232
+ the judge's rubric (contract, mapping, errors); its prompt and reviewer
233
+ sidecars carry the same rules as `CONTRIBUTING.md`.
234
+
235
+ ## Auth profile
236
+
237
+ `.llm4ts/soap/<service>/auth.json` is gitignored (discovery writes the
238
+ directory's `.gitignore`) and holds **references only**: `env:NAME` or
239
+ `file:path`. A literal where a secret belongs is refused when the profile is
240
+ read, and errors name the reference, never its value.
241
+
242
+ ```json
243
+ {
244
+ "environment": "uat",
245
+ "endpoint": "https://esb-uat.bank.internal/soap/DemoBank",
246
+ "fetch": { "auth": { "scheme": "basic", "user": "svc-wsdl", "password": "env:WSDL_PASSWORD" } },
247
+ "call": {
248
+ "auth": { "scheme": "bearer", "token": "env:SOAP_TOKEN" },
249
+ "tls": {
250
+ "pfx": "file:/secure/client.p12",
251
+ "passphrase": "env:P12_PASS",
252
+ "ca": "file:/secure/ca.pem"
253
+ }
254
+ },
255
+ "wsSecurity": { "user": "env:WS_USER", "password": "env:WS_PASSWORD", "passwordType": "digest" },
256
+ "mutating": { "uat": "deny" }
257
+ }
258
+ ```
259
+
260
+ - `environment` must be `dev`, `test`, or `uat`; nothing else is accepted.
261
+ - `fetch` and `call` are configured separately: `auth` is `none`, `basic`, or
262
+ `bearer`; `tls` is `cert`+`key` (PEM) or `pfx`+`passphrase`, plus an
263
+ optional `ca`.
264
+ - `wsSecurity` adds a WS-Security `UsernameToken` (`text` or `digest`) to
265
+ calls.
266
+ - Operations confirmed as `mutating` in `operations.md` follow the
267
+ environment's policy: `confirm` (default in dev) asks, `flag` (default in
268
+ test and uat) needs `--allow-mutating <operation>`, `deny` refuses.
269
+ Unclassified operations are never called.
270
+
271
+ Persisted exchanges drop `wsse:Security` headers and credential-bearing HTTP
272
+ headers (`authorization`, cookies, anything named like a token or secret).
273
+
274
+ ## Masking
275
+
276
+ Every SOAP sample is masked before it is written anywhere or shown to a
277
+ model; there is no switch. Personal data is found two ways:
278
+
279
+ - **by field**: element and attribute names (Italian and English words:
280
+ `iban`, `codiceFiscale`, `partitaIva`, `numeroCarta`, `intestatario`,
281
+ `indirizzo`, `dataNascita`, `codiceOtp`, ...) and the XSD type a field is
282
+ declared with (`IbanType`, `CodiceFiscaleType`);
283
+ - **by value**, anywhere in text: IBANs (mod-97), codici fiscali (check
284
+ letter), PANs (Luhn, issuer range), partite IVA after `IT`, emails, and
285
+ `+39` phone numbers.
286
+
287
+ Replacements are keyed HMAC pseudonyms that keep the format and stay valid:
288
+ an Italian IBAN keeps its ABI/CAB and gets a correct CIN and check digits, a
289
+ codice fiscale keeps the holder's sex and a valid check letter, a PAN keeps
290
+ its BIN and Luhn digit, a partita IVA its office code. One key per service
291
+ maps the same input to the same pseudonym everywhere, so relationships
292
+ between request and response survive. Free-text fields (`causale`,
293
+ `descrizione`) are only scanned for identifiers; a name written inside them
294
+ needs a `mask` override in `masking.json` (`{ "fields": { "causale": "mask" } }`),
295
+ which also accepts `keep` for a field wrongly caught by a name rule. Every
296
+ replacement is reported by path, kind, and reason.
297
+
298
+ ## Layout
299
+
300
+ ```text
301
+ soap-ace/
302
+ README.md
303
+ flows/soap-discover.ts catalog + open questions + proposed operation classes
304
+ flows/soap-sample.ts request files, calls, SoapUI import, scenario proposals
305
+ flows/soap-design.ts analysis, 1:1 mapping, design draft and check, OpenAPI projection
306
+ flows/soap-epic.ts ACE repository seed and the story plan epic-stories runs
307
+ packs/ace12-rest/ gates, judge rubric, prompts, and api-style.md (the REST style guide)
308
+ scaffolds/ace12-rest-api/ README, CONTRIBUTING.md, gate scripts for the ACE repository
309
+ patterns/ ESQL pattern cards (esito, faults, amounts and enums, paging, nil)
310
+ flows/lib/soap/ the SOAP library (XML, WSDL/XSD, auth, transport, masking, samples)
311
+ fixtures/demo-bank-soap/ synthetic service (WSDL/XSD, responses, SoapUI project, reference design) and the RUNBOOK
312
+ test/
313
+ ```
@@ -0,0 +1,102 @@
1
+ # Runbook: from a WSDL to ACE 12 branches
2
+
3
+ A developer laptop with Node 22+, llm4ts, a Claude (or other) CLI for the
4
+ reasoning seat, and IBM ACE 12 installed. Rehearse it first on the
5
+ synthetic fixture (every command below works against it with the stub), then
6
+ on a real service in dev, test, or uat, never production.
7
+
8
+ ## 0. Two repositories
9
+
10
+ ```bash
11
+ mkdir -p ~/work/conti-analysis ~/work/conti-ace
12
+ git -C ~/work/conti-analysis init && git -C ~/work/conti-ace init
13
+ cd ~/work/conti-analysis
14
+ ```
15
+
16
+ The analysis repository holds `.llm4ts/soap/<service>/`: catalog, samples,
17
+ analysis, design. The ACE repository receives the contracts and the code.
18
+
19
+ ## 1. Discover
20
+
21
+ ```bash
22
+ llm4ts run soap-discover --repo . ./wsdl/ContiService.wsdl # or an https URL
23
+ ```
24
+
25
+ Read `catalog.md`, especially the open questions. Review
26
+ `operations.md`: every `class:` is `read` or `mutating`; set
27
+ `Status: confirmed`. For a URL behind auth, write `auth.json` first (below)
28
+ and set `LLM4TS_SOAP_SERVICE`.
29
+
30
+ ## 2. Credentials and environment
31
+
32
+ `.llm4ts/soap/<service>/auth.json` (gitignored), references only:
33
+
34
+ ```json
35
+ {
36
+ "environment": "test",
37
+ "endpoint": "https://esb-test.bank.internal/soap/Conti",
38
+ "call": {
39
+ "auth": { "scheme": "basic", "user": "svc-conti", "password": "env:CONTI_PASSWORD" },
40
+ "tls": { "pfx": "file:/secure/client.p12", "passphrase": "env:P12_PASS" }
41
+ },
42
+ "wsSecurity": { "user": "env:WS_USER", "password": "env:WS_PASSWORD", "passwordType": "digest" }
43
+ }
44
+ ```
45
+
46
+ ## 3. Samples
47
+
48
+ ```bash
49
+ llm4ts run soap-sample --repo . "import ./ContiService-soapui-project.xml" # if the team has one
50
+ llm4ts run soap-sample --repo . "propose cercaMovimenti" # scenario drafts
51
+ llm4ts run soap-sample --repo . "init dettaglioConto" # or write one by hand
52
+ llm4ts run soap-sample --repo . "call all"
53
+ llm4ts run soap-sample --repo . -- --allow-mutating revocaBonifico "call revocaBonifico/gia-eseguito"
54
+ llm4ts run soap-sample --repo . list
55
+ ```
56
+
57
+ Replace seeded identifiers with ones the test environment knows before
58
+ calling. Rehearsal without a backend: `LLM4TS_SOAP_STUB=<dir of <operation>.xml>`.
59
+
60
+ ## 4. Analyse and design
61
+
62
+ ```bash
63
+ llm4ts run soap-design --repo . analyse # read analysis/*.md: this is the evidence
64
+ llm4ts run soap-design --repo . design # draft design/api-design.md
65
+ llm4ts run soap-design --repo . check # edit the JSON block, re-check, repeat
66
+ llm4ts run soap-design --repo . revise # optional: let the seat fix what check found
67
+ ```
68
+
69
+ Set `Status: approved` when the check reports no errors and the warnings are
70
+ understood; then `llm4ts run soap-design --repo . openapi`.
71
+
72
+ ## 5. Plan the ACE build
73
+
74
+ ```bash
75
+ llm4ts run soap-epic --repo . doctor
76
+ llm4ts run soap-epic --repo . -- --target ~/work/conti-ace plan
77
+ git -C ~/work/conti-ace add -A && git -C ~/work/conti-ace commit -m "Seed from soap-ace"
78
+ ```
79
+
80
+ Review `.llm4ts/epics/<epic-id>/plan.md` in the ACE repository (edit it to
81
+ re-plan), then run the command `plan` printed:
82
+
83
+ ```bash
84
+ LLM4TS_GATES="bash scripts/ace-gates.sh build;bash scripts/ace-gates.sh test" LLM4TS_WORKTREE_SETUP= \
85
+ llm4ts run epic-stories --repo ~/work/conti-ace -- --concurrency 2 "<the epic sentence>"
86
+ ```
87
+
88
+ Each story lands on its own branch and merges into `epic/<epic-id>` after its
89
+ gates and judge pass; nothing reaches `main` without a human. The board and
90
+ report live under `.llm4ts/epics/<epic-id>/`. Azure DevOps mirroring is the
91
+ optional board of ADR 0011.
92
+
93
+ ## When something is off
94
+
95
+ - `ace-gates.sh` cannot find `mqsiprofile`: set `MQSI_PROFILE` or `ACE_HOME`;
96
+ on a machine without ACE use `scripts/ace-gates-container.sh` with
97
+ `ACE_IMAGE`.
98
+ - Your ACE fix pack spells a command differently: override the whole gate
99
+ with `LLM4TS_GATES`.
100
+ - The service changed: rerun discover, sample, analyse, and `check`; the
101
+ design check points at every citation that broke. `soap-epic plan`
102
+ regenerates contracts and keeps the team's files.