@genn-inc/cluebase-cli 0.0.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/README.md +101 -0
- package/bin/cluebase-cli.mjs +11 -0
- package/package.json +17 -0
- package/src/cli-command.mjs +515 -0
- package/src/cli-invocation.mjs +17 -0
- package/src/code-evidence-analyzer.mjs +2041 -0
- package/src/contracts.mjs +36 -0
- package/src/generated-code-evidence-contract.mjs +22 -0
- package/src/generated-sdk-version-contract.mjs +5 -0
- package/src/generated-source-path-policy.mjs +20 -0
- package/src/lifecycle-guard.mjs +202 -0
- package/src/path-policy.mjs +81 -0
- package/src/setup-ai-contract.mjs +221 -0
- package/src/setup-check-constants.mjs +110 -0
- package/src/setup-check-scan-a.mjs +849 -0
- package/src/setup-check-scan-b.mjs +994 -0
- package/src/setup-check.mjs +575 -0
- package/src/setup-discover-check.mjs +755 -0
- package/src/setup-doctor-deadline.mjs +221 -0
- package/src/setup-doctor-env.mjs +331 -0
- package/src/setup-doctor-file-boundary.mjs +426 -0
- package/src/setup-doctor-probe.mjs +719 -0
- package/src/setup-doctor-quality-checks-a.mjs +593 -0
- package/src/setup-doctor-quality-checks-b.mjs +638 -0
- package/src/setup-doctor-quality-shared.mjs +382 -0
- package/src/setup-doctor-quality.mjs +209 -0
- package/src/setup-doctor-route-scan.mjs +160 -0
- package/src/setup-doctor-sdk-probe.mjs +340 -0
- package/src/setup-doctor.mjs +545 -0
- package/src/setup-documents.mjs +112 -0
- package/src/setup-help.mjs +130 -0
- package/src/setup-prepare.mjs +360 -0
- package/src/setup-repository-discovery.mjs +764 -0
- package/src/setup-step-builders-discover.mjs +701 -0
- package/src/setup-step-builders-events.mjs +229 -0
- package/src/setup-step-builders-implement.mjs +710 -0
- package/src/setup-step-commands.mjs +427 -0
- package/src/setup-tool.mjs +27 -0
|
@@ -0,0 +1,575 @@
|
|
|
1
|
+
// setup-check: mechanical verification of installed setup steps, secret
|
|
2
|
+
// leaks, and SDK lifecycle presence. Constants live in setup-check-constants,
|
|
3
|
+
// scan/detector helpers in setup-check-scan-a / -scan-b.
|
|
4
|
+
|
|
5
|
+
import { readFile } from "node:fs/promises";
|
|
6
|
+
import { join, resolve } from "node:path";
|
|
7
|
+
import {
|
|
8
|
+
findLifecycleCallApiNames,
|
|
9
|
+
findLifecycleGuardViolations,
|
|
10
|
+
stripSourceNoise,
|
|
11
|
+
} from "./lifecycle-guard.mjs";
|
|
12
|
+
import {
|
|
13
|
+
backendSdkSpecForFramework,
|
|
14
|
+
FRONTEND_SDK_PACKAGE,
|
|
15
|
+
REQUIRED_LIFECYCLE_APIS,
|
|
16
|
+
sourceMatchesBackendInit,
|
|
17
|
+
} from "./setup-check-constants.mjs";
|
|
18
|
+
import {
|
|
19
|
+
addCheck,
|
|
20
|
+
dependencyHasAnyPackage,
|
|
21
|
+
findFrontendLifecycleNonPublicEnvFiles,
|
|
22
|
+
findSecretLeaks,
|
|
23
|
+
frontendPackageRoots,
|
|
24
|
+
packageJsonSourcesWithDependency,
|
|
25
|
+
readAllowedSourceText,
|
|
26
|
+
readDependencyText,
|
|
27
|
+
readFrontendConfigText,
|
|
28
|
+
readSetupManifest,
|
|
29
|
+
setupBackendRootPaths,
|
|
30
|
+
setupSourcePaths,
|
|
31
|
+
sourceHasAuthCallbackScopedCluebaseInitCall,
|
|
32
|
+
sourceHasComponentScopedCluebaseInitCall,
|
|
33
|
+
sourceHasVerifiedFrontendSdkAccess,
|
|
34
|
+
sourceIsUnderFrontendRoot,
|
|
35
|
+
sourcesHaveBackendSdkImport,
|
|
36
|
+
sourcesHaveFrontendSdkImport,
|
|
37
|
+
startsWithRoot,
|
|
38
|
+
validateSetupManifestContract,
|
|
39
|
+
validateSetupStepFiles,
|
|
40
|
+
} from "./setup-check-scan-a.mjs";
|
|
41
|
+
import {
|
|
42
|
+
companyBoundaryPattern,
|
|
43
|
+
findBackendCluebaseEnvRequiredIndexFiles,
|
|
44
|
+
findBackendLifecycleReadEndpointFiles,
|
|
45
|
+
findBackendSdkInitMissingServiceKeyFiles,
|
|
46
|
+
findCluebaseInitCallEmptyEnvFallbackFiles,
|
|
47
|
+
findFastapiMissingMiddlewareFiles,
|
|
48
|
+
findFrontendBackendTracePropagationMissingFiles,
|
|
49
|
+
findFrontendInitBeforeCluebaseInitCallFiles,
|
|
50
|
+
findFrontendLifecycleReadSideEffectFiles,
|
|
51
|
+
findHardcodedLifecycleIdentityFiles,
|
|
52
|
+
findNextLifecycleMissingUseClientFiles,
|
|
53
|
+
findRepeatedIdentityHelperFiles,
|
|
54
|
+
frontendIdentityBoundaryPattern,
|
|
55
|
+
identityBoundaryPattern,
|
|
56
|
+
logoutBoundaryPattern,
|
|
57
|
+
} from "./setup-check-scan-b.mjs";
|
|
58
|
+
|
|
59
|
+
// FIX-118: the discovery artifact records where the setup AI decided the
|
|
60
|
+
// lifecycle boundaries actually are. setup-check uses that placement as the
|
|
61
|
+
// source of truth for whether the backend needs identify/group, instead of
|
|
62
|
+
// re-pattern-matching the raw backend source (which cannot distinguish a real
|
|
63
|
+
// one-time auth boundary from a reused token-hydration path).
|
|
64
|
+
const readDiscoveries = async (repoRoot) => {
|
|
65
|
+
try {
|
|
66
|
+
const raw = await readFile(
|
|
67
|
+
join(repoRoot, ".cluebase", "discoveries.json"),
|
|
68
|
+
"utf8",
|
|
69
|
+
);
|
|
70
|
+
const parsed = JSON.parse(raw);
|
|
71
|
+
return parsed && typeof parsed === "object" ? parsed : null;
|
|
72
|
+
} catch {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const siteIsUnderBackend = (site, backendRootPaths) =>
|
|
78
|
+
typeof site?.file === "string" &&
|
|
79
|
+
backendRootPaths.some((root) => startsWithRoot(site.file, root));
|
|
80
|
+
|
|
81
|
+
// Returns { present, hasBackendIdentify, hasBackendGroup } from discoveries.
|
|
82
|
+
// `present: false` means no discovery artifact — callers fall back to the
|
|
83
|
+
// pattern-based requirement.
|
|
84
|
+
export const backendBoundariesFromDiscovery = (
|
|
85
|
+
discoveries,
|
|
86
|
+
backendRootPaths = [],
|
|
87
|
+
) => {
|
|
88
|
+
if (!discoveries || typeof discoveries !== "object") {
|
|
89
|
+
return { present: false };
|
|
90
|
+
}
|
|
91
|
+
const identifySites = Array.isArray(discoveries.identify_sites)
|
|
92
|
+
? discoveries.identify_sites
|
|
93
|
+
: [];
|
|
94
|
+
const groupSites = Array.isArray(discoveries.group_sites)
|
|
95
|
+
? discoveries.group_sites
|
|
96
|
+
: [];
|
|
97
|
+
return {
|
|
98
|
+
present: true,
|
|
99
|
+
hasBackendIdentify: identifySites.some((site) =>
|
|
100
|
+
siteIsUnderBackend(site, backendRootPaths),
|
|
101
|
+
),
|
|
102
|
+
hasBackendGroup: groupSites.some((site) =>
|
|
103
|
+
siteIsUnderBackend(site, backendRootPaths),
|
|
104
|
+
),
|
|
105
|
+
};
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const checkSdkLifecycle = ({
|
|
109
|
+
backendRootPaths = [],
|
|
110
|
+
dependencySources = [],
|
|
111
|
+
discoveryBackendBoundaries = null,
|
|
112
|
+
framework,
|
|
113
|
+
sources,
|
|
114
|
+
}) => {
|
|
115
|
+
const combined = sources
|
|
116
|
+
.map((source) => stripSourceNoise(source.text, { stripStrings: true }))
|
|
117
|
+
.join("\n");
|
|
118
|
+
const backendSources = sources.filter((source) =>
|
|
119
|
+
backendRootPaths.some((root) => startsWithRoot(source.file_path, root)),
|
|
120
|
+
);
|
|
121
|
+
const frontendRootPaths = frontendPackageRoots(dependencySources);
|
|
122
|
+
const frontendSources = sources.filter(
|
|
123
|
+
(source) =>
|
|
124
|
+
!backendRootPaths.some((root) =>
|
|
125
|
+
startsWithRoot(source.file_path, root),
|
|
126
|
+
) && sourceIsUnderFrontendRoot(source, frontendRootPaths),
|
|
127
|
+
);
|
|
128
|
+
const backendCombined = backendSources
|
|
129
|
+
.map((source) => stripSourceNoise(source.text, { stripStrings: true }))
|
|
130
|
+
.join("\n");
|
|
131
|
+
const frontendCombined = frontendSources
|
|
132
|
+
.map((source) => stripSourceNoise(source.text, { stripStrings: true }))
|
|
133
|
+
.join("\n");
|
|
134
|
+
const foundApiNames = findLifecycleCallApiNames(combined);
|
|
135
|
+
const backendFoundApiNames = findLifecycleCallApiNames(backendCombined);
|
|
136
|
+
const frontendFoundApiNames = findLifecycleCallApiNames(frontendCombined);
|
|
137
|
+
const sourceByPath = new Map(
|
|
138
|
+
sources.map((source) => [source.file_path, source]),
|
|
139
|
+
);
|
|
140
|
+
const frontendLifecycleFilesWithoutVerifiedSdk = frontendSources
|
|
141
|
+
.filter(
|
|
142
|
+
(source) =>
|
|
143
|
+
findLifecycleCallApiNames(
|
|
144
|
+
stripSourceNoise(source.text, { stripStrings: true }),
|
|
145
|
+
).length > 0,
|
|
146
|
+
)
|
|
147
|
+
.filter((source) => {
|
|
148
|
+
const apiNames = findLifecycleCallApiNames(
|
|
149
|
+
stripSourceNoise(source.text, { stripStrings: true }),
|
|
150
|
+
);
|
|
151
|
+
return !sourceHasVerifiedFrontendSdkAccess({
|
|
152
|
+
apiNames,
|
|
153
|
+
source,
|
|
154
|
+
sourceByPath,
|
|
155
|
+
});
|
|
156
|
+
})
|
|
157
|
+
.map((source) => source.file_path);
|
|
158
|
+
let missingApis = [];
|
|
159
|
+
// `CluebaseInit` is the prescribed App Router bootstrap Client Component
|
|
160
|
+
// (see STEP 5: `export function CluebaseInit() { return null; }`), so a
|
|
161
|
+
// declaration of that name is the intended pattern, not a no-op shim. A
|
|
162
|
+
// `window.CluebaseInit` global assignment is still a shim signal, and
|
|
163
|
+
// `function/const CluebaseIdentify|CluebaseSetOrganization|CluebaseLogout` are never
|
|
164
|
+
// prescribed component names, so those remain flagged.
|
|
165
|
+
const noOpPattern =
|
|
166
|
+
/window\.Cluebase(?:Init|Identify|SetOrganization|Logout)|(?:function|const|let|var)\s+Cluebase(?:Identify|SetOrganization|Logout)\b/;
|
|
167
|
+
const componentLifecycleInitFiles = sources
|
|
168
|
+
.filter((source) => sourceHasComponentScopedCluebaseInitCall(source.text))
|
|
169
|
+
.map((source) => source.file_path);
|
|
170
|
+
const authCallbackLifecycleInitFiles = frontendSources
|
|
171
|
+
.filter((source) => sourceHasAuthCallbackScopedCluebaseInitCall(source.text))
|
|
172
|
+
.map((source) => source.file_path);
|
|
173
|
+
const blockingLifecycleCalls = sources.flatMap((source) =>
|
|
174
|
+
findLifecycleGuardViolations(
|
|
175
|
+
stripSourceNoise(source.text, { stripStrings: true }),
|
|
176
|
+
).map((violation) => ({
|
|
177
|
+
file_path: source.file_path,
|
|
178
|
+
...violation,
|
|
179
|
+
})),
|
|
180
|
+
);
|
|
181
|
+
const blockingLifecycleFiles = [
|
|
182
|
+
...new Set(blockingLifecycleCalls.map((violation) => violation.file_path)),
|
|
183
|
+
];
|
|
184
|
+
const backendPresent = backendSources.length > 0;
|
|
185
|
+
const backendSpec = backendSdkSpecForFramework(framework);
|
|
186
|
+
const backendSdkDependencyPresent =
|
|
187
|
+
!backendPresent ||
|
|
188
|
+
dependencyHasAnyPackage(dependencySources, backendSpec.packages);
|
|
189
|
+
const backendSdkImportPresent =
|
|
190
|
+
!backendPresent ||
|
|
191
|
+
sourcesHaveBackendSdkImport(backendSources, backendSpec);
|
|
192
|
+
const backendSdkPresent =
|
|
193
|
+
!backendPresent || (backendSdkDependencyPresent && backendSdkImportPresent);
|
|
194
|
+
const backendSdkInstallabilityVerified =
|
|
195
|
+
!backendPresent || backendSpec.installabilityStatus !== "unverified";
|
|
196
|
+
const backendInitPresent =
|
|
197
|
+
!backendPresent || sourceMatchesBackendInit(framework, backendCombined);
|
|
198
|
+
const backendInitObserved = backendPresent && backendInitPresent;
|
|
199
|
+
const foundApis = REQUIRED_LIFECYCLE_APIS.filter(
|
|
200
|
+
(api) =>
|
|
201
|
+
foundApiNames.includes(api) ||
|
|
202
|
+
(api === "cluebase.init" && backendInitObserved),
|
|
203
|
+
);
|
|
204
|
+
const backendSdkInitMissingServiceKeyFiles =
|
|
205
|
+
findBackendSdkInitMissingServiceKeyFiles({
|
|
206
|
+
backendSources,
|
|
207
|
+
framework,
|
|
208
|
+
});
|
|
209
|
+
const fastapiMissingMiddlewareFiles = findFastapiMissingMiddlewareFiles({
|
|
210
|
+
backendSources,
|
|
211
|
+
framework,
|
|
212
|
+
});
|
|
213
|
+
const backendCluebaseEnvRequiredIndexFiles =
|
|
214
|
+
findBackendCluebaseEnvRequiredIndexFiles(backendSources);
|
|
215
|
+
const hardcodedLifecycleIdentityFiles =
|
|
216
|
+
findHardcodedLifecycleIdentityFiles(sources);
|
|
217
|
+
const backendLifecycleReadEndpointFiles =
|
|
218
|
+
findBackendLifecycleReadEndpointFiles(backendSources);
|
|
219
|
+
const frontendLifecyclePresent = frontendFoundApiNames.length > 0;
|
|
220
|
+
const frontendSdkDependencyPresent =
|
|
221
|
+
!frontendLifecyclePresent ||
|
|
222
|
+
packageJsonSourcesWithDependency(dependencySources, FRONTEND_SDK_PACKAGE)
|
|
223
|
+
.length > 0;
|
|
224
|
+
const frontendSdkImportPresent =
|
|
225
|
+
!frontendLifecyclePresent || sourcesHaveFrontendSdkImport(frontendSources);
|
|
226
|
+
const frontendSdkPresent =
|
|
227
|
+
!frontendLifecyclePresent ||
|
|
228
|
+
(frontendSdkDependencyPresent && frontendSdkImportPresent);
|
|
229
|
+
const frontendLifecycleNonPublicEnvFiles =
|
|
230
|
+
findFrontendLifecycleNonPublicEnvFiles({
|
|
231
|
+
frontendSources,
|
|
232
|
+
});
|
|
233
|
+
const frontendLifecycleReadSideEffectFiles =
|
|
234
|
+
findFrontendLifecycleReadSideEffectFiles(frontendSources);
|
|
235
|
+
const repeatedIdentityHelperFiles = findRepeatedIdentityHelperFiles(sources);
|
|
236
|
+
const cluebaseInitEmptyEnvFallbackFiles =
|
|
237
|
+
findCluebaseInitCallEmptyEnvFallbackFiles(frontendSources);
|
|
238
|
+
const frontendInitBeforeCluebaseInitCallFiles =
|
|
239
|
+
findFrontendInitBeforeCluebaseInitCallFiles(frontendSources);
|
|
240
|
+
const frontendBackendTracePropagationMissingFiles =
|
|
241
|
+
findFrontendBackendTracePropagationMissingFiles({
|
|
242
|
+
backendPresent,
|
|
243
|
+
frontendSources,
|
|
244
|
+
});
|
|
245
|
+
const nextLifecycleMissingUseClientFiles =
|
|
246
|
+
findNextLifecycleMissingUseClientFiles({
|
|
247
|
+
dependencySources,
|
|
248
|
+
frontendSources,
|
|
249
|
+
});
|
|
250
|
+
// FIX-118: pattern-matching the backend source only tells us the backend
|
|
251
|
+
// *has auth/organization-shaped routes*, not whether any of them is a real
|
|
252
|
+
// one-time boundary. When the discovery artifact is available, use its
|
|
253
|
+
// actual placement decision as the source of truth; fall back to the
|
|
254
|
+
// pattern only when discovery is absent (e.g. setup-check run standalone).
|
|
255
|
+
const backendPatternIdentity =
|
|
256
|
+
backendPresent && identityBoundaryPattern.test(backendCombined);
|
|
257
|
+
const backendPatternOrganization =
|
|
258
|
+
backendPresent && companyBoundaryPattern.test(backendCombined);
|
|
259
|
+
const backendIdentityRequired = discoveryBackendBoundaries?.present
|
|
260
|
+
? Boolean(discoveryBackendBoundaries.hasBackendIdentify)
|
|
261
|
+
: backendPatternIdentity;
|
|
262
|
+
const backendOrganizationRequired = discoveryBackendBoundaries?.present
|
|
263
|
+
? Boolean(discoveryBackendBoundaries.hasBackendGroup)
|
|
264
|
+
: backendPatternOrganization;
|
|
265
|
+
// No silent skip: when the source pattern matched but discovery deferred the
|
|
266
|
+
// boundary to the client (browser auth), record why the requirement lifted.
|
|
267
|
+
const backendIdentityDeferredToClient =
|
|
268
|
+
Boolean(discoveryBackendBoundaries?.present) &&
|
|
269
|
+
backendPatternIdentity &&
|
|
270
|
+
!backendIdentityRequired;
|
|
271
|
+
const backendOrganizationDeferredToClient =
|
|
272
|
+
Boolean(discoveryBackendBoundaries?.present) &&
|
|
273
|
+
backendPatternOrganization &&
|
|
274
|
+
!backendOrganizationRequired;
|
|
275
|
+
const backendLogoutRequired =
|
|
276
|
+
backendPresent && logoutBoundaryPattern.test(backendCombined);
|
|
277
|
+
const backendMissingApis = [
|
|
278
|
+
...(backendIdentityRequired &&
|
|
279
|
+
!backendFoundApiNames.includes("cluebase.identify")
|
|
280
|
+
? ["cluebase.identify"]
|
|
281
|
+
: []),
|
|
282
|
+
...(backendLogoutRequired && !backendFoundApiNames.includes("cluebase.reset")
|
|
283
|
+
? ["cluebase.reset"]
|
|
284
|
+
: []),
|
|
285
|
+
...(backendOrganizationRequired &&
|
|
286
|
+
!backendFoundApiNames.includes("cluebase.group")
|
|
287
|
+
? ["cluebase.group"]
|
|
288
|
+
: []),
|
|
289
|
+
];
|
|
290
|
+
const frontendIdentityRequired =
|
|
291
|
+
frontendSources.length > 0 &&
|
|
292
|
+
frontendIdentityBoundaryPattern.test(frontendCombined);
|
|
293
|
+
const frontendLogoutRequired =
|
|
294
|
+
frontendSources.length > 0 && logoutBoundaryPattern.test(frontendCombined);
|
|
295
|
+
const frontendOrganizationRequired =
|
|
296
|
+
frontendSources.length > 0 && companyBoundaryPattern.test(frontendCombined);
|
|
297
|
+
const frontendLifecycleRequired =
|
|
298
|
+
frontendLifecyclePresent ||
|
|
299
|
+
frontendIdentityRequired ||
|
|
300
|
+
frontendOrganizationRequired ||
|
|
301
|
+
frontendLogoutRequired;
|
|
302
|
+
const frontendMissingApis = [
|
|
303
|
+
...(frontendLifecycleRequired &&
|
|
304
|
+
!frontendFoundApiNames.includes("cluebase.init")
|
|
305
|
+
? ["cluebase.init"]
|
|
306
|
+
: []),
|
|
307
|
+
...(frontendIdentityRequired &&
|
|
308
|
+
!frontendFoundApiNames.includes("cluebase.identify")
|
|
309
|
+
? ["cluebase.identify"]
|
|
310
|
+
: []),
|
|
311
|
+
...(frontendOrganizationRequired &&
|
|
312
|
+
!frontendFoundApiNames.includes("cluebase.group")
|
|
313
|
+
? ["cluebase.group"]
|
|
314
|
+
: []),
|
|
315
|
+
...(frontendLogoutRequired && !frontendFoundApiNames.includes("cluebase.reset")
|
|
316
|
+
? ["cluebase.reset"]
|
|
317
|
+
: []),
|
|
318
|
+
];
|
|
319
|
+
const requiredApis = [
|
|
320
|
+
...(backendPresent || frontendSources.length > 0 ? ["cluebase.init"] : []),
|
|
321
|
+
...(backendIdentityRequired || frontendIdentityRequired
|
|
322
|
+
? ["cluebase.identify"]
|
|
323
|
+
: []),
|
|
324
|
+
...(backendOrganizationRequired || frontendOrganizationRequired
|
|
325
|
+
? ["cluebase.group"]
|
|
326
|
+
: []),
|
|
327
|
+
...(backendLogoutRequired || frontendLogoutRequired ? ["cluebase.reset"] : []),
|
|
328
|
+
];
|
|
329
|
+
missingApis = [...new Set(requiredApis)].filter(
|
|
330
|
+
(api) => !foundApis.includes(api),
|
|
331
|
+
);
|
|
332
|
+
return {
|
|
333
|
+
foundApis,
|
|
334
|
+
missingApis,
|
|
335
|
+
required_apis: [...new Set(requiredApis)],
|
|
336
|
+
backend_lifecycle: {
|
|
337
|
+
backend_present: backendPresent,
|
|
338
|
+
backend_root_paths: backendRootPaths,
|
|
339
|
+
dependency_files: dependencySources.map((source) => source.file_path),
|
|
340
|
+
expected_sdk_packages: backendSpec.packages,
|
|
341
|
+
expected_sdk_imports: backendSpec.imports,
|
|
342
|
+
sdk_installability_status: backendSpec.installabilityStatus,
|
|
343
|
+
sdk_installability_verified: backendSdkInstallabilityVerified,
|
|
344
|
+
sdk_blocker: backendSdkInstallabilityVerified
|
|
345
|
+
? null
|
|
346
|
+
: backendSpec.blocker,
|
|
347
|
+
sdk_dependency_present: backendSdkDependencyPresent,
|
|
348
|
+
sdk_import_present: backendSdkImportPresent,
|
|
349
|
+
sdk_dependency_or_import_present: backendSdkPresent,
|
|
350
|
+
sdk_init_present: backendInitPresent,
|
|
351
|
+
sdk_init_missing_service_key_files: backendSdkInitMissingServiceKeyFiles,
|
|
352
|
+
fastapi_missing_middleware_files: fastapiMissingMiddlewareFiles,
|
|
353
|
+
cluebase_env_required_index_files: backendCluebaseEnvRequiredIndexFiles,
|
|
354
|
+
hardcoded_lifecycle_identity_files:
|
|
355
|
+
hardcodedLifecycleIdentityFiles.filter((filePath) =>
|
|
356
|
+
backendRootPaths.some((root) => startsWithRoot(filePath, root)),
|
|
357
|
+
),
|
|
358
|
+
repeated_identity_helper_files:
|
|
359
|
+
repeatedIdentityHelperFiles.filter((filePath) =>
|
|
360
|
+
backendRootPaths.some((root) => startsWithRoot(filePath, root)),
|
|
361
|
+
),
|
|
362
|
+
lifecycle_read_endpoint_files: backendLifecycleReadEndpointFiles,
|
|
363
|
+
required_apis: [
|
|
364
|
+
...(backendIdentityRequired ? ["cluebase.identify"] : []),
|
|
365
|
+
...(backendOrganizationRequired ? ["cluebase.group"] : []),
|
|
366
|
+
...(backendLogoutRequired ? ["cluebase.reset"] : []),
|
|
367
|
+
],
|
|
368
|
+
missing_apis: backendMissingApis,
|
|
369
|
+
identity_deferred_to_client: backendIdentityDeferredToClient,
|
|
370
|
+
organization_deferred_to_client: backendOrganizationDeferredToClient,
|
|
371
|
+
lifecycle_deferral_reason:
|
|
372
|
+
backendIdentityDeferredToClient || backendOrganizationDeferredToClient
|
|
373
|
+
? "discovery determined the user's identity / active organization is established client-side (browser auth); the backend has no one-time auth boundary, so backend cluebase.identify / cluebase.group are not required here. Frontend identify / group coverage is still verified."
|
|
374
|
+
: null,
|
|
375
|
+
},
|
|
376
|
+
frontend_lifecycle: {
|
|
377
|
+
lifecycle_present: frontendLifecyclePresent,
|
|
378
|
+
found_apis: frontendFoundApiNames,
|
|
379
|
+
expected_sdk_package: FRONTEND_SDK_PACKAGE,
|
|
380
|
+
sdk_dependency_present: frontendSdkDependencyPresent,
|
|
381
|
+
sdk_import_present: frontendSdkImportPresent,
|
|
382
|
+
sdk_dependency_or_import_present: frontendSdkPresent,
|
|
383
|
+
lifecycle_files_without_verified_sdk:
|
|
384
|
+
frontendLifecycleFilesWithoutVerifiedSdk,
|
|
385
|
+
wrong_sdk_packages: [],
|
|
386
|
+
frontend_lifecycle_non_public_env_files:
|
|
387
|
+
frontendLifecycleNonPublicEnvFiles,
|
|
388
|
+
cluebase_init_empty_env_fallback_files: cluebaseInitEmptyEnvFallbackFiles,
|
|
389
|
+
frontend_init_before_cluebase_init_files: frontendInitBeforeCluebaseInitCallFiles,
|
|
390
|
+
backend_trace_propagation_missing_files:
|
|
391
|
+
frontendBackendTracePropagationMissingFiles,
|
|
392
|
+
next_lifecycle_missing_use_client_files:
|
|
393
|
+
nextLifecycleMissingUseClientFiles,
|
|
394
|
+
lifecycle_read_side_effect_files: frontendLifecycleReadSideEffectFiles,
|
|
395
|
+
hardcoded_lifecycle_identity_files:
|
|
396
|
+
hardcodedLifecycleIdentityFiles.filter(
|
|
397
|
+
(filePath) =>
|
|
398
|
+
!backendRootPaths.some((root) => startsWithRoot(filePath, root)),
|
|
399
|
+
),
|
|
400
|
+
repeated_identity_helper_files:
|
|
401
|
+
repeatedIdentityHelperFiles.filter(
|
|
402
|
+
(filePath) =>
|
|
403
|
+
!backendRootPaths.some((root) => startsWithRoot(filePath, root)),
|
|
404
|
+
),
|
|
405
|
+
required_apis: [
|
|
406
|
+
...(frontendLifecycleRequired ? ["cluebase.init"] : []),
|
|
407
|
+
...(frontendIdentityRequired ? ["cluebase.identify"] : []),
|
|
408
|
+
...(frontendOrganizationRequired ? ["cluebase.group"] : []),
|
|
409
|
+
...(frontendLogoutRequired ? ["cluebase.reset"] : []),
|
|
410
|
+
],
|
|
411
|
+
missing_apis: frontendMissingApis,
|
|
412
|
+
},
|
|
413
|
+
has_noop_wrapper: noOpPattern.test(combined),
|
|
414
|
+
component_lifecycle_init_files: componentLifecycleInitFiles,
|
|
415
|
+
auth_callback_lifecycle_init_files: authCallbackLifecycleInitFiles,
|
|
416
|
+
blocking_lifecycle_files: blockingLifecycleFiles,
|
|
417
|
+
blocking_lifecycle_calls: blockingLifecycleCalls,
|
|
418
|
+
unguarded_lifecycle_files: blockingLifecycleFiles,
|
|
419
|
+
unguarded_lifecycle_calls: blockingLifecycleCalls,
|
|
420
|
+
passed:
|
|
421
|
+
missingApis.length === 0 &&
|
|
422
|
+
backendSdkPresent &&
|
|
423
|
+
backendSdkInstallabilityVerified &&
|
|
424
|
+
backendInitPresent &&
|
|
425
|
+
backendSdkInitMissingServiceKeyFiles.length === 0 &&
|
|
426
|
+
fastapiMissingMiddlewareFiles.length === 0 &&
|
|
427
|
+
backendCluebaseEnvRequiredIndexFiles.length === 0 &&
|
|
428
|
+
hardcodedLifecycleIdentityFiles.length === 0 &&
|
|
429
|
+
repeatedIdentityHelperFiles.length === 0 &&
|
|
430
|
+
backendLifecycleReadEndpointFiles.length === 0 &&
|
|
431
|
+
backendMissingApis.length === 0 &&
|
|
432
|
+
frontendMissingApis.length === 0 &&
|
|
433
|
+
frontendSdkPresent &&
|
|
434
|
+
frontendLifecycleFilesWithoutVerifiedSdk.length === 0 &&
|
|
435
|
+
frontendLifecycleNonPublicEnvFiles.length === 0 &&
|
|
436
|
+
cluebaseInitEmptyEnvFallbackFiles.length === 0 &&
|
|
437
|
+
frontendInitBeforeCluebaseInitCallFiles.length === 0 &&
|
|
438
|
+
frontendBackendTracePropagationMissingFiles.length === 0 &&
|
|
439
|
+
nextLifecycleMissingUseClientFiles.length === 0 &&
|
|
440
|
+
authCallbackLifecycleInitFiles.length === 0 &&
|
|
441
|
+
frontendLifecycleReadSideEffectFiles.length === 0 &&
|
|
442
|
+
!noOpPattern.test(combined) &&
|
|
443
|
+
componentLifecycleInitFiles.length === 0 &&
|
|
444
|
+
blockingLifecycleFiles.length === 0,
|
|
445
|
+
};
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
export const runSetupCheck = async ({
|
|
449
|
+
repoRoot,
|
|
450
|
+
request,
|
|
451
|
+
requireSdkLifecycle = false,
|
|
452
|
+
}) => {
|
|
453
|
+
const resolvedRepoRoot = resolve(repoRoot ?? ".");
|
|
454
|
+
const checks = [];
|
|
455
|
+
const setupManifest = await readSetupManifest(resolvedRepoRoot);
|
|
456
|
+
const manifestContract = validateSetupManifestContract(setupManifest);
|
|
457
|
+
addCheck(
|
|
458
|
+
checks,
|
|
459
|
+
"setup_manifest_contract",
|
|
460
|
+
!(requireSdkLifecycle || setupManifest) ||
|
|
461
|
+
(manifestContract.checked && manifestContract.findings.length === 0),
|
|
462
|
+
manifestContract.checked
|
|
463
|
+
? manifestContract.findings.length === 0
|
|
464
|
+
? "setup manifest contains current AI setup responsibility boundaries"
|
|
465
|
+
: "setup manifest is missing current AI setup responsibility boundaries"
|
|
466
|
+
: "setup manifest contract was not checked because .cluebase/setup-manifest.json is absent or unreadable",
|
|
467
|
+
{
|
|
468
|
+
checked: manifestContract.checked,
|
|
469
|
+
findings: manifestContract.findings,
|
|
470
|
+
},
|
|
471
|
+
);
|
|
472
|
+
|
|
473
|
+
const missingSetupSteps = await validateSetupStepFiles({
|
|
474
|
+
repoRoot: resolvedRepoRoot,
|
|
475
|
+
});
|
|
476
|
+
addCheck(
|
|
477
|
+
checks,
|
|
478
|
+
"setup_step_files",
|
|
479
|
+
missingSetupSteps.length === 0,
|
|
480
|
+
missingSetupSteps.length === 0
|
|
481
|
+
? "nine setup step files are installed"
|
|
482
|
+
: "setup step files are missing",
|
|
483
|
+
{ missing_files: missingSetupSteps },
|
|
484
|
+
);
|
|
485
|
+
|
|
486
|
+
const sourcePaths = await setupSourcePaths({
|
|
487
|
+
repoRoot: resolvedRepoRoot,
|
|
488
|
+
request,
|
|
489
|
+
includeFrontend: requireSdkLifecycle,
|
|
490
|
+
setupManifest,
|
|
491
|
+
});
|
|
492
|
+
const excludedPaths = request?.excluded_source_paths ?? [];
|
|
493
|
+
const sources = await readAllowedSourceText({
|
|
494
|
+
repoRoot: resolvedRepoRoot,
|
|
495
|
+
allowedSourcePaths: sourcePaths,
|
|
496
|
+
excludedSourcePaths: excludedPaths,
|
|
497
|
+
});
|
|
498
|
+
const dependencySources = await readDependencyText({
|
|
499
|
+
repoRoot: resolvedRepoRoot,
|
|
500
|
+
roots: sourcePaths,
|
|
501
|
+
});
|
|
502
|
+
const frontendConfigSources = requireSdkLifecycle
|
|
503
|
+
? await readFrontendConfigText({
|
|
504
|
+
repoRoot: resolvedRepoRoot,
|
|
505
|
+
roots: sourcePaths,
|
|
506
|
+
})
|
|
507
|
+
: [];
|
|
508
|
+
const secretLeaks = findSecretLeaks([
|
|
509
|
+
...sources,
|
|
510
|
+
...dependencySources,
|
|
511
|
+
...frontendConfigSources,
|
|
512
|
+
]);
|
|
513
|
+
addCheck(
|
|
514
|
+
checks,
|
|
515
|
+
"no_secret_values",
|
|
516
|
+
secretLeaks.length === 0,
|
|
517
|
+
"no obvious secret values found",
|
|
518
|
+
{
|
|
519
|
+
files: secretLeaks,
|
|
520
|
+
},
|
|
521
|
+
);
|
|
522
|
+
|
|
523
|
+
if (requireSdkLifecycle) {
|
|
524
|
+
const backendRootPaths = setupBackendRootPaths(request);
|
|
525
|
+
const discoveries = await readDiscoveries(resolvedRepoRoot);
|
|
526
|
+
const discoveryBackendBoundaries = backendBoundariesFromDiscovery(
|
|
527
|
+
discoveries,
|
|
528
|
+
backendRootPaths,
|
|
529
|
+
);
|
|
530
|
+
const sdkLifecycle = checkSdkLifecycle({
|
|
531
|
+
backendRootPaths,
|
|
532
|
+
dependencySources,
|
|
533
|
+
discoveryBackendBoundaries,
|
|
534
|
+
framework: request?.framework,
|
|
535
|
+
sources,
|
|
536
|
+
});
|
|
537
|
+
addCheck(
|
|
538
|
+
checks,
|
|
539
|
+
"sdk_lifecycle",
|
|
540
|
+
sdkLifecycle.passed,
|
|
541
|
+
"static SDK lifecycle references are present and non-blocking; dependency install, import, app startup, and event delivery are not verified by this check",
|
|
542
|
+
{
|
|
543
|
+
found_apis: sdkLifecycle.foundApis,
|
|
544
|
+
missing_apis: sdkLifecycle.missingApis,
|
|
545
|
+
required_apis: sdkLifecycle.required_apis,
|
|
546
|
+
verification_scope:
|
|
547
|
+
"static_source_only_not_dependency_install_or_runtime_event_delivery",
|
|
548
|
+
backend_lifecycle: sdkLifecycle.backend_lifecycle,
|
|
549
|
+
frontend_lifecycle: sdkLifecycle.frontend_lifecycle,
|
|
550
|
+
has_noop_wrapper: sdkLifecycle.has_noop_wrapper,
|
|
551
|
+
component_lifecycle_init_files:
|
|
552
|
+
sdkLifecycle.component_lifecycle_init_files,
|
|
553
|
+
auth_callback_lifecycle_init_files:
|
|
554
|
+
sdkLifecycle.auth_callback_lifecycle_init_files,
|
|
555
|
+
blocking_lifecycle_files: sdkLifecycle.blocking_lifecycle_files,
|
|
556
|
+
blocking_lifecycle_calls: sdkLifecycle.blocking_lifecycle_calls,
|
|
557
|
+
unguarded_lifecycle_files: sdkLifecycle.unguarded_lifecycle_files,
|
|
558
|
+
unguarded_lifecycle_calls: sdkLifecycle.unguarded_lifecycle_calls,
|
|
559
|
+
},
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
const passed = checks.every((check) => check.passed);
|
|
564
|
+
return {
|
|
565
|
+
passed,
|
|
566
|
+
static_passed: passed,
|
|
567
|
+
completion_status:
|
|
568
|
+
passed && requireSdkLifecycle
|
|
569
|
+
? "static_passed_runtime_verification_required"
|
|
570
|
+
: passed
|
|
571
|
+
? "passed"
|
|
572
|
+
: "failed",
|
|
573
|
+
checks,
|
|
574
|
+
};
|
|
575
|
+
};
|