@pulse-compute/wasm-contracts 0.0.0 → 1.0.0-beta.2
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 +35 -2
- package/package.json +124 -6
- package/src/assets/contracts.js +296 -0
- package/src/config/provider-resolution.js +54 -0
- package/src/config/runtime-provider-kv.js +73 -0
- package/src/config/symbolic-refs.js +192 -0
- package/src/crypto/contracts.js +647 -0
- package/src/definitions/config-schema.js +30 -0
- package/src/definitions/handler-roles.js +66 -0
- package/src/definitions/path-grammar.js +11 -0
- package/src/definitions/router-api.js +149 -0
- package/src/diagnostics/codes.js +42 -0
- package/src/diagnostics/reporter.js +20 -0
- package/src/diagnostics.js +139 -0
- package/src/dispatch-table.js +23 -0
- package/src/entities/catalog.d.ts +7 -0
- package/src/entities/catalog.js +87 -0
- package/src/entities/contracts.d.ts +95 -0
- package/src/entities/contracts.js +122 -0
- package/src/entities/json-rpc.d.ts +27 -0
- package/src/entities/json-rpc.js +69 -0
- package/src/entities/runtime.d.ts +67 -0
- package/src/entities/runtime.js +288 -0
- package/src/events/contracts.js +456 -0
- package/src/execution-plan.js +42 -0
- package/src/grip/contracts.js +106 -0
- package/src/handler/backend-json-request-body.js +58 -0
- package/src/handler/beta-lifecycle-golden.js +97 -0
- package/src/handler/canonical-native-plan.js +143 -0
- package/src/handler/canonical-native-runtime.js +168 -0
- package/src/handler/canonical-runtime.js +13 -0
- package/src/handler/context.js +131 -0
- package/src/handler/ctx-contract.js +71 -0
- package/src/handler/doctor-readiness-aggregator.js +67 -0
- package/src/handler/effects.js +446 -0
- package/src/handler/io-lifecycle.js +134 -0
- package/src/handler/lifecycle-authoring-alignment.js +56 -0
- package/src/handler/lifecycle-readiness-aggregate.js +93 -0
- package/src/handler/reference-node-lifecycle-golden.js +73 -0
- package/src/handler/request-json-body.js +147 -0
- package/src/handler/runtime-api.js +471 -0
- package/src/handler/schema-decode-result.js +135 -0
- package/src/handler/schema-response-codec.js +141 -0
- package/src/handler/surface-contract.js +601 -0
- package/src/handler/types-contract.js +51 -0
- package/src/handler-table.js +28 -0
- package/src/host/backend-capabilities.js +24 -0
- package/src/host/capabilities.js +273 -0
- package/src/host/channel-broadcaster.js +60 -0
- package/src/host/json-body.js +80 -0
- package/src/host/request-result-headers.js +38 -0
- package/src/host/streaming-passthrough.js +90 -0
- package/src/host/wasm-host-abi.js +154 -0
- package/src/index.js +75 -0
- package/src/jwt/contracts.js +427 -0
- package/src/library/api-surface.js +99 -0
- package/src/library/contracts.js +273 -0
- package/src/library/manifest.js +317 -0
- package/src/library/second-package-owned-lowering.js +39 -0
- package/src/library/sidecars.js +147 -0
- package/src/logging.js +89 -0
- package/src/package/package-contract.js +975 -0
- package/src/package/package-contract.schema.json +303 -0
- package/src/package/runtime-pulse-live.js +100 -0
- package/src/path-table.js +12 -0
- package/src/path.js +275 -0
- package/src/project/config-factory.js +48 -0
- package/src/project/config-plan.js +409 -0
- package/src/project/config-runtime.js +46 -0
- package/src/project/javascript-application.js +212 -0
- package/src/project/javascript-core-capabilities.js +44 -0
- package/src/project/native-optimization.js +9 -0
- package/src/project/package-ownership.js +242 -0
- package/src/project/package-product-projection.js +171 -0
- package/src/project/reachable-graph-identity.js +219 -0
- package/src/project/reachable-graph-projections.js +279 -0
- package/src/project/reachable-graph-resolver.js +303 -0
- package/src/project/reachable-graph-v2.js +430 -0
- package/src/project/reachable-graph-v2.schema.json +285 -0
- package/src/project/reachable-graph.js +199 -0
- package/src/project/reachable-graph.schema.json +222 -0
- package/src/project/target-support-evidence.js +559 -0
- package/src/provider/canonical-provider.js +239 -0
- package/src/provider/final-wasm-policy.js +87 -0
- package/src/provider/toolchain.js +771 -0
- package/src/route-plan.js +43 -0
- package/src/s3/contracts.js +12 -0
- package/src/schema-json/compile.js +63 -0
- package/src/schema-json/contracts.js +10 -0
- package/src/schema-json/generic-parser.js +62 -0
- package/src/schema-json/registry.js +359 -0
- package/src/schema-json/semantic-trace.js +241 -0
- package/src/schema-json/v1.js +65 -0
- package/src/schema-json/v2.js +110 -0
- package/src/sha256.js +104 -0
- package/src/spine.js +381 -0
- package/src/stable-id.js +142 -0
|
@@ -0,0 +1,559 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { sha256Hex, stableStringify } = require('../stable-id.js');
|
|
4
|
+
const { normalizeNativeEligibilityProjection } = require('./reachable-graph-projections.js');
|
|
5
|
+
|
|
6
|
+
const TARGET_SUPPORT_DECLARATION_VERSION = 'pulse.target-support-declaration.v1';
|
|
7
|
+
const TARGET_AVAILABILITY_POLICY_VERSION = 'pulse.target-availability-policy.v1';
|
|
8
|
+
const TARGET_SUPPORT_EVIDENCE_VERSION = 'pulse.target-support-evidence.v1';
|
|
9
|
+
const TARGET_SUPPORT_OBSERVATION_VERSION = 'pulse.target-support-observation.v1';
|
|
10
|
+
const TARGET_SUPPORT_KIND = 'pulse.target-support-evidence';
|
|
11
|
+
const TARGET_SUPPORT_DECLARATION_KIND = 'pulse.target-support-declaration';
|
|
12
|
+
|
|
13
|
+
const SUPPORT_STATUSES = Object.freeze(['eligible', 'pending', 'blocked', 'irrelevant']);
|
|
14
|
+
const GATE_STATUSES = Object.freeze(['satisfied', 'pending', 'blocked']);
|
|
15
|
+
const AVAILABILITY_DEFINITIONS = Object.freeze(['full-target-support', 'descriptor-supported']);
|
|
16
|
+
|
|
17
|
+
function isPlainObject(value) {
|
|
18
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
|
|
19
|
+
const prototype = Object.getPrototypeOf(value);
|
|
20
|
+
return prototype === Object.prototype || prototype === null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function deepFreeze(value) {
|
|
24
|
+
if (!value || typeof value !== 'object' || Object.isFrozen(value)) return value;
|
|
25
|
+
for (const child of Object.values(value)) deepFreeze(child);
|
|
26
|
+
return Object.freeze(value);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function assertKnownKeys(value, allowed, field) {
|
|
30
|
+
const unknown = Object.keys(value).filter((key) => !allowed.includes(key)).sort();
|
|
31
|
+
if (unknown.length > 0) throw new TypeError(`${field} contains unsupported field${unknown.length === 1 ? '' : 's'}: ${unknown.join(', ')}.`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function nonEmptyString(value, field) {
|
|
35
|
+
if (typeof value !== 'string' || value.trim().length === 0) throw new TypeError(`${field} must be a non-empty string.`);
|
|
36
|
+
return value.trim();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function optionalString(value, field) {
|
|
40
|
+
if (value == null) return null;
|
|
41
|
+
return nonEmptyString(value, field);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function nonNegativeInteger(value, field) {
|
|
45
|
+
const normalized = Number(value || 0);
|
|
46
|
+
if (!Number.isInteger(normalized) || normalized < 0) throw new TypeError(`${field} must be a non-negative integer.`);
|
|
47
|
+
return normalized;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function sortedUniqueStrings(values, field) {
|
|
51
|
+
if (!Array.isArray(values)) throw new TypeError(`${field} must be an array.`);
|
|
52
|
+
return Object.freeze([...new Set(values.map((value, index) => nonEmptyString(value, `${field}[${index}]`)))].sort());
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function normalizeEvidenceReference(value, field) {
|
|
56
|
+
if (value == null) return null;
|
|
57
|
+
if (!isPlainObject(value)) throw new TypeError(`${field} must be an object.`);
|
|
58
|
+
assertKnownKeys(value, ['artifact', 'version', 'sha256', 'status', 'cases', 'matched', 'mismatches'], field);
|
|
59
|
+
const normalized = {
|
|
60
|
+
artifact: optionalString(value.artifact, `${field}.artifact`),
|
|
61
|
+
version: optionalString(value.version, `${field}.version`),
|
|
62
|
+
sha256: optionalString(value.sha256, `${field}.sha256`),
|
|
63
|
+
status: optionalString(value.status, `${field}.status`),
|
|
64
|
+
cases: nonNegativeInteger(value.cases, `${field}.cases`),
|
|
65
|
+
matched: nonNegativeInteger(value.matched, `${field}.matched`),
|
|
66
|
+
mismatches: nonNegativeInteger(value.mismatches, `${field}.mismatches`)
|
|
67
|
+
};
|
|
68
|
+
if (normalized.matched + normalized.mismatches > normalized.cases) {
|
|
69
|
+
throw new TypeError(`${field} matched plus mismatches cannot exceed cases.`);
|
|
70
|
+
}
|
|
71
|
+
if (normalized.status === 'passed' && (normalized.mismatches !== 0 || normalized.matched !== normalized.cases)) {
|
|
72
|
+
throw new TypeError(`${field} passed evidence must match every case with zero mismatches.`);
|
|
73
|
+
}
|
|
74
|
+
return deepFreeze(normalized);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function normalizeGate(value, index) {
|
|
78
|
+
if (!isPlainObject(value)) throw new TypeError(`target support gate ${index} must be an object.`);
|
|
79
|
+
assertKnownKeys(value, ['id', 'status', 'reasonId', 'owner', 'summary', 'evidence'], `target support gate ${index}`);
|
|
80
|
+
const status = nonEmptyString(value.status, `target support gate ${index}.status`);
|
|
81
|
+
if (!GATE_STATUSES.includes(status)) throw new TypeError(`target support gate ${index}.status must be one of ${GATE_STATUSES.join(', ')}.`);
|
|
82
|
+
return deepFreeze({
|
|
83
|
+
id: nonEmptyString(value.id, `target support gate ${index}.id`),
|
|
84
|
+
status,
|
|
85
|
+
reasonId: nonEmptyString(value.reasonId, `target support gate ${index}.reasonId`),
|
|
86
|
+
owner: nonEmptyString(value.owner, `target support gate ${index}.owner`),
|
|
87
|
+
summary: nonEmptyString(value.summary, `target support gate ${index}.summary`),
|
|
88
|
+
evidence: normalizeEvidenceReference(value.evidence, `target support gate ${index}.evidence`)
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function normalizeDeclarationCommand(value, index) {
|
|
93
|
+
if (!isPlainObject(value)) throw new TypeError(`target support command ${index} must be an object.`);
|
|
94
|
+
assertKnownKeys(value, ['id', 'implemented', 'status', 'reasonId', 'owner'], `target support command ${index}`);
|
|
95
|
+
const status = nonEmptyString(value.status, `target support command ${index}.status`);
|
|
96
|
+
if (!['eligible', 'pending', 'blocked'].includes(status)) {
|
|
97
|
+
throw new TypeError(`target support command ${index}.status must be eligible, pending, or blocked.`);
|
|
98
|
+
}
|
|
99
|
+
const implemented = value.implemented === true;
|
|
100
|
+
if (implemented !== (status === 'eligible')) {
|
|
101
|
+
throw new TypeError(`target support command ${index} must be eligible exactly when implemented is true.`);
|
|
102
|
+
}
|
|
103
|
+
return deepFreeze({
|
|
104
|
+
id: nonEmptyString(value.id, `target support command ${index}.id`),
|
|
105
|
+
implemented,
|
|
106
|
+
status,
|
|
107
|
+
reasonId: nonEmptyString(value.reasonId, `target support command ${index}.reasonId`),
|
|
108
|
+
owner: nonEmptyString(value.owner, `target support command ${index}.owner`)
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function normalizeProjectCommand(value, index) {
|
|
113
|
+
if (!isPlainObject(value)) throw new TypeError(`target support project command ${index} must be an object.`);
|
|
114
|
+
assertKnownKeys(value, ['id', 'implemented', 'status', 'reasonId', 'owner'], `target support project command ${index}`);
|
|
115
|
+
const status = nonEmptyString(value.status, `target support project command ${index}.status`);
|
|
116
|
+
if (!['eligible', 'pending', 'blocked'].includes(status)) {
|
|
117
|
+
throw new TypeError(`target support project command ${index}.status must be eligible, pending, or blocked.`);
|
|
118
|
+
}
|
|
119
|
+
const implemented = value.implemented === true;
|
|
120
|
+
if (!implemented && status === 'eligible') {
|
|
121
|
+
throw new TypeError(`target support project command ${index} cannot be eligible when it is not implemented.`);
|
|
122
|
+
}
|
|
123
|
+
return deepFreeze({
|
|
124
|
+
id: nonEmptyString(value.id, `target support project command ${index}.id`),
|
|
125
|
+
implemented,
|
|
126
|
+
status,
|
|
127
|
+
reasonId: nonEmptyString(value.reasonId, `target support project command ${index}.reasonId`),
|
|
128
|
+
owner: nonEmptyString(value.owner, `target support project command ${index}.owner`)
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function assertUniqueIds(values, field) {
|
|
133
|
+
const ids = values.map((entry) => entry.id);
|
|
134
|
+
if (new Set(ids).size !== ids.length) throw new TypeError(`${field} IDs must be unique.`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function normalizeTargetSupportDeclaration(input) {
|
|
138
|
+
if (!isPlainObject(input)) throw new TypeError('Target support declaration input must be an object.');
|
|
139
|
+
assertKnownKeys(input, [
|
|
140
|
+
'provider', 'target', 'targetId', 'runtimeClass', 'definition', 'coreExecutionReady',
|
|
141
|
+
'fullTargetSupportReady', 'generalAvailable', 'automaticFallback', 'gates', 'commands', 'parity'
|
|
142
|
+
], 'target support declaration');
|
|
143
|
+
|
|
144
|
+
const definition = nonEmptyString(input.definition, 'target support declaration.definition');
|
|
145
|
+
if (!AVAILABILITY_DEFINITIONS.includes(definition)) {
|
|
146
|
+
throw new TypeError(`target support declaration.definition must be one of ${AVAILABILITY_DEFINITIONS.join(', ')}.`);
|
|
147
|
+
}
|
|
148
|
+
if (input.automaticFallback === true) throw new TypeError('Target support declaration automaticFallback must remain false.');
|
|
149
|
+
|
|
150
|
+
const gates = Object.freeze((input.gates || []).map(normalizeGate).sort((a, b) => a.id.localeCompare(b.id)));
|
|
151
|
+
const commands = Object.freeze((input.commands || []).map(normalizeDeclarationCommand).sort((a, b) => a.id.localeCompare(b.id)));
|
|
152
|
+
assertUniqueIds(gates, 'Target support gate');
|
|
153
|
+
assertUniqueIds(commands, 'Target support command');
|
|
154
|
+
|
|
155
|
+
const allGatesSatisfied = gates.length > 0 && gates.every((entry) => entry.status === 'satisfied');
|
|
156
|
+
const coreExecutionReady = input.coreExecutionReady === true;
|
|
157
|
+
const fullTargetSupportReady = input.fullTargetSupportReady === true;
|
|
158
|
+
const generalAvailable = input.generalAvailable === true;
|
|
159
|
+
if (fullTargetSupportReady !== allGatesSatisfied) {
|
|
160
|
+
throw new TypeError('fullTargetSupportReady must be true exactly when every declared support gate is satisfied.');
|
|
161
|
+
}
|
|
162
|
+
if (fullTargetSupportReady && !coreExecutionReady) {
|
|
163
|
+
throw new TypeError('A fully supported target must declare coreExecutionReady.');
|
|
164
|
+
}
|
|
165
|
+
if (definition === 'full-target-support' && generalAvailable !== fullTargetSupportReady) {
|
|
166
|
+
throw new TypeError('full-target-support generalAvailable must equal fullTargetSupportReady.');
|
|
167
|
+
}
|
|
168
|
+
if (generalAvailable && commands.some((entry) => entry.status !== 'eligible')) {
|
|
169
|
+
throw new TypeError('A generally available target cannot declare a pending or blocked command.');
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const semantic = {
|
|
173
|
+
version: TARGET_SUPPORT_DECLARATION_VERSION,
|
|
174
|
+
kind: TARGET_SUPPORT_DECLARATION_KIND,
|
|
175
|
+
provider: nonEmptyString(input.provider, 'target support declaration.provider'),
|
|
176
|
+
target: nonEmptyString(input.target, 'target support declaration.target'),
|
|
177
|
+
targetId: nonEmptyString(input.targetId, 'target support declaration.targetId'),
|
|
178
|
+
runtimeClass: nonEmptyString(input.runtimeClass, 'target support declaration.runtimeClass'),
|
|
179
|
+
availability: deepFreeze({
|
|
180
|
+
version: TARGET_AVAILABILITY_POLICY_VERSION,
|
|
181
|
+
definition,
|
|
182
|
+
coreExecutionReady,
|
|
183
|
+
fullTargetSupportReady,
|
|
184
|
+
generalAvailable,
|
|
185
|
+
automaticFallback: false,
|
|
186
|
+
gates,
|
|
187
|
+
summary: deepFreeze({
|
|
188
|
+
total: gates.length,
|
|
189
|
+
satisfied: gates.filter((entry) => entry.status === 'satisfied').length,
|
|
190
|
+
pending: gates.filter((entry) => entry.status === 'pending').length,
|
|
191
|
+
blocked: gates.filter((entry) => entry.status === 'blocked').length
|
|
192
|
+
})
|
|
193
|
+
}),
|
|
194
|
+
commands,
|
|
195
|
+
parity: normalizeEvidenceReference(input.parity, 'target support declaration.parity'),
|
|
196
|
+
policy: deepFreeze({
|
|
197
|
+
selectedTargetAuthoritative: true,
|
|
198
|
+
descriptorAndCommandTruthSeparateFromAvailability: true,
|
|
199
|
+
projectEligibilitySeparateFromGeneralAvailability: true,
|
|
200
|
+
automaticFallback: false
|
|
201
|
+
})
|
|
202
|
+
};
|
|
203
|
+
return deepFreeze({ ...semantic, declarationHash: sha256Hex(stableStringify(semantic)) });
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function normalizeSource(value, index, field) {
|
|
207
|
+
if (!isPlainObject(value)) throw new TypeError(`${field}[${index}] must be an object.`);
|
|
208
|
+
assertKnownKeys(value, ['file', 'line', 'column'], `${field}[${index}]`);
|
|
209
|
+
return deepFreeze({
|
|
210
|
+
file: nonEmptyString(value.file, `${field}[${index}].file`),
|
|
211
|
+
line: Number.isInteger(value.line) && value.line > 0 ? value.line : 1,
|
|
212
|
+
column: Number.isInteger(value.column) && value.column > 0 ? value.column : 1
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function normalizeEligibilityItem(value, index, field) {
|
|
217
|
+
if (!isPlainObject(value)) throw new TypeError(`${field}[${index}] must be an object.`);
|
|
218
|
+
assertKnownKeys(value, ['id', 'status', 'required', 'reasonId', 'owner'], `${field}[${index}]`);
|
|
219
|
+
const status = nonEmptyString(value.status, `${field}[${index}].status`);
|
|
220
|
+
if (!SUPPORT_STATUSES.includes(status)) throw new TypeError(`${field}[${index}].status must be one of ${SUPPORT_STATUSES.join(', ')}.`);
|
|
221
|
+
const required = value.required !== false;
|
|
222
|
+
if (status === 'irrelevant' && required) throw new TypeError(`${field}[${index}] cannot be required and irrelevant.`);
|
|
223
|
+
if (status !== 'irrelevant' && !required) throw new TypeError(`${field}[${index}] must be irrelevant when it is not required.`);
|
|
224
|
+
return deepFreeze({
|
|
225
|
+
id: nonEmptyString(value.id, `${field}[${index}].id`),
|
|
226
|
+
status,
|
|
227
|
+
required,
|
|
228
|
+
reasonId: nonEmptyString(value.reasonId, `${field}[${index}].reasonId`),
|
|
229
|
+
owner: nonEmptyString(value.owner, `${field}[${index}].owner`)
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function normalizePackageEligibility(value, index) {
|
|
234
|
+
if (!isPlainObject(value)) throw new TypeError(`target support package ${index} must be an object.`);
|
|
235
|
+
assertKnownKeys(value, [
|
|
236
|
+
'moduleId', 'packageName', 'packageSubpath', 'contractId', 'reachable', 'runtime', 'selected',
|
|
237
|
+
'configured', 'required', 'status', 'reasonId', 'reasonCode', 'owner', 'realization',
|
|
238
|
+
'targetStatus', 'runtimeEntry', 'sources'
|
|
239
|
+
], `target support package ${index}`);
|
|
240
|
+
const status = nonEmptyString(value.status, `target support package ${index}.status`);
|
|
241
|
+
if (!SUPPORT_STATUSES.includes(status)) throw new TypeError(`target support package ${index}.status must be one of ${SUPPORT_STATUSES.join(', ')}.`);
|
|
242
|
+
const reachable = value.reachable === true;
|
|
243
|
+
const runtime = value.runtime === true;
|
|
244
|
+
const selected = value.selected === true;
|
|
245
|
+
const configured = value.configured === true;
|
|
246
|
+
const required = value.required === true;
|
|
247
|
+
if (runtime && !reachable) throw new TypeError(`target support package ${index} cannot be runtime without being reachable.`);
|
|
248
|
+
if (required !== (runtime || selected || configured)) {
|
|
249
|
+
throw new TypeError(`target support package ${index}.required must equal runtime || selected || configured.`);
|
|
250
|
+
}
|
|
251
|
+
if (required && status === 'irrelevant') throw new TypeError(`target support package ${index} cannot be required and irrelevant.`);
|
|
252
|
+
if (!required && status !== 'irrelevant') throw new TypeError(`target support package ${index} must be irrelevant when it is not required.`);
|
|
253
|
+
return deepFreeze({
|
|
254
|
+
moduleId: nonEmptyString(value.moduleId, `target support package ${index}.moduleId`),
|
|
255
|
+
packageName: nonEmptyString(value.packageName, `target support package ${index}.packageName`),
|
|
256
|
+
packageSubpath: value.packageSubpath == null ? '.' : String(value.packageSubpath),
|
|
257
|
+
contractId: optionalString(value.contractId, `target support package ${index}.contractId`),
|
|
258
|
+
reachable,
|
|
259
|
+
runtime,
|
|
260
|
+
selected,
|
|
261
|
+
configured,
|
|
262
|
+
required,
|
|
263
|
+
status,
|
|
264
|
+
reasonId: nonEmptyString(value.reasonId, `target support package ${index}.reasonId`),
|
|
265
|
+
reasonCode: optionalString(value.reasonCode, `target support package ${index}.reasonCode`),
|
|
266
|
+
owner: nonEmptyString(value.owner, `target support package ${index}.owner`),
|
|
267
|
+
realization: optionalString(value.realization, `target support package ${index}.realization`),
|
|
268
|
+
targetStatus: optionalString(value.targetStatus, `target support package ${index}.targetStatus`),
|
|
269
|
+
runtimeEntry: optionalString(value.runtimeEntry, `target support package ${index}.runtimeEntry`),
|
|
270
|
+
sources: Object.freeze((value.sources || []).map((entry, sourceIndex) => normalizeSource(entry, sourceIndex, `target support package ${index}.sources`))
|
|
271
|
+
.sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line || a.column - b.column))
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function normalizeNativeEligibility(value) {
|
|
276
|
+
if (!isPlainObject(value)) throw new TypeError('Target support nativeEligibility must be an object.');
|
|
277
|
+
assertKnownKeys(value, ['version', 'graphHash', 'eligible', 'blockers', 'firstUnsupportedBoundary', 'handlers', 'policy', 'projectionHash'], 'target support nativeEligibility');
|
|
278
|
+
const normalized = normalizeNativeEligibilityProjection({
|
|
279
|
+
graphHash: value.graphHash,
|
|
280
|
+
blockers: (value.blockers || []).map((entry) => {
|
|
281
|
+
const { id: _id, ...semantic } = entry;
|
|
282
|
+
return semantic;
|
|
283
|
+
}),
|
|
284
|
+
handlers: value.handlers || []
|
|
285
|
+
});
|
|
286
|
+
if (value.version !== normalized.version) throw new TypeError('Target support nativeEligibility version does not match the canonical projection.');
|
|
287
|
+
if (value.projectionHash !== normalized.projectionHash) throw new TypeError('Target support nativeEligibility projectionHash does not match its canonical content.');
|
|
288
|
+
if ((value.eligible === true) !== normalized.eligible) throw new TypeError('Target support nativeEligibility eligible does not match its blockers.');
|
|
289
|
+
if (stableStringify(value.firstUnsupportedBoundary || null) !== stableStringify(normalized.firstUnsupportedBoundary)) {
|
|
290
|
+
throw new TypeError('Target support nativeEligibility firstUnsupportedBoundary does not match the canonical projection.');
|
|
291
|
+
}
|
|
292
|
+
if (!isPlainObject(value.policy) || value.policy.automaticFallback !== false) {
|
|
293
|
+
throw new TypeError('Target support nativeEligibility must prohibit automatic fallback.');
|
|
294
|
+
}
|
|
295
|
+
return normalized;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function summarizeStatuses(values) {
|
|
299
|
+
return deepFreeze({
|
|
300
|
+
total: values.length,
|
|
301
|
+
eligible: values.filter((entry) => entry.status === 'eligible').length,
|
|
302
|
+
pending: values.filter((entry) => entry.status === 'pending').length,
|
|
303
|
+
blocked: values.filter((entry) => entry.status === 'blocked').length,
|
|
304
|
+
irrelevant: values.filter((entry) => entry.status === 'irrelevant').length
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function requiredEvidence(application, capabilities, providerRequirements, packages) {
|
|
309
|
+
return [
|
|
310
|
+
...application.filter((entry) => entry.required),
|
|
311
|
+
...capabilities.filter((entry) => entry.required),
|
|
312
|
+
...providerRequirements.filter((entry) => entry.required),
|
|
313
|
+
...packages.filter((entry) => entry.required)
|
|
314
|
+
];
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function expectedProjectStatus(application, capabilities, providerRequirements, packages) {
|
|
318
|
+
const required = requiredEvidence(application, capabilities, providerRequirements, packages);
|
|
319
|
+
if (required.some((entry) => entry.status === 'blocked')) return 'blocked';
|
|
320
|
+
if (required.some((entry) => entry.status === 'pending')) return 'pending';
|
|
321
|
+
return 'eligible';
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function expectedReasonIds(application, capabilities, providerRequirements, packages) {
|
|
325
|
+
return [...new Set(requiredEvidence(application, capabilities, providerRequirements, packages)
|
|
326
|
+
.filter((entry) => entry.status !== 'eligible')
|
|
327
|
+
.map((entry) => entry.reasonId))].sort();
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function normalizeProjectEligibility(input) {
|
|
331
|
+
if (!isPlainObject(input)) throw new TypeError('Target support project eligibility must be an object.');
|
|
332
|
+
assertKnownKeys(input, [
|
|
333
|
+
'status', 'reasonIds', 'projectHash', 'configPlanHash', 'sourceHash', 'graphHash', 'applicationPlanHash',
|
|
334
|
+
'packageProjectionHash', 'packageProductHash', 'nativeEligibility', 'commands', 'application',
|
|
335
|
+
'capabilities', 'providerRequirements', 'packages'
|
|
336
|
+
], 'target support project eligibility');
|
|
337
|
+
const status = nonEmptyString(input.status, 'target support project eligibility.status');
|
|
338
|
+
if (!['eligible', 'pending', 'blocked'].includes(status)) throw new TypeError('Target support project eligibility status must be eligible, pending, or blocked.');
|
|
339
|
+
const commands = Object.freeze((input.commands || []).map(normalizeProjectCommand).sort((a, b) => a.id.localeCompare(b.id)));
|
|
340
|
+
const application = Object.freeze((input.application || []).map((entry, index) => normalizeEligibilityItem(entry, index, 'target support application decisions'))
|
|
341
|
+
.sort((a, b) => a.id.localeCompare(b.id)));
|
|
342
|
+
const capabilities = Object.freeze((input.capabilities || []).map((entry, index) => normalizeEligibilityItem(entry, index, 'target support capabilities'))
|
|
343
|
+
.sort((a, b) => a.id.localeCompare(b.id)));
|
|
344
|
+
const providerRequirements = Object.freeze((input.providerRequirements || []).map((entry, index) => normalizeEligibilityItem(entry, index, 'target support provider requirements'))
|
|
345
|
+
.sort((a, b) => a.id.localeCompare(b.id)));
|
|
346
|
+
const packages = Object.freeze((input.packages || []).map(normalizePackageEligibility)
|
|
347
|
+
.sort((a, b) => a.packageName.localeCompare(b.packageName) || a.packageSubpath.localeCompare(b.packageSubpath) || a.moduleId.localeCompare(b.moduleId)));
|
|
348
|
+
const nativeEligibility = normalizeNativeEligibility(input.nativeEligibility);
|
|
349
|
+
assertUniqueIds(commands, 'Target support project command');
|
|
350
|
+
assertUniqueIds(application, 'Target support application decision');
|
|
351
|
+
assertUniqueIds(capabilities, 'Target support capability');
|
|
352
|
+
assertUniqueIds(providerRequirements, 'Target support provider requirement');
|
|
353
|
+
if (new Set(packages.map((entry) => entry.moduleId)).size !== packages.length) throw new TypeError('Target support package module IDs must be unique.');
|
|
354
|
+
|
|
355
|
+
const expectedStatus = expectedProjectStatus(application, capabilities, providerRequirements, packages);
|
|
356
|
+
if (status !== expectedStatus) throw new TypeError(`Target support project eligibility status must be ${expectedStatus} for the supplied evidence.`);
|
|
357
|
+
const reasonIds = sortedUniqueStrings(input.reasonIds || [], 'target support project eligibility.reasonIds');
|
|
358
|
+
const expectedReasons = expectedReasonIds(application, capabilities, providerRequirements, packages);
|
|
359
|
+
if (stableStringify(reasonIds) !== stableStringify(expectedReasons)) {
|
|
360
|
+
throw new TypeError(`Target support project eligibility reasonIds must equal the non-eligible required evidence reasons: ${expectedReasons.join(', ')}.`);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
return deepFreeze({
|
|
364
|
+
status,
|
|
365
|
+
reasonIds,
|
|
366
|
+
projectHash: optionalString(input.projectHash, 'target support project eligibility.projectHash'),
|
|
367
|
+
configPlanHash: optionalString(input.configPlanHash, 'target support project eligibility.configPlanHash'),
|
|
368
|
+
sourceHash: nonEmptyString(input.sourceHash, 'target support project eligibility.sourceHash'),
|
|
369
|
+
graphHash: nonEmptyString(input.graphHash, 'target support project eligibility.graphHash'),
|
|
370
|
+
applicationPlanHash: nonEmptyString(input.applicationPlanHash, 'target support project eligibility.applicationPlanHash'),
|
|
371
|
+
packageProjectionHash: optionalString(input.packageProjectionHash, 'target support project eligibility.packageProjectionHash'),
|
|
372
|
+
packageProductHash: optionalString(input.packageProductHash, 'target support project eligibility.packageProductHash'),
|
|
373
|
+
nativeEligibility,
|
|
374
|
+
commands,
|
|
375
|
+
application,
|
|
376
|
+
capabilities,
|
|
377
|
+
providerRequirements,
|
|
378
|
+
packages,
|
|
379
|
+
summary: deepFreeze({
|
|
380
|
+
commands: summarizeStatuses(commands),
|
|
381
|
+
application: summarizeStatuses(application),
|
|
382
|
+
capabilities: summarizeStatuses(capabilities),
|
|
383
|
+
providerRequirements: summarizeStatuses(providerRequirements),
|
|
384
|
+
packages: summarizeStatuses(packages)
|
|
385
|
+
})
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function assertProjectCommandsAlign(declaration, project) {
|
|
390
|
+
const declarationById = new Map(declaration.commands.map((entry) => [entry.id, entry]));
|
|
391
|
+
const projectById = new Map(project.commands.map((entry) => [entry.id, entry]));
|
|
392
|
+
if (stableStringify([...declarationById.keys()].sort()) !== stableStringify([...projectById.keys()].sort())) {
|
|
393
|
+
throw new TypeError('Target support project commands must cover the declaration command matrix exactly.');
|
|
394
|
+
}
|
|
395
|
+
for (const [id, declared] of declarationById) {
|
|
396
|
+
const effective = projectById.get(id);
|
|
397
|
+
if (effective.implemented !== declared.implemented) {
|
|
398
|
+
throw new TypeError(`Target support project command ${id} implementation truth diverges from the declaration.`);
|
|
399
|
+
}
|
|
400
|
+
if (declared.status !== 'eligible' && effective.status !== declared.status) {
|
|
401
|
+
throw new TypeError(`Target support project command ${id} must preserve declaration status ${declared.status}.`);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
for (const id of ['test', 'dev']) {
|
|
406
|
+
const declared = declarationById.get(id);
|
|
407
|
+
const effective = projectById.get(id);
|
|
408
|
+
if (declared && declared.status === 'eligible' && effective.status !== project.status) {
|
|
409
|
+
throw new TypeError(`Target support project command ${id} must follow project execution eligibility ${project.status}.`);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
const compile = projectById.get('compile');
|
|
413
|
+
if (compile && declarationById.get('compile')?.status === 'eligible') {
|
|
414
|
+
const expected = project.nativeEligibility.eligible ? 'eligible' : 'blocked';
|
|
415
|
+
if (compile.status !== expected) throw new TypeError(`Target support project command compile must be ${expected} for the native eligibility projection.`);
|
|
416
|
+
}
|
|
417
|
+
for (const id of ['inspect', 'doctor']) {
|
|
418
|
+
const declared = declarationById.get(id);
|
|
419
|
+
const effective = projectById.get(id);
|
|
420
|
+
if (declared && declared.status === 'eligible' && effective.status !== 'eligible') {
|
|
421
|
+
throw new TypeError(`Target support project command ${id} must remain eligible when deterministic evidence was produced.`);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function normalizeLoaderObservation(input) {
|
|
427
|
+
if (input == null) return null;
|
|
428
|
+
if (!isPlainObject(input)) throw new TypeError('Target support evidence loader must be an object or null.');
|
|
429
|
+
assertKnownKeys(input, ['version', 'attempted', 'loaded', 'modulesLoaded', 'packagesLoaded', 'errorCode', 'skippedReason'], 'target support evidence loader');
|
|
430
|
+
const attempted = input.attempted === true;
|
|
431
|
+
const loaded = input.loaded === true;
|
|
432
|
+
const modulesLoaded = nonNegativeInteger(input.modulesLoaded, 'target support evidence loader.modulesLoaded');
|
|
433
|
+
const packagesLoaded = sortedUniqueStrings(input.packagesLoaded || [], 'target support evidence loader.packagesLoaded');
|
|
434
|
+
const errorCode = optionalString(input.errorCode, 'target support evidence loader.errorCode');
|
|
435
|
+
const skippedReason = optionalString(input.skippedReason, 'target support evidence loader.skippedReason');
|
|
436
|
+
if (loaded && !attempted) throw new TypeError('A loaded JavaScript application must record that loading was attempted.');
|
|
437
|
+
if (loaded && !input.version) throw new TypeError('A loaded JavaScript application must record the loader version.');
|
|
438
|
+
if (errorCode && (!attempted || loaded)) throw new TypeError('A JavaScript application load error requires an unsuccessful load attempt.');
|
|
439
|
+
if (attempted && !loaded && !errorCode) throw new TypeError('An unsuccessful JavaScript application load attempt must record an errorCode.');
|
|
440
|
+
if (!attempted && (loaded || modulesLoaded !== 0 || packagesLoaded.length !== 0 || errorCode)) {
|
|
441
|
+
throw new TypeError('JavaScript application loader counts and errors must be empty when loading was not attempted.');
|
|
442
|
+
}
|
|
443
|
+
if (!attempted && !skippedReason) throw new TypeError('A skipped JavaScript application load must include skippedReason.');
|
|
444
|
+
if (attempted && skippedReason) throw new TypeError('An attempted JavaScript application load cannot include skippedReason.');
|
|
445
|
+
return deepFreeze({
|
|
446
|
+
version: optionalString(input.version, 'target support evidence loader.version'),
|
|
447
|
+
status: loaded ? 'passed' : attempted ? 'failed' : 'skipped',
|
|
448
|
+
attempted,
|
|
449
|
+
loaded,
|
|
450
|
+
modulesLoaded,
|
|
451
|
+
packagesLoaded,
|
|
452
|
+
errorCode,
|
|
453
|
+
skippedReason
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function assertDeclarationIntegrity(declaration) {
|
|
458
|
+
const { declarationHash, ...semantic } = declaration;
|
|
459
|
+
if (declarationHash !== sha256Hex(stableStringify(semantic))) {
|
|
460
|
+
throw new TypeError('Target support declaration hash does not match its normalized content.');
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function normalizeTargetSupportEvidence(input) {
|
|
465
|
+
if (!isPlainObject(input)) throw new TypeError('Target support evidence input must be an object.');
|
|
466
|
+
assertKnownKeys(input, ['declaration', 'descriptor', 'project', 'loader'], 'target support evidence');
|
|
467
|
+
const declaration = input.declaration;
|
|
468
|
+
if (!declaration || declaration.version !== TARGET_SUPPORT_DECLARATION_VERSION || declaration.kind !== TARGET_SUPPORT_DECLARATION_KIND) {
|
|
469
|
+
throw new TypeError('Target support evidence requires a normalized target support declaration.');
|
|
470
|
+
}
|
|
471
|
+
assertDeclarationIntegrity(declaration);
|
|
472
|
+
if (!isPlainObject(input.descriptor)) throw new TypeError('Target support evidence descriptor must be an object.');
|
|
473
|
+
assertKnownKeys(input.descriptor, ['version', 'status', 'commands', 'descriptorHash'], 'target support evidence descriptor');
|
|
474
|
+
const descriptorCommands = input.descriptor.commands || {};
|
|
475
|
+
if (!isPlainObject(descriptorCommands)) throw new TypeError('Target support evidence descriptor.commands must be an object.');
|
|
476
|
+
const normalizedDescriptorCommands = deepFreeze(Object.fromEntries(Object.keys(descriptorCommands).sort().map((key) => [key, descriptorCommands[key] === true])));
|
|
477
|
+
const declarationCommandIds = declaration.commands.map((entry) => entry.id).sort();
|
|
478
|
+
if (stableStringify(Object.keys(normalizedDescriptorCommands)) !== stableStringify(declarationCommandIds)) {
|
|
479
|
+
throw new TypeError('Target support descriptor commands must cover the declaration command matrix exactly.');
|
|
480
|
+
}
|
|
481
|
+
for (const command of declaration.commands) {
|
|
482
|
+
if (normalizedDescriptorCommands[command.id] !== command.implemented) {
|
|
483
|
+
throw new TypeError(`Target support descriptor command ${command.id} implementation truth diverges from the declaration.`);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
const project = normalizeProjectEligibility(input.project);
|
|
487
|
+
assertProjectCommandsAlign(declaration, project);
|
|
488
|
+
const loader = normalizeLoaderObservation(input.loader);
|
|
489
|
+
|
|
490
|
+
const semantic = {
|
|
491
|
+
version: TARGET_SUPPORT_EVIDENCE_VERSION,
|
|
492
|
+
kind: TARGET_SUPPORT_KIND,
|
|
493
|
+
provider: declaration.provider,
|
|
494
|
+
target: declaration.target,
|
|
495
|
+
targetId: declaration.targetId,
|
|
496
|
+
runtimeClass: declaration.runtimeClass,
|
|
497
|
+
availability: declaration.availability,
|
|
498
|
+
commands: declaration.commands,
|
|
499
|
+
descriptor: deepFreeze({
|
|
500
|
+
version: nonEmptyString(input.descriptor.version, 'target support evidence descriptor.version'),
|
|
501
|
+
status: nonEmptyString(input.descriptor.status, 'target support evidence descriptor.status'),
|
|
502
|
+
commands: normalizedDescriptorCommands,
|
|
503
|
+
descriptorHash: nonEmptyString(input.descriptor.descriptorHash, 'target support evidence descriptor.descriptorHash')
|
|
504
|
+
}),
|
|
505
|
+
project,
|
|
506
|
+
provenance: deepFreeze({
|
|
507
|
+
declarationVersion: declaration.version,
|
|
508
|
+
declarationHash: declaration.declarationHash,
|
|
509
|
+
parity: declaration.parity,
|
|
510
|
+
selectedProvider: declaration.provider,
|
|
511
|
+
selectedTarget: declaration.target,
|
|
512
|
+
targetId: declaration.targetId,
|
|
513
|
+
sourceHash: project.sourceHash,
|
|
514
|
+
graphHash: project.graphHash,
|
|
515
|
+
applicationPlanHash: project.applicationPlanHash,
|
|
516
|
+
packageProjectionHash: project.packageProjectionHash,
|
|
517
|
+
packageProductHash: project.packageProductHash,
|
|
518
|
+
nativeEligibilityVersion: project.nativeEligibility.version,
|
|
519
|
+
nativeEligibilityProjectionHash: project.nativeEligibility.projectionHash,
|
|
520
|
+
nativeEligibilityEligible: project.nativeEligibility.eligible,
|
|
521
|
+
automaticFallback: false
|
|
522
|
+
}),
|
|
523
|
+
policy: deepFreeze({
|
|
524
|
+
fullTargetSupportDefinesGeneralAvailability: declaration.availability.definition === 'full-target-support',
|
|
525
|
+
coreExecutionReadinessDoesNotImplyGeneralAvailability: true,
|
|
526
|
+
projectEligibilityDoesNotChangeSelectedTarget: true,
|
|
527
|
+
compileRemainsProviderNeutral: true,
|
|
528
|
+
staticEligibilityIndependentOfLoaderObservation: true,
|
|
529
|
+
failClosedOnUnknownCapabilityOrPackage: true,
|
|
530
|
+
automaticFallback: false
|
|
531
|
+
})
|
|
532
|
+
};
|
|
533
|
+
const evidenceHash = sha256Hex(stableStringify(semantic));
|
|
534
|
+
const observation = deepFreeze({
|
|
535
|
+
version: TARGET_SUPPORT_OBSERVATION_VERSION,
|
|
536
|
+
evidenceHash,
|
|
537
|
+
loader
|
|
538
|
+
});
|
|
539
|
+
return deepFreeze({
|
|
540
|
+
...semantic,
|
|
541
|
+
evidenceHash,
|
|
542
|
+
loader,
|
|
543
|
+
observationHash: sha256Hex(stableStringify(observation))
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
module.exports = Object.freeze({
|
|
548
|
+
TARGET_SUPPORT_DECLARATION_VERSION,
|
|
549
|
+
TARGET_AVAILABILITY_POLICY_VERSION,
|
|
550
|
+
TARGET_SUPPORT_EVIDENCE_VERSION,
|
|
551
|
+
TARGET_SUPPORT_OBSERVATION_VERSION,
|
|
552
|
+
TARGET_SUPPORT_KIND,
|
|
553
|
+
TARGET_SUPPORT_DECLARATION_KIND,
|
|
554
|
+
SUPPORT_STATUSES,
|
|
555
|
+
GATE_STATUSES,
|
|
556
|
+
AVAILABILITY_DEFINITIONS,
|
|
557
|
+
normalizeTargetSupportDeclaration,
|
|
558
|
+
normalizeTargetSupportEvidence
|
|
559
|
+
});
|