mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-29 04:25:52 +01:00
Merge master into canonical Runner D delivery
Resolve the migration sequence against current master while preserving the audited Runner D data migration. Type the native backend factory mock so the merged TypeScript checks retain its two-argument contract. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
commit
83a1c438e0
227 files changed
+64329
-3014
No files matched your search
@@ -94,6 +94,12 @@ export const cancelEnvironmentCustomImageSetupSessionSchema = z.object({
|
||||
export type CancelEnvironmentCustomImageSetupSession =
|
||||
z.infer<typeof cancelEnvironmentCustomImageSetupSessionSchema>;
|
||||
|
||||
export const relinkEnvironmentCustomImageTemplateSchema = z.object({
|
||||
confirmBootSourceDrift: z.boolean().optional().default(false),
|
||||
}).strict();
|
||||
export type RelinkEnvironmentCustomImageTemplate =
|
||||
z.infer<typeof relinkEnvironmentCustomImageTemplateSchema>;
|
||||
|
||||
export const createEnvironmentCustomImageTerminalSessionTokenSchema = z.object({}).strict().default({});
|
||||
export type CreateEnvironmentCustomImageTerminalSessionToken =
|
||||
z.infer<typeof createEnvironmentCustomImageTerminalSessionTokenSchema>;
|
||||
|
||||
@@ -95,6 +95,7 @@ export {
|
||||
startEnvironmentCustomImageSetupSessionSchema,
|
||||
finishEnvironmentCustomImageSetupSessionSchema,
|
||||
cancelEnvironmentCustomImageSetupSessionSchema,
|
||||
relinkEnvironmentCustomImageTemplateSchema,
|
||||
createEnvironmentCustomImageTerminalSessionTokenSchema,
|
||||
environmentCustomImageTerminalSessionTokenSchema,
|
||||
type EnvironmentCustomImageSetupConnectionSummary,
|
||||
@@ -103,6 +104,7 @@ export {
|
||||
type StartEnvironmentCustomImageSetupSession,
|
||||
type FinishEnvironmentCustomImageSetupSession,
|
||||
type CancelEnvironmentCustomImageSetupSession,
|
||||
type RelinkEnvironmentCustomImageTemplate,
|
||||
type CreateEnvironmentCustomImageTerminalSessionToken,
|
||||
type EnvironmentCustomImageTerminalSessionToken,
|
||||
} from "./environment-custom-images.js";
|
||||
|
||||
@@ -313,18 +313,29 @@ describe("sandbox provider capability declaration validators", () => {
|
||||
expect(rejected.success).toBe(false);
|
||||
});
|
||||
|
||||
it("test_removed_concurrency_capabilities_are_rejected_as_unknown_keys", () => {
|
||||
// The concurrency flags left the public contract because no runtime path
|
||||
// enforced them. The strict schema now rejects them, so a manifest cannot
|
||||
// declare a capability the host does not honor.
|
||||
for (const key of ["concurrentSyncAndExec", "concurrentSyncOperations"]) {
|
||||
const rejected = pluginManifestV1Schema.safeParse(
|
||||
buildSandboxProviderManifest({
|
||||
sandboxCapabilities: { [key]: true },
|
||||
}),
|
||||
);
|
||||
expect(rejected.success).toBe(false);
|
||||
}
|
||||
it("test_manifest_accepts_concurrent_sync_operations_capability", () => {
|
||||
// A provider opts in to parallel bidirectional file sync with this key. The
|
||||
// strict schema accepts it and keeps the declared value.
|
||||
const parsed = pluginManifestV1Schema.parse(
|
||||
buildSandboxProviderManifest({
|
||||
sandboxCapabilities: { concurrentSyncOperations: true },
|
||||
}),
|
||||
);
|
||||
|
||||
expect(parsed.environmentDrivers?.[0]?.sandboxCapabilities).toEqual({
|
||||
concurrentSyncOperations: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("test_manifest_rejects_unknown_sync_concurrency_capability_key", () => {
|
||||
// A neighboring but unknown concurrency key must fail validation, not drop
|
||||
// silently. The strict schema rejects a capability the host does not honor.
|
||||
const rejected = pluginManifestV1Schema.safeParse(
|
||||
buildSandboxProviderManifest({
|
||||
sandboxCapabilities: { concurrentSyncAndExec: true },
|
||||
}),
|
||||
);
|
||||
expect(rejected.success).toBe(false);
|
||||
});
|
||||
|
||||
it("test_supports_reusable_leases_compat_maps_to_reusable_leases", () => {
|
||||
@@ -350,3 +361,64 @@ describe("sandbox provider capability declaration validators", () => {
|
||||
expect(resolveDeclaredSandboxCapabilities(driver!).reusableLeases).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("login pty transport capability and legacy alias", () => {
|
||||
it("test_login_pty_new_field_parses_and_carries_the_flag", () => {
|
||||
const parsed = pluginManifestV1Schema.parse(
|
||||
buildSandboxProviderManifest({ supportsLoginPty: true }),
|
||||
);
|
||||
|
||||
expect(parsed.environmentDrivers?.[0]?.supportsLoginPty).toBe(true);
|
||||
});
|
||||
|
||||
it("test_legacy_alias_only_canonicalizes_onto_login_pty", () => {
|
||||
const parsed = pluginManifestV1Schema.parse(
|
||||
buildSandboxProviderManifest({ supportsSetupTokenLogin: true }),
|
||||
);
|
||||
const driver = parsed.environmentDrivers?.[0];
|
||||
|
||||
// The validator maps the deprecated alias onto the canonical field at parse
|
||||
// time, and it drops the alias so a downstream reader cannot read the old
|
||||
// name.
|
||||
expect(driver?.supportsLoginPty).toBe(true);
|
||||
expect(driver).not.toHaveProperty("supportsSetupTokenLogin");
|
||||
});
|
||||
|
||||
it("test_conflicting_alias_and_new_field_reject", () => {
|
||||
const rejected = pluginManifestV1Schema.safeParse(
|
||||
buildSandboxProviderManifest({
|
||||
supportsLoginPty: true,
|
||||
supportsSetupTokenLogin: false,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(rejected.success).toBe(false);
|
||||
});
|
||||
|
||||
it("test_matching_alias_and_new_field_pass", () => {
|
||||
const parsed = pluginManifestV1Schema.parse(
|
||||
buildSandboxProviderManifest({
|
||||
supportsLoginPty: true,
|
||||
supportsSetupTokenLogin: true,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(parsed.environmentDrivers?.[0]?.supportsLoginPty).toBe(true);
|
||||
});
|
||||
|
||||
it("test_unknown_login_field_misspelling_rejects", () => {
|
||||
const rejected = pluginManifestV1Schema.safeParse(
|
||||
buildSandboxProviderManifest({ supportsLoginPTY: true }),
|
||||
);
|
||||
|
||||
expect(rejected.success).toBe(false);
|
||||
});
|
||||
|
||||
it("test_login_pty_accepts_literal_booleans_only", () => {
|
||||
const rejected = pluginManifestV1Schema.safeParse(
|
||||
buildSandboxProviderManifest({ supportsLoginPty: "true" }),
|
||||
);
|
||||
|
||||
expect(rejected.success).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -157,8 +157,8 @@ export const pluginEnvironmentTemplateConfigBindingSchema = z.object({
|
||||
|
||||
// The nested sandbox capability declaration is `.strict()` so an unknown
|
||||
// capability key is a validation error, not a silently dropped field. The outer
|
||||
// driver schema below is non-strict and drops unknown top-level keys, so the
|
||||
// declaration itself is the gate that a typo in a capability name cannot pass.
|
||||
// driver schema below is also `.strict()`, so a typo in a top-level capability
|
||||
// name cannot pass either.
|
||||
export const sandboxProviderCapabilitiesSchema = z.object({
|
||||
reusableLeases: z.boolean().optional(),
|
||||
nativeSyncIn: z.boolean().optional(),
|
||||
@@ -166,10 +166,15 @@ export const sandboxProviderCapabilitiesSchema = z.object({
|
||||
persistentProcessSessions: z.boolean().optional(),
|
||||
independentControlCommands: z.boolean().optional(),
|
||||
incrementalSessionOutput: z.boolean().optional(),
|
||||
concurrentSyncOperations: z.boolean().optional(),
|
||||
duplexCommandStream: z.boolean().optional(),
|
||||
}).strict();
|
||||
|
||||
export type SandboxProviderCapabilitiesInput = z.infer<typeof sandboxProviderCapabilitiesSchema>;
|
||||
|
||||
// The driver declaration schema is `.strict()` so an unknown top-level key is a
|
||||
// validation error, not a silently dropped field. A misspelled capability name
|
||||
// (for example `supportsLoginPTY`) fails validation instead of dropping.
|
||||
export const pluginEnvironmentDriverDeclarationSchema = z.object({
|
||||
driverKey: z.string().min(1).regex(
|
||||
/^[a-z0-9][a-z0-9._-]*$/,
|
||||
@@ -187,8 +192,38 @@ export const pluginEnvironmentDriverDeclarationSchema = z.object({
|
||||
templateConfigBinding: pluginEnvironmentTemplateConfigBindingSchema.optional(),
|
||||
templateIdentityPaths: z.array(z.string().min(1).max(200)).max(20).optional(),
|
||||
supportsTemplateDelete: z.boolean().optional(),
|
||||
// The neutral transport capability: does the provider host an interactive
|
||||
// login on a real pseudo-terminal? Validate a literal boolean only.
|
||||
supportsLoginPty: z.boolean().optional(),
|
||||
// Deprecated alias for `supportsLoginPty`. It exists only so an external
|
||||
// plugin manifest that declares the old name still loads. Validate a literal
|
||||
// boolean only. The transform below canonicalizes it onto `supportsLoginPty`
|
||||
// and drops it, so every downstream reader reads only the canonical field.
|
||||
supportsSetupTokenLogin: z.boolean().optional(),
|
||||
configSchema: jsonSchemaSchema,
|
||||
}).strict().superRefine((driver, ctx) => {
|
||||
// Reject a manifest that carries both the alias and the canonical field with
|
||||
// different values. Matching values pass. A single field passes.
|
||||
if (
|
||||
driver.supportsLoginPty !== undefined
|
||||
&& driver.supportsSetupTokenLogin !== undefined
|
||||
&& driver.supportsLoginPty !== driver.supportsSetupTokenLogin
|
||||
) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message:
|
||||
"supportsSetupTokenLogin is a deprecated alias for supportsLoginPty; the two flags must not carry different values",
|
||||
path: ["supportsLoginPty"],
|
||||
});
|
||||
}
|
||||
}).transform((driver) => {
|
||||
// Canonicalize before any authorization or projection reads the value. The
|
||||
// canonical field wins; the alias fills it in when only the alias is present.
|
||||
// Drop the alias so every downstream reader reads only `supportsLoginPty`. An
|
||||
// undefined value is omitted on JSON serialization, so a driver that declares
|
||||
// neither field carries no login flag.
|
||||
const { supportsSetupTokenLogin, ...rest } = driver;
|
||||
return { ...rest, supportsLoginPty: rest.supportsLoginPty ?? supportsSetupTokenLogin };
|
||||
});
|
||||
|
||||
export type PluginEnvironmentDriverDeclarationInput = z.infer<
|
||||
|
||||
Reference in new issue
Block a user