@techspokes/typescript-wsdl-client 1.4.1 → 1.5.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.
package/README.md CHANGED
@@ -9,6 +9,10 @@ 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.5.0
13
+
14
+ Version 1.5.0 adds scoped, value-redacted checked payload diagnostics and explicit digest-pinned declared-type corrections. Trusted local inspection can supply original correction targets; changed effective contracts need fresh qualification and matched artifact regeneration. Read the [1.4.x migration guidance](docs/migration.md#from-14x-to-150).
15
+
12
16
  ## Upgrading to 1.4.0
13
17
 
14
18
  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).
@@ -257,6 +261,7 @@ See [CLI Reference](docs/cli-reference.md) for all flags and examples.
257
261
  | [Core Concepts](docs/concepts.md) | Flattening, $value, primitives, determinism |
258
262
  | [Agent Skill Artifact](docs/agent-skill.md) | Release ZIP for consumer-project AI agents |
259
263
  | [Version Migration](docs/migration.md) | Upgrading between package versions |
264
+ | [1.5.0 Release Notes](https://github.com/TechSpokes/typescript-wsdl-client/blob/main/docs/releases/v1.5.0.md) | Scoped diagnostics and explicit declared-type corrections |
260
265
  | [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
266
  | [1.4.1 Release Notes](https://github.com/TechSpokes/typescript-wsdl-client/blob/main/docs/releases/v1.4.1.md) | Composable checked OpenAPI documentation |
262
267
  | [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 |
@@ -90,6 +90,7 @@ export declare function parseCheckedComponentChoices(value: unknown): Partial<Ch
90
90
  export interface PreparedCheckedSourceInputs {
91
91
  readonly main: SchemaInput;
92
92
  readonly companions: ReadonlyMap<string, SchemaInput>;
93
+ readonly correctionPreparation: ReturnType<typeof prepareCorrectedSourceInputs>;
93
94
  readonly provenance: Awaited<ReturnType<typeof prepareCorrectedSourceInputs>>['provenance'] & {
94
95
  readonly recipeFile: {
95
96
  readonly digest: string;
@@ -168,7 +169,7 @@ export declare function prepareComponentStages(wsdl: string, config: CheckedComp
168
169
  changed: boolean;
169
170
  }>[];
170
171
  corrections: readonly Readonly<{
171
- kind: "set-occurs" | "set-wildcard-namespaces" | "insert-element";
172
+ kind: "set-occurs" | "set-wildcard-namespaces" | "insert-element" | "set-declared-type";
172
173
  target: import("./compiler/sourceCorrections.js").SourceCorrectionSelector;
173
174
  }>[];
174
175
  }> & {
@@ -241,7 +241,7 @@ export async function prepareCheckedSourceInputs(source, config, acquiredMain) {
241
241
  const effectiveCompanions = new Map();
242
242
  for (const [key, input] of companions)
243
243
  effectiveCompanions.set(key, await reload(input));
244
- return { main: effectiveMain, companions: effectiveCompanions, provenance: { ...corrected.provenance,
244
+ return { main: effectiveMain, companions: effectiveCompanions, correctionPreparation: corrected, provenance: { ...corrected.provenance,
245
245
  recipeFile: { digest: createHash('sha256').update(bytes).digest('hex') } } };
246
246
  }
247
247
  /**
@@ -2,6 +2,8 @@ import type { CheckedComponentOptions } from './checkedPipeline.js';
2
2
  import type { DirectOperationSelector } from './compiler/directOperation.js';
3
3
  import type { ObservedSoapResponseMetadata } from './compiler/directSoap.js';
4
4
  import type { DirectXmlLimits } from './compiler/directCodec.js';
5
+ import type { SourceCorrectionSelector } from './compiler/sourceCorrections.js';
6
+ import type { CheckedPayloadDiagnostic } from './compiler/payloadDiagnostics.js';
5
7
  export interface CheckedResponseInspectionOptions {
6
8
  readonly checkedComponents?: Partial<CheckedComponentOptions>;
7
9
  readonly selector: DirectOperationSelector;
@@ -10,6 +12,7 @@ export interface CheckedResponseInspectionOptions {
10
12
  readonly signal?: AbortSignal;
11
13
  readonly limits?: DirectXmlLimits;
12
14
  readonly includeCorrectionDraft?: boolean;
15
+ readonly includeCorrectionTargets?: boolean;
13
16
  }
14
17
  type Assessment = 'conforming' | 'mismatch' | 'refused' | 'unassessed';
15
18
  interface ScopedOutcome {
@@ -31,7 +34,25 @@ interface InspectionDiagnostic {
31
34
  readonly source?: InspectionLocation;
32
35
  readonly schemaPath?: string;
33
36
  readonly payloadPath?: string;
37
+ readonly diagnostic?: CheckedPayloadDiagnostic;
34
38
  }
39
+ /** Explicit trusted authoring data; available selectors retain original locators and digest pins.
40
+ * @since 1.5.0
41
+ * @constraints Only requested local authoring output may contain these locators; HTTP diagnostics never do.
42
+ */
43
+ export type CheckedResponseCorrectionTarget = Readonly<{
44
+ diagnostic: number;
45
+ } & ({
46
+ kind: 'available';
47
+ target: SourceCorrectionSelector;
48
+ declaredType?: {
49
+ namespace: string;
50
+ local: string;
51
+ };
52
+ } | {
53
+ kind: 'unavailable';
54
+ reason: string;
55
+ })>;
35
56
  /** A non-applicable recipe draft; null fields require an explicit consumer decision.
36
57
  * @since Unreleased
37
58
  */
@@ -78,6 +99,7 @@ export interface CheckedResponseInspectionReport {
78
99
  };
79
100
  readonly diagnostics: readonly InspectionDiagnostic[];
80
101
  readonly correctionDraft?: CheckedResponseCorrectionDraft;
102
+ readonly correctionTargets?: readonly CheckedResponseCorrectionTarget[];
81
103
  readonly sourceCorrections?: {
82
104
  readonly recipeDigest: string;
83
105
  readonly resources: readonly {
@@ -16,7 +16,8 @@ import { createComponentValueMapping } from './compiler/directValues.js';
16
16
  import { observeComponentXml, assessObservedComponentPayload, DIRECT_XML_LIMITS } from './compiler/directCodec.js';
17
17
  import { inspectComponentRecordProjection } from './compiler/componentRecordProjection.js';
18
18
  import { acquireStandardSchemaLibrary } from './compiler/standardSchemaComponents.js';
19
- import { inspectSourceCorrectionInventory, SourceCorrectionError } from './compiler/sourceCorrections.js';
19
+ import { inspectSourceCorrectionInventory, recoverOriginalSourceCorrectionTarget, SourceCorrectionError } from './compiler/sourceCorrections.js';
20
+ import { inspectPayloadDiagnostic } from './compiler/payloadDiagnostics.js';
20
21
  import { loadSemanticSchemaInput, prepareSchemaSnapshotFiles, copyOriginalSchemaResource } from './loader/schemaInput.js';
21
22
  import { resolveResourceUri } from './loader/schemaResources.js';
22
23
  import { syntaxElements, syntaxAttribute, resolveLexicalQName, XSD_NAMESPACE, WSDL_NAMESPACE, SchemaLoadingError } from './loader/orderedSyntax.js';
@@ -59,9 +60,11 @@ function failureAssessment(kind) {
59
60
  export async function inspectCheckedResponse(source, options) {
60
61
  if (typeof source !== 'string' || !source)
61
62
  throw new TypeError('Local WSDL source string required');
62
- const supplied = record(options, ['checkedComponents', 'selector', 'responseBytes', 'responseMetadata', 'signal', 'limits', 'includeCorrectionDraft']);
63
+ const supplied = record(options, ['checkedComponents', 'selector', 'responseBytes', 'responseMetadata', 'signal', 'limits', 'includeCorrectionDraft', 'includeCorrectionTargets']);
63
64
  if (supplied.includeCorrectionDraft !== undefined && typeof supplied.includeCorrectionDraft !== 'boolean')
64
65
  throw new TypeError('includeCorrectionDraft must be boolean');
66
+ if (supplied.includeCorrectionTargets !== undefined && typeof supplied.includeCorrectionTargets !== 'boolean')
67
+ throw new TypeError('includeCorrectionTargets must be boolean');
65
68
  const selector = parseCheckedComponentChoices({ selectors: [supplied.selector] }).selectors[0];
66
69
  const choices = parseCheckedComponentChoices(supplied.checkedComponents ?? {});
67
70
  const responseByteLength = xmlByteLength(supplied.responseBytes);
@@ -90,6 +93,8 @@ export async function inspectCheckedResponse(source, options) {
90
93
  const displaySelector = { portType: { namespace: bounded(selector.portType.namespace), local: bounded(selector.portType.local) },
91
94
  binding: { namespace: bounded(selector.binding.namespace), local: bounded(selector.binding.local) }, operation: bounded(selector.operation) };
92
95
  const diagnostics = [];
96
+ let sourcePreparation;
97
+ let correctionTargets = supplied.includeCorrectionTargets ? [] : undefined;
93
98
  const inputs = [];
94
99
  const locations = new Map();
95
100
  const resources = [];
@@ -115,14 +120,53 @@ export async function inspectCheckedResponse(source, options) {
115
120
  const location = (at) => ({ resource: at.uri === 'urn:typescript-wsdl-client:inspected-response' ? 'response' : locations.get(at.uri)?.resource ?? 'unlocated-source',
116
121
  digest: at.uri === 'urn:typescript-wsdl-client:inspected-response' ? responseIdentity.digest : locations.get(at.uri)?.digest ?? at.digest,
117
122
  path: bounded(at.path), line: at.start.line, column: at.start.column });
123
+ let authoringInventory;
124
+ const inventoryForAuthoring = () => {
125
+ if (authoringInventory?.inputCount === inputs.length)
126
+ return authoringInventory;
127
+ try {
128
+ authoringInventory = { inputCount: inputs.length, value: inspectSourceCorrectionInventory(inputs, signal) };
129
+ }
130
+ catch (error) {
131
+ if (!(error instanceof SourceCorrectionError) && !(error instanceof SchemaLoadingError))
132
+ throw error;
133
+ authoringInventory = { inputCount: inputs.length, reason: `original-target-inventory-${error.category}` };
134
+ }
135
+ return authoringInventory;
136
+ };
118
137
  const add = (scope, result) => {
119
138
  if (diagnostics.length === 32) {
120
139
  truncated = true;
121
140
  return;
122
141
  }
142
+ if (result.diagnostic?.truncated)
143
+ truncated = true;
123
144
  diagnostics.push({ scope, kind: bounded(result.kind, 80), rule: result.kind === 'xml-invalid' ? 'observed-XML-invalid' : bounded(result.rule), guidance,
124
145
  ...(result.source ? { source: location(result.source) } : {}), ...(result.schemaPath ? { schemaPath: bounded(result.schemaPath) } : {}),
125
- ...(result.payloadPath ? { payloadPath: bounded(result.payloadPath) } : {}) });
146
+ ...(result.payloadPath ? { payloadPath: bounded(result.payloadPath) } : {}), ...(result.diagnostic ? { diagnostic: result.diagnostic } : {}) });
147
+ if (correctionTargets) {
148
+ const diagnostic = diagnostics.length - 1;
149
+ const native = result.diagnostic && inspectPayloadDiagnostic(result.diagnostic);
150
+ if (!native)
151
+ correctionTargets.push({ diagnostic, kind: 'unavailable', reason: 'native-declaration-unavailable' });
152
+ else if (sourcePreparation)
153
+ correctionTargets.push({ diagnostic, ...recoverOriginalSourceCorrectionTarget(sourcePreparation.correctionPreparation, native.declaration.context.source, native.declaration.context.effectiveNamespace, native.declaration.kind) });
154
+ else {
155
+ const declaration = native.declaration;
156
+ const inventory = inventoryForAuthoring();
157
+ if (!inventory.value) {
158
+ correctionTargets.push({ diagnostic, kind: 'unavailable', reason: inventory.reason });
159
+ return;
160
+ }
161
+ const matches = inventory.value.nodes.filter(node => node.id === declaration.id && node.uri === declaration.context.source.uri
162
+ && node.digest === declaration.context.source.digest && node.kind === declaration.kind
163
+ && node.namespace === declaration.context.effectiveNamespace);
164
+ correctionTargets.push(matches.length === 1 && !matches[0].ambiguousContext
165
+ ? { diagnostic, kind: 'available', target: { id: matches[0].id, uri: matches[0].uri, digest: matches[0].digest },
166
+ ...(declaration.type.kind !== 'local' ? { declaredType: declaration.type.name } : {}) }
167
+ : { diagnostic, kind: 'unavailable', reason: matches.length > 1 || matches[0]?.ambiguousContext ? 'ambiguous-original-target' : 'original-target-not-established' });
168
+ }
169
+ }
126
170
  };
127
171
  let responseOutcome = { assessment: 'unassessed' };
128
172
  let wrapper, companion;
@@ -138,12 +182,17 @@ export async function inspectCheckedResponse(source, options) {
138
182
  const report = { version: 'checked-response-inspection-v1', kind, scope: metadata ? 'full-response' : 'xml-only',
139
183
  admitted: false, executed: false, selector: displaySelector, response: responseIdentity, http, fault, resources,
140
184
  outcomes: { response: scoped(responseOutcome), ...(wrapper ? { wrapper: scoped(wrapper) } : {}), ...(companion ? { companion: scoped(companion) } : {}) }, diagnostics,
141
- ...(correctionDraft ? { correctionDraft } : {}), ...(correctionEvidence ? { sourceCorrections: correctionEvidence } : {}), truncated };
185
+ ...(correctionDraft ? { correctionDraft } : {}), ...(correctionTargets ? { correctionTargets } : {}), ...(correctionEvidence ? { sourceCorrections: correctionEvidence } : {}), truncated };
142
186
  if (Buffer.byteLength(JSON.stringify(report)) > 128 * 1024 && correctionDraft) {
143
187
  correctionDraft = undefined;
144
188
  truncated = true;
145
189
  return finish();
146
190
  }
191
+ if (Buffer.byteLength(JSON.stringify(report)) > 128 * 1024 && correctionTargets?.some(target => target.kind === 'available')) {
192
+ correctionTargets = correctionTargets.map(target => ({ diagnostic: target.diagnostic, kind: 'unavailable', reason: 'authoring-target-exceeds-report-capacity' }));
193
+ truncated = true;
194
+ return finish();
195
+ }
147
196
  return report;
148
197
  };
149
198
  if (signal?.aborted || !withinByteLimit || metadataOverLimit) {
@@ -161,6 +210,7 @@ export async function inspectCheckedResponse(source, options) {
161
210
  await fs.access(localFile(choices.sourceCorrectionsFile));
162
211
  const selectedChoices = { ...choices, sourceResources: choices.sourceResources ?? [], signal };
163
212
  const prepared = await prepareCheckedComponentPlan(rootFile, selectedChoices, undefined, selector);
213
+ sourcePreparation = prepared.sources;
164
214
  if (prepared.sourceInput)
165
215
  retain(prepared.sourceInput);
166
216
  if (prepared.sources) {
@@ -1,3 +1,4 @@
1
+ import type { CheckedPayloadDiagnostic } from '../compiler/payloadDiagnostics.js';
1
2
  import type { ComponentClientResult } from './directClientRuntime.js';
2
3
  import { DIRECT_CLIENT_RUNTIME_VERSION, COMPONENT_CLIENT_RUNTIME_VERSION, COMPONENT_SOAP12_CLIENT_RUNTIME_VERSION } from './directClientVersion.js';
3
4
  interface Name {
@@ -34,6 +35,7 @@ interface Failure {
34
35
  readonly usage: Usage;
35
36
  readonly repair?: string;
36
37
  readonly phase?: 'input' | 'response';
38
+ readonly diagnostic?: CheckedPayloadDiagnostic;
37
39
  }
38
40
  type Success = {
39
41
  readonly value: unknown;
@@ -1,3 +1,4 @@
1
+ import type { CheckedPayloadDiagnostic } from '../compiler/payloadDiagnostics.js';
1
2
  import type { SemanticBudgetLimits, ResolverUsage } from '../compiler/semanticBudget.js';
2
3
  import type { DirectCapsuleFailure } from '../compiler/directSourceCapsule.js';
3
4
  import type { DirectSoapOptions, ComponentSoapOptions, DirectSoapFailure, ComponentProtocolFault, DeclaredSoapFault, Soap11FaultMetadata, Soap12FaultMetadata } from '../compiler/directSoap.js';
@@ -36,6 +37,7 @@ export interface DirectClientFailure {
36
37
  readonly usage: ResolverUsage;
37
38
  readonly repair?: string;
38
39
  readonly phase?: 'input' | 'response';
40
+ readonly diagnostic?: CheckedPayloadDiagnostic;
39
41
  }
40
42
  export type DirectClientResult = DirectClientFailure | Readonly<{
41
43
  kind: 'soap-response';
@@ -156,7 +156,11 @@ async function executeClient(expectedBase64, input, options, component, protocol
156
156
  if (!('decoded' in result)) {
157
157
  if (result.kind === 'soap-fault' && result.fault.version !== protocol)
158
158
  throw new TypeError('Protocol fault requires its generated runtime binding');
159
- return result;
159
+ if (result.kind === 'soap-fault')
160
+ return result;
161
+ // @constraints Native source provenance can contain endpoint secrets; only the safe diagnostic crosses the runtime boundary.
162
+ return Object.freeze({ kind: result.kind, rule: result.rule, usage: result.usage,
163
+ ...(result.phase ? { phase: result.phase } : {}), ...(result.diagnostic ? { diagnostic: result.diagnostic } : {}) });
160
164
  }
161
165
  if (options.soap.signal?.aborted)
162
166
  return fail('cancelled', 'cooperative-cancellation');
@@ -86,7 +86,7 @@ export function generateDirectClient(expected, signal) {
86
86
  '}',
87
87
  'export interface Failure {',
88
88
  ' readonly kind: "incompatible-artifact" | "unsupported" | "missing-authority" | "resource-limit" | "cancelled" | "inconclusive" | "invalid-schema" | "scalar-invalid" | "value-invalid" | "xml-invalid" | "mismatch" | "transport" | "protocol";',
89
- ' readonly rule: string; readonly repair?: string; readonly usage: Usage;',
89
+ ' readonly rule: string; readonly repair?: string; readonly usage: Usage; readonly diagnostic?: import("@techspokes/typescript-wsdl-client").CheckedPayloadDiagnostic;',
90
90
  '}',
91
91
  'export interface NativeSuccess { readonly kind: "soap-response" | "soap-fault"; readonly value: unknown;',
92
92
  ' readonly admitted: false; readonly status: number; readonly usage: Usage; readonly responseBytes: Uint8Array;',
@@ -192,7 +192,7 @@ export async function generateComponentClient(expected, signal) {
192
192
  ' readonly limits?: {readonly maxBytes?: number; readonly maxDepth?: number; readonly maxNodes?: number; readonly maxText?: number}};',
193
193
  '}',
194
194
  'export interface Failure {readonly kind: "incompatible-artifact" | "unsupported" | "missing-authority" | "resource-limit" | "cancelled" | "inconclusive" | "invalid-schema" | "scalar-invalid" | "value-invalid" | "xml-invalid" | "mismatch" | "transport" | "protocol";',
195
- ' readonly rule: string; readonly repair?: string; readonly phase?: "input" | "response"; readonly usage: Usage}',
195
+ ' readonly rule: string; readonly repair?: string; readonly phase?: "input" | "response"; readonly usage: Usage; readonly diagnostic?: import("@techspokes/typescript-wsdl-client").CheckedPayloadDiagnostic}',
196
196
  'export interface NativeSuccess {readonly kind: "soap-response" | "soap-fault"; readonly value?: unknown; readonly admitted: false;',
197
197
  ' readonly status: number; readonly usage: Usage; readonly responseBytes: Uint8Array;',
198
198
  ' readonly fault?: {readonly name?: string; readonly code: Name; readonly string: string; readonly actor?: string}}',
@@ -9,6 +9,7 @@ import { assessSourceAttributeUses } from './schemaAttributeUses.js';
9
9
  import { assessStandardSchemaElement } from './standardSchemaComponents.js';
10
10
  import { namespaceAllows } from './wildcardAlgebra.js';
11
11
  import { directDataOwnership } from './directExecutionPolicy.js';
12
+ import { createPayloadDiagnostic, diagnosticName } from './payloadDiagnostics.js';
12
13
  import { DataOwnershipLimitError } from './dataOwnership.js';
13
14
  import { isXmlNCName, syntaxElements } from '../loader/orderedSyntax.js';
14
15
  const issued = new WeakMap();
@@ -318,8 +319,18 @@ export function decodeComponentRecordXml(outer, projection, bytes, options = {})
318
319
  catch (error) {
319
320
  if (budget.exhausted())
320
321
  return failure('resource-limit', 'record-projection-budget');
321
- if (error instanceof ScopeStop)
322
- return failure(error.kind, error.rule);
322
+ if (error instanceof ScopeStop) {
323
+ if (error.kind !== 'mismatch' || 'parts' in outer.output)
324
+ return failure(error.kind, error.rule);
325
+ const declaration = outer.output.element;
326
+ // @constraints Native metadata lookups consume the same budget and retain its terminal resource result.
327
+ const diagnosed = budget.guard(() => {
328
+ const diagnostic = createPayloadDiagnostic(context, declaration, '/' + diagnosticName(declaration.name) + '/[unknown-field]', { unknownField: true });
329
+ return Object.freeze({ ...failure(error.kind, error.rule), diagnostic });
330
+ });
331
+ return diagnosed.kind === 'resource-limit' && !('rule' in diagnosed)
332
+ ? failure('resource-limit', 'record-projection-budget') : diagnosed;
333
+ }
323
334
  throw error;
324
335
  }
325
336
  }
@@ -1,3 +1,4 @@
1
+ import type { CheckedPayloadDiagnostic } from './payloadDiagnostics.js';
1
2
  import type { DirectOperationContract } from './directOperation.js';
2
3
  import type { DirectValueMapping, DirectValueReceipt, DirectValueFailure, DirectExpandedValue, ComponentValueMapping, ProjectionExpandedValue, ProjectionValueReceipt } from './directValues.js';
3
4
  import type { SyntaxElement, SyntaxSource } from '../loader/orderedSyntax.js';
@@ -31,6 +32,7 @@ export interface DirectCodecFailure {
31
32
  readonly source?: SyntaxSource;
32
33
  readonly schemaPath?: string;
33
34
  readonly payloadPath?: string;
35
+ readonly diagnostic?: CheckedPayloadDiagnostic;
34
36
  readonly usage: ResolverUsage;
35
37
  }
36
38
  export interface DirectEncodedXml {
@@ -1,3 +1,4 @@
1
+ import type { CheckedPayloadDiagnostic } from './payloadDiagnostics.js';
1
2
  import type { DirectOperationContract } from './directOperation.js';
2
3
  import type { DirectCodecFailure, DirectDecodedXml, ComponentDecodedXml, DirectXmlLimits } from './directCodec.js';
3
4
  import type { ExpandedName, SyntaxElement, SyntaxSource } from '../loader/orderedSyntax.js';
@@ -23,6 +24,7 @@ export interface DirectSoapFailure {
23
24
  readonly source?: SyntaxSource;
24
25
  readonly schemaPath?: string;
25
26
  readonly payloadPath?: string;
27
+ readonly diagnostic?: CheckedPayloadDiagnostic;
26
28
  }
27
29
  /** Version-specific protocol metadata; application detail remains owned by native mappings.
28
30
  * @since 1.3.0
@@ -560,7 +560,8 @@ function assessSoapResponse(contract, output, faults, bytes, profile, options) {
560
560
  if (!decoded)
561
561
  throw new TypeError('Missing decoded SOAP response');
562
562
  if (!('validated' in decoded))
563
- return profile.component ? Object.freeze({ ...decoded, phase: 'response' }) : fail(decoded.kind, decoded.rule);
563
+ return profile.component ? Object.freeze({ ...decoded, phase: 'response' })
564
+ : Object.freeze({ ...fail(decoded.kind, decoded.rule), ...(decoded.diagnostic ? { diagnostic: decoded.diagnostic } : {}) });
564
565
  check();
565
566
  check();
566
567
  return Object.freeze({ kind: faultInfo ? 'soap-fault' : 'soap-response', admitted: false, decoded,
@@ -5,6 +5,7 @@ import type { DirectProjectionSlot } from './directStructurePlan.js';
5
5
  import type { SemanticContext } from './semanticContext.js';
6
6
  import type { ResolverUsage } from './semanticBudget.js';
7
7
  import type { ProjectionScalarList } from './projectionScalar.js';
8
+ import type { CheckedPayloadDiagnostic } from './payloadDiagnostics.js';
8
9
  export type DirectDirection = Readonly<{
9
10
  kind: 'input' | 'output';
10
11
  } | {
@@ -20,6 +21,7 @@ export interface DirectScalarDuty {
20
21
  readonly payloadSource: SyntaxSource;
21
22
  readonly payloadPath: string;
22
23
  readonly schemaPath: string;
24
+ readonly knownLocation?: string;
23
25
  readonly fixedCanonical?: string;
24
26
  readonly retainLexical?: true;
25
27
  readonly list?: ProjectionScalarList;
@@ -81,6 +83,7 @@ export type ProjectionStructuralResult = Readonly<{
81
83
  rule: string;
82
84
  schemaPath?: string;
83
85
  payloadPath?: string;
86
+ diagnostic?: CheckedPayloadDiagnostic;
84
87
  usage: ResolverUsage;
85
88
  }>;
86
89
  export type ProjectionStructuralMatch = Extract<ProjectionStructuralResult, {
@@ -104,6 +107,7 @@ export type DirectStructuralResult = Readonly<{
104
107
  rule: string;
105
108
  schemaPath?: string;
106
109
  payloadPath?: string;
110
+ diagnostic?: CheckedPayloadDiagnostic;
107
111
  usage: ResolverUsage;
108
112
  }>;
109
113
  export type DirectStructuralMatch = Extract<DirectStructuralResult, {
@@ -3,6 +3,7 @@ import { inspectDirectOperation } from './directOperation.js';
3
3
  import { planDirectProjection, flatDirectProjection, matchOrderedProjection, selectProjectionAttribute, isXmlCharacters, isXmlNamespaceBinding } from './directStructurePlan.js';
4
4
  import { assertSemanticContext } from './semanticContext.js';
5
5
  import { isXmlNCName } from '../loader/orderedSyntax.js';
6
+ import { createPayloadDiagnostic, diagnosticName, appendDiagnosticLocation } from './payloadDiagnostics.js';
6
7
  const projectionIssued = new WeakMap();
7
8
  /** Native context identity authenticates structure only, never source validity or operation admission. */
8
9
  export function inspectProjectionStructuralMatch(match, context) {
@@ -24,12 +25,14 @@ class Stop extends Error {
24
25
  rule;
25
26
  schemaPath;
26
27
  payloadPath;
27
- constructor(kind, rule, schemaPath, payloadPath) {
28
+ diagnostic;
29
+ constructor(kind, rule, schemaPath, payloadPath, diagnostic) {
28
30
  super(rule);
29
31
  this.kind = kind;
30
32
  this.rule = rule;
31
33
  this.schemaPath = schemaPath;
32
34
  this.payloadPath = payloadPath;
35
+ this.diagnostic = diagnostic;
33
36
  }
34
37
  }
35
38
  /**
@@ -160,7 +163,11 @@ export function matchDirectProjection(context, root, payload, options = {}) {
160
163
  }
161
164
  }
162
165
  const same = (a, b) => budget.compareText(a.namespace, b.namespace) === 0 && budget.compareText(a.local, b.local) === 0;
163
- const mismatch = (rule, declaration, path) => fail('mismatch', rule, declaration.context.source.path, path);
166
+ let currentLocation = '/' + diagnosticName(root.name);
167
+ const mismatch = (rule, declaration, path) => {
168
+ const unknownField = ['unexpected-child', 'unknown-attribute', 'root-name', 'scalar-child', 'ordered-particle-language'].includes(rule);
169
+ throw new Stop('mismatch', rule, declaration.context.source.path, path, createPayloadDiagnostic(context, declaration, unknownField ? appendDiagnosticLocation(currentLocation, '[unknown-field]') : currentLocation, { unknownField }));
170
+ };
164
171
  const attributes = (node, declaration, path) => {
165
172
  check();
166
173
  if (node.attributes.length)
@@ -188,6 +195,7 @@ export function matchDirectProjection(context, root, payload, options = {}) {
188
195
  end: { offset: s.end.offset, line: s.end.line, column: s.end.column } });
189
196
  occurrences.push({ declaration, type: declaration.type, builtin, lexical, namespaces,
190
197
  payloadSource, payloadPath: path, schemaPath: declaration.context.source.path,
198
+ knownLocation: declaration.kind === 'attribute' ? appendDiagnosticLocation(currentLocation, '@' + diagnosticName(declaration.name)) : currentLocation,
191
199
  ...(fixedCanonical === undefined ? {} : { fixedCanonical }), ...(retainLexical ? { retainLexical } : {}), ...(list ? { list } : {}) });
192
200
  };
193
201
  const text = (node, declaration, path) => {
@@ -216,6 +224,7 @@ export function matchDirectProjection(context, root, payload, options = {}) {
216
224
  check();
217
225
  const frame = frames.pop();
218
226
  const { declaration, path: parentPath } = frame;
227
+ currentLocation = frame.location ?? '/' + diagnosticName(declaration.name);
219
228
  let node = frame.node;
220
229
  budget.chargeWork(128 + parentPath.length * 4);
221
230
  if (node.kind === 'text') {
@@ -407,6 +416,15 @@ export function matchDirectProjection(context, root, payload, options = {}) {
407
416
  dynamicDeclarations: selected.filter(slot => !('value' in slot) && slot.element).map(slot => slot.element),
408
417
  ...(record.keyed ? { keyed: true } : {}) });
409
418
  let selectedIndex = elements.length - 1;
419
+ const occurrenceIndexes = new Map();
420
+ const locations = selected.map(slot => {
421
+ check();
422
+ if (!slot.element)
423
+ return currentLocation;
424
+ const key = slot.element.id, index = (occurrenceIndexes.get(key) ?? 0) + 1;
425
+ occurrenceIndexes.set(key, index);
426
+ return appendDiagnosticLocation(currentLocation, diagnosticName(slot.element.name, index));
427
+ });
410
428
  for (let index = node.children.length - 1; index >= 0; index--) {
411
429
  check();
412
430
  const child = node.children[index];
@@ -415,13 +433,13 @@ export function matchDirectProjection(context, root, payload, options = {}) {
415
433
  frames.push({ node: child, path: `${parentPath}/children/${index}`, declaration, value: frame.value, into: content });
416
434
  continue;
417
435
  }
418
- const item = elements[selectedIndex], slot = selected[selectedIndex--];
436
+ const item = elements[selectedIndex], childLocation = locations[selectedIndex], slot = selected[selectedIndex--];
419
437
  if (!slot.element) {
420
438
  frames.push({ node: item.node, path: item.path, declaration, value: frame.value, unassessed: true, into: content });
421
439
  continue;
422
440
  }
423
441
  frames.push({ node: item.node, path: item.path, declaration: slot.element,
424
- value: 'value' in slot ? slot.value : wildcardValue(slot.element), into: content });
442
+ value: 'value' in slot ? slot.value : wildcardValue(slot.element), into: content, location: childLocation });
425
443
  }
426
444
  continue;
427
445
  }
@@ -456,7 +474,8 @@ export function matchDirectProjection(context, root, payload, options = {}) {
456
474
  return mismatch('unexpected-child', declaration, path);
457
475
  if (slot.particle.occurs.max === '1' && count === 1)
458
476
  return mismatch('duplicate-singleton', slot.element, path);
459
- children.push({ node: child, declaration: slot.element, value: slot.value, path, into: matchedSlots[cursor].items });
477
+ children.push({ node: child, declaration: slot.element, value: slot.value, path, into: matchedSlots[cursor].items,
478
+ location: appendDiagnosticLocation(currentLocation, diagnosticName(slot.element.name, count + 1)) });
460
479
  count++;
461
480
  }
462
481
  for (; cursor < slots.length; cursor++, count = 0) {
@@ -488,7 +507,8 @@ export function matchDirectProjection(context, root, payload, options = {}) {
488
507
  if (budget.exhausted())
489
508
  return Object.freeze({ kind: 'resource-limit', rule: 'request-budget', usage: budget.usage() });
490
509
  if (error instanceof Stop)
491
- return Object.freeze({ kind: error.kind, rule: error.rule, schemaPath: error.schemaPath, payloadPath: error.payloadPath, usage: Object.freeze({ ...budget.usage() }) });
510
+ return Object.freeze({ kind: error.kind, rule: error.rule, schemaPath: error.schemaPath, payloadPath: error.payloadPath,
511
+ ...(error.diagnostic ? { diagnostic: error.diagnostic } : {}), usage: Object.freeze({ ...budget.usage() }) });
492
512
  throw error;
493
513
  }
494
514
  }
@@ -1,3 +1,4 @@
1
+ import type { CheckedPayloadDiagnostic } from './payloadDiagnostics.js';
1
2
  import type { DirectOperationContract, DirectMessageRoot, RpcMessageRoot } from './directOperation.js';
2
3
  import type { DirectDirection, DirectScalarDuty, DirectStructuralMatch } from './directStructure.js';
3
4
  import type { DirectRootPlan, DirectProjectionPlan } from './directStructurePlan.js';
@@ -96,6 +97,7 @@ export interface DirectValueFailure {
96
97
  readonly schemaPath?: string;
97
98
  readonly payloadPath?: string;
98
99
  readonly usage: ResolverUsage;
100
+ readonly diagnostic?: CheckedPayloadDiagnostic;
99
101
  }
100
102
  type Options = Readonly<{
101
103
  signal?: AbortSignal;
@@ -1,4 +1,5 @@
1
1
  import { directDataOwnership, directLimit } from './directExecutionPolicy.js';
2
+ import { createPayloadDiagnostic, diagnosticName } from './payloadDiagnostics.js';
2
3
  import { types } from 'node:util';
3
4
  /** Internal direct value chain. No XML encoding, transport or runtime admission. */
4
5
  import { checkSemanticBudget } from './semanticBudget.js';
@@ -60,12 +61,14 @@ class Stop extends Error {
60
61
  rule;
61
62
  schemaPath;
62
63
  payloadPath;
63
- constructor(kind, rule, schemaPath, payloadPath) {
64
+ diagnostic;
65
+ constructor(kind, rule, schemaPath, payloadPath, diagnostic) {
64
66
  super(rule);
65
67
  this.kind = kind;
66
68
  this.rule = rule;
67
69
  this.schemaPath = schemaPath;
68
70
  this.payloadPath = payloadPath;
71
+ this.diagnostic = diagnostic;
69
72
  }
70
73
  }
71
74
  function run(contract, options, work) {
@@ -96,7 +99,7 @@ function runContext(context, options, work) {
96
99
  return { kind: 'resource-limit', rule: error.message, usage: budget.usage() };
97
100
  if (error instanceof Stop)
98
101
  return Object.freeze({ kind: error.kind, rule: error.rule, schemaPath: error.schemaPath,
99
- payloadPath: error.payloadPath, usage: Object.freeze({ ...budget.usage() }) });
102
+ payloadPath: error.payloadPath, ...(error.diagnostic ? { diagnostic: error.diagnostic } : {}), usage: Object.freeze({ ...budget.usage() }) });
100
103
  if (error instanceof ApplicationGroupValueError)
101
104
  return Object.freeze({ kind: 'value-invalid', rule: error.message, usage: Object.freeze({ ...budget.usage() }) });
102
105
  throw error;
@@ -192,8 +195,9 @@ export function inspectComponentValueMapping(value, contract) {
192
195
  }
193
196
  function scalar(duty, s, context) {
194
197
  const { budget, check } = s;
195
- const fail = (_owner, rule) => { throw new Stop('scalar-invalid', rule, duty.schemaPath, duty.payloadPath); };
196
- const unavailable = () => { throw new Stop('unsupported', 'direct-datatype-prerequisite', duty.schemaPath, duty.payloadPath); };
198
+ const diagnostic = () => createPayloadDiagnostic(context, duty.declaration, duty.knownLocation ?? '/' + diagnosticName(duty.declaration.name), { builtin: duty.builtin });
199
+ const fail = (_owner, rule) => { throw new Stop('scalar-invalid', rule, duty.schemaPath, duty.payloadPath, diagnostic()); };
200
+ const unavailable = () => { throw new Stop('unsupported', 'direct-datatype-prerequisite', duty.schemaPath, duty.payloadPath, diagnostic()); };
197
201
  // This is a neutral lexical operand, not a schema LexicalValue or checked receipt.
198
202
  const core = datatypeValues({
199
203
  step: (_owner, count = 1) => { check(); budget.chargeWork(count); },
@@ -208,11 +212,11 @@ function scalar(duty, s, context) {
208
212
  const assessed = assessProjectionScalarOperand(context, duty.declaration, lexical, check);
209
213
  check();
210
214
  if (assessed.kind !== 'projection-scalar-operand')
211
- throw new Stop(assessed.kind === 'scalar-invalid' ? 'scalar-invalid' : assessed.kind === 'resource-limit' ? 'resource-limit' : 'unsupported', 'rule' in assessed ? assessed.rule : 'request-budget', duty.schemaPath, duty.payloadPath);
215
+ throw new Stop(assessed.kind === 'scalar-invalid' ? 'scalar-invalid' : assessed.kind === 'resource-limit' ? 'resource-limit' : 'unsupported', 'rule' in assessed ? assessed.rule : 'request-budget', duty.schemaPath, duty.payloadPath, diagnostic());
212
216
  result = assessed.operand;
213
217
  }
214
218
  if (duty.fixedCanonical !== undefined && budget.compareText(result.canonical, duty.fixedCanonical) !== 0)
215
- throw new Stop('scalar-invalid', duty.declaration.kind === 'attribute' ? 'fixed-attribute-value' : 'fixed-element-value', duty.schemaPath, duty.payloadPath);
219
+ throw new Stop('scalar-invalid', duty.declaration.kind === 'attribute' ? 'fixed-attribute-value' : 'fixed-element-value', duty.schemaPath, duty.payloadPath, diagnostic());
216
220
  const normalized = result.normalized ?? normalizeWhitespace(duty.lexical, whitespaceFor(duty.builtin.local));
217
221
  const present = (operand, builtin, lexical, retainLexical) => {
218
222
  check();
@@ -1097,14 +1101,14 @@ export function expandProjectionValue(context, root, application, options = {},
1097
1101
  if (structural.kind !== 'projection-match') {
1098
1102
  if (structural.kind === 'mismatch') {
1099
1103
  if (structural.rule === 'ordered-particle-language')
1100
- throw new Stop('value-invalid', structural.rule, structural.schemaPath, structural.payloadPath);
1104
+ throw new Stop('value-invalid', structural.rule, structural.schemaPath, structural.payloadPath, structural.diagnostic);
1101
1105
  throw new Error('Internal projection expansion violated its source mapping');
1102
1106
  }
1103
- throw new Stop(structural.kind, structural.rule, structural.schemaPath, structural.payloadPath);
1107
+ throw new Stop(structural.kind, structural.rule, structural.schemaPath, structural.payloadPath, structural.diagnostic);
1104
1108
  }
1105
1109
  const validated = assessProjectionScalars(context, structural, options, mapping);
1106
1110
  if (validated.kind !== 'projection-value')
1107
- throw new Stop(validated.kind, validated.rule, validated.schemaPath, validated.payloadPath);
1111
+ throw new Stop(validated.kind, validated.rule, validated.schemaPath, validated.payloadPath, validated.diagnostic);
1108
1112
  const expanded = s.publish({ kind: 'projection-expanded', provenance: 'generated-direct-value', admitted: false, payload, validated });
1109
1113
  projectionExpansions.set(expanded, context);
1110
1114
  return expanded;
@@ -1122,7 +1126,7 @@ export function expandDirectValue(contract, suppliedMapping, application, option
1122
1126
  if (structural.kind !== 'structural-match') {
1123
1127
  if (structural.kind === 'mismatch')
1124
1128
  throw new Error('Internal direct expansion violated its source mapping');
1125
- throw new Stop(structural.kind, structural.rule, structural.schemaPath, structural.payloadPath);
1129
+ throw new Stop(structural.kind, structural.rule, structural.schemaPath, structural.payloadPath, structural.diagnostic);
1126
1130
  }
1127
1131
  const validated = assessed(contract, structural, mapping, s);
1128
1132
  const expanded = s.publish({ kind: 'direct-expanded', provenance: 'generated-direct-value', admitted: false, payload, validated });