NODEDC_PLATFORM/packages/external-provider-contract/test/engine-private-extension.te...

371 lines
13 KiB
JavaScript

import assert from "node:assert/strict";
import {
ENGINE_PRIVATE_EXTENSION_APPLY_RECEIPT_SCHEMA_VERSION,
ENGINE_PRIVATE_EXTENSION_APPLY_REQUEST_SCHEMA_VERSION,
ENGINE_PRIVATE_EXTENSION_CREDENTIAL_TYPES,
ENGINE_PRIVATE_EXTENSION_INACTIVE_BASELINE,
ENGINE_PRIVATE_EXTENSION_MANAGE_CAPABILITY,
ENGINE_PRIVATE_EXTENSION_NODE_TYPES,
ENGINE_PRIVATE_EXTENSION_OPERATION_SCHEMA_VERSION,
ENGINE_PRIVATE_EXTENSION_PACKAGE_NAME,
ENGINE_PRIVATE_EXTENSION_PLAN_REQUEST_SCHEMA_VERSION,
ENGINE_PRIVATE_EXTENSION_PLAN_SCHEMA_VERSION,
ENGINE_PRIVATE_EXTENSION_READ_CAPABILITY,
ENGINE_PRIVATE_EXTENSION_STATUS_SCHEMA_VERSION,
authorizeEnginePrivateExtensionOperation,
computeEnginePrivateExtensionPlanHash,
validateEnginePrivateExtensionApplyReceipt,
validateEnginePrivateExtensionApplyRequest,
validateEnginePrivateExtensionOperation,
validateEnginePrivateExtensionPlan,
validateEnginePrivateExtensionPlanRequest,
validateEnginePrivateExtensionStatus,
} from "../src/index.mjs";
const digest = "03413c6f3c706a1f6a9597a1cf1730504e9719228d0c77fdfb93bbdcc57a4cf3";
const release = Object.freeze({
kind: "release",
packageName: ENGINE_PRIVATE_EXTENSION_PACKAGE_NAME,
releaseId: "0.1.0-03413c6f3c706a1f",
packageSha256: digest,
});
const inactive = Object.freeze({
kind: "inactive-baseline",
packageName: ENGINE_PRIVATE_EXTENSION_PACKAGE_NAME,
baselineId: ENGINE_PRIVATE_EXTENSION_INACTIVE_BASELINE,
});
const manage = [ENGINE_PRIVATE_EXTENSION_MANAGE_CAPABILITY];
const activateRequest = {
schemaVersion: ENGINE_PRIVATE_EXTENSION_PLAN_REQUEST_SCHEMA_VERSION,
requestId: "extension-request-20260715-001",
idempotencyKey: "extension-request-20260715-001",
action: "activate",
requestedAt: "2026-07-15T18:00:00.000Z",
requestExpiresAt: "2026-07-15T18:10:00.000Z",
expectedCurrentGeneration: 0,
target: release,
};
assert.deepEqual(
validateEnginePrivateExtensionPlanRequest(activateRequest, {
now: "2026-07-15T18:00:01.000Z",
grantedCapabilities: manage,
}),
{ ok: true, errors: [] },
);
assert.equal(
validateEnginePrivateExtensionPlanRequest(activateRequest, {
now: "2026-07-15T18:00:01.000Z",
grantedCapabilities: ["engine.l2.deploy"],
}).errors.includes("engine_private_extension_capability_required"),
true,
);
assert.equal(authorizeEnginePrivateExtensionOperation("status", [ENGINE_PRIVATE_EXTENSION_READ_CAPABILITY]).ok, true);
assert.equal(authorizeEnginePrivateExtensionOperation("apply", [ENGINE_PRIVATE_EXTENSION_READ_CAPABILITY]).ok, false);
const activatePlan = withPlanHash({
schemaVersion: ENGINE_PRIVATE_EXTENSION_PLAN_SCHEMA_VERSION,
planId: "extension-plan-20260715-001",
planHash: hash("0"),
requestId: activateRequest.requestId,
idempotencyKey: activateRequest.idempotencyKey,
action: "activate",
createdAt: "2026-07-15T18:00:01.000Z",
expiresAt: "2026-07-15T18:09:00.000Z",
singleUse: true,
requiredCapability: ENGINE_PRIVATE_EXTENSION_MANAGE_CAPABILITY,
expectedCurrentGeneration: 0,
nextGeneration: 1,
currentState: inactive,
targetState: release,
recoveryState: inactive,
actions: [
"verify_staged_immutable_release",
"prepare_sealed_package_tree",
"verify_community_package_loader_policy",
"quiesce_deploy_run_and_drain_queue",
"record_recovery_state",
"atomic_switch_current",
"force_recreate_main_workers_webhooks_as_version_barrier",
"verify_exact_runtime_acceptance",
"commit_active_state",
"resume_deploy_run",
],
transition: transition(),
acceptance: acceptanceSpec(release),
failurePolicy: failurePolicy(),
});
assert.deepEqual(validateEnginePrivateExtensionPlan(activatePlan, { request: activateRequest }), { ok: true, errors: [] });
assert.equal(
validateEnginePrivateExtensionPlan({ ...activatePlan, nextGeneration: 2 }).errors.includes("nextGeneration_must_increment_current_generation"),
true,
);
assert.equal(
validateEnginePrivateExtensionPlan({ ...activatePlan, transition: { ...transition(), runtimeAction: "restart" } })
.errors.includes("transition_policy_mismatch"),
true,
);
assert.equal(
validateEnginePrivateExtensionPlan({
...activatePlan,
transition: { ...transition(), loaderMode: "custom-extension", loaderPath: "N8N_CUSTOM_EXTENSIONS" },
}).errors.includes("transition_policy_mismatch"),
true,
);
assert.equal(
validateEnginePrivateExtensionPlan({
...activatePlan,
transition: { ...transition(), hotReload: true },
}).errors.includes("transition_policy_mismatch"),
true,
);
assert.equal(
validateEnginePrivateExtensionPlan({
...activatePlan,
transition: {
...transition(),
loaderEnvironment: { ...transition().loaderEnvironment, N8N_REINSTALL_MISSING_PACKAGES: "true" },
},
}).errors.includes("transition_policy_mismatch"),
true,
);
const applyRequest = {
schemaVersion: ENGINE_PRIVATE_EXTENSION_APPLY_REQUEST_SCHEMA_VERSION,
planId: activatePlan.planId,
planHash: activatePlan.planHash,
idempotencyKey: activatePlan.idempotencyKey,
confirmedAt: "2026-07-15T18:00:02.000Z",
};
assert.deepEqual(validateEnginePrivateExtensionApplyRequest(applyRequest, {
plan: activatePlan,
now: "2026-07-15T18:00:02.000Z",
grantedCapabilities: manage,
}), { ok: true, errors: [] });
assert.equal(validateEnginePrivateExtensionApplyRequest(applyRequest, {
plan: activatePlan,
now: "2026-07-15T18:10:00.000Z",
grantedCapabilities: manage,
}).errors.includes("plan_expired"), true);
const receipt = {
schemaVersion: ENGINE_PRIVATE_EXTENSION_APPLY_RECEIPT_SCHEMA_VERSION,
operationId: "extension-operation-20260715-001",
planId: activatePlan.planId,
planHash: activatePlan.planHash,
action: "activate",
acceptedAt: "2026-07-15T18:00:02.100Z",
state: "queued",
};
assert.deepEqual(validateEnginePrivateExtensionApplyReceipt(receipt, { plan: activatePlan }), { ok: true, errors: [] });
assert.equal(validateEnginePrivateExtensionApplyReceipt({ ...receipt, state: "active" })
.errors.includes("apply_receipt_state_must_be_queued"), true);
const activeOperation = {
schemaVersion: ENGINE_PRIVATE_EXTENSION_OPERATION_SCHEMA_VERSION,
operationId: receipt.operationId,
planId: activatePlan.planId,
planHash: activatePlan.planHash,
action: "activate",
state: "active",
outcome: "committed",
phase: "complete",
expectedCurrentGeneration: 0,
nextGeneration: 1,
targetState: release,
recoveryState: inactive,
effectiveState: release,
runtime: runtime(1, 2, 2),
acceptance: acceptanceReport(release, "accepted"),
updatedAt: "2026-07-15T18:00:32.000Z",
};
assert.deepEqual(validateEnginePrivateExtensionOperation(activeOperation), { ok: true, errors: [] });
const unexpectedSchema = structuredClone(activeOperation);
unexpectedSchema.acceptance.nodeTypes.observed.push("n8n-nodes-ndc.unreviewedNode");
assert.equal(validateEnginePrivateExtensionOperation(unexpectedSchema).errors
.includes("acceptance.nodeTypes.observed_exact_set_required"), true);
const automaticRollback = {
...activeOperation,
state: "rolled-back",
outcome: "automatically-rolled-back",
phase: "complete",
effectiveState: inactive,
acceptance: acceptanceReport(inactive, "accepted"),
errorCode: "runtime_acceptance_failed",
};
assert.deepEqual(validateEnginePrivateExtensionOperation(automaticRollback), { ok: true, errors: [] });
assert.equal(
validateEnginePrivateExtensionOperation({ ...automaticRollback, errorCode: undefined })
.errors.includes("errorCode_invalid"),
true,
);
const activeStatus = {
schemaVersion: ENGINE_PRIVATE_EXTENSION_STATUS_SCHEMA_VERSION,
packageName: ENGINE_PRIVATE_EXTENSION_PACKAGE_NAME,
generation: 1,
health: "ready",
currentState: release,
previousState: inactive,
runtime: runtime(1, 2, 2),
acceptance: acceptanceReport(release, "accepted"),
updatedAt: "2026-07-15T18:00:33.000Z",
};
assert.deepEqual(validateEnginePrivateExtensionStatus(activeStatus), { ok: true, errors: [] });
const rollbackRequest = {
schemaVersion: ENGINE_PRIVATE_EXTENSION_PLAN_REQUEST_SCHEMA_VERSION,
requestId: "extension-rollback-request-20260715-001",
idempotencyKey: "extension-rollback-request-20260715-001",
action: "rollback",
requestedAt: "2026-07-15T18:05:00.000Z",
requestExpiresAt: "2026-07-15T18:15:00.000Z",
expectedCurrentGeneration: 1,
target: { kind: "previous-state", packageName: ENGINE_PRIVATE_EXTENSION_PACKAGE_NAME },
};
assert.deepEqual(validateEnginePrivateExtensionPlanRequest(rollbackRequest, {
now: "2026-07-15T18:05:01.000Z",
grantedCapabilities: manage,
}), { ok: true, errors: [] });
const rollbackPlan = withPlanHash({
schemaVersion: ENGINE_PRIVATE_EXTENSION_PLAN_SCHEMA_VERSION,
planId: "extension-rollback-plan-20260715-001",
planHash: hash("0"),
requestId: rollbackRequest.requestId,
idempotencyKey: rollbackRequest.idempotencyKey,
action: "rollback",
createdAt: "2026-07-15T18:05:01.000Z",
expiresAt: "2026-07-15T18:14:00.000Z",
singleUse: true,
requiredCapability: ENGINE_PRIVATE_EXTENSION_MANAGE_CAPABILITY,
expectedCurrentGeneration: 1,
nextGeneration: 2,
currentState: release,
targetState: inactive,
recoveryState: release,
actions: [
"verify_previous_activation_state",
"verify_community_package_loader_policy",
"quiesce_deploy_run_and_drain_queue",
"record_recovery_state",
"atomic_switch_current_to_previous",
"force_recreate_main_workers_webhooks_as_version_barrier",
"verify_exact_runtime_acceptance",
"commit_rolled_back_state",
"resume_deploy_run",
],
transition: transition(),
acceptance: acceptanceSpec(inactive),
failurePolicy: failurePolicy(),
});
assert.deepEqual(validateEnginePrivateExtensionPlan(rollbackPlan, { request: rollbackRequest }), { ok: true, errors: [] });
const explicitRollbackOperation = {
...activeOperation,
operationId: "extension-operation-rollback-20260715-001",
planId: rollbackPlan.planId,
planHash: rollbackPlan.planHash,
action: "rollback",
state: "rolled-back",
outcome: "explicitly-rolled-back",
expectedCurrentGeneration: 1,
nextGeneration: 2,
targetState: inactive,
recoveryState: release,
effectiveState: inactive,
runtime: runtime(2, 2, 2),
acceptance: acceptanceReport(inactive, "accepted"),
};
assert.deepEqual(validateEnginePrivateExtensionOperation(explicitRollbackOperation), { ok: true, errors: [] });
const quarantinedStatus = {
...activeStatus,
health: "quarantined",
activeOperationId: "extension-operation-failed-20260715-001",
acceptance: acceptanceReport(release, "rejected"),
errorCode: "automatic_rollback_failed",
};
assert.deepEqual(validateEnginePrivateExtensionStatus(quarantinedStatus), { ok: true, errors: [] });
const pathInjection = structuredClone(activateRequest);
pathInjection.target.releaseId = "../../runtime";
assert.equal(validateEnginePrivateExtensionPlanRequest(pathInjection, {
now: "2026-07-15T18:00:01.000Z",
grantedCapabilities: manage,
}).errors.includes("target.releaseId_invalid"), true);
const commandInjection = { ...activateRequest, command: "docker exec runtime npm install" };
assert.equal(validateEnginePrivateExtensionPlanRequest(commandInjection, {
now: "2026-07-15T18:00:01.000Z",
grantedCapabilities: manage,
}).errors.includes("enginePrivateExtensionPlanRequest.command_not_allowed"), true);
console.log("external-provider Engine private-extension contract: ok");
function transition() {
return {
mountMode: "read-only",
loaderMode: "community-package",
loaderPath: "/home/node/.n8n/nodes/node_modules/n8n-nodes-ndc",
loaderEnvironment: {
N8N_COMMUNITY_PACKAGES_ENABLED: "true",
N8N_COMMUNITY_PACKAGES_PREVENT_LOADING: "false",
N8N_REINSTALL_MISSING_PACKAGES: "false",
},
quiesceMode: "block-deploy-run-and-drain-queue",
stateSwitch: "atomic-current-previous",
runtimeAction: "force-recreate",
scope: "main-workers-webhooks",
requireUniformGeneration: true,
hotReload: false,
preserveCredentials: true,
};
}
function failurePolicy() {
return {
mode: "automatic-rollback",
rollbackFailureOutcome: "quarantined",
preserveImmutableRelease: true,
preserveCredentials: true,
};
}
function acceptanceSpec(state) {
return {
mode: "exact",
nodeTypes: state.kind === "release" ? [...ENGINE_PRIVATE_EXTENSION_NODE_TYPES] : [],
credentialSchemas: state.kind === "release" ? [...ENGINE_PRIVATE_EXTENSION_CREDENTIAL_TYPES] : [],
requireUniformGeneration: true,
};
}
function acceptanceReport(state, status) {
const nodes = state.kind === "release" ? [...ENGINE_PRIVATE_EXTENSION_NODE_TYPES] : [];
const credentials = state.kind === "release" ? [...ENGINE_PRIVATE_EXTENSION_CREDENTIAL_TYPES] : [];
return {
state: status,
nodeTypes: { expected: nodes, observed: status === "pending" ? [] : nodes },
credentialSchemas: { expected: credentials, observed: status === "pending" ? [] : credentials },
uniformGeneration: status === "accepted",
};
}
function runtime(generation, expectedInstances, readyInstances) {
return { mode: "force-recreate", generation, expectedInstances, readyInstances };
}
function withPlanHash(plan) {
plan.planHash = computeEnginePrivateExtensionPlanHash(plan);
return plan;
}
function hash(character) {
return "sha256:" + character.repeat(64);
}