feat(platform): add managed data product history plane
This commit is contained in:
@@ -21,11 +21,12 @@ BUILDER_PATH = SCRIPT_DIR / "build-engine-n8n-private-extension-artifact.mjs"
|
||||
STAGE_ARTIFACT = (
|
||||
PLATFORM_ROOT
|
||||
/ "infra/deploy-artifacts"
|
||||
/ "nodedc-n8n-private-extension-n8n-nodes-ndc-release-20260716-003.tgz"
|
||||
/ "nodedc-n8n-private-extension-n8n-nodes-ndc-history-read-20260717-004.tgz"
|
||||
)
|
||||
TRANSITION_ID = "20260717-004"
|
||||
SEALED_RELEASE_ID = "0.1.2-05e4b38b14b4a019"
|
||||
SEALED_PACKAGE_SHA256 = "05e4b38b14b4a019ce1f6eee27b9e320094cb3560903bd68074966b3a1267af5"
|
||||
TRANSITION_ID = "20260717-005"
|
||||
SEALED_RELEASE_ID = "0.1.3-3354149b5245e39a"
|
||||
SEALED_PACKAGE_SHA256 = "3354149b5245e39a10f6f03a4b43796602d7e57ea1f91dcc6cb6774ead97501d"
|
||||
PREDECESSOR_RELEASE_ID = "0.1.2-05e4b38b14b4a019"
|
||||
BUILDER_ENGINE_PATHS = (
|
||||
"nodedc-source/server/assets/n8n/schema/v2.3.2/nodes.catalog.json",
|
||||
"nodedc-source/server/assets/n8n/schema/v2.3.2/credentials.catalog.json",
|
||||
@@ -50,7 +51,7 @@ PRIVATE_EXTENSION_CANON_FUNCTION_SHA256 = {
|
||||
"engine_n8n_package_loader_probe_script": "2b3b3c96fad6e5e48ebea74426b21bc7eea6b6fe0e786b537da3366d18aedd9b",
|
||||
"engine_n8n_private_loader_catalog": "a2ae389b4ef215900cf967b863d01056bd2a8eec55554566d0f6005e4e0bcbac",
|
||||
"validate_engine_n8n_base_compose_source": "adacc5b20e52684cbc427362ddba5a6d2e13ef2091a89859e2b8f7bbf2e20458",
|
||||
"preflight_engine_n8n_transition": "da3cec853e3e3e20df4d1e301aab98b77815f93ec3d68b0b61c84c6e6a3fb335",
|
||||
"preflight_engine_n8n_transition": "3d574acb081fb752e45cc0c3286d3ae4596dcee83a85e2c3fee81ac65ec796e0",
|
||||
"accept_engine_n8n_runtime": "1a2614a465161e3833e84aca402817682da1a538bdb2aecc4af66b665497338d",
|
||||
}
|
||||
|
||||
@@ -138,7 +139,7 @@ class EngineN8nPrivateExtensionTest(unittest.TestCase):
|
||||
self.assertEqual(first[4:8], b"\0\0\0\0")
|
||||
self.assertEqual(first[3] & 0x08, 0)
|
||||
|
||||
def test_successful_generation_003_runner_functions_are_frozen(self):
|
||||
def test_state_aware_upgrade_runner_functions_are_frozen(self):
|
||||
actual = {
|
||||
name: hashlib.sha256(inspect.getsource(getattr(RUNNER, name)).encode("utf-8")).hexdigest()
|
||||
for name in PRIVATE_EXTENSION_CANON_FUNCTION_SHA256
|
||||
@@ -164,8 +165,8 @@ class EngineN8nPrivateExtensionTest(unittest.TestCase):
|
||||
def test_transition_id_rejects_missing_invalid_and_previously_issued_values(self):
|
||||
cases = (
|
||||
([], "transition_id_required"),
|
||||
(["20260716-003"], "transition_id_already_issued"),
|
||||
(["engine-n8n-private-extension-20260717-004"], "transition_id_invalid"),
|
||||
(["20260717-004"], "transition_id_already_issued"),
|
||||
(["engine-n8n-private-extension-20260717-005"], "transition_id_invalid"),
|
||||
(["20260230-004"], "transition_id_invalid"),
|
||||
(["20260717-000"], "transition_id_invalid"),
|
||||
([TRANSITION_ID, "unexpected-second-id"], "transition_id_argument_count_invalid"),
|
||||
@@ -202,7 +203,7 @@ class EngineN8nPrivateExtensionTest(unittest.TestCase):
|
||||
self.assertIn("transition_artifact_already_exists", result.stderr)
|
||||
|
||||
def test_activation_and_rollback_pass_strict_runner_policy(self):
|
||||
expected = {"activation": ("activate", 7), "rollback": ("rollback-inactive", 4)}
|
||||
expected = {"activation": ("activate", 7), "rollback": ("activate", 7)}
|
||||
for kind, (action, entry_count) in expected.items():
|
||||
with self.subTest(kind=kind), tempfile.TemporaryDirectory() as directory:
|
||||
manifest, entries, payload = RUNNER.load_artifact(
|
||||
@@ -234,7 +235,7 @@ class EngineN8nPrivateExtensionTest(unittest.TestCase):
|
||||
self.assertTrue(all("usableAsTool" not in item for item in private_nodes))
|
||||
self.assertEqual((len(nodes), len(credentials)), (437, 388))
|
||||
|
||||
def test_rollback_catalog_restores_verified_inactive_baseline(self):
|
||||
def test_rollback_catalog_restores_verified_predecessor_release(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
_manifest, _entries, payload = RUNNER.load_artifact(
|
||||
self.artifact(0, "rollback"),
|
||||
@@ -242,9 +243,20 @@ class EngineN8nPrivateExtensionTest(unittest.TestCase):
|
||||
)
|
||||
nodes = json.loads((payload / RUNNER.ENGINE_N8N_NODES_CATALOG_REL).read_text())
|
||||
credentials = json.loads((payload / RUNNER.ENGINE_N8N_CREDENTIALS_CATALOG_REL).read_text())
|
||||
self.assertEqual([item for item in nodes if item.get("name", "").startswith("n8n-nodes-ndc.")], [])
|
||||
self.assertEqual([item for item in credentials if item.get("name") in EXPECTED_CREDENTIALS], [])
|
||||
self.assertEqual((len(nodes), len(credentials)), (434, 385))
|
||||
descriptor = RUNNER.read_engine_n8n_transition_descriptor(
|
||||
payload / RUNNER.ENGINE_N8N_TRANSITION_DESCRIPTOR_REL
|
||||
)
|
||||
self.assertEqual(descriptor["releaseId"], PREDECESSOR_RELEASE_ID)
|
||||
self.assertEqual(descriptor["expectedCurrent"], SEALED_RELEASE_ID)
|
||||
self.assertEqual(
|
||||
[item.get("name") for item in nodes if item.get("name", "").startswith("n8n-nodes-ndc.")],
|
||||
EXPECTED_NODES,
|
||||
)
|
||||
self.assertEqual(
|
||||
[item.get("name") for item in credentials if item.get("name") in EXPECTED_CREDENTIALS],
|
||||
EXPECTED_CREDENTIALS,
|
||||
)
|
||||
self.assertEqual((len(nodes), len(credentials)), (437, 388))
|
||||
|
||||
def test_compose_override_is_runner_derived_and_offline(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
@@ -259,7 +271,7 @@ class EngineN8nPrivateExtensionTest(unittest.TestCase):
|
||||
self.assertEqual(override, RUNNER.expected_engine_n8n_compose_override(descriptor))
|
||||
self.assertEqual(
|
||||
hashlib.sha256(override.encode("utf-8")).hexdigest(),
|
||||
"a29e2f92b87dda59da2b4e3ce250bc9705ed9fa551c0fbe4e95464fca5caa3e1",
|
||||
"256142c14ae295bf9be4d74fdc8dcd6e134e4ded8a264f15e66b38e85503b589",
|
||||
)
|
||||
self.assertIn("pull_policy: never", override)
|
||||
self.assertIn("N8N_USER_FOLDER: /home/node", override)
|
||||
@@ -268,7 +280,7 @@ class EngineN8nPrivateExtensionTest(unittest.TestCase):
|
||||
self.assertNotIn("N8N_CUSTOM_EXTENSIONS", override)
|
||||
self.assertNotIn("build:", override)
|
||||
|
||||
def test_generation_003_override_is_accepted_as_installed_canon(self):
|
||||
def test_upgrade_override_is_accepted_as_installed_canon(self):
|
||||
original_root = RUNNER.COMPONENTS["engine"]["payload_root"]
|
||||
original_compose = RUNNER.COMPONENTS["engine"]["compose_root"]
|
||||
try:
|
||||
@@ -551,7 +563,7 @@ class EngineN8nPrivateExtensionTest(unittest.TestCase):
|
||||
|
||||
def test_sealed_release_exact_set_includes_implicit_directories(self):
|
||||
release_relative = Path(
|
||||
"payload/releases/n8n-nodes-ndc/0.1.2-05e4b38b14b4a019"
|
||||
"payload/releases/n8n-nodes-ndc/0.1.3-3354149b5245e39a"
|
||||
)
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
work = Path(directory)
|
||||
|
||||
Reference in New Issue
Block a user