The onboarding scan started on its own, discarded what it found, claimed to read email and calendars, simulated progress for everyone who could not scan, and its project creation always failed (wrong Space fields and a space lookup by title across tenants). Discovery now asks first and creates nothing without a choice: - The scan step appears only for the deployment owner when ONBOARDING_SCAN_HOME is set; everyone else skips it. The simulated scan is gone. - It shows the folder and asks. Only directory listings are read: git projects up to four levels deep, their languages and a few tools (Docker, GitHub Actions, Terraform). Symlinks are never followed and each chosen folder is resolved again before use, so nothing outside the folder is reached. Results stay with the account that scanned. - Found projects are a checklist (none preselected); found languages and tools are facts (preselected). "Add" creates one project space per chosen project with createSpaceForMember and wireProjectWorkspace in the user's own organization, reusing the user's own space already linked to that folder, and saves the chosen facts as onboarding facts under the profile lock and fact limit. - When a connection becomes connected (direct connect, authorization round trip, or catalog reconciliation), "Uses <App>" is remembered as a tools fact from the connector. Revoking keeps it. The E2E harness now points ONBOARDING_SCAN_HOME at a fixture folder and starts without a deployment owner, so owner-only onboarding steps appear only in specs that take the seat through /__e2e/deployment-owner. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
50 lines
1.9 KiB
TypeScript
50 lines
1.9 KiB
TypeScript
import * as z from "zod";
|
|
import { Id } from "./ids.js";
|
|
|
|
/** Upper bounds on one scan's choices; the server lists at most this many projects and facts. */
|
|
export const ONBOARDING_SCAN_MAX_PROJECTS = 30;
|
|
export const ONBOARDING_SCAN_MAX_FACTS = 20;
|
|
|
|
/** The folder a consented scan would read, or null when this account cannot scan. */
|
|
export const OnboardingScanTargetSchema = z.object({ folder: z.string().nullable() });
|
|
export type OnboardingScanTarget = z.infer<typeof OnboardingScanTargetSchema>;
|
|
|
|
export const OnboardingScanProjectSchema = z.object({
|
|
id: z.string(),
|
|
name: z.string(),
|
|
/** Relative to the scanned folder. */
|
|
path: z.string(),
|
|
});
|
|
export type OnboardingScanProject = z.infer<typeof OnboardingScanProjectSchema>;
|
|
|
|
export const OnboardingScanFactSchema = z.object({
|
|
id: z.string(),
|
|
/** A language or tool name, e.g. "TypeScript". */
|
|
label: z.string(),
|
|
});
|
|
export type OnboardingScanFact = z.infer<typeof OnboardingScanFactSchema>;
|
|
|
|
export const OnboardingScanStatusSchema = z.discriminatedUnion("status", [
|
|
z.object({ status: z.literal("scanning") }),
|
|
z.object({ status: z.literal("failed") }),
|
|
z.object({
|
|
status: z.literal("complete"),
|
|
projects: z.array(OnboardingScanProjectSchema),
|
|
facts: z.array(OnboardingScanFactSchema),
|
|
}),
|
|
]);
|
|
export type OnboardingScanStatus = z.infer<typeof OnboardingScanStatusSchema>;
|
|
|
|
export const OnboardingScanApplyInputSchema = z.object({
|
|
scanId: z.string(),
|
|
projectIds: z.array(z.string()).max(ONBOARDING_SCAN_MAX_PROJECTS),
|
|
factIds: z.array(z.string()).max(ONBOARDING_SCAN_MAX_FACTS),
|
|
});
|
|
export type OnboardingScanApplyInput = z.infer<typeof OnboardingScanApplyInputSchema>;
|
|
|
|
export const OnboardingScanApplyResultSchema = z.object({
|
|
spaces: z.array(z.object({ id: Id, name: z.string() })),
|
|
factsAdded: z.number().int().nonnegative(),
|
|
});
|
|
export type OnboardingScanApplyResult = z.infer<typeof OnboardingScanApplyResultSchema>;
|