@techspokes/typescript-wsdl-client 1.2.0 → 1.4.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 (43) hide show
  1. package/README.md +13 -0
  2. package/dist/checkedPipeline.d.ts +73 -2
  3. package/dist/checkedPipeline.js +118 -8
  4. package/dist/checkedPlanning.d.ts +5 -2
  5. package/dist/checkedPlanning.js +30 -5
  6. package/dist/checkedResponseInspection.d.ts +100 -0
  7. package/dist/checkedResponseInspection.js +499 -0
  8. package/dist/cli.js +55 -1
  9. package/dist/client/checkedDirectRuntime.d.ts +19 -1
  10. package/dist/client/checkedDirectRuntime.js +27 -2
  11. package/dist/client/directClientRuntime.d.ts +25 -1
  12. package/dist/client/directClientRuntime.js +23 -5
  13. package/dist/client/directClientVersion.d.ts +4 -0
  14. package/dist/client/directClientVersion.js +4 -0
  15. package/dist/client/generateDirectClient.js +14 -9
  16. package/dist/compiler/componentRecordProjection.d.ts +1 -0
  17. package/dist/compiler/componentRecordProjection.js +15 -4
  18. package/dist/compiler/directCodec.js +4 -12
  19. package/dist/compiler/directOperation.d.ts +2 -1
  20. package/dist/compiler/directOperation.js +27 -11
  21. package/dist/compiler/directSoap.d.ts +87 -12
  22. package/dist/compiler/directSoap.js +316 -94
  23. package/dist/compiler/directSoapSource.js +3 -2
  24. package/dist/compiler/directSourceCapsule.js +12 -3
  25. package/dist/compiler/soapHeaders.d.ts +10 -4
  26. package/dist/compiler/soapHeaders.js +28 -9
  27. package/dist/compiler/sourceCorrections.d.ts +118 -0
  28. package/dist/compiler/sourceCorrections.js +794 -0
  29. package/dist/gateway/generateCheckedGateway.js +27 -8
  30. package/dist/index.d.ts +3 -1
  31. package/dist/index.js +2 -1
  32. package/dist/util/readBoundedFile.d.ts +9 -0
  33. package/dist/util/readBoundedFile.js +36 -0
  34. package/dist/util/xmlBytes.d.ts +9 -0
  35. package/dist/util/xmlBytes.js +24 -0
  36. package/docs/api-reference.md +28 -1
  37. package/docs/checked-gateway.md +52 -0
  38. package/docs/checked-generation.md +4 -4
  39. package/docs/cli-reference.md +16 -1
  40. package/docs/migration.md +20 -0
  41. package/docs/start-here.md +1 -1
  42. package/docs/supported-patterns.md +6 -0
  43. package/package.json +1 -1
package/README.md CHANGED
@@ -9,6 +9,14 @@ Turn legacy WSDL/SOAP services into typed TypeScript clients and optional REST A
9
9
 
10
10
  Created from production integration work at [TechSpokes](https://www.techspokes.com); built for teams modernizing real enterprise SOAP integrations.
11
11
 
12
+ ## Upgrading to 1.4.0
13
+
14
+ Version 1.4.0 adds explicit JSON source corrections, offline SOAP response inspection, and checked SOAP 1.1 protocol faults without invented application values. Existing runtime bindings remain available; regenerate matched SOAP 1.1 artifacts to adopt the additive V3 binding. Read the [1.3.x migration guidance](docs/migration.md#from-13x-to-140).
15
+
16
+ ## Upgrading to 1.3.0
17
+
18
+ Version 1.3.0 adds opt-in checked SOAP 1.2 document/literal execution through the existing adapter. Existing SOAP 1.1 and direct-runtime bindings retain their contracts. SOAP 1.2 clients use an additive runtime handshake; regenerate matched artifacts when adopting that binding. Read the [1.2.x migration guidance](docs/migration.md#from-12x-to-130) for adoption and evidence limits.
19
+
12
20
  ## Upgrading to 1.2.0
13
21
 
14
22
  Existing projects keep legacy generation unless they explicitly choose another profile. Regeneration can change affected DTO names, stream records and corrected scalar/simple-content results; 1.2.0 is not an unconditional drop-in regeneration upgrade. Read the [1.1.x migration guide](docs/migration.md#from-11x-to-120) before updating, including matched artifact regeneration, scaffold preservation, consumer checks and rollback.
@@ -250,7 +258,10 @@ See [CLI Reference](docs/cli-reference.md) for all flags and examples.
250
258
  | [Agent Skill Artifact](docs/agent-skill.md) | Release ZIP for consumer-project AI agents |
251
259
  | [Version Migration](docs/migration.md) | Upgrading between package versions |
252
260
  | [1.2.0 Release Notes](https://github.com/TechSpokes/typescript-wsdl-client/blob/main/docs/releases/v1.2.0.md) | Composition, consumer compatibility and bounded RPC qualification |
261
+ | [1.4.0 Release Notes](https://github.com/TechSpokes/typescript-wsdl-client/blob/main/docs/releases/v1.4.0.md) | Contract corrections, offline inspection and SOAP 1.1 protocol faults |
262
+ | [1.3.0 Release Notes](https://github.com/TechSpokes/typescript-wsdl-client/blob/main/docs/releases/v1.3.0.md) | Checked SOAP 1.2 execution and preserved SOAP 1.1 compatibility |
253
263
  | [XML/JSON Mapping Contract](docs/mapping-contract.md) | Checked keyed, namespace, nil and group representations |
264
+ | [Protocol Fault Compatibility](https://github.com/TechSpokes/typescript-wsdl-client/blob/main/docs/decisions/005-checked-protocol-fault-compatibility.md) | Runtime adoption and complete-source inspection decisions |
254
265
  | [Product Corpus](docs/corpus.md) | Representative public inputs and their executable evidence |
255
266
 
256
267
  ### Contributing
@@ -323,6 +334,8 @@ Built and maintained by [TechSpokes](https://www.techspokes.com). Created by [Se
323
334
 
324
335
  ## Schema assessment diagnostics
325
336
 
337
+ For saved SOAP responses, use `wsdl-tsc inspect-response` to assess a checked contract offline and optionally produce a source correction draft. XML-only reports leave HTTP checks unassessed. Checked generation accepts consumer-owned external JSON corrections through `sourceCorrectionsFile`; generated runtime artifacts use the effective metadata without needing the recipe. See [source corrections and response inspection](docs/checked-gateway.md#consumer-owned-source-corrections) for configuration, limits and the update workflow.
338
+
326
339
  `assess` is an additive diagnostic command. It writes a versioned JSON report without generating artifacts or dispatching SOAP.
327
340
 
328
341
  ```bash
@@ -2,12 +2,14 @@ import type { PipelineOptions } from './pipeline.js';
2
2
  import type { DirectOperationSelector } from './compiler/directOperation.js';
3
3
  import type { ReconstructedComponentSource } from './compiler/directSourceCapsule.js';
4
4
  import type { GeneratedComponentClient } from './client/generateDirectClient.js';
5
- import type { SchemaInput } from './loader/schemaInput.js';
5
+ import type { SchemaInput, SchemaInputOptions } from './loader/schemaInput.js';
6
+ import { prepareCorrectedSourceInputs } from './compiler/sourceCorrections.js';
6
7
  import type { ComponentRecordProjectionSpec, ComponentRecordProjectionAssociation, ComponentRecordProjection } from './compiler/componentRecordProjection.js';
7
8
  import type { ApplicationGroupsOptions, ApplicationGroupTarget, ApplicationGroupRecord } from './compiler/applicationGroups.js';
8
9
  /** Explicit service operations and independently authorized snapshots for component generation. */
9
10
  export interface CheckedComponentOptions {
10
11
  readonly applicationGroups?: ApplicationGroupsOptions;
12
+ readonly sourceCorrectionsFile?: string;
11
13
  readonly service: {
12
14
  readonly namespace: string;
13
15
  readonly local: string;
@@ -85,6 +87,57 @@ export declare function parseCheckedComponentOptions(value: unknown): CheckedCom
85
87
  * @throws TypeError For malformed or unsupported controls.
86
88
  */
87
89
  export declare function parseCheckedComponentChoices(value: unknown): Partial<CheckedComponentOptions>;
90
+ export interface PreparedCheckedSourceInputs {
91
+ readonly main: SchemaInput;
92
+ readonly companions: ReadonlyMap<string, SchemaInput>;
93
+ readonly provenance: Awaited<ReturnType<typeof prepareCorrectedSourceInputs>>['provenance'] & {
94
+ readonly recipeFile: {
95
+ readonly digest: string;
96
+ };
97
+ };
98
+ }
99
+ /**
100
+ * Acquire the complete authorized original source closure for correction and diagnostic inventory.
101
+ * @param source Main source location, retaining its configured logical URI.
102
+ * @param config Explicit loading scope and companion sources.
103
+ * @param acquiredMain Already acquired main closure, reused without another source read.
104
+ * @returns Original main and companion inputs with their shared loading policy and work accounting.
105
+ * @throws For unavailable or unauthorized sources and exhausted source limits.
106
+ * @constraints Acquisition does not qualify sources or grant response authority.
107
+ */
108
+ export declare function acquireCheckedSourceInputs(source: string, config: Pick<CheckedComponentOptions, 'fileRoots' | 'sourceOrigins' | 'sourceResources'> & Partial<CheckedComponentOptions>, acquiredMain?: SchemaInput): Promise<{
109
+ main: Readonly<{
110
+ root: import("./loader/orderedSyntax.js").SyntaxDocument;
111
+ documents: readonly import("./loader/orderedSyntax.js").SyntaxDocument[];
112
+ schemas: readonly import("./loader/schemaInput.js").SchemaInterpretation[];
113
+ resources: readonly import("./loader/schemaResources.js").ResourceMetadata[];
114
+ resourceAliases: ReadonlyMap<string, string>;
115
+ edges: readonly import("./loader/schemaInput.js").ResolutionEdge[];
116
+ limits: import("./loader/schemaResources.js").LoadingLimits;
117
+ metrics: Readonly<{
118
+ resources: number;
119
+ totalBytes: number;
120
+ ioMs: number;
121
+ }>;
122
+ }>;
123
+ companions: Map<string, Readonly<{
124
+ root: import("./loader/orderedSyntax.js").SyntaxDocument;
125
+ documents: readonly import("./loader/orderedSyntax.js").SyntaxDocument[];
126
+ schemas: readonly import("./loader/schemaInput.js").SchemaInterpretation[];
127
+ resources: readonly import("./loader/schemaResources.js").ResourceMetadata[];
128
+ resourceAliases: ReadonlyMap<string, string>;
129
+ edges: readonly import("./loader/schemaInput.js").ResolutionEdge[];
130
+ limits: import("./loader/schemaResources.js").LoadingLimits;
131
+ metrics: Readonly<{
132
+ resources: number;
133
+ totalBytes: number;
134
+ ioMs: number;
135
+ }>;
136
+ }>>;
137
+ loading: SchemaInputOptions;
138
+ }>;
139
+ /** Correct and reload the complete authorized project closure without mutating original sources. */
140
+ export declare function prepareCheckedSourceInputs(source: string, config: Pick<CheckedComponentOptions, 'fileRoots' | 'sourceOrigins' | 'sourceResources'> & Partial<CheckedComponentOptions>, acquiredMain?: SchemaInput): Promise<PreparedCheckedSourceInputs>;
88
141
  /**
89
142
  * Generate the bounded preview without creating a flattened catalog authority.
90
143
  * @param opts Public pipeline options with the checked model explicitly selected.
@@ -104,7 +157,25 @@ export declare function prepareComponentStages(wsdl: string, config: CheckedComp
104
157
  gatewayDir: string;
105
158
  openapiFile: string;
106
159
  basePath: string;
107
- }): Promise<{
160
+ }, sourcePreparation?: PreparedCheckedSourceInputs): Promise<{
161
+ sourceCorrections?: (Readonly<{
162
+ version: 1;
163
+ canonicalRecipeDigest: string;
164
+ resources: readonly Readonly<{
165
+ uri: string;
166
+ originalDigest: string;
167
+ effectiveDigest: string;
168
+ changed: boolean;
169
+ }>[];
170
+ corrections: readonly Readonly<{
171
+ kind: "set-occurs" | "set-wildcard-namespaces" | "insert-element";
172
+ target: import("./compiler/sourceCorrections.js").SourceCorrectionSelector;
173
+ }>[];
174
+ }> & {
175
+ readonly recipeFile: {
176
+ readonly digest: string;
177
+ };
178
+ }) | undefined;
108
179
  expected: ReconstructedComponentSource[];
109
180
  projections: Map<string, ComponentRecordProjection>;
110
181
  namespaces: Map<string, string>;
@@ -9,12 +9,14 @@ import { fileURLToPath } from 'node:url';
9
9
  import { pathToFileURL } from 'node:url';
10
10
  import { types } from 'node:util';
11
11
  import { createHash } from 'node:crypto';
12
+ import { constants as bufferConstants } from 'node:buffer';
12
13
  import { resolveResourceUri } from './loader/schemaResources.js';
13
14
  import { selectComponentOperations, inspectDirectOperation } from './compiler/directOperation.js';
14
15
  import { prepareComponentSourceForOperation } from './compiler/directSourceCapsule.js';
15
16
  import { inspectComponentSourceService } from './compiler/directSourceCapsule.js';
16
17
  import { generateComponentClient, writeComponentServiceClient } from './client/generateDirectClient.js';
17
- import { copyOriginalSchemaResource, prepareSchemaSnapshotFiles } from './loader/schemaInput.js';
18
+ import { copyOriginalSchemaResource, loadSemanticSchemaInput, prepareSchemaSnapshotFiles } from './loader/schemaInput.js';
19
+ import { prepareCorrectedSourceInputs, SourceCorrectionError } from './compiler/sourceCorrections.js';
18
20
  import { createApplicationBudget } from './compiler/semanticBudget.js';
19
21
  import { directLoadingLimits } from './compiler/directExecutionPolicy.js';
20
22
  import { generateComponentServiceGateway } from './gateway/generateCheckedGateway.js';
@@ -86,7 +88,7 @@ function parseComponentOptions(value, resolved) {
86
88
  throw new TypeError('Known unique standard libraries required');
87
89
  return ids.sort();
88
90
  };
89
- const data = record(value, ['selectors', 'service', 'port', 'fileRoots', 'sourceResources', 'sourceOrigins', 'sourceUri', 'namespaceLocations', 'basePath', 'signal', 'standardLibraries', 'recordProjections', 'applicationGroups'], resolved ? ['selectors', 'service', 'port', 'fileRoots', 'sourceResources', 'sourceOrigins'] : []);
91
+ const data = record(value, ['selectors', 'service', 'port', 'fileRoots', 'sourceResources', 'sourceOrigins', 'sourceUri', 'namespaceLocations', 'basePath', 'signal', 'standardLibraries', 'recordProjections', 'applicationGroups', 'sourceCorrectionsFile'], resolved ? ['selectors', 'service', 'port', 'fileRoots', 'sourceResources', 'sourceOrigins'] : []);
90
92
  if (['selectors', 'service', 'port', 'fileRoots', 'sourceResources', 'sourceOrigins'].some(key => Object.hasOwn(data, key) && data[key] === undefined))
91
93
  throw new TypeError('Explicit component choices must not be undefined');
92
94
  const selectors = data.selectors === undefined ? undefined : array(data.selectors).map(selector);
@@ -142,6 +144,7 @@ function parseComponentOptions(value, resolved) {
142
144
  if (data.signal !== undefined && !(data.signal instanceof AbortSignal))
143
145
  throw new TypeError('Native AbortSignal required');
144
146
  return { ...(selectors ? { selectors } : {}), ...(data.service === undefined ? {} : { service: name(data.service) }),
147
+ ...(data.sourceCorrectionsFile === undefined ? {} : { sourceCorrectionsFile: text(data.sourceCorrectionsFile) }),
145
148
  ...(Object.hasOwn(data, 'applicationGroups') ? { applicationGroups: parseApplicationGroups(data.applicationGroups) } : {}),
146
149
  ...(data.port === undefined ? {} : { port: text(data.port) }), ...(fileRoots ? { fileRoots } : {}),
147
150
  ...(sourceResources ? { sourceResources } : {}), ...(sourceOrigins ? { sourceOrigins } : {}),
@@ -149,6 +152,98 @@ function parseComponentOptions(value, resolved) {
149
152
  ...(standardLibraries ? { standardLibraries } : {}), ...(recordProjections ? { recordProjections } : {}),
150
153
  ...(namespaceLocations ? { namespaceLocations } : {}), ...(basePath ? { basePath } : {}), ...(data.signal ? { signal: data.signal } : {}) };
151
154
  }
155
+ /**
156
+ * Acquire the complete authorized original source closure for correction and diagnostic inventory.
157
+ * @param source Main source location, retaining its configured logical URI.
158
+ * @param config Explicit loading scope and companion sources.
159
+ * @param acquiredMain Already acquired main closure, reused without another source read.
160
+ * @returns Original main and companion inputs with their shared loading policy and work accounting.
161
+ * @throws For unavailable or unauthorized sources and exhausted source limits.
162
+ * @constraints Acquisition does not qualify sources or grant response authority.
163
+ */
164
+ export async function acquireCheckedSourceInputs(source, config, acquiredMain) {
165
+ const tick = () => config.signal?.throwIfAborted();
166
+ const budget = createApplicationBudget();
167
+ tick();
168
+ const loading = { policy: { fileRoots: config.fileRoots, allowedOrigins: config.sourceOrigins },
169
+ namespaceLocations: new Map(config.namespaceLocations?.map(item => [item.namespace, item.location])), signal: config.signal,
170
+ limits: acquiredMain?.limits, chargeWork: amount => { tick(); budget.chargeWork(amount); } };
171
+ const acquire = async (reference, main) => {
172
+ tick();
173
+ const prepared = await prepareSchemaSnapshotFiles(reference, config.sourceResources, { ...loading,
174
+ ...(main && config.sourceUri ? { sourceUri: config.sourceUri } : {}) });
175
+ return loadSemanticSchemaInput(main && config.sourceUri ? config.sourceUri : prepared.source, prepared.loading);
176
+ };
177
+ const main = acquiredMain ?? await acquire(source, true);
178
+ loading.limits = main.limits;
179
+ const companions = new Map();
180
+ for (const projection of config.recordProjections ?? []) {
181
+ tick();
182
+ const key = resolveResourceUri(projection.source);
183
+ if (!companions.has(key))
184
+ companions.set(key, key === main.root.uri ? main : await acquire(projection.source, false));
185
+ }
186
+ return { main, companions, loading };
187
+ }
188
+ /** Correct and reload the complete authorized project closure without mutating original sources. */
189
+ export async function prepareCheckedSourceInputs(source, config, acquiredMain) {
190
+ if (!config.sourceCorrectionsFile)
191
+ throw new TypeError('Source correction preparation requires sourceCorrectionsFile');
192
+ const { main, companions, loading } = await acquireCheckedSourceInputs(source, config, acquiredMain);
193
+ const tick = () => config.signal?.throwIfAborted();
194
+ const recipeFile = path.resolve(config.sourceCorrectionsFile);
195
+ tick();
196
+ const handle = await fs.promises.open(recipeFile, 'r');
197
+ let bytes;
198
+ try {
199
+ const stat = await handle.stat();
200
+ const maximum = Math.min(main.limits.resourceBytes, bufferConstants.MAX_LENGTH - 1);
201
+ if (!stat.isFile() || !Number.isSafeInteger(stat.size) || stat.size > maximum)
202
+ throw new RangeError('Source correction recipe exceeds the source resource byte capacity');
203
+ // @constraints One extra byte detects growth without permitting an unbounded read after the size check.
204
+ const buffer = Buffer.alloc(stat.size + 1);
205
+ let offset = 0;
206
+ while (offset < buffer.length) {
207
+ tick();
208
+ const result = await handle.read(buffer, offset, buffer.length - offset, null);
209
+ if (!result.bytesRead)
210
+ break;
211
+ offset += result.bytesRead;
212
+ }
213
+ tick();
214
+ if (offset !== stat.size)
215
+ throw new TypeError('Source correction recipe changed during acquisition');
216
+ bytes = buffer.subarray(0, offset);
217
+ loading.chargeWork?.(512 + bytes.byteLength * 8);
218
+ }
219
+ finally {
220
+ await handle.close();
221
+ }
222
+ tick();
223
+ let recipe;
224
+ try {
225
+ recipe = JSON.parse(bytes.toString('utf8'));
226
+ }
227
+ catch {
228
+ throw new SourceCorrectionError('invalid-recipe', 'Source correction file must contain valid JSON');
229
+ }
230
+ const corrected = prepareCorrectedSourceInputs([main, ...companions.values()], recipe, config.signal);
231
+ const effective = new Map();
232
+ const reload = async (input) => {
233
+ const previous = effective.get(input.root.uri);
234
+ if (previous)
235
+ return previous;
236
+ const result = await loadSemanticSchemaInput(input.root.uri, { ...loading, offlineResources: corrected.resources });
237
+ effective.set(input.root.uri, result);
238
+ return result;
239
+ };
240
+ const effectiveMain = await reload(main);
241
+ const effectiveCompanions = new Map();
242
+ for (const [key, input] of companions)
243
+ effectiveCompanions.set(key, await reload(input));
244
+ return { main: effectiveMain, companions: effectiveCompanions, provenance: { ...corrected.provenance,
245
+ recipeFile: { digest: createHash('sha256').update(bytes).digest('hex') } } };
246
+ }
152
247
  /**
153
248
  * Generate the bounded preview without creating a flattened catalog authority.
154
249
  * @param opts Public pipeline options with the checked model explicitly selected.
@@ -210,7 +305,9 @@ export async function runCheckedPipeline(opts) {
210
305
  basePath: planning.config.basePath ?? `/${versionSlug}/${serviceSlug}` }, planning.native, planning.report);
211
306
  }
212
307
  /** Prepare through native owners only; internal handles remain within this invocation. */
213
- export async function prepareComponentStages(wsdl, config, acquired, outputs = { clientDir: 'client', gatewayDir: 'gateway', openapiFile: 'openapi.json', basePath: '/v1/service' }) {
308
+ export async function prepareComponentStages(wsdl, config, acquired, outputs = { clientDir: 'client', gatewayDir: 'gateway', openapiFile: 'openapi.json', basePath: '/v1/service' }, sourcePreparation) {
309
+ const correctedSources = sourcePreparation ?? (config.sourceCorrectionsFile ? await prepareCheckedSourceInputs(wsdl, config, acquired) : undefined);
310
+ acquired = correctedSources?.main ?? acquired;
214
311
  const tick = () => { if (config.signal?.aborted)
215
312
  throw new Error('Checked component generation cancelled'); };
216
313
  const namespaces = new Map(config.namespaceLocations?.map(location => [location.namespace, location.location]));
@@ -242,10 +339,16 @@ export async function prepareComponentStages(wsdl, config, acquired, outputs = {
242
339
  for (const group of groups.values()) {
243
340
  tick();
244
341
  const first = group[0];
245
- const source = await prepareSchemaSnapshotFiles(first.source, config.sourceResources, { policy: {
246
- fileRoots: config.fileRoots, allowedOrigins: config.sourceOrigins
247
- }, namespaceLocations: namespaces,
248
- limits: directLoadingLimits(budget), signal: config.signal, chargeWork: amount => { tick(); budget.chargeWork(amount); } });
342
+ const companionInput = correctedSources?.companions.get(resolveResourceUri(first.source));
343
+ const companionOffline = companionInput ? new Map([...companionInput.resourceAliases].map(([alias, uri]) => {
344
+ const resource = companionInput.resources.find(item => item.uri === uri);
345
+ return [alias, { bytes: copyOriginalSchemaResource(companionInput, uri, loading.chargeWork), digest: resource.digest, canonicalUri: uri }];
346
+ })) : undefined;
347
+ const source = companionInput ? { source: companionInput.root.uri, loading: { ...loading, offlineResources: companionOffline } }
348
+ : await prepareSchemaSnapshotFiles(first.source, config.sourceResources, { policy: {
349
+ fileRoots: config.fileRoots, allowedOrigins: config.sourceOrigins
350
+ }, namespaceLocations: namespaces,
351
+ limits: directLoadingLimits(budget), signal: config.signal, chargeWork: amount => { tick(); budget.chargeWork(amount); } });
249
352
  const companions = await selectComponentOperations(source.source, group.map(item => item.recordSelector), { loading: source.loading,
250
353
  signal: config.signal, standardLibraries: first.standardLibraries?.map(acquireStandardSchemaLibrary) });
251
354
  for (const [index, entry] of group.entries()) {
@@ -340,7 +443,8 @@ export async function prepareComponentStages(wsdl, config, acquired, outputs = {
340
443
  }
341
444
  }
342
445
  }
343
- return { expected, projections, namespaces, refusals, layout, generatedClients, gateway, applicationGroups };
446
+ return { expected, projections, namespaces, refusals, layout, generatedClients, gateway, applicationGroups,
447
+ ...(correctedSources ? { sourceCorrections: correctedSources.provenance } : {}) };
344
448
  }
345
449
  function prepareComponentLayout(expected, projections, tick) {
346
450
  const inspection = inspectDirectOperation(expected[0].contract), input = inspection.source;
@@ -421,6 +525,12 @@ async function runComponentStages(opts, config, outputs, native, report) {
421
525
  ...(native.applicationGroups ? { applicationGroups: native.applicationGroups } : {}),
422
526
  selection: { selector: config.selectors[0], selectors: config.selectors, service: config.service, port: config.port }, operations };
423
527
  fs.writeFileSync(path.join(clientDir, 'inventory.json'), JSON.stringify(inventory, null, 2) + '\n');
528
+ // @why This generator-owned manifest must describe the current generation, including removal of corrections.
529
+ const correctionManifest = path.join(clientDir, 'source-corrections.json');
530
+ if (native.sourceCorrections)
531
+ fs.writeFileSync(correctionManifest, JSON.stringify(native.sourceCorrections, null, 2) + '\n');
532
+ else
533
+ fs.rmSync(correctionManifest, { force: true });
424
534
  const component = { source: root, capsules: Object.fromEntries(Object.entries(contracts).map(([name, file]) => [name, path.resolve(clientDir, file)])),
425
535
  sourceResources, sourceOrigins: config.sourceOrigins, ...(new URL(input.root.uri).protocol === 'file:' ? {} : { sourceUri: input.root.uri }),
426
536
  ...(namespaceLocations.length ? { namespaceLocations } : {}),
@@ -1,6 +1,7 @@
1
1
  import { prepareComponentStages } from './checkedPipeline.js';
2
- import type { CheckedComponentOptions } from './checkedPipeline.js';
2
+ import type { CheckedComponentOptions, PreparedCheckedSourceInputs } from './checkedPipeline.js';
3
3
  import type { DirectOperationSelector } from './compiler/directOperation.js';
4
+ import type { SchemaInput } from './loader/schemaInput.js';
4
5
  import type { ExpandedName } from './loader/orderedSyntax.js';
5
6
  type Category = 'missing-dependency' | 'permission' | 'ambiguous-selection' | 'invalid-source' | 'unsupported-capability' | 'inconclusive' | 'cancelled' | 'resource-limit' | 'operational-failure' | 'configuration';
6
7
  export interface ComponentPlanBlocker {
@@ -115,9 +116,11 @@ export declare function prepareCheckedComponentPlan(source: string, choices?: un
115
116
  gatewayDir: string;
116
117
  openapiFile: string;
117
118
  basePath: string;
118
- }): Promise<{
119
+ }, inspectionSelector?: DirectOperationSelector): Promise<{
119
120
  report: CheckedComponentPlan;
120
121
  config?: CheckedComponentOptions;
121
122
  native?: Awaited<ReturnType<typeof prepareComponentStages>>;
123
+ sources?: PreparedCheckedSourceInputs;
124
+ sourceInput?: SchemaInput;
122
125
  }>;
123
126
  export {};
@@ -6,9 +6,10 @@ import path from 'node:path';
6
6
  import fs from 'node:fs';
7
7
  import { fileURLToPath } from 'node:url';
8
8
  import { types } from 'node:util';
9
- import { parseCheckedComponentOptions, parseCheckedComponentChoices, prepareComponentStages } from './checkedPipeline.js';
9
+ import { parseCheckedComponentOptions, parseCheckedComponentChoices, prepareComponentStages, prepareCheckedSourceInputs } from './checkedPipeline.js';
10
10
  import { inspectDirectOperation } from './compiler/directOperation.js';
11
11
  import { loadSemanticSchemaInput, prepareSchemaSnapshotFiles } from './loader/schemaInput.js';
12
+ import { SourceCorrectionError } from './compiler/sourceCorrections.js';
12
13
  import { resolveResourceUri } from './loader/schemaResources.js';
13
14
  import { syntaxElements, syntaxAttribute, resolveLexicalQName, WSDL_NAMESPACE, XSD_NAMESPACE, SchemaLoadingError } from './loader/orderedSyntax.js';
14
15
  const same = (a, b) => a.namespace === b.namespace && a.local === b.local;
@@ -55,7 +56,7 @@ export function writeCheckedPlanningFile(file, value, force = false) {
55
56
  fs.writeFileSync(file, JSON.stringify(value, null, 2) + '\n', { flag: force ? 'w' : 'wx' });
56
57
  }
57
58
  // @constraints Internal composition only. Native handles are never attached to the public report.
58
- export async function prepareCheckedComponentPlan(source, choices = {}, outputs) {
59
+ export async function prepareCheckedComponentPlan(source, choices = {}, outputs, inspectionSelector) {
59
60
  const documents = [], blockers = [];
60
61
  let complete = false, config;
61
62
  let intake;
@@ -104,6 +105,8 @@ export async function prepareCheckedComponentPlan(source, choices = {}, outputs)
104
105
  order(operations);
105
106
  };
106
107
  let native;
108
+ let correctedSources;
109
+ let sourceInput;
107
110
  let preparingNative = false;
108
111
  try {
109
112
  if (!choices || typeof choices !== 'object' || types.isProxy(choices) || ![null, Object.prototype].includes(Object.getPrototypeOf(choices)))
@@ -136,7 +139,25 @@ export async function prepareCheckedComponentPlan(source, choices = {}, outputs)
136
139
  namespaceLocations: new Map(intake.namespaceLocations?.map(n => [n.namespace, n.location])),
137
140
  onDocument: (document) => { documents.push(document); } };
138
141
  const prepared = Object.hasOwn(explicit, 'sourceResources') ? await prepareSchemaSnapshotFiles(source, intake.sourceResources, loading) : undefined;
139
- const input = await loadSemanticSchemaInput(intake.sourceUri ?? prepared?.source ?? source, prepared?.loading ?? loading);
142
+ let input = await loadSemanticSchemaInput(intake.sourceUri ?? prepared?.source ?? source, prepared?.loading ?? loading);
143
+ sourceInput = input;
144
+ correctedSources = intake.sourceCorrectionsFile ? await prepareCheckedSourceInputs(source, intake, input) : undefined;
145
+ if (correctedSources) {
146
+ input = correctedSources.main;
147
+ sourceInput = input;
148
+ documents.splice(0, documents.length, ...input.documents);
149
+ }
150
+ // @why Correction pins authenticate the complete project closure. Narrow only
151
+ // after preparation, so inspecting one operation cannot discard pinned companions.
152
+ if (inspectionSelector) {
153
+ const selected = (candidate) => candidate.operation === inspectionSelector.operation
154
+ && same(candidate.portType, inspectionSelector.portType) && same(candidate.binding, inspectionSelector.binding);
155
+ if (intake.selectors && !intake.selectors.some(selected))
156
+ throw new TypeError('Inspection selector is not a configured operation');
157
+ intake = { ...intake, selectors: [inspectionSelector],
158
+ ...(intake.recordProjections ? { recordProjections: intake.recordProjections.filter(entry => selected(entry.selector)) } : {}) };
159
+ explicit.selectors = [inspectionSelector];
160
+ }
140
161
  complete = true;
141
162
  inventory();
142
163
  const possible = candidates.filter(c => (!Object.hasOwn(explicit, 'service') || intake.service !== undefined && same(c.service, intake.service))
@@ -178,7 +199,7 @@ export async function prepareCheckedComponentPlan(source, choices = {}, outputs)
178
199
  config = { ...config, sourceResources: [...config.sourceResources, ...input.resources.filter(r => r.uri !== input.root.uri && !known.has(r.uri) && new URL(r.uri).protocol === 'file:')
179
200
  .map(r => ({ uri: r.uri, file: fileURLToPath(r.uri) }))] };
180
201
  preparingNative = true;
181
- native = await prepareComponentStages(source, config, !prepared && new URL(input.root.uri).protocol !== 'file:' ? input : undefined, outputs);
202
+ native = await prepareComponentStages(source, config, correctedSources ? input : !prepared && new URL(input.root.uri).protocol !== 'file:' ? input : undefined, outputs, correctedSources);
182
203
  if (native.expected.length) {
183
204
  const fresh = inspectDirectOperation(native.expected[0].contract).source;
184
205
  if (input.resources.length !== fresh.resources.length || input.resources.some(r => !fresh.resources.some(n => n.uri === r.uri && n.digest === r.digest))) {
@@ -233,6 +254,9 @@ export async function prepareCheckedComponentPlan(source, choices = {}, outputs)
233
254
  }
234
255
  if (intake?.signal?.aborted)
235
256
  add('cancelled', complete ? 'native-preparation' : 'loading', 'caller-cancelled', 'Rerun with an active signal.', 'signal');
257
+ else if (error instanceof SourceCorrectionError) {
258
+ add(error.category === 'resource-limit' ? 'resource-limit' : error.category === 'cancelled' ? 'cancelled' : 'configuration', 'configuration', `source-corrections:${error.category}`, 'Review the pinned correction file against the original source inventory; regenerate stale drafts or supply replacement snapshots for unsupported edits.', 'checkedComponents.sourceCorrectionsFile', selectors);
259
+ }
236
260
  else if (error instanceof SchemaLoadingError && error.message === 'Duplicate logical schema snapshot') {
237
261
  add('configuration', 'configuration', 'duplicate-logical-snapshot', 'Supply unique logical snapshot identities.', 'checkedComponents.sourceResources');
238
262
  }
@@ -264,5 +288,6 @@ export async function prepareCheckedComponentPlan(source, choices = {}, outputs)
264
288
  instructions: 'Save configuration as checked JSON. Replace REQUIRED_SCHEMA_LOCATION with an authorized schema path; paths resolve from the invocation working directory. Rerun the same pipeline with --checked-config. Reports never authorize generation.',
265
289
  configuration: { ...Object.fromEntries(Object.entries(explicit).filter(([key]) => key !== 'signal')), namespaceLocations: unresolved }
266
290
  } } : {}) };
267
- return { report, ...(config ? { config } : {}), ...(native ? { native } : {}) };
291
+ return { report, ...(config ? { config } : {}), ...(native ? { native } : {}), ...(correctedSources ? { sources: correctedSources } : {}),
292
+ ...(sourceInput ? { sourceInput } : {}) };
268
293
  }
@@ -0,0 +1,100 @@
1
+ import type { CheckedComponentOptions } from './checkedPipeline.js';
2
+ import type { DirectOperationSelector } from './compiler/directOperation.js';
3
+ import type { ObservedSoapResponseMetadata } from './compiler/directSoap.js';
4
+ import type { DirectXmlLimits } from './compiler/directCodec.js';
5
+ export interface CheckedResponseInspectionOptions {
6
+ readonly checkedComponents?: Partial<CheckedComponentOptions>;
7
+ readonly selector: DirectOperationSelector;
8
+ readonly responseBytes: Uint8Array;
9
+ readonly responseMetadata?: ObservedSoapResponseMetadata;
10
+ readonly signal?: AbortSignal;
11
+ readonly limits?: DirectXmlLimits;
12
+ readonly includeCorrectionDraft?: boolean;
13
+ }
14
+ type Assessment = 'conforming' | 'mismatch' | 'refused' | 'unassessed';
15
+ interface ScopedOutcome {
16
+ readonly assessment: Assessment;
17
+ readonly rule?: string;
18
+ }
19
+ interface InspectionLocation {
20
+ readonly resource: string;
21
+ readonly digest: string;
22
+ readonly path: string;
23
+ readonly line: number;
24
+ readonly column: number;
25
+ }
26
+ interface InspectionDiagnostic {
27
+ readonly scope: 'preparation' | 'response' | 'wrapper' | 'companion';
28
+ readonly kind: string;
29
+ readonly rule: string;
30
+ readonly guidance: string;
31
+ readonly source?: InspectionLocation;
32
+ readonly schemaPath?: string;
33
+ readonly payloadPath?: string;
34
+ }
35
+ /** A non-applicable recipe draft; null fields require an explicit consumer decision.
36
+ * @since Unreleased
37
+ */
38
+ export interface CheckedResponseCorrectionDraft {
39
+ readonly version: 1;
40
+ readonly resources: readonly {
41
+ readonly uri: string;
42
+ readonly digest: string;
43
+ }[];
44
+ readonly corrections: readonly Readonly<Record<string, unknown>>[];
45
+ }
46
+ /** Bounded diagnostic data without XML, application values, native handles or acceptance authority.
47
+ * @since Unreleased
48
+ */
49
+ export interface CheckedResponseInspectionReport {
50
+ readonly version: 'checked-response-inspection-v1';
51
+ readonly kind: 'conforming' | 'mismatch' | 'refused';
52
+ readonly scope: 'xml-only' | 'full-response';
53
+ readonly admitted: false;
54
+ readonly executed: false;
55
+ readonly selector: DirectOperationSelector;
56
+ readonly response: {
57
+ readonly digest?: string;
58
+ readonly digestAssessment: 'computed' | 'unassessed-limit' | 'unassessed-cancelled';
59
+ readonly byteLength: number;
60
+ };
61
+ readonly http: {
62
+ readonly assessment: Assessment | 'not-established';
63
+ readonly status?: number;
64
+ };
65
+ readonly fault: {
66
+ readonly assessment: 'unassessed' | 'conforming';
67
+ readonly observed?: boolean;
68
+ readonly declared?: boolean;
69
+ };
70
+ readonly resources: readonly {
71
+ readonly resource: string;
72
+ readonly digest: string;
73
+ }[];
74
+ readonly outcomes: {
75
+ readonly response: ScopedOutcome;
76
+ readonly wrapper?: ScopedOutcome;
77
+ readonly companion?: ScopedOutcome;
78
+ };
79
+ readonly diagnostics: readonly InspectionDiagnostic[];
80
+ readonly correctionDraft?: CheckedResponseCorrectionDraft;
81
+ readonly sourceCorrections?: {
82
+ readonly recipeDigest: string;
83
+ readonly resources: readonly {
84
+ readonly resource: string;
85
+ readonly originalDigest: string;
86
+ readonly effectiveDigest: string;
87
+ readonly changed: boolean;
88
+ }[];
89
+ };
90
+ readonly truncated: boolean;
91
+ }
92
+ /** Inspect actual caller-owned SOAP bytes without constructing a request or contacting any endpoint.
93
+ * @param source Local WSDL path, retaining optional configured logical snapshot identities.
94
+ * @param options Full operation selector, closed checked choices, actual bytes and optional actual HTTP metadata.
95
+ * @returns A bounded value-redacted report; independently conforming companion diagnostics never erase outer failures.
96
+ * @throws TypeError For malformed invocation data; RangeError for invalid capacities; filesystem errors for unavailable input files.
97
+ * @sideEffects Reads authorized local metadata only; creates no files and performs no SOAP transport.
98
+ */
99
+ export declare function inspectCheckedResponse(source: string, options: CheckedResponseInspectionOptions): Promise<CheckedResponseInspectionReport>;
100
+ export {};