@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 +5 -0
- package/dist/checkedPipeline.d.ts +2 -1
- package/dist/checkedPipeline.js +1 -1
- package/dist/checkedResponseInspection.d.ts +22 -0
- package/dist/checkedResponseInspection.js +54 -4
- package/dist/client/checkedDirectRuntime.d.ts +2 -0
- package/dist/client/directClientRuntime.d.ts +2 -0
- package/dist/client/directClientRuntime.js +5 -1
- package/dist/client/generateDirectClient.js +2 -2
- package/dist/compiler/componentRecordProjection.js +13 -2
- package/dist/compiler/directCodec.d.ts +2 -0
- package/dist/compiler/directSoap.d.ts +2 -0
- package/dist/compiler/directSoap.js +2 -1
- package/dist/compiler/directStructure.d.ts +4 -0
- package/dist/compiler/directStructure.js +26 -6
- package/dist/compiler/directValues.d.ts +2 -0
- package/dist/compiler/directValues.js +14 -10
- package/dist/compiler/payloadDiagnostics.d.ts +60 -0
- package/dist/compiler/payloadDiagnostics.js +73 -0
- package/dist/compiler/sourceCorrections.d.ts +50 -1
- package/dist/compiler/sourceCorrections.js +157 -8
- package/dist/gateway/generateCheckedGateway.js +19 -3
- package/dist/index.d.ts +2 -1
- package/docs/api-reference.md +2 -0
- package/docs/checked-gateway.md +57 -1
- package/docs/migration.md +12 -0
- package/package.json +1 -1
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { AttributeNode, ElementNode } from './canonicalGraph.js';
|
|
2
|
+
import type { SemanticContext } from './semanticContext.js';
|
|
3
|
+
import type { ExpandedName } from '../loader/orderedSyntax.js';
|
|
4
|
+
/** Optional additive metadata shared by inspection, direct runtimes and generated HTTP failures.
|
|
5
|
+
* @since 1.5.0
|
|
6
|
+
* @constraints Resource and universe identifiers are opaque; source locators remain trusted local data.
|
|
7
|
+
*/
|
|
8
|
+
export interface CheckedPayloadDiagnostic {
|
|
9
|
+
readonly version: 'checked-payload-diagnostic-v1';
|
|
10
|
+
readonly universe: string;
|
|
11
|
+
readonly resource: string;
|
|
12
|
+
readonly digest: string;
|
|
13
|
+
readonly declaration: {
|
|
14
|
+
readonly kind: 'element' | 'attribute';
|
|
15
|
+
readonly name: ExpandedName;
|
|
16
|
+
};
|
|
17
|
+
readonly owner?: {
|
|
18
|
+
readonly kind: string;
|
|
19
|
+
readonly name?: ExpandedName;
|
|
20
|
+
};
|
|
21
|
+
readonly datatype?: ExpandedName;
|
|
22
|
+
readonly builtin?: ExpandedName;
|
|
23
|
+
readonly location: string;
|
|
24
|
+
readonly unknownField?: true;
|
|
25
|
+
readonly truncated?: true;
|
|
26
|
+
}
|
|
27
|
+
type Evidence = Readonly<{
|
|
28
|
+
context: SemanticContext;
|
|
29
|
+
declaration: ElementNode | AttributeNode;
|
|
30
|
+
}>;
|
|
31
|
+
/** Format only a schema-established expanded name, with a bounded occurrence index.
|
|
32
|
+
* @param name Name supplied by the native schema owner, never an unknown payload name.
|
|
33
|
+
* @param index One-based occurrence index.
|
|
34
|
+
* @returns A bounded readable location segment.
|
|
35
|
+
*/
|
|
36
|
+
export declare function diagnosticName(name: ExpandedName, index?: number): string;
|
|
37
|
+
/** Append a known-field segment while retaining an explicit marker for omitted ancestor information.
|
|
38
|
+
* @param parent Bounded location already built from native schema fields.
|
|
39
|
+
* @param segment Native known-field segment or fixed diagnostic marker.
|
|
40
|
+
* @returns At most 2048 characters, including an explicit truncation marker when necessary.
|
|
41
|
+
*/
|
|
42
|
+
export declare function appendDiagnosticLocation(parent: string, segment: string): string;
|
|
43
|
+
/** Publish safe diagnostic facts while retaining native source ownership privately.
|
|
44
|
+
* @param context Actual semantic owner of the failed declaration.
|
|
45
|
+
* @param declaration Actual declaration selected by native matching.
|
|
46
|
+
* @param location Location built from matched schema fields.
|
|
47
|
+
* @param options Optional scalar builtin and unknown-field marker.
|
|
48
|
+
* @returns Bounded, immutable, value-redacted metadata with no native handles or source URI.
|
|
49
|
+
*/
|
|
50
|
+
export declare function createPayloadDiagnostic(context: SemanticContext, declaration: ElementNode | AttributeNode, location: string, options?: Readonly<{
|
|
51
|
+
builtin?: ExpandedName;
|
|
52
|
+
unknownField?: boolean;
|
|
53
|
+
}>): CheckedPayloadDiagnostic;
|
|
54
|
+
/** Read native provenance only for a diagnostic emitted in this process.
|
|
55
|
+
* @param diagnostic Exact issued diagnostic object.
|
|
56
|
+
* @returns Native declaration ownership or undefined for copied/public data.
|
|
57
|
+
* @constraints This trusted internal seam is not a public HTTP or JSON authority surface.
|
|
58
|
+
*/
|
|
59
|
+
export declare function inspectPayloadDiagnostic(diagnostic: CheckedPayloadDiagnostic): Evidence | undefined;
|
|
60
|
+
export {};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/** Bounded value-redacted facts emitted by the native declaration owner.
|
|
2
|
+
* @since 1.5.0
|
|
3
|
+
* @constraints Display metadata never establishes validation or correction authority.
|
|
4
|
+
*/
|
|
5
|
+
import { createHash } from 'node:crypto';
|
|
6
|
+
const evidence = new WeakMap();
|
|
7
|
+
const hash = (value) => createHash('sha256').update(value).digest('hex');
|
|
8
|
+
/** Format only a schema-established expanded name, with a bounded occurrence index.
|
|
9
|
+
* @param name Name supplied by the native schema owner, never an unknown payload name.
|
|
10
|
+
* @param index One-based occurrence index.
|
|
11
|
+
* @returns A bounded readable location segment.
|
|
12
|
+
*/
|
|
13
|
+
export function diagnosticName(name, index = 1) {
|
|
14
|
+
return `{${name.namespace.slice(0, 256)}}${name.local.slice(0, 256)}[${index > 100_000 ? '>100000' : index}]`
|
|
15
|
+
+ (name.namespace.length > 256 || name.local.length > 256 ? '[truncated]' : '');
|
|
16
|
+
}
|
|
17
|
+
/** Append a known-field segment while retaining an explicit marker for omitted ancestor information.
|
|
18
|
+
* @param parent Bounded location already built from native schema fields.
|
|
19
|
+
* @param segment Native known-field segment or fixed diagnostic marker.
|
|
20
|
+
* @returns At most 2048 characters, including an explicit truncation marker when necessary.
|
|
21
|
+
*/
|
|
22
|
+
export function appendDiagnosticLocation(parent, segment) {
|
|
23
|
+
if (parent.endsWith('/[truncated]'))
|
|
24
|
+
return parent;
|
|
25
|
+
const location = parent + '/' + segment;
|
|
26
|
+
return location.length > 2048 ? location.slice(0, 2036) + '/[truncated]' : location;
|
|
27
|
+
}
|
|
28
|
+
/** Publish safe diagnostic facts while retaining native source ownership privately.
|
|
29
|
+
* @param context Actual semantic owner of the failed declaration.
|
|
30
|
+
* @param declaration Actual declaration selected by native matching.
|
|
31
|
+
* @param location Location built from matched schema fields.
|
|
32
|
+
* @param options Optional scalar builtin and unknown-field marker.
|
|
33
|
+
* @returns Bounded, immutable, value-redacted metadata with no native handles or source URI.
|
|
34
|
+
*/
|
|
35
|
+
export function createPayloadDiagnostic(context, declaration, location, options = {}) {
|
|
36
|
+
let truncated = location.length > 2048 || location.includes('[truncated]') || location.includes('[>100000]');
|
|
37
|
+
const name = (value) => {
|
|
38
|
+
if (value.namespace.length > 256 || value.local.length > 256)
|
|
39
|
+
truncated = true;
|
|
40
|
+
return Object.freeze({ namespace: value.namespace.slice(0, 256), local: value.local.slice(0, 256) });
|
|
41
|
+
};
|
|
42
|
+
let ownerId = context.owner(declaration.id), owner;
|
|
43
|
+
for (let depth = 0; ownerId && depth < 32; depth++) {
|
|
44
|
+
const found = context.lookup(ownerId);
|
|
45
|
+
if (found.kind !== 'found')
|
|
46
|
+
break;
|
|
47
|
+
if (found.node.identity.kind === 'global') {
|
|
48
|
+
owner = Object.freeze({ kind: found.node.kind, name: name(found.node.identity.name) });
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
ownerId = context.owner(ownerId);
|
|
52
|
+
}
|
|
53
|
+
const resolved = context.resolve(declaration.type);
|
|
54
|
+
const datatype = resolved.kind === 'builtin' ? resolved.name : resolved.kind === 'found'
|
|
55
|
+
&& resolved.node.identity.kind === 'global' ? resolved.node.identity.name : undefined;
|
|
56
|
+
const source = declaration.context.source;
|
|
57
|
+
const result = Object.freeze({ version: 'checked-payload-diagnostic-v1',
|
|
58
|
+
universe: `universe-${hash(context.contextId)}`, resource: `resource-${hash(source.uri)}`, digest: source.digest,
|
|
59
|
+
declaration: Object.freeze({ kind: declaration.kind, name: name(declaration.name) }),
|
|
60
|
+
...(owner ? { owner } : {}), ...(datatype ? { datatype: name(datatype) } : {}),
|
|
61
|
+
...(options.builtin ? { builtin: name(options.builtin) } : {}), location: location.slice(0, 2048),
|
|
62
|
+
...(options.unknownField ? { unknownField: true } : {}), ...(truncated ? { truncated: true } : {}) });
|
|
63
|
+
evidence.set(result, { context, declaration });
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
/** Read native provenance only for a diagnostic emitted in this process.
|
|
67
|
+
* @param diagnostic Exact issued diagnostic object.
|
|
68
|
+
* @returns Native declaration ownership or undefined for copied/public data.
|
|
69
|
+
* @constraints This trusted internal seam is not a public HTTP or JSON authority surface.
|
|
70
|
+
*/
|
|
71
|
+
export function inspectPayloadDiagnostic(diagnostic) {
|
|
72
|
+
return evidence.get(diagnostic);
|
|
73
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { SchemaInput } from "../loader/schemaInput.js";
|
|
2
|
+
import type { SyntaxSource } from "../loader/orderedSyntax.js";
|
|
2
3
|
import type { OfflineSchemaResource } from "../loader/schemaResources.js";
|
|
3
4
|
import type { GraphNode, NodeId } from "./canonicalGraph.js";
|
|
4
5
|
export type SourceCorrectionSelector = Readonly<{
|
|
@@ -42,6 +43,10 @@ export type SourceCorrectionInventoryNode = Readonly<{
|
|
|
42
43
|
parent?: NodeId;
|
|
43
44
|
chameleon: boolean;
|
|
44
45
|
ambiguousContext: boolean;
|
|
46
|
+
declaredType?: Readonly<{
|
|
47
|
+
namespace: string;
|
|
48
|
+
local: string;
|
|
49
|
+
}>;
|
|
45
50
|
}>;
|
|
46
51
|
export type SourceCorrectionInventory = Readonly<{
|
|
47
52
|
version: 1;
|
|
@@ -88,7 +93,19 @@ type InsertCorrection = Readonly<{
|
|
|
88
93
|
position: InsertPosition;
|
|
89
94
|
element: InsertElement;
|
|
90
95
|
}>;
|
|
91
|
-
type
|
|
96
|
+
type DeclaredTypeCorrection = Readonly<{
|
|
97
|
+
kind: "set-declared-type";
|
|
98
|
+
target: SourceCorrectionSelector;
|
|
99
|
+
expectedType: Readonly<{
|
|
100
|
+
namespace: string;
|
|
101
|
+
local: string;
|
|
102
|
+
}>;
|
|
103
|
+
type: Readonly<{
|
|
104
|
+
namespace: string;
|
|
105
|
+
local: string;
|
|
106
|
+
}>;
|
|
107
|
+
}>;
|
|
108
|
+
type Correction = OccursCorrection | WildcardCorrection | InsertCorrection | DeclaredTypeCorrection;
|
|
92
109
|
export type SourceCorrectionProvenance = Readonly<{
|
|
93
110
|
version: 1;
|
|
94
111
|
canonicalRecipeDigest: string;
|
|
@@ -107,6 +124,38 @@ export type PreparedCorrectedSourceInputs = Readonly<{
|
|
|
107
124
|
inventory: SourceCorrectionInventory;
|
|
108
125
|
resources: ReadonlyMap<string, OfflineSchemaResource>;
|
|
109
126
|
provenance: SourceCorrectionProvenance;
|
|
127
|
+
originalTargets: readonly Readonly<{
|
|
128
|
+
uri: string;
|
|
129
|
+
effectiveDigest: string;
|
|
130
|
+
effectiveStart: number;
|
|
131
|
+
namespace: string;
|
|
132
|
+
kind: GraphNode['kind'];
|
|
133
|
+
ambiguous: boolean;
|
|
134
|
+
target: SourceCorrectionSelector;
|
|
135
|
+
declaredType?: Readonly<{
|
|
136
|
+
namespace: string;
|
|
137
|
+
local: string;
|
|
138
|
+
}>;
|
|
139
|
+
}>[];
|
|
140
|
+
}>;
|
|
141
|
+
/** Recover an original target from the actual edit spans, never an effective ordinal path.
|
|
142
|
+
* @param prepared Native correction result retaining the original pinned inventory and edit correspondence.
|
|
143
|
+
* @param source Effective declaration source obtained from its native semantic owner.
|
|
144
|
+
* @param namespace Effective schema interpretation namespace.
|
|
145
|
+
* @param kind Actual declaration kind.
|
|
146
|
+
* @returns A pinned original selector, or an explicit reason when correspondence is unavailable or ambiguous.
|
|
147
|
+
* @constraints This trusted local authoring result contains source locators and must not enter HTTP diagnostics.
|
|
148
|
+
*/
|
|
149
|
+
export declare function recoverOriginalSourceCorrectionTarget(prepared: PreparedCorrectedSourceInputs, source: SyntaxSource, namespace: string, kind: GraphNode['kind']): Readonly<{
|
|
150
|
+
kind: 'available';
|
|
151
|
+
target: SourceCorrectionSelector;
|
|
152
|
+
declaredType?: Readonly<{
|
|
153
|
+
namespace: string;
|
|
154
|
+
local: string;
|
|
155
|
+
}>;
|
|
156
|
+
} | {
|
|
157
|
+
kind: 'unavailable';
|
|
158
|
+
reason: 'original-target-not-established' | 'ambiguous-original-target';
|
|
110
159
|
}>;
|
|
111
160
|
export declare class SourceCorrectionError extends Error {
|
|
112
161
|
readonly category: "invalid-recipe" | "stale-source" | "ambiguous-target" | "unsupported-target" | "resource-limit" | "cancelled";
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/** Explicit, source-pinned XML corrections for loaded schema inputs. */
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
3
|
import { copyOriginalSchemaResource } from "../loader/schemaInput.js";
|
|
4
|
-
import { decodeMetadataXml, isXmlNCName, syntaxElements, XSD_NAMESPACE } from "../loader/orderedSyntax.js";
|
|
4
|
+
import { decodeMetadataXml, isXmlNCName, syntaxAttribute, syntaxElements, XSD_NAMESPACE } from "../loader/orderedSyntax.js";
|
|
5
5
|
import { DEFAULT_GRAPH_NODES, GraphError, isBuiltinType } from "./canonicalGraph.js";
|
|
6
6
|
import { buildCanonicalGraph } from "./buildCanonicalGraph.js";
|
|
7
7
|
import { namespaceAllows, wildcardNamespaces } from './wildcardAlgebra.js';
|
|
8
|
+
import { normalizeWhitespace } from '../util/xmlWhitespace.js';
|
|
8
9
|
const RECIPE_VERSION = 1;
|
|
9
10
|
const MAX_RECIPE_BYTES = 256 * 1024;
|
|
10
11
|
const MAX_RECIPE_DEPTH = 32;
|
|
@@ -13,6 +14,22 @@ const MAX_OUTPUT_GROWTH = 2 * 1024 * 1024;
|
|
|
13
14
|
const MAX_SYNTAX_INDEX_NODES = DEFAULT_GRAPH_NODES;
|
|
14
15
|
const MAX_LEXICAL_SCAN = 64 * 1024 * 1024;
|
|
15
16
|
const HASH = /^[a-f0-9]{64}$/;
|
|
17
|
+
/** Recover an original target from the actual edit spans, never an effective ordinal path.
|
|
18
|
+
* @param prepared Native correction result retaining the original pinned inventory and edit correspondence.
|
|
19
|
+
* @param source Effective declaration source obtained from its native semantic owner.
|
|
20
|
+
* @param namespace Effective schema interpretation namespace.
|
|
21
|
+
* @param kind Actual declaration kind.
|
|
22
|
+
* @returns A pinned original selector, or an explicit reason when correspondence is unavailable or ambiguous.
|
|
23
|
+
* @constraints This trusted local authoring result contains source locators and must not enter HTTP diagnostics.
|
|
24
|
+
*/
|
|
25
|
+
export function recoverOriginalSourceCorrectionTarget(prepared, source, namespace, kind) {
|
|
26
|
+
const matches = prepared.originalTargets.filter(item => item.uri === source.uri && item.effectiveDigest === source.digest
|
|
27
|
+
&& item.effectiveStart === source.start.offset && item.namespace === namespace && item.kind === kind);
|
|
28
|
+
if (matches.length !== 1 || matches[0].ambiguous)
|
|
29
|
+
return { kind: 'unavailable',
|
|
30
|
+
reason: matches.length > 1 || matches[0]?.ambiguous ? 'ambiguous-original-target' : 'original-target-not-established' };
|
|
31
|
+
return { kind: 'available', target: matches[0].target, ...(matches[0].declaredType ? { declaredType: matches[0].declaredType } : {}) };
|
|
32
|
+
}
|
|
16
33
|
export class SourceCorrectionError extends Error {
|
|
17
34
|
category;
|
|
18
35
|
constructor(category, message) {
|
|
@@ -127,6 +144,13 @@ function checkOccurs(min, max, label) {
|
|
|
127
144
|
if (max !== "unbounded" && (min.length > max.length || (min.length === max.length && min > max)))
|
|
128
145
|
fail("invalid-recipe", `${label} minimum exceeds maximum`);
|
|
129
146
|
}
|
|
147
|
+
function expandedType(value, label) {
|
|
148
|
+
const item = record(value, ["namespace", "local"], label);
|
|
149
|
+
const namespace = text(item.namespace, `${label}.namespace`, true), local = text(item.local, `${label}.local`);
|
|
150
|
+
if (!isXmlNCName(local))
|
|
151
|
+
fail("invalid-recipe", `${label}.local must be an XML NCName`);
|
|
152
|
+
return Object.freeze({ namespace, local });
|
|
153
|
+
}
|
|
130
154
|
function parseRecipe(value, signal) {
|
|
131
155
|
boundedJsonSize(value, signal);
|
|
132
156
|
const root = record(value, ["version", "resources", "corrections"], "recipe");
|
|
@@ -145,6 +169,12 @@ function parseRecipe(value, signal) {
|
|
|
145
169
|
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
146
170
|
fail("invalid-recipe", `corrections[${index}] must be an object`);
|
|
147
171
|
const kind = raw.kind;
|
|
172
|
+
if (kind === "set-declared-type") {
|
|
173
|
+
const item = record(raw, ["kind", "target", "expectedType", "type"], `corrections[${index}]`);
|
|
174
|
+
return Object.freeze({ kind, target: selector(item.target, `corrections[${index}].target`),
|
|
175
|
+
expectedType: expandedType(item.expectedType, `corrections[${index}].expectedType`),
|
|
176
|
+
type: expandedType(item.type, `corrections[${index}].type`) });
|
|
177
|
+
}
|
|
148
178
|
if (kind === "set-occurs") {
|
|
149
179
|
const item = record(raw, ["kind", "target", "min", "max"], `corrections[${index}]`);
|
|
150
180
|
const min = occurrence(item.min, `corrections[${index}].min`, false), max = occurrence(item.max, `corrections[${index}].max`, true);
|
|
@@ -312,9 +342,10 @@ function targetMap(inputs, signal) {
|
|
|
312
342
|
if (old.node.context.effectiveNamespace !== node.context.effectiveNamespace || old.node.context.source.path !== source.path) {
|
|
313
343
|
fail("ambiguous-target", "One canonical identity has conflicting schema contexts");
|
|
314
344
|
}
|
|
345
|
+
old.universes.push(graph);
|
|
315
346
|
continue;
|
|
316
347
|
}
|
|
317
|
-
targets.set(key, { node, syntax, ambiguousContext: false });
|
|
348
|
+
targets.set(key, { node, syntax, ambiguousContext: false, universes: [graph] });
|
|
318
349
|
}
|
|
319
350
|
}
|
|
320
351
|
for (const target of targets.values()) {
|
|
@@ -350,7 +381,9 @@ function targetMap(inputs, signal) {
|
|
|
350
381
|
...(nodeName(node) ? { name: nodeName(node) } : {}), syntaxName: syntax.name, uri: source.uri, digest: source.digest, path: source.path,
|
|
351
382
|
start: source.start.offset, end: source.end.offset, attributes: rawAttributes, ...(node.kind === "particle" ? { occurs: node.occurs } : {}),
|
|
352
383
|
...(wildcard ? { wildcard } : {}), ...(children ? { children } : {}), ...(parents.has(node.id) ? { parent: parents.get(node.id) } : {}),
|
|
353
|
-
chameleon: Boolean(node.context.chameleon || contextChameleon.get(sourceKey(source.uri, source.digest, source.path))), ambiguousContext
|
|
384
|
+
chameleon: Boolean(node.context.chameleon || contextChameleon.get(sourceKey(source.uri, source.digest, source.path))), ambiguousContext,
|
|
385
|
+
...((node.kind === 'element' || node.kind === 'attribute') && syntaxAttribute(syntax, 'type') !== undefined && node.type.kind !== 'local'
|
|
386
|
+
? { declaredType: node.type.name } : {}) });
|
|
354
387
|
nodes.push(entry);
|
|
355
388
|
}
|
|
356
389
|
nodes.sort((a, b) => compareText(a.uri, b.uri) || compareText(a.path, b.path) || compareText(a.id, b.id));
|
|
@@ -588,6 +621,73 @@ function validateTargetType(qnameValue, targets) {
|
|
|
588
621
|
if (!exists)
|
|
589
622
|
fail("unsupported-target", "Inserted element type must refer to an existing named type or XML Schema builtin");
|
|
590
623
|
}
|
|
624
|
+
/** Check a replacement in each declaring source universe and schema interpretation.
|
|
625
|
+
* @param target Original authored declaration and its independent acquired universes.
|
|
626
|
+
* @param replacement Expanded replacement type, never a payload-derived guess.
|
|
627
|
+
* @param signal Cooperative cancellation signal.
|
|
628
|
+
* @param budget Shared bounded correction work allowance.
|
|
629
|
+
* @returns Nothing when every origin permits the existing type reference.
|
|
630
|
+
* @throws SourceCorrectionError For unavailable, illegal, unimported or ambiguous types, cancellation or exhausted work.
|
|
631
|
+
* @constraints An independent companion cannot lend its types or import permissions to this declaration.
|
|
632
|
+
*/
|
|
633
|
+
function validateDeclaredType(target, replacement, signal, budget) {
|
|
634
|
+
const charge = () => {
|
|
635
|
+
checkAbort(signal);
|
|
636
|
+
if (--budget.remaining < 0)
|
|
637
|
+
fail('resource-limit', 'Declared-type correction exceeds its work limit');
|
|
638
|
+
};
|
|
639
|
+
if (replacement.namespace === XSD_NAMESPACE && isBuiltinType(replacement.local)) {
|
|
640
|
+
if (target.node.kind === 'attribute' && replacement.local === 'anyType')
|
|
641
|
+
fail('unsupported-target', 'Attribute declared type must be simple');
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
for (const graph of target.universes) {
|
|
645
|
+
charge();
|
|
646
|
+
const matches = graph.nodes.filter(node => {
|
|
647
|
+
charge();
|
|
648
|
+
return (node.kind === 'simpleType' || node.kind === 'complexType') && node.identity.kind === 'global'
|
|
649
|
+
&& node.identity.name.namespace === replacement.namespace && node.identity.name.local === replacement.local;
|
|
650
|
+
});
|
|
651
|
+
if (matches.length !== 1)
|
|
652
|
+
fail(matches.length > 1 ? 'ambiguous-target' : 'unsupported-target', 'Replacement type must be an existing named type in the target source universe');
|
|
653
|
+
if (target.node.kind === 'attribute' && matches[0].kind !== 'simpleType')
|
|
654
|
+
fail('unsupported-target', 'Attribute declared type must be simple');
|
|
655
|
+
const imports = new Map();
|
|
656
|
+
const importSyntax = new Map();
|
|
657
|
+
for (const retained of graph.schemaRetained) {
|
|
658
|
+
charge();
|
|
659
|
+
if (retained.syntax.name.namespace === XSD_NAMESPACE && retained.syntax.name.local === 'import') {
|
|
660
|
+
const source = retained.syntax.source;
|
|
661
|
+
importSyntax.set(sourceKey(source.uri, source.digest, source.path), retained.syntax);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
for (const edge of graph.loading.edges) {
|
|
665
|
+
charge();
|
|
666
|
+
if (edge.kind !== 'import')
|
|
667
|
+
continue;
|
|
668
|
+
const syntax = importSyntax.get(sourceKey(edge.source.uri, edge.source.digest, edge.source.path));
|
|
669
|
+
if (!syntax)
|
|
670
|
+
fail('unsupported-target', 'Replacement type import prerequisite is unavailable');
|
|
671
|
+
const namespaces = imports.get(edge.from) ?? new Set();
|
|
672
|
+
namespaces.add(normalizeWhitespace(syntaxAttribute(syntax, 'namespace') ?? '', 'collapse'));
|
|
673
|
+
imports.set(edge.from, namespaces);
|
|
674
|
+
}
|
|
675
|
+
const source = target.node.context.source;
|
|
676
|
+
const origins = (graph.origins[target.node.id] ?? []).filter(origin => {
|
|
677
|
+
charge();
|
|
678
|
+
const actual = origin.context.source;
|
|
679
|
+
return sourceKey(actual.uri, actual.digest, actual.path) === sourceKey(source.uri, source.digest, source.path);
|
|
680
|
+
});
|
|
681
|
+
if (!origins.length)
|
|
682
|
+
fail('unsupported-target', 'Replacement type declaring interpretation is unavailable');
|
|
683
|
+
for (const origin of origins) {
|
|
684
|
+
charge();
|
|
685
|
+
if (replacement.namespace !== origin.context.effectiveNamespace && !imports.get(origin.interpretation)?.has(replacement.namespace)) {
|
|
686
|
+
fail('unsupported-target', 'Replacement type namespace is not imported by the declaring schema');
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
591
691
|
function decodedResource(source, cache) {
|
|
592
692
|
const prior = cache.get(source.uri);
|
|
593
693
|
if (prior !== undefined)
|
|
@@ -599,6 +699,30 @@ function decodedResource(source, cache) {
|
|
|
599
699
|
function validateCorrection(correction, targets, sources, decoded, claimed, insertSlots, signal, scanBudget = { remaining: MAX_LEXICAL_SCAN }) {
|
|
600
700
|
const target = targetFor(correction.target, targets), source = checkSelectorResource(correction.target, sources), node = target.node;
|
|
601
701
|
checkAbort(signal);
|
|
702
|
+
if (correction.kind === 'set-declared-type') {
|
|
703
|
+
if ((node.kind !== 'element' && node.kind !== 'attribute') || syntaxAttribute(target.syntax, 'ref') !== undefined
|
|
704
|
+
|| syntaxAttribute(target.syntax, 'type') === undefined || node.type.kind === 'local'
|
|
705
|
+
|| syntaxElements(target.syntax, XSD_NAMESPACE).some(child => child.name.local === 'simpleType' || child.name.local === 'complexType')) {
|
|
706
|
+
fail('unsupported-target', 'set-declared-type requires an authored element or attribute declaration with an existing @type; inline and ref forms refuse');
|
|
707
|
+
}
|
|
708
|
+
if (node.type.name.namespace !== correction.expectedType.namespace || node.type.name.local !== correction.expectedType.local) {
|
|
709
|
+
fail('stale-source', 'Declared type does not match expectedType in the original schema context');
|
|
710
|
+
}
|
|
711
|
+
validateDeclaredType(target, correction.type, signal, scanBudget);
|
|
712
|
+
if (node.context.chameleon && correction.type.namespace === '')
|
|
713
|
+
fail('unsupported-target', 'A no-namespace replacement would be reinterpreted by chameleon adoption');
|
|
714
|
+
const claim = `${sourceKey(source.uri, source.digest, node.context.source.path)}:type`;
|
|
715
|
+
if (claimed.has(claim))
|
|
716
|
+
fail('invalid-recipe', 'Conflicting correction for declared type');
|
|
717
|
+
claimed.add(claim);
|
|
718
|
+
const xml = decodedResource(source, decoded), replacement = qname(correction.type.namespace, correction.type.local, node.context.namespaces, new Set());
|
|
719
|
+
const edits = updateStartTag(xml, target, { type: replacement.lexical }, signal, scanBudget);
|
|
720
|
+
if (replacement.declaration) {
|
|
721
|
+
const tag = startTag(xml, node.context.source.start.offset, signal, scanBudget);
|
|
722
|
+
edits.push({ start: tag.close, end: tag.close, text: ` ${replacement.declaration}`, label: `${node.id}:attributes` });
|
|
723
|
+
}
|
|
724
|
+
return { patches: new Map([[source.uri, edits]]), provenance: { kind: correction.kind, target: correction.target } };
|
|
725
|
+
}
|
|
602
726
|
if (correction.kind === "set-occurs") {
|
|
603
727
|
if (node.kind !== "particle")
|
|
604
728
|
fail("unsupported-target", "set-occurs target must be an XSD particle");
|
|
@@ -692,9 +816,10 @@ function validateCorrection(correction, targets, sources, decoded, claimed, inse
|
|
|
692
816
|
insertSlots.add(slot);
|
|
693
817
|
return { patches: new Map([[source.uri, [{ start: position, end: replacementEnd ?? position, text, label: `${node.id}:insert` }]]]), provenance: { kind: correction.kind, target: correction.target } };
|
|
694
818
|
}
|
|
695
|
-
function
|
|
819
|
+
function normalizedPatches(patches, signal) {
|
|
696
820
|
const combined = new Map();
|
|
697
821
|
for (const patch of patches) {
|
|
822
|
+
checkAbort(signal);
|
|
698
823
|
const key = `${patch.start}:${patch.end}`;
|
|
699
824
|
const prior = combined.get(key);
|
|
700
825
|
if (!prior) {
|
|
@@ -706,10 +831,12 @@ function applyPatches(original, patches, signal) {
|
|
|
706
831
|
}
|
|
707
832
|
combined.set(key, { ...prior, text: prior.text + patch.text.replace(/^\s+/, " ") });
|
|
708
833
|
}
|
|
709
|
-
|
|
834
|
+
return [...combined.values()].sort((a, b) => a.start - b.start || a.end - b.end || compareText(a.label, b.label));
|
|
835
|
+
}
|
|
836
|
+
function applyPatches(original, patches, signal) {
|
|
710
837
|
let previousEnd = 0;
|
|
711
838
|
const chunks = [];
|
|
712
|
-
for (const patch of
|
|
839
|
+
for (const patch of patches) {
|
|
713
840
|
checkAbort(signal);
|
|
714
841
|
if (patch.start < previousEnd || patch.end < patch.start || patch.end > original.length)
|
|
715
842
|
fail("invalid-recipe", "Correction source spans overlap or exceed the original resource");
|
|
@@ -755,12 +882,14 @@ export function prepareCorrectedSourceInputs(inputs, recipeValue, signal) {
|
|
|
755
882
|
provenanceCorrections.push(result.provenance);
|
|
756
883
|
}
|
|
757
884
|
const resources = new Map(), provenanceResources = [];
|
|
885
|
+
const originalTargets = [];
|
|
758
886
|
let effectiveBytesTotal = 0;
|
|
759
887
|
let outputGrowth = 0;
|
|
760
888
|
const outputLimit = Math.min(...inputs.map(input => input.limits.totalBytes)), singleLimit = Math.min(...inputs.map(input => input.limits.resourceBytes));
|
|
761
889
|
for (const source of [...sources.values()].sort((a, b) => compareText(a.uri, b.uri))) {
|
|
762
890
|
checkAbort(signal);
|
|
763
|
-
|
|
891
|
+
// @constraints Application and original-target correspondence consume exactly the same combined edits.
|
|
892
|
+
const edits = normalizedPatches(patches.get(source.uri) ?? [], signal);
|
|
764
893
|
const originalText = edits.length ? decodedResource(source, decoded) : "";
|
|
765
894
|
const updated = edits.length ? applyPatches(originalText, edits, signal) : "";
|
|
766
895
|
const bytes = edits.length && updated !== originalText ? utf8Output(updated, source.bytes) : source.bytes;
|
|
@@ -773,6 +902,26 @@ export function prepareCorrectedSourceInputs(inputs, recipeValue, signal) {
|
|
|
773
902
|
if (outputGrowth > MAX_OUTPUT_GROWTH)
|
|
774
903
|
fail("resource-limit", "Correction output growth exceeds the limit");
|
|
775
904
|
const effectiveDigest = sha256(bytes), changed = effectiveDigest !== source.digest;
|
|
905
|
+
// @constraints Encoding-declaration rewriting is outside edit-span correspondence; refuse rather than guess.
|
|
906
|
+
if (!edits.length || decodeMetadataXml(bytes) === updated)
|
|
907
|
+
for (const node of inventory.nodes) {
|
|
908
|
+
checkAbort(signal);
|
|
909
|
+
if (node.uri !== source.uri || node.digest !== source.digest)
|
|
910
|
+
continue;
|
|
911
|
+
let effectiveStart = node.start, established = true;
|
|
912
|
+
for (const edit of edits) {
|
|
913
|
+
if (edit.end <= node.start)
|
|
914
|
+
effectiveStart += edit.text.length - (edit.end - edit.start);
|
|
915
|
+
else if (edit.start <= node.start)
|
|
916
|
+
established = false;
|
|
917
|
+
}
|
|
918
|
+
const native = targets.get(selectorKey({ id: node.id, uri: node.uri, digest: node.digest }))?.node;
|
|
919
|
+
const declaredType = native && (native.kind === 'element' || native.kind === 'attribute') && native.type.kind !== 'local' ? native.type.name : undefined;
|
|
920
|
+
if (established)
|
|
921
|
+
originalTargets.push(Object.freeze({ uri: source.uri, effectiveDigest, effectiveStart,
|
|
922
|
+
namespace: node.namespace, kind: node.kind, ambiguous: node.ambiguousContext,
|
|
923
|
+
target: Object.freeze({ id: node.id, uri: node.uri, digest: node.digest }), ...(declaredType ? { declaredType } : {}) }));
|
|
924
|
+
}
|
|
776
925
|
const resource = Object.freeze({ bytes: new Uint8Array(bytes), digest: effectiveDigest });
|
|
777
926
|
resources.set(source.uri, resource);
|
|
778
927
|
for (const alias of source.aliases)
|
|
@@ -790,5 +939,5 @@ export function prepareCorrectedSourceInputs(inputs, recipeValue, signal) {
|
|
|
790
939
|
}
|
|
791
940
|
const provenance = Object.freeze({ version: 1, canonicalRecipeDigest: canonicalRecipeDigest(recipe),
|
|
792
941
|
resources: Object.freeze(provenanceResources), corrections: Object.freeze(provenanceCorrections.map(item => Object.freeze(item))) });
|
|
793
|
-
return Object.freeze({ inventory, resources, provenance });
|
|
942
|
+
return Object.freeze({ inventory, resources, provenance, originalTargets: Object.freeze(originalTargets) });
|
|
794
943
|
}
|
|
@@ -84,6 +84,21 @@ async function generateGatewayFromReconstruction(expected, options) {
|
|
|
84
84
|
} };
|
|
85
85
|
const fault = { oneOf: [...faultVariants(false), protocolFault] };
|
|
86
86
|
const documentedFault = { oneOf: [...faultVariants(true), protocolFault] };
|
|
87
|
+
const diagnosticNameSchema = { type: 'object', additionalProperties: false, required: ['namespace', 'local'],
|
|
88
|
+
properties: { namespace: { type: 'string', maxLength: 256 }, local: { type: 'string', maxLength: 256 } } };
|
|
89
|
+
const diagnosticSchema = { type: 'object', additionalProperties: false,
|
|
90
|
+
required: ['version', 'universe', 'resource', 'digest', 'declaration', 'location'], properties: {
|
|
91
|
+
version: { const: 'checked-payload-diagnostic-v1' }, universe: { type: 'string', pattern: '^universe-[a-f0-9]{64}$' },
|
|
92
|
+
resource: { type: 'string', pattern: '^resource-[a-f0-9]{64}$' }, digest: { type: 'string', pattern: '^[a-f0-9]{64}$' },
|
|
93
|
+
declaration: { type: 'object', additionalProperties: false, required: ['kind', 'name'],
|
|
94
|
+
properties: { kind: { enum: ['element', 'attribute'] }, name: diagnosticNameSchema } },
|
|
95
|
+
owner: { type: 'object', additionalProperties: false, required: ['kind'],
|
|
96
|
+
properties: { kind: { type: 'string' }, name: diagnosticNameSchema } },
|
|
97
|
+
datatype: diagnosticNameSchema, builtin: diagnosticNameSchema, location: { type: 'string', maxLength: 2048 },
|
|
98
|
+
unknownField: { const: true }, truncated: { const: true },
|
|
99
|
+
} };
|
|
100
|
+
const failureSchema = { type: 'object', additionalProperties: false, required: ['kind', 'rule'],
|
|
101
|
+
properties: { kind: { type: 'string' }, rule: { type: 'string' }, diagnostic: diagnosticSchema } };
|
|
87
102
|
const schemas = { request: { $schema: draft07, ...request }, response: { $schema: draft07, ...response }, fault: { $schema: draft07, ...fault } };
|
|
88
103
|
const doc = {
|
|
89
104
|
openapi: '3.1.1', jsonSchemaDialect: 'https://json-schema.org/draft/2020-12/schema',
|
|
@@ -102,8 +117,9 @@ async function generateGatewayFromReconstruction(expected, options) {
|
|
|
102
117
|
responses: { '200': { description: 'Independently validated SOAP response', content: { 'application/json': { schema: documentation.response } } },
|
|
103
118
|
'400': { description: 'Invalid HTTP request; no SOAP request is dispatched' },
|
|
104
119
|
'502': { description: 'SOAP fault or refused upstream response', content: { 'application/json': { schema: { oneOf: [documentedFault,
|
|
105
|
-
|
|
106
|
-
'
|
|
120
|
+
failureSchema] } } } },
|
|
121
|
+
'499': { description: 'Cancelled request', content: { 'application/json': { schema: failureSchema } } },
|
|
122
|
+
'500': { description: 'Incompatible artifacts or gateway configuration', content: { 'application/json': { schema: failureSchema } } } } } } },
|
|
107
123
|
};
|
|
108
124
|
let clientImport = path.relative(options.gatewayDir, path.join(options.clientDir, 'client.js')).replace(/\\/g, '/');
|
|
109
125
|
if (!clientImport.startsWith('.'))
|
|
@@ -176,7 +192,7 @@ const plugin: FastifyPluginAsync<CheckedGatewayOptions> = async (fastify, option
|
|
|
176
192
|
}
|
|
177
193
|
const status = result.kind === 'incompatible-artifact' || result.kind === 'missing-authority' ? 500
|
|
178
194
|
: result.kind === 'cancelled' ? 499 : result.phase === 'input' ? 400 : 502;
|
|
179
|
-
return reply.code(status).send({kind: result.kind, rule: result.rule});
|
|
195
|
+
return reply.code(status).send({kind: result.kind, rule: result.rule, ...(result.diagnostic ? {diagnostic: result.diagnostic} : {})});
|
|
180
196
|
} finally {
|
|
181
197
|
request.raw.removeListener('aborted', disconnected);
|
|
182
198
|
reply.raw.removeListener('close', disconnected);
|
package/dist/index.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ export type { CheckedComponentOptions, CheckedInventory } from './checkedPipelin
|
|
|
13
13
|
export { planCheckedComponents, formatCheckedComponentPlan, writeCheckedPlanningFile, CheckedPlanningError } from './checkedPlanning.js';
|
|
14
14
|
export type { CheckedComponentPlan, ComponentPlanBlocker } from './checkedPlanning.js';
|
|
15
15
|
export { inspectCheckedResponse } from './checkedResponseInspection.js';
|
|
16
|
-
export type { CheckedResponseInspectionOptions, CheckedResponseInspectionReport } from './checkedResponseInspection.js';
|
|
16
|
+
export type { CheckedResponseInspectionOptions, CheckedResponseInspectionReport, CheckedResponseCorrectionTarget } from './checkedResponseInspection.js';
|
|
17
17
|
export type { ComponentRecordProjectionSpec, ComponentRecordProjectionAssociation } from './compiler/componentRecordProjection.js';
|
|
18
18
|
export { loadStreamConfigFile, parseStreamConfig, StreamConfigError, type OperationStreamMetadata, type ShapeCatalogRef, type StreamConfig, } from "./util/streamConfig.js";
|
|
19
19
|
export { applyShapeCatalogs, type ApplyShapeCatalogsOptions } from "./compiler/shapeResolver.js";
|
|
@@ -50,3 +50,4 @@ export declare function compileWsdlToProject(input: {
|
|
|
50
50
|
export { assessSchemaSource, serializeAssessmentReport, parseAssessmentReport, assessmentExitCode, ASSESSMENT_VERSION, ASSESSMENT_BASIS } from './compiler/checkedAssessment.js';
|
|
51
51
|
export type { ApplicationGroupsOptions, ApplicationGroupName } from './compiler/applicationGroups.js';
|
|
52
52
|
export type { CheckedAssessmentReport, AssessmentObligation, AssessedOperation, AssessmentStatus } from './compiler/checkedAssessment.js';
|
|
53
|
+
export type { CheckedPayloadDiagnostic } from './compiler/payloadDiagnostics.js';
|
package/docs/api-reference.md
CHANGED
|
@@ -21,6 +21,8 @@ console.log(report.kind);
|
|
|
21
21
|
|
|
22
22
|
The report separates wrapper and companion outcomes and excludes raw XML and decoded values. Configuration can select a consumer-owned `sourceCorrectionsFile`; the same effective contract is used for planning, inspection and generation. A correction draft does not authorize execution until its semantic choices are completed. See [source corrections and offline inspection](checked-gateway.md#consumer-owned-source-corrections).
|
|
23
23
|
|
|
24
|
+
Optional `diagnostic` metadata uses `CheckedPayloadDiagnostic` and `checked-payload-diagnostic-v1` across inspection and newly generated runtime/gateway failures. It identifies native scoped declarations, effective types and bounded known-field locations without source locators or payload values. For explicit local authoring, `includeCorrectionTargets: true` adds `CheckedResponseCorrectionTarget` descriptors with original pinned selectors and original `declaredType`, or an unavailable reason; retain these trusted locators outside HTTP responses and logs. See [trusted original correction targets](checked-gateway.md#trusted-original-correction-targets) for compatibility and recovery rules.
|
|
25
|
+
|
|
24
26
|
Set `includeCorrectionDraft: true` to request an available correction draft. Its exact resource pins can contain local source URIs; default reports retain only diagnostic resource labels.
|
|
25
27
|
|
|
26
28
|
Pass the complete multi-operation checked configuration unchanged. Inspection authenticates the complete correction-source closure before selecting the requested operation and its projection; inactive companions retain their freshness checks.
|
package/docs/checked-gateway.md
CHANGED
|
@@ -104,7 +104,7 @@ Set `sourceCorrectionsFile` in checked generation configuration to apply a versi
|
|
|
104
104
|
|
|
105
105
|
Maintain upstream snapshots unchanged and keep the correction file under version control. The correction file pins the complete original resource set by logical URI and digest. Refreshing upstream metadata requires regenerating and reviewing the correction draft. Scoped targets identify original declarations and particles, not generated TypeScript names. All edits resolve against the original inventory before application.
|
|
106
106
|
|
|
107
|
-
The bounded operations change particle occurrence bounds, narrow element wildcard namespaces,
|
|
107
|
+
The bounded operations change particle occurrence bounds, narrow element wildcard namespaces, insert a typed element into an existing sequence, or change an authored element or attribute type reference. Insertions require an existing named type or global element reference. Corrections do not define new types, rewrite imports, or change incoming SOAP XML. Use replacement snapshots for unsupported restructuring or sources that cannot construct a canonical inventory.
|
|
108
108
|
|
|
109
109
|
The correction document has `version: 1`, `resources: [{uri, digest}]`, and `corrections`. Retain every original resource pin supplied by the tool. A target is `{id, uri, digest}` copied from the inventory or draft; do not replace it with a generated DTO name. Complete unresolved draft values before passing the file to generation.
|
|
110
110
|
|
|
@@ -113,6 +113,7 @@ The correction document has `version: 1`, `resources: [{uri, digest}]`, and `cor
|
|
|
113
113
|
| `set-occurs` | `target`, `min`, and `max`; bounds are canonical decimal strings, with `"unbounded"` also permitted for `max` |
|
|
114
114
|
| `set-wildcard-namespaces` | `target` and `namespaces`; use explicit namespace strings and `""` for the no-namespace case; the original `processContents` is preserved |
|
|
115
115
|
| `insert-element` | Sequence `target`, `position`, and `element`; position is `"append"` or `{kind: "before" or "after", target: originalChildTarget}` |
|
|
116
|
+
| `set-declared-type` | Declaration `target`, original `expectedType`, and replacement `type`, both as `{namespace, local}` |
|
|
116
117
|
|
|
117
118
|
For a local inserted declaration, `element` contains `{kind: "declaration", name, type: {namespace, local}, min, max}`. The type must be a built-in XSD type or an existing named type. A global element reference instead uses `{kind: "reference", target: globalElementTarget, min, max}`. For example, a reviewed new scalar can use `type: {namespace: "http://www.w3.org/2001/XMLSchema", local: "string"}`; selecting that type is a contract decision, not an inference from one text value. Local declarations follow the containing schema's element-form rules.
|
|
118
119
|
|
|
@@ -124,6 +125,27 @@ Effective metadata is freshly parsed and checked before generation. Native decod
|
|
|
124
125
|
|
|
125
126
|
One generated project retains one effective byte sequence per logical URI. Shared declarations change for all their uses in that project. Conflicting URI contents and interpretation-specific edits to shared chameleon declarations refuse; separate replacement snapshots are required for those cases. This feature does not extend checked multi-service composition or legacy generation.
|
|
126
127
|
|
|
128
|
+
#### Changing an existing declared type
|
|
129
|
+
|
|
130
|
+
Use `set-declared-type` for an existing authored element or attribute `@type`. Obtain the original declaration selector and `declaredType` through [trusted correction targets](#trusted-original-correction-targets), then copy `declaredType` into `expectedType`. Keep every original resource pin; an effective datatype or ordinal path alone does not establish an original target.
|
|
131
|
+
|
|
132
|
+
```json
|
|
133
|
+
{
|
|
134
|
+
"kind": "set-declared-type",
|
|
135
|
+
"target": {"id": "ORIGINAL_DECLARATION_ID", "uri": "ORIGINAL_RESOURCE_URI", "digest": "ORIGINAL_SHA256"},
|
|
136
|
+
"expectedType": {"namespace": "http://www.w3.org/2001/XMLSchema", "local": "anyURI"},
|
|
137
|
+
"type": {"namespace": "http://www.w3.org/2001/XMLSchema", "local": "string"}
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
The original type guard compares expanded names resolved in the declaration's schema context. Replacements may select XML Schema builtins or existing named types from the declaration's authorized source universe. A foreign named type also requires an existing import in the declaring schema; another companion or including schema cannot lend its type declarations or import permissions. Namespace prefixes are emitted deterministically without changing expanded names.
|
|
142
|
+
|
|
143
|
+
Inline anonymous types, implicit types, `ref` uses, new definitions and new imports refuse. Attribute replacements require simple types. Missing or stale selectors, mismatched old types, conflicting edits and ambiguous shared chameleon interpretations also refuse; pins are never refreshed automatically.
|
|
144
|
+
|
|
145
|
+
Changing `anyURI` to `string` admits raw text and escaped embed content while removing the URI guarantee. Changing `dateTime` to `string` also admits nonsense. An existing constrained string type may check a timestamp layout while still accepting impossible calendar dates; it does not prove calendar validity or normalize timezones.
|
|
146
|
+
|
|
147
|
+
Every use of the selected shared declaration adopts its new type in that project. Independent source universes with colliding names keep their own declarations. Original source and response bytes remain unchanged; regenerate and qualify matching client, native/runtime, capsule and OpenAPI artifacts against the new effective bytes before adoption.
|
|
148
|
+
|
|
127
149
|
### Offline response inspection
|
|
128
150
|
|
|
129
151
|
Use `inspect-response` with a local WSDL, closed snapshot configuration, one configured operation and a saved SOAP response. It does not acquire missing remote metadata, dispatch SOAP, or read credentials. Create the output directory first; existing report or draft files are not overwritten.
|
|
@@ -140,6 +162,40 @@ Drafts retain exact logical source URIs, which can include local filesystem path
|
|
|
140
162
|
|
|
141
163
|
Exit codes are 0 for conformance within the reported scope, 1 for mismatch/refusal, and 2 for invocation/file errors. A conforming SOAP fault remains identified as a fault. The package-root `inspectCheckedResponse` API accepts the same checked choices, a full operation selector, response bytes, optional actual response metadata, cancellation and XML limits; it returns diagnostic data without writing files.
|
|
142
164
|
|
|
165
|
+
### Scoped payload diagnostics
|
|
166
|
+
|
|
167
|
+
Native element and attribute failures can include an optional `diagnostic` object in inspection reports, direct runtime failures and newly generated gateway error responses. `checked-payload-diagnostic-v1` identifies the source universe and resource with opaque hashes, retains the effective source digest, and identifies the declared field, owning declaration and effective datatype when established by the native owner. A scalar diagnostic also identifies its builtin datatype.
|
|
168
|
+
|
|
169
|
+
`location` uses schema-established expanded names and one-based occurrence indexes. An unknown child or wrapper is represented by `[unknown-field]` under the nearest known parent; its caller-controlled name is omitted. Diagnostics contain no XML values, source URI, absolute local path, raw exception or native handle.
|
|
170
|
+
|
|
171
|
+
Names and locations are bounded, and `truncated: true` indicates shortened diagnostic metadata. An occurrence above 100000 is displayed as `[>100000]`; omitted ancestor information is marked `[truncated]`. Neither marker is an exact original location or a correction selector.
|
|
172
|
+
|
|
173
|
+
This metadata is additive: the inspection report remains `checked-response-inspection-v1`, existing runtime bindings retain their versions, and consumers must continue branching on the existing failure `kind` and `rule`. Existing generated gateways keep their previous envelope until regenerated; regenerate the matching client, gateway and OpenAPI artifacts to obtain the documented optional `diagnostic`. HTTP status and native strict validation remain unchanged; a missing diagnostic never makes a failure acceptable.
|
|
174
|
+
|
|
175
|
+
### Trusted original correction targets
|
|
176
|
+
|
|
177
|
+
Pass `includeCorrectionTargets: true` to the public `inspectCheckedResponse()` API only for trusted local authoring. The optional `correctionTargets` array associates each descriptor with its zero-based `diagnostic` index. An available descriptor includes `target: {id, uri, digest}` for the original element or attribute declaration and its original expanded `declaredType` when named. Use that type as the explicit `expectedType` guard when authoring a declared-type correction.
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
import {inspectCheckedResponse} from '@techspokes/typescript-wsdl-client';
|
|
181
|
+
import type {CheckedComponentOptions} from '@techspokes/typescript-wsdl-client';
|
|
182
|
+
declare const source: string;
|
|
183
|
+
declare const selector: {portType: {namespace: string; local: string}; binding: {namespace: string; local: string}; operation: string};
|
|
184
|
+
declare const responseBytes: Uint8Array;
|
|
185
|
+
declare const checkedComponents: Partial<CheckedComponentOptions>;
|
|
186
|
+
const report = await inspectCheckedResponse(source, {
|
|
187
|
+
selector,
|
|
188
|
+
responseBytes,
|
|
189
|
+
checkedComponents,
|
|
190
|
+
includeCorrectionTargets: true,
|
|
191
|
+
});
|
|
192
|
+
const available = report.correctionTargets?.filter(item => item.kind === 'available');
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
These descriptors intentionally contain original source locators and must stay out of public HTTP responses and logs. Default inspection reports and generated gateway diagnostics do not include them. Requesting `includeCorrectionDraft` is a separate authoring operation.
|
|
196
|
+
|
|
197
|
+
Original target recovery uses native declaration ownership and retained edit-span correspondence when a recipe already exists. It never treats an effective ordinal path as an original target. A descriptor with `kind: 'unavailable'` states a reason, including absent native declaration evidence, an ambiguous original context, a newly inserted declaration without an original, or correspondence that cannot be established after encoding-declaration rewriting. Do not infer a selector or silently refresh pins from that result.
|
|
198
|
+
|
|
143
199
|
### Planning and missing-schema recovery
|
|
144
200
|
|
|
145
201
|
`--checked-report` writes versioned `checked-component-plan-v1` JSON on native success or refusal. Human refusals identify the native reason, stage, dimension, affected full operation identities and configuration pointer. Source legality, correspondence, mapping/artifact readiness and runtime remain separate dimensions. Unsupported or inconclusive qualification does not establish invalid vendor schemas. Loading interruptions retain discovered counts plus explicit incomplete inventory; independent operation refusals are collected before generation writes project artifacts.
|