@mnemonik/shared 7.97.2 → 7.98.1
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/dist/ambientPointer.d.ts.map +1 -1
- package/dist/ambientPointer.js +2 -1
- package/dist/ambientPointer.js.map +1 -1
- package/dist/apiOrigin.d.ts +3 -0
- package/dist/apiOrigin.d.ts.map +1 -0
- package/dist/apiOrigin.js +5 -0
- package/dist/apiOrigin.js.map +1 -0
- package/dist/ast/astChunker.d.ts +12 -12
- package/dist/ast/astChunker.js +32 -32
- package/dist/ast/grammars.d.ts +9 -9
- package/dist/ast/grammars.js +15 -15
- package/dist/codeScanner.d.ts +56 -43
- package/dist/codeScanner.d.ts.map +1 -1
- package/dist/codeScanner.js +262 -143
- package/dist/codeScanner.js.map +1 -1
- package/dist/contextBudget.js +2 -2
- package/dist/contextBudget.js.map +1 -1
- package/dist/gitEditDetector.d.ts +6 -6
- package/dist/gitEditDetector.js +7 -7
- package/dist/hookRuntime.d.ts +35 -2
- package/dist/hookRuntime.d.ts.map +1 -1
- package/dist/hookRuntime.js +127 -33
- package/dist/hookRuntime.js.map +1 -1
- package/dist/hookTimeouts.d.ts +6 -6
- package/dist/hookTimeouts.js +6 -6
- package/dist/hostAdapter.d.ts +135 -0
- package/dist/hostAdapter.d.ts.map +1 -0
- package/dist/hostAdapter.js +323 -0
- package/dist/hostAdapter.js.map +1 -0
- package/dist/hostBinary.d.ts +29 -0
- package/dist/hostBinary.d.ts.map +1 -0
- package/dist/hostBinary.js +179 -0
- package/dist/hostBinary.js.map +1 -0
- package/dist/index.d.ts +11 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -1
- package/dist/index.js.map +1 -1
- package/dist/instructions.d.ts +26 -26
- package/dist/instructions.js +26 -26
- package/dist/jitDeliveryAck.d.ts.map +1 -1
- package/dist/jitDeliveryAck.js +2 -0
- package/dist/jitDeliveryAck.js.map +1 -1
- package/dist/projectIdentityFile.d.ts +25 -0
- package/dist/projectIdentityFile.d.ts.map +1 -0
- package/dist/projectIdentityFile.js +87 -0
- package/dist/projectIdentityFile.js.map +1 -0
- package/dist/projectSetupHandoff.d.ts +24 -0
- package/dist/projectSetupHandoff.d.ts.map +1 -0
- package/dist/projectSetupHandoff.js +249 -0
- package/dist/projectSetupHandoff.js.map +1 -0
- package/dist/protectedPaths.d.ts +9 -0
- package/dist/protectedPaths.d.ts.map +1 -0
- package/dist/protectedPaths.js +66 -0
- package/dist/protectedPaths.js.map +1 -0
- package/dist/readiness.d.ts +143 -0
- package/dist/readiness.d.ts.map +1 -0
- package/dist/readiness.js +304 -0
- package/dist/readiness.js.map +1 -0
- package/dist/repositoryFingerprint.d.ts +50 -0
- package/dist/repositoryFingerprint.d.ts.map +1 -0
- package/dist/repositoryFingerprint.js +160 -0
- package/dist/repositoryFingerprint.js.map +1 -0
- package/dist/repositoryRoot.d.ts +56 -0
- package/dist/repositoryRoot.d.ts.map +1 -0
- package/dist/repositoryRoot.js +226 -0
- package/dist/repositoryRoot.js.map +1 -0
- package/dist/runtimeReader.d.ts +102 -0
- package/dist/runtimeReader.d.ts.map +1 -0
- package/dist/runtimeReader.js +408 -0
- package/dist/runtimeReader.js.map +1 -0
- package/dist/runtimeSigners.d.ts +37 -0
- package/dist/runtimeSigners.d.ts.map +1 -0
- package/dist/runtimeSigners.js +507 -0
- package/dist/runtimeSigners.js.map +1 -0
- package/dist/scannerSupervisor.d.ts +35 -0
- package/dist/scannerSupervisor.d.ts.map +1 -0
- package/dist/scannerSupervisor.js +2 -0
- package/dist/scannerSupervisor.js.map +1 -0
- package/dist/secretPatterns.d.ts +9 -9
- package/dist/secretPatterns.d.ts.map +1 -1
- package/dist/secretPatterns.js +27 -25
- package/dist/secretPatterns.js.map +1 -1
- package/dist/testing/hostAdapterConformance.d.ts +17 -0
- package/dist/testing/hostAdapterConformance.d.ts.map +1 -0
- package/dist/testing/hostAdapterConformance.js +477 -0
- package/dist/testing/hostAdapterConformance.js.map +1 -0
- package/dist/usageGuide.d.ts +1 -1
- package/dist/usageGuide.d.ts.map +1 -1
- package/dist/usageGuide.js +4 -4
- package/package.json +7 -2
- package/queries/bash.tags.scm +1 -1
- package/queries/groovy.tags.scm +1 -1
- package/scripts/vendor-grammars.ts +13 -13
- package/src/ambientPointer.ts +2 -1
- package/src/apiOrigin.ts +4 -0
- package/src/ast/astChunker.ts +39 -39
- package/src/ast/grammars.ts +16 -16
- package/src/codeScanner.ts +274 -149
- package/src/contextBudget.ts +2 -2
- package/src/gitEditDetector.ts +7 -7
- package/src/hookRuntime.ts +162 -34
- package/src/hookTimeouts.ts +6 -6
- package/src/hostAdapter.ts +468 -0
- package/src/hostBinary.ts +205 -0
- package/src/index.ts +20 -0
- package/src/instructions.ts +26 -26
- package/src/jitDeliveryAck.ts +2 -0
- package/src/projectIdentityFile.ts +100 -0
- package/src/projectSetupHandoff.ts +275 -0
- package/src/protectedPaths.ts +88 -0
- package/src/readiness.ts +516 -0
- package/src/repositoryFingerprint.ts +199 -0
- package/src/repositoryRoot.ts +261 -0
- package/src/runtimeReader.ts +495 -0
- package/src/runtimeSigners.ts +577 -0
- package/src/scannerSupervisor.ts +26 -0
- package/src/secretPatterns.ts +27 -25
- package/src/testing/hostAdapterConformance.ts +538 -0
- package/src/usageGuide.ts +4 -4
- package/test-fixtures/identity/runner.d.mts +12 -0
- package/test-fixtures/identity/runner.mjs +282 -0
- package/tests/fixtures/repository-fingerprint-v1.golden.json +834 -0
- package/tests/projectIdentity.test.ts +56 -0
- package/tests/readiness.test.ts +158 -0
- package/tests/repositoryFingerprint.test.ts +26 -0
- package/tsconfig.test.json +9 -0
- package/wasm/bash.tags.scm +1 -1
- package/wasm/groovy.tags.scm +1 -1
- package/wasm/javascript.tags.scm +2 -2
- package/wasm/solidity.tags.scm +1 -1
package/src/readiness.ts
ADDED
|
@@ -0,0 +1,516 @@
|
|
|
1
|
+
export const READINESS_SCHEMA_VERSION = 1 as const;
|
|
2
|
+
|
|
3
|
+
export type ReadinessState = 'READY' | 'LIMITED' | 'ACTION_REQUIRED' | 'FAILED';
|
|
4
|
+
|
|
5
|
+
export type ReadinessConditionKind =
|
|
6
|
+
| 'selected_component_failed'
|
|
7
|
+
| 'host_trust_pending'
|
|
8
|
+
| 'vendor_policy_pending'
|
|
9
|
+
| 'login_pending'
|
|
10
|
+
| 'restart_pending'
|
|
11
|
+
| 'project_identity_choice_pending'
|
|
12
|
+
| 'scanner_not_verified'
|
|
13
|
+
| 'hook_not_verified'
|
|
14
|
+
| 'hooks_missing'
|
|
15
|
+
| 'host_grant_unbound'
|
|
16
|
+
| 'host_not_connected'
|
|
17
|
+
| 'scanner_omitted'
|
|
18
|
+
| 'project_uncovered'
|
|
19
|
+
| 'host_skipped'
|
|
20
|
+
| 'windows_task_creation_failed'
|
|
21
|
+
| 'post_commit_upload_failed'
|
|
22
|
+
| 'indexing_failed'
|
|
23
|
+
| 'indexing_stalled';
|
|
24
|
+
|
|
25
|
+
export interface ReadinessCondition {
|
|
26
|
+
kind: ReadinessConditionKind;
|
|
27
|
+
component?: string;
|
|
28
|
+
reason: string;
|
|
29
|
+
action?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface ReadinessSummary {
|
|
33
|
+
state: ReadinessState;
|
|
34
|
+
reasons: string[];
|
|
35
|
+
actions: string[];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface ReadinessSubjectInput {
|
|
39
|
+
conditions: readonly ReadinessCondition[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface IndexingCounts {
|
|
43
|
+
total: number | null;
|
|
44
|
+
completed: number | null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface ProjectReadinessInput {
|
|
48
|
+
projectId?: string | null;
|
|
49
|
+
displayName?: string | null;
|
|
50
|
+
owner?: string | null;
|
|
51
|
+
repositoryMatch?: string | null;
|
|
52
|
+
identityFile?: string | null;
|
|
53
|
+
summary: ReadinessSubjectInput | ReadinessSummary;
|
|
54
|
+
indexing?: Partial<IndexingCounts> | null;
|
|
55
|
+
action?: string | null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface ProjectReadiness {
|
|
59
|
+
projectId: string | null;
|
|
60
|
+
displayName: string | null;
|
|
61
|
+
owner: string | null;
|
|
62
|
+
repositoryMatch: string | null;
|
|
63
|
+
identityFile: string | null;
|
|
64
|
+
summary: ReadinessSummary;
|
|
65
|
+
indexing: IndexingCounts | null;
|
|
66
|
+
action: string | null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface InstallSessionStatus {
|
|
70
|
+
id: string | null;
|
|
71
|
+
status: string | null;
|
|
72
|
+
kind: string | null;
|
|
73
|
+
startedAt: string | null;
|
|
74
|
+
completedAt: string | null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface DeviceGrantStatus {
|
|
78
|
+
id: string;
|
|
79
|
+
client: string;
|
|
80
|
+
device: string | null;
|
|
81
|
+
platform: string | null;
|
|
82
|
+
scopes: string[];
|
|
83
|
+
resource: string;
|
|
84
|
+
createdAt: string;
|
|
85
|
+
lastUsedAt: string | null;
|
|
86
|
+
expiresAt: string | null;
|
|
87
|
+
incompleteInstallation: boolean;
|
|
88
|
+
revokeAction: string;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface ComponentCredentialStatus {
|
|
92
|
+
id: string;
|
|
93
|
+
kind: string;
|
|
94
|
+
displayPrefix: string | null;
|
|
95
|
+
device: string | null;
|
|
96
|
+
scopes: string[];
|
|
97
|
+
lastUsedAt: string | null;
|
|
98
|
+
expiresAt: string | null;
|
|
99
|
+
revokeAction: string;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export interface ScannerStatus {
|
|
103
|
+
roots: string[] | null;
|
|
104
|
+
heartbeatAt: string | null;
|
|
105
|
+
version: string | null;
|
|
106
|
+
readiness: ReadinessSummary | null;
|
|
107
|
+
acceptedDisclosureVersion: string | null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export interface ProjectSetupResult {
|
|
111
|
+
projectId: string | null;
|
|
112
|
+
displayName: string | null;
|
|
113
|
+
owner: string | null;
|
|
114
|
+
repositoryMatch: string | null;
|
|
115
|
+
identityFile: string | null;
|
|
116
|
+
action: string | null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export interface DefaultOwnerStatus {
|
|
120
|
+
kind: 'personal' | 'team';
|
|
121
|
+
id: string | null;
|
|
122
|
+
name: string | null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export interface LimitedModeStatus {
|
|
126
|
+
acknowledgement: string | null;
|
|
127
|
+
enableScannerAction: string;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Optional receipt metadata; older schema-1 installers omit these observations. */
|
|
131
|
+
export interface ReadinessVersions {
|
|
132
|
+
cli?: string;
|
|
133
|
+
scanner?: string;
|
|
134
|
+
hosts?: Array<{ host: string; editor?: string; hooks?: string }>;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export interface ReadinessDocumentInput {
|
|
138
|
+
versions?: ReadinessVersions;
|
|
139
|
+
platform?: string;
|
|
140
|
+
installation: ReadinessSubjectInput | ReadinessSummary;
|
|
141
|
+
projects?: readonly ProjectReadinessInput[];
|
|
142
|
+
indexing?: Partial<IndexingCounts> | null;
|
|
143
|
+
installSession?: InstallSessionStatus | null;
|
|
144
|
+
devicesAndGrants?: readonly DeviceGrantStatus[] | null;
|
|
145
|
+
componentCredentials?: readonly ComponentCredentialStatus[] | null;
|
|
146
|
+
scanner?: ScannerStatus | null;
|
|
147
|
+
projectSetupResults?: readonly ProjectSetupResult[] | null;
|
|
148
|
+
defaultOwner?: DefaultOwnerStatus | null;
|
|
149
|
+
limitedMode?: LimitedModeStatus | null;
|
|
150
|
+
generatedAt?: string;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export interface ReadinessDocument {
|
|
154
|
+
versions?: ReadinessVersions;
|
|
155
|
+
platform?: string;
|
|
156
|
+
schemaVersion: typeof READINESS_SCHEMA_VERSION;
|
|
157
|
+
installation: ReadinessSummary;
|
|
158
|
+
projects?: ProjectReadiness[];
|
|
159
|
+
indexing: IndexingCounts | null;
|
|
160
|
+
installSession: InstallSessionStatus | null;
|
|
161
|
+
devicesAndGrants: DeviceGrantStatus[] | null;
|
|
162
|
+
componentCredentials: ComponentCredentialStatus[] | null;
|
|
163
|
+
scanner: ScannerStatus | null;
|
|
164
|
+
projectSetupResults: ProjectSetupResult[] | null;
|
|
165
|
+
defaultOwner: DefaultOwnerStatus | null;
|
|
166
|
+
limitedMode: LimitedModeStatus | null;
|
|
167
|
+
generatedAt: string;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const stateFor: Record<ReadinessConditionKind, Exclude<ReadinessState, 'READY'>> = {
|
|
171
|
+
selected_component_failed: 'FAILED',
|
|
172
|
+
host_trust_pending: 'ACTION_REQUIRED',
|
|
173
|
+
vendor_policy_pending: 'ACTION_REQUIRED',
|
|
174
|
+
login_pending: 'ACTION_REQUIRED',
|
|
175
|
+
restart_pending: 'ACTION_REQUIRED',
|
|
176
|
+
project_identity_choice_pending: 'ACTION_REQUIRED',
|
|
177
|
+
scanner_not_verified: 'LIMITED',
|
|
178
|
+
hook_not_verified: 'LIMITED',
|
|
179
|
+
hooks_missing: 'ACTION_REQUIRED',
|
|
180
|
+
host_grant_unbound: 'LIMITED',
|
|
181
|
+
host_not_connected: 'LIMITED',
|
|
182
|
+
scanner_omitted: 'LIMITED',
|
|
183
|
+
project_uncovered: 'LIMITED',
|
|
184
|
+
host_skipped: 'LIMITED',
|
|
185
|
+
windows_task_creation_failed: 'LIMITED',
|
|
186
|
+
post_commit_upload_failed: 'FAILED',
|
|
187
|
+
indexing_failed: 'FAILED',
|
|
188
|
+
indexing_stalled: 'FAILED',
|
|
189
|
+
};
|
|
190
|
+
const rank: Record<ReadinessState, number> = {
|
|
191
|
+
READY: 0,
|
|
192
|
+
LIMITED: 1,
|
|
193
|
+
ACTION_REQUIRED: 2,
|
|
194
|
+
FAILED: 3,
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
function defaultAction(condition: ReadinessCondition): string | undefined {
|
|
198
|
+
if (condition.kind === 'host_skipped' && condition.component)
|
|
199
|
+
return `Connect it later: mnemonik connect ${condition.component}`;
|
|
200
|
+
if (condition.kind === 'windows_task_creation_failed')
|
|
201
|
+
return 'Run mnemonik scanner enable to try again.';
|
|
202
|
+
return condition.action;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export function reduceReadiness(input: readonly ReadinessCondition[]): ReadinessSummary {
|
|
206
|
+
let state: ReadinessState = 'READY';
|
|
207
|
+
const reasons: string[] = [];
|
|
208
|
+
const actions: string[] = [];
|
|
209
|
+
for (const condition of input) {
|
|
210
|
+
const candidate = stateFor[condition.kind];
|
|
211
|
+
if (rank[candidate] > rank[state]) state = candidate;
|
|
212
|
+
if (!reasons.includes(condition.reason)) reasons.push(condition.reason);
|
|
213
|
+
const action = defaultAction(condition);
|
|
214
|
+
if (action && !actions.includes(action)) actions.push(action);
|
|
215
|
+
}
|
|
216
|
+
return { state, reasons, actions };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function sentence(value: string): string {
|
|
220
|
+
return /[.!?]$/u.test(value) ? value : `${value}.`;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export function remainingReadinessCount(summary: ReadinessSummary): number {
|
|
224
|
+
return summary.reasons.filter((reason) => reason !== 'dev_release_source').length;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export function describeReadiness(summary: ReadinessSummary): string {
|
|
228
|
+
if (summary.state === 'READY')
|
|
229
|
+
return 'Done. Your editors will use Mnemonik on their next session.';
|
|
230
|
+
const count = remainingReadinessCount(summary);
|
|
231
|
+
const opening =
|
|
232
|
+
summary.state === 'FAILED'
|
|
233
|
+
? 'Setup failed.'
|
|
234
|
+
: count === 0
|
|
235
|
+
? 'Done.'
|
|
236
|
+
: summary.state === 'ACTION_REQUIRED'
|
|
237
|
+
? `Setup needs ${count === 1 ? 'one action' : `${count} actions`}.`
|
|
238
|
+
: `Done, with ${count === 1 ? 'one thing' : `${count} things`} left.`;
|
|
239
|
+
return [opening, ...summary.reasons.map(sentence), ...summary.actions].join(' ');
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const counts = (value: Partial<IndexingCounts> | null | undefined): IndexingCounts | null =>
|
|
243
|
+
value ? { total: value.total ?? null, completed: value.completed ?? null } : null;
|
|
244
|
+
|
|
245
|
+
const summary = (value: ReadinessSubjectInput | ReadinessSummary): ReadinessSummary =>
|
|
246
|
+
'conditions' in value
|
|
247
|
+
? reduceReadiness(value.conditions)
|
|
248
|
+
: { state: value.state, reasons: [...value.reasons], actions: [...value.actions] };
|
|
249
|
+
|
|
250
|
+
export function serializeReadiness(input: ReadinessDocumentInput): ReadinessDocument {
|
|
251
|
+
const projects = input.projects?.map((project) => ({
|
|
252
|
+
projectId: project.projectId ?? null,
|
|
253
|
+
displayName: project.displayName ?? null,
|
|
254
|
+
owner: project.owner ?? null,
|
|
255
|
+
repositoryMatch: project.repositoryMatch ?? null,
|
|
256
|
+
identityFile: project.identityFile ?? null,
|
|
257
|
+
summary: summary(project.summary),
|
|
258
|
+
indexing: counts(project.indexing),
|
|
259
|
+
action: project.action ?? null,
|
|
260
|
+
}));
|
|
261
|
+
return {
|
|
262
|
+
schemaVersion: READINESS_SCHEMA_VERSION,
|
|
263
|
+
...(input.platform !== undefined ? { platform: input.platform } : {}),
|
|
264
|
+
...(input.versions
|
|
265
|
+
? {
|
|
266
|
+
versions: {
|
|
267
|
+
...input.versions,
|
|
268
|
+
...(input.versions.hosts
|
|
269
|
+
? { hosts: input.versions.hosts.map((host) => ({ ...host })) }
|
|
270
|
+
: {}),
|
|
271
|
+
},
|
|
272
|
+
}
|
|
273
|
+
: {}),
|
|
274
|
+
installation: summary(input.installation),
|
|
275
|
+
...(projects ? { projects } : {}),
|
|
276
|
+
indexing: counts(input.indexing),
|
|
277
|
+
installSession: input.installSession ?? null,
|
|
278
|
+
devicesAndGrants: input.devicesAndGrants ? [...input.devicesAndGrants] : null,
|
|
279
|
+
componentCredentials: input.componentCredentials ? [...input.componentCredentials] : null,
|
|
280
|
+
scanner: input.scanner ?? null,
|
|
281
|
+
projectSetupResults: input.projectSetupResults ? [...input.projectSetupResults] : null,
|
|
282
|
+
defaultOwner: input.defaultOwner ?? null,
|
|
283
|
+
limitedMode: input.limitedMode ?? null,
|
|
284
|
+
generatedAt: input.generatedAt ?? new Date().toISOString(),
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const strings = (value: unknown): value is string[] =>
|
|
289
|
+
Array.isArray(value) && value.every((item) => typeof item === 'string');
|
|
290
|
+
const record = (value: unknown): value is Record<string, unknown> =>
|
|
291
|
+
!!value && typeof value === 'object' && !Array.isArray(value);
|
|
292
|
+
const exact = (value: Record<string, unknown>, keys: readonly string[]): boolean =>
|
|
293
|
+
Object.keys(value).every((key) => keys.includes(key)) && keys.every((key) => key in value);
|
|
294
|
+
const nullableString = (value: unknown): boolean => value === null || typeof value === 'string';
|
|
295
|
+
const nullableCount = (value: unknown): boolean =>
|
|
296
|
+
value === null || (Number.isInteger(value) && Number(value) >= 0);
|
|
297
|
+
const versionString = (value: unknown): value is string =>
|
|
298
|
+
typeof value === 'string' &&
|
|
299
|
+
value.length > 0 &&
|
|
300
|
+
value.length <= 128 &&
|
|
301
|
+
/^[\x20-\x7e]+$/u.test(value);
|
|
302
|
+
const validVersions = (value: unknown): boolean =>
|
|
303
|
+
record(value) &&
|
|
304
|
+
Object.keys(value).every((key) => ['cli', 'scanner', 'hosts'].includes(key)) &&
|
|
305
|
+
(value.cli === undefined || versionString(value.cli)) &&
|
|
306
|
+
(value.scanner === undefined || versionString(value.scanner)) &&
|
|
307
|
+
(value.hosts === undefined ||
|
|
308
|
+
(Array.isArray(value.hosts) &&
|
|
309
|
+
value.hosts.length <= 32 &&
|
|
310
|
+
value.hosts.every(
|
|
311
|
+
(host) =>
|
|
312
|
+
record(host) &&
|
|
313
|
+
versionString(host.host) &&
|
|
314
|
+
Object.keys(host).every((key) => ['host', 'editor', 'hooks'].includes(key)) &&
|
|
315
|
+
(host.editor === undefined || versionString(host.editor)) &&
|
|
316
|
+
(host.hooks === undefined || versionString(host.hooks))
|
|
317
|
+
)));
|
|
318
|
+
const validSummary = (value: unknown): value is ReadinessSummary => {
|
|
319
|
+
if (!record(value) || !exact(value, ['state', 'reasons', 'actions'])) return false;
|
|
320
|
+
const row = value;
|
|
321
|
+
return (
|
|
322
|
+
['READY', 'LIMITED', 'ACTION_REQUIRED', 'FAILED'].includes(String(row.state)) &&
|
|
323
|
+
strings(row.reasons) &&
|
|
324
|
+
strings(row.actions)
|
|
325
|
+
);
|
|
326
|
+
};
|
|
327
|
+
const validCounts = (value: unknown): boolean => {
|
|
328
|
+
if (value === null) return true;
|
|
329
|
+
if (!record(value) || !exact(value, ['total', 'completed'])) return false;
|
|
330
|
+
const row = value;
|
|
331
|
+
return nullableCount(row.total) && nullableCount(row.completed);
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
const validProject = (value: unknown): boolean => {
|
|
335
|
+
if (
|
|
336
|
+
!record(value) ||
|
|
337
|
+
!exact(value, [
|
|
338
|
+
'projectId',
|
|
339
|
+
'displayName',
|
|
340
|
+
'owner',
|
|
341
|
+
'repositoryMatch',
|
|
342
|
+
'identityFile',
|
|
343
|
+
'summary',
|
|
344
|
+
'indexing',
|
|
345
|
+
'action',
|
|
346
|
+
])
|
|
347
|
+
)
|
|
348
|
+
return false;
|
|
349
|
+
return (
|
|
350
|
+
nullableString(value.projectId) &&
|
|
351
|
+
nullableString(value.displayName) &&
|
|
352
|
+
nullableString(value.owner) &&
|
|
353
|
+
nullableString(value.repositoryMatch) &&
|
|
354
|
+
nullableString(value.identityFile) &&
|
|
355
|
+
validSummary(value.summary) &&
|
|
356
|
+
validCounts(value.indexing) &&
|
|
357
|
+
nullableString(value.action)
|
|
358
|
+
);
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
const validInstallSession = (value: unknown): boolean =>
|
|
362
|
+
value === null ||
|
|
363
|
+
(record(value) &&
|
|
364
|
+
exact(value, ['id', 'status', 'kind', 'startedAt', 'completedAt']) &&
|
|
365
|
+
[value.id, value.status, value.kind, value.startedAt, value.completedAt].every(nullableString));
|
|
366
|
+
|
|
367
|
+
const validGrant = (value: unknown): boolean =>
|
|
368
|
+
record(value) &&
|
|
369
|
+
exact(value, [
|
|
370
|
+
'id',
|
|
371
|
+
'client',
|
|
372
|
+
'device',
|
|
373
|
+
'platform',
|
|
374
|
+
'scopes',
|
|
375
|
+
'resource',
|
|
376
|
+
'createdAt',
|
|
377
|
+
'lastUsedAt',
|
|
378
|
+
'expiresAt',
|
|
379
|
+
'incompleteInstallation',
|
|
380
|
+
'revokeAction',
|
|
381
|
+
]) &&
|
|
382
|
+
typeof value.id === 'string' &&
|
|
383
|
+
typeof value.client === 'string' &&
|
|
384
|
+
nullableString(value.device) &&
|
|
385
|
+
nullableString(value.platform) &&
|
|
386
|
+
strings(value.scopes) &&
|
|
387
|
+
typeof value.resource === 'string' &&
|
|
388
|
+
typeof value.createdAt === 'string' &&
|
|
389
|
+
nullableString(value.lastUsedAt) &&
|
|
390
|
+
nullableString(value.expiresAt) &&
|
|
391
|
+
typeof value.incompleteInstallation === 'boolean' &&
|
|
392
|
+
typeof value.revokeAction === 'string';
|
|
393
|
+
|
|
394
|
+
const validComponent = (value: unknown): boolean =>
|
|
395
|
+
record(value) &&
|
|
396
|
+
exact(value, [
|
|
397
|
+
'id',
|
|
398
|
+
'kind',
|
|
399
|
+
'displayPrefix',
|
|
400
|
+
'device',
|
|
401
|
+
'scopes',
|
|
402
|
+
'lastUsedAt',
|
|
403
|
+
'expiresAt',
|
|
404
|
+
'revokeAction',
|
|
405
|
+
]) &&
|
|
406
|
+
typeof value.id === 'string' &&
|
|
407
|
+
typeof value.kind === 'string' &&
|
|
408
|
+
nullableString(value.displayPrefix) &&
|
|
409
|
+
nullableString(value.device) &&
|
|
410
|
+
strings(value.scopes) &&
|
|
411
|
+
nullableString(value.lastUsedAt) &&
|
|
412
|
+
nullableString(value.expiresAt) &&
|
|
413
|
+
typeof value.revokeAction === 'string';
|
|
414
|
+
|
|
415
|
+
const validScanner = (value: unknown): boolean =>
|
|
416
|
+
value === null ||
|
|
417
|
+
(record(value) &&
|
|
418
|
+
exact(value, ['roots', 'heartbeatAt', 'version', 'readiness', 'acceptedDisclosureVersion']) &&
|
|
419
|
+
(value.roots === null || strings(value.roots)) &&
|
|
420
|
+
nullableString(value.heartbeatAt) &&
|
|
421
|
+
nullableString(value.version) &&
|
|
422
|
+
(value.readiness === null || validSummary(value.readiness)) &&
|
|
423
|
+
nullableString(value.acceptedDisclosureVersion));
|
|
424
|
+
|
|
425
|
+
const validSetup = (value: unknown): boolean =>
|
|
426
|
+
record(value) &&
|
|
427
|
+
exact(value, [
|
|
428
|
+
'projectId',
|
|
429
|
+
'displayName',
|
|
430
|
+
'owner',
|
|
431
|
+
'repositoryMatch',
|
|
432
|
+
'identityFile',
|
|
433
|
+
'action',
|
|
434
|
+
]) &&
|
|
435
|
+
[
|
|
436
|
+
value.projectId,
|
|
437
|
+
value.displayName,
|
|
438
|
+
value.owner,
|
|
439
|
+
value.repositoryMatch,
|
|
440
|
+
value.identityFile,
|
|
441
|
+
value.action,
|
|
442
|
+
].every(nullableString);
|
|
443
|
+
|
|
444
|
+
/** Strict enough for the install-session trust boundary; nested console rows stay JSON-only. */
|
|
445
|
+
export function isReadinessDocument(value: unknown): value is ReadinessDocument {
|
|
446
|
+
if (!record(value)) return false;
|
|
447
|
+
const required = [
|
|
448
|
+
'schemaVersion',
|
|
449
|
+
'installation',
|
|
450
|
+
'indexing',
|
|
451
|
+
'installSession',
|
|
452
|
+
'devicesAndGrants',
|
|
453
|
+
'componentCredentials',
|
|
454
|
+
'scanner',
|
|
455
|
+
'projectSetupResults',
|
|
456
|
+
'defaultOwner',
|
|
457
|
+
'limitedMode',
|
|
458
|
+
'generatedAt',
|
|
459
|
+
];
|
|
460
|
+
if (
|
|
461
|
+
!Object.keys(value).every((key) =>
|
|
462
|
+
[...required, 'projects', 'versions', 'platform'].includes(key)
|
|
463
|
+
)
|
|
464
|
+
)
|
|
465
|
+
return false;
|
|
466
|
+
const row = value;
|
|
467
|
+
if ('versions' in row && !validVersions(row.versions)) return false;
|
|
468
|
+
if ('platform' in row && !versionString(row.platform)) return false;
|
|
469
|
+
if (
|
|
470
|
+
row.schemaVersion !== READINESS_SCHEMA_VERSION ||
|
|
471
|
+
!validSummary(row.installation) ||
|
|
472
|
+
!validCounts(row.indexing) ||
|
|
473
|
+
typeof row.generatedAt !== 'string' ||
|
|
474
|
+
!Number.isFinite(Date.parse(row.generatedAt))
|
|
475
|
+
)
|
|
476
|
+
return false;
|
|
477
|
+
if (
|
|
478
|
+
row.projects !== undefined &&
|
|
479
|
+
(!Array.isArray(row.projects) || !row.projects.every(validProject))
|
|
480
|
+
)
|
|
481
|
+
return false;
|
|
482
|
+
if (!required.every((field) => field in row)) return false;
|
|
483
|
+
if (!validInstallSession(row.installSession)) return false;
|
|
484
|
+
if (
|
|
485
|
+
row.devicesAndGrants !== null &&
|
|
486
|
+
(!Array.isArray(row.devicesAndGrants) || !row.devicesAndGrants.every(validGrant))
|
|
487
|
+
)
|
|
488
|
+
return false;
|
|
489
|
+
if (
|
|
490
|
+
row.componentCredentials !== null &&
|
|
491
|
+
(!Array.isArray(row.componentCredentials) || !row.componentCredentials.every(validComponent))
|
|
492
|
+
)
|
|
493
|
+
return false;
|
|
494
|
+
if (!validScanner(row.scanner)) return false;
|
|
495
|
+
if (
|
|
496
|
+
row.projectSetupResults !== null &&
|
|
497
|
+
(!Array.isArray(row.projectSetupResults) || !row.projectSetupResults.every(validSetup))
|
|
498
|
+
)
|
|
499
|
+
return false;
|
|
500
|
+
if (
|
|
501
|
+
row.defaultOwner !== null &&
|
|
502
|
+
(!record(row.defaultOwner) ||
|
|
503
|
+
!exact(row.defaultOwner, ['kind', 'id', 'name']) ||
|
|
504
|
+
!['personal', 'team'].includes(String(row.defaultOwner.kind)) ||
|
|
505
|
+
!nullableString(row.defaultOwner.id) ||
|
|
506
|
+
!nullableString(row.defaultOwner.name))
|
|
507
|
+
)
|
|
508
|
+
return false;
|
|
509
|
+
return (
|
|
510
|
+
row.limitedMode === null ||
|
|
511
|
+
(record(row.limitedMode) &&
|
|
512
|
+
exact(row.limitedMode, ['acknowledgement', 'enableScannerAction']) &&
|
|
513
|
+
nullableString(row.limitedMode.acknowledgement) &&
|
|
514
|
+
typeof row.limitedMode.enableScannerAction === 'string')
|
|
515
|
+
);
|
|
516
|
+
}
|