upstream-radar 0.43.5 → 0.45.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-zh-CN.md +50 -24
- package/README.md +64 -33
- package/dist/src/cli.js +155 -1
- package/dist/src/cli.js.map +1 -1
- package/dist/src/dsh-compatibility-ledger.d.ts +2 -0
- package/dist/src/dsh-compatibility-ledger.d.ts.map +1 -1
- package/dist/src/dsh-compatibility-ledger.js +5 -0
- package/dist/src/dsh-compatibility-ledger.js.map +1 -1
- package/dist/src/dsh-directory-feed.d.ts +15 -6
- package/dist/src/dsh-directory-feed.d.ts.map +1 -1
- package/dist/src/dsh-directory-feed.js +104 -28
- package/dist/src/dsh-directory-feed.js.map +1 -1
- package/dist/src/dsh-install-observation.d.ts +6 -0
- package/dist/src/dsh-install-observation.d.ts.map +1 -1
- package/dist/src/dsh-install-observation.js +2 -2
- package/dist/src/dsh-install-observation.js.map +1 -1
- package/dist/src/dsh-surface-agent-plan.d.ts +64 -0
- package/dist/src/dsh-surface-agent-plan.d.ts.map +1 -0
- package/dist/src/dsh-surface-agent-plan.js +267 -0
- package/dist/src/dsh-surface-agent-plan.js.map +1 -0
- package/dist/src/dsh-surface-observation.d.ts +162 -0
- package/dist/src/dsh-surface-observation.d.ts.map +1 -0
- package/dist/src/dsh-surface-observation.js +1033 -0
- package/dist/src/dsh-surface-observation.js.map +1 -0
- package/dist/src/dsh-surface.d.ts +142 -0
- package/dist/src/dsh-surface.d.ts.map +1 -0
- package/dist/src/dsh-surface.js +805 -0
- package/dist/src/dsh-surface.js.map +1 -0
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +3 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/upstream-observer.d.ts +4 -0
- package/dist/src/upstream-observer.d.ts.map +1 -1
- package/dist/src/upstream-observer.js +40 -4
- package/dist/src/upstream-observer.js.map +1 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/docs/README.zh-CN.md +19 -19
- package/package.json +2 -2
|
@@ -0,0 +1,805 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { parseDshCompatibilityLedger } from './dsh-compatibility-ledger.js';
|
|
3
|
+
import { parseDshHeadlessAgentPlans } from './dsh-headless-agent-plan.js';
|
|
4
|
+
import { parseDshSurfaceAgentPlans } from './dsh-surface-agent-plan.js';
|
|
5
|
+
import { extractPnpmRequiredDependencyBuilds } from './dsh-install-observation.js';
|
|
6
|
+
import { DSH_SURFACE_OBSERVATION_SCHEMA, } from './dsh-surface-observation.js';
|
|
7
|
+
import { parseNpmSpec } from './npm.js';
|
|
8
|
+
export const DSH_SURFACE_TARGETS_SCHEMA = 'upstream-radar.dsh-surface-targets/v1alpha1';
|
|
9
|
+
export const DSH_SURFACE_LEDGER_SCHEMA = 'upstream-radar.dsh-surface-ledger/v1alpha1';
|
|
10
|
+
export const DSH_SURFACE_IR_SCHEMA = 'upstream-radar.dsh-surface-ir/v1alpha1';
|
|
11
|
+
const EXACT_VERSION = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
|
|
12
|
+
const CASE_ID = /^[a-z0-9][a-z0-9._-]{0,63}$/;
|
|
13
|
+
const PROFILE_NAME = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$/;
|
|
14
|
+
const FINGERPRINT = /^sha256:[a-f0-9]{64}$/;
|
|
15
|
+
const BARE_SHA256 = /^[a-f0-9]{64}$/;
|
|
16
|
+
const RESULTS = new Set(['compatible', 'surface-incompatible', 'environment-unsupported', 'unknown']);
|
|
17
|
+
const STAGE_STATUS = new Set(['passed', 'failed', 'skipped']);
|
|
18
|
+
const DEFAULT_REFRESH_AFTER_HOURS = 7 * 24;
|
|
19
|
+
const MAX_TARGETS = 32;
|
|
20
|
+
const MAX_LEDGER_ENTRIES = 128;
|
|
21
|
+
const SURFACE_CONTRACT_REVISION = 'dsh-surface-contract/4';
|
|
22
|
+
function record(value, label) {
|
|
23
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
24
|
+
throw new Error(`${label} must be an object`);
|
|
25
|
+
return value;
|
|
26
|
+
}
|
|
27
|
+
function boundedString(value, label, maximum) {
|
|
28
|
+
if (typeof value !== 'string' || value.trim() === '' || value.length > maximum) {
|
|
29
|
+
throw new Error(`${label} must be a non-empty string no longer than ${maximum} characters`);
|
|
30
|
+
}
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
function caseId(value, label) {
|
|
34
|
+
const parsed = boundedString(value, label, 64);
|
|
35
|
+
if (!CASE_ID.test(parsed))
|
|
36
|
+
throw new Error(`${label} must be a short lowercase label`);
|
|
37
|
+
return parsed;
|
|
38
|
+
}
|
|
39
|
+
function profileName(value, label) {
|
|
40
|
+
const parsed = boundedString(value, label, 64);
|
|
41
|
+
if (!PROFILE_NAME.test(parsed))
|
|
42
|
+
throw new Error(`${label} must be a short safe DSH profile name`);
|
|
43
|
+
return parsed;
|
|
44
|
+
}
|
|
45
|
+
function fingerprint(value, label) {
|
|
46
|
+
const parsed = boundedString(value, label, 71);
|
|
47
|
+
if (!FINGERPRINT.test(parsed))
|
|
48
|
+
throw new Error(`${label} must be a sha256 fingerprint`);
|
|
49
|
+
return parsed;
|
|
50
|
+
}
|
|
51
|
+
function bareSha256(value, label) {
|
|
52
|
+
const parsed = boundedString(value, label, 64);
|
|
53
|
+
if (!BARE_SHA256.test(parsed))
|
|
54
|
+
throw new Error(`${label} must be a SHA-256 hex digest`);
|
|
55
|
+
return parsed;
|
|
56
|
+
}
|
|
57
|
+
function exactSpec(value, label) {
|
|
58
|
+
const parsed = parseNpmSpec(boundedString(value, label, 512));
|
|
59
|
+
if (!EXACT_VERSION.test(parsed.version))
|
|
60
|
+
throw new Error(`${label} must be an exact npm package coordinate`);
|
|
61
|
+
return `${parsed.name}@${parsed.version}`;
|
|
62
|
+
}
|
|
63
|
+
function exactVersion(value, label) {
|
|
64
|
+
const parsed = boundedString(value, label, 256);
|
|
65
|
+
if (!EXACT_VERSION.test(parsed))
|
|
66
|
+
throw new Error(`${label} must be an exact semantic version`);
|
|
67
|
+
return parsed;
|
|
68
|
+
}
|
|
69
|
+
function executionPlane(value, label) {
|
|
70
|
+
if (value !== 'web' && value !== 'tui')
|
|
71
|
+
throw new Error(`${label} must be web or tui`);
|
|
72
|
+
return value;
|
|
73
|
+
}
|
|
74
|
+
function nodeMajor(value, label) {
|
|
75
|
+
if (!Number.isSafeInteger(value) || value < 16 || value > 40) {
|
|
76
|
+
throw new Error(`${label} must be a supported Node.js major version`);
|
|
77
|
+
}
|
|
78
|
+
return value;
|
|
79
|
+
}
|
|
80
|
+
function isoDate(value, label) {
|
|
81
|
+
const parsed = boundedString(value, label, 64);
|
|
82
|
+
if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/.test(parsed) || Number.isNaN(Date.parse(parsed))) {
|
|
83
|
+
throw new Error(`${label} must be an ISO timestamp`);
|
|
84
|
+
}
|
|
85
|
+
return parsed;
|
|
86
|
+
}
|
|
87
|
+
export function parseDshSurfaceTargets(input) {
|
|
88
|
+
const root = record(input, 'DSH surface targets');
|
|
89
|
+
if (root.schema !== DSH_SURFACE_TARGETS_SCHEMA)
|
|
90
|
+
throw new Error(`DSH surface targets schema must be ${DSH_SURFACE_TARGETS_SCHEMA}`);
|
|
91
|
+
const refreshAfterHours = root.refreshAfterHours === undefined ? DEFAULT_REFRESH_AFTER_HOURS : Number(root.refreshAfterHours);
|
|
92
|
+
if (!Number.isSafeInteger(refreshAfterHours) || refreshAfterHours < 1 || refreshAfterHours > 90 * 24) {
|
|
93
|
+
throw new Error('DSH surface target refreshAfterHours must be an integer between 1 and 2160');
|
|
94
|
+
}
|
|
95
|
+
if (!Array.isArray(root.surfaces) || root.surfaces.length === 0 || root.surfaces.length > MAX_TARGETS) {
|
|
96
|
+
throw new Error(`DSH surface targets must contain between 1 and ${MAX_TARGETS} surfaces`);
|
|
97
|
+
}
|
|
98
|
+
const autoDiscoverRecord = root.autoDiscover === undefined
|
|
99
|
+
? undefined
|
|
100
|
+
: record(root.autoDiscover, 'DSH surface targets autoDiscover');
|
|
101
|
+
if (autoDiscoverRecord !== undefined && typeof autoDiscoverRecord.webClientGaps !== 'boolean') {
|
|
102
|
+
throw new Error('DSH surface targets autoDiscover.webClientGaps must be boolean');
|
|
103
|
+
}
|
|
104
|
+
const autoDiscover = autoDiscoverRecord === undefined
|
|
105
|
+
? undefined
|
|
106
|
+
: { webClientGaps: autoDiscoverRecord.webClientGaps };
|
|
107
|
+
const ids = new Set();
|
|
108
|
+
const pairs = new Set();
|
|
109
|
+
const surfaces = root.surfaces.map((value, index) => {
|
|
110
|
+
const item = record(value, `surfaces[${index}]`);
|
|
111
|
+
const id = caseId(item.id, `surfaces[${index}].id`);
|
|
112
|
+
if (ids.has(id))
|
|
113
|
+
throw new Error(`duplicate DSH surface target id: ${id}`);
|
|
114
|
+
ids.add(id);
|
|
115
|
+
const sourceCaseId = caseId(item.sourceCaseId, `surfaces[${index}].sourceCaseId`);
|
|
116
|
+
const plane = executionPlane(item.plane, `surfaces[${index}].plane`);
|
|
117
|
+
const profile = profileName(item.profile, `surfaces[${index}].profile`);
|
|
118
|
+
if (plane === 'web' && profile !== 'web')
|
|
119
|
+
throw new Error('a Web target must use the official web profile');
|
|
120
|
+
if (plane === 'tui' && profile === 'web')
|
|
121
|
+
throw new Error('a TUI target cannot use the reserved web profile');
|
|
122
|
+
const runtimeId = boundedString(item.runtimeId, `surfaces[${index}].runtimeId`, 214);
|
|
123
|
+
const reason = boundedString(item.reason, `surfaces[${index}].reason`, 2_048);
|
|
124
|
+
const pair = `${sourceCaseId}\u0000${plane}`;
|
|
125
|
+
if (pairs.has(pair))
|
|
126
|
+
throw new Error(`duplicate DSH surface plane for source case: ${sourceCaseId} ${plane}`);
|
|
127
|
+
pairs.add(pair);
|
|
128
|
+
return { id, sourceCaseId, plane, profile, runtimeId, reason };
|
|
129
|
+
});
|
|
130
|
+
surfaces.sort((left, right) => left.id.localeCompare(right.id));
|
|
131
|
+
return {
|
|
132
|
+
schema: DSH_SURFACE_TARGETS_SCHEMA,
|
|
133
|
+
refreshAfterHours,
|
|
134
|
+
...(autoDiscover === undefined ? {} : { autoDiscover }),
|
|
135
|
+
surfaces,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function digest(value) {
|
|
139
|
+
return `sha256:${createHash('sha256').update(JSON.stringify(value)).digest('hex')}`;
|
|
140
|
+
}
|
|
141
|
+
function isWebClientPackage(name) {
|
|
142
|
+
return name.startsWith('@deepseek-ai/dsh-client-');
|
|
143
|
+
}
|
|
144
|
+
function dshCompatibilityGapNames(entry) {
|
|
145
|
+
const runtimeGraph = entry.resolution?.runtimeGraph;
|
|
146
|
+
return [
|
|
147
|
+
...(runtimeGraph?.unresolvedDependencies ?? []).map(item => item.name),
|
|
148
|
+
...(runtimeGraph?.pluginPeerContracts?.issues ?? []).map(item => item.name),
|
|
149
|
+
];
|
|
150
|
+
}
|
|
151
|
+
/** Route any review cell with a DSH browser-client gap into Web observation. */
|
|
152
|
+
export function hasDshWebClientCoverageGap(entry) {
|
|
153
|
+
if (entry.result !== 'peer-contract-incompatible' && entry.result !== 'unknown')
|
|
154
|
+
return false;
|
|
155
|
+
return dshCompatibilityGapNames(entry).some(isWebClientPackage);
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* True only when the headless result is unresolved exclusively because the
|
|
159
|
+
* stock Web profile is absent. The intended Web plane may cover this gap; a
|
|
160
|
+
* host/runtime mismatch must remain visible even when browser boot succeeds.
|
|
161
|
+
*/
|
|
162
|
+
export function isDshWebClientOnlyCoverageGap(entry) {
|
|
163
|
+
if (entry.result !== 'peer-contract-incompatible' && entry.result !== 'unknown')
|
|
164
|
+
return false;
|
|
165
|
+
const names = dshCompatibilityGapNames(entry);
|
|
166
|
+
return names.length > 0 && names.every(isWebClientPackage);
|
|
167
|
+
}
|
|
168
|
+
function automaticWebTargetId(sourceCaseId, usedIds) {
|
|
169
|
+
const direct = `${sourceCaseId}-web`;
|
|
170
|
+
if (direct.length <= 64 && !usedIds.has(direct))
|
|
171
|
+
return direct;
|
|
172
|
+
for (let attempt = 0; attempt < 100; attempt += 1) {
|
|
173
|
+
const suffix = `-${createHash('sha256').update(`${sourceCaseId}\u0000web\u0000${attempt}`).digest('hex').slice(0, 8)}`;
|
|
174
|
+
const candidate = `${sourceCaseId.slice(0, 64 - suffix.length)}${suffix}`;
|
|
175
|
+
if (!usedIds.has(candidate))
|
|
176
|
+
return candidate;
|
|
177
|
+
}
|
|
178
|
+
throw new Error(`could not derive a unique automatic Web target id for ${sourceCaseId}`);
|
|
179
|
+
}
|
|
180
|
+
export function createDshSurfaceSourceFingerprint(entry) {
|
|
181
|
+
return digest({
|
|
182
|
+
caseId: entry.caseId,
|
|
183
|
+
plugin: entry.plugin,
|
|
184
|
+
dshVersion: entry.dshVersion,
|
|
185
|
+
nodeMajor: entry.runtime.nodeMajor,
|
|
186
|
+
artifactSha256: entry.artifact.sha256,
|
|
187
|
+
staticFingerprint: entry.staticFingerprint,
|
|
188
|
+
contractFingerprint: entry.contractFingerprint,
|
|
189
|
+
approvedDependencyBuilds: entry.approvedDependencyBuilds ?? [],
|
|
190
|
+
observerSchema: entry.observer.schema,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
function contractFingerprint(target, entry, runtimeId, approvedDependencyBuilds) {
|
|
194
|
+
return digest({
|
|
195
|
+
revision: SURFACE_CONTRACT_REVISION,
|
|
196
|
+
plane: target.plane,
|
|
197
|
+
profile: target.profile,
|
|
198
|
+
runtimeId,
|
|
199
|
+
nodeMajor: entry.runtime.nodeMajor,
|
|
200
|
+
approvedDependencyBuilds,
|
|
201
|
+
web: target.plane === 'web'
|
|
202
|
+
? { browser: 'chromium', root: '#root', manifest: '__DSH_BOOT__', bootHandoff: '[data-dsh-boot] removed after graph activation', externalRequests: 'blocked' }
|
|
203
|
+
: undefined,
|
|
204
|
+
tui: target.plane === 'tui'
|
|
205
|
+
? { terminal: 'xterm-256color', columns: 100, rows: 32, frame: 'ansi-and-printable', interaction: 'ctrl-l', shutdown: 'double-ctrl-c' }
|
|
206
|
+
: undefined,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
function desiredCase(target, source, approvedDependencyBuilds) {
|
|
210
|
+
// The headless result may legitimately remain unknown when the unresolved
|
|
211
|
+
// edges belong to the Web or TUI host that this target is about to provide.
|
|
212
|
+
// An explicit surface target needs only exact artifact provenance here; the
|
|
213
|
+
// plane observer independently reinstalls the package, verifies this digest,
|
|
214
|
+
// and establishes its own result.
|
|
215
|
+
if (source.artifact.sha256 === undefined || !BARE_SHA256.test(source.artifact.sha256))
|
|
216
|
+
return undefined;
|
|
217
|
+
if (source.result === 'build-approval-required' || source.result === 'runtime-incompatible'
|
|
218
|
+
|| source.result === 'install-failed' || source.result === 'load-failed')
|
|
219
|
+
return undefined;
|
|
220
|
+
if (source.result === 'unknown' && source.resolution?.runtimeGraph?.digest === undefined)
|
|
221
|
+
return undefined;
|
|
222
|
+
// DSH's browser manifest is keyed by the client package name. The Cordis
|
|
223
|
+
// patch row id is a different namespace and may remain stable while a
|
|
224
|
+
// package migrates. Deriving this value from the exact observed coordinate
|
|
225
|
+
// prevents a stale loader id from becoming a false incompatibility.
|
|
226
|
+
const runtimeId = target.plane === 'web' ? parseNpmSpec(source.plugin).name : target.runtimeId;
|
|
227
|
+
return {
|
|
228
|
+
id: target.id,
|
|
229
|
+
sourceCaseId: source.caseId,
|
|
230
|
+
plugin: source.plugin,
|
|
231
|
+
dshVersion: source.dshVersion,
|
|
232
|
+
nodeMajor: source.runtime.nodeMajor,
|
|
233
|
+
plane: target.plane,
|
|
234
|
+
profile: target.profile,
|
|
235
|
+
runtimeId,
|
|
236
|
+
artifactSha256: source.artifact.sha256,
|
|
237
|
+
allowedBuilds: [...approvedDependencyBuilds].sort().join(','),
|
|
238
|
+
sourceFingerprint: createDshSurfaceSourceFingerprint(source),
|
|
239
|
+
contractFingerprint: contractFingerprint(target, source, runtimeId, approvedDependencyBuilds),
|
|
240
|
+
reasons: [],
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
export function emptyDshSurfaceLedger() {
|
|
244
|
+
return { schema: DSH_SURFACE_LEDGER_SCHEMA, entries: [] };
|
|
245
|
+
}
|
|
246
|
+
function parseStage(value, label) {
|
|
247
|
+
const item = record(value, label);
|
|
248
|
+
if (!STAGE_STATUS.has(item.status))
|
|
249
|
+
throw new Error(`${label}.status is unsupported`);
|
|
250
|
+
const status = item.status;
|
|
251
|
+
const code = item.code === undefined || item.code === null
|
|
252
|
+
? item.code
|
|
253
|
+
: Number.isSafeInteger(item.code) ? item.code : (() => { throw new Error(`${label}.code must be an integer or null`); })();
|
|
254
|
+
const detail = item.detail === undefined ? undefined : boundedString(item.detail, `${label}.detail`, 2_048);
|
|
255
|
+
return {
|
|
256
|
+
status,
|
|
257
|
+
...(code === undefined ? {} : { code }),
|
|
258
|
+
...(detail === undefined ? {} : { detail }),
|
|
259
|
+
...(item.timedOut === true ? { timedOut: true } : {}),
|
|
260
|
+
...(item.outputExceeded === true ? { outputExceeded: true } : {}),
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
function parseStages(value, label) {
|
|
264
|
+
const item = record(value, label);
|
|
265
|
+
return {
|
|
266
|
+
runtime: parseStage(item.runtime, `${label}.runtime`),
|
|
267
|
+
artifact: parseStage(item.artifact, `${label}.artifact`),
|
|
268
|
+
profile: parseStage(item.profile, `${label}.profile`),
|
|
269
|
+
install: parseStage(item.install, `${label}.install`),
|
|
270
|
+
registration: parseStage(item.registration, `${label}.registration`),
|
|
271
|
+
host: parseStage(item.host, `${label}.host`),
|
|
272
|
+
surface: parseStage(item.surface, `${label}.surface`),
|
|
273
|
+
interaction: parseStage(item.interaction, `${label}.interaction`),
|
|
274
|
+
shutdown: parseStage(item.shutdown, `${label}.shutdown`),
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
function stringArray(value, label) {
|
|
278
|
+
if (!Array.isArray(value) || value.length > 32)
|
|
279
|
+
throw new Error(`${label} must be an array of at most 32 strings`);
|
|
280
|
+
return value.map((item, index) => boundedString(item, `${label}[${index}]`, 512));
|
|
281
|
+
}
|
|
282
|
+
function packageNames(value, label) {
|
|
283
|
+
if (!Array.isArray(value) || value.length > 32)
|
|
284
|
+
throw new Error(`${label} must be an array of at most 32 package names`);
|
|
285
|
+
const names = value.map((item, index) => {
|
|
286
|
+
const name = boundedString(item, `${label}[${index}]`, 214);
|
|
287
|
+
if (!/^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/.test(name))
|
|
288
|
+
throw new Error(`${label}[${index}] is not a package name`);
|
|
289
|
+
return name;
|
|
290
|
+
});
|
|
291
|
+
if (new Set(names).size !== names.length)
|
|
292
|
+
throw new Error(`${label} contains duplicate package names`);
|
|
293
|
+
return names.sort();
|
|
294
|
+
}
|
|
295
|
+
function optionalInteger(value, label) {
|
|
296
|
+
if (value === undefined)
|
|
297
|
+
return undefined;
|
|
298
|
+
if (!Number.isSafeInteger(value) || value < 0 || value > 1_000_000_000)
|
|
299
|
+
throw new Error(`${label} must be a bounded non-negative integer`);
|
|
300
|
+
return value;
|
|
301
|
+
}
|
|
302
|
+
function parseEvidence(value, plane, label) {
|
|
303
|
+
const item = record(value, label);
|
|
304
|
+
if (item.plane !== plane)
|
|
305
|
+
throw new Error(`${label}.plane does not match the report plane`);
|
|
306
|
+
if (plane === 'web') {
|
|
307
|
+
const booleanKeys = ['rootMounted', 'bootManifestPresent', 'pluginEntryPresent', 'pluginMaterialized'];
|
|
308
|
+
for (const key of booleanKeys)
|
|
309
|
+
if (typeof item[key] !== 'boolean')
|
|
310
|
+
throw new Error(`${label}.${key} must be boolean`);
|
|
311
|
+
return {
|
|
312
|
+
plane: 'web',
|
|
313
|
+
url: boundedString(item.url, `${label}.url`, 2_048),
|
|
314
|
+
...(optionalInteger(item.httpStatus, `${label}.httpStatus`) === undefined ? {} : { httpStatus: optionalInteger(item.httpStatus, `${label}.httpStatus`) }),
|
|
315
|
+
...(item.title === undefined ? {} : { title: boundedString(item.title, `${label}.title`, 256) }),
|
|
316
|
+
rootMounted: item.rootMounted,
|
|
317
|
+
bootManifestPresent: item.bootManifestPresent,
|
|
318
|
+
...(item.bootEntryIds === undefined ? {} : { bootEntryIds: stringArray(item.bootEntryIds, `${label}.bootEntryIds`) }),
|
|
319
|
+
pluginEntryPresent: item.pluginEntryPresent,
|
|
320
|
+
...(item.pluginBundleUrl === undefined ? {} : { pluginBundleUrl: boundedString(item.pluginBundleUrl, `${label}.pluginBundleUrl`, 2_048) }),
|
|
321
|
+
...(optionalInteger(item.pluginBundleStatus, `${label}.pluginBundleStatus`) === undefined ? {} : { pluginBundleStatus: optionalInteger(item.pluginBundleStatus, `${label}.pluginBundleStatus`) }),
|
|
322
|
+
applicationMounted: typeof item.applicationMounted === 'boolean'
|
|
323
|
+
? item.applicationMounted
|
|
324
|
+
: item.pluginMaterialized,
|
|
325
|
+
pluginMaterialized: item.pluginMaterialized,
|
|
326
|
+
...(item.bootFailureText === undefined ? {} : { bootFailureText: boundedString(item.bootFailureText, `${label}.bootFailureText`, 512) }),
|
|
327
|
+
consoleErrors: stringArray(item.consoleErrors, `${label}.consoleErrors`),
|
|
328
|
+
pageErrors: stringArray(item.pageErrors, `${label}.pageErrors`),
|
|
329
|
+
failedRequests: stringArray(item.failedRequests, `${label}.failedRequests`),
|
|
330
|
+
...(item.blockedExternalRequests === undefined ? {} : { blockedExternalRequests: stringArray(item.blockedExternalRequests, `${label}.blockedExternalRequests`) }),
|
|
331
|
+
...(item.screenshot === undefined ? {} : { screenshot: boundedString(item.screenshot, `${label}.screenshot`, 256) }),
|
|
332
|
+
...(item.trace === undefined ? {} : { trace: boundedString(item.trace, `${label}.trace`, 256) }),
|
|
333
|
+
...(item.hostLog === undefined ? {} : { hostLog: boundedString(item.hostLog, `${label}.hostLog`, 256) }),
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
for (const key of ['frameObserved', 'inputSent', 'exitedAfterShutdown', 'truncated']) {
|
|
337
|
+
if (typeof item[key] !== 'boolean')
|
|
338
|
+
throw new Error(`${label}.${key} must be boolean`);
|
|
339
|
+
}
|
|
340
|
+
if (item.terminal !== 'xterm-256color')
|
|
341
|
+
throw new Error(`${label}.terminal must be xterm-256color`);
|
|
342
|
+
return {
|
|
343
|
+
plane: 'tui',
|
|
344
|
+
terminal: 'xterm-256color',
|
|
345
|
+
columns: optionalInteger(item.columns, `${label}.columns`) ?? 0,
|
|
346
|
+
rows: optionalInteger(item.rows, `${label}.rows`) ?? 0,
|
|
347
|
+
frameObserved: item.frameObserved,
|
|
348
|
+
inputSent: item.inputSent,
|
|
349
|
+
exitedAfterShutdown: item.exitedAfterShutdown,
|
|
350
|
+
...(optionalInteger(item.exitCode, `${label}.exitCode`) === undefined ? {} : { exitCode: optionalInteger(item.exitCode, `${label}.exitCode`) }),
|
|
351
|
+
...(optionalInteger(item.signal, `${label}.signal`) === undefined ? {} : { signal: optionalInteger(item.signal, `${label}.signal`) }),
|
|
352
|
+
...(item.transcript === undefined ? {} : { transcript: boundedString(item.transcript, `${label}.transcript`, 256) }),
|
|
353
|
+
normalizedFrame: typeof item.normalizedFrame === 'string' && item.normalizedFrame.length <= 8_192 ? item.normalizedFrame : (() => { throw new Error(`${label}.normalizedFrame must be a bounded string`); })(),
|
|
354
|
+
capturedBytes: optionalInteger(item.capturedBytes, `${label}.capturedBytes`) ?? 0,
|
|
355
|
+
truncated: item.truncated,
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
function parseRuntime(value, label) {
|
|
359
|
+
const item = record(value, label);
|
|
360
|
+
return {
|
|
361
|
+
nodeMajor: nodeMajor(item.nodeMajor, `${label}.nodeMajor`),
|
|
362
|
+
nodeVersion: exactVersion(item.nodeVersion, `${label}.nodeVersion`),
|
|
363
|
+
platform: boundedString(item.platform, `${label}.platform`, 64),
|
|
364
|
+
architecture: boundedString(item.architecture, `${label}.architecture`, 64),
|
|
365
|
+
...(item.pnpmVersion === undefined ? {} : { pnpmVersion: exactVersion(item.pnpmVersion, `${label}.pnpmVersion`) }),
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
function parseReport(input) {
|
|
369
|
+
const root = record(input, 'DSH surface report');
|
|
370
|
+
if (root.schema !== DSH_SURFACE_OBSERVATION_SCHEMA || root.probe !== 'dsh-surface' || root.scope !== 'surface-runtime-behavior') {
|
|
371
|
+
throw new Error(`report schema must be ${DSH_SURFACE_OBSERVATION_SCHEMA}`);
|
|
372
|
+
}
|
|
373
|
+
const tool = record(root.tool, 'report.tool');
|
|
374
|
+
if (tool.name !== 'upstream-radar')
|
|
375
|
+
throw new Error('report.tool.name must be upstream-radar');
|
|
376
|
+
const plane = executionPlane(root.plane, 'report.plane');
|
|
377
|
+
const artifact = record(root.artifact, 'report.artifact');
|
|
378
|
+
const result = boundedString(root.result, 'report.result', 64);
|
|
379
|
+
if (!RESULTS.has(result))
|
|
380
|
+
throw new Error('report.result is unsupported');
|
|
381
|
+
const boundary = record(root.boundary, 'report.boundary');
|
|
382
|
+
const plugin = exactSpec(root.plugin, 'report.plugin');
|
|
383
|
+
const stages = parseStages(root.stages, 'report.stages');
|
|
384
|
+
const reason = boundedString(root.reason, 'report.reason', 2_048);
|
|
385
|
+
const requiredDependencyBuilds = boundary.requiredDependencyBuilds === undefined
|
|
386
|
+
? (result === 'environment-unsupported'
|
|
387
|
+
? extractPnpmRequiredDependencyBuilds(`${stages.install.detail ?? ''}\n${reason}`, parseNpmSpec(plugin).name)
|
|
388
|
+
: [])
|
|
389
|
+
: packageNames(boundary.requiredDependencyBuilds, 'report.boundary.requiredDependencyBuilds');
|
|
390
|
+
if (requiredDependencyBuilds.length > 0 && result !== 'environment-unsupported') {
|
|
391
|
+
throw new Error('report may require dependency builds only for environment-unsupported');
|
|
392
|
+
}
|
|
393
|
+
return {
|
|
394
|
+
schema: DSH_SURFACE_OBSERVATION_SCHEMA,
|
|
395
|
+
tool: { name: 'upstream-radar', version: boundedString(tool.version, 'report.tool.version', 64) },
|
|
396
|
+
probe: 'dsh-surface',
|
|
397
|
+
scope: 'surface-runtime-behavior',
|
|
398
|
+
startedAt: isoDate(root.startedAt, 'report.startedAt'),
|
|
399
|
+
completedAt: isoDate(root.completedAt, 'report.completedAt'),
|
|
400
|
+
caseId: caseId(root.caseId, 'report.caseId'),
|
|
401
|
+
sourceCaseId: caseId(root.sourceCaseId, 'report.sourceCaseId'),
|
|
402
|
+
sourceFingerprint: fingerprint(root.sourceFingerprint, 'report.sourceFingerprint'),
|
|
403
|
+
contractFingerprint: fingerprint(root.contractFingerprint, 'report.contractFingerprint'),
|
|
404
|
+
plugin,
|
|
405
|
+
dshVersion: exactVersion(root.dshVersion, 'report.dshVersion'),
|
|
406
|
+
plane,
|
|
407
|
+
profile: profileName(root.profile, 'report.profile'),
|
|
408
|
+
runtimeId: boundedString(root.runtimeId, 'report.runtimeId', 214),
|
|
409
|
+
runtime: parseRuntime(root.runtime, 'report.runtime'),
|
|
410
|
+
artifact: {
|
|
411
|
+
...(artifact.sha256 === undefined ? {} : { sha256: bareSha256(artifact.sha256, 'report.artifact.sha256') }),
|
|
412
|
+
...(optionalInteger(artifact.bytes, 'report.artifact.bytes') === undefined ? {} : { bytes: optionalInteger(artifact.bytes, 'report.artifact.bytes') }),
|
|
413
|
+
...(artifact.integrity === undefined ? {} : { integrity: boundedString(artifact.integrity, 'report.artifact.integrity', 1_024) }),
|
|
414
|
+
},
|
|
415
|
+
stages,
|
|
416
|
+
evidence: parseEvidence(root.evidence, plane, 'report.evidence'),
|
|
417
|
+
result,
|
|
418
|
+
reason,
|
|
419
|
+
boundary: {
|
|
420
|
+
isolationProviderClaim: (() => {
|
|
421
|
+
const value = boundary.isolationProviderClaim;
|
|
422
|
+
if (value !== 'github-actions-hosted-runner' && value !== 'firecracker' && value !== 'other')
|
|
423
|
+
throw new Error('report.boundary.isolationProviderClaim is unsupported');
|
|
424
|
+
return value;
|
|
425
|
+
})(),
|
|
426
|
+
isolationVerifiedByRadar: false,
|
|
427
|
+
disposableEnvironmentRequired: true,
|
|
428
|
+
inheritedHostSecrets: false,
|
|
429
|
+
externalBrowserRequestsBlocked: boundary.externalBrowserRequestsBlocked === true,
|
|
430
|
+
approvedDependencyBuilds: packageNames(boundary.approvedDependencyBuilds ?? [], 'report.boundary.approvedDependencyBuilds'),
|
|
431
|
+
...(requiredDependencyBuilds.length === 0 ? {} : { requiredDependencyBuilds }),
|
|
432
|
+
note: boundedString(boundary.note, 'report.boundary.note', 2_048),
|
|
433
|
+
},
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
export function parseDshSurfaceLedger(input) {
|
|
437
|
+
const root = record(input, 'DSH surface ledger');
|
|
438
|
+
if (root.schema !== DSH_SURFACE_LEDGER_SCHEMA)
|
|
439
|
+
throw new Error(`DSH surface ledger schema must be ${DSH_SURFACE_LEDGER_SCHEMA}`);
|
|
440
|
+
if (!Array.isArray(root.entries) || root.entries.length > MAX_LEDGER_ENTRIES)
|
|
441
|
+
throw new Error(`DSH surface ledger must contain at most ${MAX_LEDGER_ENTRIES} entries`);
|
|
442
|
+
const ids = new Set();
|
|
443
|
+
const entries = root.entries.map((value, index) => {
|
|
444
|
+
const item = record(value, `entries[${index}]`);
|
|
445
|
+
const parsedCaseId = caseId(item.caseId, `entries[${index}].caseId`);
|
|
446
|
+
if (ids.has(parsedCaseId))
|
|
447
|
+
throw new Error(`duplicate DSH surface ledger case: ${parsedCaseId}`);
|
|
448
|
+
ids.add(parsedCaseId);
|
|
449
|
+
const plane = executionPlane(item.plane, `entries[${index}].plane`);
|
|
450
|
+
const artifact = record(item.artifact, `entries[${index}].artifact`);
|
|
451
|
+
const observer = record(item.observer, `entries[${index}].observer`);
|
|
452
|
+
if (observer.schema !== DSH_SURFACE_OBSERVATION_SCHEMA)
|
|
453
|
+
throw new Error(`entries[${index}].observer.schema is unsupported`);
|
|
454
|
+
const result = boundedString(item.result, `entries[${index}].result`, 64);
|
|
455
|
+
if (!RESULTS.has(result))
|
|
456
|
+
throw new Error(`entries[${index}].result is unsupported`);
|
|
457
|
+
const plugin = exactSpec(item.plugin, `entries[${index}].plugin`);
|
|
458
|
+
const stages = parseStages(item.stages, `entries[${index}].stages`);
|
|
459
|
+
const reason = boundedString(item.reason, `entries[${index}].reason`, 2_048);
|
|
460
|
+
const requiredDependencyBuilds = item.requiredDependencyBuilds === undefined
|
|
461
|
+
? (result === 'environment-unsupported'
|
|
462
|
+
? extractPnpmRequiredDependencyBuilds(`${stages.install.detail ?? ''}\n${reason}`, parseNpmSpec(plugin).name)
|
|
463
|
+
: [])
|
|
464
|
+
: packageNames(item.requiredDependencyBuilds, `entries[${index}].requiredDependencyBuilds`);
|
|
465
|
+
if (requiredDependencyBuilds.length > 0 && result !== 'environment-unsupported') {
|
|
466
|
+
throw new Error(`entries[${index}] may require dependency builds only for environment-unsupported`);
|
|
467
|
+
}
|
|
468
|
+
return {
|
|
469
|
+
caseId: parsedCaseId,
|
|
470
|
+
sourceCaseId: caseId(item.sourceCaseId, `entries[${index}].sourceCaseId`),
|
|
471
|
+
plugin,
|
|
472
|
+
dshVersion: exactVersion(item.dshVersion, `entries[${index}].dshVersion`),
|
|
473
|
+
plane,
|
|
474
|
+
profile: profileName(item.profile, `entries[${index}].profile`),
|
|
475
|
+
runtimeId: boundedString(item.runtimeId, `entries[${index}].runtimeId`, 214),
|
|
476
|
+
...(item.approvedDependencyBuilds === undefined ? {} : { approvedDependencyBuilds: packageNames(item.approvedDependencyBuilds, `entries[${index}].approvedDependencyBuilds`) }),
|
|
477
|
+
...(requiredDependencyBuilds.length === 0 ? {} : { requiredDependencyBuilds }),
|
|
478
|
+
sourceFingerprint: fingerprint(item.sourceFingerprint, `entries[${index}].sourceFingerprint`),
|
|
479
|
+
contractFingerprint: fingerprint(item.contractFingerprint, `entries[${index}].contractFingerprint`),
|
|
480
|
+
observedAt: isoDate(item.observedAt, `entries[${index}].observedAt`),
|
|
481
|
+
runtime: parseRuntime(item.runtime, `entries[${index}].runtime`),
|
|
482
|
+
artifact: {
|
|
483
|
+
sha256: bareSha256(artifact.sha256, `entries[${index}].artifact.sha256`),
|
|
484
|
+
...(optionalInteger(artifact.bytes, `entries[${index}].artifact.bytes`) === undefined ? {} : { bytes: optionalInteger(artifact.bytes, `entries[${index}].artifact.bytes`) }),
|
|
485
|
+
...(artifact.integrity === undefined ? {} : { integrity: boundedString(artifact.integrity, `entries[${index}].artifact.integrity`, 1_024) }),
|
|
486
|
+
},
|
|
487
|
+
stages,
|
|
488
|
+
evidence: parseEvidence(item.evidence, plane, `entries[${index}].evidence`),
|
|
489
|
+
result,
|
|
490
|
+
reason,
|
|
491
|
+
observer: {
|
|
492
|
+
schema: DSH_SURFACE_OBSERVATION_SCHEMA,
|
|
493
|
+
version: boundedString(observer.version, `entries[${index}].observer.version`, 64),
|
|
494
|
+
},
|
|
495
|
+
};
|
|
496
|
+
});
|
|
497
|
+
entries.sort((left, right) => left.caseId.localeCompare(right.caseId));
|
|
498
|
+
return { schema: DSH_SURFACE_LEDGER_SCHEMA, entries };
|
|
499
|
+
}
|
|
500
|
+
export function buildDshSurfacePlan(targetsInput, sourceLedgerInput, surfaceLedgerInput, now = new Date(), agentPlansInput, surfaceAgentPlansInput) {
|
|
501
|
+
const targets = parseDshSurfaceTargets(targetsInput);
|
|
502
|
+
const sourceLedger = parseDshCompatibilityLedger(sourceLedgerInput);
|
|
503
|
+
const surfaceLedger = parseDshSurfaceLedger(surfaceLedgerInput);
|
|
504
|
+
const agentPlans = agentPlansInput === undefined
|
|
505
|
+
? undefined
|
|
506
|
+
: parseDshHeadlessAgentPlans(agentPlansInput);
|
|
507
|
+
const surfaceAgentPlans = surfaceAgentPlansInput === undefined
|
|
508
|
+
? undefined
|
|
509
|
+
: parseDshSurfaceAgentPlans(surfaceAgentPlansInput);
|
|
510
|
+
const sourceById = new Map(sourceLedger.entries.map(entry => [entry.caseId, entry]));
|
|
511
|
+
const currentById = new Map(surfaceLedger.entries.map(entry => [entry.caseId, entry]));
|
|
512
|
+
const desiredTargets = [...targets.surfaces];
|
|
513
|
+
const usedTargetIds = new Set(desiredTargets.map(target => target.id));
|
|
514
|
+
const usedSurfacePairs = new Set(desiredTargets.map(target => `${target.sourceCaseId}\u0000${target.plane}`));
|
|
515
|
+
const include = [];
|
|
516
|
+
const blocked = [];
|
|
517
|
+
if (targets.autoDiscover?.webClientGaps === true) {
|
|
518
|
+
for (const source of [...sourceLedger.entries].sort((left, right) => left.caseId.localeCompare(right.caseId))) {
|
|
519
|
+
const pair = `${source.caseId}\u0000web`;
|
|
520
|
+
if (usedSurfacePairs.has(pair) || !hasDshWebClientCoverageGap(source))
|
|
521
|
+
continue;
|
|
522
|
+
const id = automaticWebTargetId(source.caseId, usedTargetIds);
|
|
523
|
+
if (desiredTargets.length >= MAX_TARGETS) {
|
|
524
|
+
blocked.push({ id, reason: `automatic Web observation skipped because the ${MAX_TARGETS}-surface run budget is full` });
|
|
525
|
+
continue;
|
|
526
|
+
}
|
|
527
|
+
const runtimeId = parseNpmSpec(source.plugin).name;
|
|
528
|
+
desiredTargets.push({
|
|
529
|
+
id,
|
|
530
|
+
sourceCaseId: source.caseId,
|
|
531
|
+
plane: 'web',
|
|
532
|
+
profile: 'web',
|
|
533
|
+
runtimeId,
|
|
534
|
+
reason: 'Headless evidence contains only DSH browser-client dependency gaps; observe the exact artifact in the stock Web plane.',
|
|
535
|
+
});
|
|
536
|
+
usedTargetIds.add(id);
|
|
537
|
+
usedSurfacePairs.add(pair);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
const staleBefore = now.getTime() - targets.refreshAfterHours * 60 * 60 * 1_000;
|
|
541
|
+
for (const target of desiredTargets) {
|
|
542
|
+
const source = sourceById.get(target.sourceCaseId);
|
|
543
|
+
if (source === undefined) {
|
|
544
|
+
blocked.push({ id: target.id, reason: `source compatibility case ${target.sourceCaseId} is missing` });
|
|
545
|
+
continue;
|
|
546
|
+
}
|
|
547
|
+
const retainedAgentPlan = agentPlans?.entries.find(plan => (plan.caseId === source.caseId
|
|
548
|
+
&& plan.targetId === source.targetId
|
|
549
|
+
&& plan.plugin === source.plugin
|
|
550
|
+
&& plan.dshVersion === source.dshVersion
|
|
551
|
+
&& plan.nodeMajor === source.runtime.nodeMajor
|
|
552
|
+
&& plan.artifactSha256 !== undefined
|
|
553
|
+
&& plan.artifactSha256 === source.artifact.sha256));
|
|
554
|
+
const expectedSourceFingerprint = createDshSurfaceSourceFingerprint(source);
|
|
555
|
+
const retainedSurfaceAgentPlan = surfaceAgentPlans?.entries.find(plan => (plan.caseId === target.id
|
|
556
|
+
&& plan.sourceCaseId === source.caseId
|
|
557
|
+
&& plan.plugin === source.plugin
|
|
558
|
+
&& plan.dshVersion === source.dshVersion
|
|
559
|
+
&& plan.nodeMajor === source.runtime.nodeMajor
|
|
560
|
+
&& plan.plane === target.plane
|
|
561
|
+
&& plan.profile === target.profile
|
|
562
|
+
&& plan.sourceFingerprint === expectedSourceFingerprint
|
|
563
|
+
&& plan.artifactSha256 === source.artifact.sha256));
|
|
564
|
+
const approvedDependencyBuilds = [...new Set([
|
|
565
|
+
...(source.approvedDependencyBuilds ?? []),
|
|
566
|
+
...(retainedAgentPlan?.approvedBuilds ?? []),
|
|
567
|
+
...(retainedSurfaceAgentPlan?.approvedBuilds ?? []),
|
|
568
|
+
])].sort();
|
|
569
|
+
const desired = desiredCase(target, source, approvedDependencyBuilds);
|
|
570
|
+
if (desired === undefined) {
|
|
571
|
+
const hasExactArtifact = source.artifact.sha256 !== undefined && BARE_SHA256.test(source.artifact.sha256);
|
|
572
|
+
blocked.push({
|
|
573
|
+
id: target.id,
|
|
574
|
+
reason: hasExactArtifact
|
|
575
|
+
? `source compatibility case ${target.sourceCaseId} is ${source.result}; its headless environment must be resolved before entering ${target.plane}`
|
|
576
|
+
: `source compatibility case ${target.sourceCaseId} has no exact artifact bytes`,
|
|
577
|
+
});
|
|
578
|
+
continue;
|
|
579
|
+
}
|
|
580
|
+
const current = currentById.get(target.id);
|
|
581
|
+
if (current === undefined)
|
|
582
|
+
desired.reasons.push('missing-evidence');
|
|
583
|
+
else {
|
|
584
|
+
if (current.sourceFingerprint !== desired.sourceFingerprint || current.artifact.sha256 !== desired.artifactSha256
|
|
585
|
+
|| current.plugin !== desired.plugin || current.dshVersion !== desired.dshVersion || current.runtime.nodeMajor !== desired.nodeMajor) {
|
|
586
|
+
desired.reasons.push('source-evidence-changed');
|
|
587
|
+
}
|
|
588
|
+
if (current.contractFingerprint !== desired.contractFingerprint || current.plane !== desired.plane
|
|
589
|
+
|| current.profile !== desired.profile || current.runtimeId !== desired.runtimeId) {
|
|
590
|
+
desired.reasons.push('surface-contract-changed');
|
|
591
|
+
}
|
|
592
|
+
if (Date.parse(current.observedAt) < staleBefore)
|
|
593
|
+
desired.reasons.push('stale-evidence');
|
|
594
|
+
}
|
|
595
|
+
if (desired.reasons.length > 0)
|
|
596
|
+
include.push(desired);
|
|
597
|
+
}
|
|
598
|
+
include.sort((left, right) => left.id.localeCompare(right.id));
|
|
599
|
+
blocked.sort((left, right) => left.id.localeCompare(right.id));
|
|
600
|
+
return {
|
|
601
|
+
run: include.length > 0,
|
|
602
|
+
matrix: { include },
|
|
603
|
+
blocked,
|
|
604
|
+
reason: include.length > 0
|
|
605
|
+
? `${include.length} execution-plane observation(s) need fresh exact evidence`
|
|
606
|
+
: blocked.length > 0
|
|
607
|
+
? `no runnable execution-plane observations; ${blocked.length} source evidence gap(s) remain`
|
|
608
|
+
: 'all configured execution planes have fresh exact evidence',
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
function mismatch(expected, report) {
|
|
612
|
+
const comparisons = [
|
|
613
|
+
['case id', expected.id, report.caseId],
|
|
614
|
+
['source case id', expected.sourceCaseId, report.sourceCaseId],
|
|
615
|
+
['plugin', expected.plugin, report.plugin],
|
|
616
|
+
['DSH version', expected.dshVersion, report.dshVersion],
|
|
617
|
+
['Node major', expected.nodeMajor, report.runtime.nodeMajor],
|
|
618
|
+
['plane', expected.plane, report.plane],
|
|
619
|
+
['profile', expected.profile, report.profile],
|
|
620
|
+
['runtime id', expected.runtimeId, report.runtimeId],
|
|
621
|
+
['approved dependency builds', expected.allowedBuilds, report.boundary.approvedDependencyBuilds.join(',')],
|
|
622
|
+
['artifact SHA-256', expected.artifactSha256, report.artifact.sha256],
|
|
623
|
+
['source fingerprint', expected.sourceFingerprint, report.sourceFingerprint],
|
|
624
|
+
['contract fingerprint', expected.contractFingerprint, report.contractFingerprint],
|
|
625
|
+
];
|
|
626
|
+
const changed = comparisons.find(([, left, right]) => left !== right);
|
|
627
|
+
return changed === undefined ? undefined : `${changed[0]} mismatch: expected ${String(changed[1])}, observed ${String(changed[2])}`;
|
|
628
|
+
}
|
|
629
|
+
function transition(previous, current) {
|
|
630
|
+
const infrastructure = current.result === 'environment-unsupported' || current.result === 'unknown';
|
|
631
|
+
let status;
|
|
632
|
+
if (previous === undefined) {
|
|
633
|
+
status = current.result === 'compatible' ? 'compatible' : infrastructure ? 'new-infrastructure-gap' : 'new-incompatibility';
|
|
634
|
+
}
|
|
635
|
+
else if (current.result === 'compatible') {
|
|
636
|
+
status = previous.result === 'compatible' ? 'compatible' : 'resolved-incompatibility';
|
|
637
|
+
}
|
|
638
|
+
else if (infrastructure) {
|
|
639
|
+
status = previous.result === current.result && previous.reason === current.reason ? 'persisting-infrastructure-gap' : 'new-infrastructure-gap';
|
|
640
|
+
}
|
|
641
|
+
else {
|
|
642
|
+
status = previous.result === current.result && previous.reason === current.reason ? 'persisting-incompatibility' : 'changed-incompatibility';
|
|
643
|
+
}
|
|
644
|
+
return {
|
|
645
|
+
caseId: current.caseId,
|
|
646
|
+
status,
|
|
647
|
+
result: current.result,
|
|
648
|
+
reason: current.reason,
|
|
649
|
+
...(previous === undefined ? {} : { previousResult: previous.result }),
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
export function mergeDshSurfaceLedger(input) {
|
|
653
|
+
const ledger = parseDshSurfaceLedger(input.ledger);
|
|
654
|
+
if (input.expected.length > MAX_TARGETS)
|
|
655
|
+
throw new Error(`surface reconciliation accepts at most ${MAX_TARGETS} expected cases`);
|
|
656
|
+
if (input.reports.length > MAX_TARGETS)
|
|
657
|
+
throw new Error(`surface reconciliation accepts at most ${MAX_TARGETS} reports`);
|
|
658
|
+
const expectedById = new Map();
|
|
659
|
+
for (const value of input.expected) {
|
|
660
|
+
const id = caseId(value.id, 'expected.id');
|
|
661
|
+
if (expectedById.has(id))
|
|
662
|
+
throw new Error(`duplicate expected DSH surface case: ${id}`);
|
|
663
|
+
const allowedBuilds = value.allowedBuilds === ''
|
|
664
|
+
? []
|
|
665
|
+
: packageNames(value.allowedBuilds.split(','), `expected ${id}.allowedBuilds`);
|
|
666
|
+
expectedById.set(id, {
|
|
667
|
+
id,
|
|
668
|
+
sourceCaseId: caseId(value.sourceCaseId, `expected ${id}.sourceCaseId`),
|
|
669
|
+
plugin: exactSpec(value.plugin, `expected ${id}.plugin`),
|
|
670
|
+
dshVersion: exactVersion(value.dshVersion, `expected ${id}.dshVersion`),
|
|
671
|
+
nodeMajor: nodeMajor(value.nodeMajor, `expected ${id}.nodeMajor`),
|
|
672
|
+
plane: executionPlane(value.plane, `expected ${id}.plane`),
|
|
673
|
+
profile: profileName(value.profile, `expected ${id}.profile`),
|
|
674
|
+
runtimeId: boundedString(value.runtimeId, `expected ${id}.runtimeId`, 214),
|
|
675
|
+
artifactSha256: bareSha256(value.artifactSha256, `expected ${id}.artifactSha256`),
|
|
676
|
+
allowedBuilds: allowedBuilds.join(','),
|
|
677
|
+
sourceFingerprint: fingerprint(value.sourceFingerprint, `expected ${id}.sourceFingerprint`),
|
|
678
|
+
contractFingerprint: fingerprint(value.contractFingerprint, `expected ${id}.contractFingerprint`),
|
|
679
|
+
reasons: Array.isArray(value.reasons) ? value.reasons.map((reason, index) => boundedString(reason, `expected ${id}.reasons[${index}]`, 128)) : [],
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
const previousById = new Map(ledger.entries.map(entry => [entry.caseId, entry]));
|
|
683
|
+
const accepted = new Map();
|
|
684
|
+
const rejectedReports = [];
|
|
685
|
+
for (const inputReport of input.reports) {
|
|
686
|
+
let report;
|
|
687
|
+
try {
|
|
688
|
+
report = parseReport(inputReport);
|
|
689
|
+
}
|
|
690
|
+
catch (error) {
|
|
691
|
+
rejectedReports.push(`invalid report: ${error instanceof Error ? error.message : String(error)}`);
|
|
692
|
+
continue;
|
|
693
|
+
}
|
|
694
|
+
const expected = expectedById.get(report.caseId);
|
|
695
|
+
if (expected === undefined) {
|
|
696
|
+
rejectedReports.push(`${report.caseId}: report was not scheduled`);
|
|
697
|
+
continue;
|
|
698
|
+
}
|
|
699
|
+
if (accepted.has(report.caseId)) {
|
|
700
|
+
rejectedReports.push(`${report.caseId}: duplicate report`);
|
|
701
|
+
accepted.delete(report.caseId);
|
|
702
|
+
continue;
|
|
703
|
+
}
|
|
704
|
+
const difference = mismatch(expected, report);
|
|
705
|
+
if (difference !== undefined) {
|
|
706
|
+
rejectedReports.push(`${report.caseId}: ${difference}`);
|
|
707
|
+
continue;
|
|
708
|
+
}
|
|
709
|
+
if (report.artifact.sha256 === undefined) {
|
|
710
|
+
rejectedReports.push(`${report.caseId}: report did not establish exact artifact bytes`);
|
|
711
|
+
continue;
|
|
712
|
+
}
|
|
713
|
+
accepted.set(report.caseId, {
|
|
714
|
+
caseId: report.caseId,
|
|
715
|
+
sourceCaseId: report.sourceCaseId,
|
|
716
|
+
plugin: report.plugin,
|
|
717
|
+
dshVersion: report.dshVersion,
|
|
718
|
+
plane: report.plane,
|
|
719
|
+
profile: report.profile,
|
|
720
|
+
runtimeId: report.runtimeId,
|
|
721
|
+
...(report.boundary.approvedDependencyBuilds.length === 0 ? {} : { approvedDependencyBuilds: report.boundary.approvedDependencyBuilds }),
|
|
722
|
+
...((report.boundary.requiredDependencyBuilds?.length ?? 0) === 0
|
|
723
|
+
? {}
|
|
724
|
+
: { requiredDependencyBuilds: report.boundary.requiredDependencyBuilds }),
|
|
725
|
+
sourceFingerprint: report.sourceFingerprint,
|
|
726
|
+
contractFingerprint: report.contractFingerprint,
|
|
727
|
+
observedAt: report.completedAt,
|
|
728
|
+
runtime: report.runtime,
|
|
729
|
+
artifact: {
|
|
730
|
+
sha256: report.artifact.sha256,
|
|
731
|
+
...(report.artifact.bytes === undefined ? {} : { bytes: report.artifact.bytes }),
|
|
732
|
+
...(report.artifact.integrity === undefined ? {} : { integrity: report.artifact.integrity }),
|
|
733
|
+
},
|
|
734
|
+
stages: report.stages,
|
|
735
|
+
evidence: report.evidence,
|
|
736
|
+
result: report.result,
|
|
737
|
+
reason: report.reason,
|
|
738
|
+
observer: { schema: DSH_SURFACE_OBSERVATION_SCHEMA, version: report.tool.version },
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
const next = new Map(previousById);
|
|
742
|
+
for (const [id, entry] of accepted)
|
|
743
|
+
next.set(id, entry);
|
|
744
|
+
const entries = [...next.values()].sort((left, right) => left.caseId.localeCompare(right.caseId));
|
|
745
|
+
if (entries.length > MAX_LEDGER_ENTRIES)
|
|
746
|
+
throw new Error(`surface ledger exceeds ${MAX_LEDGER_ENTRIES} entries`);
|
|
747
|
+
const acceptedCaseIds = [...accepted.keys()].sort();
|
|
748
|
+
const missingCaseIds = [...expectedById.keys()].filter(id => !accepted.has(id)).sort();
|
|
749
|
+
const transitions = acceptedCaseIds.map(id => transition(previousById.get(id), accepted.get(id)));
|
|
750
|
+
rejectedReports.sort();
|
|
751
|
+
return {
|
|
752
|
+
ledger: { schema: DSH_SURFACE_LEDGER_SCHEMA, entries },
|
|
753
|
+
transitions,
|
|
754
|
+
acceptedCaseIds,
|
|
755
|
+
missingCaseIds,
|
|
756
|
+
rejectedReports,
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
export function buildDshSurfaceIR(ledgerInput) {
|
|
760
|
+
const ledger = parseDshSurfaceLedger(ledgerInput);
|
|
761
|
+
return {
|
|
762
|
+
schema: DSH_SURFACE_IR_SCHEMA,
|
|
763
|
+
generatedAt: ledger.entries.reduce((latest, entry) => entry.observedAt > latest ? entry.observedAt : latest, '1970-01-01T00:00:00.000Z'),
|
|
764
|
+
cells: ledger.entries.map(entry => ({
|
|
765
|
+
id: entry.caseId,
|
|
766
|
+
sourceCaseId: entry.sourceCaseId,
|
|
767
|
+
plane: entry.plane,
|
|
768
|
+
profile: entry.profile,
|
|
769
|
+
runtimeId: entry.runtimeId,
|
|
770
|
+
plugin: { spec: entry.plugin, artifactSha256: entry.artifact.sha256 },
|
|
771
|
+
upstream: { package: '@deepseek-ai/dsh', dshVersion: entry.dshVersion },
|
|
772
|
+
runtime: entry.runtime,
|
|
773
|
+
observation: {
|
|
774
|
+
observedAt: entry.observedAt,
|
|
775
|
+
result: entry.result,
|
|
776
|
+
reason: entry.reason,
|
|
777
|
+
...(entry.requiredDependencyBuilds === undefined ? {} : { requiredDependencyBuilds: entry.requiredDependencyBuilds }),
|
|
778
|
+
stages: entry.stages,
|
|
779
|
+
evidence: entry.evidence,
|
|
780
|
+
},
|
|
781
|
+
})),
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
export function renderDshSurfaceLedgerMerge(merge) {
|
|
785
|
+
const lines = [
|
|
786
|
+
'# DSH execution-plane reconciliation',
|
|
787
|
+
'',
|
|
788
|
+
`Accepted: ${merge.acceptedCaseIds.length}`,
|
|
789
|
+
`Missing: ${merge.missingCaseIds.length}`,
|
|
790
|
+
`Rejected: ${merge.rejectedReports.length}`,
|
|
791
|
+
'',
|
|
792
|
+
];
|
|
793
|
+
for (const transition of merge.transitions) {
|
|
794
|
+
lines.push(`- ${transition.caseId}: **${transition.status}** — ${transition.result}: ${transition.reason}`);
|
|
795
|
+
}
|
|
796
|
+
if (merge.missingCaseIds.length > 0)
|
|
797
|
+
lines.push('', `Missing cases: ${merge.missingCaseIds.join(', ')}`);
|
|
798
|
+
if (merge.rejectedReports.length > 0) {
|
|
799
|
+
lines.push('', 'Rejected reports:');
|
|
800
|
+
for (const item of merge.rejectedReports)
|
|
801
|
+
lines.push(`- ${item}`);
|
|
802
|
+
}
|
|
803
|
+
return `${lines.join('\n')}\n`;
|
|
804
|
+
}
|
|
805
|
+
//# sourceMappingURL=dsh-surface.js.map
|