From c7f2f02de4a276bb6b0f2d6d5ae196415c9a88ec Mon Sep 17 00:00:00 2001 From: Sathish Krishnan <10681383+SatyKrish@users.noreply.github.com> Date: Sun, 26 Apr 2026 16:06:57 -0400 Subject: [PATCH 1/7] Move Agent Bricks runtime to agent package Promote Agent Bricks creation from bootstrap helper into versioned agent code, remove OBO fallback configuration, and update deployment/docs around the target-state flow. Validated with pytest, py_compile, bash -n, git diff --check, and Databricks bundle validate for demo. --- .github/workflows/deploy.yml | 4 +- CLAUDE.md | 15 +- PRODUCTION_READINESS.md | 5 +- README.md | 63 +++++++-- SECURITY.md | 19 +-- VALIDATION.md | 39 +++++- .../agent_bricks.py | 129 ++++++++++-------- agent/tools.py | 4 +- app/README.md | 13 +- app/agent_bricks_client.py | 2 +- app/app.py | 3 - databricks.yml | 2 +- docs/design.md | 38 ++++-- docs/runbook.md | 63 ++++++--- resources/consumers/analyst.app.yml | 11 +- scripts/bootstrap-demo.sh | 15 +- 16 files changed, 263 insertions(+), 162 deletions(-) rename scripts/bootstrap_agent_bricks.py => agent/agent_bricks.py (86%) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 5ca9ee2..d658e52 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -104,7 +104,7 @@ while True: databricks bundle run -t demo --var "warehouse_id=$DOCINTEL_WAREHOUSE_ID" doc_intel_pipeline python scripts/wait_for_kpis.py --min-rows 3 --timeout 900 databricks bundle run -t demo --var "warehouse_id=$DOCINTEL_WAREHOUSE_ID" --var "agent_endpoint_name=$AGENT_ENDPOINT_NAME" index_refresh - python scripts/bootstrap_agent_bricks.py \ + python -m agent.agent_bricks \ --target demo \ --catalog "$DOCINTEL_CATALOG" \ --schema "$DOCINTEL_SCHEMA" \ @@ -132,7 +132,7 @@ while True: - name: Verify OBO scopes survived deploy # `bundle run` may wipe user_api_scopes (documented destructive-update - # behavior). Fail loudly because user-token passthrough is mandatory. + # behavior). Fail loudly if required user scopes are missing. run: | databricks apps get doc-intel-analyst-demo --output json > /tmp/app.json python -c " diff --git a/CLAUDE.md b/CLAUDE.md index 66acf46..adb298c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -16,15 +16,15 @@ For an end-to-end overview written for humans, read [`README.md`](./README.md). The bundle has three chicken-egg dependencies that prevent a single `databricks bundle deploy -t demo` from succeeding on a fresh workspace: -1. **Databricks App resource binding** references the Agent Bricks Supervisor endpoint that `scripts/bootstrap_agent_bricks.py` creates after the Vector Search index exists. +1. **Databricks App config** needs the generated Agent Bricks Supervisor endpoint name from `agent/agent_bricks.py`, which can only run after the Vector Search index exists. 2. **Lakehouse Monitor** (`resources/consumers/kpi_drift.yml`) attaches to `gold_filing_kpis`, which doesn't exist until the pipeline runs once. 3. **Lakebase database_catalog + Databricks App** race the `database_instance` provisioning. -**Canonical fix**: Run `./scripts/bootstrap-demo.sh` for fresh stand-ups; plain `databricks bundle deploy -t demo` for steady-state. The script does a **staged deploy** — `resources/` is split into `foundation/` (no data deps) and `consumers/` (need data). Stage 1 temporarily renames consumer YAMLs to `*.yml.skip` so the bundle glob skips them; stage 2 produces data and then runs full `bundle deploy`. Both deploys should succeed cleanly. +**Canonical fix**: Run `./scripts/bootstrap-demo.sh` for fresh stand-ups. For steady-state manual deploys, resolve the generated Supervisor endpoint and pass it as a bundle variable: `databricks bundle deploy -t demo --var "agent_endpoint_name=$(./scripts/resolve-agent-endpoint.sh demo)"`. The script does a **staged deploy** — `resources/` is split into `foundation/` (no data deps) and `consumers/` (need data). Stage 1 temporarily renames consumer YAMLs to `*.yml.skip` so the bundle glob skips them; stage 2 produces data and then runs full `bundle deploy`. Both deploys should succeed cleanly. **Do NOT try to "fix" these by:** - Adding `depends_on` between heterogeneous DAB resource types — DAB doesn't reliably honor it across instance↔catalog↔app. -- Reintroducing a custom MLflow pyfunc serving endpoint. Agent Bricks Knowledge Assistant + Supervisor Agent is the production path. +- Bypassing Agent Bricks Knowledge Assistant + Supervisor Agent for the production path. - Splitting monitors into a separate target overlay — adds complexity for a one-time concern. Full breakdown lives in [`docs/runbook.md`](./docs/runbook.md) §"Known deploy ordering gaps". @@ -33,13 +33,13 @@ Full breakdown lives in [`docs/runbook.md`](./docs/runbook.md) §"Known deploy o ``` pipelines/sql/ Lakeflow SDP — Bronze → Silver → Gold (SQL only, principle III) -agent/ Deterministic Agent Bricks tool glue only +agent/ Agent Bricks definition + deterministic tool glue app/ Streamlit on Databricks Apps + Lakebase psycopg client evals/ MLflow CLEARS gate (clears_eval.py + dataset.jsonl) jobs/ Lakeflow Jobs Python tasks (retention, index_refresh) resources/foundation/ DAB resources with no data deps: catalog/schema/volume, pipeline, retention job, Lakebase instance resources/consumers/ DAB resources that depend on foundation data: monitor, index-refresh job, app, dashboard, Lakebase catalog -scripts/ Operational scripts (bootstrap-demo.sh, bootstrap_agent_bricks.py, wait_for_kpis.py) +scripts/ Operational scripts (bootstrap-demo.sh, wait_for_kpis.py) samples/ Synthetic 10-K PDFs (regenerable via synthesize.py) specs/001-… Spec-Kit artifacts (spec, plan, tasks, research, data-model, contracts, quickstart) docs/runbook.md Day-2 ops + bring-up workflow @@ -51,6 +51,7 @@ docs/runbook.md Day-2 ops + bring-up workflow - Validate: `databricks bundle validate -t demo` - Fresh stand-up: `./scripts/bootstrap-demo.sh` (requires `DOCINTEL_CATALOG`, `DOCINTEL_SCHEMA`, `DOCINTEL_WAREHOUSE_ID`) - Steady-state deploy: `databricks bundle deploy -t demo --var "agent_endpoint_name=$(./scripts/resolve-agent-endpoint.sh demo)"` +- App config/restart: `databricks bundle run -t demo --var "agent_endpoint_name=$(./scripts/resolve-agent-endpoint.sh demo)" analyst_app` - Run pipeline: `databricks bundle run -t demo doc_intel_pipeline` - Run eval: `python evals/clears_eval.py --endpoint "$(./scripts/resolve-agent-endpoint.sh demo)" --dataset evals/dataset.jsonl` @@ -69,7 +70,9 @@ These were discovered the painful way during the 2026-04-25 bring-up. Future ses - **Section normalization**: `pipelines/sql/03_gold_classify_extract.sql` POSEXPLODES `parsed:sections[*]` and represents sectionless VARIANT output as one `full_document` row so we never lose a filing. - **`lakebase_stopped: true` is rejected on instance creation**: the API doesn't allow creating a database_instance directly into stopped state. Default is `false`; flip to `true` only after the instance exists. Reference: `databricks.yml` variable description. - **macOS doesn't ship `python`**: scripts must prefer `.venv/bin/python` then fall back to `python3`. Reference: `scripts/bootstrap-demo.sh`. -- **Agent Bricks resources are SDK-managed**: `scripts/bootstrap_agent_bricks.py` creates/updates the Knowledge Assistant, its Vector Search knowledge source, the UC KPI function, and the Supervisor Agent. DAB still manages the surrounding data/app/monitor resources. +- **Agent Bricks resources are SDK-managed**: `agent/agent_bricks.py` creates/updates the Knowledge Assistant, its Vector Search knowledge source, the UC KPI function, and the Supervisor Agent. DAB still manages the surrounding data/app/monitor resources. +- **Agent Bricks generates endpoint names**: use `scripts/resolve-agent-endpoint.sh ` and pass the result as `--var agent_endpoint_name=...` for deploys and app runs. +- **Agent Bricks invocation uses the invocations path directly**: `app/agent_bricks_client.py` posts to `/serving-endpoints/{endpoint}/invocations` with the user's OBO token and an `X-Request-ID`. Do not swap this back to `WorkspaceClient.serving_endpoints.query()` without revalidating the Agent Bricks response shape. - **Streamlit on Databricks Apps requires CORS+XSRF off via env vars**: not flags. `STREAMLIT_SERVER_ENABLE_CORS=false` and `STREAMLIT_SERVER_ENABLE_XSRF_PROTECTION=false` in `app/app.yaml`. Databricks Apps runtime config: https://docs.databricks.com/aws/en/dev-tools/databricks-apps/app-runtime. - **`bundle deploy` doesn't apply app config / restart**: must follow with `databricks bundle run -t analyst_app` (or use `databricks apps deploy`). Databricks Apps deploy docs: https://docs.databricks.com/aws/en/dev-tools/databricks-apps/deploy. - **`bundle run` may wipe `user_api_scopes`**: documented as a destructive-update behavior in the Databricks Apps deploy docs. Bootstrap step 5c re-asserts; CI verifies. If you change the App resource, double-check OBO scopes after. diff --git a/PRODUCTION_READINESS.md b/PRODUCTION_READINESS.md index dd0a594..293432e 100644 --- a/PRODUCTION_READINESS.md +++ b/PRODUCTION_READINESS.md @@ -10,6 +10,8 @@ This project is open-sourced as a Databricks reference implementation. Treat it | Pilot-ready | Real filings exercise document variability and cost/latency | Reference-ready plus a reviewed EDGAR pilot corpus | | Production-ready | Analysts can use it under governed identity and SLOs | Pilot-ready plus end-to-end OBO, dashboards, alerts, rollback, and runbook evidence | +Current demo status as of 2026-04-26: Agent Bricks bootstrap and direct Supervisor endpoint smoke passed, but the project is not reference-ready yet. The target workspace did not have Databricks Apps user-token passthrough enabled, and the latest synthetic CLEARS run failed the configured quality/latency gate. See [`VALIDATION.md`](./VALIDATION.md#latest-demo-snapshot). + ## Reference-Ready Checklist - `databricks bundle validate --strict -t demo` passes. @@ -17,7 +19,8 @@ This project is open-sourced as a Databricks reference implementation. Treat it - Synthetic PDFs in `samples/` produce at least ACME/BETA/GAMMA KPI rows. - Vector Search index sync completes and the Agent Bricks Supervisor endpoint answers a smoke question with citations. - `python evals/clears_eval.py --endpoint "$(./scripts/resolve-agent-endpoint.sh demo)" --dataset evals/dataset.jsonl` passes. -- App starts via `databricks bundle run -t demo analyst_app`. +- Databricks Apps user-token passthrough is enabled in the workspace. +- App starts via `databricks bundle run -t demo --var "agent_endpoint_name=$(./scripts/resolve-agent-endpoint.sh demo)" analyst_app`. ## Pilot-Ready Checklist diff --git a/README.md b/README.md index 93fe5d2..bd3b707 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ A **Databricks-native document intelligence + agent** stack: parse PDFs once wit [2], regulation [3]…" ``` -For motivation, architecture diagrams, the Spec-Kit + Claude Code build workflow, and the chicken-egg deploy-ordering story, see [**`docs/design.md`**](./docs/design.md). For day-2 ops, see [**`docs/runbook.md`**](./docs/runbook.md). +For architecture and deploy ordering, see [**`docs/design.md`**](./docs/design.md). For operations, validation, and troubleshooting, see [**`docs/runbook.md`**](./docs/runbook.md). --- @@ -37,6 +37,7 @@ For motivation, architecture diagrams, the Spec-Kit + Claude Code build workflow - [Features](#features) - [Readiness levels](#readiness-levels) +- [How Agent Bricks is used](#how-agent-bricks-is-used) - [Prerequisites](#prerequisites) - [Getting started](#getting-started) - [CLEARS quality gate](#clears-quality-gate) @@ -55,7 +56,7 @@ For motivation, architecture diagrams, the Spec-Kit + Claude Code build workflow ## Features - **End-to-end document intelligence pipeline** — Auto Loader ingest → `ai_parse_document` → section explosion → `ai_classify` + `ai_extract` → 5-dim quality rubric → Vector Search Delta-Sync index (the endpoint is DAB-managed; the index is created/synced by `jobs/index_refresh/sync_index.py`). SQL-only pipeline (Lakeflow Spark Declarative Pipelines). -- **Cited-answer agent** — Agent Bricks-first runtime: Knowledge Assistant for cited document Q&A, Supervisor Agent for cross-company orchestration, and a deterministic KPI tool for structured comparisons. No custom pyfunc, retrieval loop, or supervisor runtime is retained. +- **Cited-answer agent** — Agent Bricks Knowledge Assistant for cited document Q&A, Supervisor Agent for cross-company orchestration, and a deterministic KPI tool for structured comparisons. - **Streamlit chat UI on Databricks Apps** — citation chips, thumbs feedback, conversation history persisted to Lakebase Postgres. - **Eval-gated promotion** — `mlflow.evaluate(model_type="databricks-agent")` against a 30-question set with thresholds for Correctness, Adherence, Relevance, Execution, Safety, Latency p95. - **Reproducible synthetic corpus** — `samples/synthesize.py` generates ACME / BETA / GAMMA 10-Ks plus a deliberately-low-quality `garbage_10K_2024.pdf` for the rubric-exclusion test (SC-006). No EDGAR dependency in CI. @@ -72,6 +73,31 @@ For motivation, architecture diagrams, the Spec-Kit + Claude Code build workflow Full checklists in [`PRODUCTION_READINESS.md`](./PRODUCTION_READINESS.md). +> Latest demo status, 2026-04-26: Agent Bricks bootstrap and direct Supervisor endpoint smoke passed. Reference-ready remains blocked by Databricks Apps user-token passthrough and CLEARS thresholds. See [`VALIDATION.md`](./VALIDATION.md). + +--- + +## How Agent Bricks is used + +Databricks creation path: [Create an AI agent](https://docs.databricks.com/aws/en/generative-ai/agent-framework/create-agent) → Knowledge Assistant for document Q&A, with Supervisor Agent coordinating hosted tools. + +The production agent path is: + +1. `jobs/index_refresh/sync_index.py` creates/syncs the Mosaic AI Vector Search Delta-Sync index over `gold_filing_sections_indexable`. +2. `agent/agent_bricks.py` creates or updates the Agent Bricks Knowledge Assistant with that Vector Search index as its knowledge source. The source uses `summary` as the searchable text column and `filename` as the document URI column. +3. The same bootstrap creates or updates the UC SQL function `lookup_10k_kpis`. +4. The bootstrap creates or updates the Agent Bricks Supervisor Agent with two tools: the Knowledge Assistant for cited document Q&A and the UC function for deterministic KPI lookups. +5. Agent Bricks generates concrete serving endpoint names. Resolve the live Supervisor endpoint with `./scripts/resolve-agent-endpoint.sh `. +6. The Databricks App receives the resolved endpoint through the `agent_endpoint_name` bundle variable as `DOCINTEL_AGENT_ENDPOINT`. +7. The app invokes `POST /serving-endpoints/{endpoint}/invocations` directly with the user's OBO token. `WorkspaceClient.serving_endpoints.query()` is not used for Agent Bricks invocation because validation showed it did not preserve the needed Agent Bricks response shape. +8. Knowledge Assistant citations currently arrive as markdown footnotes in Agent Bricks output messages. `app/agent_bricks_response.py` normalizes the final answer and extracts citation chips from those footnotes. + +Useful Databricks references: + +- [Create an AI agent](https://docs.databricks.com/aws/en/generative-ai/agent-framework/create-agent) +- [Knowledge Assistant](https://docs.databricks.com/aws/en/generative-ai/agent-bricks/knowledge-assistant) +- [Supervisor Agent](https://docs.databricks.com/aws/en/generative-ai/agent-bricks/multi-agent-supervisor) + --- ## Prerequisites @@ -101,7 +127,7 @@ You need a workspace with **all** of the following enabled: - Serverless SQL warehouse (AI Functions GA — `ai_parse_document`, `ai_classify`, `ai_extract`, `ai_query`) - Mosaic AI Vector Search (endpoint + Delta-Sync index) -- Agent Bricks (Knowledge Assistant, Supervisor Agent, Custom Agents on Apps) +- Agent Bricks Knowledge Assistant and Supervisor Agent - AI Gateway with OBO / identity enforcement - Lakebase Postgres (preview / GA depending on region) - Databricks Apps (Streamlit runtime) @@ -194,18 +220,24 @@ After the first bring-up, iteration depends on what changed: ```bash # YAML / pipeline / job / app config changes -databricks bundle deploy -t demo -databricks bundle run -t demo analyst_app # apply app config + restart +AGENT_ENDPOINT_NAME="$(./scripts/resolve-agent-endpoint.sh demo)" +databricks bundle deploy -t demo --var "agent_endpoint_name=${AGENT_ENDPOINT_NAME}" +databricks bundle run -t demo --var "agent_endpoint_name=${AGENT_ENDPOINT_NAME}" analyst_app # Agent Bricks configuration / tool glue changes -databricks bundle deploy -t demo -databricks bundle run -t demo analyst_app +DOCINTEL_CATALOG=workspace \ +DOCINTEL_SCHEMA=docintel_10k_demo \ +DOCINTEL_WAREHOUSE_ID= \ +python -m agent.agent_bricks --target demo +AGENT_ENDPOINT_NAME="$(./scripts/resolve-agent-endpoint.sh demo)" +databricks bundle deploy -t demo --var "agent_endpoint_name=${AGENT_ENDPOINT_NAME}" +databricks bundle run -t demo --var "agent_endpoint_name=${AGENT_ENDPOINT_NAME}" analyst_app # Pipeline SQL changes that need to re-process existing filings databricks bundle run -t demo doc_intel_pipeline ``` -You can also re-run `./scripts/bootstrap-demo.sh` — it auto-detects steady-state and does the full cycle (deploy → refresh data → register/promote → app run → grants → smoke) in one command. +You can also re-run `./scripts/bootstrap-demo.sh` — it auto-detects steady-state and does the full cycle (deploy → refresh data → update Agent Bricks → app run → grants → smoke) in one command. For a guided 30-minute tour, see [`specs/001-doc-intel-10k/quickstart.md`](./specs/001-doc-intel-10k/quickstart.md). @@ -235,7 +267,7 @@ Before any deploy reaches production, an evaluation must pass (constitution prin The bar is hard-coded; changing it requires editing `.specify/memory/constitution.md`, which is its own small ceremony (PR + version bump + Sync Impact Report). -Implementation uses `mlflow.evaluate(model_type="databricks-agent")` for the four LLM-judged axes; Execution + Latency are computed from the raw response stream. Per-row Correctness is sliced from `result.tables['eval_results']` for the SC-002/SC-003 P2 vs P3 thresholds. +Implementation uses `mlflow.evaluate(model_type="databricks-agent")` for the LLM-judged axes; Execution and Latency are computed from the raw response stream. When the active MLflow/databricks-agents version exposes per-row correctness in `result.tables['eval_results']`, the runner also logs SC-002/SC-003 P2 vs P3 slices. Current 1.x aggregate outputs may omit those slice columns, so the aggregate CLEARS gate remains the required pass/fail signal. --- @@ -255,6 +287,7 @@ Implementation uses `mlflow.evaluate(model_type="databricks-agent")` for the fou | `quality_threshold` | `22` | Section quality cutoff (0-30) for index inclusion | | `max_pdf_bytes` | `52428800` (50 MB) | Reject filings larger than this | | `analyst_group` | `account users` | UC group granted SELECT/USE on schema, READ/WRITE on volume | +| `agent_endpoint_name` | `UNSET_AGENT_BRICKS_ENDPOINT` | Generated Agent Bricks Supervisor endpoint resolved by `scripts/resolve-agent-endpoint.sh`; pass it on deploy/app-run commands after bootstrap | Override via `--var name=value` on any `bundle` command. @@ -288,9 +321,9 @@ bash -n scripts/bootstrap-demo.sh # Compile checks for all modified Python .venv/bin/python -m py_compile \ - agent/tools.py \ - app/app.py app/lakebase_client.py \ - evals/clears_eval.py scripts/bootstrap_agent_bricks.py \ + agent/agent_bricks.py agent/tools.py \ + app/app.py app/agent_bricks_client.py app/agent_bricks_response.py app/lakebase_client.py \ + evals/clears_eval.py \ scripts/wait_for_kpis.py samples/synthesize.py ``` @@ -303,9 +336,9 @@ End-to-end is exercised by [`./scripts/bootstrap-demo.sh`](./scripts/bootstrap-d | Path | When | |---|---| | `./scripts/bootstrap-demo.sh` | Fresh-workspace bring-up (or after `bundle destroy`). Auto-detects FIRST-DEPLOY vs STEADY-STATE; handles staged deploy + data production + UC grants in either mode. | -| `databricks bundle deploy -t demo` | YAML / pipeline / job / app config changes after the first bring-up. | -| `databricks bundle run -t demo analyst_app` | After any change to `app/` or `resources/consumers/analyst.app.yml` — required to apply runtime config + restart the app. | -| `databricks bundle deploy -t prod --var service_principal_id=` | Production deploy, run as the prod SP. | +| `databricks bundle deploy -t demo --var "agent_endpoint_name=$(./scripts/resolve-agent-endpoint.sh demo)"` | YAML / pipeline / job / app config changes after the first bring-up. | +| `databricks bundle run -t demo --var "agent_endpoint_name=$(./scripts/resolve-agent-endpoint.sh demo)" analyst_app` | After any change to `app/` or `resources/consumers/analyst.app.yml` — required to apply runtime config + restart the app. | +| `databricks bundle deploy -t prod --var service_principal_id= --var "agent_endpoint_name=$(./scripts/resolve-agent-endpoint.sh prod)"` | Production deploy, run as the prod SP after prod Agent Bricks bootstrap. | | GitHub Actions on push to `main` | Steady-state CI: full `bundle deploy` → wait for Lakebase AVAILABLE → upload samples + run pipeline → Agent Bricks / AI Gateway validation → UC grants → `bundle run analyst_app` → CLEARS eval gate. (The first-ever bring-up of a workspace must be done locally with `./scripts/bootstrap-demo.sh`.) | For day-2 ops (Agent Bricks configuration validation, debugging low quality scores, inspecting CLEARS metrics in MLflow), see [`docs/runbook.md`](./docs/runbook.md). For the production-readiness checklist, see [`PRODUCTION_READINESS.md`](./PRODUCTION_READINESS.md). diff --git a/SECURITY.md b/SECURITY.md index 476dd34..96b4c3d 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,25 +2,16 @@ ## Supported Security Posture -This reference is designed for Databricks workspaces using Unity Catalog, Agent Bricks, AI Gateway, Databricks Apps resource bindings, and mandatory end-to-end on-behalf-of (OBO) user identity. - -## Identity Modes - -| Mode | Use | Production row-level security | -|---|---|---| -| End-to-end OBO | Demo and production analyst use | Yes, after audit verification | - -Service-principal fallback is not supported for the agent path. If Databricks Apps user-token passthrough, Agent Bricks OBO, or AI Gateway identity enforcement is unavailable, deployment must fail with an actionable prerequisite error. +This reference is designed for Databricks workspaces using Unity Catalog, Agent Bricks, AI Gateway, Databricks Apps resource bindings, and end-to-end on-behalf-of (OBO) user identity. The App requires the request's `x-forwarded-access-token`; missing tokens fail loudly. ## Enabling End-To-End OBO 1. Workspace admin enables Databricks Apps user-token passthrough. -2. Declare the required `user_api_scopes` in `resources/consumers/analyst.app.yml`. -3. Redeploy and run the app resource. -4. Verify `serving.serving-endpoints` and `sql` scopes are present after deployment. -5. Verify audit logs show downstream calls under the invoking user where required. +2. Redeploy and run the app resource. The deploy fails if the workspace cannot grant the declared `user_api_scopes`. +3. Verify `serving.serving-endpoints` and `sql` scopes are present after deployment. +4. Verify audit logs show downstream calls under the invoking user where required. -Agent Bricks / AI Gateway must enforce downstream access to document Q&A, SQL tools, models, and any external tools under the invoking user's identity. The previous custom MLflow auth-policy path has been removed from the production implementation. +Agent Bricks / AI Gateway enforce downstream access to document Q&A, SQL tools, models, and any external tools under the invoking user's identity. ## Secrets And Credentials diff --git a/VALIDATION.md b/VALIDATION.md index 97aa9b1..4cf8c5b 100644 --- a/VALIDATION.md +++ b/VALIDATION.md @@ -7,8 +7,8 @@ Use this guide to prove the reference implementation works in a Databricks works ```bash python3 -m py_compile \ agent/tools.py \ - app/app.py app/lakebase_client.py \ - evals/clears_eval.py scripts/bootstrap_agent_bricks.py \ + app/app.py app/agent_bricks_client.py app/agent_bricks_response.py app/lakebase_client.py \ + evals/clears_eval.py agent/agent_bricks.py \ scripts/wait_for_kpis.py samples/synthesize.py bash -n scripts/bootstrap-demo.sh @@ -41,10 +41,12 @@ Expected outcomes: - Pipeline creates Gold rows. - Agent Bricks Knowledge Assistant and Supervisor Agent are created or updated. - Consumer resources deploy cleanly. -- App config is applied with `bundle run analyst_app`. +- App config is applied with `bundle run analyst_app`, including `DOCINTEL_AGENT_ENDPOINT` set from the generated Supervisor endpoint name. - Bootstrap verifies mandatory OBO scopes. - Smoke query reaches the Agent Bricks supervisor endpoint. +If `bundle run analyst_app` fails with `Databricks Apps - user token passthrough feature is not enabled`, the Agent Bricks path is still deployable but the app is not production-valid in that workspace. Enable the workspace/org feature and rerun bootstrap. Do not bypass OBO. + ## Data Checks ```sql @@ -73,7 +75,7 @@ python evals/clears_eval.py \ Expected: - Correctness, adherence, relevance, execution, safety, and latency thresholds pass. -- P2 and P3 correctness slices are logged. +- P2 and P3 correctness slices are logged when the active MLflow/databricks-agents metric output includes per-row correctness columns. Current 1.x aggregate outputs may not expose those slice columns; treat missing slices as validation evidence to record, not as a reason to bypass the aggregate gate. - No citations reference `garbage_10K_2024.pdf`. ## App Checks @@ -86,7 +88,34 @@ Expected: ## OBO Verification - Confirm `resources/consumers/analyst.app.yml:user_api_scopes` is present. -- Run `databricks bundle deploy -t demo && databricks bundle run -t demo analyst_app`. +- Run: + ```bash + AGENT_ENDPOINT_NAME="$(./scripts/resolve-agent-endpoint.sh demo)" + databricks bundle deploy -t demo --var "agent_endpoint_name=${AGENT_ENDPOINT_NAME}" + databricks bundle run -t demo --var "agent_endpoint_name=${AGENT_ENDPOINT_NAME}" analyst_app + ``` - Confirm bootstrap or CI verifies `serving.serving-endpoints` and `sql` scopes. - Check audit logs for user-scoped downstream access through Agent Bricks, Knowledge Assistant, and the structured KPI SQL function. - If the workspace cannot grant user-token passthrough, deployment is invalid and must fail. + +## Latest Demo Snapshot + +As of 2026-04-26, the demo workspace evidence is: + +- Bundle validation passed with the resolved Agent Bricks Supervisor endpoint. +- Agent Bricks bootstrap succeeded: + - Knowledge Assistant display name: `doc-intel-knowledge-demo` + - Supervisor display name: `doc-intel-supervisor-demo` + - UC function: `workspace.docintel_10k_demo.lookup_10k_kpis` +- Direct Supervisor endpoint smoke passed. The ACME FY2024 revenue question returned `$94.2 billion` with a parsed `ACME_10K_2024.pdf` citation. +- Databricks App deploy was blocked by workspace configuration: Databricks Apps user-token passthrough was not enabled. +- CLEARS live eval completed but failed the configured gate: + - MLflow run ID: `772e902cab92459f9bf569296fc5f801` + - correctness: `0.323` + - adherence: `0.000` + - relevance/groundedness: `0.516` + - safety: `1.000` + - execution: `1.000` + - latency p95: `31711ms` + +Status: Agent Bricks deployment mechanics and direct serving smoke passed. Reference-ready quality and app-level OBO readiness remain open. diff --git a/scripts/bootstrap_agent_bricks.py b/agent/agent_bricks.py similarity index 86% rename from scripts/bootstrap_agent_bricks.py rename to agent/agent_bricks.py index 565a0d5..a57dfe8 100644 --- a/scripts/bootstrap_agent_bricks.py +++ b/agent/agent_bricks.py @@ -1,15 +1,4 @@ -"""Create or update the Agent Bricks runtime for the document intelligence app. - -This is the production agent bootstrap path. It configures: - -* Agent Bricks Knowledge Assistant over the governed Vector Search source. -* A deterministic Unity Catalog SQL function for structured KPI lookups. -* Agent Bricks Supervisor Agent that coordinates the Knowledge Assistant and - the KPI function. - -The earlier hand-built MLflow pyfunc agent runtime is intentionally not part of -this path. -""" +"""Agent Bricks definition and SDK application logic.""" from __future__ import annotations @@ -18,7 +7,7 @@ import os import sys import time -from dataclasses import asdict +from dataclasses import asdict, dataclass from typing import Iterable, TypeVar from databricks.sdk import WorkspaceClient @@ -38,6 +27,24 @@ T = TypeVar("T") +@dataclass +class AgentBricksRuntime: + knowledge_assistant: KnowledgeAssistant + supervisor_agent: SupervisorAgent + kpi_function: str + supervisor_endpoint: str + knowledge_endpoint: str + + def as_dict(self) -> dict: + return { + "knowledge_assistant": _as_dict(self.knowledge_assistant), + "supervisor_agent": _as_dict(self.supervisor_agent), + "kpi_function": self.kpi_function, + "supervisor_endpoint": self.supervisor_endpoint, + "knowledge_endpoint": self.knowledge_endpoint, + } + + def _find_by_display_name(items: Iterable[T], display_name: str) -> T | None: for item in items: if getattr(item, "display_name", None) == display_name: @@ -162,7 +169,6 @@ def _ensure_knowledge_assistant( w: WorkspaceClient, *, display_name: str, - endpoint_name: str, index_name: str, ) -> KnowledgeAssistant: description = ( @@ -179,7 +185,6 @@ def _ensure_knowledge_assistant( existing = _find_by_display_name(w.knowledge_assistants.list_knowledge_assistants(), display_name) desired = KnowledgeAssistant( display_name=display_name, - endpoint_name=endpoint_name, description=description, instructions=instructions, ) @@ -239,7 +244,6 @@ def _ensure_supervisor( w: WorkspaceClient, *, display_name: str, - endpoint_name: str, knowledge_assistant: KnowledgeAssistant, kpi_function_name: str, ) -> SupervisorAgent: @@ -255,7 +259,6 @@ def _ensure_supervisor( ) desired = SupervisorAgent( display_name=display_name, - endpoint_name=endpoint_name, description=description, instructions=instructions, ) @@ -277,7 +280,6 @@ def _ensure_supervisor( ), knowledge_assistant=SupervisorKnowledgeAssistant( knowledge_assistant_id=knowledge_assistant.id or _id_from_name(knowledge_assistant.name), - serving_endpoint_name=knowledge_assistant.endpoint_name, ), ) kpi_tool = Tool( @@ -368,62 +370,77 @@ def _grant_endpoint_query(w: WorkspaceClient, endpoint_name: str, group_name: st ) -def main() -> int: - parser = argparse.ArgumentParser() - parser.add_argument("--target", default=os.environ.get("DOCINTEL_TARGET", "demo")) - parser.add_argument("--catalog", default=os.environ.get("DOCINTEL_CATALOG")) - parser.add_argument("--schema", default=os.environ.get("DOCINTEL_SCHEMA")) - parser.add_argument("--warehouse-id", default=os.environ.get("DOCINTEL_WAREHOUSE_ID")) - parser.add_argument("--analyst-group", default=os.environ.get("DOCINTEL_ANALYST_GROUP", "account users")) - parser.add_argument("--requested-supervisor-endpoint") - parser.add_argument("--requested-knowledge-endpoint") - parser.add_argument("--supervisor-endpoint", dest="requested_supervisor_endpoint", help=argparse.SUPPRESS) - parser.add_argument("--knowledge-endpoint", dest="requested_knowledge_endpoint", help=argparse.SUPPRESS) - args = parser.parse_args() - - if not args.catalog or not args.schema or not args.warehouse_id: - parser.error("--catalog, --schema, and --warehouse-id are required") - - target = args.target - requested_supervisor_endpoint = args.requested_supervisor_endpoint or f"analyst-agent-{target}" - requested_knowledge_endpoint = args.requested_knowledge_endpoint or f"doc-intel-knowledge-{target}" - index_name = f"{args.catalog}.{args.schema}.filings_summary_idx" +def deploy_agent_bricks_runtime( + w: WorkspaceClient, + *, + target: str, + catalog: str, + schema: str, + warehouse_id: str, + analyst_group: str, +) -> AgentBricksRuntime: + index_name = f"{catalog}.{schema}.filings_summary_idx" - w = WorkspaceClient() kpi_function_name = _create_or_update_kpi_function( w, - catalog=args.catalog, - schema=args.schema, - warehouse_id=args.warehouse_id, + catalog=catalog, + schema=schema, + warehouse_id=warehouse_id, ) knowledge_assistant = _ensure_knowledge_assistant( w, display_name=f"doc-intel-knowledge-{target}", - endpoint_name=requested_knowledge_endpoint, index_name=index_name, ) supervisor = _ensure_supervisor( w, display_name=f"doc-intel-supervisor-{target}", - endpoint_name=requested_supervisor_endpoint, knowledge_assistant=knowledge_assistant, kpi_function_name=kpi_function_name, ) - actual_supervisor_endpoint = supervisor.endpoint_name or requested_supervisor_endpoint - actual_knowledge_endpoint = knowledge_assistant.endpoint_name or requested_knowledge_endpoint + if not supervisor.endpoint_name: + raise RuntimeError(f"Supervisor Agent doc-intel-supervisor-{target} did not return an endpoint_name") + if not knowledge_assistant.endpoint_name: + raise RuntimeError(f"Knowledge Assistant doc-intel-knowledge-{target} did not return an endpoint_name") + + actual_supervisor_endpoint = supervisor.endpoint_name + actual_knowledge_endpoint = knowledge_assistant.endpoint_name - _grant_endpoint_query(w, actual_supervisor_endpoint, args.analyst_group) + _grant_endpoint_query(w, actual_supervisor_endpoint, analyst_group) if actual_knowledge_endpoint: - _grant_endpoint_query(w, actual_knowledge_endpoint, args.analyst_group) - - print(json.dumps({ - "knowledge_assistant": _as_dict(knowledge_assistant), - "supervisor_agent": _as_dict(supervisor), - "kpi_function": kpi_function_name, - "supervisor_endpoint": actual_supervisor_endpoint, - "knowledge_endpoint": actual_knowledge_endpoint, - }, indent=2, default=str)) + _grant_endpoint_query(w, actual_knowledge_endpoint, analyst_group) + + return AgentBricksRuntime( + knowledge_assistant=knowledge_assistant, + supervisor_agent=supervisor, + kpi_function=kpi_function_name, + supervisor_endpoint=actual_supervisor_endpoint, + knowledge_endpoint=actual_knowledge_endpoint, + ) + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--target", default=os.environ.get("DOCINTEL_TARGET", "demo")) + parser.add_argument("--catalog", default=os.environ.get("DOCINTEL_CATALOG")) + parser.add_argument("--schema", default=os.environ.get("DOCINTEL_SCHEMA")) + parser.add_argument("--warehouse-id", default=os.environ.get("DOCINTEL_WAREHOUSE_ID")) + parser.add_argument("--analyst-group", default=os.environ.get("DOCINTEL_ANALYST_GROUP", "account users")) + args = parser.parse_args() + + if not args.catalog or not args.schema or not args.warehouse_id: + parser.error("--catalog, --schema, and --warehouse-id are required") + + runtime = deploy_agent_bricks_runtime( + WorkspaceClient(), + target=args.target, + catalog=args.catalog, + schema=args.schema, + warehouse_id=args.warehouse_id, + analyst_group=args.analyst_group, + ) + print(json.dumps(runtime.as_dict(), indent=2, default=str)) return 0 diff --git a/agent/tools.py b/agent/tools.py index 5385dfa..4773d4c 100644 --- a/agent/tools.py +++ b/agent/tools.py @@ -1,9 +1,7 @@ """Deterministic KPI tool glue for Agent Bricks. The production tool is a Unity Catalog SQL function created by -`scripts/bootstrap_agent_bricks.py`. These helpers keep the SQL access pattern -testable and available for local validation without reintroducing a custom -agent runtime. +`agent.agent_bricks`. These helpers keep the SQL access pattern testable. """ from __future__ import annotations diff --git a/app/README.md b/app/README.md index 20e328c..ce344e3 100644 --- a/app/README.md +++ b/app/README.md @@ -14,8 +14,9 @@ Source for the Databricks App `doc-intel-analyst-${target}`. Streamlit chat UI o ## Running deployed (canonical) ```bash -databricks bundle deploy -t demo -databricks bundle run -t demo analyst_app +AGENT_ENDPOINT_NAME="$(./scripts/resolve-agent-endpoint.sh demo)" +databricks bundle deploy -t demo --var "agent_endpoint_name=${AGENT_ENDPOINT_NAME}" +databricks bundle run -t demo --var "agent_endpoint_name=${AGENT_ENDPOINT_NAME}" analyst_app # Open the App URL from the workspace UI ("Apps" → doc-intel-analyst-demo) ``` @@ -54,10 +55,10 @@ DROP TABLE IF EXISTS conversation_history CASCADE; ## OBO (on-behalf-of) flow -The app forwards each user's `x-forwarded-access-token` header to the Agent Bricks Supervisor endpoint via a `WorkspaceClient(token=...)` cache (`app.py:_user_client`). Agent Bricks, Knowledge Assistant, and the UC KPI function must run under the invoking user's identity, not broad App SP reads. +The app builds a `WorkspaceClient(token=...)` from each user's `x-forwarded-access-token` header (`app.py:_user_client`) and invokes the Agent Bricks Supervisor endpoint through `POST /serving-endpoints/{endpoint}/invocations` (`agent_bricks_client.py`). Agent Bricks, Knowledge Assistant, and the UC KPI function must run under the invoking user's identity. -`user_api_scopes` declared in `resources/consumers/analyst.app.yml` (`serving.serving-endpoints`, `sql`, `iam.access-control:read`, `iam.current-user:read`) are required for app-level OBO. Deployment is invalid if these scopes are not granted. +The endpoint name is generated by Agent Bricks, resolved with `scripts/resolve-agent-endpoint.sh`, and injected into the app as `DOCINTEL_AGENT_ENDPOINT` by `resources/consumers/analyst.app.yml`. -**Streamlit gotcha** (per the [Databricks Apps runtime docs](https://docs.databricks.com/aws/en/dev-tools/databricks-apps/app-runtime)): the OBO token is captured at the initial HTTP request; the connection then upgrades to WebSocket and the token never refreshes. If a user's UC permissions change mid-session, ask them to reload the page. +`user_api_scopes` is declared in `resources/consumers/analyst.app.yml` (`serving.serving-endpoints`, `sql`, `iam.access-control:read`, `iam.current-user:read`) and requires the workspace-level "Databricks Apps - user token passthrough" feature. Deployment is invalid if these scopes are not granted. -**Local-dev caveat**: `st.context.headers` won't have `x-forwarded-access-token` when running `streamlit run` outside the Databricks Apps reverse proxy. The app raises a prerequisite error instead of using service-principal reads for agent calls. +**Streamlit gotcha** (per the [Databricks Apps runtime docs](https://docs.databricks.com/aws/en/dev-tools/databricks-apps/app-runtime)): the OBO token is captured at the initial HTTP request; the connection then upgrades to WebSocket and the token never refreshes. If a user's UC permissions change mid-session, ask them to reload the page. diff --git a/app/agent_bricks_client.py b/app/agent_bricks_client.py index 3539779..944f37d 100644 --- a/app/agent_bricks_client.py +++ b/app/agent_bricks_client.py @@ -25,7 +25,7 @@ def invoke_agent_endpoint( url = f"{host}/serving-endpoints/{endpoint_name}/invocations" body = json.dumps({"input": [{"role": "user", "content": question}]}).encode("utf-8") # For an OBO WorkspaceClient built with Config(token=), - # authenticate() emits that user token. There is no App SP fallback here. + # authenticate() emits that user token. headers = { "Content-Type": "application/json", "X-Request-ID": client_request_id or str(uuid.uuid4()), diff --git a/app/app.py b/app/app.py index 6ab1f04..0137e03 100644 --- a/app/app.py +++ b/app/app.py @@ -32,9 +32,6 @@ def _user_client(token: str) -> WorkspaceClient: the initial HTTP request, then the connection switches to WebSocket — the token never refreshes. Long-lived sessions should reload the page after permission changes. - - Missing tokens are a deployment prerequisite failure. Production must run - through Databricks Apps user-token passthrough. """ return WorkspaceClient(config=Config( host=os.environ["DATABRICKS_HOST"], diff --git a/databricks.yml b/databricks.yml index 1ee6c38..66fa154 100644 --- a/databricks.yml +++ b/databricks.yml @@ -37,7 +37,7 @@ variables: description: UC group granted SELECT/USE on the catalog/schema default: account users agent_endpoint_name: - description: Agent Bricks Supervisor serving endpoint name resolved by bootstrap_agent_bricks.py + description: Agent Bricks Supervisor serving endpoint name resolved by agent.agent_bricks default: UNSET_AGENT_BRICKS_ENDPOINT targets: diff --git a/docs/design.md b/docs/design.md index 32b1220..93ebc4c 100644 --- a/docs/design.md +++ b/docs/design.md @@ -8,7 +8,7 @@ This document covers the *why*, the architecture, and the build workflow behind - [Architecture](#architecture) - [Two halves: an offline pipeline, and an online agent](#two-halves-an-offline-pipeline-and-an-online-agent) - [Vector Search bridges data and agent](#vector-search-bridges-data-and-agent) - - [Agent has two paths, one endpoint](#agent-has-two-paths-one-endpoint) + - [Agent Bricks target runtime](#agent-bricks-target-runtime) - [Runtime stack](#runtime-stack) - [How it's built — three pillars](#how-its-built--three-pillars) - [Pillar 1 — Spec-Kit](#pillar-1--spec-kit-spec-driven-development) @@ -23,9 +23,7 @@ This document covers the *why*, the architecture, and the build workflow behind Databricks shipped a lot of new generative-AI surface area in 2025–2026: Document Intelligence (`ai_parse_document`, `ai_classify`, `ai_extract`), Agent Bricks, AI Gateway, Lakebase, and Databricks Apps. The two source articles for this reference are Databricks' Document Intelligence launch article ("Why Your Agents Can't Read Enterprise Documents") and the Agent Bricks platform article. The reference exists to demonstrate those patterns end to end: parse messy enterprise PDFs into a governed document data layer, then build a governed agent on that enriched layer through Agent Bricks. -This repo is that worked example. Drop a PDF into a governed UC volume; ten minutes later, an analyst can ask cited questions in plain English with end-to-end audit. The desired target architecture is **Agent Bricks-first**: Document Intelligence prepares the governed source of truth; Knowledge Assistant handles cited document Q&A; Supervisor Agent coordinates document Q&A with structured KPI tools; AI Gateway, Unity Catalog, OBO, Lakebase, and CLEARS provide the governance and operating layer. - -The earlier custom `mlflow.pyfunc` agent path diverged from that target by re-introducing custom serving lifecycle, auth-policy ordering, retrieval, and supervisor code that Agent Bricks is meant to absorb. The production path now uses Agent Bricks bootstrap instead of that custom runtime. +This repo is that worked example. Drop a PDF into a governed UC volume; ten minutes later, an analyst can ask cited questions in plain English with end-to-end audit. Document Intelligence prepares the governed source of truth; Knowledge Assistant handles cited document Q&A; Supervisor Agent coordinates document Q&A with structured KPI tools; AI Gateway, Unity Catalog, OBO, Lakebase, and CLEARS provide the governance and operating layer. It also demonstrates a development workflow: **Spec-Kit** for spec-driven design, **Claude Code** with Databricks skill bundles for AI-assisted implementation, six **non-negotiable constitution principles** that gate every plan. See [How it's built](#how-its-built--three-pillars). @@ -93,7 +91,7 @@ It also demonstrates a development workflow: **Spec-Kit** for spec-driven design "Quality before retrieval." ``` -**Ownership note**: DAB manages the Vector Search **endpoint** (`resources/foundation/filings_index.yml`) and the index-refresh **job** (`resources/consumers/index_refresh.job.yml`). The **index** itself isn't yet a DAB-managed resource type as of CLI 0.298 — `jobs/index_refresh/sync_index.py` creates the Delta-Sync index on first run and triggers a sync on subsequent runs. The endpoint lives in foundation so first-deploy bootstrap can materialize the index before `scripts/bootstrap_agent_bricks.py` attaches it to Knowledge Assistant. +**Ownership note**: DAB manages the Vector Search **endpoint** (`resources/foundation/filings_index.yml`) and the index-refresh **job** (`resources/consumers/index_refresh.job.yml`). The **index** itself isn't yet a DAB-managed resource type as of CLI 0.298 — `jobs/index_refresh/sync_index.py` creates the Delta-Sync index on first run and triggers a sync on subsequent runs. The endpoint lives in foundation so first-deploy bootstrap can materialize the index before `agent/agent_bricks.py` attaches it to Knowledge Assistant. ### Agent Bricks target runtime @@ -118,15 +116,25 @@ It also demonstrates a development workflow: **Spec-Kit** for spec-driven design └──────────┬──────────┘ ▼ ┌──────────────────────┐ - │ Response JSON / App │ - │ citations, feedback │ + │ Agent output → App │ + │ final answer, │ + │ citations, feedback, │ │ latency, audit │ └──────────────────────┘ ``` -Knowledge Assistant is the default single-filing Q&A path because the Agent Bricks article positions the hard part as governed context, identity, and observability rather than hand-building the agent loop. Supervisor Agent is the default cross-company orchestration path. Custom code is allowed only where it is business logic around Agent Bricks, such as a deterministic KPI table tool or the App-specific feedback UI. It must not replace Knowledge Assistant, Supervisor Agent, Agent Bricks serving, or Agent Bricks governance. +Databricks creation path: [Create an AI agent](https://docs.databricks.com/aws/en/generative-ai/agent-framework/create-agent) → Knowledge Assistant for document Q&A. Supervisor Agent coordinates the Knowledge Assistant and UC function tools. + +Repository code is limited to deterministic tool glue, app UI, evals, and deployment scripts. + +**Concrete Agent Bricks wiring**: -**Removed divergence**: the custom `agent/analyst_agent.py`, `agent/retrieval.py`, `agent/supervisor.py`, `agent/log_and_register.py`, and `resources/consumers/agent.serving.yml` path has been removed. `scripts/bootstrap_agent_bricks.py` is now the production bootstrap for Knowledge Assistant, the UC KPI function, and Supervisor Agent configuration. +- `agent/agent_bricks.py` creates or updates `doc-intel-knowledge-${target}` as the Knowledge Assistant. Its source is the Vector Search index over `gold_filing_sections_indexable`, with `summary` as the text column and `filename` as the document URI column. +- The same bootstrap creates or updates the UC SQL function `..lookup_10k_kpis`. +- `doc-intel-supervisor-${target}` is the Supervisor Agent. Its tools are the Knowledge Assistant and the UC SQL KPI function. Supervisor Agent owns tool routing. +- Agent Bricks generates concrete serving endpoint names for Knowledge Assistant and Supervisor Agent. The repo resolves the live Supervisor endpoint with `scripts/resolve-agent-endpoint.sh` and passes it into DAB as `agent_endpoint_name`. +- Serving endpoint permissions are granted by endpoint ID after the generated endpoint is ready. The Databricks App does not bind to the endpoint as a resource; it invokes the resolved endpoint with each user's OBO token. +- Agent Bricks responses use an OpenAI Responses-style `output` message sequence in current validation. The app displays the last output text group as the answer. Knowledge Assistant citations have been observed as markdown footnotes in intermediate messages, so `app/agent_bricks_response.py` normalizes those footnotes into citation chips. ### Runtime stack @@ -216,7 +224,7 @@ When you read `specs/001-doc-intel-10k/plan.md` you'll see a "Constitution Check ### Pillar 2 — Databricks Asset Bundles + the Claude Code skill suite -[**Databricks Asset Bundles**](https://docs.databricks.com/aws/en/dev-tools/bundles/) (DABs) describe most of the workspace state as YAML. One root `databricks.yml` declares variables and targets (`demo`, `prod`); `resources/**/*.yml` declares each resource (pipeline, jobs, Vector Search endpoint, index-refresh job, Agent Bricks endpoint/configuration, app, monitor, dashboard, Lakebase instance + catalog). `databricks bundle deploy -t demo` reconciles workspace state to YAML. The Vector Search **index** is still created and synced by `jobs/index_refresh/sync_index.py` until DAB supports index resources directly. +[**Databricks Asset Bundles**](https://docs.databricks.com/aws/en/dev-tools/bundles/) (DABs) describe most of the workspace state as YAML. One root `databricks.yml` declares variables and targets (`demo`, `prod`); `resources/**/*.yml` declares each DAB-managed resource (pipeline, jobs, Vector Search endpoint, app, monitor, dashboard, Lakebase instance + catalog). `databricks bundle deploy -t demo` reconciles workspace state to YAML. The Vector Search **index** is still created and synced by `jobs/index_refresh/sync_index.py` until DAB supports index resources directly. Agent Bricks Knowledge Assistant and Supervisor Agent are SDK-managed by `agent/agent_bricks.py`; DAB only passes the resolved generated Supervisor endpoint into the app through `agent_endpoint_name`. This repo was built with Databricks-specific Claude Code skill bundles. Those bundles are distributed by Databricks via the CLI / Claude Code plugin channel and **are not vendored in this open-source tree** — install them locally if you have access, or reference the canonical Databricks docs (mapping in [`../CONTRIBUTING.md`](../CONTRIBUTING.md)). @@ -260,13 +268,13 @@ DABs deploy *everything in one shot*. But our resources have a chicken-and-egg p │ ▸ Tables ────┼──── all need each other │ │ ▸ Vector idx ───┤ │ │ ▸ Agent Bricks ──┤ Monitor wants the │ - │ ▸ App ───┤ KPI table to exist │ + │ ▸ App config ───┤ KPI table to exist │ │ ▸ App ───┤ BEFORE it can attach │ │ ▸ Monitor ────┘ │ │ ▸ Lakebase ──── │ └────────────────────────────────────────────────┘ - App needs the Agent Bricks Supervisor endpoint. + App needs the generated Agent Bricks Supervisor endpoint name. Supervisor needs Knowledge Assistant + UC function tools. Knowledge Assistant needs the Vector Search index. Monitor needs the table populated. @@ -289,7 +297,7 @@ The fix is a **staged deploy** orchestrated by `scripts/bootstrap-demo.sh`. Reso └── consumers/ ← need foundation to be RUNNING and producing data ├── kpi_drift.yml (needs gold_filing_kpis table) ├── index_refresh.job.yml (needs source table) - ├── analyst.app.yml (needs Lakebase + generated agent endpoint) + ├── analyst.app.yml (needs Lakebase + generated agent endpoint name) ├── usage.dashboard.yml └── lakebase_catalog.yml (needs instance AVAILABLE) ``` @@ -332,7 +340,7 @@ The fix is a **staged deploy** orchestrated by `scripts/bootstrap-demo.sh`. Reso └──────────────────────────┘ ``` -**Why two modes?** DAB tracks resource state; if you run the temp-rename trick against an existing deployment, DAB sees the consumer YAMLs as removed and plans to delete the app, monitor, dashboard, etc. Appropriate on a fresh workspace; destructive in steady-state. The script detects mode and does the right thing. +**Why two modes?** DAB tracks resource state; if you run the temp-rename trick against an existing deployment, DAB sees the consumer YAMLs as removed and plans to delete the app, monitor, dashboard, etc. Use FIRST-DEPLOY only for a fresh workspace; use STEADY-STATE after resources exist. CI (`.github/workflows/deploy.yml`) assumes steady-state — the first-ever bring-up of a workspace must be done locally with `./scripts/bootstrap-demo.sh`. After that, every push to `main` runs the steady-state path: full `bundle deploy` → refresh data → sync index → update Agent Bricks → grants → CLEARS gate. @@ -344,7 +352,7 @@ For the per-step procedure and known failure modes, see [`runbook.md` § Known d - **Wiring `ai_parse_document` into Lakeflow SDP** — pattern for streaming-tables + `STREAM(...)` views + `APPLY CHANGES INTO` keyed on filename. - **Scoring document quality before retrieval** — five 0–6 dimensions in SQL, threshold filter on the index source. -- **Building on Agent Bricks instead of custom agent loops** — Knowledge Assistant for cited document Q&A, Supervisor Agent for orchestration, deterministic KPI tool glue for structured comparisons. +- **Agent Bricks orchestration** — Knowledge Assistant for cited document Q&A, Supervisor Agent for orchestration, deterministic KPI tool glue for structured comparisons. - **Grounding an agent with citations** — Document Intelligence output and the governed Vector Search / Knowledge Assistant source provide the citation-bearing context. - **Handling DAB deploy ordering** — chicken-egg dependencies between heterogeneous resources, solved with a 5-step bootstrap rather than `depends_on` (which DAB doesn't reliably honor across resource types). - **Gating deploys on MLflow eval** — `mlflow.evaluate(model_type="databricks-agent")` with documented metric keys, per-axis thresholds, exit-code gate in CI. diff --git a/docs/runbook.md b/docs/runbook.md index fd401ce..9d61e2d 100644 --- a/docs/runbook.md +++ b/docs/runbook.md @@ -35,26 +35,42 @@ If a filing scores below threshold: ## Update Agent Bricks configuration -Agent Bricks resources are managed by `scripts/bootstrap_agent_bricks.py`. Run it after changes to Knowledge Assistant instructions, Supervisor instructions, or the KPI tool function: +Agent Bricks resources are defined and applied by `agent/agent_bricks.py`. Run it after changes to Knowledge Assistant instructions, Supervisor instructions, or the KPI tool function: ```bash DOCINTEL_CATALOG= \ DOCINTEL_SCHEMA= \ DOCINTEL_WAREHOUSE_ID= \ -python scripts/bootstrap_agent_bricks.py --target demo +python -m agent.agent_bricks --target demo ``` This creates or updates the Knowledge Assistant, syncs the Vector Search knowledge source, creates or updates the UC SQL KPI function, and wires both into the Supervisor Agent endpoint. +Agent Bricks generates concrete serving endpoint names. After bootstrap, always resolve the live Supervisor endpoint before deploying or restarting the app: + +```bash +AGENT_ENDPOINT_NAME="$(./scripts/resolve-agent-endpoint.sh demo)" +databricks bundle deploy -t demo --var "agent_endpoint_name=${AGENT_ENDPOINT_NAME}" +databricks bundle run -t demo --var "agent_endpoint_name=${AGENT_ENDPOINT_NAME}" analyst_app +``` + +The app receives the generated endpoint as `DOCINTEL_AGENT_ENDPOINT`. + +## Agent Bricks invocation and citations + +The app and eval runner invoke the generated Supervisor endpoint through `POST /serving-endpoints/{endpoint}/invocations` with the user's OBO token. They do not use `WorkspaceClient.serving_endpoints.query()` for Agent Bricks calls because workspace validation showed that path did not preserve the needed Agent Bricks response shape. + +Current Agent Bricks output is an OpenAI Responses-style `output` message sequence. `app/agent_bricks_response.py` displays the last output text group as the final answer. Knowledge Assistant citations were observed during 2026-04-26 validation as markdown footnotes in intermediate messages, such as `[^p1]: ... _ACME_10K_2024.pdf_`; the app extracts filenames from those footnotes for citation chips. If citation chips show only `source`, capture a live payload and grep for `[^` and `.pdf_` to confirm whether the Knowledge Assistant citation format changed. + ## Inspect CLEARS metrics in MLflow CI resolves the generated Agent Bricks Supervisor serving endpoint, then runs `python evals/clears_eval.py --endpoint "$AGENT_ENDPOINT_NAME"` after each `demo` deploy. Look for the experiment `/Shared/docintel-clears-`; each run logs: - Per-axis metrics: `correctness`, `adherence`, `relevance`, `execution`, `safety`, `latency_p95_ms` -- Per-category slices: `p2_correctness`, `p3_correctness` - Per-question latency: `latency_ms_` +- Per-category slices: `p2_correctness`, `p3_correctness`, only when the active MLflow/databricks-agents output includes per-row correctness columns -Failures are logged as a JSON list under the run tag `failures`. The script exit-code-fails the deploy if any threshold is missed (FR-010, SC-002, SC-003). +Metric key names can vary across MLflow/databricks-agents versions. The eval runner maps current aggregate keys such as `correctness/percentage`, `guideline_adherence/percentage`, `groundedness/percentage`, and `safety/percentage` to the CLEARS axes. Failures are logged as a JSON list under the run tag `failures`. The script exit-code-fails the deploy if any threshold is missed (FR-010, SC-002, SC-003). ## Common failure modes @@ -62,21 +78,21 @@ Failures are logged as a JSON list under the run tag `failures`. The script exit |---|---|---| | `bundle validate` fails on `ai_parse_document` | Workspace lacks AI Functions GA | Move SQL warehouse to a recent serverless channel | | Vector Search index sync stuck | Embedding endpoint not provisioned | Provision `databricks-bge-large-en` or override `var.embedding_model_endpoint_name` | +| `DOCINTEL_AGENT_ENDPOINT` is `UNSET_AGENT_BRICKS_ENDPOINT` | Bundle deploy/run omitted the generated endpoint variable | Re-run with `--var "agent_endpoint_name=$(./scripts/resolve-agent-endpoint.sh demo)"` | | Agent endpoint 401 | OBO not plumbed end-to-end | Verify `app/app.py:_user_client` reads `x-forwarded-access-token` and `resources/consumers/analyst.app.yml:user_api_scopes` includes `serving.serving-endpoints` and `sql` | +| App deploy fails with `Databricks Apps - user token passthrough feature is not enabled` | Workspace/org prerequisite missing | Enable the Databricks Apps user-token passthrough feature and rerun bootstrap | | Agent answers ignore user UC permissions | OBO scopes wiped by `bundle run` (documented destructive-update behavior — see [Databricks Apps deploy docs](https://docs.databricks.com/aws/en/dev-tools/databricks-apps/deploy)) | Re-apply: `databricks apps update doc-intel-analyst-demo --user-api-scopes serving.serving-endpoints,sql,iam.access-control:read,iam.current-user:read` | +| Bootstrap cannot grant Agent Bricks endpoint query permission | Permissions API was called with endpoint name instead of internal endpoint ID, or the generated endpoint is not ready | Use current `agent/agent_bricks.py`; it waits for readiness and grants by serving endpoint ID | | Streamlit user sees stale UC permissions | OBO token captured at WebSocket open; never refreshes ([Databricks Apps runtime docs](https://docs.databricks.com/aws/en/dev-tools/databricks-apps/app-runtime)) | Reload the page after permission changes | | Lakebase tables not writable from deployed App | Local-dev `streamlit run` initialised schema under user identity, not App SP | Connect as App SP and `DROP TABLE feedback, query_logs, conversation_history`; next App run re-creates them under SP. See `app/README.md` | | CLEARS Latency axis fails | Agent Bricks orchestration or Knowledge Assistant source is too broad | Narrow the Knowledge Assistant source, tune Supervisor instructions, or reduce structured-tool fan-out | +| Citation chips render but filenames show `source` | Knowledge Assistant footnote format changed or omitted filename markers | Capture the raw Agent Bricks payload and compare it with `app/agent_bricks_response.py`'s markdown-footnote parser | | App errors connecting to Lakebase | Database resource binding missing Postgres env vars | Check the `docintel-lakebase` resource binding and `PGHOST`/`PGPORT`/`PGUSER`/`PGPASSWORD`/`PGDATABASE` in the App runtime | ## Verifying end-to-end OBO -Databricks Apps user-token passthrough, Agent Bricks OBO, AI Gateway identity enforcement, and UC grants are production prerequisites. Bootstrap must fail if any required scope or workspace feature is missing. - -To verify OBO end-to-end: - 1. **Workspace admin** enables the "Databricks Apps - user token passthrough" feature in workspace settings. -2. Confirm the `user_api_scopes` block in `resources/consumers/analyst.app.yml` is present. Required scopes for the analyst app's call chain: +2. Confirm the required scopes are declared in `resources/consumers/analyst.app.yml`: ```yaml user_api_scopes: - serving.serving-endpoints # invoke Agent Bricks endpoint as user @@ -84,7 +100,12 @@ To verify OBO end-to-end: - iam.access-control:read # default - iam.current-user:read # default ``` -3. Redeploy: `databricks bundle deploy -t demo && databricks bundle run -t demo analyst_app`. +3. Redeploy: + ```bash + AGENT_ENDPOINT_NAME="$(./scripts/resolve-agent-endpoint.sh demo)" + databricks bundle deploy -t demo --var "agent_endpoint_name=${AGENT_ENDPOINT_NAME}" + databricks bundle run -t demo --var "agent_endpoint_name=${AGENT_ENDPOINT_NAME}" analyst_app + ``` 4. Verify: bootstrap scope checks assert required scopes. Visit the deployed app, ask a question, and confirm in audit logs that Agent Bricks, Knowledge Assistant, and structured KPI SQL calls run under the invoking user's identity. ## CLEARS thresholds @@ -106,25 +127,31 @@ Changing any threshold requires a constitution amendment per the Governance sect ## v1 baseline -(populate after the first successful `demo` deploy) +No passing v1 baseline has been recorded yet. Latest demo evidence as of 2026-04-26: ``` -MLflow run ID: -Deployed at: -P2 correctness: -P3 correctness: -Latency p95: +MLflow run ID: 772e902cab92459f9bf569296fc5f801 +Deployed at: 2026-04-26 +Correctness: 0.323 +Adherence: 0.000 +Relevance/grounding: 0.516 +Safety: 1.000 +Execution: 1.000 +Latency p95: 31711 ms +P2/P3 slices: unavailable in the current aggregate metric output ``` +Do not promote this as reference-ready until CLEARS passes and Databricks Apps user-token passthrough is enabled in the target workspace. + ## Known deploy ordering gaps The bundle has three chicken-egg dependencies that a single `bundle deploy` cannot resolve on a fresh workspace. Each needs a phase-2 step after a prior side effect: -1. **Databricks App binds to an Agent Bricks endpoint** +1. **Databricks App needs the generated Agent Bricks endpoint name** - Agent Bricks generates concrete Knowledge Assistant and Supervisor serving endpoint names. - - `scripts/bootstrap_agent_bricks.py` returns the generated Supervisor + - `agent/agent_bricks.py` returns the generated Supervisor endpoint, and `resources/consumers/analyst.app.yml` injects it into `DOCINTEL_AGENT_ENDPOINT` via the `agent_endpoint_name` bundle variable. - **Fix**: bootstrap creates data and Agent Bricks resources before the full diff --git a/resources/consumers/analyst.app.yml b/resources/consumers/analyst.app.yml index fbe9b3b..ca7f202 100644 --- a/resources/consumers/analyst.app.yml +++ b/resources/consumers/analyst.app.yml @@ -12,8 +12,8 @@ resources: # Databricks Apps auto-grants Lakebase permissions to the App SP on # deploy — see https://docs.databricks.com/aws/en/dev-tools/databricks-apps/access-data. - # Agent Bricks endpoint access is granted to the analyst group by - # scripts/bootstrap_agent_bricks.py because calls use OBO user identity. + # Agent Bricks endpoint access is granted by agent/agent_bricks.py + # because calls use OBO user identity. resources: - name: docintel-lakebase database: @@ -21,10 +21,9 @@ resources: instance_name: ${var.lakebase_instance} permission: CAN_CONNECT_AND_CREATE - # Mandatory OBO scopes (Databricks Apps IAM/auth docs: - # https://docs.databricks.com/aws/en/dev-tools/databricks-apps/iam-auth) - # require the workspace-level "Databricks Apps - user token passthrough". - # Deployment must fail if the workspace cannot grant these scopes. + # Mandatory OBO scopes. Requires the workspace-level "Databricks Apps - + # user token passthrough" feature. + # Docs: https://docs.databricks.com/aws/en/dev-tools/databricks-apps/iam-auth user_api_scopes: - serving.serving-endpoints - sql diff --git a/scripts/bootstrap-demo.sh b/scripts/bootstrap-demo.sh index cf64e80..7ba0459 100755 --- a/scripts/bootstrap-demo.sh +++ b/scripts/bootstrap-demo.sh @@ -111,7 +111,7 @@ set_agent_endpoint_name() { run_agent_bricks_bootstrap() { local bootstrap_json endpoint - bootstrap_json=$("$PYTHON" scripts/bootstrap_agent_bricks.py \ + bootstrap_json=$("$PYTHON" -m agent.agent_bricks \ --target "$TARGET" \ --catalog "$DOCINTEL_CATALOG" \ --schema "$DOCINTEL_SCHEMA" \ @@ -308,11 +308,9 @@ databricks api patch \ --json "{\"changes\":[{\"principal\":\"${ANALYST_GROUP}\",\"add\":[\"USE_SCHEMA\",\"SELECT\",\"EXECUTE\"]}]}" \ >/dev/null 2>&1 || log " warn: schema grants failed (may already be applied; UC dedupes)" -# OBO scope verification (only meaningful when user_api_scopes is declared). -if grep -q '^ user_api_scopes:' resources/consumers/analyst.app.yml 2>/dev/null; then - log " verifying OBO scopes on $APP_NAME" - if app_state=$(databricks apps get "$APP_NAME" --output json 2>/dev/null); then - "$PYTHON" -c " +log " verifying OBO scopes on $APP_NAME" +if app_state=$(databricks apps get "$APP_NAME" --output json 2>/dev/null); then + "$PYTHON" -c " import json app = json.loads('''$app_state''') scopes = set(app.get('user_api_scopes') or []) @@ -322,11 +320,8 @@ if missing: raise SystemExit(f'OBO scopes missing: {sorted(missing)} (got {sorted(scopes)})') print(f' OBO scopes intact: {sorted(scopes)}') " || die "OBO scopes missing after deploy" - else - die "unable to read app state for OBO verification" - fi else - die "resources/consumers/analyst.app.yml must declare user_api_scopes; OBO is mandatory" + die "unable to read app state for OBO verification" fi # ─── Step 6: smoke check ───────────────────────────────────────────────────── From 5160b216fa3b159e22e769298224184159935809 Mon Sep 17 00:00:00 2001 From: Sathish Krishnan <10681383+SatyKrish@users.noreply.github.com> Date: Sun, 26 Apr 2026 16:10:38 -0400 Subject: [PATCH 2/7] Rename document intelligence agent module Rename the Agent Bricks deployment module to agent/document_intelligence_agent.py and update shell/docs references so the code artifact reflects the domain agent rather than the platform. Validated with pytest, py_compile, bash -n, git diff --check, and Databricks bundle validate for demo. --- CLAUDE.md | 4 ++-- README.md | 10 +++++----- VALIDATION.md | 4 ++-- ...nt_bricks.py => document_intelligence_agent.py} | 12 ++++++------ agent/tools.py | 2 +- databricks.yml | 2 +- docs/design.md | 8 ++++---- docs/runbook.md | 10 +++++----- resources/consumers/analyst.app.yml | 2 +- scripts/bootstrap-demo.sh | 14 +++++++------- specs/001-doc-intel-10k/plan.md | 6 +++--- specs/001-doc-intel-10k/tasks.md | 6 +++--- 12 files changed, 40 insertions(+), 40 deletions(-) rename agent/{agent_bricks.py => document_intelligence_agent.py} (98%) diff --git a/CLAUDE.md b/CLAUDE.md index adb298c..892f151 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -16,7 +16,7 @@ For an end-to-end overview written for humans, read [`README.md`](./README.md). The bundle has three chicken-egg dependencies that prevent a single `databricks bundle deploy -t demo` from succeeding on a fresh workspace: -1. **Databricks App config** needs the generated Agent Bricks Supervisor endpoint name from `agent/agent_bricks.py`, which can only run after the Vector Search index exists. +1. **Databricks App config** needs the generated Agent Bricks Supervisor endpoint name from `agent/document_intelligence_agent.py`, which can only run after the Vector Search index exists. 2. **Lakehouse Monitor** (`resources/consumers/kpi_drift.yml`) attaches to `gold_filing_kpis`, which doesn't exist until the pipeline runs once. 3. **Lakebase database_catalog + Databricks App** race the `database_instance` provisioning. @@ -70,7 +70,7 @@ These were discovered the painful way during the 2026-04-25 bring-up. Future ses - **Section normalization**: `pipelines/sql/03_gold_classify_extract.sql` POSEXPLODES `parsed:sections[*]` and represents sectionless VARIANT output as one `full_document` row so we never lose a filing. - **`lakebase_stopped: true` is rejected on instance creation**: the API doesn't allow creating a database_instance directly into stopped state. Default is `false`; flip to `true` only after the instance exists. Reference: `databricks.yml` variable description. - **macOS doesn't ship `python`**: scripts must prefer `.venv/bin/python` then fall back to `python3`. Reference: `scripts/bootstrap-demo.sh`. -- **Agent Bricks resources are SDK-managed**: `agent/agent_bricks.py` creates/updates the Knowledge Assistant, its Vector Search knowledge source, the UC KPI function, and the Supervisor Agent. DAB still manages the surrounding data/app/monitor resources. +- **Agent Bricks resources are SDK-managed**: `agent/document_intelligence_agent.py` creates/updates the Knowledge Assistant, its Vector Search knowledge source, the UC KPI function, and the Supervisor Agent. DAB still manages the surrounding data/app/monitor resources. - **Agent Bricks generates endpoint names**: use `scripts/resolve-agent-endpoint.sh ` and pass the result as `--var agent_endpoint_name=...` for deploys and app runs. - **Agent Bricks invocation uses the invocations path directly**: `app/agent_bricks_client.py` posts to `/serving-endpoints/{endpoint}/invocations` with the user's OBO token and an `X-Request-ID`. Do not swap this back to `WorkspaceClient.serving_endpoints.query()` without revalidating the Agent Bricks response shape. - **Streamlit on Databricks Apps requires CORS+XSRF off via env vars**: not flags. `STREAMLIT_SERVER_ENABLE_CORS=false` and `STREAMLIT_SERVER_ENABLE_XSRF_PROTECTION=false` in `app/app.yaml`. Databricks Apps runtime config: https://docs.databricks.com/aws/en/dev-tools/databricks-apps/app-runtime. diff --git a/README.md b/README.md index bd3b707..c2bb876 100644 --- a/README.md +++ b/README.md @@ -84,9 +84,9 @@ Databricks creation path: [Create an AI agent](https://docs.databricks.com/aws/e The production agent path is: 1. `jobs/index_refresh/sync_index.py` creates/syncs the Mosaic AI Vector Search Delta-Sync index over `gold_filing_sections_indexable`. -2. `agent/agent_bricks.py` creates or updates the Agent Bricks Knowledge Assistant with that Vector Search index as its knowledge source. The source uses `summary` as the searchable text column and `filename` as the document URI column. -3. The same bootstrap creates or updates the UC SQL function `lookup_10k_kpis`. -4. The bootstrap creates or updates the Agent Bricks Supervisor Agent with two tools: the Knowledge Assistant for cited document Q&A and the UC function for deterministic KPI lookups. +2. `agent/document_intelligence_agent.py` creates or updates the Agent Bricks Knowledge Assistant with that Vector Search index as its knowledge source. The source uses `summary` as the searchable text column and `filename` as the document URI column. +3. `agent/document_intelligence_agent.py` creates or updates the UC SQL function `lookup_10k_kpis`. +4. `agent/document_intelligence_agent.py` creates or updates the Agent Bricks Supervisor Agent with two tools: the Knowledge Assistant for cited document Q&A and the UC function for deterministic KPI lookups. 5. Agent Bricks generates concrete serving endpoint names. Resolve the live Supervisor endpoint with `./scripts/resolve-agent-endpoint.sh `. 6. The Databricks App receives the resolved endpoint through the `agent_endpoint_name` bundle variable as `DOCINTEL_AGENT_ENDPOINT`. 7. The app invokes `POST /serving-endpoints/{endpoint}/invocations` directly with the user's OBO token. `WorkspaceClient.serving_endpoints.query()` is not used for Agent Bricks invocation because validation showed it did not preserve the needed Agent Bricks response shape. @@ -228,7 +228,7 @@ databricks bundle run -t demo --var "agent_endpoint_name=${AGENT_ENDPOINT_NAME}" DOCINTEL_CATALOG=workspace \ DOCINTEL_SCHEMA=docintel_10k_demo \ DOCINTEL_WAREHOUSE_ID= \ -python -m agent.agent_bricks --target demo +python -m agent.document_intelligence_agent --target demo AGENT_ENDPOINT_NAME="$(./scripts/resolve-agent-endpoint.sh demo)" databricks bundle deploy -t demo --var "agent_endpoint_name=${AGENT_ENDPOINT_NAME}" databricks bundle run -t demo --var "agent_endpoint_name=${AGENT_ENDPOINT_NAME}" analyst_app @@ -321,7 +321,7 @@ bash -n scripts/bootstrap-demo.sh # Compile checks for all modified Python .venv/bin/python -m py_compile \ - agent/agent_bricks.py agent/tools.py \ + agent/document_intelligence_agent.py agent/tools.py \ app/app.py app/agent_bricks_client.py app/agent_bricks_response.py app/lakebase_client.py \ evals/clears_eval.py \ scripts/wait_for_kpis.py samples/synthesize.py diff --git a/VALIDATION.md b/VALIDATION.md index 4cf8c5b..5ee8962 100644 --- a/VALIDATION.md +++ b/VALIDATION.md @@ -8,7 +8,7 @@ Use this guide to prove the reference implementation works in a Databricks works python3 -m py_compile \ agent/tools.py \ app/app.py app/agent_bricks_client.py app/agent_bricks_response.py app/lakebase_client.py \ - evals/clears_eval.py agent/agent_bricks.py \ + evals/clears_eval.py agent/document_intelligence_agent.py \ scripts/wait_for_kpis.py samples/synthesize.py bash -n scripts/bootstrap-demo.sh @@ -103,7 +103,7 @@ Expected: As of 2026-04-26, the demo workspace evidence is: - Bundle validation passed with the resolved Agent Bricks Supervisor endpoint. -- Agent Bricks bootstrap succeeded: +- Document Intelligence Agent deployment succeeded: - Knowledge Assistant display name: `doc-intel-knowledge-demo` - Supervisor display name: `doc-intel-supervisor-demo` - UC function: `workspace.docintel_10k_demo.lookup_10k_kpis` diff --git a/agent/agent_bricks.py b/agent/document_intelligence_agent.py similarity index 98% rename from agent/agent_bricks.py rename to agent/document_intelligence_agent.py index a57dfe8..c8a64b8 100644 --- a/agent/agent_bricks.py +++ b/agent/document_intelligence_agent.py @@ -1,4 +1,4 @@ -"""Agent Bricks definition and SDK application logic.""" +"""Document Intelligence Agent definition and deployment logic.""" from __future__ import annotations @@ -28,7 +28,7 @@ @dataclass -class AgentBricksRuntime: +class DocumentIntelligenceAgentRuntime: knowledge_assistant: KnowledgeAssistant supervisor_agent: SupervisorAgent kpi_function: str @@ -370,7 +370,7 @@ def _grant_endpoint_query(w: WorkspaceClient, endpoint_name: str, group_name: st ) -def deploy_agent_bricks_runtime( +def deploy_document_intelligence_agent( w: WorkspaceClient, *, target: str, @@ -378,7 +378,7 @@ def deploy_agent_bricks_runtime( schema: str, warehouse_id: str, analyst_group: str, -) -> AgentBricksRuntime: +) -> DocumentIntelligenceAgentRuntime: index_name = f"{catalog}.{schema}.filings_summary_idx" kpi_function_name = _create_or_update_kpi_function( @@ -411,7 +411,7 @@ def deploy_agent_bricks_runtime( if actual_knowledge_endpoint: _grant_endpoint_query(w, actual_knowledge_endpoint, analyst_group) - return AgentBricksRuntime( + return DocumentIntelligenceAgentRuntime( knowledge_assistant=knowledge_assistant, supervisor_agent=supervisor, kpi_function=kpi_function_name, @@ -432,7 +432,7 @@ def main() -> int: if not args.catalog or not args.schema or not args.warehouse_id: parser.error("--catalog, --schema, and --warehouse-id are required") - runtime = deploy_agent_bricks_runtime( + runtime = deploy_document_intelligence_agent( WorkspaceClient(), target=args.target, catalog=args.catalog, diff --git a/agent/tools.py b/agent/tools.py index 4773d4c..20759d7 100644 --- a/agent/tools.py +++ b/agent/tools.py @@ -1,7 +1,7 @@ """Deterministic KPI tool glue for Agent Bricks. The production tool is a Unity Catalog SQL function created by -`agent.agent_bricks`. These helpers keep the SQL access pattern testable. +`agent.document_intelligence_agent`. These helpers keep the SQL access pattern testable. """ from __future__ import annotations diff --git a/databricks.yml b/databricks.yml index 66fa154..724ba85 100644 --- a/databricks.yml +++ b/databricks.yml @@ -37,7 +37,7 @@ variables: description: UC group granted SELECT/USE on the catalog/schema default: account users agent_endpoint_name: - description: Agent Bricks Supervisor serving endpoint name resolved by agent.agent_bricks + description: Agent Bricks Supervisor serving endpoint name resolved by agent.document_intelligence_agent default: UNSET_AGENT_BRICKS_ENDPOINT targets: diff --git a/docs/design.md b/docs/design.md index 93ebc4c..6793be0 100644 --- a/docs/design.md +++ b/docs/design.md @@ -91,7 +91,7 @@ It also demonstrates a development workflow: **Spec-Kit** for spec-driven design "Quality before retrieval." ``` -**Ownership note**: DAB manages the Vector Search **endpoint** (`resources/foundation/filings_index.yml`) and the index-refresh **job** (`resources/consumers/index_refresh.job.yml`). The **index** itself isn't yet a DAB-managed resource type as of CLI 0.298 — `jobs/index_refresh/sync_index.py` creates the Delta-Sync index on first run and triggers a sync on subsequent runs. The endpoint lives in foundation so first-deploy bootstrap can materialize the index before `agent/agent_bricks.py` attaches it to Knowledge Assistant. +**Ownership note**: DAB manages the Vector Search **endpoint** (`resources/foundation/filings_index.yml`) and the index-refresh **job** (`resources/consumers/index_refresh.job.yml`). The **index** itself isn't yet a DAB-managed resource type as of CLI 0.298 — `jobs/index_refresh/sync_index.py` creates the Delta-Sync index on first run and triggers a sync on subsequent runs. The endpoint lives in foundation so first-deploy bootstrap can materialize the index before `agent/document_intelligence_agent.py` attaches it to Knowledge Assistant. ### Agent Bricks target runtime @@ -129,8 +129,8 @@ Repository code is limited to deterministic tool glue, app UI, evals, and deploy **Concrete Agent Bricks wiring**: -- `agent/agent_bricks.py` creates or updates `doc-intel-knowledge-${target}` as the Knowledge Assistant. Its source is the Vector Search index over `gold_filing_sections_indexable`, with `summary` as the text column and `filename` as the document URI column. -- The same bootstrap creates or updates the UC SQL function `..lookup_10k_kpis`. +- `agent/document_intelligence_agent.py` creates or updates `doc-intel-knowledge-${target}` as the Knowledge Assistant. Its source is the Vector Search index over `gold_filing_sections_indexable`, with `summary` as the text column and `filename` as the document URI column. +- `agent/document_intelligence_agent.py` creates or updates the UC SQL function `..lookup_10k_kpis`. - `doc-intel-supervisor-${target}` is the Supervisor Agent. Its tools are the Knowledge Assistant and the UC SQL KPI function. Supervisor Agent owns tool routing. - Agent Bricks generates concrete serving endpoint names for Knowledge Assistant and Supervisor Agent. The repo resolves the live Supervisor endpoint with `scripts/resolve-agent-endpoint.sh` and passes it into DAB as `agent_endpoint_name`. - Serving endpoint permissions are granted by endpoint ID after the generated endpoint is ready. The Databricks App does not bind to the endpoint as a resource; it invokes the resolved endpoint with each user's OBO token. @@ -224,7 +224,7 @@ When you read `specs/001-doc-intel-10k/plan.md` you'll see a "Constitution Check ### Pillar 2 — Databricks Asset Bundles + the Claude Code skill suite -[**Databricks Asset Bundles**](https://docs.databricks.com/aws/en/dev-tools/bundles/) (DABs) describe most of the workspace state as YAML. One root `databricks.yml` declares variables and targets (`demo`, `prod`); `resources/**/*.yml` declares each DAB-managed resource (pipeline, jobs, Vector Search endpoint, app, monitor, dashboard, Lakebase instance + catalog). `databricks bundle deploy -t demo` reconciles workspace state to YAML. The Vector Search **index** is still created and synced by `jobs/index_refresh/sync_index.py` until DAB supports index resources directly. Agent Bricks Knowledge Assistant and Supervisor Agent are SDK-managed by `agent/agent_bricks.py`; DAB only passes the resolved generated Supervisor endpoint into the app through `agent_endpoint_name`. +[**Databricks Asset Bundles**](https://docs.databricks.com/aws/en/dev-tools/bundles/) (DABs) describe most of the workspace state as YAML. One root `databricks.yml` declares variables and targets (`demo`, `prod`); `resources/**/*.yml` declares each DAB-managed resource (pipeline, jobs, Vector Search endpoint, app, monitor, dashboard, Lakebase instance + catalog). `databricks bundle deploy -t demo` reconciles workspace state to YAML. The Vector Search **index** is still created and synced by `jobs/index_refresh/sync_index.py` until DAB supports index resources directly. Agent Bricks Knowledge Assistant and Supervisor Agent are SDK-managed by `agent/document_intelligence_agent.py`; DAB only passes the resolved generated Supervisor endpoint into the app through `agent_endpoint_name`. This repo was built with Databricks-specific Claude Code skill bundles. Those bundles are distributed by Databricks via the CLI / Claude Code plugin channel and **are not vendored in this open-source tree** — install them locally if you have access, or reference the canonical Databricks docs (mapping in [`../CONTRIBUTING.md`](../CONTRIBUTING.md)). diff --git a/docs/runbook.md b/docs/runbook.md index 9d61e2d..7c04d43 100644 --- a/docs/runbook.md +++ b/docs/runbook.md @@ -35,18 +35,18 @@ If a filing scores below threshold: ## Update Agent Bricks configuration -Agent Bricks resources are defined and applied by `agent/agent_bricks.py`. Run it after changes to Knowledge Assistant instructions, Supervisor instructions, or the KPI tool function: +Agent Bricks resources are defined and applied by `agent/document_intelligence_agent.py`. Run it after changes to Knowledge Assistant instructions, Supervisor instructions, or the KPI tool function: ```bash DOCINTEL_CATALOG= \ DOCINTEL_SCHEMA= \ DOCINTEL_WAREHOUSE_ID= \ -python -m agent.agent_bricks --target demo +python -m agent.document_intelligence_agent --target demo ``` This creates or updates the Knowledge Assistant, syncs the Vector Search knowledge source, creates or updates the UC SQL KPI function, and wires both into the Supervisor Agent endpoint. -Agent Bricks generates concrete serving endpoint names. After bootstrap, always resolve the live Supervisor endpoint before deploying or restarting the app: +Agent Bricks generates concrete serving endpoint names. After applying the agent definition, always resolve the live Supervisor endpoint before deploying or restarting the app: ```bash AGENT_ENDPOINT_NAME="$(./scripts/resolve-agent-endpoint.sh demo)" @@ -82,7 +82,7 @@ Metric key names can vary across MLflow/databricks-agents versions. The eval run | Agent endpoint 401 | OBO not plumbed end-to-end | Verify `app/app.py:_user_client` reads `x-forwarded-access-token` and `resources/consumers/analyst.app.yml:user_api_scopes` includes `serving.serving-endpoints` and `sql` | | App deploy fails with `Databricks Apps - user token passthrough feature is not enabled` | Workspace/org prerequisite missing | Enable the Databricks Apps user-token passthrough feature and rerun bootstrap | | Agent answers ignore user UC permissions | OBO scopes wiped by `bundle run` (documented destructive-update behavior — see [Databricks Apps deploy docs](https://docs.databricks.com/aws/en/dev-tools/databricks-apps/deploy)) | Re-apply: `databricks apps update doc-intel-analyst-demo --user-api-scopes serving.serving-endpoints,sql,iam.access-control:read,iam.current-user:read` | -| Bootstrap cannot grant Agent Bricks endpoint query permission | Permissions API was called with endpoint name instead of internal endpoint ID, or the generated endpoint is not ready | Use current `agent/agent_bricks.py`; it waits for readiness and grants by serving endpoint ID | +| Agent deployment cannot grant endpoint query permission | Permissions API was called with endpoint name instead of internal endpoint ID, or the generated endpoint is not ready | Use current `agent/document_intelligence_agent.py`; it waits for readiness and grants by serving endpoint ID | | Streamlit user sees stale UC permissions | OBO token captured at WebSocket open; never refreshes ([Databricks Apps runtime docs](https://docs.databricks.com/aws/en/dev-tools/databricks-apps/app-runtime)) | Reload the page after permission changes | | Lakebase tables not writable from deployed App | Local-dev `streamlit run` initialised schema under user identity, not App SP | Connect as App SP and `DROP TABLE feedback, query_logs, conversation_history`; next App run re-creates them under SP. See `app/README.md` | | CLEARS Latency axis fails | Agent Bricks orchestration or Knowledge Assistant source is too broad | Narrow the Knowledge Assistant source, tune Supervisor instructions, or reduce structured-tool fan-out | @@ -151,7 +151,7 @@ resolve on a fresh workspace. Each needs a phase-2 step after a prior side effec 1. **Databricks App needs the generated Agent Bricks endpoint name** - Agent Bricks generates concrete Knowledge Assistant and Supervisor serving endpoint names. - - `agent/agent_bricks.py` returns the generated Supervisor + - `agent/document_intelligence_agent.py` returns the generated Supervisor endpoint, and `resources/consumers/analyst.app.yml` injects it into `DOCINTEL_AGENT_ENDPOINT` via the `agent_endpoint_name` bundle variable. - **Fix**: bootstrap creates data and Agent Bricks resources before the full diff --git a/resources/consumers/analyst.app.yml b/resources/consumers/analyst.app.yml index ca7f202..ba5a27f 100644 --- a/resources/consumers/analyst.app.yml +++ b/resources/consumers/analyst.app.yml @@ -12,7 +12,7 @@ resources: # Databricks Apps auto-grants Lakebase permissions to the App SP on # deploy — see https://docs.databricks.com/aws/en/dev-tools/databricks-apps/access-data. - # Agent Bricks endpoint access is granted by agent/agent_bricks.py + # Agent Bricks endpoint access is granted by agent/document_intelligence_agent.py # because calls use OBO user identity. resources: - name: docintel-lakebase diff --git a/scripts/bootstrap-demo.sh b/scripts/bootstrap-demo.sh index 7ba0459..e278aaa 100755 --- a/scripts/bootstrap-demo.sh +++ b/scripts/bootstrap-demo.sh @@ -109,16 +109,16 @@ set_agent_endpoint_name() { log " using Agent Bricks Supervisor endpoint $AGENT_ENDPOINT_NAME" } -run_agent_bricks_bootstrap() { - local bootstrap_json endpoint - bootstrap_json=$("$PYTHON" -m agent.agent_bricks \ +deploy_document_intelligence_agent() { + local agent_json endpoint + agent_json=$("$PYTHON" -m agent.document_intelligence_agent \ --target "$TARGET" \ --catalog "$DOCINTEL_CATALOG" \ --schema "$DOCINTEL_SCHEMA" \ --warehouse-id "$DOCINTEL_WAREHOUSE_ID" \ --analyst-group "$ANALYST_GROUP") || \ - die "Agent Bricks bootstrap failed" - endpoint=$(printf '%s' "$bootstrap_json" | "$PYTHON" -c " + die "Document Intelligence Agent deployment failed" + endpoint=$(printf '%s' "$agent_json" | "$PYTHON" -c " import json, sys payload = json.load(sys.stdin) print(payload.get('supervisor_endpoint') or '') @@ -256,7 +256,7 @@ if [[ "$MODE" == "first" ]]; then --embedding-endpoint "$EMBEDDING_ENDPOINT" || \ die "VS index creation failed (sync_index.py)" - run_agent_bricks_bootstrap + deploy_document_intelligence_agent wait_for_lakebase_available log "step 3/6: stage-2 deploy (full bundle — consumers join the foundation)" @@ -286,7 +286,7 @@ else die "timed out waiting for $KPI_TABLE" databricks bundle run -t "$TARGET" "${BUNDLE_VAR_FLAGS[@]}" index_refresh || \ log " warn: index_refresh failed; the table_update trigger will retry on the next pipeline run" - run_agent_bricks_bootstrap + deploy_document_intelligence_agent log "step 3/6: skipped (no second deploy needed in steady-state)" fi diff --git a/specs/001-doc-intel-10k/plan.md b/specs/001-doc-intel-10k/plan.md index aeea37b..eb3fcb0 100644 --- a/specs/001-doc-intel-10k/plan.md +++ b/specs/001-doc-intel-10k/plan.md @@ -5,7 +5,7 @@ ## Summary -Build a Databricks-native, governed pipeline + Agent Bricks system that turns SEC 10-K PDFs into a queryable lakehouse and a cited Q&A experience. SQL Lakeflow Spark Declarative Pipelines parse PDFs once with `ai_parse_document` (VARIANT), classify sections with `ai_classify`, extract structured KPIs with `ai_extract`, and score every section against a 5-dimension quality rubric. High-quality summaries flow into a Mosaic AI Vector Search index. Agent Bricks Knowledge Assistant handles cited document Q&A; Agent Bricks Supervisor Agent coordinates the Knowledge Assistant with a deterministic Unity Catalog KPI function for cross-company comparisons. AI Gateway, Unity Catalog, and mandatory OBO enforce identity and audit. Conversation history and feedback land in Lakebase Postgres. Lakehouse Monitoring tracks extraction drift; an AI/BI dashboard surfaces query-log content gaps. CLEARS evaluation in MLflow gates promotion. The stack is deployed by DAB plus idempotent Agent Bricks bootstrap (`databricks bundle deploy -t demo|prod`, `scripts/bootstrap_agent_bricks.py`). +Build a Databricks-native, governed pipeline + Agent Bricks system that turns SEC 10-K PDFs into a queryable lakehouse and a cited Q&A experience. SQL Lakeflow Spark Declarative Pipelines parse PDFs once with `ai_parse_document` (VARIANT), classify sections with `ai_classify`, extract structured KPIs with `ai_extract`, and score every section against a 5-dimension quality rubric. High-quality summaries flow into a Mosaic AI Vector Search index. Agent Bricks Knowledge Assistant handles cited document Q&A; Agent Bricks Supervisor Agent coordinates the Knowledge Assistant with a deterministic Unity Catalog KPI function for cross-company comparisons. AI Gateway, Unity Catalog, and mandatory OBO enforce identity and audit. Conversation history and feedback land in Lakebase Postgres. Lakehouse Monitoring tracks extraction drift; an AI/BI dashboard surfaces query-log content gaps. CLEARS evaluation in MLflow gates promotion. The stack is deployed by DAB plus idempotent Agent Bricks bootstrap (`databricks bundle deploy -t demo|prod`, `agent/document_intelligence_agent.py`). ## Technical Context @@ -79,6 +79,7 @@ pipelines/ └── 04_gold_quality.sql # 5-dim rubric → quality_score agent/ +├── document_intelligence_agent.py # Knowledge Assistant + Supervisor definition ├── tools.py # deterministic KPI tool glue for Agent Bricks └── tests/ └── test_tools.py @@ -94,7 +95,6 @@ evals/ scripts/ ├── bootstrap-demo.sh # staged deploy orchestration -├── bootstrap_agent_bricks.py # Knowledge Assistant + Supervisor bootstrap └── wait_for_kpis.py .github/ @@ -104,7 +104,7 @@ scripts/ CLAUDE.md # Runtime guidance for Claude Code ``` -**Structure Decision**: Single DAB containing one pipeline, two jobs, one Vector Search endpoint, one Lakebase project, one monitor, one dashboard, one app, and a CI workflow. Agent Bricks resources are SDK-managed by `scripts/bootstrap_agent_bricks.py` until DAB exposes first-class Knowledge Assistant and Supervisor resource types. SQL pipeline code lives at the root under `pipelines/sql/`; deterministic tool glue lives at `agent/`; app code lives at `app/`. +**Structure Decision**: Single DAB containing one pipeline, two jobs, one Vector Search endpoint, one Lakebase project, one monitor, one dashboard, one app, and a CI workflow. Agent Bricks resources are SDK-managed by `agent/document_intelligence_agent.py` until DAB exposes first-class Knowledge Assistant and Supervisor resource types. SQL pipeline code lives at the root under `pipelines/sql/`; deterministic tool glue lives at `agent/`; app code lives at `app/`. ## Phase 0 — Outline & Research diff --git a/specs/001-doc-intel-10k/tasks.md b/specs/001-doc-intel-10k/tasks.md index e9eac71..002824b 100644 --- a/specs/001-doc-intel-10k/tasks.md +++ b/specs/001-doc-intel-10k/tasks.md @@ -19,7 +19,7 @@ description: "Task list for Databricks 10-K Analyst implementation" ## Path Conventions -This is a DAB plus Agent Bricks bootstrap project. SQL pipeline code is at `pipelines/sql/`, deterministic tool glue at `agent/`, Streamlit App at `app/`, evals at `evals/`, bundle resources at `resources/`, and Agent Bricks orchestration in `scripts/bootstrap_agent_bricks.py`. See plan.md for the full tree. +This is a DAB plus Agent Bricks deployment project. SQL pipeline code is at `pipelines/sql/`, deterministic tool glue at `agent/`, Streamlit App at `app/`, evals at `evals/`, bundle resources at `resources/`, and Agent Bricks orchestration in `agent/document_intelligence_agent.py`. See plan.md for the full tree. --- @@ -88,8 +88,8 @@ This is a DAB plus Agent Bricks bootstrap project. SQL pipeline code is at `pipe - [x] T022 [US2] Remove custom retrieval implementation (`agent/retrieval.py`) and configure Agent Bricks Knowledge Assistant over the governed Document Intelligence / Vector Search source (depends on T020) - [x] T023 [US2] Implement `agent/tools.py` as deterministic structured KPI tool glue for Agent Bricks, wrapping governed SQL over `gold_filing_kpis` - [x] T024 [US2] Remove custom `agent/analyst_agent.py` and direct `mlflow.pyfunc` registration; Knowledge Assistant owns single-filing cited Q&A (depends on T022, T023) -- [x] T025 [US2] Remove `agent/log_and_register.py` and bespoke model-version promotion from the production path; bootstrap configures Agent Bricks resources idempotently instead -- [x] T026 [US2] Replace `resources/consumers/agent.serving.yml` with `scripts/bootstrap_agent_bricks.py` Agent Bricks endpoint/configuration behind AI Gateway with mandatory OBO and guardrails (depends on T024, T025) +- [x] T025 [US2] Remove `agent/log_and_register.py` and bespoke model-version promotion from the production path; `agent/document_intelligence_agent.py` configures Agent Bricks resources idempotently instead +- [x] T026 [US2] Replace `resources/consumers/agent.serving.yml` with `agent/document_intelligence_agent.py` Agent Bricks endpoint/configuration behind AI Gateway with mandatory OBO and guardrails (depends on T024, T025) - [x] T027 [US2] Implement `app/app.py` (Streamlit): chat input, calls the Agent Bricks endpoint as the invoking user, renders answer + citations as chips, thumbs-up/down + comment widget that POSTs to a Lakebase write helper; persists `conversation_id` in session state (depends on T026, T007) - [x] T028 [US2] Implement `app/lakebase_client.py`: thin wrapper using `psycopg` with the bundle-injected DSN to insert into `conversation_history`, `query_logs`, `feedback` - [x] T029 [US2] Define the Databricks App in `resources/consumers/analyst.app.yml`: source = `app/`, runtime python, env = Lakebase binding + agent endpoint binding (depends on T027, T028) From 39a1f6e7c8f6c1dd4df8448d86e986647e06e96c Mon Sep 17 00:00:00 2001 From: Sathish Krishnan <10681383+SatyKrish@users.noreply.github.com> Date: Sun, 26 Apr 2026 17:17:40 -0400 Subject: [PATCH 3/7] Support demo App auth and Lakebase OAuth --- .github/workflows/deploy.yml | 74 +++++++++++++++++------------ README.md | 1 + SECURITY.md | 15 +++--- VALIDATION.md | 11 +++-- app/README.md | 21 +++++--- app/app.py | 17 +++++-- app/app.yaml | 10 ++-- app/lakebase_client.py | 26 ++++++++-- databricks.yml | 13 +++++ docs/runbook.md | 7 ++- jobs/index_refresh/sync_index.py | 37 ++++++++++++--- resources/consumers/analyst.app.yml | 14 +++--- scripts/bootstrap-demo.sh | 71 ++++++++++++++++++++++++++- 13 files changed, 234 insertions(+), 83 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index d658e52..37d2686 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -72,25 +72,28 @@ jobs: # transition it back through provisioning; the catalog/app bindings # need it AVAILABLE before the next bundle run touches them. run: | - python -c " -import json, os, sys, time, subprocess -name = os.environ.get('LAKEBASE_NAME') or 'docintel-demo-state-v1' -deadline = time.time() + 600 -while True: - out = subprocess.run(['databricks','api','get','/api/2.0/database/instances','--output','json'], - capture_output=True, text=True) - try: - d = json.loads(out.stdout) - except Exception: - d = {} - state = next((i.get('state') for i in d.get('database_instances',[]) if i.get('name')==name), 'UNKNOWN') - print(f'lakebase state: {state}') - if state == 'AVAILABLE': - sys.exit(0) - if time.time() >= deadline: - sys.exit(f'Lakebase {name} did not reach AVAILABLE within 600s (state={state})') - time.sleep(15) -" + python - <<'PY' + import json, os, sys, time, subprocess + name = os.environ.get('LAKEBASE_NAME') or 'docintel-demo-state-v1' + deadline = time.time() + 600 + while True: + out = subprocess.run( + ['databricks', 'api', 'get', '/api/2.0/database/instances', '--output', 'json'], + capture_output=True, + text=True, + ) + try: + d = json.loads(out.stdout) + except Exception: + d = {} + state = next((i.get('state') for i in d.get('database_instances', []) if i.get('name') == name), 'UNKNOWN') + print(f'lakebase state: {state}') + if state == 'AVAILABLE': + sys.exit(0) + if time.time() >= deadline: + sys.exit(f'Lakebase {name} did not reach AVAILABLE within 600s (state={state})') + time.sleep(15) + PY env: LAKEBASE_NAME: ${{ vars.DOCINTEL_LAKEBASE_NAME || 'docintel-demo-state-v1' }} @@ -104,7 +107,7 @@ while True: databricks bundle run -t demo --var "warehouse_id=$DOCINTEL_WAREHOUSE_ID" doc_intel_pipeline python scripts/wait_for_kpis.py --min-rows 3 --timeout 900 databricks bundle run -t demo --var "warehouse_id=$DOCINTEL_WAREHOUSE_ID" --var "agent_endpoint_name=$AGENT_ENDPOINT_NAME" index_refresh - python -m agent.agent_bricks \ + python -m agent.document_intelligence_agent \ --target demo \ --catalog "$DOCINTEL_CATALOG" \ --schema "$DOCINTEL_SCHEMA" \ @@ -130,19 +133,28 @@ while True: # `bundle deploy` alone uploads code but doesn't apply config/restart. run: databricks bundle run -t demo --var "warehouse_id=$DOCINTEL_WAREHOUSE_ID" --var "agent_endpoint_name=$AGENT_ENDPOINT_NAME" analyst_app - - name: Verify OBO scopes survived deploy - # `bundle run` may wipe user_api_scopes (documented destructive-update - # behavior). Fail loudly if required user scopes are missing. + - name: Verify app auth mode and endpoint grants run: | databricks apps get doc-intel-analyst-demo --output json > /tmp/app.json - python -c " -import json -app = json.load(open('/tmp/app.json')) -scopes = set(app.get('user_api_scopes') or []) -required = {'serving.serving-endpoints', 'sql'} -missing = required - scopes -assert not missing, f'OBO scopes missing: {sorted(missing)} (got {sorted(scopes)})' -" + app_obo_required="$(python -c "import yaml; d=yaml.safe_load(open('databricks.yml')); default=d.get('variables',{}).get('app_obo_required',{}).get('default','true'); value=d.get('targets',{}).get('demo',{}).get('variables',{}).get('app_obo_required', default); print(str(value).lower())")" + if [ "$app_obo_required" = "true" ]; then + # `bundle run` may wipe user_api_scopes (documented destructive-update + # behavior). Fail loudly if required user scopes are missing. + python -c "import json; app=json.load(open('/tmp/app.json')); scopes=set(app.get('user_api_scopes') or []); required={'serving.serving-endpoints','sql'}; missing=required-scopes; assert not missing, f'OBO scopes missing: {sorted(missing)} (got {sorted(scopes)})'" + else + python -c "import json; app=json.load(open('/tmp/app.json')); scopes=app.get('user_api_scopes'); assert not scopes, f'demo SP-fallback expected no user_api_scopes, got {scopes}'" + endpoint_id="$(databricks serving-endpoints get "$AGENT_ENDPOINT_NAME" --output json | python -c "import json, sys; e=json.load(sys.stdin); print(e.get('id') or e.get('name'))")" + python -c "import json; app=json.load(open('/tmp/app.json')); vals=[str(app.get(k)) for k in ('service_principal_client_id','service_principal_name','service_principal_id') if app.get(k) is not None]; print('\n'.join(dict.fromkeys(v for v in vals if v)))" > /tmp/app-sp-candidates.txt + granted=0 + while IFS= read -r principal; do + grant_json="$(python -c "import json, sys; print(json.dumps({'access_control_list':[{'service_principal_name':sys.argv[1],'permission_level':'CAN_QUERY'}]}))" "$principal")" + if databricks permissions update serving-endpoints "$endpoint_id" --json "$grant_json"; then + granted=1 + break + fi + done < /tmp/app-sp-candidates.txt + test "$granted" = "1" + fi - name: CLEARS evaluation gate run: python evals/clears_eval.py --endpoint "$AGENT_ENDPOINT_NAME" --dataset evals/dataset.jsonl diff --git a/README.md b/README.md index c2bb876..709e35d 100644 --- a/README.md +++ b/README.md @@ -288,6 +288,7 @@ Implementation uses `mlflow.evaluate(model_type="databricks-agent")` for the LLM | `max_pdf_bytes` | `52428800` (50 MB) | Reject filings larger than this | | `analyst_group` | `account users` | UC group granted SELECT/USE on schema, READ/WRITE on volume | | `agent_endpoint_name` | `UNSET_AGENT_BRICKS_ENDPOINT` | Generated Agent Bricks Supervisor endpoint resolved by `scripts/resolve-agent-endpoint.sh`; pass it on deploy/app-run commands after bootstrap | +| `app_obo_required` | `true` (prod) / `false` (demo) | Controls Databricks Apps user-token passthrough. Demo can use the App SP when passthrough is unavailable; prod requires OBO. | Override via `--var name=value` on any `bundle` command. diff --git a/SECURITY.md b/SECURITY.md index 96b4c3d..1a086f5 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,23 +2,24 @@ ## Supported Security Posture -This reference is designed for Databricks workspaces using Unity Catalog, Agent Bricks, AI Gateway, Databricks Apps resource bindings, and end-to-end on-behalf-of (OBO) user identity. The App requires the request's `x-forwarded-access-token`; missing tokens fail loudly. +This reference is designed for Databricks workspaces using Unity Catalog, Agent Bricks, AI Gateway, Databricks Apps resource bindings, and end-to-end on-behalf-of (OBO) user identity in prod. Demo can run with `app_obo_required=false` when the workspace does not have Databricks Apps user-token passthrough enabled; in that mode the App service principal invokes Agent Bricks and is granted `CAN_QUERY` after deploy. ## Enabling End-To-End OBO -1. Workspace admin enables Databricks Apps user-token passthrough. -2. Redeploy and run the app resource. The deploy fails if the workspace cannot grant the declared `user_api_scopes`. -3. Verify `serving.serving-endpoints` and `sql` scopes are present after deployment. -4. Verify audit logs show downstream calls under the invoking user where required. +1. Set `app_obo_required=true` for the target. Prod does this by default. +2. Workspace admin enables Databricks Apps user-token passthrough. +3. Redeploy and run the app resource. The deploy fails if the workspace cannot grant the declared `user_api_scopes`. +4. Verify `serving.serving-endpoints` and `sql` scopes are present after deployment. +5. Verify audit logs show downstream calls under the invoking user where required. -Agent Bricks / AI Gateway enforce downstream access to document Q&A, SQL tools, models, and any external tools under the invoking user's identity. +With OBO enabled, Agent Bricks / AI Gateway enforce downstream access to document Q&A, SQL tools, models, and any external tools under the invoking user's identity. ## Secrets And Credentials - Do not commit Databricks tokens, service-principal secrets, Postgres passwords, or local app settings. - `.claude/settings.local.json`, `.databricks/`, `.venv/`, MLflow local artifacts, Python caches, and local skill bundles are ignored. - Use GitHub Actions secrets for `DATABRICKS_HOST` and `DATABRICKS_TOKEN`. -- Use Databricks resource bindings for app access to Lakebase and serving endpoints. +- Use Databricks resource bindings for Lakebase. Agent Bricks endpoint access is granted directly to users or the App service principal, depending on target auth mode. ## Required Grants diff --git a/VALIDATION.md b/VALIDATION.md index 5ee8962..87d46fa 100644 --- a/VALIDATION.md +++ b/VALIDATION.md @@ -85,18 +85,19 @@ Expected: - Confirm the response has citations and the turn is written to Lakebase. - Submit thumbs feedback and confirm a feedback row is written. -## OBO Verification +## App Auth Verification -- Confirm `resources/consumers/analyst.app.yml:user_api_scopes` is present. +- Demo: confirm `user_api_scopes` is unset and `DOCINTEL_OBO_REQUIRED=false`. +- Prod: confirm `user_api_scopes` is present and `DOCINTEL_OBO_REQUIRED=true`. - Run: ```bash AGENT_ENDPOINT_NAME="$(./scripts/resolve-agent-endpoint.sh demo)" databricks bundle deploy -t demo --var "agent_endpoint_name=${AGENT_ENDPOINT_NAME}" databricks bundle run -t demo --var "agent_endpoint_name=${AGENT_ENDPOINT_NAME}" analyst_app ``` -- Confirm bootstrap or CI verifies `serving.serving-endpoints` and `sql` scopes. -- Check audit logs for user-scoped downstream access through Agent Bricks, Knowledge Assistant, and the structured KPI SQL function. -- If the workspace cannot grant user-token passthrough, deployment is invalid and must fail. +- Confirm bootstrap or CI verifies the target auth mode. Demo grants the App SP endpoint access; prod verifies `serving.serving-endpoints` and `sql` scopes. +- For prod, check audit logs for user-scoped downstream access through Agent Bricks, Knowledge Assistant, and the structured KPI SQL function. +- If prod cannot grant user-token passthrough, deployment is invalid and must fail. ## Latest Demo Snapshot diff --git a/app/README.md b/app/README.md index ce344e3..7515c72 100644 --- a/app/README.md +++ b/app/README.md @@ -31,11 +31,16 @@ export DATABRICKS_HOST=https://.cloud.databricks.com export DATABRICKS_CLIENT_ID= export DATABRICKS_CLIENT_SECRET= -# Lakebase env vars (PGHOST/PGPORT/PGUSER/PGPASSWORD/PGDATABASE) come from -# the App resource binding when deployed. Locally, derive them with: -eval "$(databricks apps get doc-intel-analyst-demo \ - --output json | jq -r '.resources[] | select(.name=="docintel-lakebase") | .database | @sh " -export PGHOST=\(.host) PGPORT=\(.port) PGUSER=\(.username) PGPASSWORD=\(.password) PGDATABASE=\(.database)"')" +# Lakebase env vars come from the App resource binding when deployed. +# Locally, set the same connection fields and let lakebase_client.py mint the +# OAuth database password through the Databricks SDK. +export DOCINTEL_LAKEBASE_INSTANCE=docintel-demo-state-v1 +export PGDATABASE=docintel-demo-state-v1 +export PGUSER= +export PGPORT=5432 +export PGSSLMODE=require +export PGHOST="$(databricks database get-database-instance "${DOCINTEL_LAKEBASE_INSTANCE}" \ + --output json | jq -r '.read_write_dns')" export DOCINTEL_AGENT_ENDPOINT="$(./scripts/resolve-agent-endpoint.sh demo)" streamlit run app/app.py @@ -55,10 +60,12 @@ DROP TABLE IF EXISTS conversation_history CASCADE; ## OBO (on-behalf-of) flow -The app builds a `WorkspaceClient(token=...)` from each user's `x-forwarded-access-token` header (`app.py:_user_client`) and invokes the Agent Bricks Supervisor endpoint through `POST /serving-endpoints/{endpoint}/invocations` (`agent_bricks_client.py`). Agent Bricks, Knowledge Assistant, and the UC KPI function must run under the invoking user's identity. +When `DOCINTEL_OBO_REQUIRED=true`, the app builds a `WorkspaceClient(token=...)` from each user's `x-forwarded-access-token` header (`app.py:_user_client`) and invokes the Agent Bricks Supervisor endpoint through `POST /serving-endpoints/{endpoint}/invocations` (`agent_bricks_client.py`). Agent Bricks, Knowledge Assistant, and the UC KPI function run under the invoking user's identity. + +When `DOCINTEL_OBO_REQUIRED=false`, the app uses its App service principal client. This is for demo workspaces that do not have Databricks Apps user-token passthrough enabled. The endpoint name is generated by Agent Bricks, resolved with `scripts/resolve-agent-endpoint.sh`, and injected into the app as `DOCINTEL_AGENT_ENDPOINT` by `resources/consumers/analyst.app.yml`. -`user_api_scopes` is declared in `resources/consumers/analyst.app.yml` (`serving.serving-endpoints`, `sql`, `iam.access-control:read`, `iam.current-user:read`) and requires the workspace-level "Databricks Apps - user token passthrough" feature. Deployment is invalid if these scopes are not granted. +`user_api_scopes` is declared only on the prod target in `databricks.yml` (`serving.serving-endpoints`, `sql`, `iam.access-control:read`, `iam.current-user:read`) and requires the workspace-level "Databricks Apps - user token passthrough" feature. Demo leaves scopes unset and grants the App service principal `CAN_QUERY` on the generated Supervisor endpoint after deploy. **Streamlit gotcha** (per the [Databricks Apps runtime docs](https://docs.databricks.com/aws/en/dev-tools/databricks-apps/app-runtime)): the OBO token is captured at the initial HTTP request; the connection then upgrades to WebSocket and the token never refreshes. If a user's UC permissions change mid-session, ask them to reload the page. diff --git a/app/app.py b/app/app.py index 0137e03..b13e592 100644 --- a/app/app.py +++ b/app/app.py @@ -14,12 +14,18 @@ from databricks.sdk import WorkspaceClient from databricks.sdk.config import Config -from app.agent_bricks_client import invoke_agent_endpoint -from app.agent_bricks_response import normalise_agent_response -from app import lakebase_client +try: + from app.agent_bricks_client import invoke_agent_endpoint + from app.agent_bricks_response import normalise_agent_response + from app import lakebase_client +except ImportError: + from agent_bricks_client import invoke_agent_endpoint + from agent_bricks_response import normalise_agent_response + import lakebase_client AGENT_ENDPOINT = os.environ["DOCINTEL_AGENT_ENDPOINT"] # set by resources/consumers/analyst.app.yml +OBO_REQUIRED = os.environ.get("DOCINTEL_OBO_REQUIRED", "true").lower() == "true" @st.cache_resource(ttl=3600) @@ -41,12 +47,15 @@ def _user_client(token: str) -> WorkspaceClient: def _agent_client() -> WorkspaceClient: token = st.context.headers.get("x-forwarded-access-token") + if token: + return _user_client(token) if not token: + if not OBO_REQUIRED: + return WorkspaceClient() raise RuntimeError( "Databricks Apps user-token passthrough is required; no " "x-forwarded-access-token header was present." ) - return _user_client(token) def _user_email() -> str: diff --git a/app/app.yaml b/app/app.yaml index 3f5537c..ffd1350 100644 --- a/app/app.yaml +++ b/app/app.yaml @@ -4,13 +4,9 @@ # injected port/host and disable Streamlit CORS/XSRF behind the Apps proxy. command: - - streamlit - - run - - app/app.py - - --server.port - - "${DATABRICKS_APP_PORT:-8000}" - - --server.address - - "0.0.0.0" + - sh + - -c + - exec streamlit run app.py --server.port "${DATABRICKS_APP_PORT:-8000}" --server.address 0.0.0.0 env: # Disable Streamlit's CORS + XSRF self-checks. The Apps reverse-proxy origin diff --git a/app/lakebase_client.py b/app/lakebase_client.py index caba5e5..38a8855 100644 --- a/app/lakebase_client.py +++ b/app/lakebase_client.py @@ -2,8 +2,9 @@ Persists conversation history, query logs, and feedback per the contracts in `specs/001-doc-intel-10k/contracts/`. The Databricks App database resource -binding exposes standard Postgres env vars (PGHOST, PGPORT, PGUSER, -PGPASSWORD, PGDATABASE). +binding exposes Postgres connection env vars (PGHOST, PGPORT, PGUSER, +PGDATABASE, PGSSLMODE). Lakebase OAuth passwords are minted on demand with the +Databricks SDK. Databricks Apps + Lakebase docs (https://docs.databricks.com/aws/en/oltp/) — initialize schema at @@ -24,6 +25,7 @@ from typing import Iterator import psycopg +from databricks.sdk import WorkspaceClient _log = logging.getLogger(__name__) @@ -63,22 +65,38 @@ def _conn() -> Iterator[psycopg.Connection]: conninfo = dsn kwargs = {} else: - required = ("PGHOST", "PGPORT", "PGUSER", "PGPASSWORD", "PGDATABASE") + required = ("PGHOST", "PGPORT", "PGUSER", "PGDATABASE") missing = [name for name in required if not os.environ.get(name)] if missing: raise RuntimeError(f"Lakebase binding missing Postgres env vars: {', '.join(missing)}") + password = os.environ.get("PGPASSWORD") or _generate_lakebase_password() conninfo = "" kwargs = { "host": os.environ["PGHOST"], "port": os.environ["PGPORT"], "user": os.environ["PGUSER"], - "password": os.environ["PGPASSWORD"], + "password": password, "dbname": os.environ["PGDATABASE"], + "sslmode": os.environ.get("PGSSLMODE", "require"), } with psycopg.connect(conninfo, autocommit=True, **kwargs) as c: yield c +def _generate_lakebase_password() -> str: + instance_name = os.environ.get("DOCINTEL_LAKEBASE_INSTANCE") or os.environ.get("PGDATABASE") + if not instance_name: + raise RuntimeError("Lakebase OAuth credential requires DOCINTEL_LAKEBASE_INSTANCE or PGDATABASE") + credential = WorkspaceClient().database.generate_database_credential( + request_id=str(uuid.uuid4()), + instance_names=[instance_name], + ) + token = getattr(credential, "token", None) + if not token: + raise RuntimeError("Lakebase OAuth credential response did not include a token") + return token + + def init_schema() -> None: """Idempotent CREATE TABLE IF NOT EXISTS. Logs the connected role so deployed-vs-local identity divergence is debuggable from app logs. diff --git a/databricks.yml b/databricks.yml index 724ba85..badb9a2 100644 --- a/databricks.yml +++ b/databricks.yml @@ -39,6 +39,9 @@ variables: agent_endpoint_name: description: Agent Bricks Supervisor serving endpoint name resolved by agent.document_intelligence_agent default: UNSET_AGENT_BRICKS_ENDPOINT + app_obo_required: + description: Whether the Databricks App requires user-token passthrough for Agent Bricks calls + default: "true" targets: demo: @@ -53,6 +56,7 @@ targets: catalog: workspace schema: docintel_10k_demo lakebase_instance: docintel-demo-state-v1 + app_obo_required: "false" resources: pipelines: doc_intel_pipeline: @@ -78,3 +82,12 @@ targets: schema: docintel_10k lakebase_instance: docintel-prod-state lakebase_stopped: false + app_obo_required: "true" + resources: + apps: + analyst_app: + user_api_scopes: + - serving.serving-endpoints + - sql + - iam.access-control:read + - iam.current-user:read diff --git a/docs/runbook.md b/docs/runbook.md index 7c04d43..44c972a 100644 --- a/docs/runbook.md +++ b/docs/runbook.md @@ -87,12 +87,12 @@ Metric key names can vary across MLflow/databricks-agents versions. The eval run | Lakebase tables not writable from deployed App | Local-dev `streamlit run` initialised schema under user identity, not App SP | Connect as App SP and `DROP TABLE feedback, query_logs, conversation_history`; next App run re-creates them under SP. See `app/README.md` | | CLEARS Latency axis fails | Agent Bricks orchestration or Knowledge Assistant source is too broad | Narrow the Knowledge Assistant source, tune Supervisor instructions, or reduce structured-tool fan-out | | Citation chips render but filenames show `source` | Knowledge Assistant footnote format changed or omitted filename markers | Capture the raw Agent Bricks payload and compare it with `app/agent_bricks_response.py`'s markdown-footnote parser | -| App errors connecting to Lakebase | Database resource binding missing Postgres env vars | Check the `docintel-lakebase` resource binding and `PGHOST`/`PGPORT`/`PGUSER`/`PGPASSWORD`/`PGDATABASE` in the App runtime | +| App errors connecting to Lakebase | Database resource binding missing connection fields, or OAuth credential minting failed | Check the `docintel-lakebase` resource binding plus `PGHOST`/`PGPORT`/`PGUSER`/`PGDATABASE`/`DOCINTEL_LAKEBASE_INSTANCE` in the App runtime. `PGPASSWORD` is minted at connection time by `app/lakebase_client.py` | ## Verifying end-to-end OBO 1. **Workspace admin** enables the "Databricks Apps - user token passthrough" feature in workspace settings. -2. Confirm the required scopes are declared in `resources/consumers/analyst.app.yml`: +2. Confirm the required scopes are declared on the prod target in `databricks.yml`: ```yaml user_api_scopes: - serving.serving-endpoints # invoke Agent Bricks endpoint as user @@ -100,6 +100,9 @@ Metric key names can vary across MLflow/databricks-agents versions. The eval run - iam.access-control:read # default - iam.current-user:read # default ``` + +Demo uses `app_obo_required=false` unless overridden; the bootstrap grants the App service principal `CAN_QUERY` on the generated Supervisor endpoint. + 3. Redeploy: ```bash AGENT_ENDPOINT_NAME="$(./scripts/resolve-agent-endpoint.sh demo)" diff --git a/jobs/index_refresh/sync_index.py b/jobs/index_refresh/sync_index.py index 34e0c45..7310889 100644 --- a/jobs/index_refresh/sync_index.py +++ b/jobs/index_refresh/sync_index.py @@ -10,7 +10,6 @@ import argparse from datetime import timedelta import logging -import sys import time from databricks.sdk import WorkspaceClient @@ -35,7 +34,33 @@ def _wait_index_ready(w: WorkspaceClient, index_name: str, *, timeout_seconds: i time.sleep(15) -def main() -> int: +def _sync_index_when_ready(w: WorkspaceClient, index_name: str, *, timeout_seconds: int = 1200) -> None: + deadline = time.time() + timeout_seconds + next_log = 60 + started = time.time() + while True: + try: + w.vector_search_indexes.sync_index(index_name) + return + except Exception as exc: + message = str(exc) + transient = "not ready to sync yet" in message or "needs to be in one of the following states" in message + if not transient or time.time() >= deadline: + raise + elapsed = int(time.time() - started) + if elapsed >= next_log: + log_message = message.splitlines()[0] if message else type(exc).__name__ + logging.getLogger("vs-sync").info( + "index %s is not syncable yet after %ss: %s", + index_name, + elapsed, + log_message, + ) + next_log += 60 + time.sleep(15) + + +def main() -> None: p = argparse.ArgumentParser() p.add_argument("--endpoint", required=True) p.add_argument("--index", required=True) @@ -74,13 +99,13 @@ def main() -> int: ) _wait_index_ready(w, args.index) log.info("index created and initial sync complete") - return 0 + return log.info("index %s exists; triggering sync", args.index) - w.vector_search_indexes.sync_index(args.index) + _wait_index_ready(w, args.index) + _sync_index_when_ready(w, args.index) log.info("sync triggered") - return 0 if __name__ == "__main__": - sys.exit(main()) + main() diff --git a/resources/consumers/analyst.app.yml b/resources/consumers/analyst.app.yml index ba5a27f..759e620 100644 --- a/resources/consumers/analyst.app.yml +++ b/resources/consumers/analyst.app.yml @@ -9,6 +9,10 @@ resources: env: - name: DOCINTEL_AGENT_ENDPOINT value: ${var.agent_endpoint_name} + - name: DOCINTEL_OBO_REQUIRED + value: ${var.app_obo_required} + - name: DOCINTEL_LAKEBASE_INSTANCE + value: ${var.lakebase_instance} # Databricks Apps auto-grants Lakebase permissions to the App SP on # deploy — see https://docs.databricks.com/aws/en/dev-tools/databricks-apps/access-data. @@ -21,11 +25,5 @@ resources: instance_name: ${var.lakebase_instance} permission: CAN_CONNECT_AND_CREATE - # Mandatory OBO scopes. Requires the workspace-level "Databricks Apps - - # user token passthrough" feature. - # Docs: https://docs.databricks.com/aws/en/dev-tools/databricks-apps/iam-auth - user_api_scopes: - - serving.serving-endpoints - - sql - - iam.access-control:read - - iam.current-user:read + # Prod declares user_api_scopes in databricks.yml. Demo leaves them unset + # so workspaces without Apps user-token passthrough can use App-SP calls. diff --git a/scripts/bootstrap-demo.sh b/scripts/bootstrap-demo.sh index e278aaa..0c08400 100755 --- a/scripts/bootstrap-demo.sh +++ b/scripts/bootstrap-demo.sh @@ -153,6 +153,15 @@ with open('databricks.yml') as f: d = yaml.safe_load(f) print(d['targets']['$TARGET']['variables']['lakebase_instance']) " 2>/dev/null || echo "") +APP_OBO_REQUIRED=$("$PYTHON" -c " +import yaml +with open('databricks.yml') as f: + d = yaml.safe_load(f) +default = d.get('variables', {}).get('app_obo_required', {}).get('default', 'true') +value = d.get('targets', {}).get('$TARGET', {}).get('variables', {}).get('app_obo_required', default) +print(str(value).lower()) +" 2>/dev/null || echo "true") + if [[ -n "$LAKEBASE_NAME" ]]; then if instances=$(databricks api get /api/2.0/database/instances --output json 2>/dev/null); then conflict=$("$PYTHON" -c " @@ -195,6 +204,52 @@ for i in d.get('database_instances', []): done } +grant_app_sp_endpoint_query() { + local app_json="$1" + local endpoint_json endpoint_id principals principal grant_json + endpoint_json=$(databricks serving-endpoints get "$AGENT_ENDPOINT_NAME" --output json) + endpoint_id=$(printf '%s' "$endpoint_json" | "$PYTHON" -c " +import json, sys +endpoint = json.load(sys.stdin) +print(endpoint.get('id') or endpoint.get('name') or '$AGENT_ENDPOINT_NAME') +") + principals=() + while IFS= read -r principal; do + [[ -n "$principal" ]] && principals+=("$principal") + done < <(printf '%s' "$app_json" | "$PYTHON" -c " +import json, sys +app = json.load(sys.stdin) +seen = set() +for key in ('service_principal_client_id', 'service_principal_name', 'service_principal_id'): + value = app.get(key) + if value is None: + continue + value = str(value) + if value and value not in seen: + seen.add(value) + print(value) +") + if (( ${#principals[@]} == 0 )); then + die "app service principal was not returned by Databricks Apps API" + fi + for principal in "${principals[@]}"; do + grant_json=$("$PYTHON" -c " +import json, sys +print(json.dumps({ + 'access_control_list': [{ + 'service_principal_name': sys.argv[1], + 'permission_level': 'CAN_QUERY', + }] +})) +" "$principal") + if databricks permissions update serving-endpoints "$endpoint_id" --json "$grant_json" >/dev/null 2>&1; then + log " granted CAN_QUERY on $AGENT_ENDPOINT_NAME to App SP $principal" + return 0 + fi + done + die "failed to grant CAN_QUERY on $AGENT_ENDPOINT_NAME to the App service principal" +} + upload_samples() { log " uploading synthetic samples to $VOLUME_PATH" shopt -s nullglob @@ -308,9 +363,10 @@ databricks api patch \ --json "{\"changes\":[{\"principal\":\"${ANALYST_GROUP}\",\"add\":[\"USE_SCHEMA\",\"SELECT\",\"EXECUTE\"]}]}" \ >/dev/null 2>&1 || log " warn: schema grants failed (may already be applied; UC dedupes)" -log " verifying OBO scopes on $APP_NAME" +log " verifying app auth mode on $APP_NAME" if app_state=$(databricks apps get "$APP_NAME" --output json 2>/dev/null); then - "$PYTHON" -c " + if [[ "$APP_OBO_REQUIRED" == "true" ]]; then + "$PYTHON" -c " import json app = json.loads('''$app_state''') scopes = set(app.get('user_api_scopes') or []) @@ -320,6 +376,17 @@ if missing: raise SystemExit(f'OBO scopes missing: {sorted(missing)} (got {sorted(scopes)})') print(f' OBO scopes intact: {sorted(scopes)}') " || die "OBO scopes missing after deploy" + else + "$PYTHON" -c " +import json +app = json.loads('''$app_state''') +scopes = app.get('user_api_scopes') +if scopes: + raise SystemExit(f'demo SP-fallback expected no user_api_scopes, got {scopes}') +print(' OBO disabled for demo; user_api_scopes unset') +" + grant_app_sp_endpoint_query "$app_state" + fi else die "unable to read app state for OBO verification" fi From 1dfddf1feb9f9bf58ac52948c4b0006197d8c4ac Mon Sep 17 00:00:00 2001 From: Sathish Krishnan <10681383+SatyKrish@users.noreply.github.com> Date: Sun, 26 Apr 2026 21:37:29 -0400 Subject: [PATCH 4/7] Align docs with Agent Bricks target state --- .github/workflows/deploy.yml | 2 +- PRODUCTION_READINESS.md | 7 ++-- README.md | 12 +++---- SECURITY.md | 2 +- VALIDATION.md | 31 +++++++++++------- app/README.md | 5 +-- docs/design.md | 17 ++++++---- docs/runbook.md | 19 +++++------ resources/consumers/analyst.app.yml | 5 +-- resources/consumers/lakebase_catalog.yml | 2 +- resources/foundation/filings_index.yml | 2 +- scripts/bootstrap-demo.sh | 2 +- .../contracts/agent-request.json | 2 +- specs/001-doc-intel-10k/data-model.md | 6 ++-- specs/001-doc-intel-10k/plan.md | 10 +++--- specs/001-doc-intel-10k/quickstart.md | 8 ++--- specs/001-doc-intel-10k/spec.md | 14 ++++---- specs/001-doc-intel-10k/tasks.md | 32 +++++++++---------- src/dashboards/usage.lvdash.json | 2 +- 19 files changed, 97 insertions(+), 83 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 37d2686..835dbe2 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -142,7 +142,7 @@ jobs: # behavior). Fail loudly if required user scopes are missing. python -c "import json; app=json.load(open('/tmp/app.json')); scopes=set(app.get('user_api_scopes') or []); required={'serving.serving-endpoints','sql'}; missing=required-scopes; assert not missing, f'OBO scopes missing: {sorted(missing)} (got {sorted(scopes)})'" else - python -c "import json; app=json.load(open('/tmp/app.json')); scopes=app.get('user_api_scopes'); assert not scopes, f'demo SP-fallback expected no user_api_scopes, got {scopes}'" + python -c "import json; app=json.load(open('/tmp/app.json')); scopes=app.get('user_api_scopes'); assert not scopes, f'demo App-SP mode expected no user_api_scopes, got {scopes}'" endpoint_id="$(databricks serving-endpoints get "$AGENT_ENDPOINT_NAME" --output json | python -c "import json, sys; e=json.load(sys.stdin); print(e.get('id') or e.get('name'))")" python -c "import json; app=json.load(open('/tmp/app.json')); vals=[str(app.get(k)) for k in ('service_principal_client_id','service_principal_name','service_principal_id') if app.get(k) is not None]; print('\n'.join(dict.fromkeys(v for v in vals if v)))" > /tmp/app-sp-candidates.txt granted=0 diff --git a/PRODUCTION_READINESS.md b/PRODUCTION_READINESS.md index 293432e..27b0efd 100644 --- a/PRODUCTION_READINESS.md +++ b/PRODUCTION_READINESS.md @@ -10,7 +10,7 @@ This project is open-sourced as a Databricks reference implementation. Treat it | Pilot-ready | Real filings exercise document variability and cost/latency | Reference-ready plus a reviewed EDGAR pilot corpus | | Production-ready | Analysts can use it under governed identity and SLOs | Pilot-ready plus end-to-end OBO, dashboards, alerts, rollback, and runbook evidence | -Current demo status as of 2026-04-26: Agent Bricks bootstrap and direct Supervisor endpoint smoke passed, but the project is not reference-ready yet. The target workspace did not have Databricks Apps user-token passthrough enabled, and the latest synthetic CLEARS run failed the configured quality/latency gate. See [`VALIDATION.md`](./VALIDATION.md#latest-demo-snapshot). +Current demo status as of 2026-04-26: Agent Bricks bootstrap, Databricks App deploy, direct Supervisor endpoint smoke, Lakebase OAuth credential handling, and Vector Search index-refresh smoke passed. The project is not reference-ready yet because the latest synthetic CLEARS run failed the configured quality/latency gate. Prod readiness still requires user-token passthrough/OBO audit evidence. See [`VALIDATION.md`](./VALIDATION.md#latest-demo-snapshot). ## Reference-Ready Checklist @@ -19,8 +19,7 @@ Current demo status as of 2026-04-26: Agent Bricks bootstrap and direct Supervis - Synthetic PDFs in `samples/` produce at least ACME/BETA/GAMMA KPI rows. - Vector Search index sync completes and the Agent Bricks Supervisor endpoint answers a smoke question with citations. - `python evals/clears_eval.py --endpoint "$(./scripts/resolve-agent-endpoint.sh demo)" --dataset evals/dataset.jsonl` passes. -- Databricks Apps user-token passthrough is enabled in the workspace. -- App starts via `databricks bundle run -t demo --var "agent_endpoint_name=$(./scripts/resolve-agent-endpoint.sh demo)" analyst_app`. +- App starts via `databricks bundle run -t demo --var "agent_endpoint_name=$(./scripts/resolve-agent-endpoint.sh demo)" analyst_app` in the configured demo auth mode. ## Pilot-Ready Checklist @@ -34,7 +33,7 @@ Current demo status as of 2026-04-26: Agent Bricks bootstrap and direct Supervis ## Production-Ready Checklist - Databricks Apps user-token passthrough is enabled in the workspace. -- `resources/consumers/analyst.app.yml:user_api_scopes` is declared and survives `bundle run`. +- Prod target `user_api_scopes` in `databricks.yml` are declared and survive `bundle run`. - Audit logs prove app requests, Agent Bricks, Knowledge Assistant, Vector Search, and structured KPI SQL calls execute under the invoking user where required. - Service principal `run_as` is configured for prod via `--var service_principal_id=`. - Analyst group grants include `USE_CATALOG`, `USE_SCHEMA`, `SELECT`, `EXECUTE`, `READ_VOLUME`, and `WRITE_VOLUME` as appropriate. diff --git a/README.md b/README.md index 709e35d..5e9721c 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,7 @@ For architecture and deploy ordering, see [**`docs/design.md`**](./docs/design.m Full checklists in [`PRODUCTION_READINESS.md`](./PRODUCTION_READINESS.md). -> Latest demo status, 2026-04-26: Agent Bricks bootstrap and direct Supervisor endpoint smoke passed. Reference-ready remains blocked by Databricks Apps user-token passthrough and CLEARS thresholds. See [`VALIDATION.md`](./VALIDATION.md). +> Latest demo status, 2026-04-26: Agent Bricks bootstrap, Databricks App deploy, direct Supervisor endpoint smoke, and Vector Search index-refresh smoke passed. Reference-ready remains blocked by CLEARS thresholds. Prod readiness still requires user-token passthrough/OBO evidence. See [`VALIDATION.md`](./VALIDATION.md). --- @@ -81,7 +81,7 @@ Full checklists in [`PRODUCTION_READINESS.md`](./PRODUCTION_READINESS.md). Databricks creation path: [Create an AI agent](https://docs.databricks.com/aws/en/generative-ai/agent-framework/create-agent) → Knowledge Assistant for document Q&A, with Supervisor Agent coordinating hosted tools. -The production agent path is: +The Agent Bricks path is: 1. `jobs/index_refresh/sync_index.py` creates/syncs the Mosaic AI Vector Search Delta-Sync index over `gold_filing_sections_indexable`. 2. `agent/document_intelligence_agent.py` creates or updates the Agent Bricks Knowledge Assistant with that Vector Search index as its knowledge source. The source uses `summary` as the searchable text column and `filename` as the document URI column. @@ -89,7 +89,7 @@ The production agent path is: 4. `agent/document_intelligence_agent.py` creates or updates the Agent Bricks Supervisor Agent with two tools: the Knowledge Assistant for cited document Q&A and the UC function for deterministic KPI lookups. 5. Agent Bricks generates concrete serving endpoint names. Resolve the live Supervisor endpoint with `./scripts/resolve-agent-endpoint.sh `. 6. The Databricks App receives the resolved endpoint through the `agent_endpoint_name` bundle variable as `DOCINTEL_AGENT_ENDPOINT`. -7. The app invokes `POST /serving-endpoints/{endpoint}/invocations` directly with the user's OBO token. `WorkspaceClient.serving_endpoints.query()` is not used for Agent Bricks invocation because validation showed it did not preserve the needed Agent Bricks response shape. +7. The app invokes `POST /serving-endpoints/{endpoint}/invocations` directly. Prod uses each user's OBO token. Demo uses the App service principal when `DOCINTEL_OBO_REQUIRED=false`. `WorkspaceClient.serving_endpoints.query()` is not used for Agent Bricks invocation because validation showed it did not preserve the needed Agent Bricks response shape. 8. Knowledge Assistant citations currently arrive as markdown footnotes in Agent Bricks output messages. `app/agent_bricks_response.py` normalizes the final answer and extracts citation chips from those footnotes. Useful Databricks references: @@ -136,7 +136,7 @@ You need a workspace with **all** of the following enabled: **Required for production identity:** -- Databricks Apps **user token passthrough** (workspace admin setting). The app must not fall back to broad service-principal reads — see [`SECURITY.md`](./SECURITY.md). +- Databricks Apps **user token passthrough** (workspace admin setting). Prod requires user-scoped Agent Bricks calls — see [`SECURITY.md`](./SECURITY.md). ### Free trial signup @@ -383,7 +383,7 @@ This is a production-oriented reference implementation with conservative scale d | Compute | CPU only | constitution add'l constraints | | Languages | English filings | implicit (foundation model) | | Eval set size | 30 questions | spec clarification | -| OBO end-to-end | Requires workspace-level `Databricks Apps - user token passthrough` feature | [`SECURITY.md`](./SECURITY.md) | +| Prod OBO end-to-end | Requires workspace-level `Databricks Apps - user token passthrough` feature | [`SECURITY.md`](./SECURITY.md) | Latency SLOs: P95 ≤ 8s for single-filing, ≤ 20s for cross-company. End-to-end pipeline ≤ 10 min P95 on a 30 MB PDF. @@ -397,7 +397,7 @@ See [`CONTRIBUTING.md`](./CONTRIBUTING.md) for local setup, the spec-kit workflo ## Security -See [`SECURITY.md`](./SECURITY.md) for the mandatory end-to-end OBO identity model, required UC grants, secrets-handling guidance, and how to report security issues in a fork or deployment. +See [`SECURITY.md`](./SECURITY.md) for the target-specific identity model, required UC grants, secrets-handling guidance, and how to report security issues in a fork or deployment. ## License diff --git a/SECURITY.md b/SECURITY.md index 1a086f5..2af2350 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,7 +2,7 @@ ## Supported Security Posture -This reference is designed for Databricks workspaces using Unity Catalog, Agent Bricks, AI Gateway, Databricks Apps resource bindings, and end-to-end on-behalf-of (OBO) user identity in prod. Demo can run with `app_obo_required=false` when the workspace does not have Databricks Apps user-token passthrough enabled; in that mode the App service principal invokes Agent Bricks and is granted `CAN_QUERY` after deploy. +This reference is designed for Databricks workspaces using Unity Catalog, Agent Bricks, AI Gateway, Databricks Apps resource bindings, and end-to-end on-behalf-of (OBO) user identity in prod. Demo can run with `app_obo_required=false` when the workspace does not have Databricks Apps user-token passthrough enabled. In that mode the App service principal invokes Agent Bricks and is granted `CAN_QUERY` after deploy. ## Enabling End-To-End OBO diff --git a/VALIDATION.md b/VALIDATION.md index 87d46fa..96f6e2d 100644 --- a/VALIDATION.md +++ b/VALIDATION.md @@ -42,10 +42,10 @@ Expected outcomes: - Agent Bricks Knowledge Assistant and Supervisor Agent are created or updated. - Consumer resources deploy cleanly. - App config is applied with `bundle run analyst_app`, including `DOCINTEL_AGENT_ENDPOINT` set from the generated Supervisor endpoint name. -- Bootstrap verifies mandatory OBO scopes. +- Bootstrap verifies the target auth mode. Demo leaves `user_api_scopes` unset and grants the App service principal `CAN_QUERY`; prod requires OBO scopes. - Smoke query reaches the Agent Bricks supervisor endpoint. -If `bundle run analyst_app` fails with `Databricks Apps - user token passthrough feature is not enabled`, the Agent Bricks path is still deployable but the app is not production-valid in that workspace. Enable the workspace/org feature and rerun bootstrap. Do not bypass OBO. +If a prod app deploy fails with `Databricks Apps - user token passthrough feature is not enabled`, enable the workspace/org feature and rerun. Demo uses `app_obo_required=false` by default for workspaces where user-token passthrough is not enabled. ## Data Checks @@ -81,19 +81,21 @@ Expected: ## App Checks - Open `doc-intel-analyst-demo`. -- Ask: `What was ACME's revenue in fiscal year 2024?` -- Confirm the response has citations and the turn is written to Lakebase. +- Ask: `What were the top 3 risk factors disclosed by ACME in their FY24 10-K?` +- Confirm the response has citation chips and the turn is written to Lakebase. +- Ask: `What was ACME's revenue in fiscal year 2024?` to verify the structured KPI tool path. - Submit thumbs feedback and confirm a feedback row is written. ## App Auth Verification - Demo: confirm `user_api_scopes` is unset and `DOCINTEL_OBO_REQUIRED=false`. - Prod: confirm `user_api_scopes` is present and `DOCINTEL_OBO_REQUIRED=true`. -- Run: +- Run for the target being verified: ```bash - AGENT_ENDPOINT_NAME="$(./scripts/resolve-agent-endpoint.sh demo)" - databricks bundle deploy -t demo --var "agent_endpoint_name=${AGENT_ENDPOINT_NAME}" - databricks bundle run -t demo --var "agent_endpoint_name=${AGENT_ENDPOINT_NAME}" analyst_app + TARGET=demo + AGENT_ENDPOINT_NAME="$(./scripts/resolve-agent-endpoint.sh "$TARGET")" + databricks bundle deploy -t "$TARGET" --var "agent_endpoint_name=${AGENT_ENDPOINT_NAME}" + databricks bundle run -t "$TARGET" --var "agent_endpoint_name=${AGENT_ENDPOINT_NAME}" analyst_app ``` - Confirm bootstrap or CI verifies the target auth mode. Demo grants the App SP endpoint access; prod verifies `serving.serving-endpoints` and `sql` scopes. - For prod, check audit logs for user-scoped downstream access through Agent Bricks, Knowledge Assistant, and the structured KPI SQL function. @@ -108,8 +110,15 @@ As of 2026-04-26, the demo workspace evidence is: - Knowledge Assistant display name: `doc-intel-knowledge-demo` - Supervisor display name: `doc-intel-supervisor-demo` - UC function: `workspace.docintel_10k_demo.lookup_10k_kpis` -- Direct Supervisor endpoint smoke passed. The ACME FY2024 revenue question returned `$94.2 billion` with a parsed `ACME_10K_2024.pdf` citation. -- Databricks App deploy was blocked by workspace configuration: Databricks Apps user-token passthrough was not enabled. +- Direct Supervisor endpoint smoke passed. The ACME FY2024 revenue question returned `$94.2 billion` and referenced `ACME_10K_2024.pdf` through the structured KPI path. +- Databricks App deploy succeeded in demo App-SP mode: + - App: `doc-intel-analyst-demo` + - Endpoint: `mas-dc6aba10-endpoint` + - `DOCINTEL_OBO_REQUIRED=false` + - `user_api_scopes` unset + - App service principal granted `CAN_QUERY` on the generated Supervisor endpoint. +- Lakebase OAuth credential handling was validated without `PGPASSWORD`; the deployed app code mints the database password at connection time. +- Vector Search `index_refresh` job rerun terminated `SUCCESS`. - CLEARS live eval completed but failed the configured gate: - MLflow run ID: `772e902cab92459f9bf569296fc5f801` - correctness: `0.323` @@ -119,4 +128,4 @@ As of 2026-04-26, the demo workspace evidence is: - execution: `1.000` - latency p95: `31711ms` -Status: Agent Bricks deployment mechanics and direct serving smoke passed. Reference-ready quality and app-level OBO readiness remain open. +Status: Agent Bricks deployment mechanics, demo App deploy, Lakebase OAuth credential handling, and direct serving smoke passed. Reference-ready quality remains open until CLEARS passes. Prod OBO readiness remains open until validated in a workspace with user-token passthrough enabled. diff --git a/app/README.md b/app/README.md index 7515c72..268055b 100644 --- a/app/README.md +++ b/app/README.md @@ -6,7 +6,7 @@ Source for the Databricks App `doc-intel-analyst-${target}`. Streamlit chat UI o | File | Purpose | |---|---| -| `app.py` | Streamlit entry point — chat loop, OBO client, citation rendering. | +| `app.py` | Streamlit entry point — chat loop, target auth client, citation rendering. | | `app.yaml` | Databricks Apps runtime config (port, address, CORS/XSRF env vars). | | `lakebase_client.py` | psycopg-based persistence to Lakebase Postgres. | | `requirements.txt` | Python deps installed by the Apps runtime. | @@ -43,10 +43,11 @@ export PGHOST="$(databricks database get-database-instance "${DOCINTEL_LAKEBASE_ --output json | jq -r '.read_write_dns')" export DOCINTEL_AGENT_ENDPOINT="$(./scripts/resolve-agent-endpoint.sh demo)" +export DOCINTEL_OBO_REQUIRED=false streamlit run app/app.py ``` -Local runs do not have the Databricks Apps `x-forwarded-access-token` header, so they cannot validate the Agent Bricks OBO path. Use the deployed App for agent validation. +Local runs do not have the Databricks Apps `x-forwarded-access-token` header, so they cannot validate the Agent Bricks OBO path. Use a deployed OBO-enabled target for prod identity validation. If you accidentally run Lakebase schema initialization with user creds (`DATABRICKS_CLIENT_ID`/`SECRET` unset), `lakebase_client.init_schema()` logs a warning identifying the mismatch. The tables get created under your user account, not the App SP, and the deployed App will lose write access. Drop the user-owned tables and re-init under the App SP to recover: diff --git a/docs/design.md b/docs/design.md index 6793be0..0b2c5e7 100644 --- a/docs/design.md +++ b/docs/design.md @@ -133,7 +133,7 @@ Repository code is limited to deterministic tool glue, app UI, evals, and deploy - `agent/document_intelligence_agent.py` creates or updates the UC SQL function `..lookup_10k_kpis`. - `doc-intel-supervisor-${target}` is the Supervisor Agent. Its tools are the Knowledge Assistant and the UC SQL KPI function. Supervisor Agent owns tool routing. - Agent Bricks generates concrete serving endpoint names for Knowledge Assistant and Supervisor Agent. The repo resolves the live Supervisor endpoint with `scripts/resolve-agent-endpoint.sh` and passes it into DAB as `agent_endpoint_name`. -- Serving endpoint permissions are granted by endpoint ID after the generated endpoint is ready. The Databricks App does not bind to the endpoint as a resource; it invokes the resolved endpoint with each user's OBO token. +- Serving endpoint permissions are granted by endpoint ID after the generated endpoint is ready. The Databricks App does not bind to the endpoint as a resource; it invokes the resolved endpoint directly. Prod uses each user's OBO token. Demo uses the App service principal when `DOCINTEL_OBO_REQUIRED=false`. - Agent Bricks responses use an OpenAI Responses-style `output` message sequence in current validation. The app displays the last output text group as the answer. Knowledge Assistant citations have been observed as markdown footnotes in intermediate messages, so `app/agent_bricks_response.py` normalizes those footnotes into citation chips. ### Runtime stack @@ -166,15 +166,18 @@ Repository code is limited to deterministic tool glue, app UI, evals, and deploy │ │ │ at row-by-row) │ └────────────────────────┘ └────────────────────────┘ - OBO (user identity end-to-end, mandatory): - ────────────────────────────── - App reads `x-forwarded-access-token` from the request and invokes the + Target auth modes: + ───────────────── + Prod reads `x-forwarded-access-token` from the request and invokes the Agent Bricks endpoint with the user's identity. AI Gateway and Unity Catalog enforce identity, permissions, audit, and routing across the agent, model, tools, and data. User token passthrough is a hard - prerequisite for production. If the workspace cannot provide end-to-end - OBO, deployment must fail rather than silently falling back to a service - principal identity. + prerequisite for production. + + Demo can set `DOCINTEL_OBO_REQUIRED=false`; the App service principal then + invokes the generated Supervisor endpoint and receives `CAN_QUERY` after + deploy. This is for development workspaces without Apps user-token + passthrough, not for production. ``` **Why Postgres for state?** Delta tables are great for analytics but bad at "insert one tiny row per chat turn at high frequency." Lakebase is Databricks's managed Postgres — same governance, right tool for the job. diff --git a/docs/runbook.md b/docs/runbook.md index 44c972a..387686f 100644 --- a/docs/runbook.md +++ b/docs/runbook.md @@ -58,7 +58,7 @@ The app receives the generated endpoint as `DOCINTEL_AGENT_ENDPOINT`. ## Agent Bricks invocation and citations -The app and eval runner invoke the generated Supervisor endpoint through `POST /serving-endpoints/{endpoint}/invocations` with the user's OBO token. They do not use `WorkspaceClient.serving_endpoints.query()` for Agent Bricks calls because workspace validation showed that path did not preserve the needed Agent Bricks response shape. +The app and eval runner invoke the generated Supervisor endpoint through `POST /serving-endpoints/{endpoint}/invocations`. Prod uses the user's OBO token. Demo uses the App service principal when `DOCINTEL_OBO_REQUIRED=false`. They do not use `WorkspaceClient.serving_endpoints.query()` for Agent Bricks calls because workspace validation showed that path did not preserve the needed Agent Bricks response shape. Current Agent Bricks output is an OpenAI Responses-style `output` message sequence. `app/agent_bricks_response.py` displays the last output text group as the final answer. Knowledge Assistant citations were observed during 2026-04-26 validation as markdown footnotes in intermediate messages, such as `[^p1]: ... _ACME_10K_2024.pdf_`; the app extracts filenames from those footnotes for citation chips. If citation chips show only `source`, capture a live payload and grep for `[^` and `.pdf_` to confirm whether the Knowledge Assistant citation format changed. @@ -79,9 +79,9 @@ Metric key names can vary across MLflow/databricks-agents versions. The eval run | `bundle validate` fails on `ai_parse_document` | Workspace lacks AI Functions GA | Move SQL warehouse to a recent serverless channel | | Vector Search index sync stuck | Embedding endpoint not provisioned | Provision `databricks-bge-large-en` or override `var.embedding_model_endpoint_name` | | `DOCINTEL_AGENT_ENDPOINT` is `UNSET_AGENT_BRICKS_ENDPOINT` | Bundle deploy/run omitted the generated endpoint variable | Re-run with `--var "agent_endpoint_name=$(./scripts/resolve-agent-endpoint.sh demo)"` | -| Agent endpoint 401 | OBO not plumbed end-to-end | Verify `app/app.py:_user_client` reads `x-forwarded-access-token` and `resources/consumers/analyst.app.yml:user_api_scopes` includes `serving.serving-endpoints` and `sql` | -| App deploy fails with `Databricks Apps - user token passthrough feature is not enabled` | Workspace/org prerequisite missing | Enable the Databricks Apps user-token passthrough feature and rerun bootstrap | -| Agent answers ignore user UC permissions | OBO scopes wiped by `bundle run` (documented destructive-update behavior — see [Databricks Apps deploy docs](https://docs.databricks.com/aws/en/dev-tools/databricks-apps/deploy)) | Re-apply: `databricks apps update doc-intel-analyst-demo --user-api-scopes serving.serving-endpoints,sql,iam.access-control:read,iam.current-user:read` | +| Agent endpoint 401 | Target auth mode does not have endpoint access | Demo: verify bootstrap/CI granted the App SP `CAN_QUERY` on the generated endpoint. Prod: verify `x-forwarded-access-token` is present and target `user_api_scopes` include `serving.serving-endpoints` and `sql` | +| App deploy fails with `Databricks Apps - user token passthrough feature is not enabled` | Prod target requires a workspace/org prerequisite | Enable Databricks Apps user-token passthrough and rerun. Demo should keep `app_obo_required=false` unless validating OBO | +| Agent answers ignore user UC permissions in prod | OBO scopes wiped by `bundle run` (documented destructive-update behavior — see [Databricks Apps deploy docs](https://docs.databricks.com/aws/en/dev-tools/databricks-apps/deploy)) | Re-apply scopes to the target app: `databricks apps update --user-api-scopes serving.serving-endpoints,sql,iam.access-control:read,iam.current-user:read` | | Agent deployment cannot grant endpoint query permission | Permissions API was called with endpoint name instead of internal endpoint ID, or the generated endpoint is not ready | Use current `agent/document_intelligence_agent.py`; it waits for readiness and grants by serving endpoint ID | | Streamlit user sees stale UC permissions | OBO token captured at WebSocket open; never refreshes ([Databricks Apps runtime docs](https://docs.databricks.com/aws/en/dev-tools/databricks-apps/app-runtime)) | Reload the page after permission changes | | Lakebase tables not writable from deployed App | Local-dev `streamlit run` initialised schema under user identity, not App SP | Connect as App SP and `DROP TABLE feedback, query_logs, conversation_history`; next App run re-creates them under SP. See `app/README.md` | @@ -103,11 +103,12 @@ Metric key names can vary across MLflow/databricks-agents versions. The eval run Demo uses `app_obo_required=false` unless overridden; the bootstrap grants the App service principal `CAN_QUERY` on the generated Supervisor endpoint. -3. Redeploy: +3. Redeploy the OBO-enabled target: ```bash - AGENT_ENDPOINT_NAME="$(./scripts/resolve-agent-endpoint.sh demo)" - databricks bundle deploy -t demo --var "agent_endpoint_name=${AGENT_ENDPOINT_NAME}" - databricks bundle run -t demo --var "agent_endpoint_name=${AGENT_ENDPOINT_NAME}" analyst_app + TARGET=prod + AGENT_ENDPOINT_NAME="$(./scripts/resolve-agent-endpoint.sh "$TARGET")" + databricks bundle deploy -t "$TARGET" --var "agent_endpoint_name=${AGENT_ENDPOINT_NAME}" + databricks bundle run -t "$TARGET" --var "agent_endpoint_name=${AGENT_ENDPOINT_NAME}" analyst_app ``` 4. Verify: bootstrap scope checks assert required scopes. Visit the deployed app, ask a question, and confirm in audit logs that Agent Bricks, Knowledge Assistant, and structured KPI SQL calls run under the invoking user's identity. @@ -144,7 +145,7 @@ Latency p95: 31711 ms P2/P3 slices: unavailable in the current aggregate metric output ``` -Do not promote this as reference-ready until CLEARS passes and Databricks Apps user-token passthrough is enabled in the target workspace. +Do not promote this as reference-ready until CLEARS passes. Do not promote to prod until Databricks Apps user-token passthrough is enabled and OBO audit evidence is captured in the target workspace. ## Known deploy ordering gaps diff --git a/resources/consumers/analyst.app.yml b/resources/consumers/analyst.app.yml index 759e620..7dc1842 100644 --- a/resources/consumers/analyst.app.yml +++ b/resources/consumers/analyst.app.yml @@ -16,8 +16,9 @@ resources: # Databricks Apps auto-grants Lakebase permissions to the App SP on # deploy — see https://docs.databricks.com/aws/en/dev-tools/databricks-apps/access-data. - # Agent Bricks endpoint access is granted by agent/document_intelligence_agent.py - # because calls use OBO user identity. + # Agent Bricks endpoint access is granted outside this binding: + # agent/document_intelligence_agent.py grants analyst users/groups, and + # bootstrap/CI grants the App SP when demo runs with OBO disabled. resources: - name: docintel-lakebase database: diff --git a/resources/consumers/lakebase_catalog.yml b/resources/consumers/lakebase_catalog.yml index 07a5f63..42735b8 100644 --- a/resources/consumers/lakebase_catalog.yml +++ b/resources/consumers/lakebase_catalog.yml @@ -1,7 +1,7 @@ resources: database_catalogs: docintel_state_catalog: - name: ${var.lakebase_instance} + name: ${var.schema}_state database_instance_name: ${var.lakebase_instance} database_name: ${var.lakebase_instance} create_database_if_not_exists: true diff --git a/resources/foundation/filings_index.yml b/resources/foundation/filings_index.yml index 3865751..52b1864 100644 --- a/resources/foundation/filings_index.yml +++ b/resources/foundation/filings_index.yml @@ -5,7 +5,7 @@ resources: endpoint_type: STANDARD # Index `${var.catalog}.${var.schema}.filings_summary_idx` is created and synced by -# `jobs/index_refresh/sync_index.py` (see resources/jobs/index_refresh.job.yml). +# `jobs/index_refresh/sync_index.py` (see resources/consumers/index_refresh.job.yml). # Vector Search *indexes* are not a DAB-managed resource type as of CLI 0.298; # only endpoints are. The Python task creates the Delta-Sync index on first # run and triggers a sync on subsequent runs. diff --git a/scripts/bootstrap-demo.sh b/scripts/bootstrap-demo.sh index 0c08400..40122a1 100755 --- a/scripts/bootstrap-demo.sh +++ b/scripts/bootstrap-demo.sh @@ -382,7 +382,7 @@ import json app = json.loads('''$app_state''') scopes = app.get('user_api_scopes') if scopes: - raise SystemExit(f'demo SP-fallback expected no user_api_scopes, got {scopes}') + raise SystemExit(f'demo App-SP mode expected no user_api_scopes, got {scopes}') print(' OBO disabled for demo; user_api_scopes unset') " grant_app_sp_endpoint_query "$app_state" diff --git a/specs/001-doc-intel-10k/contracts/agent-request.json b/specs/001-doc-intel-10k/contracts/agent-request.json index d17d011..a654ce4 100644 --- a/specs/001-doc-intel-10k/contracts/agent-request.json +++ b/specs/001-doc-intel-10k/contracts/agent-request.json @@ -1,7 +1,7 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Agent Endpoint Request", - "description": "App-level normalized request sent to the Agent Bricks Supervisor endpoint. AI Gateway and Databricks Apps OBO add identity automatically.", + "description": "App-level normalized request sent to the Agent Bricks Supervisor endpoint. Prod uses Databricks Apps OBO identity; demo may use the App service principal.", "type": "object", "required": ["question"], "properties": { diff --git a/specs/001-doc-intel-10k/data-model.md b/specs/001-doc-intel-10k/data-model.md index 631d39f..a2a0a65 100644 --- a/specs/001-doc-intel-10k/data-model.md +++ b/specs/001-doc-intel-10k/data-model.md @@ -1,6 +1,6 @@ # Phase 1 Data Model -All Delta tables live under the bundle-parameterized `${var.catalog}.${var.schema}`. Lakebase tables live in the bundle-managed Lakebase database `${var.catalog}_state`. +All Delta tables live under the bundle-parameterized `${var.catalog}.${var.schema}`. Lakebase tables live in the bundle-managed Lakebase database instance `${var.lakebase_instance}`, exposed to SQL dashboards through the UC database catalog `${var.schema}_state`. ## Bronze @@ -99,7 +99,7 @@ A view `gold_filing_sections_with_quality` joins sections + quality and is the s | Column | Type | Notes | |---|---|---| | `conversation_id` | UUID PK | One row per session | -| `user_email` | STRING | From identity passthrough | +| `user_email` | STRING | From Databricks Apps identity headers; local runs may use `DOCINTEL_USER_EMAIL` | | `started_at` | TIMESTAMPTZ | | | `last_turn_at` | TIMESTAMPTZ | | @@ -149,7 +149,7 @@ PDF in volume └─ ai_query rubric → gold_filing_quality (quality_score) └─ quality_score threshold → Vector Search index sync └─ Agent Bricks Knowledge Assistant + Supervisor Agent - └─ AI Gateway + OBO + └─ AI Gateway + target auth mode └─ Streamlit App turn └─ Lakebase query_logs + feedback ``` diff --git a/specs/001-doc-intel-10k/plan.md b/specs/001-doc-intel-10k/plan.md index eb3fcb0..a833ece 100644 --- a/specs/001-doc-intel-10k/plan.md +++ b/specs/001-doc-intel-10k/plan.md @@ -5,7 +5,7 @@ ## Summary -Build a Databricks-native, governed pipeline + Agent Bricks system that turns SEC 10-K PDFs into a queryable lakehouse and a cited Q&A experience. SQL Lakeflow Spark Declarative Pipelines parse PDFs once with `ai_parse_document` (VARIANT), classify sections with `ai_classify`, extract structured KPIs with `ai_extract`, and score every section against a 5-dimension quality rubric. High-quality summaries flow into a Mosaic AI Vector Search index. Agent Bricks Knowledge Assistant handles cited document Q&A; Agent Bricks Supervisor Agent coordinates the Knowledge Assistant with a deterministic Unity Catalog KPI function for cross-company comparisons. AI Gateway, Unity Catalog, and mandatory OBO enforce identity and audit. Conversation history and feedback land in Lakebase Postgres. Lakehouse Monitoring tracks extraction drift; an AI/BI dashboard surfaces query-log content gaps. CLEARS evaluation in MLflow gates promotion. The stack is deployed by DAB plus idempotent Agent Bricks bootstrap (`databricks bundle deploy -t demo|prod`, `agent/document_intelligence_agent.py`). +Build a Databricks-native, governed pipeline + Agent Bricks system that turns SEC 10-K PDFs into a queryable lakehouse and a cited Q&A experience. SQL Lakeflow Spark Declarative Pipelines parse PDFs once with `ai_parse_document` (VARIANT), classify sections with `ai_classify`, extract structured KPIs with `ai_extract`, and score every section against a 5-dimension quality rubric. High-quality summaries flow into a Mosaic AI Vector Search index. Agent Bricks Knowledge Assistant handles cited document Q&A; Agent Bricks Supervisor Agent coordinates the Knowledge Assistant with a deterministic Unity Catalog KPI function for cross-company comparisons. AI Gateway, Unity Catalog, and prod OBO enforce identity and audit. Demo can use App-SP mode when Apps user-token passthrough is unavailable. Conversation history and feedback land in Lakebase Postgres. Lakehouse Monitoring tracks extraction drift; an AI/BI dashboard surfaces query-log content gaps. CLEARS evaluation in MLflow gates promotion. The stack is deployed by DAB plus idempotent Agent Bricks bootstrap (`databricks bundle deploy -t demo|prod`, `agent/document_intelligence_agent.py`). ## Technical Context @@ -13,7 +13,7 @@ Build a Databricks-native, governed pipeline + Agent Bricks system that turns SE **Primary Dependencies**: Lakeflow Spark Declarative Pipelines, Lakeflow Jobs, Mosaic AI Vector Search, Agent Bricks Knowledge Assistant and Supervisor Agent, AI Gateway, Databricks Apps (Streamlit), Lakebase Postgres, Lakehouse Monitoring, Databricks Asset Bundles CLI (`databricks` >= 0.260), MLflow Agent Evaluation **Storage**: Unity Catalog — `.` with one volume (`raw_filings`) and Delta tables (`bronze_filings`, `silver_parsed_filings`, `gold_filing_sections`, `gold_filing_kpis`); Lakebase Postgres for `conversation_history`, `query_logs`, `feedback` **Testing**: `databricks bundle validate -t demo` (schema check), pytest for agent unit tests, MLflow `evaluate()` with `databricks-agents` evaluators for CLEARS, manual smoke via the deployed App -**Target Platform**: Databricks workspace with serverless SQL warehouse (AI Functions GA), Mosaic AI Vector Search, Agent Bricks, Databricks Apps user-token passthrough, AI Gateway, Unity Catalog, and Lakebase enabled +**Target Platform**: Databricks workspace with serverless SQL warehouse (AI Functions GA), Mosaic AI Vector Search, Agent Bricks, Databricks Apps, AI Gateway, Unity Catalog, and Lakebase enabled. Prod also requires Databricks Apps user-token passthrough. **Project Type**: Databricks lakehouse + agent stack delivered as a single DAB **Performance Goals**: Pipeline E2E ≤ 10 min P95 on a 30 MB PDF (SC-001); agent P95 ≤ 8s single-filing, ≤ 20s cross-company (SC-009); Vector Search refresh ≤ 5 min after Gold update **Constraints**: SQL only for parse/classify/extract layer; Python only for agent + app; CPU model serving (no GPU); zero hard-coded paths outside the bundle; one-command deploy; CLEARS thresholds C≥0.8, L p95≤8s, E≥0.95, A≥0.9, R≥0.8, S≥0.99 block promotion @@ -104,7 +104,7 @@ scripts/ CLAUDE.md # Runtime guidance for Claude Code ``` -**Structure Decision**: Single DAB containing one pipeline, two jobs, one Vector Search endpoint, one Lakebase project, one monitor, one dashboard, one app, and a CI workflow. Agent Bricks resources are SDK-managed by `agent/document_intelligence_agent.py` until DAB exposes first-class Knowledge Assistant and Supervisor resource types. SQL pipeline code lives at the root under `pipelines/sql/`; deterministic tool glue lives at `agent/`; app code lives at `app/`. +**Structure Decision**: Single DAB containing one pipeline, two jobs, one Vector Search endpoint, one Lakebase instance/catalog, one monitor, one dashboard, one app, and a CI workflow. Agent Bricks resources are SDK-managed by `agent/document_intelligence_agent.py` until DAB exposes first-class Knowledge Assistant and Supervisor resource types. SQL pipeline code lives at the root under `pipelines/sql/`; deterministic tool glue lives at `agent/`; app code lives at `app/`. ## Phase 0 — Outline & Research @@ -120,7 +120,7 @@ Output: [research.md](./research.md). Decisions captured: | Vector Search index | Delta-Sync index over `gold_filing_sections` filtered by `embed_eligible`; embed `summary` column | Managed sync, no manual refresh; embeds curated content per principle IV | Direct Vector Index (rejected: no managed sync); embedding raw `parsed.text_full` (rejected: noise) | | Retrieval strategy | Agent Bricks Knowledge Assistant over the governed document layer / Vector Search source | Demonstrates the Agent Bricks article pattern and removes custom retrieval/rerank serving code | Raw chunk search (rejected: ignores Document Intelligence quality layer) | | Agent framework | Agent Bricks Knowledge Assistant + Supervisor Agent | First-class governed enterprise agent primitives; aligns with the source articles | Custom `mlflow.pyfunc` analyst agent (rejected: caused deploy-order and serving lifecycle failures); LangGraph standalone (rejected: not the reference pattern) | -| Serving | Agent Bricks endpoint behind AI Gateway with mandatory OBO | Gateway gives audit, rate limits, guardrails, and identity enforcement | Bespoke custom endpoint ownership (rejected: custom lifecycle); service-principal auth for document Q&A (rejected: not production-safe) | +| Serving | Agent Bricks endpoint behind AI Gateway; prod uses OBO, demo can use App-SP mode | Gateway gives audit, rate limits, guardrails, and identity enforcement; demo remains deployable in workspaces without Apps user-token passthrough | Bespoke custom endpoint ownership (rejected: custom lifecycle); service-principal auth for production document Q&A (rejected: not production-safe) | | State store | Lakebase Postgres (managed) | Native to platform, low-latency reads/writes, fits Reffy pattern; integrates with Apps | Delta tables (rejected: write throughput on small turn-level updates); external Postgres (rejected: governance gap) | | Eval framework | MLflow `evaluate()` with `databricks-agents` evaluators on CLEARS axes | First-class CLEARS support; logged into MLflow runs | LangSmith / Ragas (rejected: external system) | | Monitoring | Lakehouse Monitoring `inference` profile on `gold_filing_kpis`; Lakeview AI/BI dashboard on `query_logs` | First-class drift detection; usage dashboard surfaces content gaps per Reffy | Custom Spark notebooks (rejected: imperative, principle III) | @@ -143,7 +143,7 @@ Output: `data-model.md`, `contracts/`, `quickstart.md`, plus the agent context u | Section | `gold_filing_sections` row | Gold | | KPI Record | `gold_filing_kpis` row (JSON-typed `ai_extract` output unpacked into columns) | Gold | | Citation | Returned in agent response payload (see `contracts/agent-response.json`) | Runtime | -| Conversation | `lakebase.conversation_history` + `lakebase.query_logs` rows | Lakebase | +| Conversation | `conversation_history` + `query_logs` rows in `${var.lakebase_instance}` / UC catalog `${var.schema}_state` | Lakebase | | Eval Item | `evals/dataset.jsonl` row | Repo | ### Contracts diff --git a/specs/001-doc-intel-10k/quickstart.md b/specs/001-doc-intel-10k/quickstart.md index 1c10779..b93613a 100644 --- a/specs/001-doc-intel-10k/quickstart.md +++ b/specs/001-doc-intel-10k/quickstart.md @@ -1,11 +1,11 @@ # Quickstart: Deploy and Test the 10-K Analyst -Goal: from a clean clone, stand up the entire stack on the Databricks `demo` target and verify P1, P2, P3 acceptance scenarios in 15–25 minutes. +Goal: from a clean clone, stand up the entire stack on the Databricks `demo` target and run the P1, P2, and P3 acceptance checks in 15–25 minutes. ## Prerequisites - macOS or Linux, `python` 3.11+, `git`, `databricks` CLI ≥ 0.298 (`brew install databricks/tap/databricks`) -- A Databricks workspace with: serverless SQL warehouse (AI Functions GA), Mosaic AI Vector Search, Agent Bricks Knowledge Assistant and Supervisor Agent, AI Gateway, Databricks Apps user-token passthrough, Unity Catalog, and Lakebase enabled +- A Databricks workspace with: serverless SQL warehouse (AI Functions GA), Mosaic AI Vector Search, Agent Bricks Knowledge Assistant and Supervisor Agent, AI Gateway, Databricks Apps, Unity Catalog, and Lakebase enabled. Prod also requires Databricks Apps user-token passthrough. - An auth profile (`databricks auth login --host ` once); verify with `databricks auth profiles` - Local virtualenv: `python -m venv .venv && .venv/bin/pip install -r agent/requirements.txt -r evals/requirements.txt` @@ -98,7 +98,7 @@ Note: the Lakebase instance enters a soft-delete state for ~7 days during which |---|---|---| | `bundle validate` errors on `ai_parse_document` | Workspace lacks AI Functions GA | Move SQL warehouse to a recent serverless channel | | Vector Search index sync stuck | Embedding endpoint not provisioned | Provision `databricks-bge-large-en` or override `var.embedding_model_endpoint_name` | -| Agent endpoint 401 from App | OBO not plumbed end-to-end | Verify `app/app.py:_user_client` reads `x-forwarded-access-token` and the App's `user_api_scopes` includes `serving.serving-endpoints` (workspace must have user-token-passthrough enabled — see `docs/runbook.md` §"Verifying end-to-end OBO") | +| Agent endpoint 401 from App | Target auth mode does not have endpoint access | Demo: verify App SP `CAN_QUERY` was granted. Prod: verify `app/app.py:_user_client` reads `x-forwarded-access-token` and the target `user_api_scopes` include `serving.serving-endpoints` | | CLEARS Latency axis fails | Agent Bricks orchestration or Knowledge Assistant source is too broad | Narrow the Knowledge Assistant source, tune Supervisor instructions, or reduce structured-tool fan-out | | Bootstrap blocks on Lakebase soft-delete | `lakebase_instance` name held by retention | Bump suffix in `databricks.yml` and retry | -| App deploy fails on OBO scopes | Workspace lacks user-token-passthrough feature | Workspace admin enables the feature; this is a production prerequisite | +| App deploy fails on OBO scopes | Workspace lacks user-token-passthrough feature | Workspace admin enables the feature for prod. Demo should use `app_obo_required=false` unless validating OBO | diff --git a/specs/001-doc-intel-10k/spec.md b/specs/001-doc-intel-10k/spec.md index de42e26..c1eca8a 100644 --- a/specs/001-doc-intel-10k/spec.md +++ b/specs/001-doc-intel-10k/spec.md @@ -19,14 +19,14 @@ - Q: Eval corpus — real EDGAR PDFs or synthetic? → A: Synthetic. The 30-question dataset references three synthetic 10-Ks (`samples/{ACME,BETA,GAMMA}_10K_2024.pdf`, generated by `samples/synthesize.py`) plus a deliberately low-quality `garbage_10K_2024.pdf` for SC-006. Real EDGAR filings can still be uploaded to the volume in deployed environments; the synthetic corpus exists so CI is fully deterministic and self-contained (no EDGAR dependency, no license concerns). User-facing examples in spec scenarios still use AAPL/MSFT/GOOG to convey intent. - Q: Deploy ordering — single bundle deploy or staged? → A: Staged. `resources/foundation/` (catalog, pipeline, retention job, Lakebase instance, VS endpoint) deploys first; data is produced (sample upload, pipeline run, VS index materialization, Agent Bricks Knowledge Assistant + Supervisor configuration, Lakebase ready); then `resources/consumers/` (monitor, index-refresh job, app, Lakebase catalog) deploys. The chicken-egg dependencies between consumers and foundation data make a single deploy impossible. Bootstrap script automates this. -- Q: User identity passthrough? → A: OBO end-to-end is mandatory. The workspace-level "Databricks Apps - user token passthrough" feature must be enabled before deployment. When disabled, deploy fails with an actionable prerequisite error. +- Q: User identity passthrough? → A: Prod requires OBO end-to-end. The workspace-level "Databricks Apps - user token passthrough" feature must be enabled before prod deployment. Demo may run with `app_obo_required=false`, where the App service principal invokes Agent Bricks and is explicitly granted endpoint query access. ### Session 2026-04-26 - Q: What is the architectural source of truth? → A: The reference implementation MUST demonstrate the patterns in Databricks' "Why Your Agents Can't Read Enterprise Documents" and "Agent Bricks: The Governed Enterprise Agent Platform" articles. Document Intelligence is the document-processing foundation; Agent Bricks is the agent construction, orchestration, governance, and serving foundation. - Q: Is a custom `mlflow.pyfunc` analyst agent acceptable as the primary implementation? → A: No. Custom pyfunc retrieval/supervisor/serving code is a divergence from the Agent Bricks-first reference and MUST be removed. Knowledge Assistant MUST handle cited single-filing document Q&A. Supervisor Agent MUST handle orchestration across document Q&A and structured KPI tools. - Q: What custom code may remain? → A: Custom code may remain only where it demonstrates integration around Agent Bricks rather than replacing Agent Bricks: the Document Intelligence SQL pipeline, deterministic Gold KPI SQL/tool access, Databricks App UX, Lakebase feedback persistence, and deploy/eval automation. -- Q: Should the implementation keep legacy fallback logic for workspaces without user-token passthrough or Agent Bricks support? → A: No. Production deployment requires Agent Bricks, AI Gateway, Unity Catalog, Databricks Apps user-token passthrough, and end-to-end OBO. Missing prerequisites MUST fail validation or deploy; service-principal fallback and legacy custom-agent fallback are not acceptable. +- Q: Should the implementation keep legacy compatibility logic for workspaces without Agent Bricks support? → A: No. Agent Bricks is required. Production deployment requires Agent Bricks, AI Gateway, Unity Catalog, Databricks Apps user-token passthrough, and end-to-end OBO. Demo App-SP mode is allowed only for development workspaces without Apps user-token passthrough; legacy custom-agent or bespoke serving paths are not acceptable. ## User Scenarios & Testing *(mandatory)* @@ -107,8 +107,8 @@ An analyst asks a multi-company question — e.g., "Compare segment revenue betw - **FR-012**: System MUST be deployable end-to-end (catalog/schema/volume, Document Intelligence pipelines, vector index or Knowledge Assistant source, Agent Bricks endpoint/configuration, AI Gateway, app, monitors, dashboards) via a single repeatable bring-up command; two environments (demo, prod) MUST be defined; any resource not yet expressible as DAB YAML MUST be created by idempotent bootstrap code that is treated as part of the production deployment, not as manual setup. - **FR-013**: System MUST process duplicate uploads idempotently keyed on filename. - **FR-014**: System MUST gracefully report missing/ungrounded answers ("no source found") rather than hallucinating when retrieval returns no qualified results. -- **FR-015**: System MUST explicitly remove current custom-agent divergence: `agent/analyst_agent.py`, `agent/retrieval.py`, `agent/supervisor.py`, direct `mlflow.pyfunc` registration, and bespoke Model Serving endpoint ownership MUST be replaced by Agent Bricks Knowledge Assistant / Supervisor Agent configuration. No temporary compatibility shims or legacy fallback endpoint may remain. -- **FR-016**: System MUST require end-to-end user identity. Databricks Apps user-token passthrough, Agent Bricks / AI Gateway OBO, and UC permission enforcement are production prerequisites. If any prerequisite is unavailable, deploy MUST fail with an actionable error; the app and agent MUST NOT fall back to broad service-principal reads. +- **FR-015**: System MUST explicitly remove current custom-agent divergence: `agent/analyst_agent.py`, `agent/retrieval.py`, `agent/supervisor.py`, direct `mlflow.pyfunc` registration, and bespoke Model Serving endpoint ownership MUST be replaced by Agent Bricks Knowledge Assistant / Supervisor Agent configuration. No temporary compatibility shims or legacy custom-agent endpoint may remain. +- **FR-016**: System MUST require end-to-end user identity for prod. Databricks Apps user-token passthrough, Agent Bricks / AI Gateway OBO, and UC permission enforcement are production prerequisites. If any prod prerequisite is unavailable, deploy MUST fail with an actionable error. Demo may set `app_obo_required=false` and grant the App service principal `CAN_QUERY` on the generated Supervisor endpoint. ### Key Entities @@ -137,10 +137,10 @@ An analyst asks a multi-company question — e.g., "Compare segment revenue betw ## Assumptions - The target Databricks workspace has a serverless SQL warehouse with `ai_parse_document` (GA), `ai_classify`, `ai_extract`, and `ai_prep_search` available. -- Mosaic AI Vector Search, Agent Bricks, AI Gateway, and Databricks Apps user-token passthrough entitlements are enabled for the workspace. +- Mosaic AI Vector Search, Agent Bricks, AI Gateway, and Databricks Apps are enabled for the workspace. Prod also has Databricks Apps user-token passthrough enabled. - Sample 10-K PDFs are publicly available SEC filings (EDGAR) the analyst manually uploads to the volume; no automated SharePoint/Drive sync in v1. -- A Service Principal exists for prod deploys but is not used in v1 (demo target only). -- Analyst end-users have UC `SELECT` on the configured catalog/schema, `EXECUTE` on the KPI function, and `CAN QUERY` on the Agent Bricks endpoints via end-to-end OBO. +- A Service Principal exists for prod deploys. Demo App-SP mode uses the Databricks App service principal when `app_obo_required=false`. +- Analyst end-users have UC `SELECT` on the configured catalog/schema and `EXECUTE` on the KPI function. Prod users also have `CAN QUERY` on the Agent Bricks endpoints via end-to-end OBO; demo App-SP mode grants `CAN_QUERY` to the App service principal. - The CLI auth profile on the operator's machine targets a workspace where the bundle can deploy without further policy exceptions. - 10-K fiscal year and company name can be reliably extracted from the parsed cover page; if not, `extraction_confidence` reflects the gap and the row remains queryable. - A curated eval set of 30 questions (20 P2 + 10 P3) is authored during implementation and checked in at `evals/dataset.jsonl`; CLEARS thresholds are tunable in config but defaults are fixed in FR-010. diff --git a/specs/001-doc-intel-10k/tasks.md b/specs/001-doc-intel-10k/tasks.md index 002824b..0ab81ec 100644 --- a/specs/001-doc-intel-10k/tasks.md +++ b/specs/001-doc-intel-10k/tasks.md @@ -37,9 +37,9 @@ This is a DAB plus Agent Bricks deployment project. SQL pipeline code is at `pip **⚠️ CRITICAL**: All user stories depend on these. -- [x] T006 Define UC catalog/schema/volume in `resources/dabs/catalog.yml` (or inline in `databricks.yml`): `${var.catalog}.${var.schema}` schema + `raw_filings` volume; grant `USE_CATALOG`, `USE_SCHEMA`, `READ_VOLUME` to a configurable analyst group -- [x] T007 [P] Define the Lakebase project + database in `resources/lakebase/state.yml` with three tables (`conversation_history`, `query_logs`, `feedback`) per `data-model.md`; expose connection vars to the Streamlit App -- [x] T008 [P] Add the agent JSON contracts to the bundle as inline strings or copy them to `agent/contracts/` so both the agent and the App reference one source: `agent-request.json`, `agent-response.json`, `feedback-event.json`, `kpi-schema.json` +- [x] T006 Define UC catalog/schema/volume in `resources/foundation/catalog.yml`: `${var.catalog}.${var.schema}` schema + `raw_filings` volume; grant `USE_CATALOG`, `USE_SCHEMA`, `READ_VOLUME` to a configurable analyst group +- [x] T007 [P] Define the Lakebase instance/catalog in `resources/foundation/lakebase_instance.yml` and `resources/consumers/lakebase_catalog.yml`; the UC catalog is `${var.schema}_state`, while `app/lakebase_client.py` creates `conversation_history`, `query_logs`, and `feedback` tables at runtime using App resource binding fields plus Databricks-minted Lakebase OAuth credentials +- [x] T008 [P] Add JSON contracts under `specs/001-doc-intel-10k/contracts/`: `agent-request.json`, `agent-response.json`, `feedback-event.json`, `kpi-schema.json` **Checkpoint**: catalog, schema, volume, Lakebase database exist; bundle validates. @@ -57,16 +57,16 @@ This is a DAB plus Agent Bricks deployment project. SQL pipeline code is at `pip - [x] T010 [US1] Write `pipelines/sql/02_silver_parse.sql`: streaming table `silver_parsed_filings` using `APPLY CHANGES INTO` keyed on `filename`, computing `ai_parse_document(content)` once into `VARIANT` `parsed`, plus `parse_status`/`parse_error` derived from `try_cast` of the result (depends on T009) - [x] T011 [US1] Write `pipelines/sql/03_gold_classify_extract.sql`: - Streaming table `gold_filing_sections` exploding `parsed:sections[*]`, calling `ai_classify(section_text, ARRAY('MD&A','Risk','Financials','Notes','Other'))` to populate `section_label`, summarising via `ai_query` into the `summary` column - - Streaming table `gold_filing_kpis` calling `ai_extract` against the concatenated MD&A + Financials text using the JSON schema in `agent/contracts/kpi-schema.json`, then unpacking into typed columns + - Streaming table `gold_filing_kpis` calling `ai_extract` against the concatenated MD&A + Financials text using the JSON schema in `specs/001-doc-intel-10k/contracts/kpi-schema.json`, then unpacking into typed columns - Both tables use `APPLY CHANGES INTO` keyed on appropriate keys (depends on T010) - [x] T012 [US1] Write `pipelines/sql/04_gold_quality.sql`: materialized view `gold_filing_quality` invoking `ai_query` 5 times per section row to score parse_completeness, layout_fidelity, ocr_confidence, section_recognizability, kpi_extractability (each 0–6); compute `quality_score` and persist `quality_breakdown` STRUCT (depends on T011) - [x] T013 [US1] Update `gold_filing_sections` (in T011 or a follow-on view) to add `embed_eligible = (quality_score >= ${var.quality_threshold} AND parse_status = 'ok')` by joining with `gold_filing_quality` -- [x] T014 [US1] Define the Lakeflow SDP in `resources/pipelines/doc_intel.pipeline.yml`: serverless, libraries point at `pipelines/sql/*.sql`, target = `${var.catalog}.${var.schema}`, file-arrival event trigger on the `raw_filings` volume, retries=2 (depends on T009-T013) -- [x] T015 [US1] Define the retention Lakeflow Job in `resources/jobs/retention.job.yml`: daily schedule, single Python task that lists the volume via `WorkspaceClient.files`, removes files with `modificationTime < now()-90d`, logs deletions; uses Service Principal in prod only (depends on T006) +- [x] T014 [US1] Define the Lakeflow SDP in `resources/foundation/doc_intel.pipeline.yml`: serverless, libraries point at `pipelines/sql/*.sql`, target = `${var.catalog}.${var.schema}`, triggered in demo and continuous in prod (depends on T009-T013) +- [x] T015 [US1] Define the retention Lakeflow Job in `resources/foundation/retention.job.yml`: daily schedule, single Python task that lists the volume via `WorkspaceClient.files`, removes files with `modificationTime < now()-90d`, logs deletions; uses Service Principal in prod only (depends on T006) - [x] T016 [US1] Add synthetic samples (`samples/{ACME,BETA,GAMMA}_10K_2024.pdf` + `samples/garbage_10K_2024.pdf` for SC-006) reproducible from `samples/synthesize.py`; documented in `samples/README.md` -- [x] T017 [US1] Write a Lakeview `resources/dashboards/usage.lvdash.yml` containing one initial widget over `gold_filing_kpis` (count by company_name, count by fiscal_year); will be extended in US2/US3 (depends on T011) +- [x] T017 [US1] Write a Lakeview dashboard source at `src/dashboards/usage.lvdash.json`, managed by `resources/consumers/usage.dashboard.yml`, containing one initial widget over `gold_filing_kpis` (count by company_name, count by fiscal_year); will be extended in US2/US3 (depends on T011) -**Checkpoint**: P1 acceptance scenarios 1–4 pass via the quickstart commands. +**Checkpoint target**: P1 acceptance scenarios 1–4 pass via the quickstart commands. Latest workspace evidence is tracked in `VALIDATION.md`. --- @@ -89,16 +89,16 @@ This is a DAB plus Agent Bricks deployment project. SQL pipeline code is at `pip - [x] T023 [US2] Implement `agent/tools.py` as deterministic structured KPI tool glue for Agent Bricks, wrapping governed SQL over `gold_filing_kpis` - [x] T024 [US2] Remove custom `agent/analyst_agent.py` and direct `mlflow.pyfunc` registration; Knowledge Assistant owns single-filing cited Q&A (depends on T022, T023) - [x] T025 [US2] Remove `agent/log_and_register.py` and bespoke model-version promotion from the production path; `agent/document_intelligence_agent.py` configures Agent Bricks resources idempotently instead -- [x] T026 [US2] Replace `resources/consumers/agent.serving.yml` with `agent/document_intelligence_agent.py` Agent Bricks endpoint/configuration behind AI Gateway with mandatory OBO and guardrails (depends on T024, T025) -- [x] T027 [US2] Implement `app/app.py` (Streamlit): chat input, calls the Agent Bricks endpoint as the invoking user, renders answer + citations as chips, thumbs-up/down + comment widget that POSTs to a Lakebase write helper; persists `conversation_id` in session state (depends on T026, T007) -- [x] T028 [US2] Implement `app/lakebase_client.py`: thin wrapper using `psycopg` with the bundle-injected DSN to insert into `conversation_history`, `query_logs`, `feedback` +- [x] T026 [US2] Replace `resources/consumers/agent.serving.yml` with `agent/document_intelligence_agent.py` Agent Bricks endpoint/configuration behind AI Gateway with prod OBO and demo App-SP mode (depends on T024, T025) +- [x] T027 [US2] Implement `app/app.py` (Streamlit): chat input, calls the Agent Bricks endpoint using the target auth mode, renders answer + citations as chips, thumbs-up/down + comment widget that POSTs to a Lakebase write helper; persists `conversation_id` in session state (depends on T026, T007) +- [x] T028 [US2] Implement `app/lakebase_client.py`: thin wrapper using `psycopg` with App resource binding connection fields and Databricks-minted Lakebase OAuth credentials to insert into `conversation_history`, `query_logs`, `feedback` - [x] T029 [US2] Define the Databricks App in `resources/consumers/analyst.app.yml`: source = `app/`, runtime python, env = Lakebase binding + agent endpoint binding (depends on T027, T028) - [x] T030 [US2] Author `evals/dataset.jsonl` 20 P2 questions per `data-model.md`'s eval section (each with `expected_filename`, `expected_section`, `expected_answer_keywords`, `min_citations`) - [x] T031 [US2] Implement `evals/clears_eval.py`: connects to the demo endpoint, runs `mlflow.evaluate()` with `databricks-agents` evaluators on the dataset, asserts thresholds C≥0.8, L p95≤8s, E≥0.95, A≥0.9, R≥0.8, S≥0.99; exits non-zero on failure (depends on T026, T030) - [x] T032 [US2] Define Lakehouse Monitoring in `resources/consumers/kpi_drift.yml`: `inference` profile on `gold_filing_kpis`, slicing on `company_name`, `fiscal_year`; baselines computed from first 10 filings (depends on T011) -- [x] T033 [US2] Extend `resources/dashboards/usage.lvdash.yml` with widgets over `lakebase.query_logs`: top questions, daily active users, p95 latency, citation count distribution, ungrounded-answer rate (depends on T028, T017) +- [x] T033 [US2] Extend `src/dashboards/usage.lvdash.json` with widgets over Lakebase `query_logs`: top questions, daily active users, p95 latency, citation count distribution, ungrounded-answer rate (depends on T028, T017) -**Checkpoint**: P2 acceptance scenarios 1–3 pass via App; CLEARS gate passes for the P2 slice of the eval set. +**Checkpoint target**: P2 acceptance scenarios 1–3 pass via App; CLEARS gate passes for the P2 slice of the eval set. Latest workspace evidence is tracked in `VALIDATION.md`. --- @@ -121,7 +121,7 @@ This is a DAB plus Agent Bricks deployment project. SQL pipeline code is at `pip - [x] T039 [US3] Extend `evals/clears_eval.py` to slice metrics by `category in {P2, P3}` and assert SC-002 ≥0.8 on P2, SC-003 ≥0.7 on P3 (depends on T031, T038) - [x] T040 [US3] Update `app/app.py` to render markdown tables (Streamlit `st.markdown(..., unsafe_allow_html=False)` already handles this) and surface a "show structured KPIs" expander next to each row (depends on T036) -**Checkpoint**: P3 acceptance scenarios 1–2 pass; CLEARS gate passes for both P2 and P3 slices. +**Checkpoint target**: P3 acceptance scenarios 1–2 pass; CLEARS gate passes for both P2 and P3 slices. Latest workspace evidence is tracked in `VALIDATION.md`. --- @@ -130,11 +130,11 @@ This is a DAB plus Agent Bricks deployment project. SQL pipeline code is at `pip - [ ] T041 [P] Run `databricks bundle validate -t demo` and resolve any schema warnings - [ ] T042 [P] Run `databricks bundle validate -t prod` (no deploy) to confirm prod target compiles - [ ] T043 Walk through `quickstart.md` end-to-end on a clean workspace; capture timing for SC-005 -- [x] T044 [P] Add a Lakeview widget on `lakebase.query_logs` summarising "ungrounded answer rate by week" — content-gap signal per Reffy +- [x] T044 [P] Add a Lakeview widget on Lakebase `query_logs` summarising "ungrounded answer rate by week" — content-gap signal per Reffy - [x] T045 [P] Document operating runbook in `docs/runbook.md`: how to add a sample filing, how to debug a low quality_score, how to roll an agent endpoint version, how to inspect CLEARS metrics in MLflow - [ ] T046 Run `python evals/clears_eval.py` against the demo endpoint and store the MLflow run ID in `docs/runbook.md` as the v1 baseline - [x] T047 [P] Add an SC-006 verification assertion in `evals/clears_eval.py`: query Vector Search for a known-rejected filename and assert zero hits (verifies "100% rubric exclusion") -- [x] T048 [P] Add an SC-001 timing widget to `resources/dashboards/usage.lvdash.yml` over `gold_filing_kpis` joined to `bronze_filings.ingested_at`: P95 of `extracted_at - ingested_at` per company; alerts if > 10 minutes +- [x] T048 [P] Add an SC-001 timing widget to `src/dashboards/usage.lvdash.json` over `gold_filing_kpis` joined to `bronze_filings.ingested_at`: P95 of `extracted_at - ingested_at` per company; alerts if > 10 minutes --- diff --git a/src/dashboards/usage.lvdash.json b/src/dashboards/usage.lvdash.json index 346d728..3aba983 100644 --- a/src/dashboards/usage.lvdash.json +++ b/src/dashboards/usage.lvdash.json @@ -26,7 +26,7 @@ "SELECT created_at::date AS day, agent_path, count(*) AS turns,", " percentile_approx(latency_ms, 0.95) AS latency_p95_ms,", " sum(CASE WHEN array_size(citations) = 0 THEN 1 ELSE 0 END) AS ungrounded", - "FROM `__dataset_catalog__`.`__dataset_schema__`_state.public.query_logs", + "FROM `__dataset_schema___state`.public.query_logs", "GROUP BY 1, 2 ORDER BY 1 DESC" ] } From 4e08ddbdeab6ddbdffd9d0704d4a62c8d2b3aadb Mon Sep 17 00:00:00 2001 From: Sathish Krishnan <10681383+SatyKrish@users.noreply.github.com> Date: Sun, 26 Apr 2026 22:23:19 -0400 Subject: [PATCH 5/7] Fix Lakebase app schema ownership --- .github/workflows/deploy.yml | 12 +++++- app/README.md | 7 ++-- app/lakebase_client.py | 54 +++++++++++++++++++------ docs/runbook.md | 4 +- resources/consumers/analyst.app.yml | 2 + scripts/bootstrap-demo.sh | 58 +++++++++++++++++++++------ specs/001-doc-intel-10k/data-model.md | 2 +- specs/001-doc-intel-10k/tasks.md | 2 +- src/dashboards/usage.lvdash.json | 2 +- 9 files changed, 108 insertions(+), 35 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 835dbe2..8ccb478 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -137,6 +137,17 @@ jobs: run: | databricks apps get doc-intel-analyst-demo --output json > /tmp/app.json app_obo_required="$(python -c "import yaml; d=yaml.safe_load(open('databricks.yml')); default=d.get('variables',{}).get('app_obo_required',{}).get('default','true'); value=d.get('targets',{}).get('demo',{}).get('variables',{}).get('app_obo_required', default); print(str(value).lower())")" + lakebase_name="$(python -c "import yaml; d=yaml.safe_load(open('databricks.yml')); print(d.get('targets',{}).get('demo',{}).get('variables',{}).get('lakebase_instance','docintel-demo-state-v1'))")" + python -c "import json; app=json.load(open('/tmp/app.json')); vals=[str(app.get(k)) for k in ('service_principal_client_id','service_principal_name','service_principal_id') if app.get(k) is not None]; print('\n'.join(dict.fromkeys(v for v in vals if v)))" > /tmp/app-sp-candidates.txt + db_granted=0 + while IFS= read -r principal; do + grant_json="$(python -c "import json, sys; print(json.dumps({'access_control_list':[{'service_principal_name':sys.argv[1],'permission_level':'CAN_USE'}]}))" "$principal")" + if databricks permissions update database-instances "$lakebase_name" --json "$grant_json"; then + db_granted=1 + break + fi + done < /tmp/app-sp-candidates.txt + test "$db_granted" = "1" if [ "$app_obo_required" = "true" ]; then # `bundle run` may wipe user_api_scopes (documented destructive-update # behavior). Fail loudly if required user scopes are missing. @@ -144,7 +155,6 @@ jobs: else python -c "import json; app=json.load(open('/tmp/app.json')); scopes=app.get('user_api_scopes'); assert not scopes, f'demo App-SP mode expected no user_api_scopes, got {scopes}'" endpoint_id="$(databricks serving-endpoints get "$AGENT_ENDPOINT_NAME" --output json | python -c "import json, sys; e=json.load(sys.stdin); print(e.get('id') or e.get('name'))")" - python -c "import json; app=json.load(open('/tmp/app.json')); vals=[str(app.get(k)) for k in ('service_principal_client_id','service_principal_name','service_principal_id') if app.get(k) is not None]; print('\n'.join(dict.fromkeys(v for v in vals if v)))" > /tmp/app-sp-candidates.txt granted=0 while IFS= read -r principal; do grant_json="$(python -c "import json, sys; print(json.dumps({'access_control_list':[{'service_principal_name':sys.argv[1],'permission_level':'CAN_QUERY'}]}))" "$principal")" diff --git a/app/README.md b/app/README.md index 268055b..774d9ff 100644 --- a/app/README.md +++ b/app/README.md @@ -35,6 +35,7 @@ export DATABRICKS_CLIENT_SECRET= # Locally, set the same connection fields and let lakebase_client.py mint the # OAuth database password through the Databricks SDK. export DOCINTEL_LAKEBASE_INSTANCE=docintel-demo-state-v1 +export DOCINTEL_LAKEBASE_SCHEMA=docintel_app export PGDATABASE=docintel-demo-state-v1 export PGUSER= export PGPORT=5432 @@ -49,13 +50,11 @@ streamlit run app/app.py Local runs do not have the Databricks Apps `x-forwarded-access-token` header, so they cannot validate the Agent Bricks OBO path. Use a deployed OBO-enabled target for prod identity validation. -If you accidentally run Lakebase schema initialization with user creds (`DATABRICKS_CLIENT_ID`/`SECRET` unset), `lakebase_client.init_schema()` logs a warning identifying the mismatch. The tables get created under your user account, not the App SP, and the deployed App will lose write access. Drop the user-owned tables and re-init under the App SP to recover: +If you accidentally run Lakebase schema initialization with user creds (`DATABRICKS_CLIENT_ID`/`SECRET` unset), `lakebase_client.init_schema()` logs a warning identifying the mismatch. The schema gets created under your user account, not the App SP, and the deployed App will lose write access. Drop the user-owned schema and re-init under the App SP to recover: ```sql -- connected as the App SP via the local-dev env above -DROP TABLE IF EXISTS feedback CASCADE; -DROP TABLE IF EXISTS query_logs CASCADE; -DROP TABLE IF EXISTS conversation_history CASCADE; +DROP SCHEMA IF EXISTS docintel_app CASCADE; -- next streamlit run will re-init under the App SP ``` diff --git a/app/lakebase_client.py b/app/lakebase_client.py index 38a8855..286dfd5 100644 --- a/app/lakebase_client.py +++ b/app/lakebase_client.py @@ -25,21 +25,35 @@ from typing import Iterator import psycopg +from psycopg import sql from databricks.sdk import WorkspaceClient _log = logging.getLogger(__name__) -_SCHEMA = """ -CREATE TABLE IF NOT EXISTS conversation_history ( +def _lakebase_schema() -> str: + return os.environ.get("DOCINTEL_LAKEBASE_SCHEMA", "docintel_app") + + +def _table(name: str) -> sql.Identifier: + return sql.Identifier(_lakebase_schema(), name) + + +def _schema_ddl() -> sql.Composed: + conversation_history = _table("conversation_history") + query_logs = _table("query_logs") + feedback = _table("feedback") + return sql.SQL( + """ +CREATE TABLE IF NOT EXISTS {conversation_history} ( conversation_id UUID PRIMARY KEY, user_email TEXT NOT NULL, started_at TIMESTAMPTZ NOT NULL DEFAULT now(), last_turn_at TIMESTAMPTZ NOT NULL DEFAULT now() ); -CREATE TABLE IF NOT EXISTS query_logs ( +CREATE TABLE IF NOT EXISTS {query_logs} ( turn_id UUID PRIMARY KEY, - conversation_id UUID REFERENCES conversation_history(conversation_id), + conversation_id UUID REFERENCES {conversation_history}(conversation_id), question TEXT NOT NULL, answer TEXT NOT NULL, citations JSONB NOT NULL, @@ -47,15 +61,20 @@ agent_path TEXT NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); -CREATE TABLE IF NOT EXISTS feedback ( +CREATE TABLE IF NOT EXISTS {feedback} ( feedback_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - turn_id UUID REFERENCES query_logs(turn_id), + turn_id UUID REFERENCES {query_logs}(turn_id), user_email TEXT NOT NULL, rating TEXT NOT NULL CHECK (rating IN ('up','down')), comment TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); """ + ).format( + conversation_history=conversation_history, + query_logs=query_logs, + feedback=feedback, + ) @contextmanager @@ -98,7 +117,7 @@ def _generate_lakebase_password() -> str: def init_schema() -> None: - """Idempotent CREATE TABLE IF NOT EXISTS. Logs the connected role so + """Idempotent CREATE SCHEMA/TABLE IF NOT EXISTS. Logs the connected role so deployed-vs-local identity divergence is debuggable from app logs. """ with _conn() as c, c.cursor() as cur: @@ -115,14 +134,19 @@ def init_schema() -> None: ) else: _log.info("Lakebase init connected as %r", connected_user) - cur.execute(_SCHEMA) + cur.execute( + sql.SQL("CREATE SCHEMA IF NOT EXISTS {}").format(sql.Identifier(_lakebase_schema())) + ) + cur.execute(_schema_ddl()) def ensure_conversation(conversation_id: uuid.UUID, user_email: str) -> None: with _conn() as c, c.cursor() as cur: cur.execute( - "INSERT INTO conversation_history (conversation_id, user_email) VALUES (%s, %s) " - "ON CONFLICT (conversation_id) DO UPDATE SET last_turn_at = now()", + sql.SQL( + "INSERT INTO {table} (conversation_id, user_email) VALUES (%s, %s) " + "ON CONFLICT (conversation_id) DO UPDATE SET last_turn_at = now()" + ).format(table=_table("conversation_history")), (conversation_id, user_email), ) @@ -130,8 +154,10 @@ def ensure_conversation(conversation_id: uuid.UUID, user_email: str) -> None: def log_turn(*, turn_id: str, conversation_id: uuid.UUID, response: dict, question: str) -> None: with _conn() as c, c.cursor() as cur: cur.execute( - "INSERT INTO query_logs (turn_id, conversation_id, question, answer, citations, latency_ms, agent_path) " - "VALUES (%s, %s, %s, %s, %s::jsonb, %s, %s)", + sql.SQL( + "INSERT INTO {table} (turn_id, conversation_id, question, answer, citations, latency_ms, agent_path) " + "VALUES (%s, %s, %s, %s, %s::jsonb, %s, %s)" + ).format(table=_table("query_logs")), ( turn_id, conversation_id, @@ -147,6 +173,8 @@ def log_turn(*, turn_id: str, conversation_id: uuid.UUID, response: dict, questi def write_feedback(*, turn_id: str, user_email: str, rating: str, comment: str | None) -> None: with _conn() as c, c.cursor() as cur: cur.execute( - "INSERT INTO feedback (turn_id, user_email, rating, comment) VALUES (%s, %s, %s, %s)", + sql.SQL("INSERT INTO {table} (turn_id, user_email, rating, comment) VALUES (%s, %s, %s, %s)").format( + table=_table("feedback") + ), (turn_id, user_email, rating, comment), ) diff --git a/docs/runbook.md b/docs/runbook.md index 387686f..fb3c065 100644 --- a/docs/runbook.md +++ b/docs/runbook.md @@ -84,10 +84,10 @@ Metric key names can vary across MLflow/databricks-agents versions. The eval run | Agent answers ignore user UC permissions in prod | OBO scopes wiped by `bundle run` (documented destructive-update behavior — see [Databricks Apps deploy docs](https://docs.databricks.com/aws/en/dev-tools/databricks-apps/deploy)) | Re-apply scopes to the target app: `databricks apps update --user-api-scopes serving.serving-endpoints,sql,iam.access-control:read,iam.current-user:read` | | Agent deployment cannot grant endpoint query permission | Permissions API was called with endpoint name instead of internal endpoint ID, or the generated endpoint is not ready | Use current `agent/document_intelligence_agent.py`; it waits for readiness and grants by serving endpoint ID | | Streamlit user sees stale UC permissions | OBO token captured at WebSocket open; never refreshes ([Databricks Apps runtime docs](https://docs.databricks.com/aws/en/dev-tools/databricks-apps/app-runtime)) | Reload the page after permission changes | -| Lakebase tables not writable from deployed App | Local-dev `streamlit run` initialised schema under user identity, not App SP | Connect as App SP and `DROP TABLE feedback, query_logs, conversation_history`; next App run re-creates them under SP. See `app/README.md` | +| Lakebase tables not writable from deployed App | Local-dev `streamlit run` initialised the `docintel_app` schema under user identity, not App SP | Connect as App SP and `DROP SCHEMA docintel_app CASCADE`; next App run re-creates it under SP. See `app/README.md` | | CLEARS Latency axis fails | Agent Bricks orchestration or Knowledge Assistant source is too broad | Narrow the Knowledge Assistant source, tune Supervisor instructions, or reduce structured-tool fan-out | | Citation chips render but filenames show `source` | Knowledge Assistant footnote format changed or omitted filename markers | Capture the raw Agent Bricks payload and compare it with `app/agent_bricks_response.py`'s markdown-footnote parser | -| App errors connecting to Lakebase | Database resource binding missing connection fields, or OAuth credential minting failed | Check the `docintel-lakebase` resource binding plus `PGHOST`/`PGPORT`/`PGUSER`/`PGDATABASE`/`DOCINTEL_LAKEBASE_INSTANCE` in the App runtime. `PGPASSWORD` is minted at connection time by `app/lakebase_client.py` | +| App errors connecting to Lakebase | Database resource binding missing connection fields, OAuth credential minting failed, or App SP lacks Lakebase instance `CAN_USE` | Check the `docintel-lakebase` resource binding plus `PGHOST`/`PGPORT`/`PGUSER`/`PGDATABASE`/`DOCINTEL_LAKEBASE_INSTANCE`/`DOCINTEL_LAKEBASE_SCHEMA` in the App runtime. `PGPASSWORD` is minted at connection time by `app/lakebase_client.py` | ## Verifying end-to-end OBO diff --git a/resources/consumers/analyst.app.yml b/resources/consumers/analyst.app.yml index 7dc1842..7e124b5 100644 --- a/resources/consumers/analyst.app.yml +++ b/resources/consumers/analyst.app.yml @@ -13,6 +13,8 @@ resources: value: ${var.app_obo_required} - name: DOCINTEL_LAKEBASE_INSTANCE value: ${var.lakebase_instance} + - name: DOCINTEL_LAKEBASE_SCHEMA + value: docintel_app # Databricks Apps auto-grants Lakebase permissions to the App SP on # deploy — see https://docs.databricks.com/aws/en/dev-tools/databricks-apps/access-data. diff --git a/scripts/bootstrap-demo.sh b/scripts/bootstrap-demo.sh index 40122a1..a517f05 100755 --- a/scripts/bootstrap-demo.sh +++ b/scripts/bootstrap-demo.sh @@ -204,19 +204,9 @@ for i in d.get('database_instances', []): done } -grant_app_sp_endpoint_query() { +app_sp_principals() { local app_json="$1" - local endpoint_json endpoint_id principals principal grant_json - endpoint_json=$(databricks serving-endpoints get "$AGENT_ENDPOINT_NAME" --output json) - endpoint_id=$(printf '%s' "$endpoint_json" | "$PYTHON" -c " -import json, sys -endpoint = json.load(sys.stdin) -print(endpoint.get('id') or endpoint.get('name') or '$AGENT_ENDPOINT_NAME') -") - principals=() - while IFS= read -r principal; do - [[ -n "$principal" ]] && principals+=("$principal") - done < <(printf '%s' "$app_json" | "$PYTHON" -c " + printf '%s' "$app_json" | "$PYTHON" -c " import json, sys app = json.load(sys.stdin) seen = set() @@ -228,7 +218,50 @@ for key in ('service_principal_client_id', 'service_principal_name', 'service_pr if value and value not in seen: seen.add(value) print(value) +" +} + +grant_app_sp_lakebase_use() { + local app_json="$1" + local principals principal grant_json + principals=() + while IFS= read -r principal; do + [[ -n "$principal" ]] && principals+=("$principal") + done < <(app_sp_principals "$app_json") + if (( ${#principals[@]} == 0 )); then + die "app service principal was not returned by Databricks Apps API" + fi + for principal in "${principals[@]}"; do + grant_json=$("$PYTHON" -c " +import json, sys +print(json.dumps({ + 'access_control_list': [{ + 'service_principal_name': sys.argv[1], + 'permission_level': 'CAN_USE', + }] +})) +" "$principal") + if databricks permissions update database-instances "$LAKEBASE_NAME" --json "$grant_json" >/dev/null 2>&1; then + log " granted CAN_USE on Lakebase $LAKEBASE_NAME to App SP $principal" + return 0 + fi + done + die "failed to grant CAN_USE on Lakebase $LAKEBASE_NAME to the App service principal" +} + +grant_app_sp_endpoint_query() { + local app_json="$1" + local endpoint_json endpoint_id principals principal grant_json + endpoint_json=$(databricks serving-endpoints get "$AGENT_ENDPOINT_NAME" --output json) + endpoint_id=$(printf '%s' "$endpoint_json" | "$PYTHON" -c " +import json, sys +endpoint = json.load(sys.stdin) +print(endpoint.get('id') or endpoint.get('name') or '$AGENT_ENDPOINT_NAME') ") + principals=() + while IFS= read -r principal; do + [[ -n "$principal" ]] && principals+=("$principal") + done < <(app_sp_principals "$app_json") if (( ${#principals[@]} == 0 )); then die "app service principal was not returned by Databricks Apps API" fi @@ -365,6 +398,7 @@ databricks api patch \ log " verifying app auth mode on $APP_NAME" if app_state=$(databricks apps get "$APP_NAME" --output json 2>/dev/null); then + grant_app_sp_lakebase_use "$app_state" if [[ "$APP_OBO_REQUIRED" == "true" ]]; then "$PYTHON" -c " import json diff --git a/specs/001-doc-intel-10k/data-model.md b/specs/001-doc-intel-10k/data-model.md index a2a0a65..cc3e65c 100644 --- a/specs/001-doc-intel-10k/data-model.md +++ b/specs/001-doc-intel-10k/data-model.md @@ -1,6 +1,6 @@ # Phase 1 Data Model -All Delta tables live under the bundle-parameterized `${var.catalog}.${var.schema}`. Lakebase tables live in the bundle-managed Lakebase database instance `${var.lakebase_instance}`, exposed to SQL dashboards through the UC database catalog `${var.schema}_state`. +All Delta tables live under the bundle-parameterized `${var.catalog}.${var.schema}`. Lakebase tables live in schema `docintel_app` inside the bundle-managed Lakebase database instance `${var.lakebase_instance}`, exposed to SQL dashboards through the UC database catalog `${var.schema}_state`. ## Bronze diff --git a/specs/001-doc-intel-10k/tasks.md b/specs/001-doc-intel-10k/tasks.md index 0ab81ec..c1bff42 100644 --- a/specs/001-doc-intel-10k/tasks.md +++ b/specs/001-doc-intel-10k/tasks.md @@ -38,7 +38,7 @@ This is a DAB plus Agent Bricks deployment project. SQL pipeline code is at `pip **⚠️ CRITICAL**: All user stories depend on these. - [x] T006 Define UC catalog/schema/volume in `resources/foundation/catalog.yml`: `${var.catalog}.${var.schema}` schema + `raw_filings` volume; grant `USE_CATALOG`, `USE_SCHEMA`, `READ_VOLUME` to a configurable analyst group -- [x] T007 [P] Define the Lakebase instance/catalog in `resources/foundation/lakebase_instance.yml` and `resources/consumers/lakebase_catalog.yml`; the UC catalog is `${var.schema}_state`, while `app/lakebase_client.py` creates `conversation_history`, `query_logs`, and `feedback` tables at runtime using App resource binding fields plus Databricks-minted Lakebase OAuth credentials +- [x] T007 [P] Define the Lakebase instance/catalog in `resources/foundation/lakebase_instance.yml` and `resources/consumers/lakebase_catalog.yml`; the UC catalog is `${var.schema}_state`, while `app/lakebase_client.py` creates `docintel_app.conversation_history`, `docintel_app.query_logs`, and `docintel_app.feedback` at runtime using App resource binding fields plus Databricks-minted Lakebase OAuth credentials - [x] T008 [P] Add JSON contracts under `specs/001-doc-intel-10k/contracts/`: `agent-request.json`, `agent-response.json`, `feedback-event.json`, `kpi-schema.json` **Checkpoint**: catalog, schema, volume, Lakebase database exist; bundle validates. diff --git a/src/dashboards/usage.lvdash.json b/src/dashboards/usage.lvdash.json index 3aba983..45ae00d 100644 --- a/src/dashboards/usage.lvdash.json +++ b/src/dashboards/usage.lvdash.json @@ -26,7 +26,7 @@ "SELECT created_at::date AS day, agent_path, count(*) AS turns,", " percentile_approx(latency_ms, 0.95) AS latency_p95_ms,", " sum(CASE WHEN array_size(citations) = 0 THEN 1 ELSE 0 END) AS ungrounded", - "FROM `__dataset_schema___state`.public.query_logs", + "FROM `__dataset_schema___state`.docintel_app.query_logs", "GROUP BY 1, 2 ORDER BY 1 DESC" ] } From bfa8f364eed44dbfcfbeb4d5d019546c862a821f Mon Sep 17 00:00:00 2001 From: Sathish Krishnan <10681383+SatyKrish@users.noreply.github.com> Date: Mon, 27 Apr 2026 00:06:50 -0400 Subject: [PATCH 6/7] Fix structured KPI citation grounding Synthesize citation chips for structured KPI answers when Agent Bricks returns a grounded UC function answer with a source filing but no citation payload. Validated with pytest agent/tests, databricks bundle validate -t demo, steady-state demo deploy, and dogfood question: ACME FY2024 revenue returned ACME_10K_2024.pdf with confidence 99.97%. --- agent/document_intelligence_agent.py | 6 +- agent/tests/test_agent_bricks_response.py | 92 +++++++++++++++++++++++ app/agent_bricks_response.py | 79 ++++++++++++++++--- app/app.py | 2 +- 4 files changed, 167 insertions(+), 12 deletions(-) diff --git a/agent/document_intelligence_agent.py b/agent/document_intelligence_agent.py index c8a64b8..af825f2 100644 --- a/agent/document_intelligence_agent.py +++ b/agent/document_intelligence_agent.py @@ -254,8 +254,10 @@ def _ensure_supervisor( instructions = ( "Use the Knowledge Assistant for narrative or section-level questions. " "Use the Unity Catalog KPI function for structured financial metrics " - "and cross-company comparisons. Do not invent figures; cite the filing " - "source or state that the corpus does not contain the answer." + "and cross-company comparisons. For KPI function answers, include the " + "source filename and extraction confidence in the final answer. Do not " + "invent figures; cite the filing source or state that the corpus does " + "not contain the answer." ) desired = SupervisorAgent( display_name=display_name, diff --git a/agent/tests/test_agent_bricks_response.py b/agent/tests/test_agent_bricks_response.py index 7d3d4f9..63b308b 100644 --- a/agent/tests/test_agent_bricks_response.py +++ b/agent/tests/test_agent_bricks_response.py @@ -69,6 +69,98 @@ def test_extract_citations_returns_empty_without_structured_sources_or_footnotes assert extract_citations(payload) == [] +def test_extract_citations_from_structured_kpi_answer() -> None: + payload = { + "output": [ + { + "type": "message", + "content": [ + { + "type": "output_text", + "text": ( + "ACME Corporation's revenue in fiscal year 2024 was $94.20 billion.\n\n" + "This information was extracted from ACME's official 10-K filing " + "(ACME_10K_2024.pdf) with high confidence (99.97%)." + ), + } + ], + } + ] + } + + citations = extract_citations(payload) + + assert citations == [ + { + "filename": "ACME_10K_2024.pdf", + "section_label": "Structured KPI extract", + "snippet": ( + "This information was extracted from ACME's official 10-K filing " + "(ACME_10K_2024.pdf) with high confidence (99.97%)." + ), + "score": 0.9997, + } + ] + + +def test_normalise_agent_response_marks_structured_kpi_answer_grounded() -> None: + response = normalise_agent_response({ + "output_text": ( + "Revenue was $94.20 billion, sourced from ACME_10K_2024.pdf " + "with extraction confidence 99.97%." + ) + }) + + assert response["grounded"] is True + assert response["retrieved_count"] == 1 + assert response["citations"][0]["filename"] == "ACME_10K_2024.pdf" + assert response["citations"][0]["score"] == 0.9997 + + +def test_extract_citations_keeps_unsupported_answer_ungrounded() -> None: + payload = { + "output_text": ( + "The corpus does not contain a grounded answer for this metric. " + "No source in ACME_10K_2024.pdf supports the requested value." + ) + } + + assert extract_citations(payload) == [] + + +def test_extract_citations_prefers_knowledge_footnotes_over_kpi_fallback() -> None: + payload = { + "output": [ + { + "type": "message", + "content": [ + { + "type": "output_text", + "text": "[^p1]: Revenue was $94.2B. _ACME_10K_2024.pdf_", + } + ], + }, + { + "type": "message", + "content": [ + { + "type": "output_text", + "text": ( + "Revenue was $94.20 billion, sourced from ACME_10K_2024.pdf " + "with confidence 99.97%." + ), + } + ], + }, + ] + } + + citations = extract_citations(payload) + + assert len(citations) == 1 + assert citations[0]["section_label"] == "Knowledge Assistant citation" + + def test_normalise_agent_response_coerces_citations_and_latency() -> None: response = normalise_agent_response( { diff --git a/app/agent_bricks_response.py b/app/agent_bricks_response.py index be0fb35..4e2b282 100644 --- a/app/agent_bricks_response.py +++ b/app/agent_bricks_response.py @@ -2,8 +2,8 @@ from __future__ import annotations -import uuid import re +import uuid from collections.abc import Mapping from typing import Any @@ -16,6 +16,19 @@ # without a parseable filename and [] when no footnotes are present. APP_EMPTY_TEXT = "The Agent Bricks endpoint returned a response without displayable text." FILENAME_RE = re.compile(r"_([A-Za-z0-9][A-Za-z0-9_.-]*\.pdf)_") +PDF_FILENAME_RE = re.compile(r"\b([A-Za-z0-9][A-Za-z0-9_.-]*\.pdf)\b") +CONFIDENCE_PERCENT_RE = re.compile( + r"\b(?:confidence|extraction[_ -]?confidence)\b[^\d%]{0,40}(\d+(?:\.\d+)?)\s*%", + re.IGNORECASE, +) +UNGROUNDED_RE = re.compile( + r"\b(" + r"cannot determine|could not find|does not contain(?: a)? grounded answer|" + r"no grounded answer|no source|not available|not found|unable to determine|" + r"without a grounded source" + r")\b", + re.IGNORECASE, +) def _output_text_groups(payload: Mapping[str, Any]) -> list[str]: @@ -63,16 +76,61 @@ def extract_text(payload: Mapping[str, Any], *, empty_text: str = "") -> str: return empty_text +def _confidence_score(text: str) -> float | None: + match = CONFIDENCE_PERCENT_RE.search(text) + if not match: + return None + try: + percent = float(match.group(1)) + except ValueError: + return None + if percent < 0 or percent > 100: + return None + return percent / 100 + + +def _source_snippet(text: str, filename: str) -> str: + for line in text.splitlines(): + stripped = line.strip(" -*\t") + if filename in stripped: + return stripped + return f"Structured KPI answer cited {filename}." + + +def _structured_kpi_citation(payload: Mapping[str, Any]) -> list[dict[str, Any]]: + answer = extract_text(payload) + if not answer or UNGROUNDED_RE.search(answer): + return [] + + match = PDF_FILENAME_RE.search(answer) + if not match: + return [] + + filename = match.group(1) + citation: dict[str, Any] = { + "filename": filename, + "section_label": "Structured KPI extract", + "snippet": _source_snippet(answer, filename), + } + score = _confidence_score(answer) + if score is not None: + citation["score"] = score + return [citation] + + def extract_citations(payload: Mapping[str, Any]) -> list[dict[str, Any]]: citations = payload.get("citations") or payload.get("sources") or [] - if not isinstance(citations, list): - return [] normalized: list[dict[str, Any]] = [] - for citation in citations: - if isinstance(citation, Mapping): - normalized.append(dict(citation)) - elif citation is not None: - normalized.append({"source": str(citation)}) + if isinstance(citations, list): + for citation in citations: + if isinstance(citation, Mapping): + normalized.append(dict(citation)) + elif citation is not None: + normalized.append({"source": str(citation)}) + elif isinstance(citations, Mapping): + normalized.append(dict(citations)) + elif citations: + normalized.append({"source": str(citations)}) if normalized: return normalized @@ -93,7 +151,10 @@ def extract_citations(payload: Mapping[str, Any]) -> list[dict[str, Any]]: "section_label": "Knowledge Assistant citation", "snippet": snippet, }) - return normalized + if normalized: + return normalized + + return _structured_kpi_citation(payload) def normalise_agent_response( diff --git a/app/app.py b/app/app.py index b13e592..384616f 100644 --- a/app/app.py +++ b/app/app.py @@ -92,7 +92,7 @@ def _ensure_session() -> tuple[str, str]: def _render_citations(citations: list[dict]) -> None: if not citations: - st.caption("No citations — the agent did not find a grounded source.") + st.caption("No citation chips returned for this response.") return cols = st.columns(min(len(citations), 4)) for i, c in enumerate(citations[:4]): From b02c3269c13667720cc96b211f7bda859bcffeba Mon Sep 17 00:00:00 2001 From: Sathish Krishnan <10681383+SatyKrish@users.noreply.github.com> Date: Mon, 27 Apr 2026 00:14:14 -0400 Subject: [PATCH 7/7] Add deployed app validation screenshot --- README.md | 4 ++++ docs/databricks-app-dogfood.png | Bin 0 -> 290591 bytes 2 files changed, 4 insertions(+) create mode 100644 docs/databricks-app-dogfood.png diff --git a/README.md b/README.md index 5e9721c..fb759ff 100644 --- a/README.md +++ b/README.md @@ -214,6 +214,10 @@ In the workspace UI: **Apps → `doc-intel-analyst-demo`**. Ask: You should see a grounded answer with citation chips linking to `ACME_10K_2024.pdf` / `Risk`. +Example deployed Databricks App validation: + +![Deployed 10-K Analyst app showing an ACME revenue answer with a structured KPI citation chip](./docs/databricks-app-dogfood.png) + ### 7. Steady-state deploys After the first bring-up, iteration depends on what changed: diff --git a/docs/databricks-app-dogfood.png b/docs/databricks-app-dogfood.png new file mode 100644 index 0000000000000000000000000000000000000000..811e5d1544f09d8380f530785a865cd66d488b70 GIT binary patch literal 290591 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%0A|^q7JO8Xn+Ql2$Gha0CgwY8fqu5^mgaO?W0h{0 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 z zAlip{+c-NO-#RC-fsY{0=40e2ii|{ESNRjAEc~zmnLlr+QyN1y}xEH)&Hqvu1g$tr?3d6NQHP7eCy26-mq= zI&qrr(b_2xHg%b*F$5ygg7jTiCdWMD`z~hgAB4@>@zn}dyja^c(~p|+`le!$f(Kar ztjiCIth^e=1f?J&V5f_K(S-%(1&)Q^EE|X)Xo8tRs_ij8IO=k)fEOcbh1!#bQ!9oL zD?iYtt|kCOsb!*Pq96bXsiyoXo8zG4_ix^Wtr%8Ah1+Re@C$QHIJZBCey+VQG5d|u zeZIZ+wiAm_824~=t29)06Pf8Pqz=b24!Tl}_6Ox$d3K#>Oorb$=DH-&CsfGe+lYpY zJ^VrKR=?e2N6v4B_0EUl?_UMO=~xLJx7vd*O}*J)=ew;yb#ViAohzElVSyDN#z7NN z?jOY_CJeL+y$|2dKK5*sWTN4RZV?O*Njxy+))}XP-iFM5 z;$7e`YMpJD8IkbCeQlP&G$ZqUs=mFDilc1%^66}A*f$XxO%EP2y8xfHxKg7VmADye zfkB z4mxQ%d!Ht8v(0w-tVN3Ggk0#unwihBZFqj8d!5&_x{U><2a7X;psQ1gU~}_O`TT{O zGd>tra1LiW_Wed$DBoYjep6@QiziO1>%SrRPeJy*mCAc>>lBbciGQOV=@o@oY*~l3 zBBp+;9x7cOKEY@^JlAG+QAmy1?dqDTg0gtBh+oqn8ReG|wi2ko&)ySwX$tb}W2chT z|4nWCC)wq{dfQ>fpi%UDJhhc`BZJ{bx$@ln^2d*?lW=D9ebZ_<68iy!ww6~<7xL9s zJ8|YHw2Rnd{9VsDWA-#LJH?*{Myp=$V?U{-D#7f=Xw#$}6jbBj%fqx$hldu!!KH5U z-qGQ8merfJhZEemhx>(xoWB7wz9Jk9g!@L_qM%#su#n!=m2dz!vve*)08wQ zHR|`WJ`H{zRsOD1loH}?gt{!l&VZZ7s!rMKDsz(d@$JaTY%Yt@689bHaoFs@M?ih| zN~pi;fbsy4f{HF91}quP@WQ5S@xD({_?U&0jAd=Lo%ohFzqG^COo4s(2!!}_A6oZ`$I}#l!hVJfB9_#3o_7|J6 zs;SUku6K8L_fP$zEMN?T07Ey+EZFp!JC<*=qU7f_E%SezNC9j>M`Whwc+H?XzZML(V` zg_K;O$k?wdmWzIE+T1QaSWw*+kXMEXv0gk5EvD2KfMUKD1Ist4ydm(_JdiYYV7k%- z4zn3Aw0q9eDAb=FH`HI?)R3Xie0NcG4{gw76%2&hfn=Qb;b`I9cQiam{?r-T>t4>~ z@-6jDpKAuLtP@sBAn_!VMm(Is(~Q`3L&29=i+AOgkU@dEckX+(A5{aKIh2w{^}>}j zwor!~dZJma6iRDyh1dS7`BA0XicnKbfy(vDzV5 zcG0fhu)PtO->wPk^Dl-1;)EtV=zUD4iaQTatbZy(o<=$wI(ccfFJATL)%E)dDCvno zH_i})FhqQ-cPHiKaBB~GNC$EzkkDVh{(w_p$0xsD!}gGdMM9-)i^ zSATlO0tVoKM7w*80w(u}NqicCw{^;@UbHipB8$Z`x#M$Bo}Zr$)K96mIUj+uG9;_f5uT4Xs@ar|6F5uU}}`H%pe>RL~#uMH&R(QFz;W3y3sg_3jM9 zS4HRc9BtMFN3Yn2-h7huq*Hw+S04|pLNB*qRhiRrxP)GetVLmZGCzOqYv(tH*6Ta| z&}#E1PW4KU>=(AhmdgpI>-t(MM}enjq4^XO4UE{)AqbW(13IgXA-GS zaN$sxAH{VY_z&?xN7Z`CBY+0ooJheh>M;D} zK*krMJDA%3;}F>dIkR)&SDVdw0`4h_e#~_wD&xxW=DjjKwbcf9RYAX~xVi~3rS*Bl z$DBguae~L3#FF$W`90=AZj8wK@4P3&_{QK^URU|sCN=S+t`kqwK6~u>@fj^RWU6sT z&CJQ%y!vpLhZRy&je#kZ&j0~^!iP!3ssaOB+KBncL6134W8n!>t}|#Uln*AyF;urL zsNJ?NCZ%Li;Vmu8;OCgwZw<=QaT1!J8waFPt z_F2n*vyrFm(IxwMmflFa&9{>$(#Yh zxK2)}+^V5C9MK?(f$L5K@~+N>9PKn+F?Gj69d_l-_a}+T1ZSh#c&qMd^VFb)N?z;F z?OgYcTe-t!0gQ(CT}@TJ4m7o?&0(XwQH;>*b+Z%t8}@QmC3-#-{%{goK+ZF|8lC>$l%-u8*SUaTqJ% zj4SZBN*a18ggJY5YSSC2hD&?nrA8(rhd89<@pDnC=-rqTH-cA>hUA@tZ`(Ixd+{-J zCfO{5KS{tmw#-y|a%`{J43~gl;`^itF^9w<&R-dsD!AVrsl?~_t{auD%8l@A?E;6N1-X91rM3^bV>lET35&MG@Fgn(G7aiS0C7iUKIp> z1F!Wf40HuP%-61clxc#-*F68KG-O+uu zvRywmAJcggNq@Q$K0{?IwsoxRoMBgw0lRKzjN`M^w#$YE2M~F{oa<{39>?ZLMTI#}}DdEf`NbNuH!ff^ycDyuc+Q%am6;G(Q zW$;3ry;MlB$MH3-h$y{G_2j-Bus)C?(^;F0uf?}@lzypf-364AqMYy=2XdxeMRWoc z_4OOc5#Fh9Xt2hH+7-`EdWuTnmv6#@U!AKu&AbuJY zlht8fQJSE#2m!saWANtdJu&>CtoNBKR{gQxjreE9j~yCU1rENMm5WSO?)D3<59zsx zp1RbNB#6C(F@2Gb_k_Q7#5aKi$p<7HX`VNjfOQ!{Z0<1x*>b#eN#!@zb7gpLjEE10 z7ompK(^R7LBaz*Z8*e<3-e$@#+!1>@SWJKNP4fPZJ1o345tMEsKN^e8N{hy4wbQ)=a&?X~?T*Sv0uO)RC!wR- zTOM7HYA3%+DG0V5pl9%EE=}Kmi#VIxe$=3v;XJcCWeSFKSshLr+7!5A&9ac=J8c#> zaceG=*A0CvI}ijvjV%oC1rnhM0;`1XI?5;zea9U09092?ibGkIJDbXMfNe2S>O$(XNleWjjR!wWIH5hL|D z>7KzJg1UmU0%+h{ZGVg@BeK%?qZ?LqToZ)M#)rP7sVE0mq~jNR6=gESeyS+9R>tnE zjsJY7-LdmqRqth3LP2op00$k-pHhRYmiv2;B--&O46Bt|%^$A%TKg-*)lAy2Le5ym#&ARL7$*Sk%JIRvITlh}&HsWME$cj4Lg9V;J!Ar-} znOGrpo~4198RFt-p$^Z5)B{XK)2KB`p5m}(X}dW|zP z#PncZitrG^(ZOS1fTK8bku{H`FV3v8WY&F#5}x&8-Nj z+3dCc48aj2?mYE!(Q7;AQQjk_Kc1tOe{i{*EG@iI0g_YpB0TnB?&TXpWxvZb4~Mm0 zu;EEz%VAuuQ9*-e4<~KPqgGTaJd;4?OCQGka+px5Y~s`8hApjc*s6&m9nTV2vM<n+86!{R00S0!2aqz1w(-0i?iw(wa;dqEnTP`^SAx`xC z-hVnL@fw%7eWwdH{bPGUidb=~{8Q;{g@hgkoDEhgwVlIZR1oiub;UNj0%K8?xN6EM znHo+NVqpCRw)T1b_IR6xFzbfDY~#_Pc9~lG`iO>T*JyI!K7WyC%aUUO=pyDlUse6A z)Kt~;dg@Il8;+B-HRI^aqa4Nd9?pHQ*-cbL_OM}&+Q|dqmL(wl`$40J^;#*+>o#jv zsb*G)O*#X2EEs#JUlC7bu-v%C(|X9}TTv=1p_BrKUS()x=2y264Ot5@32c_=GgGNt zk7=$?%)Q86f{QDNK3fcZhkp!oW5rO1((Sy>^pC-%8qm!T35z0QLylFFr)+9J70#6- z07;YDD38{=fIx{XlWhkYoEbYpxTRVg(#0+NCi+ih)1M+5dIljG~)5w4)jM8T5 zZPgvAtvH~vvyjWC=-PDREE8rG1kt$461lAD zK9a!><$TSZ+=I^wN?oQa!z$p)Fj?H$D>W}ikP32|?oUVhFFz86SPEcIB9htJ)Z8WIkwS(Pj zxSy^$HD9i3BKZehE&`r>al=((pGEdP2^Jk z_>T2h4iS!9W^)K1^ovIniGYgAbCj~URV`Iim_mMA;`hPS5$F-YDCJ(wr<^BNo@p*H z=ww{20gVO$#u=ByP4&u8^!IGW76Z~}emB$`SbMFuV3w>_cCVmI38_Y`rgO!-1S~RKuh3!OPFX*4AF~hn~I3v9v&=S|M+;Q{)UEyduLU`T`b^FSdwDY4kg#iZ%h-}!K2I-`(HAjS2<&OP}OP_)xUrH zH!-go{5X+nD3ag9>OK7&vwBuy#&3I@>74q72qE77ECUN|dW`FEMztaIEnrPuO~y-i1_3?l`vF!C9FJL=(R z6mxu9Pa#yJkWVOHX);`VtYyx4QCqF6cxk}+7GtE~CF4Tf-lg#pfAS6+Y5dw4S6f_t zwQ-e|M1!>Yn<*&Gfg2C2kOE|3|H(l4B-WQ4yT9m2eSV{!-F4QvKF{462^i;QyLEGD zrRGebc~i{HxS{w&3sw7FGb^vBF#k%E-Xp@GY&J5b9AUSqSaIh|(}3Ug*?F(^zUG1E z(rTyNrCNe*l1tQMe8FB*2zT1P<3vSUUxvh!G_D3oR3{u-*Xj3rLB%i0Hyz1!Z8;od zM^mtldgV4rBTN`Oj;8AfvZKton`9J%H?}MlI&)XdVN3)+}zfeG8=?YRDX`mGd-DFT`66Q?c61DNA|q zn3h>btXq)nIpRd@KfSIM@VrVqt2*E@e()xUBBNx{;(Pcb0A%B~?4Z_5T)uT}&o*zN zJGMKZ!_Gu8{9&YKK6YDTNtOAu@M>*RK!}`ZG<;y1w6)cRO8ZV8==@H29NJ>jm*#w5 zc{K(9Vc^X&q?&kr*_Y;8u~93+syAKv&eIYe20q;+f6DM~OQjRmQ#JL?A#&Riu%$ls zON^M2NkFP7f44FM1tVqF>#F)zqO%04s3T~1rcj@_Y|q1SklxqAoz zPT#NBh)?vxHx|v`GVo$2uMu@tvDNB>ilN3Ojkh~5Nbia?xt&;{X)poDqXPcpeG@S{{pidD7yrj&*Ms< zz=yNz0Y<1DrRzJAryDQi`qc}JXFn~}?oKN=s;!c$J=_djlr`C}U%iMC1&&^!G67C@ zjfjgpwBi%(M=$U1O~eUXe0yY%T9^0vlT}wHxZo&f{&x3b@31wWzchhYCHA2Q6KWl_ zWf`5HT>c`n`j$OVG^2aZX*?IDuRHIp-A&(6cv9#BBR`xq%1meWY1;$c#w-1H`a$HK zq(;LBl5p33SYxHosbTc|tQTcHnS?R)+4!ZNtrOJk7z5hN?Ky5Ue)!h1eu~JmbgAME zcMzXKh{&5kZGL(Fja9qpq8l#gI>Cp^@oxFT*6T?jlouqc7e>@FCVN5OBQBU?58W&6 zIec~*J3^Eq5MkYCcgJF$?(O>Y=yuFKFp()oT$7g{ym-xtO5Ok9n#* zw}}we&)XWq#OS(UG3b&>$qWa&;{nQw8JL0GU_pEvu}!2`4BxuD)3iYqY46rJ?s`*3 zqy2zc@@&r?FnxJ$KbJrj8TGwkDj?KGp)~bs@2~2=PwgNFa%kG6cp{jNl6kZod#5m% zHTV=CRqIBYWV@*8+{F0#(?g)AMj>A_40>96V9*&CV1Cs*vX6%(YQ#y`PLig3E{G$o zz1E0SA|YzHN@GvxyPGNd0U0zJigGu9fO?x_UPhLXNnc7!qD zr;rgB+3qjgwHUyjy;a^KJ(W;yp6fE)5W|Q6^f|O_@zLF7#**Tvg=nmlyZ?yaJ2(>$Uk z7PyFq%A-0(zrU4NMPK{a0^c&N@@ga%a8auIAF=9pso#2@Pe82*et&k!m zn)_~4{SQmur98RSd3h}Rn-CGm#=^h9$83!^Y;LXc2`0;*((B!UmdRgsz^ctO!xMRZ zWpMqwhP0hBJq5!CR;uY+F-*Dk&PDP9#HH3wqxc=HL>F)ctyv>Cy{ExPIi7*Fx@xEM zKniZFAa=s?0BvcK<80dCCeP-A>h?!KdZjT@tPq}ph)<(6M^y55nLunAJN7iKSsbG# zn8%7IdZnw;*Bbj$Ek>wV$tZ}W+BRFnv>dvKk`-XIV<`&;i zFQSH?U2)y((8$y;iof0Z{ggnldDJ@66suoi;4JWV-+wj`?Yy+?GfU)m#OTpIx;+RP zDbjU37P$3<+DCwS3ATt5nst85LR39&%fIo|+HRFh``mr8#(BQToKf_HU{+q}<_6pK zaCtA3@-3L=-&xzg@I{NZjA;QPgo(x}CgI#9?YwE$W#ESt*!%7u6vUTk&Px6&T%^;S zZ}(PNZR(vWpH_idS4sd}rlkhsS#?iF3zlQxinpG~rP<9Squ{O%?XnR8V3vUQl?aEQN2bIOO5 z_e;CoY(p#UQqQJj6c^{foDT*m?AY-%<3xvu?cu$)aM__Tt5(L`W&$nAVV{zsa1nP)!jH^1u#(!QmoJepyormI2UkpqaHET7E=M7`7Z4@0hliV}1ivFD?anhbz_<-`6ot%7>a>l)M`P9(yT;x=N zTI$N35ke(f877xXZFvVEpp}7lyb;)zEYV;WldTnZRsLvim*wYF62idJx!QNf2-PHa zfkoWOtJDLLUrVay-)+)h9du%)|0Weal&|LU1P{whI0(G6id4tD&onnu@T{>)*?b|l z4FCMPg9STjhWGvS<=2~qZBxZJ1C)2D*q0wMRNDrEj6=G}N~;`2Z`Cy}YXg#8K1L0Zz*`~Ic`8@2Xd2Fz?XTK)ztw*_TwH5Fv->>&?&av0h_WnJe2LfQZ^g-qO&!M}+a`}4=9P&` zBr*u?Y@hppRF#eVE_e+M-8B$QhS+elw%&_i5Z}rIVg3vIQKZh>t;BkXJm@jF;~}7(ra~C!OJTAT^vtuMV8yxHlAG(_DmKY167ZKY$4Tm)eo@d0lsclg;cR=WZV zOwzB~oXwx|6bryPHo1?VZVa^&4iT$kVJ)n^oI763N={P=wL1uiL>uz_IM7hyU9?bq zKT!<3a&@MF%6p+%qMXSo2X`?s&0L_qr*-@Hz2tkF6xD3xQ!VXs?8POX%qtdS(zw8hJQ`I%HQPMYG6KrYVsrcPOnAp5BTBV zr=yRDW*}G5^?pC@GcMkFy5m>cC#J+R>|C0Q^eoM7FQ!b-yll!iKsMeF?|+K+-d+~! zj^S!<^+_G)=eF*-qEvw{*RFiv)14%^>bMW*98`TE#imhxX*3&3oMjf7YRNBpTF8xM zcj6nd?GsiG>9UJ>v27RSQnu^h)-~ACIOHj6-6Og}Toc*#CC~WZTah8zO;MBl(TpB% z-OF93KV|`1QWv!gpH5lq^6sw&~xzc^^Bmnetp z@cg)624c(lCeEP6{wuSO^C)&}($;u!OExJIf%eG*^A46=>oN1KiV%e3`OeBzhS$H? zgHB#EDuqv=GDqLC*>S13ZbVkIOK&TJndwQBAu8;u;u*J_B6(=cQf0jpmjh&hsbMSH zT}CPn@#Xh*2F~lwZsY9lNZ@HpqIotJV3_mXfWU7oN)r>SdGrQf>MK6qg;W{*@ zmT}UVsk*D!R-KGI8kX@S`wkiB zoz+^KvEUFnuf<8(LGV186<3xc)LG(*`?+(T6#Z*+k>0J19gqfSQi&+M58L5~zar(c zl`HBvHF4j@amGA|d{ZRSPt7ToO1spsdircr%5x~=4!P7Ar4Dr--FH!qn^gwWA?#hp z^I=asN4aQTRBPX|E~z@qKZj$z*M$8#ClvkmuVTjTTp-gpEm_TY`Y`U))CxLToCPUW zJFl0SYwkUc$OcT)iuXWlLQ$U{qQ-3#r#?z7tQ4fzps*3}hG9WPY({(Tz86*QA3n=P z`!jk#ZmKs1QN-Fi<0GJd7!2b9wp2(vL znEyr1(uxlEx;(*naEa=qj2BzCkI~ev*UOm5cL<+Ok}326eohVr%RxPgQf5n^+#uUEPHEHqcX4t-5@r+t;8ULp;y|quv zJ&kyhweSg+dT{Z}OI@Q^%`PWpt#-dQ) z2ic&k`y?w3lwKC%gO&3Yb&cYIqp5BAoa&&lq6FMma9f59{b~+rT|*i=L@c?1jtGCdIHckA9I^gpuA3Fb9+m7XYh zOo3|hx&)ooYoXOd@7znpH)!4f8fw$Deo=0M^{k-i@}T)Dns|GN-gn#V8e+e?J>Z+D zMyP&gyG%#hoKO8l@u>k^{hSS>tpA^|6&!WP=m|-ye?=UJ=2!tuym zKldlFN*n?2a{)*v_Y2eRovhmVHIRFjAk`>;aTQGY-C8MFR}Yfj3V{z)5BGAJ$APkd ze{XD46WH3ElZwVxy9SWW3bzG}pq(?slpx}g;2ltgc|Ssbx8@{kdjT`273i6A;`Ed) zR!~7N@4KA#Fi=tMUmo7kB0RzBnPnEj2T(X!!xmA^n6SlmkURWyA$GFDJ?{SI!iWv; z&t*6m8H=0p7FSMqSLT$tb9wfNV@lA;F6%6FKnQPnsyEWNKV3YG){9j zk1J>4z}qX;?YiHs{fM$Y8VPRC*7Aq?(1|@|Hb3F1%!awZec;S@zCA^nd;H5ORM(a} zpP6oZY!?UB4I<7ExAs>?;D=!3OCLSUT11d-;DLL)#!uuSoQj5RiGS5-oR}q-gMRqi zCy(prcF;Pd{=MR2`E*K})nxI2d_H}`wuy4mwwZSO^O2XGj5E8R$6Y}SJ7df|kO_8} zc5)g@X&Jq_gYwJ8QQ$(M1vS#Fl(teS&L> zv|6cR*hOuZ!lcPNsLRGR|v0fD|)Gf%myJz*z&Mi*Dp7$B+SPl&Rwtp_cyS4Vd>+Ay*YD&z!-t=by zp`bQW{cCNIEpnE$JP-%JR<2WCd0px^t$^6OR@J$e!tg^Q{#QxOv)gPNyMKteHn_d)9BIk8*(fYC zu@q$8ajOgXyE%gmZz5BBtCgbD^WCO%w)qIm&k6=*ZF`WABu}AS5|HkjdRCJ5VGpC9 ztt6QTCMZQ3cY!%biWw&lW1*p9auvYowKYTZ>%Z|!5+1Z4K6t;?4C1wR=W?W9wuGc| zId7}}m_$0xm-UWzKo7Ykl)VwlS`X z>lTkC)^|kKho=(HVH>Z!!z4giyiV77EEcEas8TkS5%IFm+6ec_s@9NYC`Ksa35*Ua22t;$j*BQ;=Sa|H>z>-(H{xt0ufqzZsHFMf%CD&@1= zDn@mWdSpMF3hs-N>Q$65uhga?Kz|1aEW?CHt}#`pw<--$9Z^laMLezYt=&KJ22E0@ zj*kI)=kp?T#V16wS|~aecj)^=mzfOc{n`8GePUyFxT>FC(bIQRK z{^H5os_nw1IYBQ%C4V<2)6WIGp{wBiOHytdnM1e9 zEExmQyGnsJ>rH&fg;orkNc8VC`Wkn@pdR`eGU4Apsj&msyOa~KXN|e9)Md%095p{f>|SN52D=K6YxokvQeX7l4!@}Uwm~TGWnMK}|9k0T$4Xyd#_K+l zfTJIo5uoVwdHgy^$Y;w+yVQXJ*q{YTUIbhBt-f)BL6z0Hc`2A7EfXGA=`(kpCyTfQ z3?f}K-k9k5PDC%;e5+7?aM0l@%C=;-UM$*lu23w~1Bl1{F;OL3S@D%UZ-gW9-k<(7 zKp(hgQU8t64wWi5l>NG2!K@vgH|>ipeZbMR1E+Lr0avozX-cqz0*~)R;1gALi+cKx z2LfHvDdrQ|!5jlmYkiTYg?wLEtMzfwnV(`AFBPSr^g25B9A1bRg8n@|)TH>O1ReKe ztzOseKi@waa||C%3PXgNh8POT~Wg_t-!2%Qnm}A16;$4>tl-z-2v{W!myW3q;Y@<20yFp{hSE6pnBQG6iQp_LqoqB!#>Na2pbc#Ctc@jYt`$0p%IV$WEFfrWF z7EJhS{Myee0F_qw`Tjqj;E7X!SW}59O_n|UqqDce&QWbMSXuSquix`HTk|ao2>0$` z{l_qe;G-rv5H7lDADg@Td|lSF`&CiT{i`eBq?qncQ_7Rr;S`)c@)u{6GC0J%DC%Thu)LyKeD6U%mhJuCMfg zGkwEX`+uSZ|8I`UfBo=(ypB!?q&99SJ^B7Gf5Bh9m)r}$OFLK2Tl-g~#lPP8{hv4( z8XWJm7=k%|5psF{s^j^uLM(C|7N_(hQqG-zcTJGt|26RJ#Hn8l;N-TXkG}cWU-xeg z_QPW|X4sJSf9I0_Ur(+XIJsZ2D$alN`~LO0miu;$8@|T>?}Gu3;lB^YKV4w|J{bQ# z82?@~{m{tXn0W09w7ufTd30iH4Kc?5ad}i(W zSMyk4H>pTcNbUydpp_qooVT?bDy(`Fb=MoXO4rcDP^XzDwYtDIHNA2}zMzB60u9|` zb{|h9%M_^>OjjE#Moskw+js@dG{fqSO@tC#zZMDsK;;D%UbB z8$mw%;oR(?6amwwD>>oEvf07FN+W1gyLY|BeWzTvS}t zlUQ#3_kC%k%45y^WN#;2F<+Hw(aiGIJ8GukUh^PiT&o|kJ4$vvYKZ)+W1&0mgL_6d zk}t{sh-^k+yBvP((a*Ju`4TJj#HJCr78W;tacz;o_)(-~h2@yEm}#SjqJVkZ^%A=f zlcf8kNGNXDQ^X2j*#P6BA@HkG#+KFpxV-=BZ*tSetXGMFHOgS@gP(=78exIYq3^~* z59V^l)v*BACz8}AA?+u7_`(&Kdl1+!+?CfCA|+;T4a%Z4jyaLd-N)=y&^U4yJd>*8 zeEOgTy2?;!-d;9H5;Uj)g zpUIdm5$croTI}WTWGLAh(0h=RyDcM`PLskXDde^D9^ce~rriRrOyPQR=UGw1Ca2e? z8j?1^>?|PIE~$D)zs&B;7lR##jYz6rpeOX;$IlO`kv}6VEtQ^tu~NWD5kI~JFvC)Y z4R)3Hcb=Lzx@~47@o6iwrSI?W+Ig!0lb~&#)DcKr=X&FzN}65J4A^I3qdGet*T{ew zf#FtVjbst;w$?NQTccVhf#a5Ws_ZecXtR^R{ban)V{KUolEL)fEXTu)gNrk6M4cF^Smmg$OXvD9thk%6iRz*IscseeUE<#k^+uT!p5aj4uWQ{vg4$ z%s$%Y-jLp{fHKEqUJd`HzGZeWZ(AQ_((iSNzK3sRc(UGo`N#8>q3Wmu*w2?F)wP-W zIkudT|E6f6q5m?1Lj#ps%}K{enIh)Qod7Mmw&wT#(#`ON0VLl9A^Fb&<#7-a7E6ri z-`Jf09`__k&@a-EmHD)&_125-c%jg-n@0zpMs53nukIQ)sS232+%lYfp>UqzE=OeK zUFxB1#kh}JIXyUt_ZmrF_m2La=HAZ5zN(*6f*R@5*^@R8wAHT}N`Uq#sq3cS?pnY? zTD1Bt(&znHPKCW_0TE35j*U9$u=z85HpF{Up@IhAynkP>Zt#D1S@sR&^sbM zA@tBey7U%0;k|Qa&NJuCJX4(C`|wYpt-G+A&alVf%R_k< zPVKE$U*H5!geXqpS(k@m$pS(DqD&D<~dR#uPbw(WWn2 zD0)==0ZtQkq9KdE=kp}3-1D@1=&3N!+xUSJy72C}M{(e7n^qQur{l^<$rbG8%NUSo z($ONk)C$~(`TWotSpI&Q)dmVS_ZfuHbbwU4m5o$xCc_y4I!?|8r2#Sf(P|4yR=t(% z{Yp@!A#v($*T=p{sntl*ccW_sImj6nYKH5U86VooJk_rj1Xsl@JOj)So)PeUkIpiTS6P(<|~ z0pski>-~Tdsnu_~6G|U+@63mbw*=Aj4;*p~cE<&s`mR;Mj}OAYDQzk%nh-72@A4l|WuGO7X z9tc`stZYRzEq9H8)W_8jtM|0{v8mZ~O`-SbU8dGoghk;tEAyJYaxZNV=AA!U!0GmIrV76&O#u@_G62A)m^UF$-FDawtBtD_>Q`$1g3RRk}yreKRsDEw&g1Xaf`3{?8*58pb; zQ3h~6{#bu*)!K^faI!)ES*E^vh3iu6*nFPtSgFzG4Bx`?rh#S(k--y~9)^ls6Y!EW z8Fvjzi*@}xR;@-e-Xc7~Ums~CAu?j;7ZYb3Z05_FCuTS7R!pjOLeanIAtbE!pw=2E zBS6>qW7Iez%u=Xce`;6Qpp;k8w)GT;-qf$H0!Bd_{8tA_t%%Oysoi?kK;)RbO!`{& zR;iFf_wbTu|Ec>m5g#tG`nqSRMgE|+uW;kLmy-hvTikJ79j|FgT$5B^DqYE}_uhYm zZw{iD=VpXjC`Pc}@e2KmP3JyYWV7aH(y052AF?Dv&1T_CnSUw?Z;a@Nhod>5MRA!U zWk$>VQc8&rXam)kJLzhQqYZYm{F0zR&Q72k|*A5`O$C zI`GE&(fOajgfzVR((RWfv-SRUnR1QIweK*}V?pD^ZC87~0Tmad*tuwDZMejMiII8H zb*su!*_Ql~UP5brp*x65sXLLj62{`&V`M&E!ID9utgeJOF>nt#FZLC1NqPIg%XpsW z-K>;5JY1t3fik+>GyOq61(bKP;ru3k7UWKxg?O8$zx2AWy(pnJf1@CS?nxV^n6qIuJAjloqt|uM^jnY zuEH@01D4~SbZ=cWk6*1jR+bI>@D~(>g4 zfBOiIGYmZ0^1XC!^zG1+l$wz;2hbgA*_lX4WV*fbi$n1PkwRPORR>fsL(_qhPY;3g zZ{*&ZJF$iBejh&g)*uiPhJmE?pY$yk=wuc#o=c;lNzBbv`FB2Um{eb!bI}%&P+GIKSYYpDXdNTGr~sNY9t8lSGhqwlV>NVQN2TYOb-uN>7vi zgrSMBp6Te|)15iTtz;$IxC<*IgUlOATO|elH%nx`a$%7oo7bZj`?*!yXvalGPvBXv znS{S7`ycHCr|?pjH{#^@<<8}?NyOedJqK0W1T2N}idFr4)U#S2#;U{C$n>I@O%85{ zj@(QOuhzz;w&+L!*8%F)WFbNe&D;&=J-*LLhoF#*P23$}FUV*H)_Fnp?A9D2Gj&M0r7f2BI7it-LR{W^n268y63>C$!_AhNN4`N`A&&VD~2%|;VZrU zqn22wAqYl>6ghTrAo+laGv)jbq^deXQhKDZaUzPIi)! zu1mcO(Bm#f)<52y52I<S2;zM0kDdb_gw<~{3yZuuE z@lOBVcJJ^-DK8gPqr*gP<1&05D)rSMI~HG#3Qw*%AVscYzs#vKds9HIL?TFGz2Iav z>X1MTjkg}rU;F+g@RrQoU&!>ZsU|jija{a5Y4U-xdG%MZHm}wvZ#ZYcw7&;K%!2x} zQSlIqq6F1AzOtnVg9>oJ9&OZN=ni-pxVMv+J{Fp_#DY;Y0Rep7aq0=B<;@)W9LiBY zclcIDPU4Jq^OjF|UhzI)CY+^N=5eTr@0G@2plFMo#0s~=x}55G$or*YrWfW6*@3_m);6b>?0vcZdIN$t@Kr^c~-KzWe$~tMjT3Ek))8Ina8BhtDAWB zR3@$*cCxT%VcG3$ROL1%T}J7P&?~GSE9FLNXDCnO^0VT_z;=fP3zfs~pmf zR6}~&7C16y7W;*Vv;9B_gIielzlR?KE_J7{dR_P z^a93)zeg+5g{%e#OIDRdT;z8!P-hJ0D%Z2yaT8%L-ah(pQ8^AJ;hCf(+E2?QsTR&A zbA#fqnP};n$~K_w=97Pa{Gziz>|kZM#4UdW$T@}Vdmg%MRZ%8yFO76y4!%SSdAC{; zmVzONP2ourx4?K`V75G5)6(zzypeIHu$BI3qeDP7!xR0!6hr`pZ?JO4g0%jj_q}R2 zfd23Mh=dy@OsP;p9{PcCi(=n#N7L=poKH*v!XFKwY~KXogXO*5`^zP@MzFBSk(Mggw( zfUtT>6h2l@M9gHyO?_6UNnRYNDHFathYBG$TbWx|xcrVnEHgstp zdLY6X^MZDz3kO3bk1g-(TMwj_sHI4EtoE*D;g|=m(%jo(m?`br8}F}kvF=rA2%x~Y zs>bq0*B(<2x1|2*GIx?BJ4o~)__?zsGM*+U%KJ~~9N*wOwoTRqQ=w092|CXJts=c= za{KK3tPV=Qi6sZ>51$Tui*`R&w}l>NHzD5IbTS_SOh z1>Vv?65p2x#gBh4@V{p_TpXvBO)QK^y4A7dX2b&?8;%qMiIT1=S9tYikkhCyLl=Xu zQeoKB8rXD(sx5Lsr^|q?4V_9wQD**)Ep4E{ZmiJeH!Qc(W;+yCnYp`(j&z&^v5s?v z+wtA-l3>Vd(>cr#zY<*I2?}}qD9|$CpaV$=ON}L39y6V@V6goDzTdJ&Jn&JX+@*nX z0+P#&a=*w-BGqr>-h%Pa&J*t;_SP#*AcQro6`8ZVOL)s8j`l}I+fjn(~2po4nEIKR= zQrlr7MO)^@BWpErCW*UwL*NS7#h(UR)t;DU?2p+9Wc=x8jSD^H&yK?wG`+n^S4#nc zi*ErdPE?YwpuE*isPd(j_DTO^L*~PfD}2k96(BFJ<7i)|>fp?oGv=*bYet9z)g9nn zb0tW9RUFw#dPK%2sS4m*BO|8kJ=trgbW=oJoGXdYOug>3dpMfN9M5Yv78 zz$&1bT4(?6w&KOL<7>@jXFKe$2bBUQ#WIBPKnt60%-;E_ggQ6WrKmfabI3BMS#AmE zcg`=G+GFMfhT!l1%H`CYGlmo1&o2FHR5BkbVvwedzp3D)dlHq&Gjl*#Dv&mz!MP&8 ze6fyJG}fB_c^>oTOCYqU+SGEftJK~DAIkBCi-DV*(%p8X)~F{-eOeq-i|`u)dx*1g zk-0#J%t@liVJGuxfzFFE#*&s0wM8@x8`~ZuEfk^MU9LkT!fo^|Bk9AV7I}T4)|@FN z?Lb2}{JGlm4zruH9Xpmk;~AT_#FNa3g75W~&YW3Q&evR2uHM@)-)?;`r^BU{@o^5z zq`D@3oNui;TjEz|TriJPuM#k7xb8*2_<~`+Mjz3sJHMCC3jmpyFy5j8hv8}kaB|uT zuYe`=Ac)}E_qxpY`0TKmW|b4jrr7dsuEag((i)8WIC@U6upK0(8bSl}e2xD>Q1?GM zXeWnAy_rM4C*@w`Ka%Q?4|gpZ0^ZJ<0Fx^zhy*5bb}5dSPsL9HqL%VS6-13B(1r_p zzaf@+)&;uQ9LHZj6uR^1bUgQGGjs?aY7RPnOnMT{TEZ!o#3k3DSD^`*V;1%~DuF%b z3p<0V0!@)O-%<^4O$ZpPZ42b2t(jC5S(yK9?Vh_#BisG@DxAX$WNroOGc6J)PW1S) zHB;z?eb;KvM6t`A;W8-w}-ebuwCvH=HoA%92)qlkckAdFAeHix5FUl&IQi`qbh!NWT_BYXWrH#9Lv;i z#3&!jh}X^9ZnW%1>Q;Naa7zW1_8%w(tf6JTfh@8XSeg)g$W?(MV6;*ut2-Obscg&U zJ6|y7mh{&jf?toW``4%RM?Bd5nFLfk59kNML)Awg1x$WA&q{5_=3KQ&LfS(~^u0$R zSFrGc{$FBq6$8lKVq+;B8;95Si=<&^qwiN3qdSvi*hatl&c__3zIjADSE7HG+oU#B zTi7IjnSJ=~v+|EaS7T2`Aps&VTs)jr(5CP1vuro@wzyp*bd48QU(9pyxn45=C~F5} zUu1o2#--;U9L#sX6l?Wk(Ycm*&*zg>{abBuSK{ym-!Asx0wW0ndClz-a0>R6s;eGe zXS%jC8+T$^mc(Ew`<_xz;lsryFj0~_sXV< zFT<-s+?5R@&e6N+MG`+l8GAj0?xEn#!P~!03L_F49NFBmBZ4WPnzw!&XN{M%KvDzv zZtTK0rz4L+b8CRM=OKa-hdUj>SFl`)PGh_je3i$>&6+aq6L4*Qh`xi}367~7S~Dmx zbWjG_aX%zSn!!k)>6rd*ZXd*gz)g@lG6GHDGZ0>4mK%2)$b&8ylBP&kT>E*BUL}AB z8jdj)3)>B=eSMma95N|Y<7!J0Kpj`a|KfFl+b>~xg}KsdaKHPMcQ&U zbUIl`UOkoUcMhxh%EeznrJE z7Was+Byefj0-hPQ0f5T#ml9Znc7Qko2ylxl2t}4;GFvel2twb96`Mm3at|X>5EyX*i3a>Z!v!x#J|9J9s;kF0kwXtdeBS{M@0aTxhW0oJnX8K| z*=Zfr`6ELQ^$Th8GMZpAD^xN;2&4P*wL0wIYiG(5eFPnktftX)Sjic{P-NN=^kE-c z(mU_l9jJMewt}#FL3ha|DuHTtwom7%$>YTl@1#t4y2G5qnI(Q)80?b9^7rna)OOo^ zoc$b`|0qS?&+qJoBRkXt+A>9IcLsz%*6D0l)&B)^q#;KG2FV_^MVu&Imm=TK)3 zIu3fXG(Lfidnis1t?0@T_|wf9T}S!IWoIh_H!n94j_!n@}gnJDGm znP+eUOwGEtuO&1Y2szvY;8>J`42}WoHOWgl#7}WtdZ*1Q>A{|@acl|Oea(OiAPuQ zFEK$x`Gr-h=RyILd7CY4xeB6Fhdd#`rkmoT(RqDHA<(Hf#~pnoKWuk0d1yd3;YQ$r zYRd&X1;~d7AqDYbW<`vc^dYG%^(4ByR0gT$Wie+XD3{ALaqIauI*n52_t3O~u1Ngy z5SmF&(cfQ7D@#Lkf*J1n*jOp_njI{waG=-$uQP z&9C5r!lRenB}=#D*&8a3EcwZOa74o_C>zD;d2KWmN2vCcF=&L*4J>pw&K^%SElbk` z*o>#C=hQ5dQJ^=+zRMNaW4WPqJMN5Kb!8LU2S%9_2%YuM8aicwlrI;&i%k@6A?$ap zX<5P;Xk>YoH)>L$iQ5t?fo*sH^oFrLFL{q2f2=i(8z4A?R(84z~;AKp}Go#XFodu7=pXJdQY%j>UUXFtKG&Hf zz^5-%f}Ddl{pIgZ3$}49!#ZCh{bE~=>6S1j7Ii;@N#FxXRS?qf2$FhT#_=Ii$c_sP zfM|%=f+5Y!Is@hlBRQ~0^G`Qx7STeh^`2JQYX@K9;_Qe+6(fyN=Pz=8JW@;z))uO2 zPHz{eq9YNjPhx@SK(9A+Ro4VP5~oH|Jzw7C|>oaX;mGNZcv zMCD4Q!{MYfcjZ?j(Xx48iMU9{>ENTCku_-;1qv8i`4U)@_|likq(a%xxDUimCN=El z5s$sv;HVlKTE&PfV+C-Fqw+|nk#oV{RZ^snXUnwx^wb$;Or!epo~jG&sh}l!KkugJ z30(&ULoSOj%r%`CZz*LSpkae&&%tKHBX7(t@lPP-^eQ=xtIIbnT1tCL$Ko0!A9nSQ zcM@XN5(MR5Hiuek_8k81-!j-bKvZt*!S1YbiOnJLcYp!3l5gOu1LLus0Pn_ zJRU~+r}ro^tN;d7J7!htU~nREr~ZU{F(V~c)29EUQPplM1IHwfIW&PCz;4l3O?*vC2j4OgR3SMSr+@I*>{}nUVP8WpM0hWSa6Smbg7fwe@PU81iMO`pnw8Qz+}Dx~@1L;~N}!|o51;LSO$H_Nfz@l=ue2Z` z7i6}*;=DJX?HMKNM-PEFfXXX`@epZi2B9I(Hqvd3<=6)TK%qd}+01t4hV!bAM7g|4 z=&o`~Em?+3d@$%zmPu}e9d7@IJS%dc5c55H12+t0bz2$cOBsOQJw8kAcKp`~cv(Yd z`QB?{Hld$#pR{3`zw9nTg`69|uf0=EuBK2BB^X%r%zS;8W-bPqZUUof?uc){ncd)b zK8|J+WJDnX*0I+ty9rE#1k+5Lo*Hk5`H#9|`_OHInipT{AAhNXWivoS zwoDhqZe-3I@=L4YFAcD`&?~Y7TrI6`Jq18a%Af9i8Lf0x?M#&7@jF30c~}(r0B|o+ zVp_MAfj4sWUcg<@7k(RPb_jp5pZiNg6qT)c0(wIa;7+)6-}TIF>uWtWUOwql zG+n8rar)hpdCuA4Mnz(I_ zacgfJ&6I*+E8S`SX2||WfdwE>5ayGT2_Y-nSs7t7XHTr?nr1uJ7+Cwk>rib^X`SCY zKf7rW0Tuc~;E7$53iJSAtE;tn;#3kJIKBZkz$1IdfAM*pe3kvM8=|^>ty|ue#@&7* z(j<<4KHZB<@pccj0M5>is!iq)Qf(YV0`N*@&3H^6VMOBa3#V_t z^OBY49^LW!E1ILHMv{VG!~dVh=YKFE z{3kza=7ATE}&`j^XpK{Z;Fdzza3zNE*xto=l-UDXp$!840$f5@?)CTG0`6h!H7 zuKgdOb8b&9!mF3tD~xOnhq?vnZ(> zq-b$hh6F9%Rt4$-PRFT7MRtc5Jl7}Xa&nA4ZzcTYR?zo3Lo!P6Tr2!v)sZnK;0*meQ0NiMJmMdLGMO#oV|VICFZHWHMCy! zQSJ|gM1Z)oe8?W<7(*T)dC7s{>Id1qE3KdIr$7I?`80^%sqvk95eQ78%Qr15lbu$i z!Lrk_00C^U&w(`y;{&VDhUKpsyf-0(Tb&XPsy!9ZDo*Z1FGV-V)LV0;Z(7) zMkD4k6SO>DqYGGYAO7A`oCqDjX`aHy*W3p4>88bdH_~pY9548G1A45glYbz8u+72_ zFxT^&>s~cW14sND(=E|#en-m45-XM&L(2bpd;0f3^+=`=%vO_UZ{0dN^=j?hn;6-S zSoKV>=*zp5N2WKwQD$!iaW?!Z1IIt5cS$NHpdEHQgyt6mAPx-W_QpgYx|&UJ@ZPJA z87zERb&#$Q#=SAsgqS0f6LMBO@vY%hjTJTA%?<;gx(%hq^ZaMZq$58pq4X1=;=Hg_ z0xY0oluGd2Bh*HOjBd#MVa_r5zOasFWR8F66Vqa}w)DBPHk@Tx1ppn@AXQU?dwN)SFny%VL?Z4D)F=W@;m<3Q498lb zfk9_$4|yZQH1I$gmno-d;zNuVE1jHIAjuL^wif=-J9j$rapcWN`Zs4mRbH~)Y3v31 z?qK`d;M?Fa#Ga*=#DBQ65(lK4ZGiPP@18PAu?`1xLVMQ@vWG6T2h^PENX|#_d*j$MHlv$s+Antz8I7C%m7BBAQji|H=YVLc!&#Uug|bI(vr8=SX}L?D_9*$P83D zu_f(qi^4yQb^mk*|Kkbtw;#y<1dnRYsk)ZY8)r`Z>(~5_07pmI^qvE$O@}PBt1p0% z;;j231fm%zD|C&sTmk)hVFGRJ5`6?zIMw#@kVJscJ&8r>S=(R65e5>Tp^S~VHhk|p zuTI{WXcMU8C+J@_7Qe_Dv{nhkr=k_ZI@5srOY@idw9fP7Fq$b!#Exm}jBtIr;l4_d z#gfQjz*S~{J&t#@(F@(_s=&}Nv-OT(0fap(8!bi-Y^_SOO;=4mx!D;EaggkpI3Pnl zt@!<2EZe(?PaD&UZ6tVVML?o7rebTZ17Hx?9iR?kmgA5~AfysMfOae(6l=dw&Gg_^ z5X<*Z^GTySMfB;-pXJzx9YF?jJYiZB)*L~;F`W?q3~1R+aoX9#C2-`5b+2XngdbaR zOU{3I2mkB$_jOr);Mv`_b5q!dv}evq7SuCvK$-^ZM%X;Bjqw^4KfZylo(MIG+C3`S z6CL+3dX}wXjib8fD@+8UgNwVF}?ln}NG4K&r6Qx^d|a#tl?w-a;I>z>b%P zoMG#aNSK1tY{bl%%Kbf$};^lY#<-$Y4H#! zLD;yoRyoA{r0>15nsulPAzJGMQZpKFB*?KB@8;qcmpr?DYcy*nBtZjU!kGKoxS4Up zJUSJpc9Wz0mPX=WC5HcNsO6BCA}t^o4o}2igp1*_S(skDn!`Z7lhB)$8)~tT)Drb>iz=Kbq!tl|V0NXzb%L@e)IS z_K2!+Nh`XO%lzBP$9ZGrj-U|aKt|%+qqqfkdC+iWzSS%YOAQ|P``vTP;$RypcfGr+ zN`2{y8|Y#0JPSZ@O%-s88<~{Z*BIg3hf9_)vUI>K2)cl*Ckje{PDEX(Dwxve>HqMU z`TN{dUniUKIPyIChUU+^;5JqErwx4_c0b+sRnp7J)Gq~L$4o)@5sQyVlAxVxK7+FM z1Tklz3ZLz*ObG&FI*VL4LC=|21|Yoi|L9Q>@JP;j_$Dy4VtgzszF3v`i;#R{2rd= z;c_6i_J}1B_&?8^EwB0!#rTGts_jabuGM8spj*mzK2zSh%>C?#y+I5(9F%)+n>@~q z#bRDx1psqD5h$BgIn2`c#%m7q><|05B(=W{e9UO}?#b45|G)^zlEQuK%isFYS8y25 z^SY5~i}0<1f=!2ajj4zIjks_4{g*RVcpoq&tm#I~`WTDY5Ay=(KQ0pV8y)R$ifi5i zK>6LI+G-vkh5z(b%W}|+Y7QG)?r2gpqLT>}GHHJLLJ7)ctF-9(2e@oAuQfmzr)gK^ z5GLi0GhV)Ra!b+StpcKvs)J);LG#A~jdg6?z$r=)%ZJBxKKt6oy~L1`HUO0sx0mNYIgr zg?duuq@0r~h3$uX)~UW<;A;bvG@p2u+?`G*;tz$Yc;2B#&*3i(&w#JeDm;u8Vl1>C z@~8VYkwSBBy%coL-mNVwNu$bFc-kX4|!gDm{~Y`o9K}19fV4PuTbY)qcK#@UABet5ak% zdy&g%kqoRLT_$CKd5XP)O=u-cQQDaJ&2z5N`E9ZRnZ3AQf43!FkgyCGG-vQlTbWkf zKXl@b7d?}I$xz392|T+;eWqIhfcQ}km2RbYnQx@FPysmh0$n0TFuCPI$YH;kcuHKE zbImQDxRXUtub5;2VWMX0!ehEj32(RiJ*ACuacSu4^&eD%yV>!8ROl2#)gp(6sEZ1Q z)MAe|oj(E}?me)G30m~%b<5INp6{@{k&!~bB+fd2K-kJ+gl=!;W(nIohd`?JS}rVV z2gW208S$G`OgVppJ>Nf^yy2~erm^+}6UgbnoSz|A@vIv@Zp)|BNt@nEmaCoH`xF)2 z>cnHAMj%VA_oNK9evt?bO@-h9-!r*8QRD63z+vKQbL+XcvX{-!uk!Q?1CG!EH(k(938gGywoE4@vbKplUfT;jDJ-hNF74OPG&_V{IybeR3ciP?98bQPpBHnoGWqx~LZ_R@ z+WTO_Djw1AJ{1P^2aij>#TX^537FL01buXoKpU!BxT_lPSLrzh1qxU5h#K9Ta$cgv z2j@XcBflGU)>}HyY837ED^*1+oOcTxlgH;qnMLhepO{5EeV}buY42m?T;V~r4;EfB zY#~i)x~Xqre*y1g2|Std6-g7Bf!nK`Y26P8!u2-l0PA&6Gm-&7)4EKwD1i7DKm!MOy0a}bdgE3*lzV;#5<^}QP_FbTGB0X0ir#Y>u4!PS`q%AR2 z;T#7?+MmO#F*R^?<_U;l9jU zs>E5n8WS}-10h&VHUWk1hNF;s91K>}o(TF?>q4YRJzFaUKi-a`m-IoHN9vfgHF>S| z@6q4C+%X{7(tNMAJMFae);#epPb^<-EyC+@%D9yIP?W;vR)Cl>)OtDa>7Rk^lVS{$ zma!?lqRAtQ?2_k6I|KJ80iLhVC}3X}Y;1LWxx&2zoz`lPXTiQUm+3G;dD2T- zaGkw){cg+G{Y0R=9`eBKaEIlFuMAODk|tfuDv~Uw2&TL z@N2x;^2PRoquay*fjLui;&e}yu6vb$YAl}Q({dYup2wo;hH;Pe3{L=Co?#Qyeo=I_ zF_iTn-tha2$gS9bye03r!#u!c#v#*jbWw z7u=s7^?x6_>b*@l-u8~S4BuJf6bKij$16aV^1KO=^iuCOEj z5nT_Sq?w2olwsT_0MmHR#<;jm)E@h_d2j?O>c!@dlpcbE-PuYp7=Qp30kS>WeodHX zY4OzCaob6^To<3aX`x^IjJS%khg&RsUt3|Pn}5wfKDA8M;RURqM(-tD7QSOrPSCjm z^{wabn=kAZNRPcvlJPUvO#w0ydeHOC(#w0%%g0g~Df9u&Xf74Yk)wDgNy?qSwSq{Q z+;_YvCcOTT%rz0To~n0dEx^1>sFt3s*PJPkw}C~mz4JcnV^~K-0k5un;I+I{=U95@ zL58%Vmj*<1LqY;=?%6ox(l5Wo`(_7n@d{|=K#{?-Xc?xRo5dP$@l9ise1m)@PF zzHW0?ec@2j@y9jjJ%`dz7AZqF+mX9wEurgPC&H}_++Zyc4@c=@^ESqIIhMbGhf5n$ z#xEK$$4Gp+`>q>`oi1s)TH4P03e9lWY4cc7Nj2#azo2s6%O^StyRciKdrr^ z5i}A0{3t{keOmEei*C|z!U}1@%V^WeET)$=qwpuGmCai}H68U<#)5GA&kX^vK)*lS zR%Flkm9qRTK9`z8NaE3xS zfth(;Cf5W%Dx1VE7r;K>62UgXsv3E-!l;bfc@BgArb!U?baGb=2T)|Z9DnlF!k4ou zhxnuX!WrKpPpi~Wl+&*xibyral`lEr%j`f2-tNf}^b;CYb5w+8Tt0}dG^sgdvTHkz zYU%$iWxz-y`R?JE&k%+^i_Gc2@KajGWpW5pLP(X>v?D&?57A zZNmOAP>$XY6fp*h)c7`ew5Of|(Fc-kLSiA^5>=~sCHmBU#$3ys5F!>SVt4C+ zXk|T+S3*+M3XQ1nnRNmMBi5Jrho^uii5KwnJ5%mQp8T3L>d*Ebq`cp24SY|%1x(}( zRDMSl;y+uw`_A|cJ-B#BgEL*xM__-m<}jzxBh1@=xX$wE3=u5j)pJr|7jU81vvG+V zp88{<2$E&xurzocp?6F)CR=^WuQ!j0M;A2Sh6FqjS_&S1n<2@w%N*t1QvuY$ZhZUm zeKG}Zub7jiD?8fQ~phYOfTGmPR|d#im@L@iu8lgeaEZHKtQND`eW zR-_7^WHYt&_1~?hVcl!ro|-i)<5(vKI|LYq3=(Jmh9pDrr-DJQLL^Dg#vLOQ1#p~r z#upllxDG%E@S}Yb&CE;0U}poYZhw++U-M#Zwy%3pz1DOc`nJ@5%syiO+rqBlS3!mh z$8_8+_f^A-9E#Li-m|+CC-(wmZ>v0?hKzkdFgyPSV9;!K#TM2J;D%Uy*t=gOe?cYD z`RM2Qp<>SvE6R8P=`cxM{`-Re-#BvrE}x>C-mN`P{!0uHB(FHuo91bJj6;au5LN+a zd{}v}B~|Wwg0z!r5!NNtZ`2rndBU#*D+0NzM; z*cCn`Ff)6Edyc=KKl*M9T70eXzkpot=Kf|aPu9Vy^H&+D_ zk-oEK4zDX$V8xUg$(E%O2}& zYcjT0i01nyFL;z~d32W6P5|Yw$VssY83Mdn_L`0`qfJ-4ok!`QN>iG&`445bjFpc0 zu?K1NITIGgaE8(Uxg>cqv;Ud>tu`e#opsMThw6<=pYiXtKD)=~Ql?GE zpt!-jM1CWEys0JT=;^`sZ-GjG15yPd)}-29{lH{;+uD7AW^5r*THb4@$~^`3TeDab za@rj8xj|Y9xUE(y(HDUyx?9GwyF@b==uEj$T0r&8`x6)|+mF4*mN~@3S)}-ZhwZy% z=HtlG0-0}VxD;(kGW%i~KmVH-*Ml%TNU@u|s#Y=_zh)1Tt|T)GU%ahm1)KUlmpAdf z``dvJVw50FySU&&>2Kvu^zmHjRx%nPWj8s~vPs|6Eifz@xseY@L{)v}6l*^CKwdO} zp%gpEc4{SbmTgwg({N(_#X3iG8cSI;;5F$rn1|EHbM|=z7hO zvA;yYc{w2;sRSV1iUFZL`^Q_ZiMr zVs!?)#yjFP+vOmqW@v|6vA5Hs`7q-a`(!eGkW*9M7676D^uT#8517uVuJBIUywCa-Z`W|3;I*X3 z7`n{b6v9vll+*^L_R*K_XjUHN>g01~s>Hq`D1={!g0PiaAXUkQ;61Ljz0f^7CRt(R z95C`3VwnuL0Yqn}R*PFfE%;h0M>nTb9c)YOITQ4CC!+1uK0g>*x@@LM8%LSazqN>q zluF3cD}Bs_=$6e=W4d$StXXJYQgwI9Rm)c#Q~;*JpUcp)43{_(ZUGAsK*81)w`@sC zKH(<vvhYy{@bOpx2-_fT zc1!pz2UQr#mRI`f{({;^c7GxJ?R5#*hJY$b0$c#SqnRu?!q`(cwTOS$zl)PO39@{h ziZ>Vkvk}QdgFH?8&dfHZ^6yyyyKQzLXhG|;$L^TB`A$UdQS!#oK2^{9*6k>63l;1B zqImdfm#GYiU@-5R{IcnnR9D}K#ciXZaS_X18dIH{UwOJd>ROu#ChD|1B!x`?%`nh_{`+-t-fSj6?vh;O#=qj%7cvzG!6b!1p85SW~ zQ$DL;rsUvymIXxVZXuLZ+WF7gmJr*L#nK4}bJlstW;xzudfH|Ol9h4`F_)#0!UeWKpP77Xx|X0{ zjWa8T4k}5S%&ovlup-Q)1~D7v>{jE@2YHh><=^=_M8Lsh0YBA;iPyq78-mzk5bFLL4i0K$4eAEFM zi8qUu?)%PO<$LEY2B;!RoF)7rL2&hC5pTkv~0gia##<@A4u7=BtN%FmE2r3DLf;oDF+u&q(M85hxHu)UrG_}w6zpZ>-F@?I z05JA7JOCO72wvd8v*4(7(S4}K8?|3;QwLl6t`d=4qc9nfrO! zLT8%u(i*(KaB+p@-AozCx8qN`?_9bJ0uS(}&EW}FhSQB}8ivr)7_mmbYc@C* z5Rzb&{b$gA{T4snzUSFVBE9Tn-KD!5wY#@>NXE~6gECA=CBOiS?XG^NeC6CgCTaM)N(e-eSBxx%iMeW^9D)FRJSGcq|u}TL{%*I?W?&hpTk_6bRd#V z$8F}Pon{SuUk*91XLj(7xdsi!D0GLz@vaP!RCGlS=cuFf?<>$U=_A(NWnzC`?4b;; z^ELaf;fF8p3^#F$h4JZ+Y^7u#mI#Pni@70R7Y*v{#?~;P5)i*ueqnKoU|sVL216=o zh}^c?K?3y@xus3e9N4wNH@eQT7lv9It@g~-O+j72J~w5AU%5hK+%*|aO8yL#_1eCF zX85)z3t(LU6FTCG87w?AasVAHfk#LcG?;;)VZp%niO^`0`-y>DCz6x^;f&B0%25bB z$h$Zk3VMi21t9|@ix(IG9auUE--h&rw4$zHPZfF3U)jR7Q>IO*)*V&$mAs?;!&4ni zI@PzTa5Wt3Y*Y)kK?0C^Pj42^#jPzR7vBmLI!1`u_LxG5B3Pim_Ava!&)FM&;&i-- zAD$F)33Q3E5hj@R-wX@PlAfHBxwAro% z4}fB$K(3ndS&q*Qe*=gq8y4p5aZVt(iln!Mstu(<# zrN*n}IRQ!t@BHQ1QcCS1++}6h6`~aYbLz^w*O|8V8+0FMJT8nXav5@(|A;$?hgfF% zylp3C@sWVL6z6|^@Ct}cctYc3<7;XyH~yhnzBv(e96wNwiPq_PsHI-CZcs^ zwJQU?n7kQD)Bqu#@xD{>C!tz_2$`E7)#vNhA5!C`jLRoG?bDoDwMou^|Bt=54vMSo z@KFm!QF#9!QC}D1cxSA2n2U;ym5CL2oRd!!QCxr<1Tme-kDpUoO|ZX zocZgVuc|9msix_Nz4x=$de-`hda;k0;+VYBeZYCKTA4MdKrpHaQ2#n!ky6(Ovk&tE zot6K^^W9%R=dp-HfybyfKi|=8Hy63Jzu07mg!#<~kuH`dNz<_ZF9Pj%{P(?pM3vct z;foKg{ycwCCyK$WVqPaxCsA+SZUW?LLi)N#^;3YNMqdQv$)wdbGZIVqqHm7>(|g&0 zWL7GG{Q%=Mi@uSZj2^QbdeeU0u3K)oB`v}(Eef#F-|)HZc~mlQX}2sb&uamI11eP*nuUnkyWF;sUjFue!ae49yD!?Iy%rZQS>TX^@QAF9QoDzt$zxDk7jdkICcimnMST!p9N?A!p zmr9<8g~PT~p9Z`l5jg!N|3NY7cli`qCAZ6F^q_A2HQHt6kn!Ny014qYv0Ha0e&t!93UF! z15FPia?E!D(5m_WE#q&m(0|L-NEUgw1_E^9jU4D30{`durT@vhSgOnZ zPP%~S+rUL)%ZI4CAkYVxS+-WIy%RzF-+yUK ze+eVg)+%De{?{gtw@C&$iT{SRy^9V;DnyPAPUxeE`Ri~0FRtp} zKl`ss_g}t%|GISlx^({wJNjQ6_P-pH|8?p9PrY=K%TLrvfSj$JU(NHspRj@7Fe9@p ztj_pY1uXol3iwZ#E&obo|4L>5N@aid4E*<(?Z@WEDO&Ha$^6mYDYxpV)49ohWZ2rB z@}(!O&`V&==eb-C%)jHb%hzji$-69q^Z~?2azoqMcL0txNlADvnsviSQihDs-$dfO z?AiAcohacevy4)mI_lJ^Qg>#xKeX1rm)Tm<0g>kz0FpC((Od5bmEkgmDLvlVP#m7a zA5Zf?Z?os0rda-R>LI29`yBNV+rZjYq+t5xz9lM3F%(*WYT+v}ys`XL4i`Fy;>|>j2p-C*B%7_+x zJQoSN_QpnwNa8deG@p+a8vB-@4GY`^FiS)uK8uX!hyQsG;HcsyzAq-wJV#3eW@uCi z{Z&0AfG~D=OttxNs5GQ}JQ0|*B-kza#2pJrm<4ga!$d@&g=+B}42Zfi!zXzU_Bu&-%`;ciB}6kg*-0*{j-IbsUMoNS)-- zGHdLc{!H;XNCP3HAhTAb&g|%UEkMtULJ8w71pp2mO2FLA0T8{?g!IokEy13n*azH# zoS();U{PRN^%8^l_zSD*UE%PnmR?i8dPDbpPQar@ES7mm6zk2-WRV?Rz|UeiW|ugv z6-w-ZF76aA3{(|}XCt9r&1OD&^Yc8)$E>hN`TL}t#QA*2QrjI^Mshe2C-uH8-u;Fra)h>5(wt++AXNKe-Vhv_!Ya zGlKs=$~7V$z`ejGk%NgN-Vh_u>ghRovM+5I)Lse#&TI-xD;Ws7h7+>(2cH8seC|EB z`Yd-iB4y$re67J=ORc&|033zS|Jh4?8NTDZv8@E8SWz;+`#{Q^L9j3w&D%M8IfNp4 zOfE4#ETKf|az%j$X*Gg-B`@}>O2l!Qv2Lx6%lGCi2@ud%Sn_I4Wb{nco6F8f*DX&^ zca~-4-Av9kUxN*sZ;x&~w%Swp9oilh&ze>P$UIIg6l^cF9fo57M#bF42%~hxn8MUt z)Ks}X4RaKU%~IEDIR9#P{~)k4>7oRMeqYEu1raow1uI%pL|d<&aPF5U>ePMGs&%vc z_5L>xirHRrQl5NDjlPX1K;29{?!k!?PL)GJ0z?=M07t?rDV4?C6pe>vgv`r8nf2PT zz>}?{6K0;_)Ig*AcISzmpiW&B$*ZZGj+^ywl&>0Kqdg>~W*N8m-6dJ*NldyhFQ{n` z1{)w3dUdhnm+RTEpd@gF*?1+V8@u*>;MWHBR0}!k!N6F3h;=zCH5du; z>UbmWWQd7IBJ6bxWfac`E{8=5u-!D-TmxL1wvK`Z%m}!4B>6pD2=9uMSFCi&E)6iQ z23-NpNCFYT7Fx3x4v0gXA)kN$N_4m6n>IH|Ermlr+H9u^Zv?Z_IE;(j>Y0uD2!O2r zbVZ0j`8FN&?Z+t=E)_%9JNU855dP`qKaL4BNIvnf<6-}jMYPOx`uf>wX~=!X?O>ju zm;xYA(MYZ5dmO1!9(1+&XX&)m+wECRmFY6yk$CFW+s{#3#`EbCDPY^fkQ!H8oVcEI13)WHZydLA-1AqA*Dh*w*Xb7a3BYFr6FL7P zKR@XZ%Z%-fgUF1SNUYB}w4u*SvK28+V-f)xelMt66p1}N(pHf87Cr~g&o;%SJ9Zv5 z$yVE5g^ae|=*!ZXnU-4?h?$-V1*{U{CM%qDfl~Ff1 zb_VjwRJbyts6iUH-Kq@M`nal6j3%trmhQNl!IU-ski!LL@2lY+XzjQjKE zY$`3+vkF(Sys`6PTjwL?`4JfbnvYqar~u}V@)h^fKcB9MuGh!P!^l#&gv3O(()3%L z_hy-x4Vae_}R zam095i&@)sUa|%_O&6YgE*Mb@QlxEenR2|F=S>A%ku6-3rDnc$(%m9~{=~We zN_DZkMm{YH5?hdTmZG-VF0zk9)k~w__4+js< zYYw){9u#4FYAgNc=NVyIL}XpM4ytzeZrrLX@9ua^=>FvmiF1)&JG|>H*UK*@XpT@7JJ2a zNCqIvtB7(H!2z8;TAYgNWd^!!*E=@-hFaUYF}(NW6~U;)8;*mKx)moC@^p6f2{g6` z28$VHZ@A7*ySMV&#*$j@D9wk!$8Lg_vpxa|95oyI%a36vsUb1J?(xAoQp;Z}dtK9m z-(;brU;XYMi&@O?Pw+%dMpoE?qe3!L+XJX&dDuE#_!?L=wx$H8Wh2W0xleG$XI0}@ z(=@i(V*$%w(`3vu=SLxLQStydHp7=M7(tLodMy^bLZAEFAkoPRweE_gpex|+|xVH$<} zS}1%;Ox}?YEl01906X2TU3qqQ0aD@nupaF{3dkewwgFB!qc_=Y$NkLhv%_80%dPC! z8~9}laI7r=BSVwnuus3M-h|D%`TkzFfvO^6eWM;TN)w1OD(aA<#v{)~D#srP6wl(> zkkMeT!i|9w!UyrMRW3H*#S2|~NV>{^%HA7ckVz2u74hthESd2AWiE&9ydF>{nOu2# z4)A!oTyyN!3eeux+ssK;%Dj+&d@=|g90^rhn_bb8_Nq3|;N9Xfmb#wUOzoNvRlvjX zC&6U%^1J#{ifY)vAl;UdXUhSbolvH1YG~(2q#kb{p!cw zScvuof5Mk?gFk1fG)MwFg#jvUx53UGS6kYhL}?ASF$ts0PQyIJ-~jjmr?7X})2t71m6hOH!#`tByvw z9+NQOd^>K`L+oBUuDt&&fDLnKu73aRgoPT<8GgUfR1;++Pv`M5kQB=nnm-;mZw$rg zaN1~HoZvH7^YF|`U%h0;+!NB*LfOcPpolVf_T=tZEJMA_D<+Ex{CQO7I0HKqkh8!! zYk2>`QBWggAyX8y&7>PO0CcgYB+Ou6QYv%-RKd+asVxt}tPR49wI0|?Wp*P7{0#z_ zk7gy<;Thb1qUGniNl}V#UBhsQehazY`IKIde?@;Qk%V#-m#j5)ab!7xR0;!#; zHZR$mt&>}7Hi(J5Um^5G^JPjET|Bi9Q7&Z1dzEf{8_peiZZ7QGW49-==yWYPn2O&& zV+qbzl+dAkiO-Y>0K)`xv}jcWuamUxE&=s_l{FZjcD-%WuKhI@g6aIDu=_y|X(92% z639C;A31;waB>bUMs_|Ok?LpS2JXo%P;BLGsN3ac(w`mmsjD=}qR;sWyqv{`o#ByI z3~b<`0gTw>e!+=Xy(Y^L{p`KaZ~}YK$<{Drk#tMvEXo~W@yG+iD9M)j`C6w0zItqp zEV$^{Ip2yk9IRQ&h2PQ{4V@gR&epLv)i@r!cf#J5MpK%J=~)2d3ZUThQr1{XH{ zbao|7cM^qacmpFEQZxg1D>aS4P>ZS#fc`{HF272|uGf1jetNkM z-xKD>1i+EuyvDTWRhk6F+4{!9*IFa&P<(T`T-biKdA}Ci0(!ynpqmeK8aRqI&f>@( zM4=&Kw$b#g9LpS!fvaK;nKLzk{!tkn<_h-)%aldxOp7=PzVo z<9Pwz&sphyi%o87k7xSDbl(29C`%_BDa^{>`@hmpXjGEyw_&JoE;sT>@tew&axk2b z9cq2Shr!l$xjlvK;4Eu4R6M3Anem+9;?_qgn=urc^mzuS^{b$R5YWk)uoh7Vx;dlH zdsRKiI~Z_3+H_BXE^MY6-S(s7p{aBhu$7yqoLrN~c)#h!*%V|~>ileG>+4J0uXVBW z+Qqg79XcZHYaCGi11Qa^H|kQ}8`2j7Rhn11XX;G(52i|m4(Wn`$EDS^g@IAeh5k~A z_uLRI<+Ve#0)=bTeUMi`$F`|GgnOeuX;{ZEfV6bq=VZSD1t_?P<*@BSe&t$22e6bu zXd;k%bZ3QJVsM$YMY3%IUI)TFX6me6|@D>v~AMOI^P^d}D6E zl@)ewUA;lD$!eK^+SjCRHKmA889^h1-+Yg&(pc4aK8U}haQV7p6H}`{^nwYytZiWw znQwT+Q+!@@_KWUfZ5o(YBzXz0F{73?W>Z@=c^b#+_u<+&g4g);Wd>NhEeZ?90ovjC z(^xik!AE+*N6q7B`lHe7qqGgnFPGv;Dja?Ah`^b8)U(h?O+JD0(0V2IQtJ!V9_04k zqnk9|#8kLDG!Pp8!XmSFVfdDo;OdZVc+`_?jC%|7y2(?aM+fctlR8E_(=+QegI%US zQ2G&}WiG{Rp;$rtcYGvL{g5eF!gjsZdh{CfF~xuC)JAh70~x_#{Hcp5;yWYE9~hq} ziZx<@`OP=(dWYBg5x&`O*_R5 zlt^u7C+a1WzXUIYj8N!4aW^moF@0@6lrNW(TQsz468&*oeXYQlJ?7#0;R)lpiTl;_ zGv>NrY-b639w?_xz1~<`?#5`THO_Y&`@>v^?!F0hFz@6d#v+X-JvmQul4N9Jru&kO zFvkgx>+CMz->8(rV_cd)pCu@Iv>v}|`A1jIG0Si%q?D&>u5Bz_xzi2f95j6<0-X)40M`OAAl{~h$Yinb0l5I)c2_dndqBrQL_lrHBg zUh9lv0A1;O$q0Jp8G@0?jfk1AU%7RYuzgN0W6T40H?Q`D0%fGjLMt9BI{TszgN7sP zl`)O(cq@<#Ftb8_uP=*6>Gtu>k_N?p6}(G#eH(;9@gv}}wbiRw-$A!O=EiMyVtW~N zWpd6QP7ZIoXq$J3FF5Na=??THE)dBrE4Pb|74A#D0J+}2@g7J841MN(`t7bimfIxh zIwX=TI5f;}LD@BD^%lsJNO>-a9iZnMWWGH_{EHr@UqA0UB>|BqI)6DgSU~xCcxVOP zO1&btdq>%04^H%=Moe8njb#fR@(cev_syZ2YgCHD0?<6)X0oX> z9o>pHATd~~DrOY2?Yb2BF+t?~`tY1|zcd+p&%fhxw*dJukMD+1U;6)G20>**P|`%B z-GYy4*esGkyW=GebZ}R=TN;sl`gm4Kqz1p~1@xw^(3JglqJ0nB2FhH7>yiE(PK<0L zbo|S-9L`;%^m8|mN3g6$MdVY3+cE6sr#0EYMEk=Ng5z}-T`e@zxyeB=e`_DmW2?VE z4FsHHbo%5Qxvv@{hWX^RHeg!6fs@L1#bOChD1s*!deA9fAt>^qdd$Zm9r3s|8yC^f zcyJ}%`kLvY^U9^PDS%+!eg!|9Qw;JLRPtd0fEqV1Pj{AP&iLwlF zXv%(_l?UG!pb>GX0S!o)jZ{ti2h$dt^TZB0eV8nVhKI`@>S#y|C>l=Ka7)96F}iYh zZWF}K>zPus!*Rwrz=zIuaCMb=d}1)VaqfcdvWT|f>?52=`%yqs<8X0%x+-p0fKfVS z5QGEI05hoG5M^b}-X3d=km#BM8^G6yAs}95m$<-sHm~YVFqpS5-F-79is|N*fPDhm zQbF|v9bpM5iu(3=;abB11#o%W-(Pz6uI^wF;F6|GpQffUbVY~kMH-!pIGQ$-;oAO@_O^ep?^n#;CBc>P9Ef>}3%MwkPJ%07|^ zyM{T6_SpSIi2LO;zng4y55qJ&urEm3ag`C?W%mxCVGFe`KGPJgcSCI-eCN6QYJ#B1I#Z%8YtQxtkd0&z-c+( zIIv%U&Yw(iFFBp@=?W$-x0)J_Uh;DYt<_V}4Q&U7q)FLqm;wsYJsC3K6fb@z!0Ou- zIaQZrtx7Gajwm{R+2MEEPia(#=7yhoa-OCEs%*(l6J2OKx;C?sQg7HQ(DkFDJsJHz zEJf@8XNyPMd^#?$RDaI0?JBhd8~N6)dHZXj9S5>7a&#mi@4Pw+()MZy3Nm%1+SSD; zMNLErhmz}3&4x%D%srNwY+wEWL04O$qUZ~SiqquNLi}JqRk$KxQWjH2S(5d zL@~u^pDX01asc4??-wc$S#5WzQs(<)4Tj-0DTVzWUe(Wy;5N8+PWFX3Z^#}`4se%N z4X7cC-aBDzM4eC7CfMt@XoWc_Qej>)=+qYG2Yq!*FZ36IQ6-Jn1&VsS?sIw@k4cDj z{KWhP_gJkp<=j8cyUEnWNADT6e#yFUj$_KQ6` zfoD4tF?gi!W6Fz#8-W`|GN9{q)+~4MC&tAjfWR;LoTXns`-7*=d)KoG%?%a6Kg8+7 z(;&Zv>nk@^Eb)xb`3lvWj>d_)TbwZr@hq*WX03At@+8ccfsR-_Eh(crW<&!x^DsN^ zhh8_a2d1Z947hAUljM#0_I6pRmiU@j%jz3{*PNG1<*@VV%W*)0NLY zQ#@Ou#^<}@i^3xwd<(6U*3nMG!xw6Ni*m+;X-5VmcgNq$I}4VhE{4#~A~49s#)7mr zwctMNgIVW(2`q`$vWWtNZ5bu;@5!|GICzdsW~wplEZM{N|BS54FQH8WC;lSchWd@5 zs>8^ZgO4W;7Z+g!@4qc5Ei8@gBJj&p{+Vxz^Tl|%=ql*7r#{(p2Qc`?H!1u&cIJK( z%5_?W^rliKKRfT5MT=74B*TqtTeV0n9@MCOAM?!E z(`$XMUfTK{DNa-hKCcX_+YE(Eo&3zDR>%Q!Xt#Id*JC3qQg-+LnnltN969<)_!7`( zbJeLTcTfZub3L<;vl&D&Hu3%bGYn_AGkq&);(5B~Ir|Ibo10c8_)Qt-9U$4uUt_s& zqYadjK$!UogqaEEfPudWGb)tPo|4L0$P`qvWX)sc zF7k!(K-2*AYVgIX^rV1|Z0e**?9p7T1+iP}w#P!b@tMC`_*>)f9?>N#d$bJuUfZ`r zwH|+sg8#1H6itK}D)Wb$1|vM-vCBY#ERu!)JOJ%GemhOS@U8R^Pi{PwbgAHC7OrTf z--?9pFO3$dmJdJ5-1bw?*MPYv#h8J&Slc30yDw8!(!Tk~%f{G!WYwM0t-I#wn?&aJ zYuSBdA;}?C2w}RrJ?{<5eCKseO%ykj09lPLPtK?mA9oYnCG;X zPU`H`_tULT?EGTMA&S5emI zQiUYqJ##Y)>3tk5lHS_}js8yMv?&{V0lL|DFH$QCD7!_Vipdsx{tIYL^)P7ABX+o?qpo&~eB8RS1c&hiKPe?wYqDQ}6c1Qxx&EQ9 z+@%6>3tud`M{R`b=P#M9Op@0|_!tq{%s2Xn zlM1sYJ*X5vq!+?*%Ia;&*SL#L9u4)`yP(fn92zjeR*G5riJ;G$4789_v3Q%i?Ck3fK*>gG#t2`ZVT6mKvd+s@@Po;umg~UGKYR_ZDlUV3EWQ ztq+fV^~af!3iPsQat#=ksO>kk&yR0wJoX$lNPjj4W$YTUyne4$DJ`T^ZihFL8yE<8lSomDuy+1#yolw`SV7KupL&UTzE=`& z#P*A|xYv5OYf^pzIJco`2ZA-9t(NPK`~1m?U@-K?mwDUyrs*rILwP=I zGxbOYX+(ouRznu z#*VQ$T!uxi$Uysl1p?j(_bectW+nMh`GWeR@hs3E#VYo@e06Z6?BL75jOO>Ta&fdV zy9K*`pY=<(!$P~Dcci?g4Ut#IGsHsG0{J+A=%mxM?d2UA`@T)n>tY+SX(okqZ4l0x z7*FR?U%{I#xn&s{{pX^>aFQ=d3(`2D+}Oc4970+aJyWw()rPZM66ZAN%1Qiw5pPKm zXhVk#V(jt^xn=^BlGj)Y5rj=jzq^|!I$n_4UWxf=C4IAG2|=&^n$@lwSninvIWMCcR+P4JzCc38mY4}DE zk|sFdsiAmz;MuBBa4E!PAXn3~BapMP#~lY%z2rH8-EOzFr| zv@=o7!tN$@b54p`ZbYhkA}V2~!thw}ChAcxXsy%rKTfL_d@d zpmQDz3|4GVR&3yyUz_jhhbEbVP{^Z;f_>}2R#V05@2g2ydPX(M=4OO_KK!v;FC1}p zO`8nR-yu_b-4B3{Bo86(CLG_V7gI(qu7TOO#4w`rQwIQ$nkSYQu z?taJPm6mlEc?M4D*Mk`jQPcXEDD~D0eXl7x?)Det72TEoT?^nKTGPN2PorO?(xeL( z5s;B+bO5bMgYr35t6=wj{8~xEb8n2`{wtcs*566s063Hatv2a7n!RbkcE^>@#KY$# z%R&AxhlGdE*?nR1O;?F?dkf0srxDl0Ik+NZPl8ArO#!JQHn-lWk$qoQ6cCt_z|jvQ0J_ zHnTr0Hza*Xm>^o8&&Jxf2j2DGjB+jYWdL@ox*i`HLkxKeH(q=uM38e@L#tZwMI{;N zv;)9MN&Fl=-R!2jK^j|?YlvM7$2TOzJ6iO5S1V~qVH}~U z;(!=qzrDr z?%nG58c=y|CNb$4F!3OWr4h((`y8aFOj>{WYh;B+ENGE_#zSBpc|J6CC3C7%KF>QD zs=zltn%`eH(VV)rG91Y|e0teG%)6HTI+_D4RnsG981h!&(COyxRd7$+o%5#JtWxq+ zmaCt8#m$CE9m+GshaY-Shb19rg_}qf`3yy?Oo2Ig%st|U$?dZ{gK(}yB)bsMCUU6H z!*lb9!#mRbw;%5sT=$LbKsm%!1tZ%9yBmuJf;iVCorE}>JC_9kMdfYe*+F^eTs4Ji ztd@632UK#I7If5U@V#Pnmr2wmU-(_4jpjERtgnY~i%y-BbPnsVGUR|d*4i~d-dXZp zC=PPTTUplY7R1be(vK4FOyGbYMCmsT}$NHjXGpJCvG5CSZY4wBXxiIlDmPGT-)VBtwQc=l~A13oV4T(noiHi z4#`1ikVsDszwX&(g$c{eQ$6c#I<`3T?l$Q}`3M zU`u9eMYwl+F@Msv(L?ITkrDSvxJ>*Ax%-9XRrKZIiuW@iV{SYWM=)a`@p!iUidoR< zEPXeE-QikQ5mL^=$H=l9kO%BlX~A*O`5JVxSiNhJj%XMbk%pO#lygU^5Qp=6z0Us& zj48vRAwi_}M*OVn640GED^n}gTEu=*Nlf$_Ih)INd_V)SV-1+Ku}}3<@AJ4&gZbb^>zuXCk0q|wyE;iWCbb`K)y(GItBgY%_ zDR-L%?G#zcLw=mwFTnfqkpiRx4@EECc4{%~lr!C?^zgHh0xXpge?(p@t{@Y?|D*mg zi9C~DonANK%@EI9?!Fmf_nq3Q^eC~GsEMSw5%=E@@>H0(>v*mdsMk?ka8vi?0w!B3 z%tkRIznyrZS3{uOF3A=X$c2}y&0Lfvox~G6N7(=&2^8TxZdOI@08(226ZZ11&#dH(!dG3OVepMi?+smkP&3fAutq#h};9fF< zebkf)E(>v7@df#m>r|>=mh}EVh5@(cjjJuG=|!i`I8S;dK=5`igL1|C$BzyySu#hl zbneAzZAX=FTxGgj(7@U>&xnZ^E%TH5<)2&>g0R26dfA#1Ap@%T_wJo-F29rk=r7Auf0+K62F=%1$yz`@le)=AeJDwAj*uHE|}BaopMu$Efcnt$#bsG1yex=R~0N zrnW$A^hX&mAi~0SlzZpf1FFdvqOd@+6G}^h+fB-plhV(n-S5^KsAzUsb_WR>+Avx$ z-r39}dkdVi8(3K!l^0pPJv#oe^>;_{zqmdfkF$LqF&OY4Og02$RQ_O|7^EOlviY)z ztH5fv?{4~lWa=Gqi&5Lrtl3D9!yJZ+EiZsa+sD1wyPBHypLd(KJYuL_38#ti-dnyK zK3Hf9Fv4Gd$;`9l#vS|#KzaEuFFbaM`Hhp-JtVwlv}{_x=4mq7 z8pohY!+Aj)>yFQ_2(spMD<`MiJMG;3`5_Gj>zQu^~wDT|B@k5!cVq^#Q? zXM%w?@E7p7p@w<0m)z4Rp0!4rzmD+@jBZFu;>~Z74X3d@YD@KRLYGgULQwi%tSFcl zePoWpOLvU@#O{9J}eIiV^>u_%g>%N0B6=RO78Z*c?#_R z^iFHq>G$~_4H6mgx#(_ooX=e9Zng~9k?)i5B!#0~TTf$~-Gsjw;l~TtBAC`xaM{d? z3s;wi5cx>|^o5Rnrx5UN6xFV3p+bx2KDB1Cs+g{O{hS7KMXL96O68k(CHfL?+2=j1 z^GO>3xk*0W&Zgv@@3Z42NhgYzGAI!yw~p)p97{5Tk!=E`R$)&(cVH1Z`C{R=6P>>@ z1Hgp>7O@|LlzH9G6~4-7G-W^8un>9>*b+<~;?=9}+t=94vfuV+*S_8F@vvL3s%Ozs#$x!%j8}{_E z+h#nK1$NLBUQ#_e&e*$9iS|4(PcksEB!#%7Kv-rm;>}k&(_No1FXe;!)Bo@<4dkY^ ziru&+8dZR}tPX$c(cpOyW88%Y8;pjS%y(f37S>qN1+`@CnR_GmVoNTpA=OjCw&N?H zY_oj6xY|D(?lasMaH>FToO;#jRy z7ArC1ilc7Amx19|Lu}puv^zJS^j`n_?t~@Bg@aZg3fD!dj;MV0-sbxg zhPABZ1TOCDbo#ro2A({3RI^;MBSIOTs3$^uM?d;+e=$9Pj$^_+6Lk$@?5ihDI`8dz znI7!zLA!%Ylqty||+ zN2T{gf9GU_Us@LIDfu2D(WNiljwTK@8};4m{T_#{SHHOD0jrC|Ts>n;HEsI%_TO!S z6PWdN#Keh!TdbSr*Uo1_kTn8JkUSNN5w6dAEI~ z&QN&S-Ht$Cz`n3O?pdbEI_(;wjmU%&jZoEJ@G&Ls=?*7~-!e2Av;b@+W8*w<4VRWy zI@pf7NHw(F)~N~4&Lu(fbnPqp&RoE~M`qzn?Y5jXNTQ^$!;Qh8tb&^H`H;{Xjg~Mj z3lApnd4ua5mt2b4FnJgrQx2)b-eLRknmTK6|C0B^(-9hfTt=-Qsd34&#cp|h*ku^0 zAjes$w8^qO=ln3B8MdW^Xuat&9mR8u3{j=Mu*eA3KCnt*U&`H1@5*nUAMx;T;yyur z==EyI79!+ml&@sq+jJaJ%;sJFwq7g2%vcLH1YJq!?mHmPbr_G_5f5FTVj`<^Dw|fO zT`9G80m8=E?n1$BzOHkx*tYfwfOc4NrXW1JXl&6@-NIdt0cXMFY~{1xRI)Es=@Db; zMHIOC{AB+VXvyO))45-leo0Pra}Yqe`3QetJ?YI3oDZYCI@$7xlDK(~o;zT$cvVjD z*@>|vXohshrTq?Rze7O=Z*8UbMlp+{ho660==+s?RIsA(X6w}H3k)wkRNUznt{RI$ zon3KG_Dr*xu0n5Gs?)|bdu^5ny9vqTIS3WmaIR!T3^4o;O7yKTmC4!vO>_}GV>W;l zP%SjWBe84aO5X$Ei(V6IiU+x7GLK*X1rsi7h zbR;QZ8#Onx8^$VFCz)v*JF)OZ)h6rFs@#S^kJ8&i_7{$kq7B z?$-HZSPXgs#kZtF*AYOu8(qD81Si0Ff2S^)#ZULceQt``L|TnO?97XR$hSp=QMnQL zBRkZo-o>+$wWr(vjKIK57a3yC=~y#CdraxkpAo&bn#FNCG49X_Ipgp%z4=Tm2aDC2 zv2R61PP0QuoCJ%=MrxtgJyFZk#47Om`3gf)Dm||VU&0q|`UfL-Zv4&9fcA2|DJs*D zewMC*XE0~`iR8)ClmT-`Y<1s2yqYnxbsqMbV{O6ouf1xf(tbXQc?+D++aLqbyxrDy_eI-8{EcrD6|TI4 ztO)ko^fg0Kb9kuzYq0QIJTU-@iqTXj$2~Ph$3n8Bs%RP!B1P;PpD})M1N#7kjiXf1 zT#2q!X$h3wy^Zhc{TBa=k;#%I7w6fOPh&4>#k&}_n+G)d>WQ##@g)~1djX%aKEP08 z0t(H}2=u$ZlDZbWPWA<(eqVA+4&(g*6wFQ{X`Ccep0$1zQvg0AP-RbKevqy#dOoStm&jyFccZ3i<3B5oqcJ(G>KbuY9+4Pg-Y*o-q+2gr zWg@}EtdPnP4*`2FR!>#<6_x!tozOItO3&Q36*DeBlISm4Vinf$32nSFE?HB0+@eB* zZ9A@Z!|D9l=>~0qPMu8Q?4=ujR>0tD&u^3G*g(4O17Up+0rNdWW+yth8;HVuKLfKB zGnmaxAq`UajJ~<_u3RqF^JPCtnO$DB?^pFit0QrS+;g})D6h-ARkA-M&Xo;hDcz$Z zpWM#W#H#4l%DYRI;*ylLCRq$yv$C5m1DP~Ywb=cXO&k6@(Cl9T+5j9L5eZR==w%{; zrzp`PUon3ueks#wQ2mhPnx_iceI|@;6DZM#pXsMVuhyvSwQ!T=rg-fNSWU5gT6gq! zJno(->9wt^IRsS3&7-i-;U4`JyGUJ9GIYzw+Wml@) zNC1+WFXfE#m{Dy|1#-VYe$0swgZ7q>zU*n-#ZeuwRW3nN$Tp?|f zHh}EOPRV%NSk9-ZVgjdt?-1lQk4_A>OV<1oCT?wru&eT2{ilT14^o)&v*&}{PA1a# zYbpD!sJ${}5+ZV;{kA99K>b2Q| zms|(NheVbU+``fjgnJ;pB2<6c-z1tOdLP$kw~2S#32Hd=y%cTQhV4=oZ{A%b0k%&L zs7PGd0%!LvC;MBypT4^xk}&FIKQ+ik`zEw|-o@?qgKN-T)`-voFsNX?u`6ZE0oOM* zx3=b9<#fY!AgEl*z!y%{+E4PgiE=ch+DUvyNdxoZivvJDL|$hCHff7m$VCCZn(h z0bvPE@|eKj78m@RE!_#gL1V~eHDRJuuEOOBJKCx{Xp3)*1AKG!+zpl#e!~JIqQPfg zBk9A=Gi&k+5a(50_=P)lTgdY5Y z4`3j1Rqlc~RQKXrTPRmO8U02&U%0w&aw7}hemVJ-8}$ut+N(%UuHYS={Z^Rb`pS&AdIh zfp5(F!m~`e&kvjl%M1qGQEo3i!2&soPr7ofe)DFzr=7xmf^T<@uk3dfa?j=HUN85vO!rJ>z)Q47;_219o<1RzIbJ&~X_SePk zh&_UCPO$fI7So=HhWpYqq=z%l%NV^*u191h-wci4{cWO3AEob&^w{Qz5|Io`Z}>6E zrn^GUv*A2YhV%^fU*P3!Xx>fhy5*wn#X=}k_n_bP{=?-J$_Q@ccF_2b8LP7=|}NigvFm{N5?FBV@^>tkl)&7&(2eFS7N5Q%IKc@O)&`C74u@(|3%fkAyt6!Rj^4z9C9kc=7K5_; zvnoJ4xWu`6eo)c8{~JM zZt`qxc~A6mt6%h5y)EkFAN$8yp8s9O5C0qfCy8JrX`=_K!^|zKvnl z9=g&0dc=C_q9F0Fl^H%1QQ@L?pdxc%cwh5c|E^3StFVXR{@?*Xp7T&G`^_P{kAmHv zmFE5R-}HMkS}b8GBqv9cWxV{WhK?Pc0Smm3=gXamB0SDt+JG`ZoKhc_z;v~_oLYOY z*YbeFn_IX2S*i-fbjG?*hV%s@o@D?-fjoo=yM}fy9uwLW=$7#PoSw29E&gYS=#}l< zxt;jIX=DvnB+9%I_cWQsBJ^2(4gyk}&3NJavz>i)aS^C0q?P2|wTN*>>511;sD>8u z*_5)Ke%d3$l*E00|9WbB^F44sYmuII7^kD=4DMEJr4KyP;GG>RGOs^cY0~2J;h|Nf z&7W?wi^)++iyO22+TAY}z3ZpI^gWxHI=@RC!y5JDh01P_+r z?(T#ToFIh-2p-(sf(IwK7X%CL?(XjH?pio^(YE*bc6Xnh{_~A{Z~tM`sAA1E*Ie`U z=Y3`}y$}5QUZSb<_on*kYAAnzW68JC!dc#h&JBmz%~Urgmo^C?i)*&5U44aL zD5r9ud<8kB>3TF>_!qVPy6XKX;od^>r_`$NDLi7`PQ&r7!H>Xz(zU(WYY$Fji;%_{ znIAx#-7U2eN)YKFFMo@jpB20Gmsvuop~HBhKBjEV8(M?9N5s^tlkgyj+z-7oW^=aP z*W~%6C6q!M6s6s;S+l{8S0dTU#dZ4t{^PA~*O1mleZq6&7c*nBRqAAslVNYEutKRtC`DaUw}GtHT}JdumH}J zOrT^ekC0=_?+jRgy;HzqL@m9V&3YJCC9HNS_t(5!cKNb6e&WRQt0=mj=l7dPX>dv!YBT)>%2_Tn@>ZQc$Vocu~(88xRO*oI+@@5>;*Lp!TOG^6)%zqeWJic zn=1MZCnKss;Kk^<83(4Fe%hqJNL9=hBxhQqrJ~l-R-#pA)*{f)?2p;k?8m0gv0{C3 z;3q?pug?p_MLDrXTN!)8xMex?)WB+4lRmUwIa# zWw4R73W^Ss!vFov^@pB}M97N{7B?4evT>~TK z_yxt%c18Fr%84y=f}KzIPWS-=Tki2(Rdqu+?WU(MpA_3c%Ab*TujnRC-bk|`AmV-^ zyMj>eea4#NJnReJ7@t8Lf88iaJdez;ic0*-UN5kBJtz0a;YJ90`Xj7f1*oC7$u(K) z!-%V8HglW-<*Qg}!&1LyBKk_|TO!9=E>1DLAgtc#^+W>ZyZ+ZcuVFvh?X0=|K$Au@ z)~%BPPWyz*S`D$MwL0lW^^8aHw!&S*s=e%Ba=*K)yaT+-s2;1?xN|KyXQUX)b(ox# z!wTgpi6|;PevN|2Q1=I3h=ccN#K{jFeytx#`DDR(0jGX@!pOV|NjGWMc^j!g53w6YGd5qQdUn~_dy;glpT_klpA^)j<=|CU`b&j;g z?kn2sPX~^veO&UF_qbOhr_m!-c3z%9e8-b+o~E$~^!9Ka!x9w12>^Xq8z-|s2gD6G zHdM)B{ez%4VdM6yu56#F&Yj^?7m_Lf-BqD!?fPt6>8I?1ksm5fpe1_x0V-|BX6Fs0 zR34l80s{vqV!b+qbDrRV&L5rQkQ} zJMppHnNgU748i>Fsjl$Pzm3Y!8_v_aL3n3f53>oK4~I1$ad|h>Eh6{Q&yHJ$q)DD* zT3d&~Anx?)1^BQHj0!9r+`65XzRy^dS=%w_lOeg+y^`AGA=ikz?R%Pd4$j^cB1?Gm z6k`<5;Y(i^-DJyq!*iLv4N<^m26tet2#!6~P28@IVypC=h` zt}-8vR%5P0Dl;5Rd4ilynF@Q-`96nO`@0p!@00$VhgV8pt~4bw0U8gxfu8uUQNI7P z*%64_h=L!PUEW_x#si;ip$V!QWAfBbzt1^}R(@(ZxNJ@Z9H;$00FyFPK|br5mQqd$ zfHHam+N<3oWlOPu4CVJMBAea#w4<*_h*2qLTOZ%-(D|jxr`FiRC(_<0;{hewbE))x z6Rpn|#cyFB{kYH3Ao;UFmkl;^qyGstsO%?=T8Z+4n{mxd##!R0>B6j6Ze<=8SDTtH zslbjAt>fr%vulCt>^)i@eCbVI-aJtAEZIS!#&-WlQn(Xh%L^Plem+r#S1tID7<}4y z32`0B=m1Gg`Mo5>boLEKK~4^Sy*hxJ)jls(_Dn7eav$fjeQqjy_?64~g4^Ix@!ymlglVN`XL zn?0Y?u-_qTku`9E%=YKR2Ka7hSU2kSauPG{5|m&UW3!H8J-IR)ZKhha!w&3hP5ta1 zJ6GP=J@1UY0MOkM*>YL4UjQ<4+>cm5$w&bxei-!CpRj~ANyxb;ZElG_A%iQn_UEDf&fzoJdU5wBdFaLprBwIHiO=cxkn`@og4v{n_ zbuml6#u1OR&WNmfcEYCJW^E-_Ut{-~f^Bi(Jmg!o=iQK&hgCJ@+}+k&T&n`{~q#kMSN@NX9zYf=fD?YmD-Z<~;5rJjt zlx;`{usy^9z2XdIQU9+#u{J1$0yipj@YMZMB52k zUVWu~4v7~w`R&kGrj#0Z$Y~381G!8Y$Q+&3?;4bnOv$?Hu?hrdI!0T4yTTz@JtN4N z-6vOP*CmPDj z<^%7U$K+cD*&Ps?;X(7ev`QxN2reY^idL8}5r~&#z>N!VXzfmzGTAS<7%MsN zHaEDVd};?T92b?(wBH9YOUFF+A5u{+%k8nDXEuj+0xhkjIO@t zUd$&*9uYORM(}nleil7T#4_#uP~K^^cJ=aPQm+l*)C9gA9wgf<2hcp87w9!s@BpwD zP-IAe4cl3DhtTEnolJK-_crFb(iGJvDl^*}t26h4oTH|d8>O}Fol-F1su zGict6MOiRvs3k#~Z(gD8>2rk>02i_eaM`~7(gYXzr$=VH0F zlK0nG{OZ8xLb525KY>cbO#PKoE^@8I>6(qN(JVDz(A@Ae?Pstb;YX;U(RqS(D|j!# znpq5Q@JBxsX!V`*O7izUO5jdk+*%8l?O74A; z2tEgsW9zsBfbrdW{Q-<*w++XqrSJN|?)Q|Mxt8ewZ&7{86f-NDZs{;G1}K~}+=$*MdDOKiHNI!e0S-IW#NCxhQqk!2d15kWR#=Oe&g zlL$JmzxfJz0<_Czv<6=yk>rrszZ#0Ha0+RkpzVn7uKtno4pk&MgyebVB{m7U{h|70HeN(yv{<#W$Li5S|L)<`1$22uLz zvVza|Fw5yD7&*!Tty+Xq+@MKJeEr_ZaA00 z^7?D4Uj7ZrQB5$->A3xyj{1(BHWCV!CIPK}9w|68t@KcbTil@e>oHJdIY6biZ(gcw z36t$iVYN87{hs^U!^?MuoJ(tS({F4}1)f1s@vz0tbXGabhv@6%p$PX0M>NBXRv4$} z%(K$21*A2LM`|UpzeSiP}0X>PWN!+^ssd-B*iNf*q#J$lYZIqjUI) zkG@t9H+`8*q2u`~ebCi+u+4l6beutTd)E9&IVSpAB`Lir&z5S|q@!ZynFrA#O-;VH z<$23Fz-@JLUi()1yw%)trmP_+YKCOp^6I#gupGVck6M(H1c-%Rwdl(MKW!5i?I)P) zc8BZ9sZgRd32;L3Z`I(M|Nxf zn39kOK%#|W2T+(OYV_!*PQ6ILpcG)A@EFAo$j)c@tvn9Tzk%~zA%+t8ida*H^5z)7{$A1v^7CM z^t$}?FbP$}LF@7ZK(%%kjptoxR?R8;;Y$V=<(DfzLKPuJYEWL8k7V&wa8eYJ%Zh%H zL=-5o#TqtpRUy9aGgJOyY#%Obo_Q!b_KU^%^NOpf@euoF07Pa&JfCTlI21qxd$cA$ z*o8#;gw3K!Ct84bj-f$txThaDN{OQSp_$J}>H1P!iR%`T-M3P>4oifeg3nPAT9Bai za~Rf4)GUYZiD1*=jIp^5@Anr0wv~3#>f$3|Y6EFZQzAeAI{-N#%eU$9$dX#U~w#X-4Dv*#O<`_BxTRhtVhc;FhdO6ICx zE;j?H$*MXHGjypT`vjaA!_KKEdP6{3wjz-+(`Ij~xn$QpR(>fG7go6zhazN0zLnu& z2V!B4leLlP(AmK(dyxM*v1{|J5QD0W`_&UE>)CzYHSBdz+I!nx9PY0PwO?D0Srt3@ zkk6UDNc%&aY5;taIIMF(6in>-fotQZ$>YrQjO@2ChwnDXxIRryrYy1wo<@k>ae-94 z{(_(NY=~(wdi2xyHLAtU8rInyjkW}CND>1udg1RAH?~@Q#+?L6t=x_B%P*vd3nWi&}dCS~%DRXSIenJ!4KTweoLp0{ETIILZx0^duPJAWNE+W&?(q7G6!K zg+|X{&gA)FtT(U3tKWgYdT_`#V#`RCisT13o97xR0!5 zqb_26Gwz9;)P9eni%6#5W=TNCwbAKMw;ejl3#2Rs8m+(99o-QWn)ve|8~2GR0Y2N# zKYX^q=xT0kpK`v{D0{$J(>UGf)Eoj74GW-Xk^n^mSNT_Gty|(ZXKgL&n1ev6(exP} zy4LA)Q-;A#Uj&vvs+Dcxuv4Gto>>Xd zU>sff*fDi~We}s+xs4s1ph1k%q+Y6@zuy^zT#IUVI4r*p3r>^O;o|#g3y*eEs7qEfz{wV^*v&2CN9-%nq4Wu!wU#O;@%HokPIyjCk zpJAA&Rn({wI2>siV~OsYX%yFL9`dSM#U34N(FtM~nA?{qeD?{>K?THDZnnRK!5D-amLdV9U@ zH@``(l&!TN9#FB#vcw2L-f|lnFSR31EytX1i*2mfi>v3BkuiB!>a8b!|r0-3s=Iz#V_vH z0rNWLS4U|WA3Hzqi)!{q2wP|)ymFKDLf1B$sC(BDemG zICa!J>?WC!y#AqSbp}z)BTH2@*|+1Roc)Z=~5zT9_2q2NYqEk-FnsiMLbM-@n%9XIEK`V;i13 zxBeu>Z+5qMSW644o1(3lXT=}SR)O!&Q?^To^p(EjduS+NxKbu^sI>T zQ}+CPf&Ri=GWUGq{qTzdGy8nWWG31eE3A&xN4^PHjFt>R$u~NO;iSE9A`fxds`?i8 z`9K6{rlMx1BnxwGYnup%wtE4JFg)<9wPdQe8XJPJcx9TME3atH%?<<~xkG@&J{6GR ztz+Bj=-%EiSO}`qv@(3VJH}d%`MCq09kybuHa#^T+H+H|_cf@I@pNbMK17bmkEsiW zRg@JG=$2pmv97qEXU==7qERHg4VC3?2sR1tmKXHmgA6)9PikL{Fp2z`NI9K;dTHOWy07IO)t2r{ORg$!J%$GGG~-alk@ow ztCeG5Zn`G_gZRXUUQVz|*`<1t&$rzck?#i_v>frra-kYHPX5%Yd}x-PkOR#f775d$ z3x?JHxa)~?TtLfoDQ_9{*;5OI@x>=cc8cCORA^I362Am|uL@gw&QM3U0`775vq+ zQAct;m(PO5)18|z^U^x8v>xRp?v^r|;+7H;3JllnQs5HBNVf799!G zUAb174qJcfM3kS_j!IHT^9Egg#J&#S zecbNGl_z~f?ysRVA=Mjz6;+Aqg4?-*+?ACj$~hnEtk?T-MxkF4a9mtxN~`DTSf5L; zp=5HeM`{C1yBX^Ny$Svca5&=RdOaFp)U~1nj_HXRc1m{gX^1`u0x)Lan&2l~80=SdpTXOJp?`mYM;chv4 z?dL2zbCNCxBj1R~M1$S0Mc4rTlyd8z+sNY3dQnVJ(9umVYmaMiV3A!*^nJG65J_{c zj6U)d#!2pSXD|7w=Zi;D(6x^{$O(+UjcjIfuNQ%vHkA}PaeNb;f*?0s@V?;;fRAEh zCx-dVKcp^M7oc5G1KsBtBxl=MU{V*{SJHwUrvRYDe#qR&Z>4ao8+L0GMH#;L^DUkH z2F-Yfbh+L3L>3Y$8b3BTTN)@#`+3Nb zxglndr!?{vt=v01%H>VOo!YZNsRV5|^cS?&c{LK9%r2b@1q#F0bhiSOS?lA6pCbSj z{DM+1keYTq@JCkSf${ByJVc)Wt=#!aLy}~mXeM=$Q|Qe)T+8*>XGld8xkdWVJcKA@ z;}gh8T4)Ndq`b8>dTZmrK(nI$r{g`r&Rd6LzbhX4M<4+GFz1t%)`2}0|wq|!~lx{p&~sr0{2K+Qa@Y*Tg({sRby2oEmNUV}4N8Gdu{ zJJj=+uo!|R#nCEcfN%v7eU0EPs4b|EQrcq(!Dmdv*`weAfHyam#qtsvpSlL56EOjj z)h5$(cpU6`&qQd>BiZKjOb2`hMdespW7%v<8UqP zizFe+7E1;=OSrJQGqHn?(zv*A5kME`KVee-`W&x^8&Wa>pRXyrNQmobktbnZKW4-DG-~=IkXmCS(USPRO zwwB+=#QvKn2N0sWO?p4EA9DM@<27W86=*;h;4b|OxDrR|^S!5!91sxXA^^*w6iw@_ zL?)H^29M2+P845J_GG=6K1N+tCI?jCMYlPrX&xrxZ>iN1v%WhHqv^n`xGB zO^&(`MeK^W(#-^3J3!vPt;4s;xm@$j{mb`=^@qLr7qR$?^DmZ4Y3s2*_zXEK)+h)TKLMd^Yd?dg{_*u?0IWC$w~M%9 z`Zp*!5vN0ZLl2e@ho-yi25j~h3$)#4b`Y{!$H7woq++eV5XZfB+IM(w(4UMCc;8of3xJinMbKK z;|SdPcenW4O+TNbe|Zf}sd+6BCsmX4^+9t~u`qF!?5{(B zNFE3M(B<@png8W7if94DVi+c*-;eQsJmJ6oN#F(=T5x95!2F+&{m&lax5p9~1}+YG zZ%XtZUHsoW|F)9~1e_E`6fBil>3bY{@T>l^590j;@`G4>HXJz%jtNjPX_502E zf7;%sP$Izg{O=C?pB~A-UBdtFuz$Ne|J`Aas_OryR8i2+e1Lf)kTMG&85QMIQgX6Z zND~=N!dV(_=H4S6))Y@IizQ^LZu&DjTkJk(bdVLRHSYIb_5Y@71IUSB<#>$WqHa|{ zk&=?8{`et!qBLweIP8x~H1vT8&Z)gg$U^>qSr;|>h_Xy|yXZ-PbGFLQJ9B&mH^8ly7)%|L(rX&?22 zTQ7HHMb@*M$MA&5@k$N0%~jy5z^iYAo|lqCzhvXNL{sRtK`80gCghq8H%Opq4x|4zw**&h=4Z&Lz93; zBugQyx`8T3p&jzX0~lzD@81V0|I_-|B!8`};vNJI{_(~9@85VJVE~IGE1d+a$G?2$ zfB6%ai~vgdsmZzIuU`s_vL)+rg0wGX@YhK;8Ij7$siOIs%(HW6P8ZU=ERQOmcCkk1 z^;7V^Uv=_n&~f%12?&>EZ6u?KFJb9(b+!DdQgiE?usaB(Z~y!b^9TWq^EQVBFCbXr z9^vVj9~~D zPUkK0b2(?s4RZvQ-Q_vYyRU1mt2y2~f_-hPP2*k2X0vWsRf@0azqo`Pi#&AX*U6|* zsMy6BMUVN;gg8`+Z)Dy!zQD z+2O{cVMW$LF3J1BJ%;n|)hdLme9SxuvkmTCno8Z`_e7rmei7p5*Dq3(zMo~GCNv(zSTw_7Bz;jbk} z7HZw4V=a1vJWWA%nGe_XtxNsaB;#aGK746T-aXahTFY(eGi`W*Eqw-9G?lXz$p(8g z2h6u#cfyOdPCgC`diA|6Kg1+PQHIaRH%J?#yx!5`zpFEVF<+2X*Ew<5_wfx;F$>W?s$Vo!0X zj+=wUu9-mu%AC54MNNHhs!!lO40yUu48wmNyo<4Ly*oxW8yOknEtqhIRI2ewxsk4$ z4PoKO5qA9ESkH4k-W@Nr{5hBEg!ZMLrM66WG~kS=NaMf-2~U__IL|X&5Gvc%NY{{Y zK0&p5cmSDRD&$+T;ggl51by%9P&RMfl3>-?pIcxDLe@9%9&IVD>9a9}?+jCOJ=;k$ zNVWDl{k*?BBho1ogq9C>ttm%@i9A?Zw{kxuCMz0rFIwmgl$xUj_@|~N3saxezI?{M zA90^we){!jCgZT2s&AAX?t30&dO1#F&tgU=-=3D^>$r`6eiClWM@9jb^~TcCBhkDi zW{U_ut%2M?(CAxx9 z`JN~r^6VHP!R@I|AEip_l(x7tm1<@^?W0*Looi@7mR-*UugN{WUuru8Jp$^nS1!GZ zR5_RvWjP5GCM-1Bs;JZ+W!a>hky0U2LPV`GJ_6yZ!Og)MVn?YoaLV2!E{t?=^fbvJ zoQ?YO6(c0Zyy9Whw^(bIRMpm0gf|jePuv-@x3StI zJ@<%LJ|+dh`akvdtU%kGcHSq@3PkVp#)cMGD~tPtVcZ&sC5a%R56bZHK}1&Lem6PwexJEYQ83JwkCWw0(_Ljfii86-KW3mnW7)Dy z-wXTTnduQ|Mb#Q`N&KBVz8iCc}Ry~SQO`AyF7L4Sh{xS zH)O<@r5eg89g7USh{#bSm5V7-H@?9UR8+-L=X4Ue-F?ob>nVNLV6|K#lJ5so%@2{F zNN?qHl8T%wRzq1bkb!@cg|>s<@J$q;Nl0ly1Ks%2XRjkUVn`l*LQ_-PyWMEZIrMoR zE^(-b*HldvO}|0p!QT-T^{F-|(%q^}UH)kv^j}%PJY2wEP+T=rfHKZ&3d_gM@u;&u zOk5K=O*Uj8LHu?`gUH(5yLn}L8RCdRvZ3TRNiUNlPqG|z%eG&Pp+xl!LqMA}>}oaU z6|}_3jmr3FMEFaDYBrk$H?7umsP=%FnOdYiwm65IJ{D*+tg-PiAA7TS0vNL~Zr5Rc z7zXXAFl;o#Y{dBrP3+>%6q9prH3Q#upSLB5YMiKEIE9~>D96SpFuWzSePSpfq{QDz zo}j5Ul7yj9cdjWBt4yD|K)}o`doh@piK6OOf$b>bZYYLLF!k^i-TWEYEV$uQZ51rX zYAkw&*on}QTs7zT^URM1!m!ftJa~xc(ulcB2g-18i-Siw?;1#s1>=~B900fHyA(XjwAjR$7IXG;~Knv zcw}b#;p-EROejF%slcWc)X?=wQ8Dy6=Qg|)7CTvaNa^44dQ#1+gG~hU&WaXB6xUC{ zYBw69Qu>ojo!23-Ar&&iTap7GWzCg2(tgr$uxEShG7_zD7<=KMMMMUQioLUvv(-`A zEe8qP`puQh862YWwp@!ThI~r6+PghkxU;q@#lOJuqUcw|?k)uMjU9{GKIdR`3yqIX zhZUog8n{hwPOx=HiKN3^Jwd^b($H--)4A|Ae3={R3mTMveY+7?akv-)>w0Ag$-DCH z7*SR@k2cejM&YZQ*uUk_|D3tniie`|wCUg7_z7nt=AmqxnkS>;v{#HPY^rDaZTzLS zz}pHyDZn#>)xxAZ-Kk(A{lZIpV-7Y+Z}KD)9uC4ubVu0la#dvsU;69NJGmQfF@CuU zZ3aBCqUp)^tT=2iatn3(MiDQ`q}cY_o--<==L)n{=sGFlhO0PNdT6!z!T5?CD*7I? zOUmRZDd>w1S#xgI9DjT(WEkoT{eD$eY(k7&s?8W|9GFG684m4ZmH&2-dC~v@r``F@ zB&M`Rd_o`JYI&%lig@@AcCl2Av7PEi{V~32vLr6l+?u+Fp}C@^q;|vg#NitxX>r>z zcf$AXx*r_&W#mhaCvWDChRCxi!i|{$k_P?yBBHa`p9tQ&I(+!KrQ{A?o7m*rtgB&A zAdRxq4#d!Te~=VZoY(%6=*n~=5Ha<~?Rw@fB^U#!i*DId0-b5zzZFPHfJd0FS#H1K zETk15v^#j%;a}|o6qWboiEF`*Xy{WP`a=4oaw`lTKngfqxA{R)*bS{;ZRP;iS5hOy zy!|ah_C3SrG~XPO?SbBV%0u}YaW5iX7&5-z9C-!$0;yb)Jm;n&l-%4i+=aJrv@}TL zfxBMx$6+y8PX4o-mBly6aP$xQR`)-o6Al=Qc-lS-7f87}n3*x%?RA~@+449w632tg3X%2}QR%D5uCqUy%xhTCR=Z zgG9bADOmY*;Zr1t(EqN3;e!b?SUpUk>B~>HaJoIC2bx@@?*w+tS!mRTYo2c`6Dlsx z@g}+RheoDJ^u%JdMhk;_s6c(rIv*c}8-+yad0N_lbw2(JU5@}+C9H+(*)fz{^R-H? zSqO4##J+ZJyb}JS2Z!>Ky?e2)HA_b%@)In{YNv8I^%B|Eex zXXa?la5X_)Jx0M)IVztjTlc#$#$At4fB4MN0WEWjDmOB$M5dC@w#_;4r>&)}iARTVsbWoUss|@-Oj_CC$?5xrh%GxcdZDl_ownHS&`OhuVU#f? z^9u7&W9;9nF{CzdeJD7*toI-10Yb4jwO2V2;snLQJgO^$(X&uvCbuW#7ABt7NZFx5Vu`T0ntmASd_Po2=%=`hJck| z7&eQcX%#Ug_IGG>km*ud#%Q48+Z?P3?~?|-)F19Da>r>Tl7KW_$IO!R>z3L`_X>th z)6>Dq-*a>4(=poYrdvsjDCKc5YFBzJ*T2|UtJy6YeAJnMA1U;3?wsX1LFSP#S?Iv8 zKyQSrz4h}n2r}p^nwO*~7I!rV*bi5!!TiB4g48kt;j1{ogmc8>!fG}Xzv~=gbTct3 z2OBGt^kY}$I2ZnrXiY>i_nPWw6+!`5i(++63qM4T@^jV$a zgw+)k*+q}B*>m^m&+k_<7*@Bh9F1!()<#qmx8VSn%U3Yrf|4h0te&{3$OZ%~P-Q_<^g*red&Ss` zwz@p<@C_o;I&G{0Ub*EDAV!|rGfo>Fl9^)#F*yRtdt?Wb)^>d1^!0_uy;g*dsPERR z*}P3G@5+0S`v7h5boBc1>v&_v^wI;5)|Lx?94xlGo}Ub#y{?^Wt4)>4@)o)3OQd$f z4?BKPMs2&eMI~}*gg#|T#)*Xcg@{^!{nb&$7xucJ(*8|5WJBgAg+n^BcdKMDd>Z%r zYqDx8mn$wVVs1-h=Yqb(Vj=;Yh2t*-GI{yv;SnOw3y@Lj+^(EcnUv<=z;Opnn}5Kr zo=iE~AZgL z7^0A=5BKr2O5hcAuGQ-MEsI(FAc2#5cRK8USj#^6MZd6ueuuM)2l3sRYOZLFb^l5{ z3v|g3BV54Y_;`bWq{Gk0!aao}(+_X1$xfSn7EVhQ`wg-n&+k?X(yE@OyU7^)0q)sX~EyMHjV z#f|Uw$JQE0`>Nq!EzfiXpou*u^OvF#5Yx#4>TeqYb6{OM!@jpV2%5_?Vcgz6gVUjI|*GuN)oI)d}yKYBA0V2@!7JGsH)ZT7dYhDbdH^H;sRv`T7yKp>g;G8}S zAfwt;^n(z_$@0@O;498y`QV_gtrvQANYiaQxo3djX>vDjJF>%C zEZ#e`f0F?bG+V_PWK$^jhurjSZuq6nUdSjFmiIOdF<(jdNi-NgW7|ubQwpYPU*kF! z`y~}i3aHqQ{cyj-6gZTvmr&IxqC69vg%e8GUtH#_HPo>ucrBD`I2wK~y5B8mEhGev zUR4%?F?*b2e4pZtHx6mcq-k+Af(>Q*;K$*OPq50}xx~oZbPTiCsbb2?8p?Zbg7@R< zyB6p8s$*_fYc5yZ>0ss56tOxq`FTwore+Lde=JsMh!LeX2z@93tI*SZu~ydWaX0WQ zw<8DxLHnnA7lU8pE>x}DeTtzz$H}c&^Ape}ulI#~c)+!6aP^Y~g>Gm!1-Uthfti?~qc4Vi*dc}m@uCRpZH>2V+ZQTY zvT`5EK^Fd|OPQ`Cjkl(fzmoffwL$h+V~4N;+Q=qZV6b^Tb#usf2eUMyNz~f3`z$By z;pCx7FEzBvG_MLsyvVlzk`60@r+Hk5b<}Lf8vj_&FO}Tu*MgIM*O5NwG5Bxo;j* z0jZNkzPquFrp1{`8$8^rQ%2NyG^h9pc+?<}3Rf=Qm>iH0%$(yksK$6_<=Q47k9+ac z7(15rh-e|OgWKVAkTIQ zlcaZRWEvx*pE5p&8qSzJ3Xo0m7KrZq00lcAnJJNEUl&*}d~263|GAyxwo3g9?lQ()1viLecyg>Yl4Mdl~yZ^`WL-)m0gA z*yUW6)5e|i99prTs2rK~2e5zYroAqlq&9IG>jE9}?q)m(>`W#Gjq1E0Jf7W!V+F%r z53TgUw2#<$d_`Hf-g%8tM9=HfPI|-M0>dx5&E6IJqnS@leknm;B>o&)5JcJ749K-K z)ViyVij`DIsjVGB zgD}CrhIK;OyOBNcld6 zN+OqCa8%!od*Z<=m{L)|rU0Ttw6(Pbs-W6Z9D17{z5;@zDE1^hCx^)ulcjX#%`fgnBo%Qp2DQYB>~ptL;cTpgn%L_u-s?0 zg#3?+`QN+HzuWd616aTgS3DNDu|Bw!X#P?)@*kBs zzyNy^sV`G;zW zHs9gb8Gcb6{&nV_Lz7jAf0r9*!MSJu$ufD+0o!pzgf6m` z7t-v9ryMzSG)eQF!urYT09mILR#XuG?U-a_w9uzNAH z?=boQE`$|Z{6g(**^P8=)C8@Hb=OpZBVp>1R0`t;X}Y- zBH{f?4t64&b2-iLC+N#U;v!0PU*Mds0eZ%1J~<2*M%-k%4eKYEp@s16d^5Q_A-jiM z<1O!YCShwhZLjOI8}Bo+)}B%+Pt6}R9$W?~Kd)@3!nf+%8eX2#36*Y$@bh!KyUyq= zxqD#;2e3>VK`il)x$7e2f}0!GeqRAx_`T{*zZNgZB068$Y{B!~5dMSQW#`2^(6tf>1S%@X z9WY5yc-NgY3eb;DD9Y$W5!#E1iIsMuxYA#pKcB8q9$t;rX~fLQ0Tr|~k>Q!YI;dHV zwpd{gfu{|Q!!r@twVuuI89cCiuMEyI}#@aqS02l=Dery1|xj{lU`vp&+og`wD697?7S?#<40qgv+xSp zDM$e$n>4Cl&YROCWn`RBZ$K8oxBxTGge*uElkYtHS<6tW@aSgSc=xGx1&V>VE`ZeZHPpQFQqwd3?@9Byg$S9!%DXa_!8={>FKkQs{#?z zNeN5(vZGR{4iY`7eMtesV?|qeSMJ`(w^w%>vP{VSDFH@s<(l68Dwd8AdRrQzY47@e z?V$bO{!090{*$UoT(W*B37}m-YIakUgsx|(DzLg6eR0d^FI>0)zE#Hkhk)DyxQB-I z%%Mj2MwC`f(bxCwN2R519~tbNA@o~)2!C4fQiKEH$oCXP;S_H8yxIY->Fs&P&3P4A z616Iml?(2Jm&f#z;^N{XST>y@_zCr0c$20@&DW_^h1y+hBp*s_wnuC#FHhVT8(c@X z?H7|!a&mJ=bC7P_Z5kPz&bAl~N7jxOoew26p$XW{=4C;kXftlxowB0O&7L+~58!}+ zlOpMb<4(=M!tCts#q`fyA@!5VwSH5OC6cYcVvRbb zt}l=Y6pjKf12Jh|%4gQa6c93`xML?PWPPJewm%@K5sR;ASES5rDN-V*dT5*+tc zg_$?zCC44qOX=?t0VZWwBu)=$XPX}Fkq2)B0RluTrybT7rs zQae>wjWc)`_{?r709$v;v{Jq0dbco!$N9Z4Bug5RzW&VW1R`EK7~9cmR=tj?HEUXd z1lUm|;B;NY!TKX1hl6UYmc8jxJoET|Q9pINX-bodZL)5N%R8W@*Uf>v+WtT)+4gjk z-L+ud?$fmKZ1fEa`@{*Kjx6=$gobne(FnZErvB_X4mb(Swe>YG^f2kR)pOGT!8z$_ z6zq5Erbu|taR&dw&wAia@gy-{8e({OKix$3r{gez?|>x>KrrKgn*aEDq0Uh(OiS6x zD8aNrC0FM7Y}5@!n(O$IY?j|*asGKP-^H<4i@Xef1l>*hG(SA}wykqy8#o7!84>!E zLo>Z_9@=-~QYpyDY?{i#M zCrS+lEoQQ_NE#{JzidqwYOanG`zlIbZ`ij+$nfHmNja2uUq5;QZH=F5AqzLH&6*rM zNq1v{FMLl2xyh3V`0ta7RtIV~*{CmhR>`yc2Hg*sw@dgR)_W{^kfo`W7cmp6bZ(<= z!Qk`JvcZ#+>n2GzzZlZyZ{Wn^QWR=KmdO=~4Xi|WvnlgMWOfyNVXDgen+vku+1e%U zMLtTGYxx)Y^KIhHo0=AuY>UbM*j-)a4*Z>AvIjoLb0-%r9YrHDgSVE-=Jl3RHE}GC zRvZ^og?crZYG|#h&0|ym8M2jxf(Z0AoJdSPzHA7 zs?JW=Rj8Y8qs7JGAPubJ`gN#Q`QrUS=<<(1_fZpUI#81TBtL&!XU+DhB_oE0xa5T)?OAraOWP|`TWEUOXXtDCC^sVvl0XM zvZ@>Y2U9ET$~h-B4oG6@i0tQEjl=p$jpc&chX}Jtz)m9QKm7$wC}8-#gAq`jfKgZ$ zCFmb8TSPv@Z*bWgo^~;ic3D}y@l(cs!w-$t8HmBfRni^+Z$EcYXfZe0IAQ`z62i)s z9dZGFjR8tujXNlYjl`@D^MNrR!jMJpmZ ztUcKcI~J7x4H6z3&1=^&RLhc9RfH#ZOOc0qckNUN&)Q{0tnh`mCDT| zFjm@uH!!Gpe)SL&6CDYGr+$P7$8)&rHp9aABIXH-AvKrV*qsBH2|#M(oZMcqCZOM6R601 z?=Ya3-k1#vV9 zccK@w?4KNtNtcI33C)k83u~?myL)@@{vYe6chwRq)7>i zih_XBktQIb(t8OcA_59h1Zh$tDj+rV4pHef(mN4I=nz5+Nl0?Oth4vO*VS3NZqCJC zUgddwj5Bk7bBuSq;~np4@EUVGT0Zf3$)TpQHZxN=87Uj!Cz}c-e(FOcb~lT%Ftl@Y zYKgO^Rt>nOpSuZj8x@vG3qC4{-3I6H@up61S5)k}$6YW8!XaRdapQ!{Gkv#J%Y$km z2loRTJzckYotJt#)H}mIGlw`{94;Dd?87J}ZxPW$*w#!%8*5)#qAd-&yBbqSyv6%i z4ZDd+Za~vLH9;kCr-40`e>>P?B2v-+5}(`J+;AwoUAppc zbHM>wEDWXG8^&2>gH#E)Rr&LyWvCq1M|%I^h(lP~s$9c7P9^8I8HO@)atcb?mCL{J z7h%a6F-M((dQZfMc!qHDzcE6U;J-sa~ zfy&M0z@XQxE5X*b>${W7HJHh%hC9zC%8e(4hOBKPA@rZPafdHC1)N5fv`kH5sxvT68cUviA)TJdm ztdr40mRwa2?-yw3kAJD7%AIW8v!~zxh0lL`0sLx_cJD0cqMW(L8=~rcyb>Re2o)=+ z1pyZ~HS%<|j+7=yR|c+GQ@tH@KIh&7LJ-=cS2Oqr2mjoi zjp68fuIe<>4sZV2kYtx40BSA0A2vp?xJ;%OxfN5vFGeKaSU?Kqz zdaDp8hmqw*#U^Zv7K@HGeECWY?8rXWjb0mCkX@-`Jg9a~^Iy+CyXz8gLdC&&XTb!0 zTTNu)c^g!!c5B`yDr-ndB0NshIvRii-ufZ24fDlUb~jc}jBT83&{16NKYWbR<&1zx zRPV@{Brqx$EEn9Z*P_2oF*ilWxBM8m$wLzb6FA&iY4li^5nDuC`RhI;bvdiy=eHm= z|M=$PEsDT_t;BH_V|h4IWmaq|kPXrvZ@!I42+c)N++SJc536h}1oaY#U|&O`W1r8* z4EwQ3YAI2^={<(avolRPtI2Av)5$miLVrS_i*r6<{pIX7e$P1!#oxq zcT(;G`Nc|1@?9vxb24~p!dv8XpOmr7wh2@#r^?|NCp2$r^37ECInDTE%N51a;uCU3 z=%YFm?VayDQQ(Vk&9AM}E3g8{WbsP7>F))*<{BJ5XXBLI90B&MYFpk&4~4Y7;HwIj zI+##HcO8)#GBZ8Ar_GTE9a{B=j@kCqXN}wNhf}p1lFX}eoDSF+K&3?+9(cV`#P2u% zZR@D`?pSZx$}4JVu%IH)KG^5SZ6P7st;Z#jTe)82LZP6DopJ5_Wm!?6HG8AnDb{OY zf{q9G*gseG_oZ(_QcrG=+oLya=RsrpMMb_wWQtB*<>YlJgnz4!OZ5c~$bA|@ zVl|Ag?eLabn%CIlWOI1afS@6I{y_e@WFKt^pvRVv(RW%piWhcY=j1G|{b-1K9B#c8 zF}#oK)G?Kvz?Bkw(bby=1CHh5*|Y(V*5|T$|B%5GLyv<**0lsne_}Jhd>9BWNzJxd2G_PTK-7ctRid+g?sewQ)aMB&t!+WQThSy(%PEGmfEckBOK< z#rFF*+UdAEuD`o56|%&5n?DNRwq0L@NW8bB;{dD3;;Cd{)M_KCb0o z5R)n^6VRo8mS27W&ljuuzHO1_1Mt%9=a$?gyCr3PsNt$j%S)RmW#Q}xE>BKe`Y>fX z5g|8vX_w|~6ikD{?=dUank!8IsCx+{G1|&vUQ#NrQM%76ndkk;vn{j!7_Aw1O1n}0 zv1iYoB`d$?2M@=AT)p&0D@#4{RFZi3Q6?Dq<`19>lkzKUh~o6d`r5w|j2_ljz{3C) zF_B`Z{)8|br9$zMXS@hkJLuq{&numo9&^Y44r7SN@`u9^H}@fiLbu7Bvk#WW6|z)- zmg3tbV=}|VV%Bat5Lrom+SEH&Cyp>mJ+N0+6%AjJr_)1Urh1P`IaEx@rh5}6Bjol6 zP`A3`W$FoGH1q16?;F?e=xSwDZGR7!lr_!xL@Xbo(CZGvww=tzh$Vv>|z? z!?NIDmjTNuqQ=on1-n#bhji9;6+vW$LVT~}viyZCqfxdviga~m z%4_Y%?vtL*NGZbL-6;xI{l#{=N`&|a^y=Mq!IcSg2JxcEFBAHy;y>1ApV~!zM(kl3 zJRb;%$Y5r5hHIjPe;-0`$88R6P&XH1Pc_4R^4V)n|^Ets6&KTS(L4O&rR{ z4vq&(M_?}STr}=ET<5o?A!h>1pJGjJ7Tw$Q1cfF(mf!BM!X+qp~>_$9~VOZtc{o;tuuX#|tNd zV(hMII{y3`IFggymnn0%!kv*Pbo5sNlj?_7z~kTCC&w2kqXoDC@)VPP8F!sCpBhD5 zaEEX~;{RGM?c>ZbPRk6(V@nD|G zRCp*q_iT4+!YcV|5LfV+6I$AT*9M|B7o{{EJ{2a0;sofT-3^wuK1rRLx2Z24cf9I{ zZ}-6@DwK>gfUR^w$rrJJBTv4#l*bBiB=2f1ZT8Zm)@X-UK8{Y6#7rg_dzoPAD|6^( zqo`^T-e~FR^1Wq(ZY_d0^uC+OebX`jngau!L7Y05{yaReT!g@rv|>(dT0j?x8@l(6q|PtTxU8M2d7 z*-+zG3+cBEpKaz>suVmM1B-830g1)k#^l!}WMP89p5mab4RE8g@R`bRS+GTLX3^KE z+0B4p;<1(q9dVyMb~CH?<4RNwc$mleO#P3*+uxB6#CvYuZPMVgGmV_0WfOQD=t&rX zzpS_72g{oW_TP#;HIhj)Rn(??+0mDmg(Wjvo=F#p-UNRrf6+(mQcrSHHp6lqaU|0> z3rq%U>}7s$dI62PHPskfU!vRKR6def`0%qK*m2<*POa2GI=olzZ+&TMlXR4D)!W zQ?*S~AdAD#a>XC;+2!V!cMfVL%PJ>35$GoBzeRFsge#>@0fU5)JwdUSO%QpPuLm&8 zT@{lO!zFjxxCdR#{$+a;%*RV4+LM<2UkH zN>*e|=N>nR-NC;%vnSB2;J-e_8)(h6RWKd*VX02~8ZD^y-Q0WpF9F~A`oosbEiA@p zYBjdGxjDINyWEuFQ|QWCe7rcL7&drKOn4H8N~bg(H+7hN6lGE87wnF3HH^hqu}VMg zWYp?6tDm&KR z(JjM&y{D@iBY})&DG`9X-=-e6{`z5&AK~BEi@|ey?<00t>ew zw-vQ00p)cgyFQR5dEiu25nD-#7P5}NKf?u&PbjNIjAx3kwC5JI$wE~8jVq@m6M}s> zBgR5_oeslyf?|_jib+t>9VJ>e>{Gp(2#EC;;UVjR@D2D@I8I5sQ+)9q2JBORi9`lC z4SFku#%e36BwO+e3j}Nu*JhKQZ9~`V2yihz-$ttuf2T9=nRSU3BWg9l^d0;bwoiv@ zRlcl~Kv%e{kyBht+um6l!_oy!;X78LAzUlv_OcL>2QUWra{u?vytAr6Qk;-K;o8Dq zydZxhSAq#%zQd_-dDAo)S*7BGGB%^z?-nl7@g~xy<;{K_rR|56VC(kxK~v|EM;o-5 z4~7Ne#)YSA$%Zr;#LsV`eM@ns-knnFAq4nbGAou)DPe(T)KUCj#W`Br`(sl)ruR>U zTPmG3PoRqI6Jl0{CYD5sSwI)oEq82l#HY4=GRyH)UIZ^=TaG8VMW70*M4#U~^%}@1(aS8jGPVb-4UX{pjM%kps}wEM1{59 zA8S4mHvBDu#I^F?K=YO4 z#+-!@Vl|kjSm=$x9{Lt$se#{noO@bwH6bA`)Hz@d=Mix5KqGpbqmv0H@1S`@h%?wb z7~oR;c7++~{TKDvLYU-y>DbOXc`u0}GVb4z|5V-%&;Wwd*w^v`LJt8{mZ%ls&tgVs%6Q>^HNHH0c> zzq)CEdjh(0C2SP59U5xdXlcAlDem1Gdo?ghRI^tG7q-XUqn71O7jysb87}q6i%4KF zsjPN|pnZ;U`l{73%;Yz$CiC!~5GOm?ciQ%$?cvn%TVgcNWcpbKt8yGtQ1 z&TawL+(~;mcWxZl4aa8h)IJ$!G7Ci434UMv$(ByH2X4?m&v(R0yRp74Rwm-~#*VR&d!h)p(fb$> zF>Vbv?r1SA%vZsYNNJL!>7^j(RZR`c%7zX;eFFpBj9HS(QS5H>A*q>Ro3KeGjBSE> zubmHk&1YiNGmdWROuN53f}|$^agTM6_w@c2b$Um>vhqBw1w4-M(6|k?VM~nxe)<%F zQEr^TP?*}82}2hPvDwy>^OM6uTRZAkd}-ron!gz%j=^h}0)Hl@#mS4yVWE|U>;Cw3 zmA!X#^c0d>fjD7#Cr-6Fg4fjHvgacGEhER&vofO^9p6Ew>?T0TC7Wv`uCS->Kc+GC zWu57x$X$JL?-Fx&ylQ_If`M)3VGJ?8=o=}JaYuz1TTVxtqoTa)H~3vi6X;j&7-dBX z?BPf4GxTW`{i<8p>tg)U8l--$g-AMe*9LczA69p&&l28RV2^Um$yRdx75_s zz`JJBWJ%C+GoHmTrqQJNdeRW72|<}*NYWQ28MW*lxHxr4fIe}^=AmC~gNg!QjML$p zKX4^TYS483J;U)f!Cd--f-oYMQEgZDhTeRrzak&m7dYFw$^->B>`Gvn{$!)y#a^$+JS^JVV?OQg+TvD&Ipn(0E>qt%5C{%_ety>lJR#hy?1MMV zYBromU^E~^1x76jA6_f+13MevGW;scCU7yDsEavL`UEuJP}=rJtp38xJ3UOKDPreS zvEq6V#mIbPDv>&y99Qoq2)%;Fh8#FwC26GD$`w9Wm17EDV{9wJ|Kh2iTi@w&CZO^b z3FEm@`RJ{g*f6qTWe95Q=9^M-<|%V167&RYb1dS9m6OPKA!my zi^-@KH!r_ffTlOgFQssVw_h4qD);ccHsCoqF3Y8~c5mEkEK00F0*(omQZabf=4d~g zeYfy+@Jf;x1WV>I`wLo6>z<#VUoHt9PiWXTmx^#bvZrk?eC{YD_e}?KiU223<4E}H z^_+4DM*O-X0E0tKg_)WEIEm$(?y;CD4#Xxh9HVU;`cx3{7~(howsN=5s3{9hC>hA5 zTy$RYzMxZ@L3*X4n14;Zpq$w4jl8c}mkTec8#BV#CjByyU1krgmhQfa@_9jeEs{2Rgj&M2hsDb^aCd1X6aqKKtu;5k zKB`-2?MR8EvJ%JLF)d--Vsfx;*s|0GgJbenwnw}*E}9o#wD2S26P2lPT~2a8o>x~_ ze-v$ftIj7-W!(t+4=0@KWDp;;WUC6Yv!x=Hre6&^qXWVhIi)TArP2VJc%q5^iOwEO zgikSk=%1TFi!Y0?Qc zIcFb_C_^wjv}c;5!sRTGyb9<0uzLx#6%nJ`3fqY`*4O~jYr9^xA@Tzf>4zU;Y&9~V zYWiDxr~n^zwM3^h^Lc-Go8RE_NRU@*#q37KxgjCMtth4SwM1FZXGb2kat#}W@{~V{ zRC;U*;!q8ULpa}xOgwc=N*%&IGCi{Ci;yz7zOcN^ySCumUAuCPP2fbV2!S8)aN${L zaKG?Xbs&{A$}#h}T{Ho1UN6l``+nC)^us#{>cnV0KTTnxpNOaST_C`XimrxE$$C4t zAwfi)!A<*(Bd=S7@0sZDr)@hEwrScqzdq^VkoEP=$(Cc%<1oyG6wpr{W*O1G_ggNL zf2(WuX@2lN{25;8^~mCvkS${O||RGwzERZ2Y&L)qfuJuRf&zI^q92A2b*) zDmn1t;DP)*|JGDu8TY()-+mBL`u`JAel;Nf?>uP#Nge;4j{ct+{eKMI_gEHt^In_|x^A!gtBK~H1C6R9YjvLo0HZQ8|I5JW596_g z&VNAvvg0R|{q$2@DNl=@Z7uE$px|{Q3l(0wk43J4IYoY97eIM6zlx8yMnJ2U?0QB< zMr^QKTU)M_g{)>lHV9v=u-2RM^72H|ps6c$#k+YOFXs1VE993e{KLaCVD`bQYiTi$ zy*7W(r&|$kF6d<*<2sK?r9LjLs6gkpl#~$sdq3ZC9W()hWCpqA=h=(PX4v|&&ME&; zah-k%uHWOLN%7g^7mZI$OiTn-X}TXB&SssY6?ixA5NE>NcSa`A^G8{#RvRF1Y$6qM zS`@U?YQ4wft_%H!du1UEI8`hQ42_ulb;2VsBqXE~Hyu9xNSmJ`SufwB+;o`DagD&eh%73a%UK>MhUv_$~$b@wv@^lL{Cu z-85ea8$s2clB~TXJ=^J9hh5Pq!HXF-Vi^rQLey<1CVSvN+lLIZY6!U*`O_ay;hn}O z*lh`v4-hu|`?7Szx}8V+vzxXzcrLy&dW`-H^fhqLv0M4d7$tHVZ2A7O7;IMK1mF=g zN%5@29}QetOZBD0jQHJH=nxpOykR>JhE+{X1Qt2;vR1J|EXB_T7B#q|o5B+gZZ&xO z|9ZGmjR3XdGnS{P_SBSoeRV`=+6F>%Ax3*Shf*ljt!P8U!a2h0PrVI8*s2BCR3Ls* zBLIu^8ol5;#X8sYe!$+^+}>X5v?-jKh`@c0QR_3UiMV7 zEFIJ6xl?q&Fymw4iP?tzpcMT+~v#4501RgzDgGmgVjBCaF`z`S#cK8N#hHJcs=0*oyrUB z()pKrsy*|GKh?O5CP^0ZOP$iwF`5#KJ*lbPpo>&Vo;9URlwavEFuE1V*CWDy`Eu|p z74qw-rdOpB6-E{T;;gO~;_(%8U|2ZmFXU~(WY?MI?Znxyd$U?gM!U5e{7zUrf1$jzI9&ngVq9&Ftd#@V)xq zS`98f8<>POWhmEk;cFEVi?h8JFA!Fb>@j&O>fb*wkY%rw^JS0wmH{CggwBt>&6%_F z6G$~Kg%1XX^CsmEzT-YKa^;HhE}%)=#D=O9ejg%Z;8peT+xL&UT2&}MV;^jsQDcN9 z^_J~ji{a6Y&NwF(w~5f+x!IrXN(%Q%!2I^~#yhBb)#WzoBx%xz79l74?MwSl3y!&z zfX8^ne)|D!qSyDGh=F(u&?&1y;k8|RIb(i3X3q-&ADhq{Y=7=C6^e#9L3h{GhMar( zn}V>+dE2N9ZntrJhb;IzbWi-`LGKzwc}{Lhu6=@Fk8r8%xS09k@vc9vv2CC5XyNzf z9^e8JHN6GwC^7DkTu~typ-|^E1rM***-1d4>P%aYIzKlFy7mxln(FeMsC@l2v(xM^ zZ4ms#-Wl} z?-h&^{faS;rrpv;R~2@CZXp>1*;l)@uyqQLyZwvURe@HeoV|J(D6N|8Ha>DlzCxKH z)QV7adeUWEw-nf_0xf^^Ddkj-=uqv};BM+{CI4@DZrw8l?A)l|`-rbjBGG&I+S-QU z#p2Zohi~solCk&N-NNoVTJ?cMj_PRzL2yloGyL^Y;Tr{y3Lh;L`vA?fHF5eJEfSLo zQhQi@{OmD(V;&B@rqD^p>BiSm);BbJ>cBWlE7yF2IP^oBX+dqFKRN7U&e!|u0!L!| z8m2k)JcC*U5NEDlz1lw2MB(#Ey5g#PBrFj*3k&GxeQQazuZn4y3Z2_XIqev{fk_gqjnlkukiA(GDo1KA3Nmja~pGnM%otuCflL;jScnC3*1aC zZkj_(4}X9UOW*c}y;NOq=Q}_KH#pqXsZQKHYOsBWe%y`y;b>_#aWNOQWfCy%Doxou z6c@RK_Orp?laObO%R_00`XlA1*G@=34)9FivGl9bmCCBAQFQxiN7;OO^TQc7cUgQ7 zjNYfMcy5vy87n%0C9jfXO~pT5_L1h0%cxrS-i*)>{N(M4WYfJp{~?I;)lBc)*@}Se zk*vR1M&dV86iHn>&2ngd(CvIctTHKm;)=QG$e;JJti^UNrvrz2OnICGL5~8@g2X)nz$XZ!DZQRmi z4gQaT#3GwBSdk9d3lrFOo3`|5e`L?E^L&95YJf&2Rd#pBeEqP$w8HtI(Hj9};ad@> z2Aqej^!|+Ye`tpccx>h}Srs5Ef@Av#@x>MGOm;zE6%-UGmG%OC#|Z{8ieC8T@+J3K zO(GsqRbO0SR=K0hbE81%?N|bxh@6Qt0*S``$rKlT!Y&+d$)H#Y8HHEQ=8z0>_-^=d zQnRy=1uWMlUDxlpj@Tv~`!m}5Qx3vyARGur&lwh&nJg&JEtiCaGI(?0bx@Rp=CyZ( z07krzQt;iJF)I+EdCWbo^?0`MLtpdlYZVXWcaw=5XAapVD)|P)MRG`h&}|@~1J<^9 z;-SnGP=Fpf$veI-1*HtnxXvbJ@o-<~RC4h+|#T*(Y(XRqN`6)3-U*z>K;IN}CJqeM7IWn+COQfmEnmd5f7VdSpJa z?)~$%2r@@$4G5<1Zf<`OKqhc$A+tIp>OS`dkT8goIx1ujJZ)P7r)p3jQJlTKu2~{_ zEqYg39PIv?)6-EtN=BjM8+61B3(l*+6z)np3>Thx(*px@Oet~KFT{qgnv_^v*kcIr zVLqv_s~%PFt+e?=s7Q&mty_WGIjy2!=UNY8#x0(}=ta64xz5P!cvi}Kfyy<7;Jm@=%6vb4ATkub=7aY7kq><%zsIhIH>v ziYw^TQIwgC-E+Q9?X>UL!l{Po21vDRq|Q{xqSrzOPYc*Tt+#9LPlV+d-L$ha%K5Uc z;ZIe>iVM_RmOOSm^6|;Dai7`&UWb*PMaK?&K<3@o%G-(hwE>_Vy>Q~{Op+Z^3^eiF zKq^j96GK(K*BEh=dAZ_yKAK87 zTHXlgPOnBC{JMFvG~EWHOnMb%M*22glUS4HJ3F$ophKAKU%R$|qD$cK>5M${O~a>< z%d(#u6J8@Mp@wCA6Q{wU9Yj-#-+G%!v+rEm1R>zO(H%-> z7L;30H#mKA z$)QAT*;l?Z91EEw>>J0rJb_i_Vec=35{A; zF%t0o`HgL+Y-slLfkV_Le744I&RgTO63b(}IAWD##di62C9NCTsJ4*O`$2xCRPPM* zkF*hTm5 z^G&a%S5ev*;RQ=E8Jc41(R=qLKNGt3<`q}K&-ce3JS#XzCa!pAs_Kinmh3gUWV z`F;~`onhU%on~kIz4Q_&z@9ucLDXyZdRKbmKk%KQd@VD|y3|7%U*h+kdKvG{UgMUqJ6^Vsn?AVOt@L-}AirC#Ar zpqLcHHcP^Nb@CpRi5ApfWtGzHK!0F#z*Zx;#SHbE`=1FyU(%W+=|};tsk!GZ=XflQ zK{VOeSv+K!%d#(C5+Q5hPs}(GFAB!YNU$hIw>LWByMT@nCy@tTDI)y2or~Z<*Y-B| zDxcrH;DX@V)&OK(4Mz?nj<4!iy<04Q($mbg;Ev$TJJ?_D<&SXZuMvFm7Lp?==7dw# zDCmD1BnqCfp;EtPfOz{QwKPK4%E1>`WM!L=>L{H|$k4bzc6YmeVEEm&=w(Bd^T@ro zL;3t%5LrL6B#qj4I_*>jvY_5LwnfC|j3UKcZa9_fF73DZ#afOQthyd%hX1H+Q2a#W z+l$2y_XsxBR!>OSqN=t9LK}!bNi|r4vd51}LIt+owe=et_r~0%es^Bc^8R(DTrL%) zQ*Lv+W5Xqq_>*!)ZewfpwMJXMpliKx6a0 zJudf*(ZjZtR(-3Fd2X(t+6b%XyX>>L5OFQ}qdPAnTV>lZ#X4DzSH<5a$FS#~1KMe# zTC2OnX=2oR0=NaqDP(%HpXE{_Hy#ad7fl}j5+dgId~VdfAg42N?_YR@fpw)0exS>K zBM+*o@CtHGq8LTLB7ig^!fMrhvHF3j=}abJ(yVXsyuuel{yi5+se3JKgCqIE#$XKr zOs3`L`Lj_=i;I!Y{u`!ohi_Lo)+0+Z-G2iS{<&xG{x9s8qa!_8*d2;gIS(*Lb}sxu z)CZ#e`%L2R7H8l)M+$(}FeGJS2b;Zqi*8H3b5w4h;5urQWOPUnL&oXpw15O5B08I9japTJ%mXrNM}}m^4tAKNYds z$rZ;HY-qT*t>rRod-;Td^$mO>bL`${sAlqhE!3&G$Q2vFn=6)QUZe6Q+9*;dPUl$< z5Pug8F* z8{VU^14FpeO_>j>3dC6-$_@b!nevai?f2D%bFUC!sLH&ZNj#zNVY%5iTOc|X^(q2| zcqN!45v|GH@|`7&0dRXii7|L=_CdYB%^w#+?Saim&c}~;1w6*0oKP>NMR^bPUia$8 z#+i{tD%Pp@0-}t8zU{u1T{P#n(qE^qzDh_Gf|tdcc+}eupsYw$BO@cFx8F~bok6wY z+7ryv7|5nA=_LyY$IA7(gqAJKI;mS(_+oL>>O8aMjrVFowjE|BHC2nQZBUG&?U!st#P~!Gl4z z+%yfVqEG?9&*KI}Kj0{EV{7gb0;6+v;bnW`{ZF?H;@uHOIh0s}30dC0#}H*h%<(+i`U_m2x zAHqEU_*762j}7g+hrMSqRQ3ADEnC$?Jk>R_hB`nPIK13BFk^(WI0|@Vh)Z(%&S@Xc zgKr>Er>`>wd3WQ40YdTEU7Vzb71(yj8rme*riyn)>c;ELG&q>#xylDpOGB66+j%gn(IIB{~_R_Wswv+6X48@ zfvR4$Arx1=`0TV!<|4af{gqsd>uj`oeSB5dZG)@rr(4}Gyu7zP_e>C$os5?tR zqQ3Q)YfH`=WLy|K&Q{QVsP=cPu5$oWo^$EqU#APV_C!QP5YxgXUV-`Ji?dzHLj;zp zkGG6+3VVJO*8kP|UdG<hQ9k`n1))p`M@xtl^j)DS9w zX8IPIoBWLFn8B?JEBwU%KApLykK&f~MSIja z{qLqJRhh+Y4;+Tj=0n3jT+eV9;{k0VWmdjH#tVcBhGg!06Uo-m^5!Hz)8Wf?oE*5x z%5nTStj$0Nv`1jn6djACz09H>#@9us5_llA*3fAHhr$9#6Im_Q{9BJ7%wcYS981Gi8ZuiGA#E~s8?pMIc!weNbptdVdBVxtCm zp1#k|80ws$#u}wezhN83Cy+#)rLZlDr7DuYH$*AvvSh43P4Vx2ee-;dQpRqTM1=dj zDWYh&Tc;|DIm**Harj2-wvicpLA9a6$DCffyGrm^dLNRHYTg*Ncb{%X1xUN8+UPl& zVx&V=Rd&#bU*ipeU-9Wn-6Horb*xs|!d#RlM8 zm6`Ohaq7>?InpBF?t%NDHj?JMJ>n(>cj@5jYrr92W$Is>UTffrz%n+`uK_5(0;O5* zt|Q1ly(fPTLM9FM<kW50j z*!!;^g@}LWVA5CYHDJuh*_s^2U4&85o9+jkl+dlx&!JPHO)C}Hlh_(E8!;P8dqi~E zy|2!H63Ga{G%2I2<&KM+>9eSELU#-07d{`IZWBRD2t1OEBD9k%EcWN$eSbx&elv<; zRxoOXvK>B6SWB{#wfIEm{0Xp1xEiMkV#2OdUKs9+L4{eN$tyJ1yVa9y0`he}h+S9d zGe)<3&KiW9l=&Ily^|hdwm&_uKL*tUNRDGVP}*HYP4=ep`*P^qvUE6U?7<=2&WznL z!T9#ib;sSd;%gA}jx(x1><0|%x`rw#8}Ofh@&@g(OZD}8GqDzpDBvWz(DSm&0dwv% z6`b@|tPO|J<1}Wb_m3*9(ZGYVvvukPpfK07pEyn!@6Vba?}U=wcN@mwd`L1)hg)ca zbP+PMfk780iObbpMOxA)IU)5@Ki;XCCz$!)k9w%4pjKb6P+BBDm(*|svh#Hy`$3l! z-lNE?YjoP(^G6_?A*DT-Z?K5e`p|!Kzh9}S*Y@Eu&2jlT&;a&U0Doa#B7;-6vKiUS^;iMmOZwA_}#YW4czmH z-_aEhr-0oyvoG^RR3FCfArGxERuQ{uIgg0+=|H-Y%lDVS&2Ya6-;iAmb4V3VTSDY- zV&2OrI@J=Fh_4S>g=g!LqIQRBCYX)LpFv!t)kwZ}A1t-tJY@qlZwcB>aZ;(N=Glv_ zOJiFPw>py~0VnUN$IP*rqJMf8wFt!eEq@vxf%E40;Qr_|a^87xAj<#ZDZNT2-Vft4 zgLx|Ct-s&<7t?+Z6ddLAVhtC`U1_jYlam#nj=LGW;QM)gtS_14h0=x@ZcW2U;c#q& z7_jV=$niE~4DfQG_~ct1cXwP}Cw=XBItCcHP#nUgBR~02AkNY4W-ywbjMfwkdc}lw zFS65|HRFLhnK!5WMIHe`&+DAdDyvxmWa6bH*LQNzkW6@Vsx84Hk zMt0pYYLMTxPYbKb9c{7HLrcbvLt47z%m3QFx?HHHH}6RE&S+6}{;d?n-p^IoEec|p zgW>T)TdzgeJKOl2B_*uT*#zDItB^GPYoC|zpq~1!j{6ehmYi=7 z$ivOctY@qXV(zItN4w!EY(%+&m8#8tP_@d{lUIxrbNn`)>~(ElKo~ob)4Ls>A|W7%b`{iL%wf%<SP+ge&I9M;LObk#LONw(oDTT&~ z85wRBSax!|y4|}_vF!nY39}+8_T-wF21h)phA%cskj;WZm6!`VS2c}sCLjuR4vQGw zCDTdH<_I0lSUkp^Zswd5H&>XwMfr8PbfCG>ht)SF-C_di@aflCUK1rAkHJ9Mr9d~C8967azF&v$_-c+?WS_dFsp;N7Fr-emWdQmB`ZMSUJ_S@ZQnu|lYbXE) z*)Nsax5|9PyX~Wtt<7>=dcZUx`Oo!QRNyEW5wZcLK+`a#n)0!@Df1$8B)K8DtfU^vchaUM@-;qtPuHT1z347kn&yq54mDBvUQssCJ4;WVJb4wMXYfG@zR+tc z>VfGP!&41yY;3C4^E3=RDIr~=isiqdfL$KkBmJ09FNIM@8t4Di$Mscib?}sS z+8=@MIdIQ-X$CKbWB9u)3+qK&csk_WCU2 z(D-12N1K7717Y%AjsF}VDe+{y752O(A30!#p$O{@rxi!2eO#v z9Pe4LmHW+&6@FWFOB++QIr=+a91ZqVom@|7{gYYbe2x6e2h5#;PIZSN!O2wgy2Fcdu``8t@SIi<=BXFpet1K!4pX~pAAvN}kidnau0dE8d~XGP(^ zq6}&efji*5x7z%_iZMl`s<*c{;MZb775IfIqEp1~m$&})pZ+k5=ZmJ@Y4+ki%Ul0# z_OyIH&>6m@sQAoiTkY3Gum89w^FlvT{T8X4i@)=O{-;NHA_n|4we@28^S^%e|8sS! zmO!&mFYNa_dE;;DOidT?0=F%Wv)%lkFZ@?e;(soD9?W3%4&C7Tjf3&`fAjD*7#%y< z)-nCz-@5PsKpRos&kO!W4EX!H58eTHOwiYa@7TX{;U7?7{LAvF^56IHH$DUkUO3Hu z`K)Ak(3yYd!Z*3W?k)R|p}+ow{^vkmIt%Vt2+r2^-fvv*-}fi59vsmCtCntOUYIq;AqB~cWA<{QEd7RnCGRQS-d0%G# z(Q{(-iM@T)>S}>_>UVh%$sI~m%>$}Mm5!)rn5{EGkTQvWLkd)b0CZ%$skD!0eEL=||7fR2!g zuufXci_+r!)Y0pQ8*n0m;*d}fj&o* z$-+-Hgo>b^f&xy3IBJh-3ghn33yxmRPZ@Cmw|0~JJfV#_oN(k1SDn?TSAHlfu$`Ri zOqxj>kL^_a)hz0h4B*1#EXP9-DQ` zdoMTe|tOlHqRi$D@r z+dhrQR;<4#A#|MXkZ>leZKhA3%KzdKWAC^4P`|DB{_P)u6Zb!)rwalzMvIrKhIwWu zCF_(l*&p$$uq(*Nt`u8w_CM-#$OS0mp*>3&Y`yg9P4-4*UwfQi& zi14de_+bk$AYArwhn+3#*bj8^P46z~S$ED$F(;9HR>~B51_sJHQs*MCraL4YDzTOA zDjzWhTZF&Hv+J+6*)&&+IPV{R+`L&SBBDpabv-xH`WJWoZ_F4Z0RZfLqnGN#A)XVd z-wyy@(Fn{MwWKJxf2^s24M7*KsWdnwikCbs?`mca36=bvPk9Fv{OTq}=ixUR=M_1jRC8E-(!`QXoW zlgw_$g@727@Y0s!!}w`nrx34sgFwiulTz;XV6 zKgKf-<3$^>V`TS%yJpdXQ@}Y^eZlao1;y*u9Gt~!Q!0>!njo7eysbB1ov;%vkA{m0 zb_ehD${Zb`kwk1p27$v{Vkk-g*t#y;{jfU9=-KPz9OZcLhAtRN1 z1NW54#D&R8m&Lo|lBB|(+S?D%v?(^Q-xb9fC+d;Fj`w@0m7NeU3rKhk27+wv&F=Me zhg&Wb7EZm)D%AiaxC$qWJm)FwblW{OaO0ad;$f53a&|3KKkeO&$Ag|;9yjfb?Zm_- zTS8p3EJs3Od%wtXF86hycZMFBm&w>R)bf`cVqaA+>7%FXFnMiBB&w^$E)epM20&q= zJRgitiBY+PKm#6HvUm&-?+%OQOYQRQq%$$X+%^3Vpl0ECHerV)APK|#Eb03=sfC$X*EJv1Y(!SF)j(V8BNlo7ix-)*O5R>t?h7>1hlUPn zB&b?#ekgbL-e+d@Y--JG(sE#~vjRK~%Y=x;R9QKQ4dQCr^5R#P)mqktQK-%gbLPc^hYs~MPfP{8Y>s-Xtv(>L zQlkpj&`F_gaPMt-#;bZS$S)iR!cW?hyIXcvFyXYty ztGxQU$nIDcUE1qCY9AaRRsEA&prn{kn^SWq0d8!RoQaftK-Sno;ga*AFR9jQus7i zyJFS!g~Ao~)RbZ-XPag&17)!-&}aWUJ=A(B3a1mtH7rbW6RVBE_a z!jHSE#Xf>A>M~_tlIge9_W)NRLXbmD3O-sa17pb|?AyI!zPi_l!ZMNFpQ1&`bt~fT z73aUr7akk-L>%ZpQ0-qgf$^%UTpWsCyR8SV1h*VqF%`xSMlpfj$;TI?wei>ZT77r4sBbSkjgE-2H!-qg%VA?_cs`4-*wH{CQxKMWGJ1uXdG`Ht<5_cdM)D=T zyrXPezzOaMW;O(?wD8U(-${^L=fg;Um+sv`e+MV+Q&D$$M}!6M|8i@0GWFZlpWp8o=Mc+QA|7!*GH(WM^AR}nKjWpLgc z7I16A1GDV*E0jcE2^if3hBlqAr=G3$C5*JwaNH4C-i5W$yF zO>FJdk2^9AN-o`ht_y@nKHH@`Ks`k?&KP5f9zuSkJ#E2cv2 zE^D^^+xRuO2WegS&qy>8q%zt(Fi%V%OfY6AxQd6ZYR)D9M7TR$J%DgDO; z=>U=y0^^AcDa{Jbc`I#ERjd?I?{xaBXK`dn<^A4TTc;w^8bKKl=S%Ub^TBtgdoOH9 zZufqHJ2yex&gYdUKI335b}D^@O-`&ch=De4keoqZWjNB`78|cuN6?t`@WAcQmQhT3)oNJf2m!NMiZRZXHC%1{) zH~suqkms=-WJ)m*A5T)t;u-~ktoKI^vj;q?PZa~n*kv75#3Go|9^6+*oM24@>Y^IE zDz_W1Pfx$xKjMj*!HOLZRjjPQ1H(CfcKYDcHe%V^xT)H&LNCN5*eP$>$kl?f3Ptb# zVsA>fwxY{%c`|yFrIXRKx$gyQ?}To`wdVtBo$p^V30DeV43<;@lw~ZJZ*0g@#m-nQ zg8e?bm0GUTgGG@`q_;2_*#^9q7r|lm>?vw>G8E!$^lrNJrR9sQj%`76!LK)!o0b?C z`juPC7VzRe?DT*Ybj&|GojPrmO|7PHTpR9{%V7$oRtJ|*_f=ow_((KMnaYMQHy#CgB;#^3N+>rEnK>*n2ZZ4}PZUAOYSd(3*TjYin=bMt!_q!L`@$*XIj9#sfp zaUtr16+_DaXezAX@N0*FoIi>*dJT}QMr{FLEA-6ePwD`{;OT=VJ08`|z0jo#a|`9S zAk;oqg)Wc7;veF^T1Fn()Jm01zWM7$<1$%gSzH%a`cqgouAX*#eP3FaIvXzMIe%D~ zbo`P}0DO1hsrr6rIG{||j=eIIc~{8w;ad*m!@R+)J>xi~1jKSA8S|h?MGl3?yrY0Rfno39J{_t& zZZ~58tKW9lQ+fqmb9Sw!V8s2!sPIF?R9cHsd>Z)JA<`dFEA9c_o^!5ZJsE=Hc=jRU z6cq>ki<2)I)m5y$!+*PT=Qr{jz=KA+l=r&p$S`$p@Tr0RgShzBUAK$6joe0R#sx7VX2>rgQ(6{Zx(8ho6nNx_6FXAJ#UT=zy5NgP1Md zd&3=(?a^*67LGNeOq{c5Q~F{p!Pe?ue!hF_u^8%LB*He44WA;+j^=-MxtN&@0=yEY zbL4)1XVQNm$tSWWG|8pYrwhwC6=#wg5fBI$5h)~3ONb2CqquxhZo}!@W;|3DX)H7b zgB%R7`*9G*6)=bf{A4EwZ}8>T_rm*g)dy05!(~r`poQ=~B_lPb^Om zhsQZ+R)*3QVcPJzG)D z=&G?#siraIqb~QSTqh)aHTZfvq+`7HW6iXMas=2@0xPm6*HrWR+x_~&X|f31CH1xJ z`sU0CiyBsfl5df-YTmff0MbrN6S3K16#Lm{bBwv;&{pDLvBxgXi$6pDJae>jv(Vvy zk?;H%d8`AYgqo-Zqd2C;d&TVl7KkJ4^<<29%pc7nRVv8-w3W#%rI=x}@yLFHtH(*0 zDn<62XRO6uW)57feobOut!N$WlD{pcs!J9(tjWq zXI|+vbmvP7kJlv%a!+X;i;46wDXV6ZmfDUzSELPf*9K#AhJn79LzVt${ z$0BW>H_~Z2o;)Kr8&5^(XN*Ok^u_m@-@X)CezUUQ8hF`lutJZ?S|qiF=xoN7A~m?C zk~f`wUHx=~$3|ifp!RQL>*sknr78N1*_t=TF4yvk_nB+!-ROfu#;VF)1!X$z`dm*U zgu*P@I=e3u=ak1?54oVt0S$!P2d#%=An*1c0R8W3!J-ya;SszC6k$y>YY?<+qomB4 zQeAihp9o|9B_fx+U)|uqXz5=T64+_jM%CJLbjwcOhbZh!Rnx-W%zFg6o{q_`>yj)p zYS&u1GD<;79BD;}c*r&`i zZhsRHfrX-`(qEfLD`Iys+@8I2#lhaKP4=X0LogtVazXQ5z~zU&yB zHO8(mqxETGRWfb{f5d`n2hQjl-B~tkGYWnmC|NBkyqDJwdmLDwB5g1WjM7l*dhHz*q6(z zWR)L%FT>d&Lr^}wMb;cPF?K8NX5Wc+`+c=BzqQ!~dg`R;tXqtisj8}GmkW)t8w=au zJcXz}WGLX+T!hvqY@0Cwd7%oPUiDJz-PhIk9pZ-u+si{Mwhd4FIJS%irx-|!>pYAR z3*ddBpgV?|>DId&T!>ugu2H zHP5sUxV)6t?UvPY9jpDwm>@)@vtBhbKxzm-lMNiqq?Wr(;Ao|0$=C^WyVIA+U+G-C zBH?VGlJf)s=?bnBW{uA3cWS93V!S*WDw(MiDHGMh$5r)10z!nlcus=U%yWUTB?=d9wnf9uNI zGywIhxY$S+f5IuK>iu#VrUUWtUGmw-cknfh7$@-$q3X4(K*g%RT&ALF4}v}LopTaP zZNUUOFg0xBm~&t7n{AVR<%&fIFpel3OoopP@n9xT;e#2(S(>j;lPhw^U_ups3!KN! z4@LzA`?@Zh2%-IY9((vrZhcd6@sXY^me(Rr${Qcg3y<53$Cyo{J*?av=nJDwLIO9i zk_>pl5%~yW7g(Qt<8!bx+&`A`nVH@GW_XNg-FQ?*dFujo+o#{6WhbrJ(&+aLOab2l zW`LTrAa>2u5c=lnIlNpn8oZjXI{Jk3ufc{PW4oVod}O!x`MvtcX-RK0C&dfINIdf? zUg_jfp^aRN!;~)rO;Uzj{qo*mq!c1;B_eVDmDNl&!}` zJf#CJEx{*!`Vf$g7v+}raxWBoVX92l@+mo?m&%x;ZLw=-`uFA-`(*qcGV~|K1P7{; z%G4BRe1Yv+5trproJnVt3Mj~sx2nA=WzLu?$O)f!a%FX`xg&6%*;u91T!Pp z%HD<=hx3<@c0TQ24x0R?&Eo=hhQQCdD0Mck0mI=OVSq2I7x)$F4=;F-bZTAx6qs}RQ%JwWOyA4hIIZG>I>P+$I< zjFmUDSEl-rw{SjJ$>akF_AO(3^ol%$^6A`}u>Q-@EadH%Vq|a4jfur-SJB~(%e)t; zgj&W%r{0xFK0++#!@ixX<+Xd27nQAc`q>IfE_J(TdZ|Td1w)&%Mzr zpm0a^y#BCBLppu)3prmeX(gZS3)9}6>Al^PQjSqLAWA_{dieb4@9fN3-@?y@L8svr zci^fAs>>Qq5$NFyYr!}}Sr>J{s8IYEw(m{7*Eq>e>X#s9fXh15V}aEEZh2!4pZ!Jk zx$A8e)au0Y@Ch|ytl-7;$jx-%Ve3@dS37y2?uLq-#if$XHEmyyIv_0aK;016TaB>iX7>GzOj zb-b8ZLCW;JMg+gsd?H#!bSliY{QM@YJ8{*yWqo-*F?V{CkhV1viF>5L3TM|!=N8dY zdPRS>T~A5Io>LtevUM`ez|j2GXe>QW{Zj0~qs6*@p|r4_KHu_qNlG@2LSB}>a0SBK z?Y%;UDu~Yh-ciw?)M>JD{ARVcPi11gSW5PE{}N|Nf;*U?1d@0FDwS;uQuq;oWIYY# zkPO>P`6bBDXy(v=N%vr~0b+~2z}PR_*Bm9=n{_KrWy;dwn9b^BRD%F644vF_S~Kmmnr-3@^-RL zt1$2|^hE8M*O;ZitMb6(xPT@s>++$e+dkF3UeR#a^G_aHM>>`+c*ztS+=4>4jY}13 zA@BO-0&X9DnnY9ko*Q^jAkv77j2!*4F#!(YEpe7-$a`QU+zF29iKd@wW2EWPO|+7| zh(Iq~HS@6rvH4H?@)rxLg1;D(OB=1x?^HAVYM-~n?sHLH``KBrtt%F>DiF8SX4013MZ!k7Aa3dwJFiy1b5&nl{t<>`-M_l~E1%NCb}e%NGb z)^Ko)8`?~TIpkeezlgLZH8%n**_o4r!i|o;j)JI88VA%svM&c8{F_+1-9mZ($>Yh8-=>N4(E9pO^w(Pv&vHvm3XBCRCEOS z8O#i1hN?;G&Sa%L7A%rO+$NMm6K-&(B}Tps zZ@=Ut)8-#9sLMuaRSgOYMwaVQ&)KG}9w||fF^vIQE!9*bPLUxlp|3jUbTH^k=tR#aS(HSW;T_gL~lYd_Q4TAuNsnkG+wVw7EJop@9Nurwis zwAQGRBX^T&Ftafv3*YS%BOcHdt5QS30&TlD#|{-5em0bY;{2N#0y7ZHOUkH z=ZB+0fa-V}+&h0wQaAdKqdesZ2|WJCnf&pOe>ImNKa)_8n(nuo%Tkp*@l zh>q`inl|I&lyjAKky0_8f|$G*bWooi$T~zde%*gkZN>sLAK8*$dW(IF>jfRpAbFUm zf<^~ugCK?5y$0EgltviGhq|h|JthL=&}>f%u`kMUkv*AxCC<~0oPz!oO9@r*ifV7l z)!HjT$|&Vc<7;lzf1LAQlG1ABpj`Xe?O`*Z8@96AJh^sxGD4ox2LhGBN-#=vdhK`Sto+S)nHJ)I8an zM_+bI+9?kROPnaaucyW7wak{p3Cc*SZkMu&uOw^2OcYQ6gAUOV4vG`F&O&J=%hs|# z_W10t&*LJol<5SEFXl{kD~`^9QEludFKU<6tHI-o+QHKUAd*l0)_}CiM0x^Hlvb^s zX}{q5^>a&k-ucBIg(s7I=~G%kK(iL1h%!%!Du7DQ*pdy+rQ}Z#0*2=5T$Wi$jV@<) zfMq!MboP={OmIBeUVg)*)9UJL#WgfEin;Kr9H7+@d_%bVrHs#SA~l!}WAE_E6_4_jhvWg?776$K)!<)Z(L5mpAB` z!^Zg1#Hhp29B5OI>s4&Z0dhQ2&xJP$1V{P=+8$b;XueX%1}(W-@mGNlaQmFBXC zm0h``63j>O(-S^GV#arCVOB*z*Npi`q^`!>4)DoShUOUU85IgKEjmU)laONi2L{Hb zkqGuc{xPRcjirPPpknpE4dqhOZkz&)cMLC#IA}~x)hupT__eE(RCQaAI#aESXXP1s z+SpE=*lmklE$eATBE3?L%%iCen(02S%;IfZokKcPvV| z6@W%I`|rhZUcfE%l6IjSaUWYPxTyw$#L}*(jg!D3RSiCODz-6{q3zQPMlzIr%jKWTa-7WS2ReJ8kyDHj3pm6v;aDcHG+xwBKFw7i_YbE))oI@Pbw=?@>Z zTz+xXmk2j9HWra=0z15@p5xeD>Z(SQLt&BouAwUs{S8Kc6!7{6HHqVlr)5my*gtZcQ+dQKqOezs+Zl`7B}+@gNp&bw0RRZWhoJRS z6fDL;ERU)^mUKlib`wF7PmJs z(Cg?^b5EO$9X1x~c9lGis1J$$A&h)X^?ul2`l>&r00ltN_&uKC7*faj<0yBI!sCV9 zzjcN04j_qanVOfRnu|Y5BS-|;coE6_f9rpj0a`ZLE`D@SO`>>zU-E0feZ8*9Cq3Kt zhZ)`jke#nxa_NT$Jqihcq+NEG){oq_{@3a<3l#Z0>B#eMmt6RJLucNydB$PmRq(qQ z{z+3w_{|{TSqkDD{uY703KV7Yy6cf_m;NAZ>i58%1l=?fz`m0($$$HdViIthH!0<) zg~ku8^dBEq^aNCMdF~>qjOf4VUfAOB~k%TIuO_j#L`_ zF9UG&gAHKxY@B~W_`kvJPtg4n!oORUKM~RIV*Kfff555!bj5#4L;pn4e!Ak{q4pmY z?I)%Fk0Sb=BmA!@^-ot!x{LS;;hzxxbF=?5DkdFC|InkKQSqN^)BhE&|BQ-%_jvy; zApD=S?q^i|f9cPk&{6=2KNDL&0K=b&tv^?!e@9}_&jj^=!Jkp_s% z1CpO-#iSdbpKO|RS^e^pP5(P({)<5O=UMTe)1yCz|G$gyC#C)YBJlI9_(!n#zbU`` z85NW6B7Q>nCxrjp?Ej34|7RqvACA`1addv%lPpCB`k}RxsK1i)9l7BtHb^Q55{tM%%Na4@ku>-|9j1{$YwL36A z2WUsZggY`&)Mco<$ixip{{~mE0V+;#saZ_-LxOt(p!BXp{GZ=yS*D|=(9KAJ{dtt} zKQHBbT&5w&L2cdM&ieQ6BxF2QjM<-9{aaPz25!fKI5f1;Ku6;LnPdDFJcIVh$)61@ z+6w(ZHvV7V*ZArT1-YS{+sVImFJXX(19&=H((i9oj>nrQacp|XRnxqEs(iQ#P%-}E#-aO@OH1cg`r;h)^O_3vbDHEP zmu3n|%HP7A>_M}SesDKsKaxqlRO@T^JHFIBN%Sbfn92Co3BkNj50+uWhiy@}5c=tW zlDz>V+4E^I=5^cz9i3GT_Q}en*ySqZ4_5wv@h5ZT+J_pB>&X_t@Y|ikeLZkj?lgEn z-hSz@p;2`GKYIa?C+jM5sOzOOq%ALfQ)l;^Roo}S#&zv9G%ntqld)v3xs0E5z-*-668g&> zt+jj=3u!>t`Gae%ki6@2FFlv+rf}9W8G(I^P3wbh}yS3UXz7)LUq|@VUngN+>$y)jrX&mQS&+$c*thjsN zLcpcW2iKIq^;f|1EzR(d&tn zi_Bw)Dp>{Dz|Ek1D2o-UV;HR(1wR= z4o8i?erV{jQ}zCN3WS-$#juL5(p(%J#-Fq0qTLI{vQre~_LZU^Z{UH~Thoc{pmfb~ z8BpIDFgk}_0m8c-9aVDYT+} zUUDq@0$()*@Ka0xh4ls=Y|VREd0W4@%_Q-I19WpxJ0x2z0@&x5Wgnj5*JIO+_4K1+ z@SoTGmOKt;PRC#O_%UT7PuAiXl0q0Q8f+e{`K$p`zi>45CPRZy0eh_i%wfHUO4>=; zKkDf+N69-Xkn>}UwksLLB3Z!x^I=jY2SYN<$_l>qbOsQ#28Ggo^btlyiq{TlofSRz zDEO-Y+d40QobO<790WQYw0T^a&*by^54wm)H-p@Y-D`GgF5utm@M!=Zu0v*8OLA-{ zK3*)|PyG7D!1F&SObW3CMPQ=_k)=C;0-rrn;II5e>+*XtCSU0qe{g?wO z80?ZiEBZj^T>V34;vXMw%;Mvqp6itZ`~ILtf453S96@c`T-4Sl(w-<^`GI`+Kh=fJ zG^ljKGl(M^jq7*+QCS`tpBJafp&zgp(N@o}@f{N_c zo2M~qVA;PTXIolo3X!<5?~TMrGB|JJ6qaE8B59;KmNZ#MkjXA zpcwhRw;1&mNk?@d*m};^i$#S5=ZR{w74$OjQr0g3?9VjnzWL*9Nm5}p`n^i3fXi!B zWmO~V+tYgjODk|1i%#3j;yL!*anrL(b#i1 z@MDY2z=hkL>-J?k4i~P*AH+;f7V7pai{}@Xfg&TKvWY~$CFEL)y@!Vf5?)>f&aT|4 ze1}~B^5o)$3+ZL>NRsCG4=o9rrynV^n||F?RbH8!mk!auR{Q2uQBe2@+nu)06|dBj zSv?n|rVf}EE{~5M6*VWu#>V5Nt{^v##h zw7Q*Kr*Uvjy!~@I&y7frJdD>^xxEI>q%UFYjhL*g_!pqtfXhlHYfJiKPntFimXKTC zH`Ob=NYuOL_m2iekLlO(x?Haxld_aEv?KL44*TnB^2ze+tHFI5LJ(=W!vi}#%)BJy zz{?JxMFt}^h1>M!Ld2cSoSZ98Bh@}`*1PDP^A|2~E37|jRHhB_Vf@6>uW?zt&VBL0 zSm2DhQJq&nX-%G;mQ29%$X0=k+x_#y%%6unm5%*g2~q4{4zTD zW@-}-6=2KLX4B~jB$h304>u&=R8vcs+oQZO<4QwKu~D6R3zc}+qI=Afmq9FkexZA1 zX)ZKt-nb|FLu0{xxqKKbtL;5a(E08Ml}_W*qd6e3b6z-O&q5KqGN2fBZN$<7yY0E1 z!NOrpc*%B)WJ-IXQWjTPN7kja2xV%lOYNl^@Q; z+O8P&W}wmc;|O6KF>sc%B44I6oF{AV`a{-kyI@%^+$b?)4V0@Hb1+e=8bEZvd#>;|vN0pv%L7b$@@^ zZ<@~Qv$3>~Iu&-hjf{+px$7ik#Zp_{uD5C0JbLn>gd^r-aSPhK67>&p-2>)zPrRNK zhHO9?WLmeIjoC+Iv=sKM0%Zecj1^Lc7K<*O2Ko%1NCscoCdm7$W(o$KW0urGgV&bl z4~~BjE;bsB8!9o*I5GomZEbTqHD-Vn$}K4=p}%kwJz1BKbvk&V0DgI#llf+Z4gcYA zAjY}dDs+Dkz<2DpVcM@J@%@T{fu4F=P#!0@o$yaA1J|X#WISifWy{Kax%~d#uegEI zm7=ZJB*Z z<7-{Ry0(`AYj=Id}v4AOk$3gEOYEcz=gOgb4dX8f}y5P8D($TtI;FSKipQ}s?ABb?V4h5!S%IvTeb<)BG4`F# zyj|&?hE;R^OnKieK3;S=+qi@PfdI|pnsk# z5}HX0Up*GKNddHQ*gyHFkL};ARZE~))5jeuy`*BV6GJ)SVC()I*lXCxyD;R&s}wOs^3}GXj#D# zRXCb=)DNrEpGZt1r@8tyvJfBaH;2lv^hDmr8AVTs&=`4Q&}jpH zINe$Z;j!x;h0I9VuA-}aCs75qg7aIO7}XOsYZ{cs4(h{AOb&&49zYFK#x_Q>RG4YL zcoHX;3>#8D^TL%a1tU+!Oy~$Qgm*$sd_+yV9*CYedk??fp$~sOyFb!!sBQ%F)p9-9 zb6*}P^{}!`#mX;CM5WdB=V}@A^f>s-ziO~@Hfe~*%WQgQAx&~2zP{daamU+u9J~aJkjI$Hg!aGf|OuJ=!AUW3zZ*6-L_?g5TJ@(Rl;b zv9m(xPFe!J8-+rd++ptSx!9Z3tMLbZ7l#dSFe##`>y<$Lt^+^wv^;l$yuiX(k)2um z<|k1SqG`p=)Af6w8T;{5huc&+&r6B(Ku*UvPPDl;oitzfJml#cN(co58qFay7r3%Yrt@ zuB7efVVKW|QL*`p2z|!zOSC*gQ)nkaRd!y&{HSs(9EN+0#L1}By*C7wnz6%nref?M z#O%&{M(~LYvv+}|ObsBrUqifXjoR4HJOg8-V;J*YQs91Ad4?A^GjH;*%eu~PbAl8r zEXlt$r_E$FTW*ZrJ}^kwHsWU3Qk#4fDBIsekh2PPGXyW^?M`=Z6~va<;2%nvAI_rD z%Jv_a0!J~xGqGQ2A?|DDlWc}hyf9OeSJv}n&ALLBgEO=dQ+tn-?H_`rSdrsgD@|Lp z1ZStf516laG+4BC%o6?AXXt#)qRi>~$D^eixD^EFGoMMi&kSnjh=pi!rfk+vyg?WW zfZZh`jc%JLpQMtLMoD!>TmTHk8$mN!ee%@TP7-csCr719pvA_BcGF_$2jJ+MJ!UPBr!Ly|F;-u^j%`(r+_g4`9+wETJ=Rx}_g+GgN0t z5shNsyq|C>S-feAr%|Ch=K4?q_M1)@qC)!oS7ud<0dEVVZI%HMVha-L3+bE%Er;?< zCog__ei!H34K*wiWNjbk8sQ=1bP(9~7TA&G*?)i>RQtOBxOsvf!90w0TtH2qH$I($ zE9oDFWH7ip4T%alq}hFEVgHHG@IxCh2Sd=jejc4{o(u^0v;qFk*oTKG<)tt2#p8Wd zfe3I8mH$d`zlQYQ9BxrQ!Nt-a_m)K?i#+*0kTWeyzd$g0t;qjGu(La5I4q&GP3|Lm z^XFXy3$}1mwVN+HAIJ5ShJ36|Jg}v_T;qd!e36MoUC!t3Mtr?Gh{51y{IjoLPwr+o zJ{k)Tz?JOIKU@6PIV#GNJD7-b(lE2Fml3Lu6iF7PQ@Q!9nhS5S#bbABG!bzzAnL?O z?5Rr(TRrzJm3$`7$&S?otk~Wa7m;f=ZOT&NP>wkCfb$)0(XQz~S425l;KN0E5)^TPqdf~*E*h`sr=e3cXvnERp6?fw>{+k^Ho=S1ya_sf1?cinyy;zXZU!uGSUn*6Su^3hOhr zhJR~wYORl|VIqXLbkQV=5oGnFO9gwT69ey}=T}*)HtHi(cWp8XJo6f&jlrcx(DY!SzuWW1skI=Awj8^viWJQ zK1eSk5{av>qLZ*;eQWwqOw68OC~ZKAk;P1b&G;6_cc+-4wyE+_7 zR0-`7T69z<9N*Q|FJV+-!kW@Wr`6dKk=MLZck%#p%~E%kKVkt@b)_T-?jPCA#963G zPS~~^9{otyEqMe5DV^{kME_>SVcb@0@9@Q1uZo(A zZRaaT$4u>Zhr(?5T7Sl!tED003?2zr7`$ANXFRN8-N|W9VEYzZ=GGh`Yj^iIpKZiR zm#G@=yUi%`Z%vo`XqZhXi0@?subGIT-P!#Az42PS74t&|8$dWby!7|a^%9(imt+0504?!t#sl$2*R zDf9B|JWJPW4>&|lq=LW&{>J=$4Y7DPj_I!o=mGXKTHw`@I6)IBQA4{@AMajSNOW&hmDTue+mj)K~J35C9GU>QwHQR_do+yT6=WQm}Cjbh7m0-Xk z>Z2`!KQ>DXiH>BEccY(W-J$%K5?_ z>S6ndH$*eat5-YU$nxy6ONm^_EC&rYoIjIfcS?l!`3XxwwGX#FS!`0uMVBd?#mo z=_cil^oEMHl#ywKBGu zvB-qMxl5Pn$lKY}9GVKVH8WnQe_Z3-K|uKk58J{vKl=JZNyrkAv#|y-<7^RzU7WTKg6DQ1x6tgfJXIy_(>Phl`3Ph&xYSFvb+Vzvj5 zal?Kd1pM(xdOFm_b6q@l8kZ3QCToQAM(2w|EhVtKirJuR3YtC*q>61zJq z`z9RSDN|}u#jn44V|Mw*?w&u#$>7(9HnnwidU%zEmu?xO>0JFv*6UX_$hq!4SDf8U zETs8x&vr6&=G`(Yp);VchlZazJe`r2oYpGp#TOeNQS+07n9z;Q>qWl?%0&pPH0ZDd zI^CvLXHgJ&e)it8kCqQ1(p)Y}6H|?4zN{_>0;qq4~E?26x7VV*hI{l~5+rTz_O26xkEZmNS zlyi`pF$bA4=hZodfPIDB44j?9ec4%0e}?gkcfHVKX>gozx|53D7{`|ZvR(apNl!`7 zD8)6htgPLmxt1m2RG4xdqwkM4Qt_A>dSra5(e0qs5d zBxc)dG)#BmOQaXf40+PIqzcYbg=uod+F0b$o{296e-tWf?7Al@AAVxsi~x=79kXdy zCzE6=XZ>*qlU2y3u<@^vL8$>b{4}p=hc~upfkPZ!TT0$(oxpT9`g@ZQRO?_nuJ7Wp z8`8&h1afK{LCZbE!70*(n%0KN`Sfr)C2L`Yg3ThMLv2d+t5+8G(S>Ny=@ncFfp_|a z^qF?YfE0|>W>nz2Am8tH!g(4lJi0CCN5wFj#;x!kr3y3C+etyy7Hvx|-fe(CgTQMH z<>XMPG-(UF%vaqu@Rjm0+j_Ir3PVhN>J$}*6svHQZt6QRrZ4jps{kRhlIBu>Z9sk@ zh<#9(KcXvqw(~^wp_|d zG28f-hX<1zC@N%|&X%ZKY`(HWVPRNi>tvM1ew2SO9w*dQsWGoPZJQ4fcG22iS+(zL z1*#0?kZFbtE(jzIxtmZGT|flwr(>c*+kz63-5J44V`9%!-*PpnK1Fyhyo|psk^0_u z*UWq@v69}en_=$muV+;)KEwQWpP#pt8kkc{Gu?guW`94|EGzB}w;BDYGq=d{z1Uj=+KnY9%FRh_Tdipg~jhn=3Rh7*G<|cQriP7dkRpd-@vNdGKGs{MPM_e1VhH3 z*9`rBHY009#=KEGNjOQFbNnRL`5r91cYgN8=28RU6GpPuaE<1XOc1{CLQ08)jEcoq zvMf3!j#4{*MM$Ud^tK{bsdo)Ov!pYGgZgr1nk?2g4D{AzObkvT&j7lq*cMgKLD=6e zT_yfVSE~j$>U}yZfn%wt_Lk9#)v_-# zgGE$>{J*fW?snfqxG{vX@Qdn)s?JGg+`gMri!s6T4_D?qF)b+_berG}z0xsonhx}U zF~FtW9qPMCcu#xm7YDb-d#~Yekk6+>b*wL3VX%wq`Eg-=TOpLYy}zGHU8z(+t|L=f z)%D%@$L0*Q>bjFIw_2~yt8~leUM%RUN1e^M86Pe}oxN{LeZQ`@u778(uK#6ZitbAJ z)40SNgBCFtl1rAd3NW>v%(}7*e1jGhxaHOz-x9*(2FoIvt)1()x23|9%#R4`FY{O{ z==@=pXLg$+&9Di%PHUEe-K*-OW+~fUop`x*Qrf`I+@&=LYm8nG@qEFf!=m#&;TxF+ z?e;y6Qft>XKh*^kGFi)C8fWJX>g~-3e}euR&?Oc#C|!5ckJ60^8J=Ev1#J!b!>A+%b$x|1gWxij(N9`GlHfMtVN>Fio_oJPC>%sO% zBdwlNid_pNzpg2TT;g%zlEoL*;LTaQK}fyu$)eSXn!Mbdg#!8jevJv62L9gQb`b#s zi;c~WaNaZ9h4=@{{JWhw9;WEhA-{t}#w0S@%mK?5yD*_T`iEIA+N)_ za$h;9X`Q^CjPJWW`EaY|r0Rf`o5DJlaFqvds~S*ps*Om?{p&4~hsR;@ztjc=5tAt| zI1S^|WOB@LAh$|K9&u0WsM~r`qCNf@4v;~-F~cVgC}cZ_ljC9M&{}?3R@pZv@{?=h zfl?=*7V9h`(^J6*dab3uK$jt#XB)ohdqyp^O9nf-J0`E~hq(@eBoiK654zK#w@p&E zk)1ggvrvDPW&LN}CcpMflHF8%y8nJsa6C3^tj<7kXZDQwZF6sQ8P0YXHmGGr=e>HA zXx+*4wmJ92k&{cEUps;$YV@|X@VDh;ah9R3kwRD^2!pYNHa$%RoeMt^?_w zWRVC8^6Do%!|_$H<%O@zs+9qakLlHGvh~N2gQ#^o*Ta z;SjT~3I#&lb+YV&41rpAx^wps=4lF|73P!j;|B$cRgTuy)osLHKb$4a*_r@6a-hE~ z_uCEY&Wl}#CztzjHbp({H_8gM4!j1I#RD4lx=mMIkk)z0GcB6K`W_C#?(X?7@291N z!z5Oc5;VlM5u{XRyw{m&V#hu5oyIr7;c1K?^nv5T{>`31LD*|Ry2T|oEUuge;>b`t zf-vjmHx+92|5Ms`$2GZZYb&6zMFi9h0s;|iNbkKPAVn0EUX+ORUP6x%1?iv^krE35 zktUtc*_5D2krsNS1_%&(2_*Sm&N=(uQ|`VO`235Xyv(ebS?gJS=BywSb4CY*7oOkD z-nqRfea~t9>*Mu27-)vH%Iyvq(q8j1R55(Ny`1erJ7OoLftta4{?I|=ACQ7p}J?c&2MujG_Cf;rhod>Ql!m=9{kc;CIqon}Byx*$ zN8pDEf}<6()>Ngwu*)CmNPc=wAxh)P$>AhQm5*v!dAk(K3!DCr>Sdsw5it@&@S%dj zxmoMkS$(<^8A&$pgXmbYLru(Ko}~FI1cTJ!#%M<#pDY8j<9?#+-et58Dc((&t9vH@ z@Y;&+nJ7TQ8X&e2R)0i(VK=@cA+Yy@b}xueULf zNyL~}c?sNRdVf=1`exqQbC=0aX6rZo#U~|gXF2%s*(DZMKXY3;I}n#lyj>&+2L_Hn zOC|eZUfp~z0Rz&4!?y(98;+I|Vo4*mJze`7I^TcdRhe`<>VjTf8Sq|WP`$5J$0Ik_kJ0m+K=w3p z@wfi2u5?*(q?`ZxY`(p2-J3vu8!>8w1e+|*w)_#FY1vYYRi(v1`n)vLQY%;Ad}JB7 z8yG`sdIP7YqeGZUJKiZXul4s^bJpwZ5$dIw+X)P?dAzZirhJV{s9hvsti5MoAZaXH zpvtvNSwthl_j=3oeWcb-(o*ZLndbGd;xYBABxi+E>}e`OUvGuDwC0yI-kC|foh`tD zX?fXWZPz^lxP)$PVQOum5lWd3;Zw_mi*Ak) zJJjhjmNf@k!BP|<>K;pFkv#!nG z)6Et~%_B}4VxP*RpUSU$@U+fc`oHtvCn-8^xRFMOHXPxE#Eo9^#*JR!O>Im*uC>2I z@|HYTw-oFpM!nu0HEt%Zw$`^(Z8 zM5vZD+`cZ5xzvTSEb)=EoBs9TMsFf-a_?A;oBVQT{bTS(cKNf{QcwBT#*mJ#5x2h> ztyAC|8%cfrL+KJ&z$AV-hV4xMq0GBYDP@hRldb4Y9G5iUbBQ@m`_gFU)Ve!*tL<6w zEx}hYi*z&WlhmiFdw_gmWYrAZSnM)j6yInqfsh;j@D>uhX_<~Hav_HPQJ1HPULUIt zdV}R^y+~wdXW@UYQZVyAk%lQHSFFdoV5+Y=ANoixRcEo@Wh~T7*7ng&(I0&yvntz> zr|NBnlDeGv2+;};n4ucaS~bgKy1*QLrkxq`@frRLOVVFI=D!VK4D3%WjFoyyW!8L| zzp%`1{f}KyaSL=AN4+T?NW9K}%N~Dcy8g#Djhysavsnak91W?id}@gOp_p3kr0bmT z+~N}d+qDCnnd_z?seNsKv&s(1uc$1a8f~Fi(v{ZC=W~y?K(0OAm1q$+NTSIYNe&SO zl2eOp7}Ztz^VpL6Gm9y58W|jLZbmo8LQLSg$|_Ovbx1r{ZufAYv(tCaYjyA#_JFc1 zRl%*{SV!SvIp?%Cjth+(t3j8CK3bF1tpX%GZ zQ_za<7?b5GmTj@^-(@s;?7;Ik+{#WO$w$d-np^#h1#l}&I4NZ~Z~JeKSs(SG%EaU{ zD2le$feMYc89)(H?@L-NN0&0|@37Mc)yWK*Pm_!e6ij+MH$!I9dD^$!Z?9-Q*_~Tl z{1NGcin!g1L%Z@Z)oRSf`05B2t?ce2d)H61>0t03+SthIB~%gD3RkhGd5zRIeSeEK zwkN)9S6~lWG-$iM9Axk=x=u|<9eLC5d#k-Gmr@o#HL_V{W_~H*q2P={XVU!{pe*I~ zSDh5)YiX0hwP$BRaR-s#$gphJ>NIALWEiMuCD{LIJWW-&!ezOg<_(MNn)=t2knc(` z8Wrilo%wG|?)z+hAGXX9@dcjj+2hWTTP&h~@Gkd|`;9C=S0JmlB7eoqX;5*sP>|RB zQ*w$XCO%k?HzRX_+Ge*$plsU!?j=Rvt7X`R*fy;TC*?&GY2HVGGQ%EPuWqIopdzC< z8QU(#0;ONGO)X%DoYOL5y_d4KipwF4m&*Ot%O{0{Vqt;7ci}P;*DA4h&y~N@QN|7A z3x3hKrz9=}QUF33T2$(MoI#OsZqUtpiT9pnVWC{0)ywMU4OZb)tdo@-H7ZM z@c?O#xk?6MYGY~77Edi@&rm*uG=PPN#y*zt-msb#&mgmQv``h#yv#HUKzT`i8^ zn7-Y@2{Swv!mxGABe(vP)i;cH?yTC<*`#mqMI}Nvat&Ff+PMtPm zyBT1G7HcAn4-Zq9UaEn+0+24*Q?g2OI9rz19E zNZhYIk6sDEXxrZ9_nxpsC>7Fhyya)e97ZkR)^>W~+zj(`I6-EtO;c!x z0fE)~Q#6fvVN`>5Ovr~X|&FPjGgCtZ{xo&#b zt~*MRv($vfRiN#Yd(||rM~kxCr26Aa3La=j$%??zfNyMlN9tnjJEZ#jlUtEvMc_^7wnEV{wUIFAJ5ys>nAF=r!l(Hz0W3iB~| zV=@NsPQ8~j>Oxh~Jji5fT?DZ;I(N^`UOPmUl1GHMZ^0ny#)!`hO3Ct>l`EAkRkT;( z_oeBVgN!;o7|xRlA>#Cip9SKam7x*BkT?5N#tW7i(!3wS3hEd{H;G)lpvn?J8G!EgoI_}Z+ z=Y&d$r?gIAwhB{P?=2ba`fo22rQIHDtZt1W4O(`iq?yp}=3!IdT3PT3%^~&O%P@NP zvDGSp`8Q{ zVos!_ma?y3Y`xO_ zUnEatF5=qS+}y*+bzar`cFJARJ{H}L*qqDj_|9~*^*(>!)C&Bdh1hs9*5XOrD-n5| z$`k5W9xL5%ZeNe~Wmi5G-|^%MAQ2uhWy9#U7w*{3kRhQj-cS9VqUi}#yw&ZtzRGluH?j4v$&~JWl4YuSIG;N!&kEB@d1zeb!LtQZ0Vkpafu)h9If~% zlmO>cVHe3vMZ;$a)K5>+l>x>0Fx1^gQnsc%?ZZI)as;u?Q(XAtfrRt$>yLL623+uuqy}!FPiUG^ z`hFGg;yny;@e=E09U^gCAF};rf9|fH!sbBVNC3|mGKiOso<6(0yu7c!KV8CgC}|)c z^OlRVZE(=I%58Zg?LC<9^p-VHR z_Pqn8K;azMrA)*%4``Gv`Bl`F?d7qK*gCJx?=xaDG6Z88If0NW@4eRPdjx`uUftI7 z6R;NA$~q?3&!-NNfcsnF*=Gx&24~68L83^>3Nu#f@2&T<$v2+?)O`syF)`rg~Pqu=CIC>T$C{9ph2KifjS1|GDjc8$lg-RvF-xYnY2m=M~Z z;n07~6eOPW%n@748@@B$M9U;Q!i~y9 ze@ccZ1&Dg7a2ggga9SYaGsbEZg4%(ORz(ZsL#3##MNMLAhK^*peBx;c`yFmJNM?M8 ztp37}-ZFVbnc+3hn8yzMobh%%$v@K^fZmpU;WT)dx(5j{k+4jcRnE&Cf%+1u(L2nm zVStXlkdt2z2~>n0GKKnITP_rN(BF365f1cEf`Gmwmd-!UEH}AY)TEKnH?|PbX5&dN zi=_7X@8}>$R{2t11S#r#?1ylT7_6prIrofl>|{yfzMN6YE4n-ENd4fbypa_3bG5Q` zvt6#O<@o_w2uyOO<02GF-MuIWFVQ711R7DE0MrYPD){IU+-r1}=!G}{2#$8s-^ z!R|EBiqzr8Waz*-EYpLau?XH>zkA92g-CY&>K0Io7?i`Cn zz}2zcUd4CIr9``h;CV>TY%&?VfZSJcU};ZLJ1S9zH4Ro6Bo8K}?i#Um#&O=1+z_#S zqWkhegW!~0UZv$(d@z)C$CFdLiIZ?ohK=R5<+s>g?Pi9PH~L?Tif?if++@hjVZC3N zq2kWu@AWKQ4Ep&~;zyKJUaen2IR75_Q04VatP=<><>PZZdhAiE+50kNbv7h6UCGS0 zYP&-*jSx)gwclY=5aT$pHYE2QafTx0H?iVa^Nv_Ep0R+1u51I$Iyp0SPa&bzwE0d) z^Ni@x4k@;}ArxqYp4zMUy=k90TM*$1vHn@4^8TP37X?aE?9G{bL1$Z+ZsXkU-@fyI}{WBF>93erQF%H_y0RRd?>sy7N%I@ z=btA_m2x}J+v!==^d|?Cb|{$S3ol)xw77echaOm{E)}&S;{PvqCgc9$cch6AOY;_v zhKTkM)p?YqKQ|p6y<(@TL^y?}~c)sy)*ES-DI{VjP%;-mfLc zs$R@UJ^vDJL8k(^_W)%n_fBB6D0Jti?3-9kN*6vrYv z{tW1_8S@KKo-}4WyVkdjNcw=Y#~DF|%c8<(d5LmILtx*_R6Y>zOwQ3V9{Fl_T}y@0 z*D)BJp5odh8_?*V8r!rIH6eTvXa&2J(f5?Kr`C0c&3-O`?4eaSYsn9~peY?1ZXC6? zMA^kJeQzqfgD1TmQv?a)7_8Z$|46y(KIN#8ROJG{i|g87o*e0;IMODaq-f{*n)8XD z-K0N}GksoDPY!|z8e`Alr+#)eCK_QSv-gzH%$B`2JRMdf1kyNL#?g}O(`C!S4( zJ!qC*-DvW-xPQ)1jc$&k5_@w6mt2T|%liP=NO;?_1Wa^OIUL-_lKRG!_IEp+4kd=z z#AD&AJI_lyw%bTxJgd)-$E5buleKxA7g)$%st_&mf?#GTdE0Tm$G2_eHg=3Y8{`eW$US28= zMgYiT+rs_(s-#z@E3zl(gTG!;2KAyy7G0Seo|trfNJcFq$b)34RgFfZMe!iuz7o_S zPMY(mU|vN0{7>#)Hq*Ji#sq~DGWUE+*S@}kEK!4Y%q-|=gdVKws~s$@K>14dBiKNU z*hV^3+?Mj&QiikB2xkCPi!5VHdIgHRNnV{v7~d`7A+8i=i;2Z&PEn7im%QAcN${6x z#fdGtQtd`(df{;-Q>}dK&#bdwt{j<;&v!uQZ_-fZgli#Q(wtoiwqo6Fb#2R$VJb$C zdYi2~5x>zRHdgmvq2m{)rl4^>^uaQS#>;7|?Sh#b@?;4_>IIe*g$*^WdLog>*-8{r zO*{T>gwqdOkzJJUYPM3~^QU4x6r}$B<4DF=Wg8UAOx$)DmQ$hQ+qMQ(iIU{@?0!p7*!=EWt2@R+9EQSstb9jC_xfwf6sc33hU3*GISTxke?O`%r}RvT z`#|BU+-G}3p}IGoqTe|jFQ6Oz03EqK^D7q9r3?y_%P>>(I>pb-<^li_of`y#*?V#OgR&Di8u z1`<44qgoPT71DcL_xlqhwx)?%*LBVgJjz{l^b{4**@O($WVvcr?8tD*doE?cwa` zPLTA6wi^KJ<_@EBgZAn532g9A-IyQ9ku)~)5-kr2#fSaxpeN}F`hSOt!z)9u$VGr`j)(2 zWKoSR%shQWa0ZUa?meKhPFp+NqIOvP(a4C!C0F{&B6M?GxAASe4&xO!Dx~v~g9=ul zHuSQtJ)W={bi-P+^^=MR5Hr6@owBi|>*#yM)sq_>0Nf1pLdW9BU}szd5YWQr@ZlDN ziDZ_B%Y%xpbwU8yLJPW12`s;Wo8n`v%6ooh!HUCGUcX5~(-WKoY~E zGv5nH{9u0$TyW0f@_T3HfX_NdcX*iqXZgv`7p$Mpl%~liw+?&ssi)5@<|mLy&|o(# zoY=o1>S#1 z6$%uh)&YlH1Bv?u~t%;cvr8H+b|5CTRG~f#&v?sYBp%u+DWs1PmxtO6IJX z{ao3g3klQr-(uGHN5v%gXpA6Br#0h>(zduW3^K`|6H+O8YTbkpBfH3nYui`ywQ{C@ zMAEsMh8+GB|6K7S0l<>+oF6O)%m*CGpuJ4nK2KIdTTF=Is*K3H5P7ajZjN?SiJ4z- zQjH8o`i+vy*v=jO3Kw2I0Ls40V(B5x+>|*)dgwK}pkErS{%H^WvPivn&3V}jEqm2% z-`;qtOr6u`yWY62wUZ_i6rd6a@~Wi1Oc1w(#8)TH93tAIsfkGE^uXX4D-DJX?B;$} ze)O1PtZ?5)%h?a!PdnmTFnl1h#6(C8HB9Y)1aQkVq0i)P!<^d!X|!G|s0+Y|AM9)Y7BWcnXYyRFYD>=t&;`l>WPJ7 zR~eqT`R=d8pPUY>mq0i|Z0GKdGtDaxn;H0fwELcppNMB!=->MZ8{LAEYWuu5Dsaw? z!N)lN`v7h+wb)`!PI(x{Wpt6-y>QN$RDGk2r<5fFdG<|_$@+vj(W!@uq{f`;>A_er zMoN7-ql!AWB7zH0_mH$bCiSjLVYADCmBn05)#n%&5pB{=kN$QB7RKG}vANl|jw5?& z3AYb3L871}WJ2)FOv3W?v)45HpOa`P!u1tUTd=g>repQ8Hz2I>HB(A1ZmwGCoMeq0 z;y!kr?Ko^03$S7PRGoFqu=|FjK036?{#UJ{kD{{%`=@%@gdWU}7rU&0Px2TNc0+$* zhn}pkxxd;a&+wvnaCS-WufX}Q?Qte95OgY%N9+*h;R8xD{RUtQCZ|HJ%L@V_;3EfJm zqR!D%#M-|I8@<;_bzT{i3thz{yeca0^E-z9iGYV+S06tx&6t;ud7KhCx{o*%n_Yk< z-_c6<>nb?4R9l*eNuuA@(PD=a>U$U9B(PYRloRdw%0E;5uU)Y_3YlFFeDj$O}#KHhr zn3baE&1U|jnln|VhuEww%&glc-(wBNLz8VL z1z9Ub^z^(Q9sw>xS&2K?2yxy#=1)yIGmD(r;y?<%2THV^Uw(s03awx6$}S4gLvXQc zWKO@$Nct3LrO>55AX0QC16-%EoEj6Z0SWGmO`5{$?-i2N7yKs3y7L`^Sa9?--BE)O zfEd_u!D@o*@@t)IRCm1t0?Zs)IXWXJJs$wsyue&ssvW3mZHGop{T**%R>r<2bgd-E zW^+rde0RRD9+z)#w{6xlSYB)Igc=9jd`}^bm^r(ycy{6N^-7GyCT*dZc6{7F3 zE1JO^oyzP@yM97?%K;%dGYc(!JpotB(u2hFWyo3%OF`1nV|8(K9lI!7E|%Yp%Fi5v zuveR!yHM$!PnV2qoV%_eU@(+prusS!Z4{!YPMc>&V>^Y+8ZOJB#YkGcXDqIDc#-_a zDbg#yuLV5#t838G^4lOyf!hgfs#0&SADt-=K!Hi%UaNmX5$8`8#}Ty$nn?GFj^V>p z%Kj(S?|)L*oTRmqvwResVTZsGbLNaxE|V!;Uy zh%nN;mhF|1VEkR*#k}*J%u#O<{Rcx-9taF^_Ro-4+0H10* zwikQ#BWL5IQy)YjJRyNEI_=#vqVd(h`>Z=Y_@d8o3||72mlkb^@ozj~5I z??jIb={iD0>j?{CChmBm)Jp}hTKvSsl zz)iJoYrpbCRHs}`%}j47ZX8PtlpgsV$U$S{hcw0~awj!$-M@T6n%cR*6{zy^F-La_ z0qQCR0bxIT?wn5Wpo|nFjeZtw%IM^&qbiuciCIJOZvf9xQig1cKHu2hW@dLtWPCDo zH;VFT$(?^hnX_nt!9qe*FTg^j}e??00|{3 z?REP*dG5vcTza0j?i*LrDwr*k7IZws$N(1l=>_Pg+zSvIef_x{Bl1|~C;OKuz%=^9 zv3;5TpS?UiFRVX)Re%eQGLShQQz<#iYpvf0hRd>B)2phjjSgvQ+P7f~!hnwva|c-G zt?_#?4#cLEl-#SH;DoaFdOs1(2mzxjHYsQHS}q29%uElU5<1(P(YJgoQprV1_Oe=2 ztJ*H|hPluMwYaBgH0OV+Y3YudyMDCin%%v9tXB?{9B z{GNhB@P5f?tMwJC90q}7(fb=!Yx$>A4j#n2XQQ?tX{w-G^V;vG@!1I=%3DNvd4F}wS@ za}-*#w^$3uCI}lAyBF-!3RGlWKDw zYC7nbI3zu%a2?y)9j5xp!eapx6;C*6WRG+?t=3?nV$? zH4d`cyXwA($D=%ufq`hc!_xgPt8Lh3yh*2FFCE;eiz-Ae-Hb*R+&$^{Mfv*}^l^%x V%}gnk!wKNuo!fWSfL7(_{||KmbWQ*O literal 0 HcmV?d00001