Files
simbarag/migrations/models/20_20261012231017_application_foundation.py
ryan 5809ad3adb
CI / backend (pull_request) Successful in 42s
CI / frontend (pull_request) Failing after 7s
CI / backend (push) Successful in 41s
CI / frontend (push) Failing after 7s
Add durable chat runs, shared agent runtime, reliable jobs, history tools and citations
Shared runtime
- One agent entry point (runtime.invoke_agent_for_user / prepare_agent_turn) for
  web, email, iMessage, WhatsApp, scheduled and cron, so identity, permissions,
  memories, summaries and metrics match across channels.
- Serialize turns per checkpoint thread; /query validates input and returns 409
  while a durable run owns the conversation.

Reconnectable chat (durable runs)
- POST /runs creates an idempotent, owner-scoped run; GET .../events replays an
  ordered SSE log by cursor, so generation survives refresh and device switches.
  Stop cancels; disconnect only detaches. /stream-query is a compatibility adapter.
- Coalesce streamed deltas (150 ms) and wake same-process subscribers instead of
  polling; keep buffered text when a run is stopped.
- Drain runs on shutdown; serve via Hypercorn with a shutdown timeout covering
  the drain (and no longer in debug mode). CHAT_RUN_* settings are forwarded.

Scheduled messages and cron
- Atomic delivery claims, attempt history and explicit retries; ambiguous sends
  become "uncertain" and are never resent automatically.
- Cron triggers need a run_id/Idempotency-Key; legacy jobs need an explicit owner.
- Cancelling never rewrites a started delivery; series stop via tombstones.
  Legacy sent rows are not forked into new series. Malformed bodies return 400.

History, integrations and retrieval
- Message-content search, pinned conversations, JSON/Markdown export and a
  memory editor.
- Integration status panel and YNAB data-freshness provenance.
- Date-filtered document/note search with stable citations and a source viewer.

Migration 20 is generated by aerich.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 23:11:04 -04:00

219 lines
14 KiB
Python

from tortoise import BaseDBAsyncClient
RUN_IN_TRANSACTION = True
async def upgrade(db: BaseDBAsyncClient) -> str:
return """
CREATE TABLE IF NOT EXISTS "chat_runs" (
"id" UUID NOT NULL PRIMARY KEY,
"request_key" VARCHAR(128) NOT NULL,
"payload" JSONB NOT NULL,
"active_key" VARCHAR(36) UNIQUE,
"status" VARCHAR(20) NOT NULL DEFAULT 'running',
"worker_token" UUID NOT NULL,
"lease_until" TIMESTAMPTZ NOT NULL,
"cancel_requested" BOOL NOT NULL DEFAULT False,
"sequence" INT NOT NULL DEFAULT 0,
"partial" TEXT NOT NULL,
"user_saved" BOOL NOT NULL DEFAULT False,
"answer_saved" BOOL NOT NULL DEFAULT False,
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"conversation_id" UUID NOT NULL REFERENCES "conversations" ("id") ON DELETE CASCADE,
"owner_id" UUID NOT NULL REFERENCES "users" ("id") ON DELETE CASCADE,
CONSTRAINT "uid_chat_runs_owner_i_98309f" UNIQUE ("owner_id", "conversation_id", "request_key")
);
CREATE INDEX IF NOT EXISTS "idx_chat_runs_lease_u_b8a59a" ON "chat_runs" ("lease_until");
CREATE TABLE IF NOT EXISTS "chat_run_events" (
"id" BIGSERIAL NOT NULL PRIMARY KEY,
"sequence" INT NOT NULL,
"payload" JSONB NOT NULL,
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"run_id" UUID NOT NULL REFERENCES "chat_runs" ("id") ON DELETE CASCADE,
CONSTRAINT "uid_chat_run_ev_run_id_1326b1" UNIQUE ("run_id", "sequence")
);
CREATE TABLE IF NOT EXISTS "job_deliveries" (
"id" UUID NOT NULL PRIMARY KEY,
"kind" VARCHAR(20) NOT NULL,
"job_id" VARCHAR(36) NOT NULL,
"run_id" VARCHAR(255) NOT NULL,
"status" VARCHAR(20) NOT NULL DEFAULT 'pending',
"claim_token" UUID,
"claimed_at" TIMESTAMPTZ,
"error" TEXT,
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "uid_job_deliver_kind_1add26" UNIQUE ("kind", "job_id", "run_id")
);
COMMENT ON TABLE "job_deliveries" IS 'Durable run identity; never reclaim ambiguous external deliveries.';
CREATE TABLE IF NOT EXISTS "job_attempts" (
"id" UUID NOT NULL PRIMARY KEY,
"claim_token" UUID NOT NULL UNIQUE,
"status" VARCHAR(20) NOT NULL DEFAULT 'processing',
"error" TEXT,
"started_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"finished_at" TIMESTAMPTZ,
"delivery_id" UUID NOT NULL REFERENCES "job_deliveries" ("id") ON DELETE CASCADE
);
ALTER TABLE "conversations" ADD "pinned" BOOL NOT NULL DEFAULT False;
ALTER TABLE "scheduled_messages" ADD "deleted_at" TIMESTAMPTZ;
ALTER TABLE "scheduled_messages" ADD "previous_occurrence_id" UUID UNIQUE;
ALTER TABLE "scheduled_messages" ADD "series_id" UUID;
COMMENT ON COLUMN "scheduled_messages"."status" IS 'PENDING: pending
PROCESSING: processing
UNCERTAIN: uncertain
SENT: sent
FAILED: failed
CANCELLED: cancelled';
ALTER TABLE "cron_jobs" ADD "created_by_id" UUID;
ALTER TABLE "scheduled_messages" ADD CONSTRAINT "fk_schedule_schedule_31982f9c" FOREIGN KEY ("previous_occurrence_id") REFERENCES "scheduled_messages" ("id") ON DELETE SET NULL;
ALTER TABLE "cron_jobs" ADD CONSTRAINT "fk_cron_job_users_b3e69ad6" FOREIGN KEY ("created_by_id") REFERENCES "users" ("id") ON DELETE SET NULL;"""
async def downgrade(db: BaseDBAsyncClient) -> str:
return """
ALTER TABLE "scheduled_messages" DROP CONSTRAINT IF EXISTS "fk_schedule_schedule_31982f9c";
ALTER TABLE "cron_jobs" DROP CONSTRAINT IF EXISTS "fk_cron_job_users_b3e69ad6";
ALTER TABLE "cron_jobs" DROP COLUMN "created_by_id";
ALTER TABLE "conversations" DROP COLUMN "pinned";
ALTER TABLE "scheduled_messages" DROP COLUMN "deleted_at";
ALTER TABLE "scheduled_messages" DROP COLUMN "previous_occurrence_id";
ALTER TABLE "scheduled_messages" DROP COLUMN "series_id";
COMMENT ON COLUMN "scheduled_messages"."status" IS 'PENDING: pending
SENT: sent
FAILED: failed
CANCELLED: cancelled';
DROP TABLE IF EXISTS "chat_runs";
DROP TABLE IF EXISTS "job_attempts";
DROP TABLE IF EXISTS "chat_run_events";
DROP TABLE IF EXISTS "job_deliveries";"""
MODELS_STATE = (
"eJztXQtv4za2/iuEscBOATc3iScz0+zFBZyMO007eSDx3G63KTSyTNtsZMqrR1Kj2/++JC"
"VZL1KWZEkWHQLzSCQeSvr4Ouc7h4d/9pbWFJrO0aWFn6Ht6C6ycO8c/NnD+hKSH7j3+6Cn"
"r1bRXXrB1ScmEzBiJdkdfeK4tm645OZMNx1ILk2hY9hoFTwMe6ZJL1oGKYjwPLrkYfRvD2"
"quNYfuAtrkxq+/kcsIT+Ef0KG//trzHGhraEqf5K2mugunmu72fqPlVk/aDEFzmvggvyi7"
"rrnrFbv25cvVx+9ZSfoeE82wTG+Jo9Krtbuw8Ka456HpEZWh9+YQQ5s+NvZ99PUDPMJL/q"
"eQC67twc03TKMLUzjTPZOi1PvfmYcNCg5gT6L/vP2/XgncSBtQzBF2KUh//uV/VfTN7GqP"
"Puryh+H9m8G7b9hXWo47t9lNhkjvLyaou7ovygCPgGT/Z6C8XOg2H8qwfApM8qJVYAwvRD"
"hGnSsEMgSoGmq9pf6HZkI8dxfk19OzsxwY/394z5AkpRiUFunw/nC4CW6d+vcopBGEhg3D"
"DpsB8iO546Il5IOZlExBOg1Ej8IfOgow+YbpLTbXwSDIwXd8dT16GA+v7+iXLB3n3yaDaD"
"ge0Tun7Oo6dfXNu1RTbCoBP1+NfwD0V/Cv25tRuu9vyo3/1aPvpHuupWHrRdOnsfEaXg2B"
"STRsbCYq2bBJSdWwe23Y4OVjA3ahYwzNMtNeTKTSzBe8Vanm673AyQ7rRWrmOy4y8R2L57"
"3j9LS3QgQRzjJ8YVkm1DEfx0goBeOESDU1DsoqLMUX3ovb28+JPn9xNU4h+OX6YnT/5oQB"
"Swohl12+uhmn4IxpQEXVmpjIDrpNla6528xSXZOh+uDsiavIUDCy4H1v2RDN8U9wzSC8Iu"
"+hY4OnwQQq8pegmq6CFl2N3sLWXzYqcrxTkK8j3wT9Hnc5fLgcfhz1GIYT3Xh60e2pJgBz"
"CR1Hn0OHM7oDye9/uofmxpDgYxk3N679Gru51ImwTa8armZ7eFdQSDX3HpYYCN120YyYgj"
"sCMQyqkQwJOnysUys2bBIDKntrebpMX9ExGQrT4Nn0STkDZosZHxtXxax5LT62G7bqE8/1"
"56SYyaOs+8atexf+wbFdxuQqH8qwvCzWfZ5NMvrnOKGahbrsm+vhP79JmCSfb28+hcVjmt"
"vl59sLZeofokWYNfWdFdSfeAokNQlH2Ftm1MdE68bE9zx0el8eRvfngGqBj/jh6vpieA4c"
"tJzoj3hMjBWCRmBolTUgT4oYkCdiA/IkY0CiJVmGtCe4LmOIJ4RaM8Xr5CDPTk4LIElKCa"
"Fk95JYBou6ttDx1CxF6GYlpUS1GWY3q78UVUo4onVqKJIa6UbKAbWjsZ72Z3UWxa1WO6e7"
"VLfe2zfQukOHNGqfUXLoGi4te93jmGWxu/08a4wxNEtaELVghSkjq2kjizzEhbiUnRUTUa"
"aWMrU4ys3hmlrKq3oQDZuhhffmwCrQipmlpYO6cbsOrL2B1qQDq1FuPvDb8Pj4yKWTw8HH"
"3Uf1any/9qwX7Ld62sCyISnsuIww+U1phi1rhnH0M4iKKZmUmCwaYoovPP1QhDA8/SBmDO"
"m9VMyJvjYtndM9f3y4vRFEnEQiaY0BGS74DzCR020fJA8/+r35Kndau071XlpBWuUmExJ6"
"Ls3IJqVqIQ+3TwB19tTBuwIddZBWrKJ+Sm8lgSRrtOtx2BcxiJFEe6O9R1YjTGGqC8j6Y8"
"xeLPuJaAOu9QQ5fKF4TUrLvRbqNY6dCXUHah52ESfSMd/QSom2Y2m1p6N3ybAKccg1mQ2q"
"8ptaoBeUDrrkiavwy9SUTcEJDKsktFdYQOLFRVJwItyYSnG8w2zNwom+PT15+/7th8G7tx"
"/6dKjTiTu88j4H7CxmK+po0Dmzi5j4jIm0uNA1Yxg0Qnsy69fRn0sP8qSgGt4p1RY7LxWB"
"TYsqaBVRf4B8riLqD7Rhs4HsKqplR9OK0a0loYvLvBbMVCRQg5FAmQ7ZKefRvuCLD7PqEV"
"TwGeJdw6cCr9DoGTZnDEoYQ5WARexM28C23aOmRa1Vt1/N9r16G0t/JxfaBZoLuQTuusBh"
"EYIuv5v3bEfFyGcRvjs9HQzenx4P3n04e/v+/dmH4w2dkL2VxytcXH2iRkVCGQqtDLFrTR"
"q+phawa6RslAOtOQeaMoUPwmLKmsJ0lSmn7kcSStkPl9FddXwZd06n9dOoX3QpuOn26uPl"
"g6uzd8loZNHNXHXMQlNDo37sgkHtvTuIpwRSQOsHpjVH+Bws9ZUDiP4FvrKavoKVbutL4F"
"oAuQ64++lyBIh1hMhn2Ue9VMPsXOEjfsQPLumWU4AwuAu6OHiDLResbMuAjgNY3P76G+BY"
"QPefQWrXXUBqt0mFFiZ/4COemB78HyINMSBzgmUDHU/BDGHkLCArRV/JooomkUSmCZ51E9"
"Hp3WGftUNYvxO2VJnQgxpTerUbu1FjlJFY11w9GVALe0kZaDOCKpQrMrH/WCEyuiroSklJ"
"FZyw7+AEpfQejtKbUeL2o48wHlKwxS5fC6FucLWpTv7QadqOZXOTxmXkXGkb2cW+0h3nxb"
"K5jCTW7bWI9omk0iEP60DH7+quYC7NGHA4m4mXQHZxdTO8/4UfrnPB4XwufhmPhmk9Zqnz"
"AizFvXQjIKW+fVwsDUheHpBslC8xoBwypWvYW07KadgcURkj0IuAOhBjOuDkVgnSepSHlC"
"OqIA3HrQYx/byyMWQZWRVExgN3sdQNUbD/lgk1JSthjz0psg3lRLwN5SSzDcVn5LzJ79Dg"
"2GZiQNNyEoLZiCJFrKWFtrKtZzQtN6VmBFuMfTYtw4+3rid5VZFp9Uw8rZ5lplVzqq+0uW"
"15K068hdgrmRJr3zPZ+/W3Zowm5ZlUJI0K0n1lDbsJuypIvvEDKWvM191l8z4/PXU8s1Z1"
"LJLJvLrX8wtBoVKWJ8wL3TAsb+e4zhGta+hXJTEkjrGAU4+Yo1o9yf4fwvoOINO/TT7vd2"
"uy67Ah1fxoTeSdSSfQRNRsmSFz195xQaq682uSuGOokw+acvixSbXH8fiNQrZc7PJjk3vB"
"mCNWHQhmPPK/q1MPFIvOCXIyZsOKCshsIoccGl/kwClgLwVYwRfkLsDg+NupvgY2pOXJyx"
"yBzwg/OTQQ6XJhW0v948Ujnlk2eIYGqQg4ULeNBTD0lT5BJnLR7pFByjdZk28yJMh5gG5P"
"tM0FVgrnTyOkWgV+cldqcu9u3rNCXrSzHC/aWdaLNiOzCLUvST8ulXooLScnpI30TWKeCw"
"HNOUTFkhHOthNdTLlhqvmUVSgjJ1klCTlVKOhvYk3XWtnThRJCkpwy0fawYBgt3GWprDoJ"
"IQUsP58407LJE8mrldTcOKKSgKxOv1YOiKqeJbkC9NViLW7JgPcuuYMxKaV2MYaAZDEsvZ"
"NRXhdCP7WdMdlJurSlMYGxiFiMtcEWfjHhPCrKMwYylCacoblnM14YUJbv6np4Ry9jyOgu"
"R8Q6lqghxkFupGw4pXQjeUefhYTYsNcr0tFAGN7dj9UBHOi65PFO/xFTdjOsxk9OfAQevN"
"XKorscl6SLoZUJwxIOWEGbnTmoOMqOcJRBpy1PB2UEFVMZMhbIWZn6Wiu7LSUtp2yH2Amg"
"K21hOaXY34SQLOxaW2DS+TkLpjjtUFymvZw433032AHKmlPiMAyqbDfLCKrOmOqMwo1no1"
"APEXNamRpkQbdtYgsRQ5sdwZGFOXdPSkKuxf0om4W/w9tRTN1xNWjbFifMX9xlk1KSLPOt"
"87CKLzxMvlBFoh9Ew0p1rqMUvGHnDnbcF1m4w8mOKWKjjhBuubBMxkytsZ9qjXfIWGkgHk"
"htD5vKJIKkcfo4hoyIQU6Ct41ETjXcdh55TG4/OYDK0RR0dNJxWOQpy9rGaFcYJ4qzPHLp"
"GiiPfE1hJn8dQHVaX5iuvURmuer7F4OMeHC6CY0lM2HAHM9IhZ4NAf26J5qSz7WA47PHAB"
"HbDy4hqd/0K6YCTGsGC/KjSYorFrkjLDKzaFinrRKulZWuQbfrloXUIVWukCuYNUkYiezx"
"xomQK+SJvspjz0IMBN5gIYAZuVeJHq0eGh47MzZYJjhKjBBEkfirxJINyQCF6jN0ugI1Se"
"95klZMymEyKSoO68DisJoOapA/CutHazJ0XbhccWOwYnf7ecbz79aETGmsoMrtKr9VaZg6"
"WpY/1D0l1i60+5oHE7SfgPHLz+3vtbwNrRcQQxSPHbSAZIxCkf2Sp+LtkqfZbI5lnbvKr5"
"vv12UHYFRSWpOSSmntmF83OLWkSsumRJVhuWfDkihW6Bna65IWSEpMmSAbRGqwQYgO/DFW"
"W2cB3GqEpHpJx6yQDcZ8MyTeBPl2SPCZqOgpVx89m4oD28MAsd0Y7vofAENSB7Ah02mBvp"
"yguWd5DiDqB40nNUH0mKxLr54quQeePpF+RG/SL/XbMTiwbKeTT5WVVMFKCpuiqK4flpcl"
"TrZpDT/qwkURjCTkxHBQJIP4QJxBfJDJIC4691KMofDcS0kwbCbHkSSGu39+Yoet9r0zR5"
"2yP0pQRwyBalHXCUllwu3ZhFO8ldqPUOfid7i8lfKiH0TDVs+MH/cYVg+RTvoou9fQe4mO"
"ziT75rAqvITgYmqFn45cOXqlpjB8ArDKFJyUVFrnnrVOh9GGJd0GCaFXaHPZ0EArBHmRSj"
"n8SVxIUSjxo0wwhoKDVkfYW2bcLakUjhvxPYPao8PxYfhpdA7CkzYf8eh6ePX5HGyOhu0A"
"0+Jnti9jacZEZOm4rQdJtJ/s/CATB0XaYoVwk5SsnPbdQWkXOfT49ql9L1R5YNP4Oz+P7k"
"Y3H69uPmX1715w5xwE3Pojvru/vSQLgH9xEyn3iL/cXI7ux8Orm3NAVHto0+2Xj/hhdDM+"
"Bw6ZVh/x92SNGH2kr4NI733El0Mi8pldMigyZnC28P4XD6LEeLYNg9aq0qjJGlps2PvNg4"
"9ugu6ealJ6mQBrYbJufyRN8ss5mJIWWT/in0ejn+ivLxA+0d+vb2/GP9ALS7IyLsx1NxrH"
"c6BGui5vbc/NF5SQU+dXc8JJQ+qiND0fF5RkkVc0vWJzFU2vGla42S0cdpOy0aYZwdcSbx"
"pHb2XDZ2R5jmYZoT5SEkZxDTVScd3dN5MTvBt1sCyerzArU2a87R65m9uPs6DfYji2yD8F"
"Ia9w0uye+u1W7MWDNNEID6MxuPny+XOqFUSgY6Jt5gIe+j9D4Bs53bdDh9qW9G6GB/dynJ"
"qxM33FvszE+cHKhSm1C7NsUmiVCzqbNz/2ZhkkxZZxSkzZxVy7eGVbQWhKUVwjCVn6aNuY"
"KsetctxK67iFmH45Z/nPJXdjUioVfAJPliKL7mopT1ClRFUUUxeSH5bfi5MSk0QRSZ84Xm"
"CeOcs7bzw9z6gjEhrTP5Sv4yAoceXrONCG7aavo1PzolzsfHeoyn4pcl7AC7eWV+MCmujO"
"tmaIoZghS+O3+3mE6YQU1FZ+yQZI0183R3CwB/nGmkpo0TaVGgO/hPadlFLEShLOnY7Yy9"
"QgC7ptK+QMKOE5SFv6rvgwpK7aO211XhvOyOyxEOVzKNqDM9VIAnPbvdino3ywKhkh3AoU"
"q9UFVksRMU2MGMWl18ulK2LrIPgPRWwdaMOqsz/V2Z9dPvvTpgcQeraOd03qQpmp+01lci"
"HbaGKXFDACUi8J3RZeL9VodVN7q4hgZI+beA7C0AnzTsTcvTaCeKqlbXL2u0Fafm7Za8UK"
"7ocVTDVaKX4lJSsLh9UCySLo8UXRFYi3uBl3h87aHrxlI4RTYpKY3m1xgpuZuOw0EBeUsY"
"ueFInQORFH6JxkInReV+j62XGxEKe8GKdsFhzkluqJYXk1qKO9SGi+mFj2wrJKLT5pOYXo"
"pk96iCo8WTB/fLi9EXTLSCTNsCDDBf8BJnIas4JieujEQ6aLsHNEn9eQKkpRyCeC05xvSm"
"elFWR3WSADasSG4SV3EHfilJiUfbj+dUm+dD/8/QAXo9HNOZhAiB/xz8ObsTa+1cb3NP8L"
"sXWJlaq5dkdSvpAe+KSxD8yiLjxpOylU6XztPfTdmk/YdgzL5p2qDQ201E0+cBuZ9FzrCx"
"0Fwp3ELwecj6PLq+vh5zdn/dOUoyfsk2+zKqjl8k53F7slNwKSzJVteyRdfc7BU7zyh+XV"
"sl9l2Z9B16h48mFCUrm1uuXWUn7og2jXrB86cAaUTpUTl1Jey7hPZUfHZSoEvLP4FcjUEu"
"8jXTrccmi7aOY71DLOus29fp6bTg9KFTzRcghICc9wPRtOgWtZJiAdi7QFeMP2wBOJiTed"
"Q7cPqDb36J0en777hpQhbUglEAbuAoLwoWClY2hmD7ls7Ckqq0o780s/x+nnBoFwRYmUsL"
"yc1HQjNODrOh703dsCCL57KwSQ3kpZUsjlrXA5fTAUkBPBZlL7kAFexhwNy7dvjjamZTVi"
"eDqWZ1PmGM7KoJuU2gnjTjEpjUD8DG2Hm5FKSIjGJCqxoZX67ckO479mLlRZzQdqNavo7Y"
"No2CzNZWE6Z7FY2LKJCbKizfIinUkcHAcwSPNVErukVKMZHTqJmto0oDYNtLRpQDTb1YDe"
"Zaq6rk5x21OIZKfy7WAKTzLZCcc6k0fvCc3k5N6pbCy/YH0yNAzL42/ciN/u59HBa1JQ0/"
"2SHUthLWZn6oxNbzo7fJKYGbwrwMsM0lpkRMvQWyqHdcNEF8OgBIRheTkhrD9bJMHAd9dk"
"QczdS5+Qa3E3fWNaYZ276U3LKZ2cIBJSaGZC1bJY5keqVR7hnaJXm0mZpJs69+yR3NDJmN"
"TOwZPd6qlh9OTJWX9QOHyS4KvbNPtfJTA50gpU+uq7wcqVV8Bu0kORpxio2tHM/BpUhqk9"
"Z5jyTdyymkZMSp0Wy+H79kcSXIZbZQUsweZ+fytNEOy6RU2kbVVEQdtEQbiHWpvblrcqmS"
"6AK9yU/SsF0IqBqZGBWaDplJclNHcNioSUtZtAkzwN2ljnBGHm4hkXU4u6IhAaJBDmlm5q"
"ZWnXhJAkwDa9+dfHRLe55GuupZuSPMztrKVM3Bgi2tLCpMWyiJLv3wpnJJxn3EoHKTFWM2"
"d/KtPxEE3HT1TH722xH/1C/aJGZGA5KEtSGgNHuZyVwdPkgqIMni5Dqtb2A1vbFyxRlWhR"
"X4RprLat5qxg0b3EqxXE028tbK6Bac2BNQMvC90FUDcWwFljA/jVTft+FhEwWYMpms1IYw"
"F9TtrHcdk+3yWiR1lwdhHXW/8jfsS/3Awv/u6A4d0VWOgOwBZYIMcl6ksfOBYpjBxA/lAh"
"9lTqzrKn9MFwilwHvIFH8yOgk4p0x0FzvITYBXOLPnBmW0vwt7fHx8C1wN9oOsIjcOUCh9"
"gMRFD332SGbPJO9NWb2M18gebC/U9cfYez9SlQXfaq7vhbn747PR0M3p8eD959OHv7/v3Z"
"h+PNHqjsrbzNUBdXn+hwTQzrcEIUK0G0kSr5QROC7eyqqbsBDmVTTdYb6kD7GdraE7lvwi"
"kvRjpvEPGk29tNKM+oih/C4yJ3XZoKTInJ6YgqQgYOxGTgIENgBaiUM3QTQlICWX/6AN3g"
"7zMRoxhJyGnk1p8IlHwaLJUGZCMgJ4LNpKiO1O0kjDnpgCMRlYghmyWgQybZNXMTCCyy69"
"CHsMUgY74Gxao2vsjUNUGKDYrSLqe63Eztr9/l3EzK916r7x1hw+Lx97ne4khIBUOTNYDt"
"WuKxo/kbIGJiCkZfaX7mHsKRC2NcTMFI8+dpE6hV7JNZYQUp6WJzqFkzqlxBTu8U8j9psV"
"eaV1/5jnoH5Ttitsi2fQXJQv1ihkvZLQa9O2h/yyRBGFcC9CXLZXAEvn4Np7GvX6mXhvmC"
"qEOHlDVNBzz2hswpA6ePvawzqdaalUG11aDKnVM2M/N7oUFFbymDqpJBtQnJqrgHRGKuuP"
"7dH8oWULZAd2BUO/NrAFHpr4elv97pawhFeqt/c7u+uqLlFNEuzQqtwpcb9EuSp2FnBu0w"
"k1xJTVIgLglh37RGqZafw1p+HrwJ6/BBjIhgHUqV2r4gOQmBgvzJZ4ShQ+NVdeCsCGIgVs"
"U/WAjqSrchdv/uRDQIogTHAy3N401qqVGti61FOeWc2xM1XJX5PCEpJTnQQCDZMszIWsam"
"3QgpY4zm5F1aZbz/YXlJlIm2vf/MjCk5vOMyksDatI62Rwq19ezZTWOpjAllTChjoqgxsc"
"bGg6u7Qj4rKlDAhCBlNWdTeC+8lu8IKTnmE0JKm+dp8wFEZcmulJiUs2gjO/Y7sjNsb9FB"
"LW8MY9liK++xzEqrPLN7zjPLmmRGPtNfdSq2aboC1axdaFZI98+XMdKTUpIsMm2Y6h3SNM"
"fbOetxKcK6NFut/Kid1jDDaLii4XVh+QOPrlOcr+J897+QcE4aKUVURiJyhj3Un2qVrG62"
"9VyaSouLqbRtCURnpj6nSPF0R3HXTEpJMuQb75yVqPMaGPN9+2zrZ8w76iGTDEXlIFMOMi"
"nARcuVZbt+eG9ptpwrLCWwjYSJhh2uLKxpOSkRbcQLUa2bqv4pSK8UrjZlAc0ISolpIz1U"
"BRz0dreSOkIDD6GNDG6KquBOP4/01aMynWF6Dyj/7Y7pGsS87jO0nZLpD2MiktJFTUyFdG"
"iU0Xr84nICeFJofT7JWZ9POOuzhV3I49Jz0h9GIir9YcfSH/71XxWPG/0="
)