Commit 80f6c6f
authored
* feat(examples/migration_v5): fixtures foundation for the four-guarantee notebook
Phase 1 of issue #107: fixtures directory under
examples/migration_v5/ that the four-guarantee MAKO demo
notebook (landing in a follow-up commit) consumes.
Inputs from the user-authored side (the "what you actually
have to provide" floor — one ontology file):
* mako_core.ttl — the real MAKO ontology pulled from the
reference gist (https://gist.github.com/haiyuan-eng-google/
a69ff6282ebcc877f77f9aa4e3db1afd). Domain-agnostic
decision semantics, agent coordination, outcome tracking
per Yahoo Monetization Platform's design doc.
Generated / scaffolded artifacts (the SDK + plugin produce
these; the demo never asks the user to hand-author them):
* ontology.yaml — full 380-line auto-import via gm import-owl
--include-namespace https://ontology.yahoo.com/mako/.
Captures all 41 MAKO entities; the notebook displays this
in Section 0 to show the realistic "import → resolve
FILL_INs → curate" workflow.
* ontology_demo.yaml — hand-curated 5-entity demo subset
(AgentSession, DecisionPoint, Candidate, SelectionOutcome,
ContextSnapshot) with FILL_IN primary keys resolved to id
per MAKO's "every artifact has a stable identifier"
contract. Validates clean against gm validate. The other
36 MAKO entities would scaffold the same way — narrowing
keeps the notebook's four-guarantee story focused.
* binding.yaml — auto-scaffolded via gm scaffold --ontology
ontology_demo.yaml --dataset migration_v5_demo --project
test-project-0728-467323 --out scaffold/. Demonstrates
the "one file in, two files out" minimum-input path the
storyboard's Section 0.5 calls out.
* table_ddl.sql — companion to binding.yaml, also from gm
scaffold.
* property_graph.sql — user-authored CREATE PROPERTY GRAPH
for the demo subset. Beat 1's "you own the graph
definition" evidence. Uses __DATASET__ placeholder for
per-run dataset substitution.
Demo-specific Python fixtures (the notebook imports these):
* seed_events.py — deterministic seeded RNG generator
producing 404 events across 50 sessions: each session
contains 2-4 decision points; each decision evaluates
3-5 candidates against a context snapshot and produces
one outcome. Seeded RNG (_RANDOM_SEED = 20260512) so the
notebook's outputs round-trip byte-identically across
runs. Event shape mirrors the BQ AA plugin's payload so
Beat 3 extractors see the same surface as production.
* reference_extractors.py — handwritten extractor for
mako_decision events (1 DecisionPoint + 1
SelectionOutcome + 1 ContextSnapshot reference + N
Candidates + their edges per event), plus the exact
EXTRACTORS / RESOLVED_GRAPH / SPEC module contract
bqaa-revalidate-extractors requires. Mirrors the BKA
decision pattern from
bigquery_agent_analytics.structured_extraction.
* revalidation_thresholds.json — threshold gate values for
bqaa-revalidate-extractors --thresholds-json: 95%
compiled_unchanged_rate, 99% parity_match_rate, etc.
Validation done in this commit:
* gm validate examples/migration_v5/ontology_demo.yaml →
clean.
* gm validate examples/migration_v5/binding.yaml --ontology
examples/migration_v5/ontology_demo.yaml → clean.
* seed_events.py generates 404 events across 50 sessions
(50 session-start + 50 session-end + 152 context + 152
decision events).
* reference_extractors.extract_mako_decision_event() on the
first seeded decision produces 8 nodes (1 DecisionPoint +
1 Outcome + 1 Context + 5 Candidates) and 7 edges.
Next commit lands the notebook itself.
* docs(examples/migration_v5): README for the fixture foundation
Surface the design decisions baked into the fixtures so the
fixture-shape review can happen before the notebook PR.
Per-decision rationale on: 5-entity MAKO subset, id-as-primary-key
strategy, NON-sorted skos:notation on DecisionPoint (exercises the
round-3 lex-min display-token rule end-to-end), seeded event shape
+ per-beat coverage check, reference extractor scope. Lists the
four validation commands already run + their pass status.
* refactor(examples/migration_v5): TTL-only authored input + mako_agent.py
Addresses PR #155 round-1 reviewer findings.
The previous fixture shape conflated "demo configuration"
with "ontology curation": ontology_demo.yaml was a
hand-curated subset, seed_events.py hardcoded event types
and demo-only fields not present in MAKO, property_graph.sql
used semantic edge columns inconsistent with the scaffolded
binding's from_id/to_id defaults, and project/dataset were
baked into checked-in artifacts.
Revised contract: exactly TWO files in examples/migration_v5/
are user-authored inputs. Everything else is a generator
output.
Authored inputs:
* mako_core.ttl — the real MAKO ontology.
* mako_agent.py — the only authored Python. Loads the TTL,
resolves FILL_IN primary keys to synthesized id: string
(matches MAKO's "every artifact has a stable identifier"
design contract), drops cross-namespace dangling
relationships (e.g. delegatedTo → prov:Agent — MAKO
extends PROV-O but the demo doesn't model the external
namespaces; dropped relationships are recorded in the
ontology's annotations under
mako_demo:dropped_cross_namespace_relationships so the
loss is auditable). Generates binding for any
(project, dataset) pair, derives table DDL +
property-graph SQL from the binding (edge columns are
consistent across both — no more from_id vs session_id
mismatch), and emits a deterministic event stream whose
payloads carry ONLY MAKO-declared data properties.
Generated artifacts (reproducibility snapshots produced by
mako_agent.regenerate_snapshots):
* ontology.yaml — gm import-owl output with FILL_INs
resolved, dangling relationships dropped.
* binding.yaml — derived from the ontology for the demo
entity set.
* table_ddl.sql — derived from the binding.
* property_graph.sql — derived from the binding. Edge
columns match table_ddl.sql.
* events.jsonl — 398 deterministic events across 50
sessions.
Demo entity set bumped from 5 → 6: DecisionExecution is
MAKO's central hub (partOfSession an AgentSession,
atContextSnapshot a ContextSnapshot,
executedAtDecisionPoint a DecisionPoint,
hasSelectionOutcome a SelectionOutcome). The decision-flow
story doesn't hold together without it.
Edge set is now fully TTL-driven: the agent walks
ontology.relationships and picks every relationship whose
endpoints both fall within DEMO_ENTITIES. No hardcoded edge
list. Picks up 9 relationships from MAKO without any name
authoring (atContextSnapshot, evaluatesCandidate,
executedAtDecisionPoint, hasSelectionOutcome, partOfSession,
rejectedCandidate, selectedCandidate, plus two others).
Event payload contract: MAKO models domain entities; the
BQ AA plugin telemetry envelope wraps each event with
event_type / session_id / span_id / event_timestamp /
content. The content dict carries only MAKO-declared data
properties (AgentSession.sessionId, DecisionExecution
.businessEntityId / latencyMs / spanId / traceId,
DecisionPoint.reversibility, ContextSnapshot.snapshotPayload
/ snapshotTimestamp) plus foreign-key references. No
demo-only invented fields.
Validation done:
* gm validate ontology.yaml → clean.
* gm validate binding.yaml --ontology ontology.yaml → clean.
* mako_agent.py end-to-end: {"ontology_entities": 18,
"binding_entities": 6, "binding_relationships": 9,
"events": 398}.
README rewritten to document the authorship boundary
explicitly and call out each design choice with the
rejected alternative spelled out.
* refactor(examples/migration_v5): split artifacts vs runnable plugin-instrumented agent
Addresses PR #155 round-2 product clarification (#155 comment
4437670647). Previous shape had mako_agent.py double as
TTL→artifacts AND event generator, which silently bypassed the
BQ AA plugin's event-emission contract. New shape pins the
contract: the demo's event source of truth is a runnable agent
talking to BigQueryAgentAnalyticsPlugin, not a Python fixture.
Authored inputs (now five files, all in this directory):
* mako_core.ttl — unchanged. The real MAKO ontology.
* mako_artifacts.py — renamed from mako_agent.py; stripped of
TelemetryEvent / generate_events / events_as_jsonl. Pure
TTL→ontology / binding / DDL / property-graph pipeline.
Does NOT generate events.
* mako_demo_agent.py — new. Runnable ADK Agent with five MAKO
decision-flow tools (capture_context,
propose_decision_point, evaluate_candidate, commit_outcome,
complete_execution) and BigQueryAgentAnalyticsPlugin
wiring. Same pattern as
examples/decision_lineage_demo/agent/agent.py. Uses Vertex
AI Gemini (default gemini-2.5-flash).
* run_agent.py — new. Driver. `python run_agent.py --sessions
50 --project X --dataset Y` runs the agent for N sessions
and the plugin populates agent_events as a side effect.
Each prompt asks the agent to walk through one full MAKO
decision flow; the plugin captures the LLM_RESPONSE rows +
TOOL_* rows that downstream analytics consume.
* export_events_jsonl.py — new. Optional. Exports a pinned
subset of agent_events to a local JSONL snapshot for the
notebook's deterministic offline revalidation tests. NOT an
event generator — reads from BigQuery.
Generated artifacts (reproducibility snapshots; regenerable
via `python mako_artifacts.py --project X --dataset Y`):
* ontology.yaml, binding.yaml, table_ddl.sql,
property_graph.sql
Removed:
* events.jsonl — was a fixture-script output; the new shape
makes this a captured BQ snapshot only.
* TelemetryEvent class + generate_events function from the
artifact module. Events now come from the plugin, not a
fixture.
Validation done in this commit:
* PYTHONPATH=src python mako_artifacts.py → 18 ontology
entities, 6 binding entities, 9 relationships.
* PYTHONPATH=src python -m bigquery_ontology.cli validate
ontology.yaml → clean.
* PYTHONPATH=src python -m bigquery_ontology.cli validate
binding.yaml --ontology ontology.yaml → clean.
* mako_demo_agent imports cleanly: LlmAgent + 5 tools +
BigQueryAgentAnalyticsPlugin attached.
* run_agent.py --help works without live BQ / Vertex.
NOT done in this commit (next step on the same branch):
* Live end-to-end run of run_agent.py against the demo's
Vertex AI + BigQuery dataset. This is the notebook's job
in Beat 0; landing it as a separate commit after this
fixture shape is signed off.
* The notebook itself.
README rewritten to spell out the authorship boundary
explicitly and document each design decision with the
rejected alternative spelled out.
* fix(examples/migration_v5): self-edge dup columns + property-type DDL + trace mapping
Round-3 fixes for PR #155 review:
* P1 — self-referential edges generated duplicate columns.
``evolvedFrom`` / ``supersededBy`` are
DecisionExecution → DecisionExecution self-edges; naming
both endpoints ``decision_execution_id`` produced
``CREATE TABLE foo (decision_execution_id STRING,
decision_execution_id STRING)``. ``make_binding`` now
switches to ``src_{base}_id`` / ``dst_{base}_id`` only
for self-edges; heterogeneous edges keep the simpler
naming. Property graph SQL stays in sync because it reads
``from_columns`` / ``to_columns`` from the same binding.
* P1 — table DDL ignored ontology property types. Every
column was ``STRING`` (plus a hacky ``*Timestamp``
suffix), silently dropping the typing the TTL declared
(e.g. ``DecisionExecution.latencyMs`` is ``xsd:integer``,
but landed as STRING). ``make_table_ddl`` now takes the
ontology, builds an ``(entity, prop) → BQ type`` map via
the new ``_bq_type_for`` helper, and emits real BQ types.
``latency_ms`` is now ``INT64`` in ``table_ddl.sql``.
* README — added explicit "trace → MAKO mapping" contract.
The agent uses realistic snake_case tool names (e.g.
``decision_point_id``, ``snapshot_payload``,
``business_entity_id``) rather than TTL-camelCase. The
README now spells out which trace fields become
TTL-declared properties at extraction time and which are
trace-only (e.g. ``rationale``, ``candidate_label``).
* fix(examples/migration_v5): DDL metadata cols, plugin schema, ident validation
Round-4 fixes for PR #155 review:
* P1 — table DDL now includes the SDK metadata columns that
binding-validate requires on every bound table. The
materializer (``ontology_materializer._entity_columns`` /
``_relationship_columns``) writes ``session_id STRING`` +
``extracted_at TIMESTAMP`` on every ``materialize()``
call, and ``binding_validation.py`` (lines 488, 806)
enforces both columns. Without them, the notebook's
binding-validate step would fail before ontology-build.
``make_table_ddl`` now appends both, deduping against any
domain property that already maps to the same column
(MAKO's ``AgentSession.sessionId → session_id`` is the
canonical collision case).
* P1 — ``export_events_jsonl.py`` now targets the BQ AA
plugin's real schema. The previous SELECT projected
``event_id``, ``agent_name``, ``payload``, ``content``,
``event_timestamp``, ``partition_date`` — none of which
exist on the plugin's table. The plugin emits
``timestamp``, ``event_type``, ``agent``, ``session_id``,
``invocation_id``, ``user_id``, ``trace_id``, ``span_id``,
``parent_span_id``, plus JSON ``content`` / ``attributes``
/ ``latency_ms`` (verified by reading
``_get_events_schema`` in
``bigquery_agent_analytics_plugin.py``). Partitioning is
on ``timestamp`` (DAY), not a separate column. Ordering
is now ``ORDER BY timestamp, span_id`` since there is no
``event_id``.
* P2 — ``export_events_jsonl.py`` validates each segment of
``--project / --dataset / --table`` against
``[A-Za-z0-9_-]+`` (same character class
``bq_bundle_mirror._TABLE_ID_PATTERN`` uses) before
interpolating into the ``FROM`` clause. BigQuery
identifiers can't be passed as query parameters; this
check rejects whitespace, backticks, semicolons, dots,
and any character outside the BQ-permitted set per
segment.
README updated with two new design-decisions sections:
events.jsonl is captured from the real plugin schema, and
the DDL carries SDK metadata columns.
* docs(examples/migration_v5): node_id convention + soften exporter docstring
Round-5 doc-only follow-ups from PR #155 review:
* P2 — pin the reference-extractor node_id convention in
README. The materializer
(``ontology_materializer._build_edge_row`` line 322)
derives edge FK column values by *parsing endpoint
node_ids*, not by reading node properties. A node_id like
``sess:DecisionExecution:id=exec-1`` will not populate an
``decision_execution_id`` FK column ---- whatever keys
the binding declares for FKs are the keys the extractor
must encode in the node_id. This is a silent-failure
trap (extractor "runs", edges get empty-string FKs) and
is especially subtle for self-edges where the same node
needs ``src_*`` / ``dst_*`` keys too. Documented the
convention with a worked DecisionExecution example so
the notebook + reference extractor (follow-up commit)
can centralize the encoding in a ``node_id_for(...)``
helper.
* P3 — soften the exporter docstring. The selected
columns are a *subset* of the plugin schema, not the
full schema: ``content_parts`` (REPEATED RECORD for
multimodal parts) is omitted because the MAKO decision
flow is text-only. Docstring now says "selected columns
from the plugin schema" and points to the one-line
change needed if a future demo needs multimodal trace
replay.
* docs(examples/migration_v5): README parity with round-5 doc fixes
Two stale phrasings in README missed by the previous
commit:
* ``_build_edge_row`` doesn't exist; the function is
``_route_edge`` (``ontology_materializer.py:314``).
Behavior described was already correct, only the symbol
name was wrong.
* "Mirrors the BQ AA plugin's real schema" overstates what
the exporter does ---- ``content_parts`` (REPEATED
RECORD) is omitted because the MAKO demo is text-only.
README now matches the exporter docstring: "projects the
subset of the plugin schema the notebook needs", with
the one-line change for multimodal replay.
* docs(examples): replace migration notebook with four-guarantee scaffold (#107)
First pass for #107: replace the ontology-migration notebook content
with the four-guarantee storyboard scaffold.
Section 0 fully written (no live BQ required to parse):
* License header + title + four-guarantee narrative table.
* "What you need to bring" — minimum-input answer: one
ontology file. Three input shapes side by side.
* Install + auth + scratch dataset + ``FEATURES`` flags
for per-beat gating (#58/#75/#76/#104/#105).
* TTL → ontology / binding / DDL via
``mako_artifacts.regenerate_snapshots(project, dataset)``.
* Apply ``table_ddl.sql`` against the scratch dataset.
* Run the real ADK Gemini agent via ``run_agent.py
--sessions N``; the BQ AA plugin populates
``agent_events`` as a side effect.
* Sanity-check counts by event_type.
Sections 1-4 are section-header markdown placeholders; the
beat-specific cells (Own / Validate / Extract cheaply /
Resolve) land in passes 2-5. Section 5 (close) is fully
written so the storyboard's recap is in place.
The notebook validates against nbformat 4.5 (verified via
``nbformat.validate``) and the code cells parse as valid
Python (verified via ``ast.parse`` after stripping the
``!pip`` magic).
Live execution against ``test-project-0728-467323``
follows as a separate commit once Sections 1-4 are filled
in.
* fix(examples/migration_v5): live-run readiness nits from round-6 review
Four small fixes uncovered when the notebook scaffold put
``run_agent.py`` on the live path:
* P2 — agent SYSTEM_PROMPT step 5 now lists all four
arguments ``complete_execution`` requires
(``decision_point_id``, ``context_id``, ``outcome_id``,
``business_entity_id``). Gemini can infer from the
function schema, but the instruction was weaker than the
tool signature, which risks degraded event quality on
the live run.
* P3 — notebook cell 13 now passes ``env={**os.environ,
"DEMO_AGENT_LOCATION": AGENT_LOCATION, ...}`` to
``subprocess.run`` so a notebook-variable override of
``AGENT_LOCATION`` / ``PROJECT_ID`` / ``DATASET_ID``
actually reaches the child process. Without this, the
displayed value diverges from what the agent module
picks up at import time.
* P3 — feature-flag wording updated. All underlying issues
have shipped; the gates are runtime/cost knobs now ("flip
False to skip live, expensive, or environment-specific
beats"), not dependency gates.
* P3 — the PR claim "code cells parse via ``ast.parse``"
needed qualification since the ``!pip`` install cell is
valid IPython but not valid Python. The verification
script now explicitly skips shell-magic cells before
``ast.parse``.
* docs(examples): notebook Section 1 — Beat 1, "you own the graph" (#104)
Pass 2: replace the Section 1 placeholder with the live
"own the graph" beat. Cells 1.1-1.6:
* 1.1 [md] Framing — before #104 every build ran
``CREATE OR REPLACE PROPERTY GRAPH``; after, the SDK
populates base tables and leaves the user's graph DDL
alone.
* 1.2 [py] Apply ``examples/migration_v5/property_graph.sql``
to the scratch dataset.
* 1.3 [py] Capture ``before_skip_build_ts`` *after* the
user's DDL finishes; baseline a GQL traversal so we have
a row-count reference for cell 1.5.
* 1.4 [py] Discover ``session_ids`` from ``agent_events``;
run ``bq-agent-sdk ontology-build --skip-property-graph
--ontology --binding --session-ids --location``. Print
the build's ``property_graph_status`` so the skipped
state is visible.
* 1.5 [py] Two-pronged evidence:
- (a) ``INFORMATION_SCHEMA.JOBS_BY_PROJECT`` filtered by
``creation_time > @before_ts`` + dataset-name pattern
+ ``REGEXP_CONTAINS(UPPER(query),
r'CREATE\s+(OR\s+REPLACE\s+)?PROPERTY\s+GRAPH')``.
Combining the timestamp filter (so the user's own
cell-1.2 DDL is excluded) with the regex catches the
SDK-issued spelling.
- (b) Re-run the GQL traversal; assert the count is
``>= before_skip_build_rows`` (the graph object is
intact and now has populated base tables).
* 1.6 [md] Beat 1 closing.
All cells gate on ``FEATURES["skip_property_graph"]``;
flipped off they print "Skipped: skip_property_graph
feature off" rather than failing.
Used ``rf"""`` for the JOBS_BY_PROJECT SQL so the regex
literal ``r'CREATE\s+...'`` doesn't trigger
Python 3.12+'s SyntaxWarning on the ``\s`` escape.
* fix(examples): Section 1 verify-cell parity with live #104 regression test
Three findings from review of pass 2 (Section 1):
* P1 — JOBS_BY_PROJECT filter now mirrors the live #104
integration test
(``tests/test_integration_ontology_binding.py``):
``query LIKE '%mako_demo_graph%' AND EXISTS (SELECT 1
FROM UNNEST(j.labels) AS l WHERE l.key='sdk_feature'
AND l.value='ontology-gql')``. The previous
``query LIKE '%{DATASET_ID}%'`` filter was too narrow —
a regressed SDK can target the orchestrator dataset
rather than the binding's dataset in split setups, and
the regression would slip past a dataset-pattern check.
Filtering by graph name + SDK feature label catches the
regression regardless of which dataset the build wrote
into. The post-DDL timestamp filter still excludes the
user's own authored DDL from cell 1.2 (which also
carries the ontology-gql label).
* P2 — Cell 1.4 now asserts ``build_result["property_graph_status"]
== "skipped:user_requested"`` immediately after parsing
the CLI stdout. A regression that returned success but a
different (or absent) status would otherwise silently
pass the JOBS_BY_PROJECT check too.
* P3 — Cell 1.5 now asserts ``total_rows > 0`` (sum of
``build_result["rows_materialized"]``). The previous
GQL ``after >= before`` check trivially passed when
both counts were zero, so an empty-graph regression
would slip through. The build's own materialization
count is the authoritative answer to "did extraction
produce data?"
* docs(examples): notebook Section 2 — Beat 2, pre-flight binding-validate (#105)
Pass 3: replace the Section 2 placeholder with the live
"validate" beat. Also fixed a small wording nit in cell 1.5
markdown (no longer says "against the scratch dataset",
since the JOBS_BY_PROJECT filter is now graph-name + SDK-
label scoped, not dataset-scoped).
Section 2 cells:
* 2.1 [md] Framing — pre-flight catches drift in under a
second; exit code 1 short-circuits before AI.GENERATE
fires.
* 2.2 [py] Inject column-rename drift:
``ALTER TABLE context_snapshot RENAME COLUMN
snapshot_payload TO snapshot_payload_v2``. Print the
table's columns before + after so the drift is visible.
* 2.3 [py] Run ``binding-validate --format json``.
Assertions: exit code 1, at least one ``MISSING_COLUMN``
failure on ``context_snapshot.snapshot_payload``. Print
each failure with ``binding_path`` + ``bq_ref`` (the
fields that point at the exact authoring site).
* 2.4 [py] Restore the column name and re-run. Assertions:
exit code 0, ``report.ok is True``.
* 2.5 [py] Combine Beat 1 + Beat 2 in one invocation:
``ontology-build --skip-property-graph --validate-binding``.
Assertions: ``property_graph_status =
'skipped:user_requested'`` AND ``rows_materialized
total > 0`` (the build's authoritative
did-extraction-produce-data answer).
* 2.6 [md] Closing.
All cells gate on ``FEATURES["binding_validate"]``; cell
2.5 also requires ``skip_property_graph``. Gated off,
cells print "Skipped: ..." rather than failing.
Cell 1.5 markdown reworded: "jobs targeting
``mako_demo_graph`` and carrying the SDK's
``sdk_feature='ontology-gql'`` label, with
``creation_time > before_skip_build_ts``". The code does
not filter by dataset name; the markdown now matches.
* fix(examples): Section 2 round-8 — JSON code casing + idempotent restore
Three findings from review of pass 3 (Section 2):
* P1 — Cell 2.3 was checking ``f["code"] == "MISSING_COLUMN"``,
but the CLI emits ``f.code.value`` from the
``FailureCode`` enum, whose values are lowercase
(``"missing_column"``). The Python enum *names* are
uppercase but the JSON values aren't — easy to miss
without reading the CLI source. Cell 2.3 now checks
``"missing_column"`` and the 2.3 framing markdown calls
out the casing convention explicitly.
* P2 — Cell 2.2 mutates the live table; if anything between
it and cell 2.4 asserts, the column stays renamed and
the rest of the notebook breaks. Cell 2.3 was the most
likely culprit (it asserts on the validator output).
Two compounding fixes:
- Cell 2.3 now prints all structured output *before*
asserting, so the report shape is visible even when an
assertion raises.
- Cell 2.4's restore is now idempotent — it reads the
current schema and renames-or-noops based on which
column exists, so the user can re-run cell 2.4 by
itself after a cell-2.3 failure and get back to the
binding-expected shape.
* P3 — Cell 2.3 markdown said paths look like
``entities.ContextSnapshot.properties.snapshotPayload.column``,
but the validator emits indexed paths
(``binding.entities[2].properties[1].column``). The
notebook prints ``binding_path``, so the framing now
matches the real shape with a one-line callout about
the name-keyed fallback for the unresolved-spec case.
* docs(examples): notebook Section 3 — Beat 3 cost + validate (#75 + #76)
Pass 4: replace Section 3 placeholder. Cell 2.4 also
hardened against kernel-restart loss of ``rename_to`` /
``validate_cmd``.
Section 3 cells:
* 3.1 [md] Framing — session-aggregated AI.GENERATE is the
cost driver; compiled extractors handle structured spans,
AI handles narrative spans; validation-gated prune
determines transcript composition. Table maps C1 / C2
sub-phases to cells.
* 3.2 [py] Live baseline: per-session transcript size from
``agent_events`` (``LENGTH(TO_JSON_STRING(content))``
per row, summed per session). Estimate uses
4-chars-per-token; honest about being a prompt-size
estimate, not billing usage. Per-event token attribution
isn't available from current architecture; the **session**
is the cost unit.
* 3.3 [py] / 3.4 [py] / 3.5 [py] / 3.7 [py] — gated
placeholders. The cells need
``examples/migration_v5/reference_extractor.py`` (a
MAKO-specific reference extractor analogous to the
shipped ``extract_bka_decision_event``). PR #155 is
fixtures-only; the reference extractor + the live
measurement / runtime / savings cells land in PR #156.
Each placeholder documents the future-PR shape so the
follow-up author has a target.
* 3.6 [py] **Live** ``ValidationReport`` demo against
synthetic ``ExtractedGraph`` fixtures. NODE / FIELD /
EDGE scopes all fire as expected (verified locally
against ``mako_artifacts``-generated ontology +
binding); each scope is asserted so a regression in
``graph_validation.py`` scope classification would
catch here.
* 3.8 [md] Beat 3 status — what's live vs what waits on
PR #156.
The cell-2.4 harden: ``rename_to`` defaults to
``"snapshot_payload_v2"`` if not already in ``locals()``,
and ``validate_cmd`` is rebuilt locally if missing. So
restart-and-rerun-from-cell-2.4 works cleanly.
* fix(examples): Beat 3 baseline guard rails + Section 5 recap honesty
Two round-9 fixes:
* P2 — Cell 3.2 (baseline cost view) now uses
``SUM(COALESCE(LENGTH(TO_JSON_STRING(content)), 0))``
so a row whose ``content`` is NULL doesn't NULL-out the
session total (and crash the format string downstream).
Adds two guard-rail assertions: ``baseline_rows`` must
be non-empty and ``total_chars`` must be positive. A
zero baseline would mean "Section 0 didn't actually
populate ``agent_events``" — surface that as a hard
failure rather than letting Beat 3 quietly claim a live
baseline on an empty corpus.
* P2/P3 — Section 5's recap row for "Extract cheaply"
said "Per-session token table shows the savings", but
this PR only ships the *baseline* table; the
compiled-savings *delta* is deferred to PR #156. Row
now says "Cell 3.2 establishes the pre-compile
baseline; the compiled-savings delta lands with PR #156"
so the recap matches what's actually live.
* docs(examples): notebook Section 4 — Beat 4, concept-index resolver (#58)
Pass 5: replace Section 4 placeholder. Also adds inheritance
stripping to ``mako_artifacts.py`` so the binding compiles
through ``gm compile`` cleanly.
Fixture change: ``_strip_inheritance`` post-processor
The MAKO TTL declares ``mako:Candidate rdfs:subClassOf
mako:RoleTrait``; the OWL importer surfaces it as
``Candidate.extends: RoleTrait``. ``gm compile`` v0
doesn't support inheritance and rejects the binding with
``compile-validation — Entity 'Candidate' uses 'extends'``,
which blocks Section 4's ``--emit-concept-index`` step.
``RoleTrait`` is a marker class in MAKO (REQ-ONT-022,
"single-primary-parent inheritance discipline") with no
properties beyond the ``id`` PK every other entity already
carries. ``_strip_inheritance`` drops ``extends`` from the
post-import YAML, adds the ``id`` PK Candidate inherited
from RoleTrait, and records the discard under
``mako_demo:stripped_inheritance`` (per-entity flat string,
plus a comma-joined top-level summary — annotations are
typed ``dict[str, str]`` so the value can't be a list).
The check passes: 18 / 6 / 9 counts unchanged, both
``gm validate`` invocations still clean, ``gm compile
--emit-concept-index`` now emits the property-graph DDL
+ the concept-index ``CREATE OR REPLACE TABLE`` pair.
Section 4 cells:
* 4.1 [md] Framing — user-typed → canonical via
concept-index lookup. PR #92 emission + #58 reader.
Notes that MAKO TTL ships no SKOS labels, so the demo
resolves by entity name only; richer TTLs extend the
same demo with no code changes.
* 4.2 [py] Run ``gm compile --emit-concept-index
--concept-index-table <FQN>`` and print the two
``CREATE OR REPLACE TABLE`` statements (main + __meta)
with their shared ``compile_fingerprint``.
* 4.3 [py] Apply the DDL to the scratch dataset, construct
``OntologyRuntime.from_files(..., concept_index_table=,
bq_client=)``, wrap in ``LabelSynonymResolver``, resolve
``"DecisionExecution"``. Print each candidate's
``entity_name`` / ``matched_label_kind`` / ``compile_id``.
* 4.4 [py] GQL traversal scoped by the resolved entity:
``COUNT(*)`` over ``GRAPH_TABLE(... MATCH
(n:{resolved_entity}) ...)`` plus a hub-shape
``(de:DecisionExecution)-[:partOfSession]->
(s:AgentSession)`` sample so the resolver → graph wiring
is visible. Guard-rail check on the resolved entity to
ensure it's in the demo allowlist before f-stringing into
the GQL label position.
* 4.5 [md] Closing.
All cells gate on ``FEATURES["concept_index_reader"]``.
* fix(examples): Section 4 round-10 — compiler-version drift + non-zero asserts
Three findings from review of pass 5 (Section 4):
* P1 — Cell 4.3 hard-coded ``compiler_version=
"bigquery_ontology 0.2.2"`` in ``OntologyRuntime.from_files``,
but ``gm compile`` defaults to the installed package
version (0.2.3 in the reviewer's env). The version
string is part of the fingerprint hash, so the runtime
trips ``FingerprintMismatchError`` the moment package
versions don't line up. Fix: query
``{CONCEPT_INDEX_TABLE}__meta.compiler_version`` after
applying the DDL and pass that value to
``from_files(...)``. Same source of truth the compiler
emitted; no manually-pinned constant to drift.
* P2 — Cell 4.4 printed the GQL count and the hub-shape
rows without ever asserting either was non-empty. A
zero count or zero hub rows would still close the cell
"successfully", making the demo's climax claim
("user-typed name routes to real graph data") an empty
print. Added ``assert count > 0`` and
``hub_rows = list(...); assert hub_rows`` with messages
that point at the likely Beat 1 root cause (empty
materialization).
* P3 — Beat 4 closing markdown said "the user typed
natural language; the SDK resolved it via the SKOS
taxonomy the user authored once". Accurate for a
SKOS-rich TTL; misleading for the MAKO fixture, which
ships no labels and resolves only by canonical entity
name. Reworded to explicitly call out that fact and
describe the label-kind ranking
(``name > pref > alt > synonym > notation``) that
kicks in when richer rows are present.
* fix(examples): Section 4 P1 (actually) + label-kind ranking
Round-10 claimed to source ``compiler_version`` from the
``__meta`` table, but the patch never landed: an
AssertionError on a subsequent cell aborted the script
before the file write. The committed file still had
``compiler_version="bigquery_ontology 0.2.2"`` hard-coded,
which trips ``FingerprintMismatchError`` against any other
installed package version (0.2.3 in the reviewer's env).
Properly apply the fix this time. Cell 4.3 now:
* Queries
``SELECT compiler_version, compile_fingerprint FROM
`{CONCEPT_INDEX_TABLE}__meta``` right after applying the
DDL.
* Binds the result to ``COMPILER_VERSION`` and passes it
to ``OntologyRuntime.from_files(...)``. The SDK's own
``__meta`` row is the source of truth — there's no
manually-pinned constant to drift.
* Prints both ``compiler_version`` and
``compile_fingerprint`` so the audit trail is visible.
Also: Beat 4 closing markdown had ``name > pref > alt >
synonym > notation``, but the runtime priority
(``_LABEL_KIND_PRIORITY``) is ``name > pref > alt >
hidden > synonym > notation``. Added the missing
``hidden`` rung.
* docs(examples): Beat 4 wording consistent with what the fixture actually does
Two doc-only nits from round-11 review:
* Beat 4 prose in four sites still said the user types
natural-language inputs like ``"Consumer Banking"`` and
the resolver maps to canonical names like
``skos:RetailBanking``. The MAKO TTL ships no SKOS
labels, so this fixture resolves canonical entity names
(``DecisionExecution``), not natural-language. The
intro table, Beat 4 framing, climax-query paragraph,
and Section 5 recap row are now consistent: each calls
out "canonical entity label in this fixture; richer
ontologies use the same resolver path for
``skos:altLabel`` / ``skos:prefLabel`` /
``skos:notation`` rows."
* The 4.1 framing also listed ``label_kind`` candidates
without ``hidden`` (Beat 4 closing already had it, the
4.3 framing didn't). Added ``hidden`` so the three
``label_kind`` enumerations in the notebook match the
runtime priority
(``name > pref > alt > hidden > synonym > notation``).
* feat(examples): live notebook + fixture fixes for end-to-end run
Live end-to-end run against ``test-project-0728-467323``
(3 sessions, scratch dataset
``migration_v5_demo_7da57280``). All 59 cells passed.
Fixture changes that unblocked the live run:
* Entity PK columns are now per-entity rather than a bare
``id``. The materializer's
``_relationship_columns`` (``ontology_materializer.py``)
looks up edge FK columns in
``src_prop_map[col].sdk_type`` — that lookup requires
the FK column to *exactly* name a column on the source
entity. With bare ``id``, every edge would land
``(id STRING, id STRING)`` (duplicate column) AND the
materializer's FK→PK type lookup would still miss for
cross-entity edges. Renaming PK columns to
``{entity_short}_id`` matches the convention the
original V5 spec used (``decision_id``, ``adUnitId``,
etc.) and the integration-test fixture
(``tests/fixtures/test_binding.yaml``). Note the
``_entity_id_column`` helper no longer strips ``agent_``
for AgentSession — that strip collided with
``AgentSession.sessionId``'s ``session_id`` column and
the SDK metadata ``session_id``. AgentSession is now
``agent_session_id``.
* Self-edges are dropped (was 9 → 7 relationships). MAKO's
``evolvedFrom`` and ``supersededBy`` are
``DecisionExecution → DecisionExecution`` self-edges;
the materializer's FK→PK lookup can't disambiguate them
via ``src_/dst_`` prefixed columns and the natural
composite-key pair is ``(decision_execution_id,
decision_execution_id)`` — duplicate. The ontology still
declares the edges; only the binding scope drops them.
* Property graph DDL: edge ``REFERENCES`` clauses use the
node-table **alias** (``decision_execution`` not
``\`proj.ds.decision_execution\```) because BigQuery
rejects qualified refs in
``CREATE PROPERTY GRAPH``. Node ``KEY (...)`` and edge
``REFERENCES alias (col)`` both use the per-entity PK
column (``KEY (decision_execution_id)`` instead of
``KEY (id)``). Edge tables additionally need explicit
``KEY (src_col, dst_col)`` — BQ rejects without it.
* Inheritance: ``_strip_inheritance`` post-processor drops
``extends: RoleTrait`` from Candidate so ``gm compile``
v0 accepts the binding (v0 doesn't support inheritance).
Notebook changes for the live path:
* Cell 1.4 + cell 2.5 subprocess calls now surface the
child process's stderr on failure. Previous
``capture_output=True, check=True`` masked the error
text from the notebook; the operator had to re-run the
CLI by hand to see what failed.
* Cells 2.3 / 2.4 / 2.5 add a local ``import json``
because cell 1.4 imports ``json as _json`` (aliased)
which leaves the bare name ``json`` unbound for later
cells.
* Cell 4.4 builds a ``_PK_COL`` map (entity → PK column)
because the GQL ``COLUMNS`` clause needs the per-entity
PK column name (``decision_execution_id`` etc.), not
``id`` which is now the property *logical* name only.
* Hub-shape traversal in Beat 4 now degrades gracefully
to a "AgentSession synthesis is a PR #156 follow-up"
note rather than asserting. The AI extraction in this
demo doesn't synthesize ``AgentSession`` nodes /
``partOfSession`` edges from the plugin envelope; the
follow-up will.
Live evidence captured in the executed notebook (now
committed with outputs):
- Beat 1 verify: GQL count before=0, after=2; no
SDK-issued CREATE OR REPLACE PROPERTY GRAPH job;
rows_materialized total = 35;
property_graph_status = 'skipped:user_requested'.
- Beat 2: drift detected (exit 1, missing_column);
restore + re-validate (exit 0, report.ok = True);
combined build (exit 0, rows_materialized total = 35).
- Beat 3.6: ``ValidationReport`` against synthetic
``ExtractedGraph`` — NODE + FIELD + EDGE scopes all
fire as expected.
- Beat 4: concept index materialized; resolver returns
DecisionExecution with compile_id ``eca9978b0550``;
GRAPH_TABLE count = 4 against the user-authored
property graph.
Beat 3 compile / runtime / savings cells (3.3-3.5, 3.7)
remain PR #156 placeholders; their gates are still
documented as "requires MAKO reference extractor".
* docs(examples): post-live-run cleanup — install cell, README, copy
Four follow-up nits from review of the live-run capture:
* **install cell**: ``!pip install ... google-adk[vertexai] ...``
failed under zsh with ``no matches found`` because zsh
glob-expanded the ``[vertexai]`` extra. Switched to
``%pip install`` (Jupyter magic that bypasses shell
expansion) and quoted the extra. Without the fix the
install was silently a no-op and downstream cells ran
against whatever was already in the kernel env —
explaining the ``bigquery_ontology 0.2.2`` in the
committed outputs vs. ``0.2.3`` in a fresh checkout.
* **Generated artifacts regenerated against the default
``(test-project-0728-467323, migration_v5_demo)`` pair**
rather than the scratch dataset from the live run.
Previous commit shipped ``binding.yaml`` /
``table_ddl.sql`` / ``property_graph.sql`` against the
ephemeral ``migration_v5_demo_7da57280`` dataset, which
is residue, not stable defaults. Reviewers can now ``cat``
the artifacts and see the canonical target.
* **README**: substantial rewrite. Removed the "fixture
only, notebook is future work" framing; added two new
design-decision sections (8: per-entity PK columns, 9:
self-edges dropped, 10: inheritance stripped); updated
the relationship count (9 → 7); corrected the FK column
description (was "AgentSession FK is ``session_id``"
via ``_entity_id_column`` stripping ``agent_`` — now
``agent_session_id`` since the strip was removed in the
live-run reconciliation); removed the obsolete
self-edge node_id encoding section (self-edges are now
dropped); added a "What's NOT in this commit" pointing
at PR #156's ``AgentSession`` synthesis follow-up.
* **Notebook framing**: corrected "nine real MAKO
relationships" → "seven" so the framing matches the
binding.
Live-run evidence in the committed executed notebook is
unchanged; the cells ran against the scratch dataset
correctly because ``mako_artifacts.regenerate_snapshots``
is called at runtime with the scratch ``(project,
dataset)``. The committed snapshot is the stable default
that the regenerator overwrites every time.
* docs(examples): rerun notebook with %pip install cell
Live rerun against fresh scratch dataset
``migration_v5_demo_badf2e2a``. All 59 cells passed. The
``%pip`` install cell still surfaces a PEP-668
``externally-managed-environment`` rejection in homebrew
Python (the install cell is targeted at Colab; in a
system-managed env the operator pre-installs into a venv
before launching the kernel). That's environment-
specific, not a notebook-correctness issue — the live
evidence below confirms the SDK behavior works against
real BigQuery + Vertex regardless.
Updated evidence (from the committed executed notebook):
- Beat 1: GQL count before=0, after=1; rows_materialized
total=19; property_graph_status='skipped:user_requested'.
- Beat 2: drift caught (exit 1), restored (exit 0),
combined build clean (rows_materialized total=19).
- Beat 3: 3.3-3.5+3.7 print the "requires MAKO reference
extractor at examples/migration_v5/reference_extractor.py
— lands in follow-up PR #156" skip marker. 3.6 fires
NODE + FIELD + EDGE.
- Beat 4: resolver returns DecisionExecution with
compile_id=07aa0018288c; GRAPH_TABLE count = 2. Hub-
shape returns zero (expected — AgentSession synthesis
lands with PR #156).
* docs(examples): env-aware install cell + README evidence cleanup
Three follow-up nits from review of the previous rerun:
* P1 — install cell rewritten as **environment-aware**.
* In Colab: runs ``%pip install`` via
``get_ipython().run_line_magic("pip", ...)`` (the
Jupyter magic that installs into the kernel env
without going through the shell). Quoted
``"google-adk[vertexai]"`` so zsh doesn't glob it.
* Outside Colab: skips the install entirely. System
Python (PEP 668-managed) refuses pip installs and
the expected workflow is a pre-baked venv. The cell
checks that the required modules are importable
and raises a ``RuntimeError`` with a copy-paste
install command if any are missing.
No more red ``externally-managed-environment`` in the
committed notebook output.
* P2 — README's "live evidence" paragraph reworded so it
describes the *shape* of the evidence rather than the
exact numbers from one specific run. The committed
executed notebook is the authoritative source for the
per-cell values; the README now says
``after=N>0``, ``rows_materialized total>0``,
``count is non-zero``, etc.
* PR body refresh happens via ``gh pr edit`` after this
commit lands.
Re-run captured (scratch ``migration_v5_demo_56f9179f``,
3 sessions). Install cell now prints
``"Local kernel — all required packages are importable."``
Per-beat live evidence (committed in the executed
notebook):
* Beat 1: count ``before=0, after=2``, ``rows_materialized
total=38``, ``property_graph_status='skipped:user_requested'``.
* Beat 2: drift caught (exit 1) + restored (exit 0) +
combined build clean (``rows_materialized total=19``).
* Beat 3: 3.3-3.5 + 3.7 print the ``"requires MAKO
reference extractor ... lands in follow-up PR #156"``
skip marker. 3.6 fires NODE + FIELD + EDGE.
* Beat 4: resolver returns ``compile_id=ba9c1ff85870``;
``GRAPH_TABLE count = 3``. Hub-shape returns zero
(expected; AgentSession synthesis is PR #156).
* fix(examples/migration_v5): restore default-dataset snapshots after rerun
The previous commit captured the executed notebook which had
just regenerated the snapshots against the scratch dataset
(`migration_v5_demo_56f9179f`). Run `mako_artifacts.py`
with default args to restore the canonical
`(test-project-0728-467323, migration_v5_demo)` shape.
This is the same trap from the previous round; the checked-in
artifacts should always be the stable default, never live-run
residue. The notebook regenerates against the scratch at
runtime; on disk, the committed copy is the canonical
reviewer-readable target.
* docs(examples): soften Beat 4.4 framing to match what cell prints
The Beat 4.4 markdown said the cell emits a sample showing
each DecisionExecution connects an AgentSession,
ContextSnapshot, DecisionPoint, and SelectionOutcome. The
committed output only proves a nonzero DecisionExecution
count and then reports the ``partOfSession`` hub traversal
returns zero rows (AgentSession synthesis from the plugin
envelope lands with PR #156).
Reworded the framing to match: the count traversal alone
proves the resolver → graph wiring works end-to-end; the
hub-shape sample is explicitly deferred to PR #156.
* docs(examples): soften hub-shape inline comment to match deferred state
The markdown framing for cell 4.4 was already softened in
the previous commit, but the inline code comment above the
hub-shape query still said Confirms the user-authored
graph object answers a real path query. The committed
output intentionally shows zero hub rows because the
AgentSession synthesis from the plugin envelope is a PR
#156 follow-up. Updated the comment to match.
1 parent 3407966 commit 80f6c6f
13 files changed
Lines changed: 5087 additions & 808 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
0 commit comments