@happyvertical/smrt-users 0.40.51 → 0.40.53
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 +10 -0
- package/dist/app-contract.d.ts +139 -0
- package/dist/app-contract.d.ts.map +1 -0
- package/dist/app-contract.js +319 -0
- package/dist/app-contract.js.map +1 -0
- package/dist/chunks/{TerminalAuthService-E4-gpTSM.js → TerminalAuthService-C1jk06zB.js} +4 -4
- package/dist/chunks/{TerminalAuthService-E4-gpTSM.js.map → TerminalAuthService-C1jk06zB.js.map} +1 -1
- package/dist/index.js +3 -3
- package/dist/manifest.json +2 -2
- package/dist/smrt-knowledge.json +5 -4
- package/dist/sveltekit/index.d.ts +1 -0
- package/dist/sveltekit/index.d.ts.map +1 -1
- package/dist/sveltekit/resource-list-handler.d.ts +5 -0
- package/dist/sveltekit/resource-list-handler.d.ts.map +1 -1
- package/dist/sveltekit.js +12 -4
- package/dist/sveltekit.js.map +1 -1
- package/package.json +13 -9
package/README.md
CHANGED
|
@@ -10,6 +10,16 @@ pnpm add @happyvertical/smrt-users
|
|
|
10
10
|
|
|
11
11
|
## Usage
|
|
12
12
|
|
|
13
|
+
### Application discovery conformance
|
|
14
|
+
|
|
15
|
+
The SvelteKit `createResourceListHandler()` export produces the app CLI
|
|
16
|
+
discovery response and now includes a deterministic `artifact`. Consumers can
|
|
17
|
+
import the published `@happyvertical/smrt-users/app-contract` entrypoint to
|
|
18
|
+
validate the schema/version selector and SHA-256 integrity digest before using
|
|
19
|
+
the embedded resource catalog. See
|
|
20
|
+
[`@happyvertical/smrt-app-cli`](../app-cli/README.md#discovery-conformance-artifact)
|
|
21
|
+
for the exact packaged-runtime pinning workflow.
|
|
22
|
+
|
|
13
23
|
### Roles and permissions
|
|
14
24
|
|
|
15
25
|
```typescript
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { ResourceListResponseBody } from './sveltekit.js';
|
|
2
|
+
/** Immutable selector for this discovery artifact family. */
|
|
3
|
+
export declare const SMRT_DISCOVERY_CONFORMANCE_SCHEMA = "https://smrt.dev/schemas/discovery-conformance/v1";
|
|
4
|
+
/** Version of the discovery artifact payload. */
|
|
5
|
+
export declare const SMRT_DISCOVERY_CONFORMANCE_VERSION: 1;
|
|
6
|
+
/** Versioned selector for structured application results and errors. */
|
|
7
|
+
export declare const SMRT_APP_RESULT_SCHEMA = "https://smrt.dev/schemas/app-result/v1";
|
|
8
|
+
/** Version of the structured application result contract. */
|
|
9
|
+
export declare const SMRT_APP_RESULT_VERSION: 1;
|
|
10
|
+
/** MCP result `_meta` member carrying the app-result metadata. */
|
|
11
|
+
export declare const SMRT_MCP_RESULT_METADATA_KEY = "io.happyvertical/smrt";
|
|
12
|
+
/** JSON-safe value used by structured details and parameter schemas. */
|
|
13
|
+
export type JsonValue = null | boolean | number | string | JsonValue[] | {
|
|
14
|
+
[key: string]: JsonValue;
|
|
15
|
+
};
|
|
16
|
+
/** A declared input field which controls retry or optimistic-concurrency use. */
|
|
17
|
+
export interface DeclaredActionField {
|
|
18
|
+
/** JSON Schema property name supplied by the action. */
|
|
19
|
+
field: string;
|
|
20
|
+
/** Whether the action marks the field required. */
|
|
21
|
+
required: boolean;
|
|
22
|
+
}
|
|
23
|
+
/** Retry and concurrency declarations projected from a command schema. */
|
|
24
|
+
export interface CommandRequirements {
|
|
25
|
+
idempotencyKey?: DeclaredActionField;
|
|
26
|
+
expectedVersion?: DeclaredActionField;
|
|
27
|
+
}
|
|
28
|
+
/** Machine-readable metadata shared by HTTP CLI and MCP bridge results. */
|
|
29
|
+
export interface AppResultMetadata {
|
|
30
|
+
code: string;
|
|
31
|
+
message?: string;
|
|
32
|
+
details?: JsonValue;
|
|
33
|
+
retryable?: boolean;
|
|
34
|
+
correlationId?: string;
|
|
35
|
+
idempotencyKey?: DeclaredActionField;
|
|
36
|
+
expectedVersion?: DeclaredActionField;
|
|
37
|
+
}
|
|
38
|
+
export interface AppResultContract {
|
|
39
|
+
schema: typeof SMRT_APP_RESULT_SCHEMA;
|
|
40
|
+
version: typeof SMRT_APP_RESULT_VERSION;
|
|
41
|
+
mcpMetadataKey: typeof SMRT_MCP_RESULT_METADATA_KEY;
|
|
42
|
+
metadataFields: readonly [
|
|
43
|
+
'code',
|
|
44
|
+
'message',
|
|
45
|
+
'details',
|
|
46
|
+
'retryable',
|
|
47
|
+
'correlationId',
|
|
48
|
+
'idempotencyKey',
|
|
49
|
+
'expectedVersion'
|
|
50
|
+
];
|
|
51
|
+
}
|
|
52
|
+
/** Stable description of the result metadata contract contained in an artifact. */
|
|
53
|
+
export declare const SMRT_APP_RESULT_CONTRACT: AppResultContract;
|
|
54
|
+
/** The pre-artifact wire payload, kept for the existing `/_resources` fields. */
|
|
55
|
+
export type DiscoveryPayload = Omit<ResourceListResponseBody, 'artifact'>;
|
|
56
|
+
export interface DiscoveryArtifactIntegrity {
|
|
57
|
+
algorithm: 'sha256';
|
|
58
|
+
/** `sha256:<lowercase-hex>` over canonical unsigned artifact JSON. */
|
|
59
|
+
digest: string;
|
|
60
|
+
}
|
|
61
|
+
/** Versioned payload a consumer can validate and pin. */
|
|
62
|
+
export interface DiscoveryConformanceArtifact {
|
|
63
|
+
schema: typeof SMRT_DISCOVERY_CONFORMANCE_SCHEMA;
|
|
64
|
+
version: typeof SMRT_DISCOVERY_CONFORMANCE_VERSION;
|
|
65
|
+
discovery: DiscoveryPayload;
|
|
66
|
+
resultContract: AppResultContract;
|
|
67
|
+
integrity: DiscoveryArtifactIntegrity;
|
|
68
|
+
}
|
|
69
|
+
/** A JSON Schema export for consumers that validate artifacts outside Node. */
|
|
70
|
+
export declare const SMRT_DISCOVERY_CONFORMANCE_ARTIFACT_SCHEMA: {
|
|
71
|
+
readonly $id: "https://smrt.dev/schemas/discovery-conformance/v1";
|
|
72
|
+
readonly type: "object";
|
|
73
|
+
readonly required: readonly ["schema", "version", "discovery", "resultContract", "integrity"];
|
|
74
|
+
readonly properties: {
|
|
75
|
+
readonly schema: {
|
|
76
|
+
readonly const: "https://smrt.dev/schemas/discovery-conformance/v1";
|
|
77
|
+
};
|
|
78
|
+
readonly version: {
|
|
79
|
+
readonly const: 1;
|
|
80
|
+
};
|
|
81
|
+
readonly discovery: {
|
|
82
|
+
readonly type: "object";
|
|
83
|
+
readonly required: readonly ["user", "warnings", "resources"];
|
|
84
|
+
readonly properties: {
|
|
85
|
+
readonly user: {
|
|
86
|
+
readonly type: "object";
|
|
87
|
+
readonly required: readonly ["authenticated"];
|
|
88
|
+
};
|
|
89
|
+
readonly warnings: {
|
|
90
|
+
readonly type: "array";
|
|
91
|
+
readonly items: {
|
|
92
|
+
readonly type: "string";
|
|
93
|
+
};
|
|
94
|
+
};
|
|
95
|
+
readonly resources: {
|
|
96
|
+
readonly type: "array";
|
|
97
|
+
};
|
|
98
|
+
};
|
|
99
|
+
};
|
|
100
|
+
readonly resultContract: {
|
|
101
|
+
readonly type: "object";
|
|
102
|
+
readonly required: readonly ["schema", "version", "mcpMetadataKey", "metadataFields"];
|
|
103
|
+
};
|
|
104
|
+
readonly integrity: {
|
|
105
|
+
readonly type: "object";
|
|
106
|
+
readonly required: readonly ["algorithm", "digest"];
|
|
107
|
+
readonly properties: {
|
|
108
|
+
readonly algorithm: {
|
|
109
|
+
readonly const: "sha256";
|
|
110
|
+
};
|
|
111
|
+
readonly digest: {
|
|
112
|
+
readonly type: "string";
|
|
113
|
+
readonly pattern: "^sha256:[a-f0-9]{64}$";
|
|
114
|
+
};
|
|
115
|
+
};
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
};
|
|
119
|
+
/** Thrown when an artifact is malformed, non-canonical, or fails its digest. */
|
|
120
|
+
export declare class DiscoveryArtifactValidationError extends Error {
|
|
121
|
+
constructor(message: string);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Project conventionally named request fields from a JSON Schema into the
|
|
125
|
+
* discovery contract. This is declarative only: the action remains the owner
|
|
126
|
+
* of enforcement and may use a body field or adapt it to an HTTP header.
|
|
127
|
+
*/
|
|
128
|
+
export declare function deriveCommandRequirements(parameters: Record<string, unknown> | undefined): CommandRequirements | undefined;
|
|
129
|
+
/** Produce a deterministically ordered artifact with a SHA-256 integrity pin. */
|
|
130
|
+
export declare function createDiscoveryConformanceArtifact(discovery: DiscoveryPayload): DiscoveryConformanceArtifact;
|
|
131
|
+
/**
|
|
132
|
+
* Validate structure, canonical ordering, and the integrity digest. Returns
|
|
133
|
+
* the typed artifact so consumers can use one operation for validation and
|
|
134
|
+
* consumption.
|
|
135
|
+
*/
|
|
136
|
+
export declare function validateDiscoveryConformanceArtifact(value: unknown): DiscoveryConformanceArtifact;
|
|
137
|
+
/** Canonical JSON bytes whose digest is carried by the artifact. */
|
|
138
|
+
export declare function canonicalizeDiscoveryArtifact(artifact: Omit<DiscoveryConformanceArtifact, 'integrity'>): string;
|
|
139
|
+
//# sourceMappingURL=app-contract.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app-contract.d.ts","sourceRoot":"","sources":["../src/app-contract.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import { createHash, timingSafeEqual } from "node:crypto";
|
|
2
|
+
//#region src/app-contract.ts
|
|
3
|
+
var SMRT_DISCOVERY_CONFORMANCE_SCHEMA = "https://smrt.dev/schemas/discovery-conformance/v1";
|
|
4
|
+
var SMRT_DISCOVERY_CONFORMANCE_VERSION = 1;
|
|
5
|
+
var SMRT_APP_RESULT_SCHEMA = "https://smrt.dev/schemas/app-result/v1";
|
|
6
|
+
var SMRT_APP_RESULT_VERSION = 1;
|
|
7
|
+
var SMRT_MCP_RESULT_METADATA_KEY = "io.happyvertical/smrt";
|
|
8
|
+
var SMRT_APP_RESULT_CONTRACT = {
|
|
9
|
+
schema: SMRT_APP_RESULT_SCHEMA,
|
|
10
|
+
version: 1,
|
|
11
|
+
mcpMetadataKey: SMRT_MCP_RESULT_METADATA_KEY,
|
|
12
|
+
metadataFields: [
|
|
13
|
+
"code",
|
|
14
|
+
"message",
|
|
15
|
+
"details",
|
|
16
|
+
"retryable",
|
|
17
|
+
"correlationId",
|
|
18
|
+
"idempotencyKey",
|
|
19
|
+
"expectedVersion"
|
|
20
|
+
]
|
|
21
|
+
};
|
|
22
|
+
var SMRT_DISCOVERY_CONFORMANCE_ARTIFACT_SCHEMA = {
|
|
23
|
+
$id: SMRT_DISCOVERY_CONFORMANCE_SCHEMA,
|
|
24
|
+
type: "object",
|
|
25
|
+
required: [
|
|
26
|
+
"schema",
|
|
27
|
+
"version",
|
|
28
|
+
"discovery",
|
|
29
|
+
"resultContract",
|
|
30
|
+
"integrity"
|
|
31
|
+
],
|
|
32
|
+
properties: {
|
|
33
|
+
schema: { const: SMRT_DISCOVERY_CONFORMANCE_SCHEMA },
|
|
34
|
+
version: { const: 1 },
|
|
35
|
+
discovery: {
|
|
36
|
+
type: "object",
|
|
37
|
+
required: [
|
|
38
|
+
"user",
|
|
39
|
+
"warnings",
|
|
40
|
+
"resources"
|
|
41
|
+
],
|
|
42
|
+
properties: {
|
|
43
|
+
user: {
|
|
44
|
+
type: "object",
|
|
45
|
+
required: ["authenticated"]
|
|
46
|
+
},
|
|
47
|
+
warnings: {
|
|
48
|
+
type: "array",
|
|
49
|
+
items: { type: "string" }
|
|
50
|
+
},
|
|
51
|
+
resources: { type: "array" }
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
resultContract: {
|
|
55
|
+
type: "object",
|
|
56
|
+
required: [
|
|
57
|
+
"schema",
|
|
58
|
+
"version",
|
|
59
|
+
"mcpMetadataKey",
|
|
60
|
+
"metadataFields"
|
|
61
|
+
]
|
|
62
|
+
},
|
|
63
|
+
integrity: {
|
|
64
|
+
type: "object",
|
|
65
|
+
required: ["algorithm", "digest"],
|
|
66
|
+
properties: {
|
|
67
|
+
algorithm: { const: "sha256" },
|
|
68
|
+
digest: {
|
|
69
|
+
type: "string",
|
|
70
|
+
pattern: "^sha256:[a-f0-9]{64}$"
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
var DiscoveryArtifactValidationError = class extends Error {
|
|
77
|
+
constructor(message) {
|
|
78
|
+
super(message);
|
|
79
|
+
this.name = "DiscoveryArtifactValidationError";
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
function deriveCommandRequirements(parameters) {
|
|
83
|
+
const properties = isRecord(parameters?.properties) ? parameters.properties : void 0;
|
|
84
|
+
if (!properties) return void 0;
|
|
85
|
+
const required = new Set(Array.isArray(parameters?.required) ? parameters.required.filter((value) => typeof value === "string") : []);
|
|
86
|
+
const findField = (names) => {
|
|
87
|
+
const field = names.find((name) => Object.hasOwn(properties, name));
|
|
88
|
+
return field ? {
|
|
89
|
+
field,
|
|
90
|
+
required: required.has(field)
|
|
91
|
+
} : void 0;
|
|
92
|
+
};
|
|
93
|
+
const idempotencyKey = findField(["idempotencyKey", "idempotency_key"]);
|
|
94
|
+
const expectedVersion = findField(["expectedVersion", "expected_version"]);
|
|
95
|
+
if (!idempotencyKey && !expectedVersion) return void 0;
|
|
96
|
+
return {
|
|
97
|
+
...idempotencyKey ? { idempotencyKey } : {},
|
|
98
|
+
...expectedVersion ? { expectedVersion } : {}
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function createDiscoveryConformanceArtifact(discovery) {
|
|
102
|
+
const unsigned = {
|
|
103
|
+
schema: SMRT_DISCOVERY_CONFORMANCE_SCHEMA,
|
|
104
|
+
version: 1,
|
|
105
|
+
discovery: normalizeDiscovery(discovery),
|
|
106
|
+
resultContract: SMRT_APP_RESULT_CONTRACT
|
|
107
|
+
};
|
|
108
|
+
return {
|
|
109
|
+
...unsigned,
|
|
110
|
+
integrity: {
|
|
111
|
+
algorithm: "sha256",
|
|
112
|
+
digest: digestUnsignedArtifact(unsigned)
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function validateDiscoveryConformanceArtifact(value) {
|
|
117
|
+
if (!isRecord(value)) fail("artifact must be an object");
|
|
118
|
+
if (value.schema !== "https://smrt.dev/schemas/discovery-conformance/v1") fail(`unsupported artifact schema: ${String(value.schema)}`);
|
|
119
|
+
if (value.version !== 1) fail(`unsupported artifact version: ${String(value.version)}`);
|
|
120
|
+
if (!isRecord(value.integrity) || value.integrity.algorithm !== "sha256") fail("artifact integrity.algorithm must be sha256");
|
|
121
|
+
if (typeof value.integrity.digest !== "string" || !/^sha256:[a-f0-9]{64}$/u.test(value.integrity.digest)) fail("artifact integrity.digest must be lowercase sha256 hex");
|
|
122
|
+
validateResultContract(value.resultContract);
|
|
123
|
+
const artifact = {
|
|
124
|
+
schema: SMRT_DISCOVERY_CONFORMANCE_SCHEMA,
|
|
125
|
+
version: 1,
|
|
126
|
+
discovery: validateDiscovery(value.discovery),
|
|
127
|
+
resultContract: SMRT_APP_RESULT_CONTRACT,
|
|
128
|
+
integrity: {
|
|
129
|
+
algorithm: "sha256",
|
|
130
|
+
digest: value.integrity.digest
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
if (!safeDigestEqual(digestUnsignedArtifact({
|
|
134
|
+
schema: artifact.schema,
|
|
135
|
+
version: artifact.version,
|
|
136
|
+
discovery: artifact.discovery,
|
|
137
|
+
resultContract: artifact.resultContract
|
|
138
|
+
}), artifact.integrity.digest)) fail("artifact integrity digest does not match canonical payload");
|
|
139
|
+
return artifact;
|
|
140
|
+
}
|
|
141
|
+
function canonicalizeDiscoveryArtifact(artifact) {
|
|
142
|
+
return canonicalJson(artifact);
|
|
143
|
+
}
|
|
144
|
+
function normalizeDiscovery(discovery) {
|
|
145
|
+
const resources = [...discovery.resources].map(normalizeResource).sort((left, right) => compareText(left.slug, right.slug));
|
|
146
|
+
return {
|
|
147
|
+
user: discovery.user.id ? {
|
|
148
|
+
authenticated: discovery.user.authenticated,
|
|
149
|
+
id: discovery.user.id
|
|
150
|
+
} : { authenticated: discovery.user.authenticated },
|
|
151
|
+
warnings: [...discovery.warnings].sort(compareText),
|
|
152
|
+
resources
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
function normalizeResource(resource) {
|
|
156
|
+
const commands = [...resource.commands].map(normalizeCommand).sort((left, right) => compareText(left.commandName, right.commandName));
|
|
157
|
+
return {
|
|
158
|
+
slug: resource.slug,
|
|
159
|
+
className: resource.className,
|
|
160
|
+
...resource.qualifiedName ? { qualifiedName: resource.qualifiedName } : {},
|
|
161
|
+
...resource.packageName ? { packageName: resource.packageName } : {},
|
|
162
|
+
label: resource.label,
|
|
163
|
+
apiPath: resource.apiPath,
|
|
164
|
+
commands
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
function normalizeCommand(command) {
|
|
168
|
+
const requirements = command.requirements ? normalizeRequirements(command.requirements) : void 0;
|
|
169
|
+
return {
|
|
170
|
+
methodName: command.methodName,
|
|
171
|
+
commandName: command.commandName,
|
|
172
|
+
kind: command.kind,
|
|
173
|
+
scope: command.scope,
|
|
174
|
+
httpMethod: command.httpMethod,
|
|
175
|
+
pathSegments: [...command.pathSegments],
|
|
176
|
+
...command.description ? { description: command.description } : {},
|
|
177
|
+
...command.parameters ? { parameters: canonicalJsonValue(command.parameters) } : {},
|
|
178
|
+
...requirements ? { requirements } : {}
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
function validateDiscovery(value) {
|
|
182
|
+
if (!isRecord(value)) fail("artifact discovery must be an object");
|
|
183
|
+
if (!isRecord(value.user) || typeof value.user.authenticated !== "boolean") fail("artifact discovery.user.authenticated must be boolean");
|
|
184
|
+
if (value.user.id !== void 0 && typeof value.user.id !== "string") fail("artifact discovery.user.id must be a string");
|
|
185
|
+
if (!Array.isArray(value.warnings) || !value.warnings.every(isString)) fail("artifact discovery.warnings must be a string array");
|
|
186
|
+
assertSorted(value.warnings, "artifact discovery.warnings");
|
|
187
|
+
if (!Array.isArray(value.resources)) fail("artifact discovery.resources must be an array");
|
|
188
|
+
const resources = value.resources.map(validateResource);
|
|
189
|
+
assertSorted(resources.map((resource) => resource.slug), "artifact discovery.resources");
|
|
190
|
+
return {
|
|
191
|
+
user: value.user.id ? {
|
|
192
|
+
authenticated: value.user.authenticated,
|
|
193
|
+
id: value.user.id
|
|
194
|
+
} : { authenticated: value.user.authenticated },
|
|
195
|
+
warnings: [...value.warnings],
|
|
196
|
+
resources
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
function validateResource(value) {
|
|
200
|
+
if (!isRecord(value)) fail("artifact resource must be an object");
|
|
201
|
+
const slug = requireArtifactString(value.slug, "resource.slug");
|
|
202
|
+
const className = requireArtifactString(value.className, "resource.className");
|
|
203
|
+
const label = requireArtifactString(value.label, "resource.label");
|
|
204
|
+
const apiPath = requireArtifactString(value.apiPath, "resource.apiPath");
|
|
205
|
+
if (!Array.isArray(value.commands)) fail("artifact resource.commands must be an array");
|
|
206
|
+
const commands = value.commands.map(validateCommand);
|
|
207
|
+
assertSorted(commands.map((command) => command.commandName), "artifact resource.commands");
|
|
208
|
+
return {
|
|
209
|
+
slug,
|
|
210
|
+
className,
|
|
211
|
+
...typeof value.qualifiedName === "string" ? { qualifiedName: value.qualifiedName } : {},
|
|
212
|
+
...typeof value.packageName === "string" ? { packageName: value.packageName } : {},
|
|
213
|
+
label,
|
|
214
|
+
apiPath,
|
|
215
|
+
commands
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
function validateCommand(value) {
|
|
219
|
+
if (!isRecord(value)) fail("artifact command must be an object");
|
|
220
|
+
const methodName = requireArtifactString(value.methodName, "command.methodName");
|
|
221
|
+
const commandName = requireArtifactString(value.commandName, "command.commandName");
|
|
222
|
+
const httpMethod = requireArtifactString(value.httpMethod, "command.httpMethod");
|
|
223
|
+
if (value.kind !== "crud" && value.kind !== "custom") fail("artifact command.kind must be crud or custom");
|
|
224
|
+
if (value.scope !== "item" && value.scope !== "collection") fail("artifact command.scope must be item or collection");
|
|
225
|
+
if (!Array.isArray(value.pathSegments) || !value.pathSegments.every(isString)) fail("artifact command.pathSegments must be a string array");
|
|
226
|
+
if (value.parameters !== void 0 && !isRecord(value.parameters)) fail("artifact command.parameters must be an object");
|
|
227
|
+
const requirements = validateRequirements(value.requirements);
|
|
228
|
+
return {
|
|
229
|
+
methodName,
|
|
230
|
+
commandName,
|
|
231
|
+
kind: value.kind,
|
|
232
|
+
scope: value.scope,
|
|
233
|
+
httpMethod,
|
|
234
|
+
pathSegments: [...value.pathSegments],
|
|
235
|
+
...typeof value.description === "string" ? { description: value.description } : {},
|
|
236
|
+
...value.parameters ? { parameters: canonicalJsonValue(value.parameters) } : {},
|
|
237
|
+
...requirements ? { requirements } : {}
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
function validateRequirements(value) {
|
|
241
|
+
if (value === void 0) return void 0;
|
|
242
|
+
if (!isRecord(value)) fail("artifact command.requirements must be an object");
|
|
243
|
+
const parseField = (candidate, name) => {
|
|
244
|
+
if (candidate === void 0) return void 0;
|
|
245
|
+
if (!isRecord(candidate) || typeof candidate.field !== "string" || candidate.field === "" || typeof candidate.required !== "boolean") fail(`artifact command.requirements.${name} is invalid`);
|
|
246
|
+
return {
|
|
247
|
+
field: candidate.field,
|
|
248
|
+
required: candidate.required
|
|
249
|
+
};
|
|
250
|
+
};
|
|
251
|
+
return normalizeRequirements({
|
|
252
|
+
idempotencyKey: parseField(value.idempotencyKey, "idempotencyKey"),
|
|
253
|
+
expectedVersion: parseField(value.expectedVersion, "expectedVersion")
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
function normalizeRequirements(requirements) {
|
|
257
|
+
const { idempotencyKey, expectedVersion } = requirements;
|
|
258
|
+
if (!idempotencyKey && !expectedVersion) return void 0;
|
|
259
|
+
return {
|
|
260
|
+
...idempotencyKey ? { idempotencyKey } : {},
|
|
261
|
+
...expectedVersion ? { expectedVersion } : {}
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
function validateResultContract(value) {
|
|
265
|
+
if (!isRecord(value)) fail("artifact resultContract must be an object");
|
|
266
|
+
if (value.schema !== "https://smrt.dev/schemas/app-result/v1" || value.version !== 1 || value.mcpMetadataKey !== "io.happyvertical/smrt" || !Array.isArray(value.metadataFields) || canonicalJson(value.metadataFields) !== canonicalJson(SMRT_APP_RESULT_CONTRACT.metadataFields)) fail("artifact resultContract does not match SMRT app-result v1");
|
|
267
|
+
}
|
|
268
|
+
function digestUnsignedArtifact(artifact) {
|
|
269
|
+
return `sha256:${createHash("sha256").update(canonicalizeDiscoveryArtifact(artifact), "utf8").digest("hex")}`;
|
|
270
|
+
}
|
|
271
|
+
function canonicalJson(value) {
|
|
272
|
+
return JSON.stringify(canonicalJsonValue(value));
|
|
273
|
+
}
|
|
274
|
+
function canonicalJsonValue(value) {
|
|
275
|
+
if (value === null || typeof value === "boolean" || typeof value === "string") return value;
|
|
276
|
+
if (typeof value === "number") {
|
|
277
|
+
if (!Number.isFinite(value)) fail("artifact cannot contain non-finite numbers");
|
|
278
|
+
return value;
|
|
279
|
+
}
|
|
280
|
+
if (Array.isArray(value)) return value.map(canonicalJsonValue);
|
|
281
|
+
if (isRecord(value)) {
|
|
282
|
+
const normalized = {};
|
|
283
|
+
for (const key of Object.keys(value).sort(compareText)) normalized[key] = canonicalJsonValue(value[key]);
|
|
284
|
+
return normalized;
|
|
285
|
+
}
|
|
286
|
+
fail(`artifact contains unsupported value type: ${typeof value}`);
|
|
287
|
+
}
|
|
288
|
+
function safeDigestEqual(left, right) {
|
|
289
|
+
const leftBytes = Buffer.from(left, "utf8");
|
|
290
|
+
const rightBytes = Buffer.from(right, "utf8");
|
|
291
|
+
return leftBytes.length === rightBytes.length && timingSafeEqual(leftBytes, rightBytes);
|
|
292
|
+
}
|
|
293
|
+
function assertSorted(values, name) {
|
|
294
|
+
for (let index = 1; index < values.length; index += 1) {
|
|
295
|
+
const previous = values[index - 1];
|
|
296
|
+
const current = values[index];
|
|
297
|
+
if (previous !== void 0 && current !== void 0 && compareText(previous, current) > 0) fail(`${name} must be deterministically sorted`);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
function compareText(left, right) {
|
|
301
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
302
|
+
}
|
|
303
|
+
function isRecord(value) {
|
|
304
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
305
|
+
}
|
|
306
|
+
function isString(value) {
|
|
307
|
+
return typeof value === "string";
|
|
308
|
+
}
|
|
309
|
+
function requireArtifactString(value, name) {
|
|
310
|
+
if (typeof value !== "string" || value === "") fail(`artifact ${name} must be a non-empty string`);
|
|
311
|
+
return value;
|
|
312
|
+
}
|
|
313
|
+
function fail(message) {
|
|
314
|
+
throw new DiscoveryArtifactValidationError(message);
|
|
315
|
+
}
|
|
316
|
+
//#endregion
|
|
317
|
+
export { DiscoveryArtifactValidationError, SMRT_APP_RESULT_CONTRACT, SMRT_APP_RESULT_SCHEMA, SMRT_APP_RESULT_VERSION, SMRT_DISCOVERY_CONFORMANCE_ARTIFACT_SCHEMA, SMRT_DISCOVERY_CONFORMANCE_SCHEMA, SMRT_DISCOVERY_CONFORMANCE_VERSION, SMRT_MCP_RESULT_METADATA_KEY, canonicalizeDiscoveryArtifact, createDiscoveryConformanceArtifact, deriveCommandRequirements, validateDiscoveryConformanceArtifact };
|
|
318
|
+
|
|
319
|
+
//# sourceMappingURL=app-contract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app-contract.js","names":[],"sources":["../src/app-contract.ts"],"sourcesContent":["/**\n * Versioned discovery and cross-transport contracts for SMRT application\n * surfaces.\n *\n * The artifact is intentionally data-only: downstream apps may persist its\n * canonical JSON and pin the SHA-256 digest without parsing CLI help text or\n * depending on a particular transport implementation.\n *\n * @packageDocumentation\n */\n\nimport { createHash, timingSafeEqual } from 'node:crypto';\nimport type {\n CliResource,\n CommandDefinition,\n ResourceListResponseBody,\n} from './sveltekit.js';\n\n/** Immutable selector for this discovery artifact family. */\nexport const SMRT_DISCOVERY_CONFORMANCE_SCHEMA =\n 'https://smrt.dev/schemas/discovery-conformance/v1';\n/** Version of the discovery artifact payload. */\nexport const SMRT_DISCOVERY_CONFORMANCE_VERSION = 1 as const;\n/** Versioned selector for structured application results and errors. */\nexport const SMRT_APP_RESULT_SCHEMA = 'https://smrt.dev/schemas/app-result/v1';\n/** Version of the structured application result contract. */\nexport const SMRT_APP_RESULT_VERSION = 1 as const;\n/** MCP result `_meta` member carrying the app-result metadata. */\nexport const SMRT_MCP_RESULT_METADATA_KEY = 'io.happyvertical/smrt';\n\n/** JSON-safe value used by structured details and parameter schemas. */\nexport type JsonValue =\n | null\n | boolean\n | number\n | string\n | JsonValue[]\n | { [key: string]: JsonValue };\n\n/** A declared input field which controls retry or optimistic-concurrency use. */\nexport interface DeclaredActionField {\n /** JSON Schema property name supplied by the action. */\n field: string;\n /** Whether the action marks the field required. */\n required: boolean;\n}\n\n/** Retry and concurrency declarations projected from a command schema. */\nexport interface CommandRequirements {\n idempotencyKey?: DeclaredActionField;\n expectedVersion?: DeclaredActionField;\n}\n\n/** Machine-readable metadata shared by HTTP CLI and MCP bridge results. */\nexport interface AppResultMetadata {\n code: string;\n message?: string;\n details?: JsonValue;\n retryable?: boolean;\n correlationId?: string;\n idempotencyKey?: DeclaredActionField;\n expectedVersion?: DeclaredActionField;\n}\n\nexport interface AppResultContract {\n schema: typeof SMRT_APP_RESULT_SCHEMA;\n version: typeof SMRT_APP_RESULT_VERSION;\n mcpMetadataKey: typeof SMRT_MCP_RESULT_METADATA_KEY;\n metadataFields: readonly [\n 'code',\n 'message',\n 'details',\n 'retryable',\n 'correlationId',\n 'idempotencyKey',\n 'expectedVersion',\n ];\n}\n\n/** Stable description of the result metadata contract contained in an artifact. */\nexport const SMRT_APP_RESULT_CONTRACT: AppResultContract = {\n schema: SMRT_APP_RESULT_SCHEMA,\n version: SMRT_APP_RESULT_VERSION,\n mcpMetadataKey: SMRT_MCP_RESULT_METADATA_KEY,\n metadataFields: [\n 'code',\n 'message',\n 'details',\n 'retryable',\n 'correlationId',\n 'idempotencyKey',\n 'expectedVersion',\n ],\n};\n\n/** The pre-artifact wire payload, kept for the existing `/_resources` fields. */\nexport type DiscoveryPayload = Omit<ResourceListResponseBody, 'artifact'>;\n\nexport interface DiscoveryArtifactIntegrity {\n algorithm: 'sha256';\n /** `sha256:<lowercase-hex>` over canonical unsigned artifact JSON. */\n digest: string;\n}\n\n/** Versioned payload a consumer can validate and pin. */\nexport interface DiscoveryConformanceArtifact {\n schema: typeof SMRT_DISCOVERY_CONFORMANCE_SCHEMA;\n version: typeof SMRT_DISCOVERY_CONFORMANCE_VERSION;\n discovery: DiscoveryPayload;\n resultContract: AppResultContract;\n integrity: DiscoveryArtifactIntegrity;\n}\n\n/** A JSON Schema export for consumers that validate artifacts outside Node. */\nexport const SMRT_DISCOVERY_CONFORMANCE_ARTIFACT_SCHEMA = {\n $id: SMRT_DISCOVERY_CONFORMANCE_SCHEMA,\n type: 'object',\n required: ['schema', 'version', 'discovery', 'resultContract', 'integrity'],\n properties: {\n schema: { const: SMRT_DISCOVERY_CONFORMANCE_SCHEMA },\n version: { const: SMRT_DISCOVERY_CONFORMANCE_VERSION },\n discovery: {\n type: 'object',\n required: ['user', 'warnings', 'resources'],\n properties: {\n user: { type: 'object', required: ['authenticated'] },\n warnings: { type: 'array', items: { type: 'string' } },\n resources: { type: 'array' },\n },\n },\n resultContract: {\n type: 'object',\n required: ['schema', 'version', 'mcpMetadataKey', 'metadataFields'],\n },\n integrity: {\n type: 'object',\n required: ['algorithm', 'digest'],\n properties: {\n algorithm: { const: 'sha256' },\n digest: { type: 'string', pattern: '^sha256:[a-f0-9]{64}$' },\n },\n },\n },\n} as const;\n\n/** Thrown when an artifact is malformed, non-canonical, or fails its digest. */\nexport class DiscoveryArtifactValidationError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'DiscoveryArtifactValidationError';\n }\n}\n\n/**\n * Project conventionally named request fields from a JSON Schema into the\n * discovery contract. This is declarative only: the action remains the owner\n * of enforcement and may use a body field or adapt it to an HTTP header.\n */\nexport function deriveCommandRequirements(\n parameters: Record<string, unknown> | undefined,\n): CommandRequirements | undefined {\n const properties = isRecord(parameters?.properties)\n ? parameters.properties\n : undefined;\n if (!properties) return undefined;\n\n const required = new Set(\n Array.isArray(parameters?.required)\n ? parameters.required.filter(\n (value): value is string => typeof value === 'string',\n )\n : [],\n );\n const findField = (\n names: readonly string[],\n ): DeclaredActionField | undefined => {\n const field = names.find((name) => Object.hasOwn(properties, name));\n return field ? { field, required: required.has(field) } : undefined;\n };\n\n const idempotencyKey = findField(['idempotencyKey', 'idempotency_key']);\n const expectedVersion = findField(['expectedVersion', 'expected_version']);\n if (!idempotencyKey && !expectedVersion) return undefined;\n return {\n ...(idempotencyKey ? { idempotencyKey } : {}),\n ...(expectedVersion ? { expectedVersion } : {}),\n };\n}\n\n/** Produce a deterministically ordered artifact with a SHA-256 integrity pin. */\nexport function createDiscoveryConformanceArtifact(\n discovery: DiscoveryPayload,\n): DiscoveryConformanceArtifact {\n const normalizedDiscovery = normalizeDiscovery(discovery);\n const unsigned = {\n schema: SMRT_DISCOVERY_CONFORMANCE_SCHEMA,\n version: SMRT_DISCOVERY_CONFORMANCE_VERSION,\n discovery: normalizedDiscovery,\n resultContract: SMRT_APP_RESULT_CONTRACT,\n } as const;\n\n return {\n ...unsigned,\n integrity: {\n algorithm: 'sha256',\n digest: digestUnsignedArtifact(unsigned),\n },\n };\n}\n\n/**\n * Validate structure, canonical ordering, and the integrity digest. Returns\n * the typed artifact so consumers can use one operation for validation and\n * consumption.\n */\nexport function validateDiscoveryConformanceArtifact(\n value: unknown,\n): DiscoveryConformanceArtifact {\n if (!isRecord(value)) fail('artifact must be an object');\n if (value.schema !== SMRT_DISCOVERY_CONFORMANCE_SCHEMA) {\n fail(`unsupported artifact schema: ${String(value.schema)}`);\n }\n if (value.version !== SMRT_DISCOVERY_CONFORMANCE_VERSION) {\n fail(`unsupported artifact version: ${String(value.version)}`);\n }\n if (!isRecord(value.integrity) || value.integrity.algorithm !== 'sha256') {\n fail('artifact integrity.algorithm must be sha256');\n }\n if (\n typeof value.integrity.digest !== 'string' ||\n !/^sha256:[a-f0-9]{64}$/u.test(value.integrity.digest)\n ) {\n fail('artifact integrity.digest must be lowercase sha256 hex');\n }\n validateResultContract(value.resultContract);\n const discovery = validateDiscovery(value.discovery);\n const artifact: DiscoveryConformanceArtifact = {\n schema: SMRT_DISCOVERY_CONFORMANCE_SCHEMA,\n version: SMRT_DISCOVERY_CONFORMANCE_VERSION,\n discovery,\n resultContract: SMRT_APP_RESULT_CONTRACT,\n integrity: {\n algorithm: 'sha256',\n digest: value.integrity.digest,\n },\n };\n const expected = digestUnsignedArtifact({\n schema: artifact.schema,\n version: artifact.version,\n discovery: artifact.discovery,\n resultContract: artifact.resultContract,\n });\n if (!safeDigestEqual(expected, artifact.integrity.digest)) {\n fail('artifact integrity digest does not match canonical payload');\n }\n return artifact;\n}\n\n/** Canonical JSON bytes whose digest is carried by the artifact. */\nexport function canonicalizeDiscoveryArtifact(\n artifact: Omit<DiscoveryConformanceArtifact, 'integrity'>,\n): string {\n return canonicalJson(artifact);\n}\n\nfunction normalizeDiscovery(discovery: DiscoveryPayload): DiscoveryPayload {\n const resources = [...discovery.resources]\n .map(normalizeResource)\n .sort((left, right) => compareText(left.slug, right.slug));\n return {\n user: discovery.user.id\n ? { authenticated: discovery.user.authenticated, id: discovery.user.id }\n : { authenticated: discovery.user.authenticated },\n warnings: [...discovery.warnings].sort(compareText),\n resources,\n };\n}\n\nfunction normalizeResource(resource: CliResource): CliResource {\n const commands = [...resource.commands]\n .map(normalizeCommand)\n .sort((left, right) => compareText(left.commandName, right.commandName));\n return {\n slug: resource.slug,\n className: resource.className,\n ...(resource.qualifiedName\n ? { qualifiedName: resource.qualifiedName }\n : {}),\n ...(resource.packageName ? { packageName: resource.packageName } : {}),\n label: resource.label,\n apiPath: resource.apiPath,\n commands,\n };\n}\n\nfunction normalizeCommand(command: CommandDefinition): CommandDefinition {\n const requirements = command.requirements\n ? normalizeRequirements(command.requirements)\n : undefined;\n return {\n methodName: command.methodName,\n commandName: command.commandName,\n kind: command.kind,\n scope: command.scope,\n httpMethod: command.httpMethod,\n pathSegments: [...command.pathSegments],\n ...(command.description ? { description: command.description } : {}),\n ...(command.parameters\n ? {\n parameters: canonicalJsonValue(command.parameters) as Record<\n string,\n unknown\n >,\n }\n : {}),\n ...(requirements ? { requirements } : {}),\n };\n}\n\nfunction validateDiscovery(value: unknown): DiscoveryPayload {\n if (!isRecord(value)) fail('artifact discovery must be an object');\n if (!isRecord(value.user) || typeof value.user.authenticated !== 'boolean') {\n fail('artifact discovery.user.authenticated must be boolean');\n }\n if (value.user.id !== undefined && typeof value.user.id !== 'string') {\n fail('artifact discovery.user.id must be a string');\n }\n if (!Array.isArray(value.warnings) || !value.warnings.every(isString)) {\n fail('artifact discovery.warnings must be a string array');\n }\n assertSorted(value.warnings, 'artifact discovery.warnings');\n if (!Array.isArray(value.resources)) {\n fail('artifact discovery.resources must be an array');\n }\n const resources = value.resources.map(validateResource);\n assertSorted(\n resources.map((resource) => resource.slug),\n 'artifact discovery.resources',\n );\n return {\n user: value.user.id\n ? { authenticated: value.user.authenticated, id: value.user.id }\n : { authenticated: value.user.authenticated },\n warnings: [...value.warnings],\n resources,\n };\n}\n\nfunction validateResource(value: unknown): CliResource {\n if (!isRecord(value)) fail('artifact resource must be an object');\n const slug = requireArtifactString(value.slug, 'resource.slug');\n const className = requireArtifactString(\n value.className,\n 'resource.className',\n );\n const label = requireArtifactString(value.label, 'resource.label');\n const apiPath = requireArtifactString(value.apiPath, 'resource.apiPath');\n if (!Array.isArray(value.commands))\n fail('artifact resource.commands must be an array');\n const commands = value.commands.map(validateCommand);\n assertSorted(\n commands.map((command) => command.commandName),\n 'artifact resource.commands',\n );\n return {\n slug,\n className,\n ...(typeof value.qualifiedName === 'string'\n ? { qualifiedName: value.qualifiedName }\n : {}),\n ...(typeof value.packageName === 'string'\n ? { packageName: value.packageName }\n : {}),\n label,\n apiPath,\n commands,\n };\n}\n\nfunction validateCommand(value: unknown): CommandDefinition {\n if (!isRecord(value)) fail('artifact command must be an object');\n const methodName = requireArtifactString(\n value.methodName,\n 'command.methodName',\n );\n const commandName = requireArtifactString(\n value.commandName,\n 'command.commandName',\n );\n const httpMethod = requireArtifactString(\n value.httpMethod,\n 'command.httpMethod',\n );\n if (value.kind !== 'crud' && value.kind !== 'custom') {\n fail('artifact command.kind must be crud or custom');\n }\n if (value.scope !== 'item' && value.scope !== 'collection') {\n fail('artifact command.scope must be item or collection');\n }\n if (\n !Array.isArray(value.pathSegments) ||\n !value.pathSegments.every(isString)\n ) {\n fail('artifact command.pathSegments must be a string array');\n }\n if (value.parameters !== undefined && !isRecord(value.parameters)) {\n fail('artifact command.parameters must be an object');\n }\n const requirements = validateRequirements(value.requirements);\n return {\n methodName,\n commandName,\n kind: value.kind,\n scope: value.scope,\n httpMethod: httpMethod as CommandDefinition['httpMethod'],\n pathSegments: [...value.pathSegments],\n ...(typeof value.description === 'string'\n ? { description: value.description }\n : {}),\n ...(value.parameters\n ? {\n parameters: canonicalJsonValue(value.parameters) as Record<\n string,\n unknown\n >,\n }\n : {}),\n ...(requirements ? { requirements } : {}),\n };\n}\n\nfunction validateRequirements(value: unknown): CommandRequirements | undefined {\n if (value === undefined) return undefined;\n if (!isRecord(value)) fail('artifact command.requirements must be an object');\n const parseField = (\n candidate: unknown,\n name: string,\n ): DeclaredActionField | undefined => {\n if (candidate === undefined) return undefined;\n if (\n !isRecord(candidate) ||\n typeof candidate.field !== 'string' ||\n candidate.field === '' ||\n typeof candidate.required !== 'boolean'\n ) {\n fail(`artifact command.requirements.${name} is invalid`);\n }\n return { field: candidate.field, required: candidate.required };\n };\n return normalizeRequirements({\n idempotencyKey: parseField(value.idempotencyKey, 'idempotencyKey'),\n expectedVersion: parseField(value.expectedVersion, 'expectedVersion'),\n });\n}\n\nfunction normalizeRequirements(\n requirements: CommandRequirements,\n): CommandRequirements | undefined {\n const { idempotencyKey, expectedVersion } = requirements;\n if (!idempotencyKey && !expectedVersion) return undefined;\n return {\n ...(idempotencyKey ? { idempotencyKey } : {}),\n ...(expectedVersion ? { expectedVersion } : {}),\n };\n}\n\nfunction validateResultContract(value: unknown): void {\n if (!isRecord(value)) fail('artifact resultContract must be an object');\n if (\n value.schema !== SMRT_APP_RESULT_SCHEMA ||\n value.version !== SMRT_APP_RESULT_VERSION ||\n value.mcpMetadataKey !== SMRT_MCP_RESULT_METADATA_KEY ||\n !Array.isArray(value.metadataFields) ||\n canonicalJson(value.metadataFields) !==\n canonicalJson(SMRT_APP_RESULT_CONTRACT.metadataFields)\n ) {\n fail('artifact resultContract does not match SMRT app-result v1');\n }\n}\n\nfunction digestUnsignedArtifact(\n artifact: Omit<DiscoveryConformanceArtifact, 'integrity'>,\n): string {\n return `sha256:${createHash('sha256')\n .update(canonicalizeDiscoveryArtifact(artifact), 'utf8')\n .digest('hex')}`;\n}\n\nfunction canonicalJson(value: unknown): string {\n return JSON.stringify(canonicalJsonValue(value));\n}\n\nfunction canonicalJsonValue(value: unknown): JsonValue {\n if (\n value === null ||\n typeof value === 'boolean' ||\n typeof value === 'string'\n ) {\n return value;\n }\n if (typeof value === 'number') {\n if (!Number.isFinite(value))\n fail('artifact cannot contain non-finite numbers');\n return value;\n }\n if (Array.isArray(value)) return value.map(canonicalJsonValue);\n if (isRecord(value)) {\n const normalized: Record<string, JsonValue> = {};\n for (const key of Object.keys(value).sort(compareText)) {\n normalized[key] = canonicalJsonValue(value[key]);\n }\n return normalized;\n }\n fail(`artifact contains unsupported value type: ${typeof value}`);\n}\n\nfunction safeDigestEqual(left: string, right: string): boolean {\n const leftBytes = Buffer.from(left, 'utf8');\n const rightBytes = Buffer.from(right, 'utf8');\n return (\n leftBytes.length === rightBytes.length &&\n timingSafeEqual(leftBytes, rightBytes)\n );\n}\n\nfunction assertSorted(values: readonly string[], name: string): void {\n for (let index = 1; index < values.length; index += 1) {\n const previous = values[index - 1];\n const current = values[index];\n if (\n previous !== undefined &&\n current !== undefined &&\n compareText(previous, current) > 0\n ) {\n fail(`${name} must be deterministically sorted`);\n }\n }\n}\n\nfunction compareText(left: string, right: string): number {\n return left < right ? -1 : left > right ? 1 : 0;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction isString(value: unknown): value is string {\n return typeof value === 'string';\n}\n\nfunction requireArtifactString(value: unknown, name: string): string {\n if (typeof value !== 'string' || value === '') {\n fail(`artifact ${name} must be a non-empty string`);\n }\n return value;\n}\n\nfunction fail(message: string): never {\n throw new DiscoveryArtifactValidationError(message);\n}\n"],"mappings":";;AAmBO,IAAM,oCACX;AAEK,IAAM,qCAAqC;AAE3C,IAAM,yBAAyB;AAE/B,IAAM,0BAA0B;AAEhC,IAAM,+BAA+B;AAoDrC,IAAM,2BAA8C;CACzD,QAAQ;CACR,SAAA;CACA,gBAAgB;CAChB,gBAAgB;EACd;EACA;EACA;EACA;EACA;EACA;EACA;CACF;AACF;AAqBO,IAAM,6CAA6C;CACxD,KAAK;CACL,MAAM;CACN,UAAU;EAAC;EAAU;EAAW;EAAa;EAAkB;CAAW;CAC1E,YAAY;EACV,QAAQ,EAAE,OAAO,kCAAkC;EACnD,SAAS,EAAE,OAAA,EAA0C;EACrD,WAAW;GACT,MAAM;GACN,UAAU;IAAC;IAAQ;IAAY;GAAW;GAC1C,YAAY;IACV,MAAM;KAAE,MAAM;KAAU,UAAU,CAAC,eAAe;IAAE;IACpD,UAAU;KAAE,MAAM;KAAS,OAAO,EAAE,MAAM,SAAS;IAAE;IACrD,WAAW,EAAE,MAAM,QAAQ;GAC7B;EACF;EACA,gBAAgB;GACd,MAAM;GACN,UAAU;IAAC;IAAU;IAAW;IAAkB;GAAgB;EACpE;EACA,WAAW;GACT,MAAM;GACN,UAAU,CAAC,aAAa,QAAQ;GAChC,YAAY;IACV,WAAW,EAAE,OAAO,SAAS;IAC7B,QAAQ;KAAE,MAAM;KAAU,SAAS;IAAwB;GAC7D;EACF;CACF;AACF;AAGO,IAAM,mCAAN,cAA+C,MAAM;CAC1D,YAAY,SAAiB;EAC3B,MAAM,OAAO;EACb,KAAK,OAAO;CACd;AACF;AAOO,SAAS,0BACd,YACiC;CACjC,MAAM,aAAa,SAAS,YAAY,UAAU,IAC9C,WAAW,aACX,KAAA;CACJ,IAAI,CAAC,YAAY,OAAO,KAAA;CAExB,MAAM,WAAW,IAAI,IACnB,MAAM,QAAQ,YAAY,QAAQ,IAC9B,WAAW,SAAS,QACjB,UAA2B,OAAO,UAAU,QAC/C,IACA,CAAC,CACP;CACA,MAAM,aACJ,UACoC;EACpC,MAAM,QAAQ,MAAM,MAAM,SAAS,OAAO,OAAO,YAAY,IAAI,CAAC;EAClE,OAAO,QAAQ;GAAE;GAAO,UAAU,SAAS,IAAI,KAAK;EAAE,IAAI,KAAA;CAC5D;CAEA,MAAM,iBAAiB,UAAU,CAAC,kBAAkB,iBAAiB,CAAC;CACtE,MAAM,kBAAkB,UAAU,CAAC,mBAAmB,kBAAkB,CAAC;CACzE,IAAI,CAAC,kBAAkB,CAAC,iBAAiB,OAAO,KAAA;CAChD,OAAO;EACL,GAAI,iBAAiB,EAAE,eAAe,IAAI,CAAC;EAC3C,GAAI,kBAAkB,EAAE,gBAAgB,IAAI,CAAC;CAC/C;AACF;AAGO,SAAS,mCACd,WAC8B;CAE9B,MAAM,WAAW;EACf,QAAQ;EACR,SAAA;EACA,WAJ0B,mBAAmB,SAIlC;EACX,gBAAgB;CAClB;CAEA,OAAO;EACL,GAAG;EACH,WAAW;GACT,WAAW;GACX,QAAQ,uBAAuB,QAAQ;EACzC;CACF;AACF;AAOO,SAAS,qCACd,OAC8B;CAC9B,IAAI,CAAC,SAAS,KAAK,GAAG,KAAK,4BAA4B;CACvD,IAAI,MAAM,WAAA,qDACR,KAAK,gCAAgC,OAAO,MAAM,MAAM,GAAG;CAE7D,IAAI,MAAM,YAAA,GACR,KAAK,iCAAiC,OAAO,MAAM,OAAO,GAAG;CAE/D,IAAI,CAAC,SAAS,MAAM,SAAS,KAAK,MAAM,UAAU,cAAc,UAC9D,KAAK,6CAA6C;CAEpD,IACE,OAAO,MAAM,UAAU,WAAW,YAClC,CAAC,yBAAyB,KAAK,MAAM,UAAU,MAAM,GAErD,KAAK,wDAAwD;CAE/D,uBAAuB,MAAM,cAAc;CAE3C,MAAM,WAAyC;EAC7C,QAAQ;EACR,SAAA;EACA,WAJgB,kBAAkB,MAAM,SAIxC;EACA,gBAAgB;EAChB,WAAW;GACT,WAAW;GACX,QAAQ,MAAM,UAAU;EAC1B;CACF;CAOA,IAAI,CAAC,gBANY,uBAAuB;EACtC,QAAQ,SAAS;EACjB,SAAS,SAAS;EAClB,WAAW,SAAS;EACpB,gBAAgB,SAAS;CAC3B,CACqB,GAAU,SAAS,UAAU,MAAM,GACtD,KAAK,4DAA4D;CAEnE,OAAO;AACT;AAGO,SAAS,8BACd,UACQ;CACR,OAAO,cAAc,QAAQ;AAC/B;AAEA,SAAS,mBAAmB,WAA+C;CACzE,MAAM,YAAY,CAAC,GAAG,UAAU,SAAS,CAAA,CACtC,IAAI,iBAAiB,CAAA,CACrB,MAAM,MAAM,UAAU,YAAY,KAAK,MAAM,MAAM,IAAI,CAAC;CAC3D,OAAO;EACL,MAAM,UAAU,KAAK,KACjB;GAAE,eAAe,UAAU,KAAK;GAAe,IAAI,UAAU,KAAK;EAAG,IACrE,EAAE,eAAe,UAAU,KAAK,cAAc;EAClD,UAAU,CAAC,GAAG,UAAU,QAAQ,CAAA,CAAE,KAAK,WAAW;EAClD;CACF;AACF;AAEA,SAAS,kBAAkB,UAAoC;CAC7D,MAAM,WAAW,CAAC,GAAG,SAAS,QAAQ,CAAA,CACnC,IAAI,gBAAgB,CAAA,CACpB,MAAM,MAAM,UAAU,YAAY,KAAK,aAAa,MAAM,WAAW,CAAC;CACzE,OAAO;EACL,MAAM,SAAS;EACf,WAAW,SAAS;EACpB,GAAI,SAAS,gBACT,EAAE,eAAe,SAAS,cAAc,IACxC,CAAC;EACL,GAAI,SAAS,cAAc,EAAE,aAAa,SAAS,YAAY,IAAI,CAAC;EACpE,OAAO,SAAS;EAChB,SAAS,SAAS;EAClB;CACF;AACF;AAEA,SAAS,iBAAiB,SAA+C;CACvE,MAAM,eAAe,QAAQ,eACzB,sBAAsB,QAAQ,YAAY,IAC1C,KAAA;CACJ,OAAO;EACL,YAAY,QAAQ;EACpB,aAAa,QAAQ;EACrB,MAAM,QAAQ;EACd,OAAO,QAAQ;EACf,YAAY,QAAQ;EACpB,cAAc,CAAC,GAAG,QAAQ,YAAY;EACtC,GAAI,QAAQ,cAAc,EAAE,aAAa,QAAQ,YAAY,IAAI,CAAC;EAClE,GAAI,QAAQ,aACR,EACE,YAAY,mBAAmB,QAAQ,UAAU,EAInD,IACA,CAAC;EACL,GAAI,eAAe,EAAE,aAAa,IAAI,CAAC;CACzC;AACF;AAEA,SAAS,kBAAkB,OAAkC;CAC3D,IAAI,CAAC,SAAS,KAAK,GAAG,KAAK,sCAAsC;CACjE,IAAI,CAAC,SAAS,MAAM,IAAI,KAAK,OAAO,MAAM,KAAK,kBAAkB,WAC/D,KAAK,uDAAuD;CAE9D,IAAI,MAAM,KAAK,OAAO,KAAA,KAAa,OAAO,MAAM,KAAK,OAAO,UAC1D,KAAK,6CAA6C;CAEpD,IAAI,CAAC,MAAM,QAAQ,MAAM,QAAQ,KAAK,CAAC,MAAM,SAAS,MAAM,QAAQ,GAClE,KAAK,oDAAoD;CAE3D,aAAa,MAAM,UAAU,6BAA6B;CAC1D,IAAI,CAAC,MAAM,QAAQ,MAAM,SAAS,GAChC,KAAK,+CAA+C;CAEtD,MAAM,YAAY,MAAM,UAAU,IAAI,gBAAgB;CACtD,aACE,UAAU,KAAK,aAAa,SAAS,IAAI,GACzC,8BACF;CACA,OAAO;EACL,MAAM,MAAM,KAAK,KACb;GAAE,eAAe,MAAM,KAAK;GAAe,IAAI,MAAM,KAAK;EAAG,IAC7D,EAAE,eAAe,MAAM,KAAK,cAAc;EAC9C,UAAU,CAAC,GAAG,MAAM,QAAQ;EAC5B;CACF;AACF;AAEA,SAAS,iBAAiB,OAA6B;CACrD,IAAI,CAAC,SAAS,KAAK,GAAG,KAAK,qCAAqC;CAChE,MAAM,OAAO,sBAAsB,MAAM,MAAM,eAAe;CAC9D,MAAM,YAAY,sBAChB,MAAM,WACN,oBACF;CACA,MAAM,QAAQ,sBAAsB,MAAM,OAAO,gBAAgB;CACjE,MAAM,UAAU,sBAAsB,MAAM,SAAS,kBAAkB;CACvE,IAAI,CAAC,MAAM,QAAQ,MAAM,QAAQ,GAC/B,KAAK,6CAA6C;CACpD,MAAM,WAAW,MAAM,SAAS,IAAI,eAAe;CACnD,aACE,SAAS,KAAK,YAAY,QAAQ,WAAW,GAC7C,4BACF;CACA,OAAO;EACL;EACA;EACA,GAAI,OAAO,MAAM,kBAAkB,WAC/B,EAAE,eAAe,MAAM,cAAc,IACrC,CAAC;EACL,GAAI,OAAO,MAAM,gBAAgB,WAC7B,EAAE,aAAa,MAAM,YAAY,IACjC,CAAC;EACL;EACA;EACA;CACF;AACF;AAEA,SAAS,gBAAgB,OAAmC;CAC1D,IAAI,CAAC,SAAS,KAAK,GAAG,KAAK,oCAAoC;CAC/D,MAAM,aAAa,sBACjB,MAAM,YACN,oBACF;CACA,MAAM,cAAc,sBAClB,MAAM,aACN,qBACF;CACA,MAAM,aAAa,sBACjB,MAAM,YACN,oBACF;CACA,IAAI,MAAM,SAAS,UAAU,MAAM,SAAS,UAC1C,KAAK,8CAA8C;CAErD,IAAI,MAAM,UAAU,UAAU,MAAM,UAAU,cAC5C,KAAK,mDAAmD;CAE1D,IACE,CAAC,MAAM,QAAQ,MAAM,YAAY,KACjC,CAAC,MAAM,aAAa,MAAM,QAAQ,GAElC,KAAK,sDAAsD;CAE7D,IAAI,MAAM,eAAe,KAAA,KAAa,CAAC,SAAS,MAAM,UAAU,GAC9D,KAAK,+CAA+C;CAEtD,MAAM,eAAe,qBAAqB,MAAM,YAAY;CAC5D,OAAO;EACL;EACA;EACA,MAAM,MAAM;EACZ,OAAO,MAAM;EACb;EACA,cAAc,CAAC,GAAG,MAAM,YAAY;EACpC,GAAI,OAAO,MAAM,gBAAgB,WAC7B,EAAE,aAAa,MAAM,YAAY,IACjC,CAAC;EACL,GAAI,MAAM,aACN,EACE,YAAY,mBAAmB,MAAM,UAAU,EAIjD,IACA,CAAC;EACL,GAAI,eAAe,EAAE,aAAa,IAAI,CAAC;CACzC;AACF;AAEA,SAAS,qBAAqB,OAAiD;CAC7E,IAAI,UAAU,KAAA,GAAW,OAAO,KAAA;CAChC,IAAI,CAAC,SAAS,KAAK,GAAG,KAAK,iDAAiD;CAC5E,MAAM,cACJ,WACA,SACoC;EACpC,IAAI,cAAc,KAAA,GAAW,OAAO,KAAA;EACpC,IACE,CAAC,SAAS,SAAS,KACnB,OAAO,UAAU,UAAU,YAC3B,UAAU,UAAU,MACpB,OAAO,UAAU,aAAa,WAE9B,KAAK,iCAAiC,KAAI,YAAa;EAEzD,OAAO;GAAE,OAAO,UAAU;GAAO,UAAU,UAAU;EAAS;CAChE;CACA,OAAO,sBAAsB;EAC3B,gBAAgB,WAAW,MAAM,gBAAgB,gBAAgB;EACjE,iBAAiB,WAAW,MAAM,iBAAiB,iBAAiB;CACtE,CAAC;AACH;AAEA,SAAS,sBACP,cACiC;CACjC,MAAM,EAAE,gBAAgB,oBAAoB;CAC5C,IAAI,CAAC,kBAAkB,CAAC,iBAAiB,OAAO,KAAA;CAChD,OAAO;EACL,GAAI,iBAAiB,EAAE,eAAe,IAAI,CAAC;EAC3C,GAAI,kBAAkB,EAAE,gBAAgB,IAAI,CAAC;CAC/C;AACF;AAEA,SAAS,uBAAuB,OAAsB;CACpD,IAAI,CAAC,SAAS,KAAK,GAAG,KAAK,2CAA2C;CACtE,IACE,MAAM,WAAA,4CACN,MAAM,YAAA,KACN,MAAM,mBAAA,2BACN,CAAC,MAAM,QAAQ,MAAM,cAAc,KACnC,cAAc,MAAM,cAAc,MAChC,cAAc,yBAAyB,cAAc,GAEvD,KAAK,2DAA2D;AAEpE;AAEA,SAAS,uBACP,UACQ;CACR,OAAO,UAAU,WAAW,QAAQ,CAAA,CACjC,OAAO,8BAA8B,QAAQ,GAAG,MAAM,CAAA,CACtD,OAAO,KAAK;AACjB;AAEA,SAAS,cAAc,OAAwB;CAC7C,OAAO,KAAK,UAAU,mBAAmB,KAAK,CAAC;AACjD;AAEA,SAAS,mBAAmB,OAA2B;CACrD,IACE,UAAU,QACV,OAAO,UAAU,aACjB,OAAO,UAAU,UAEjB,OAAO;CAET,IAAI,OAAO,UAAU,UAAU;EAC7B,IAAI,CAAC,OAAO,SAAS,KAAK,GACxB,KAAK,4CAA4C;EACnD,OAAO;CACT;CACA,IAAI,MAAM,QAAQ,KAAK,GAAG,OAAO,MAAM,IAAI,kBAAkB;CAC7D,IAAI,SAAS,KAAK,GAAG;EACnB,MAAM,aAAwC,CAAC;EAC/C,KAAA,MAAW,OAAO,OAAO,KAAK,KAAK,CAAA,CAAE,KAAK,WAAW,GACnD,WAAW,OAAO,mBAAmB,MAAM,IAAI;EAEjD,OAAO;CACT;CACA,KAAK,6CAA6C,OAAO,OAAO;AAClE;AAEA,SAAS,gBAAgB,MAAc,OAAwB;CAC7D,MAAM,YAAY,OAAO,KAAK,MAAM,MAAM;CAC1C,MAAM,aAAa,OAAO,KAAK,OAAO,MAAM;CAC5C,OACE,UAAU,WAAW,WAAW,UAChC,gBAAgB,WAAW,UAAU;AAEzC;AAEA,SAAS,aAAa,QAA2B,MAAoB;CACnE,KAAA,IAAS,QAAQ,GAAG,QAAQ,OAAO,QAAQ,SAAS,GAAG;EACrD,MAAM,WAAW,OAAO,QAAQ;EAChC,MAAM,UAAU,OAAO;EACvB,IACE,aAAa,KAAA,KACb,YAAY,KAAA,KACZ,YAAY,UAAU,OAAO,IAAI,GAEjC,KAAK,GAAG,KAAI,kCAAmC;CAEnD;AACF;AAEA,SAAS,YAAY,MAAc,OAAuB;CACxD,OAAO,OAAO,QAAQ,KAAK,OAAO,QAAQ,IAAI;AAChD;AAEA,SAAS,SAAS,OAAkD;CAClE,OAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;AAEA,SAAS,SAAS,OAAiC;CACjD,OAAO,OAAO,UAAU;AAC1B;AAEA,SAAS,sBAAsB,OAAgB,MAAsB;CACnE,IAAI,OAAO,UAAU,YAAY,UAAU,IACzC,KAAK,YAAY,KAAI,4BAA6B;CAEpD,OAAO;AACT;AAEA,SAAS,KAAK,SAAwB;CACpC,MAAM,IAAI,iCAAiC,OAAO;AACpD"}
|