From e910276c6c6a6cfc0f309d9ff36bed2b040cd176 Mon Sep 17 00:00:00 2001 From: hshum Date: Wed, 29 Jul 2026 21:22:06 -0700 Subject: [PATCH 1/2] fix(nodekit): replay production graph identity exports --- .../schemas/nodekit-run-export.v1.schema.json | 65 ++- .../nodekit_replay.py | 458 +++++++++++++++++- .../activegraph/tests/test_nodekit_replay.py | 156 ++++++ 3 files changed, 676 insertions(+), 3 deletions(-) diff --git a/evals/activegraph/schemas/nodekit-run-export.v1.schema.json b/evals/activegraph/schemas/nodekit-run-export.v1.schema.json index b4a56568..4eed3f59 100644 --- a/evals/activegraph/schemas/nodekit-run-export.v1.schema.json +++ b/evals/activegraph/schemas/nodekit-run-export.v1.schema.json @@ -27,7 +27,10 @@ "minLength": 1, "maxLength": 256 }, - "startedAt": { "type": "integer", "minimum": 0 } + "startedAt": { "type": "integer", "minimum": 0 }, + "nativeIdentity": { + "$ref": "#/$defs/nativeIdentity" + } } }, "trace": { @@ -123,6 +126,13 @@ "verification.recorded", "evidence.attached", "approval.requested", + "node.started", + "edge.consumed", + "artifact.produced", + "node.completed", + "node.failed", + "barrier.opened", + "barrier.blocked", "run.completed", "run.failed" ] @@ -200,12 +210,63 @@ }, "fields": { "type": "object", - "maxProperties": 7, + "maxProperties": 16, "additionalProperties": { "type": ["string", "number", "boolean", "null"] } } } + }, + "nativeIdentity": { + "type": "object", + "additionalProperties": false, + "required": [ + "schemaVersion", + "identityRef", + "agentId", + "workspaceId", + "nativeSessionId", + "nativeSessionGeneration", + "snapshotHash" + ], + "properties": { + "schemaVersion": { + "const": "nodekit.native-agent-session-identity/v1" + }, + "identityRef": { + "type": "string", + "minLength": 1, + "maxLength": 256 + }, + "agentId": { + "type": "string", + "minLength": 1, + "maxLength": 256 + }, + "workspaceId": { + "type": "string", + "minLength": 1, + "maxLength": 256 + }, + "nativeSessionId": { + "type": "string", + "minLength": 1, + "maxLength": 256 + }, + "nativeSessionGeneration": { + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 + }, + "peerId": { + "type": "string", + "minLength": 1, + "maxLength": 256 + }, + "snapshotHash": { + "$ref": "#/$defs/hash" + } + } } } } diff --git a/evals/activegraph/src/nodebench_activegraph_canary/nodekit_replay.py b/evals/activegraph/src/nodebench_activegraph_canary/nodekit_replay.py index 3c659955..2f151699 100644 --- a/evals/activegraph/src/nodebench_activegraph_canary/nodekit_replay.py +++ b/evals/activegraph/src/nodebench_activegraph_canary/nodekit_replay.py @@ -51,6 +51,13 @@ "verification.recorded", "evidence.attached", "approval.requested", + "node.started", + "edge.consumed", + "artifact.produced", + "node.completed", + "node.failed", + "barrier.opened", + "barrier.blocked", "run.completed", "run.failed", } @@ -66,6 +73,13 @@ "goalId", "sessionType", "sessionStartedAt", + "identityRef", + "workspaceId", + "agentId", + "nativeSessionId", + "nativeSessionGeneration", + "peerId", + "identitySnapshotHash", } ), "span.started": frozenset( @@ -91,6 +105,109 @@ "approval.requested": frozenset( {"approvalId", "toolName", "riskLevel"} ), + "node.started": frozenset( + { + "graphId", + "graphHash", + "caseId", + "stageId", + "caseContentHash", + "nodeId", + "nodeRunId", + "nodeKind", + "frontierHash", + "reviewContextRef", + } + ), + "edge.consumed": frozenset( + { + "graphId", + "graphHash", + "caseId", + "stageId", + "caseContentHash", + "nodeId", + "nodeRunId", + "edgeId", + "bindingId", + "bindingHash", + "artifactId", + "artifactSchemaVersion", + "artifactContentHash", + "authorityKind", + } + ), + "artifact.produced": frozenset( + { + "graphId", + "graphHash", + "caseId", + "stageId", + "caseContentHash", + "nodeId", + "nodeRunId", + "artifactId", + "artifactSchemaVersion", + "artifactContentHash", + "authorityKind", + } + ), + "node.completed": frozenset( + { + "graphId", + "graphHash", + "caseId", + "stageId", + "caseContentHash", + "nodeId", + "nodeRunId", + "status", + "reviewContextRef", + "reviewSeparation", + "protectedEvaluator", + } + ), + "node.failed": frozenset( + { + "graphId", + "graphHash", + "caseId", + "stageId", + "caseContentHash", + "nodeId", + "nodeRunId", + "status", + "reasonCode", + } + ), + "barrier.opened": frozenset( + { + "graphId", + "graphHash", + "caseId", + "stageId", + "caseContentHash", + "nodeId", + "nodeRunId", + "frontierHash", + "status", + } + ), + "barrier.blocked": frozenset( + { + "graphId", + "graphHash", + "caseId", + "stageId", + "caseContentHash", + "nodeId", + "nodeRunId", + "frontierHash", + "status", + "reasonCode", + "blockingEdgeCount", + } + ), "run.completed": frozenset( {"status", "totalDurationMs", "crossCheckStatus", "dogfoodRunId"} ), @@ -104,6 +221,98 @@ ), "span.started": frozenset({"spanId"}), "span.completed": frozenset({"spanId"}), + "node.started": frozenset( + { + "graphId", + "graphHash", + "caseId", + "stageId", + "caseContentHash", + "nodeId", + "nodeRunId", + } + ), + "edge.consumed": frozenset( + { + "graphId", + "graphHash", + "caseId", + "stageId", + "caseContentHash", + "nodeId", + "nodeRunId", + "edgeId", + "bindingId", + "bindingHash", + "artifactId", + "artifactSchemaVersion", + "artifactContentHash", + "authorityKind", + } + ), + "artifact.produced": frozenset( + { + "graphId", + "graphHash", + "caseId", + "stageId", + "caseContentHash", + "nodeId", + "nodeRunId", + "artifactId", + "artifactSchemaVersion", + "artifactContentHash", + "authorityKind", + } + ), + "node.completed": frozenset( + { + "graphId", + "graphHash", + "caseId", + "stageId", + "caseContentHash", + "nodeId", + "nodeRunId", + "status", + } + ), + "node.failed": frozenset( + { + "graphId", + "graphHash", + "caseId", + "stageId", + "caseContentHash", + "nodeId", + "nodeRunId", + "status", + } + ), + "barrier.opened": frozenset( + { + "graphId", + "graphHash", + "caseId", + "stageId", + "caseContentHash", + "nodeId", + "nodeRunId", + "frontierHash", + } + ), + "barrier.blocked": frozenset( + { + "graphId", + "graphHash", + "caseId", + "stageId", + "caseContentHash", + "nodeId", + "nodeRunId", + "frontierHash", + } + ), "run.completed": frozenset({"status"}), "run.failed": frozenset({"status"}), } @@ -264,6 +473,83 @@ def _expect_safe_payload( "terminal_status_mismatch", f"{path} requires status {expected_terminal_status}", ) + if event_type == "run.started": + identity_fields = { + "identityRef", + "workspaceId", + "agentId", + "nativeSessionId", + "nativeSessionGeneration", + "identitySnapshotHash", + } + present = identity_fields & set(fields) + if present and present != identity_fields: + _fail( + "native_identity_incomplete", + f"{path} must bind the complete native identity snapshot", + ) + if present: + _expect_hash( + fields["identitySnapshotHash"], + f"{path}.fields.identitySnapshotHash", + ) + generation = fields["nativeSessionGeneration"] + if ( + type(generation) is not int + or generation < 0 + or generation > 9_007_199_254_740_991 + ): + _fail( + "native_session_generation_invalid", + f"{path}.fields.nativeSessionGeneration is invalid", + ) + if event_type in { + "node.started", + "edge.consumed", + "artifact.produced", + "node.completed", + "node.failed", + "barrier.opened", + "barrier.blocked", + }: + graph_id = fields.get("graphId") + if ( + not isinstance(graph_id, str) + or re.fullmatch( + r"execution-graph:sha256:[a-f0-9]{64}", + graph_id, + ) + is None + ): + _fail("graph_id_invalid", f"{path}.fields.graphId is invalid") + for field in { + "graphHash", + "caseContentHash", + "frontierHash", + "bindingHash", + "artifactContentHash", + }: + if field in fields and ( + not isinstance(fields[field], str) + or re.fullmatch(r"[a-f0-9]{64}", fields[field]) is None + ): + _fail( + "graph_hash_invalid", + f"{path}.fields.{field} is invalid", + ) + binding_id = fields.get("bindingId") + if binding_id is not None and ( + not isinstance(binding_id, str) + or re.fullmatch( + r"execution-edge-binding:sha256:[a-f0-9]{64}", + binding_id, + ) + is None + ): + _fail( + "edge_binding_id_invalid", + f"{path}.fields.bindingId is invalid", + ) if len(canonical_json_text(payload).encode("utf-8")) > MAX_STORED_PAYLOAD_BYTES: _fail("projected_payload_too_large", f"{path} exceeds its stored bound") return payload @@ -314,7 +600,12 @@ def validate_nodekit_export(value: Any) -> dict[str, Any]: _expect_exact_keys( session, "$.session", - {"id", "typeAtRunStart", "startedAt"}, + { + "id", + "typeAtRunStart", + "startedAt", + *(["nativeIdentity"] if "nativeIdentity" in session else []), + }, ) _expect_exact_keys( trace, @@ -345,6 +636,67 @@ def validate_nodekit_export(value: Any) -> dict[str, Any]: session["startedAt"], "$.session.startedAt", ) + native_identity = None + if "nativeIdentity" in session: + native_identity = _expect_object( + session["nativeIdentity"], + "$.session.nativeIdentity", + ) + _expect_exact_keys( + native_identity, + "$.session.nativeIdentity", + { + "schemaVersion", + "identityRef", + "agentId", + "workspaceId", + "nativeSessionId", + "nativeSessionGeneration", + *(["peerId"] if "peerId" in native_identity else []), + "snapshotHash", + }, + ) + if ( + native_identity["schemaVersion"] + != "nodekit.native-agent-session-identity/v1" + ): + _fail( + "native_identity_snapshot_mismatch", + "$.session.nativeIdentity schema is unsupported", + ) + for field in { + "identityRef", + "agentId", + "workspaceId", + "nativeSessionId", + }: + _expect_string( + native_identity[field], + f"$.session.nativeIdentity.{field}", + non_empty=True, + max_length=256, + ) + if "peerId" in native_identity: + _expect_string( + native_identity["peerId"], + "$.session.nativeIdentity.peerId", + non_empty=True, + max_length=256, + ) + generation = native_identity["nativeSessionGeneration"] + if ( + type(generation) is not int + or generation < 0 + or generation > 9_007_199_254_740_991 + ): + _fail( + "native_session_generation_invalid", + "$.session.nativeIdentity.nativeSessionGeneration is invalid", + ) + _expect_hash( + native_identity["snapshotHash"], + "$.session.nativeIdentity.snapshotHash", + ) _expect_string(trace["id"], "$.trace.id", non_empty=True) _expect_string( trace["runId"], @@ -400,6 +752,9 @@ def validate_nodekit_export(value: Any) -> dict[str, Any]: } open_spans: set[str] = set() completed_spans: set[str] = set() + open_nodes: dict[str, str] = {} + closed_node_runs: set[str] = set() + graph_scope: str | None = None for index, candidate in enumerate(events): event = _expect_object(candidate, f"$.events[{index}]") _expect_exact_keys(event, f"$.events[{index}]", event_keys) @@ -470,6 +825,54 @@ def validate_nodekit_export(value: Any) -> dict[str, Any]: ) open_spans.remove(span_id) completed_spans.add(span_id) + if event["eventType"] in { + "node.started", + "edge.consumed", + "artifact.produced", + "node.completed", + "node.failed", + "barrier.opened", + "barrier.blocked", + }: + fields = payload["fields"] + current_scope = "|".join( + str(fields[field]) + for field in ( + "graphId", + "graphHash", + "caseId", + "stageId", + "caseContentHash", + ) + ) + if graph_scope is None: + graph_scope = current_scope + if current_scope != graph_scope: + _fail( + "graph_scope_mismatch", + "One export cannot mix execution graphs, cases, or stages", + ) + node_run_id = fields["nodeRunId"] + node_id = fields["nodeId"] + if event["eventType"] == "node.started": + if ( + node_run_id in open_nodes + or node_run_id in closed_node_runs + ): + _fail( + "graph_node_duplicate_start", + f"Invalid start for {node_run_id}", + ) + open_nodes[node_run_id] = node_id + else: + if open_nodes.get(node_run_id) != node_id: + _fail( + "graph_node_not_running", + f"{event['eventType']} is not bound to {node_run_id}", + ) + if event["eventType"] in {"node.completed", "node.failed"}: + del open_nodes[node_run_id] + closed_node_runs.add(node_run_id) previous_hash = event["contentHash"] if events[0]["eventType"] != "run.started": @@ -491,6 +894,11 @@ def validate_nodekit_export(value: Any) -> dict[str, Any]: "span_lifecycle_incomplete", f"Terminal run retains {len(open_spans)} open span(s)", ) + if open_nodes: + _fail( + "graph_node_lifecycle_incomplete", + f"Terminal run retains {len(open_nodes)} open graph node(s)", + ) terminal_type = events[-1]["eventType"] expected_trace_status = ( @@ -514,6 +922,54 @@ def validate_nodekit_export(value: Any) -> dict[str, Any]: "event_snapshot_mismatch", "Session or trace metadata differs from immutable event snapshots", ) + identity_fields = { + "identityRef", + "workspaceId", + "agentId", + "nativeSessionId", + "nativeSessionGeneration", + "identitySnapshotHash", + } + present_identity_fields = identity_fields & set(start_fields) + if not present_identity_fields and native_identity is not None: + _fail( + "native_identity_snapshot_mismatch", + "Session identity is absent from run.started", + ) + if present_identity_fields: + if present_identity_fields != identity_fields: + _fail( + "native_identity_incomplete", + "run.started does not bind a complete native identity snapshot", + ) + expected_identity_body = { + "schemaVersion": "nodekit.native-agent-session-identity/v1", + "identityRef": start_fields["identityRef"], + "agentId": start_fields["agentId"], + "workspaceId": start_fields["workspaceId"], + "nativeSessionId": start_fields["nativeSessionId"], + "nativeSessionGeneration": start_fields[ + "nativeSessionGeneration" + ], + **( + {} + if "peerId" not in start_fields + else {"peerId": start_fields["peerId"]} + ), + } + expected_identity = { + **expected_identity_body, + "snapshotHash": canonical_hash(expected_identity_body), + } + if ( + native_identity != expected_identity + or start_fields["identitySnapshotHash"] + != expected_identity["snapshotHash"] + ): + _fail( + "native_identity_snapshot_mismatch", + "Session identity differs from immutable run.started fields", + ) expected_completeness = { "eventChainComplete": True, "spanLifecycleComplete": True, diff --git a/evals/activegraph/tests/test_nodekit_replay.py b/evals/activegraph/tests/test_nodekit_replay.py index 9d0ed0d1..41f68b59 100644 --- a/evals/activegraph/tests/test_nodekit_replay.py +++ b/evals/activegraph/tests/test_nodekit_replay.py @@ -160,6 +160,138 @@ def _export() -> dict[str, Any]: } +def _native_graph_export() -> dict[str, Any]: + """Production-shaped owner export used by the offline adoption scenario.""" + + run_id = "trace_native_graph_owner_export" + identity_body = { + "schemaVersion": "nodekit.native-agent-session-identity/v1", + "identityRef": "native-identity:agent-1:workspace-1:session-1:1", + "agentId": "agent-1", + "workspaceId": "workspace-1", + "nativeSessionId": "session-1", + "nativeSessionGeneration": 1, + "peerId": "peer-1", + } + identity = { + **identity_body, + "snapshotHash": canonical_hash(identity_body), + } + graph_id = f"execution-graph:sha256:{'1' * 64}" + common = { + "graphId": graph_id, + "graphHash": "2" * 64, + "caseId": "case-owner-export", + "stageId": "build", + "caseContentHash": "3" * 64, + "nodeId": "node-build-ui", + "nodeRunId": "node-run-build-ui-1", + } + started = _event( + run_id=run_id, + sequence=0, + event_type="run.started", + recorded_at=1_725_100_000_000, + payload={ + "workflowName": "Owner-scoped graph replay", + "sessionType": "agent", + "sessionStartedAt": 1_725_099_999_000, + "identityRef": identity["identityRef"], + "workspaceId": identity["workspaceId"], + "agentId": identity["agentId"], + "nativeSessionId": identity["nativeSessionId"], + "nativeSessionGeneration": identity[ + "nativeSessionGeneration" + ], + "peerId": identity["peerId"], + "identitySnapshotHash": identity["snapshotHash"], + }, + previous_hash=GENESIS_HASH, + ) + node_started = _event( + run_id=run_id, + sequence=1, + event_type="node.started", + recorded_at=1_725_100_000_010, + payload={ + **common, + "nodeKind": "task", + "frontierHash": "4" * 64, + }, + previous_hash=started["contentHash"], + ) + artifact = _event( + run_id=run_id, + sequence=2, + event_type="artifact.produced", + recorded_at=1_725_100_000_020, + payload={ + **common, + "artifactId": "artifact-owner-export", + "artifactSchemaVersion": "nodekit.build-artifact/v1", + "artifactContentHash": "5" * 64, + "authorityKind": "deterministic", + }, + previous_hash=node_started["contentHash"], + ) + node_completed = _event( + run_id=run_id, + sequence=3, + event_type="node.completed", + recorded_at=1_725_100_000_030, + payload={**common, "status": "completed"}, + previous_hash=artifact["contentHash"], + ) + completed = _event( + run_id=run_id, + sequence=4, + event_type="run.completed", + recorded_at=1_725_100_000_040, + payload={"status": "completed"}, + previous_hash=node_completed["contentHash"], + ) + events = [started, node_started, artifact, node_completed, completed] + base = { + "schemaVersion": EXPORT_SCHEMA_VERSION, + "runId": run_id, + "session": { + "id": "session-native-graph-owner-export", + "typeAtRunStart": "agent", + "startedAt": 1_725_099_999_000, + "nativeIdentity": identity, + }, + "trace": { + "id": "trace-row-native-graph-owner-export", + "runId": run_id, + "workflowName": "Owner-scoped graph replay", + "status": "completed", + "startedAt": 1_725_100_000_000, + "endedAt": 1_725_100_000_040, + }, + "events": events, + "completeness": { + "eventChainComplete": True, + "spanLifecycleComplete": True, + "contractVersion": EVENT_SCHEMA_VERSION, + "eventCount": len(events), + "firstSequence": 0, + "lastSequence": len(events) - 1, + "terminalEventType": "run.completed", + }, + "hashes": { + "algorithm": "sha256", + "chainHead": completed["contentHash"], + }, + } + return { + **base, + "hashes": { + **base["hashes"], + "exportHash": canonical_hash(base), + }, + } + + def _rehash_export(document: dict[str, Any]) -> None: previous_hash = GENESIS_HASH for event in document["events"]: @@ -197,6 +329,30 @@ def test_nodekit_export_and_report_schemas_are_valid( Draft202012Validator(nodekit_export_schema).validate(_export()) +def test_owner_scoped_native_graph_export_validates_end_to_end( + nodekit_export_schema: dict[str, Any], +) -> None: + """An owner resumes a native session and replays its bounded graph node.""" + + document = _native_graph_export() + + Draft202012Validator(nodekit_export_schema).validate(document) + assert validate_nodekit_export(document) == document + + +def test_owner_scoped_native_graph_export_rejects_identity_drift() -> None: + """A stale workspace identity cannot borrow a valid event and export hash.""" + + document = _native_graph_export() + document["session"]["nativeIdentity"]["workspaceId"] = "workspace-other" + _rehash_export(document) + + with pytest.raises(NodeKitReplayContractError) as exc_info: + validate_nodekit_export(document) + + assert exc_info.value.code == "native_identity_snapshot_mismatch" + + def test_nodekit_export_schema_rejects_terminal_status_contradiction( nodekit_export_schema: dict[str, Any], ) -> None: From fd1943316db8c0e068e1a6192d7357c4e81d7d0f Mon Sep 17 00:00:00 2001 From: hshum Date: Wed, 29 Jul 2026 21:28:02 -0700 Subject: [PATCH 2/2] docs(nodekit): close ActiveGraph production corpus --- .../NODEKIT_ACTIVEGRAPH_CANARY.md | 34 ++++++++++- ...ivegraph-production-corpus-2026-07-30.json | 56 +++++++++++++++++++ 2 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 docs/architecture/evidence/nodekit-activegraph-production-corpus-2026-07-30.json diff --git a/docs/architecture/NODEKIT_ACTIVEGRAPH_CANARY.md b/docs/architecture/NODEKIT_ACTIVEGRAPH_CANARY.md index 42d53aba..b4418721 100644 --- a/docs/architecture/NODEKIT_ACTIVEGRAPH_CANARY.md +++ b/docs/architecture/NODEKIT_ACTIVEGRAPH_CANARY.md @@ -332,7 +332,7 @@ graph and identity fields rather than the former two-event trace fixture. This proves the boundary on one deterministic representative corpus. It does not satisfy the owner-authorized multi-run adoption experiment. -### Gate 3b — representative owner-authorized corpus: implemented, live result required +### Gate 3b — representative owner-authorized corpus: closed with `reject` The repository now provides a fixed 20-scenario collector and bounded corpus runner: @@ -372,6 +372,37 @@ Passing all stop rules yields only `eligible-for-adr`; it does not adopt ActiveGraph. A reject result closes this adoption experiment while preserving the offline diagnostic canary. +The owner-authorized production run completed on 2026-07-30. It evaluated 20 +unique, complete exports across the fixed workflow classes, including a +fail-safe terminal run. All 20 exports retained exact persistence/reload +parity. The first production-shaped run also exposed a compatibility defect: +the Python evaluator and JSON Schema did not yet accept the native-session +identity snapshot and graph lifecycle events that the JavaScript boundary and +production exporter already supported. Commit +`e910276c6c6a6cfc0f309d9ff36bed2b040cd176` aligned all three validators and +added production-shaped identity, lifecycle, and stale-identity scenarios. + +The corrected corpus produced: + +- canonical validation median: `0.144125 ms`; +- ActiveGraph canary median: `2018.27055 ms`; +- latency ratio: `14003.6118×`, above the predefined `2×` stop threshold; +- material explanatory value observed: `false`; +- persistence/reload parity: `20/20`; +- adoption verdict: `reject`. + +The private source report is bound by +`sha256:46b00a19d00f83be131ffa574f02def8577918032b9bab90a166756611778559`. +The committed +[sanitized production corpus receipt](evidence/nodekit-activegraph-production-corpus-2026-07-30.json) +contains the non-sensitive measurements, source and evaluator commit bindings, +and sandbox image attestation. It intentionally excludes owner identifiers, +run identifiers, exported events, databases, and evidence-directory contents. + +This result closes the adoption experiment. ActiveGraph remains useful only as +an optional offline diagnostic canary. It is not eligible for an ADR proposing +production runtime adoption. + ### Gate 4 — production bridge: not authorized A production bridge would still require a threat model, owner and tenant @@ -398,7 +429,6 @@ This slice does not add: - an MCP ingestion path; - a Temporal backend; - a production worker or dual-write; -- a corpus-based adoption decision; or - any ActiveGraph role in user-visible answers or approval decisions. ## Reproduction diff --git a/docs/architecture/evidence/nodekit-activegraph-production-corpus-2026-07-30.json b/docs/architecture/evidence/nodekit-activegraph-production-corpus-2026-07-30.json new file mode 100644 index 00000000..71e04bf1 --- /dev/null +++ b/docs/architecture/evidence/nodekit-activegraph-production-corpus-2026-07-30.json @@ -0,0 +1,56 @@ +{ + "schemaVersion": "nodebench.activegraph.production-corpus-receipt/v1", + "evaluatedAt": "2026-07-30T04:23:17.463Z", + "source": { + "kind": "nodebench-production", + "gatewayFunction": "exportNodeKitRun", + "authorization": "gateway-injected-owner", + "ownerDataIncluded": false + }, + "bindings": { + "nodeBenchMainCommit": "6790041dc3999fc3dae3d533190be79bf2907b82", + "evaluatorFixCommit": "e910276c6c6a6cfc0f309d9ff36bed2b040cd176", + "activeGraphVersion": "1.10.0", + "sandboxImageAttestationHash": "sha256:4c2a5788c664e45657d10fd68438090799dfb64e1b0fdcb77e2c5442824b2df8" + }, + "corpus": { + "exportCount": 20, + "uniqueExportHashCount": 20, + "allExportsComplete": true, + "allPersistenceReloadParity": true, + "workflowClasses": [ + "agent-eval", + "aggregate", + "barrier", + "browser", + "build", + "check", + "context", + "decision", + "deliver", + "human-gate", + "repair", + "review" + ], + "includesFailSafeTerminalRun": true + }, + "measurements": { + "baselineValidationMedianMs": 0.14412500000071304, + "activeGraphCanaryMedianMs": 2018.2705499999993, + "latencyRatio": 14003.61179524728, + "latencyThreshold": 2, + "materialExplanatoryValueObserved": false + }, + "stopConditions": [ + "median_latency_exceeded_2x", + "no_material_explanatory_value" + ], + "adoptionVerdict": "reject", + "authority": "offline evaluation evidence only; cannot answer, approve, mutate, schedule, or replace NodeBench state", + "limitations": [ + "Gateway-injected ownership is enforced by NodeBench and is not cryptographically signed into each export.", + "The baseline is canonical NodeKit validation and does not include another graph database.", + "The reject verdict closes the adoption experiment but preserves the offline diagnostic canary." + ], + "sourceReportHash": "sha256:46b00a19d00f83be131ffa574f02def8577918032b9bab90a166756611778559" +}