feat(platform): add managed data product history plane

This commit is contained in:
Codex
2026-07-18 14:38:06 +03:00
parent 02816c4352
commit 3c5d8f6cef
34 changed files with 2091 additions and 163 deletions
@@ -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)