Skip to content

Commit d542399

Browse files
authored
examples(migration_v5): MAKO reference extractor + tests (#107 follow-up) (#157)
* feat(examples/migration_v5): MAKO reference extractor + tests (PR #156 first cut) Add ``examples/migration_v5/reference_extractor.py`` — the hand-authored reference for the notebook's Beat 3 cells and the ``bqaa-revalidate-extractors`` CLI. The module exposes: * ``EXTRACTORS`` — ``{"TOOL_COMPLETED": extract_mako_decision_event}``. Only ``TOOL_COMPLETED`` events carry MAKO tool outputs; other event types fall through to the AI fallback. * ``RESOLVED_GRAPH`` — resolved from ``ontology.yaml`` + ``binding.yaml``. The harness validates extractor output against it before fingerprinting. * ``SPEC`` — ``None``. MAKO extractors don't consume ``spec``; ``None`` matches the harness's keyword default. ``extract_mako_decision_event`` dispatches on ``content.tool`` and produces the per-tool slice of the MAKO graph: * ``capture_context`` → ``ContextSnapshot`` node. ``snapshot_payload`` (dict) is JSON-serialized to a string — MAKO declares ``snapshotPayload`` as ``xsd:string``; a raw dict would fail validation. * ``propose_decision_point`` → ``DecisionPoint`` node. * ``evaluate_candidate`` → ``Candidate`` node + ``evaluatesCandidate`` edge. * ``commit_outcome`` → ``SelectionOutcome`` node + ``selectedCandidate`` edge. Spans with a ``rationale`` field are ``partially_handled`` (rationale is trace-only, not declared on the MAKO entity). * ``complete_execution`` → ``DecisionExecution`` node + four hub edges (``executedAtDecisionPoint``, ``atContextSnapshot``, ``hasSelectionOutcome``, ``partOfSession``) + the envelope-side ``AgentSession`` node. The synthesis is what Beat 4.4's hub-shape traversal needs — the agent's tools never return a session payload, so the extractor has to manufacture the ``AgentSession`` node + the ``partOfSession`` edge from the plugin's envelope ``session_id``. Node-ID encoding follows the binding's per-entity PK columns from PR #155 (``decision_execution_id``, ``candidate_id``, etc.) so the materializer's ``parse_key_segment`` → FK column lookup resolves edges cleanly. Tests: ``tests/test_migration_v5_reference_extractor.py`` — 14 tests covering per-tool extraction, the module-level surface contract, empty/missing-field fast paths, and end-to-end validation. The decisive test ``test_full_decision_flow_validator_clean`` runs the canonical 5-event flow through the extractor + the merge helper and asserts the resulting ``ExtractedGraph`` passes ``validate_extracted_graph`` against the MAKO ``RESOLVED_GRAPH`` (6 nodes, 6 edges, ``report.ok``). Next: notebook cells 3.3-3.5 + 3.7 + 4.4 wired up to use this module. * fix(examples/migration_v5): session-scope PK values + edge IDs + span/trace provenance Three round-2 fixes for PR #157: * **P1 — cross-session PK / edge_id collision.** The agent's tools generate IDs via content-derived sha1 prefixes (``ctx-<10hex>`` / ``dp-<10hex>`` / etc.); two sessions whose tools receive the same arguments produce *identical* raw IDs (e.g. two ``capture_context`` calls with the same ``audience_size`` + ``budget_remaining_usd``). Before this fix: - Materialized node tables carried duplicate PK values across sessions. BigQuery doesn't enforce uniqueness but ``CREATE PROPERTY GRAPH`` declares ``KEY (...)`` and the graph traversal assumes uniqueness; the demo silently collapsed two sessions' decision flows. - Edge IDs (``evaluatesCandidate:{dp_id}:{cand_id}``, etc.) had the same collision because they used the raw tool IDs. Fix: new ``_scoped_id(session_id, raw_id)`` helper produces ``"{session_id}:{raw_id}"`` and is applied to every entity PK *value* + every edge_id. ``AgentSession`` is unchanged because its identity is already ``session_id``. Node_id key segments now encode the scoped PK value, so the materializer's ``parse_key_segment`` → FK column lookup populates edge tables with the matching scoped values. * **P2 — DecisionExecution provenance properties.** ``DecisionExecution.spanId`` and ``DecisionExecution.traceId`` are MAKO-declared and bound in ``binding.yaml`` (columns ``span_id`` / ``trace_id``), but the extractor wasn't emitting them. These are the provenance link back to the plugin trace. ``_extract_complete_execution`` now takes ``trace_id`` alongside ``span_id`` from the event envelope and emits both as properties when present. Sparse sources (offline replay, synthetic fixtures) that omit the envelope fields are handled gracefully — the extractor omits the properties rather than emitting empty strings. * **Tests** — three new tests, all passing alongside the existing 14: - ``test_two_sessions_have_unique_node_pk_values_and_edge_ids``: runs two distinct decision flows that share raw tool IDs, asserts every node's PK value is unique and every ``edge_id`` is unique. - ``test_complete_execution_carries_envelope_span_and_trace``: asserts the plugin envelope's ``span_id`` / ``trace_id`` flow onto the ``DecisionExecution`` properties. - ``test_complete_execution_omits_span_trace_when_missing``: a missing envelope ``trace_id`` produces a node without a ``trace_id`` property rather than an empty- string one. All 17 tests pass: ``PYTHONPATH=src python -m pytest tests/test_migration_v5_reference_extractor.py``
1 parent 80f6c6f commit d542399

2 files changed

Lines changed: 1030 additions & 0 deletions

File tree

0 commit comments

Comments
 (0)