[{"data":1,"prerenderedAt":1524},["ShallowReactive",2],{"navigation_docs":3,"-docs-architecture-messaging-and-queueing":158,"-docs-architecture-messaging-and-queueing-surround":1519},[4,15,74,98,113],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":6},"Getting Started",false,"\u002Fdocs\u002Fgetting-started","docs\u002F1.getting-started",[10],{"title":11,"path":12,"stem":13,"icon":14},"Zeeq Introduction","\u002Fdocs\u002Fgetting-started\u002Fintroduction","docs\u002F1.getting-started\u002F1.introduction","i-lucide-house",{"title":16,"path":17,"stem":18,"children":19,"page":6},"Key Features","\u002Fdocs\u002Fkey-features","docs\u002F3.key-features",[20,25,30,35,40,45,50,55,59,64,69],{"title":21,"path":22,"stem":23,"icon":24},"Membership Invitations","\u002Fdocs\u002Fkey-features\u002Fmembership-invitations","docs\u002F3.key-features\u002F1.membership-invitations","i-hugeicons-cube",{"title":26,"path":27,"stem":28,"icon":29},"Command Palette","\u002Fdocs\u002Fkey-features\u002Fcommand-palette","docs\u002F3.key-features\u002F10.command-palette","i-hugeicons-search-01",{"title":31,"path":32,"stem":33,"icon":34},"Library Import and Export","\u002Fdocs\u002Fkey-features\u002Flibrary-import-export","docs\u002F3.key-features\u002F11.library-import-export","i-lucide-package-open",{"title":36,"path":37,"stem":38,"icon":39},"LLM Settings Configuration","\u002Fdocs\u002Fkey-features\u002Fllm-settings-configuration","docs\u002F3.key-features\u002F2.llm-settings-configuration","i-hugeicons-ai-magic",{"title":41,"path":42,"stem":43,"icon":44},"Agent Code Review Findings","\u002Fdocs\u002Fkey-features\u002Fagent-code-review-findings","docs\u002F3.key-features\u002F3.agent-code-review-findings","i-lucide-list-checks",{"title":46,"path":47,"stem":48,"icon":49},"Credentials & API Tokens","\u002Fdocs\u002Fkey-features\u002Fcredentials","docs\u002F3.key-features\u002F4.credentials","i-hugeicons-key-01",{"title":51,"path":52,"stem":53,"icon":54},"Manual Document Libraries","\u002Fdocs\u002Fkey-features\u002Fmanual-document-libraries","docs\u002F3.key-features\u002F5.manual-document-libraries","i-lucide-library-big",{"title":56,"path":57,"stem":58,"icon":29},"Snippet Search","\u002Fdocs\u002Fkey-features\u002Fsnippet-search","docs\u002F3.key-features\u002F6.snippet-search",{"title":60,"path":61,"stem":62,"icon":63},"Shared Prompt Fragment","\u002Fdocs\u002Fkey-features\u002Fshared-prompt-fragment","docs\u002F3.key-features\u002F7.shared-prompt-fragment","i-lucide-notebook-pen",{"title":65,"path":66,"stem":67,"icon":68},"Code Review Sources & Telemetry","\u002Fdocs\u002Fkey-features\u002Fcode-review-sources","docs\u002F3.key-features\u002F8.code-review-sources","i-hugeicons-book-01",{"title":70,"path":71,"stem":72,"icon":73},"Excluding Documents from Code Reviews","\u002Fdocs\u002Fkey-features\u002Fcode-review-document-exclusion","docs\u002F3.key-features\u002F9.code-review-document-exclusion","i-lucide-eye-off",{"title":75,"path":76,"stem":77,"children":78,"page":6},"Runtime Configuration","\u002Fdocs\u002Fconfiguration","docs\u002F5.configuration",[79,84,89,93],{"title":80,"path":81,"stem":82,"icon":83},"Google Cloud Configuration","\u002Fdocs\u002Fconfiguration\u002Fgcp-runtime","docs\u002F5.configuration\u002F1.gcp-runtime","i-hugeicons-google",{"title":85,"path":86,"stem":87,"icon":88},"GitHub Configuration","\u002Fdocs\u002Fconfiguration\u002Fgithub-configuration","docs\u002F5.configuration\u002F2.github-configuration","i-hugeicons-github",{"title":90,"path":91,"stem":92},"Local OpenIddict Certificates","\u002Fdocs\u002Fconfiguration\u002Flocal-openiddict-certificates","docs\u002F5.configuration\u002F3.local-openiddict-certificates",{"title":94,"path":95,"stem":96,"icon":97},"System Admin Access","\u002Fdocs\u002Fconfiguration\u002Fsystem-admin-access","docs\u002F5.configuration\u002F4.system-admin-access","i-hugeicons-shield-01",{"title":99,"path":100,"stem":101,"children":102,"page":6},"Metrics","\u002Fdocs\u002Fmetrics","docs\u002F6.metrics",[103,108],{"title":104,"path":105,"stem":106,"icon":107},"Metrics Dashboard","\u002Fdocs\u002Fmetrics\u002Fmetrics-dashboard","docs\u002F6.metrics\u002F1.metrics-dashboard","i-hugeicons-analytics-01",{"title":109,"path":110,"stem":111,"icon":112},"Metrics Pipeline","\u002Fdocs\u002Fmetrics\u002Fmetrics-pipeline","docs\u002F6.metrics\u002F2.metrics-pipeline","i-hugeicons-database-02",{"title":114,"path":115,"stem":116,"children":117,"page":6},"Design and Architecture","\u002Fdocs\u002Farchitecture","docs\u002F9.architecture",[118,123,128,133,137,141,145,150,154],{"title":119,"path":120,"stem":121,"icon":122},"Caching","\u002Fdocs\u002Farchitecture\u002Fcaching","docs\u002F9.architecture\u002F1.caching","i-lucide-database",{"title":124,"path":125,"stem":126,"icon":127},"Messaging and Queueing","\u002Fdocs\u002Farchitecture\u002Fmessaging-and-queueing","docs\u002F9.architecture\u002F2.messaging-and-queueing","i-lucide-message-square",{"title":129,"path":130,"stem":131,"icon":132},"GitHub Comment Rendering","\u002Fdocs\u002Farchitecture\u002Fgithub-comment-rendering","docs\u002F9.architecture\u002F3.github-comment-rendering","i-lucide-github",{"title":134,"path":135,"stem":136},"GitHub Integration","\u002Fdocs\u002Farchitecture\u002Fgithub-integration","docs\u002F9.architecture\u002F4.github-integration",{"title":138,"path":139,"stem":140},"LLM Settings and Secret Storage","\u002Fdocs\u002Farchitecture\u002Fllm-settings-and-secret-storage","docs\u002F9.architecture\u002F5.llm-settings-and-secret-storage",{"title":142,"path":143,"stem":144},"Organization Activation and Routes","\u002Fdocs\u002Farchitecture\u002Forganization-activation-and-routes","docs\u002F9.architecture\u002F6.organization-activation-and-routes",{"title":146,"path":147,"stem":148,"icon":149},"MCP Uploaded-Diff Code Reviews","\u002Fdocs\u002Farchitecture\u002Fmcp-uploaded-diff-code-reviews","docs\u002F9.architecture\u002F7.mcp-uploaded-diff-code-reviews","i-lucide-file-search",{"title":151,"path":152,"stem":153},"Repository Content Ingest","\u002Fdocs\u002Farchitecture\u002Frepository-content-ingest","docs\u002F9.architecture\u002F8.repository-content-ingest",{"title":155,"path":156,"stem":157},"Telemetry Ingest","\u002Fdocs\u002Farchitecture\u002Ftelemetry-ingest","docs\u002F9.architecture\u002F9.telemetry-ingest",{"id":159,"title":124,"body":160,"description":1512,"extension":1513,"links":1514,"meta":1515,"navigation":1516,"path":125,"seo":1517,"stem":126,"__hash__":1518},"docs\u002Fdocs\u002F9.architecture\u002F2.messaging-and-queueing.md",{"type":161,"value":162,"toc":1493},"minimark",[163,168,172,175,186,190,193,196,199,203,294,298,308,311,317,320,324,327,334,340,343,353,356,360,363,393,396,440,444,451,494,518,524,583,593,763,791,798,804,807,811,822,825,871,881,888,892,897,900,965,969,972,1084,1088,1091,1191,1194,1198,1204,1251,1254,1333,1338,1368,1372,1378,1381,1385,1388,1391,1398,1401,1432,1447,1451,1489],[164,165,167],"h2",{"id":166},"summary","Summary",[169,170,171],"p",{},"Zeeq uses a PostgreSQL-backed message queue for background work. The goal is to keep HTTP requests fast while giving longer-running work a reliable place to run out-of-band.",[169,173,174],{},"In practice, this means webhook ingestion, retryable jobs, scheduled work, and other background tasks can be handed off to a worker instead of blocking the web request that triggered them.",[169,176,177,178,185],{},"Zeeq uses ",[179,180,184],"a",{"href":181,"rel":182},"https:\u002F\u002Fgithub.com\u002FBrighterCommand\u002FBrighter",[183],"nofollow","Brighter"," for the command processor, message pump, handler pipeline, retry behavior, and PostgreSQL transport.",[164,187,189],{"id":188},"why-this-layer-exists","Why this layer exists",[169,191,192],{},"Brighter is a flexible library, but most feature code should not need to know about Brighter publications, subscriptions, queue tables, or routing keys. Those details are important, but they are platform concerns rather than product concerns.",[169,194,195],{},"Zeeq adds a small platform layer over Brighter so feature code can express intent: this message can be published, this handler consumes that message, this message belongs to a tenant, and this work is default, priority, or low-priority.",[169,197,198],{},"The runtime turns those declarations into concrete Brighter and PostgreSQL registrations. This keeps feature code easier to read and gives the platform one place to control queue layout, retry behavior, dead-letter storage, and worker configuration.",[164,200,202],{"id":201},"key-pieces","Key pieces",[204,205,206,219],"table",{},[207,208,209],"thead",{},[210,211,212,216],"tr",{},[213,214,215],"th",{},"Piece",[213,217,218],{},"What it does",[220,221,222,234,244,254,264,274,284],"tbody",{},[210,223,224,231],{},[225,226,227],"td",{},[228,229,230],"code",{},"ConfigurePublisher",[225,232,233],{},"Declares the logical topic for a message type.",[210,235,236,241],{},[225,237,238],{},[228,239,240],{},"ConfigureConsumer",[225,242,243],{},"Declares the handler and channel for a message type.",[210,245,246,251],{},[225,247,248],{},[228,249,250],{},"IZeeqMessagePublisher",[225,252,253],{},"Publishes messages after Zeeq resolves the concrete route.",[210,255,256,261],{},[225,257,258],{},[228,259,260],{},"ZeeqMessageHandler\u003CT>",[225,262,263],{},"Base handler that applies retry and dead-letter behavior.",[210,265,266,271],{},[225,267,268],{},[228,269,270],{},"ITenantMessage",[225,272,273],{},"Marks work that belongs to an organization.",[210,275,276,281],{},[225,277,278],{},[228,279,280],{},"ISystemMessage",[225,282,283],{},"Marks platform work that does not belong to a tenant.",[210,285,286,291],{},[225,287,288],{},[228,289,290],{},"ITenantTierResolver",[225,292,293],{},"Resolves an organization into the queue tier used for routing.",[164,295,297],{"id":296},"publish-flow","Publish flow",[169,299,300,301,304,305,307],{},"At a high level, feature code creates a message and calls ",[228,302,303],{},"IZeeqMessagePublisher.PublishAsync",". Zeeq reads the logical topic from ",[228,306,230],{},", resolves the tenant tier and stable bucket for tenant messages, and then asks Brighter to write the message to the configured PostgreSQL queue table.",[169,309,310],{},"System messages skip tenant routing and use a fixed system route.",[169,312,313,314,316],{},"A worker later reads the message and dispatches it to the matching ",[228,315,260],{},". If handling fails repeatedly, the shared retry pipeline gives up and Zeeq writes the message to the dead-letter table.",[169,318,319],{},"The feature code does not choose a queue table or build the final routing key (handled by internal platform routing).",[164,321,323],{"id":322},"tenant-routing","Tenant routing",[169,325,326],{},"Tenant routing has two separate ideas that are easy to conflate.",[169,328,329,333],{},[330,331,332],"strong",{},"Tenant tier"," decides where an organization's work is routed.",[169,335,336,339],{},[330,337,338],{},"Message priority"," decides the default processing settings for that class of work.",[169,341,342],{},"For tenant messages, Zeeq resolves the organization's tier and hashes the organization id into a stable bucket inside that tier.",[344,345,351],"pre",{"className":346,"code":348,"language":349,"meta":350},[347],"language-text","reports.refresh + org_123 + Default tier\n  -> stable bucket 03 inside the default tier\n  -> reports.refresh.default.03\n","text","",[228,352,348],{"__ignoreMap":350},[169,354,355],{},"This gives each organization a predictable route while still spreading organizations across queues. Priority tenants can get more capacity, low-tier tenants can get less capacity, and one noisy tenant is less likely to consume all worker capacity for the tier.",[164,357,359],{"id":358},"worker-modes","Worker modes",[169,361,362],{},"Zeeq can run consumers in two shapes.",[204,364,365,375],{},[207,366,367],{},[210,368,369,372],{},[213,370,371],{},"Mode",[213,373,374],{},"Behavior",[220,376,377,385],{},[210,378,379,382],{},[225,380,381],{},"Web with inline consumers",[225,383,384],{},"The web process registers producers and consumers. This is convenient for local development.",[210,386,387,390],{},[225,388,389],{},"Web plus standalone worker",[225,391,392],{},"The web process registers producers only, and a separate worker process registers producers and consumers. This matches production more closely and lets handlers publish follow-up messages.",[169,394,395],{},"The main switches are:",[204,397,398,408],{},[207,399,400],{},[210,401,402,405],{},[213,403,404],{},"Setting",[213,406,407],{},"Purpose",[220,409,410,420,430],{},[210,411,412,417],{},[225,413,414],{},[228,415,416],{},"ZEEQ_RUN_MODE=worker",[225,418,419],{},"Starts the process as a standalone worker.",[210,421,422,427],{},[225,423,424],{},[228,425,426],{},"ZEEQ_MESSAGING_ROLE=producer",[225,428,429],{},"Keeps the web process producer-only.",[210,431,432,437],{},[225,433,434],{},[228,435,436],{},"ZEEQ_MESSAGING_ROLE=producer-consumer",[225,438,439],{},"Registers both producers and consumers for local inline mode or the standalone worker.",[164,441,443],{"id":442},"configuration","Configuration",[169,445,446,447,450],{},"The main configuration section is ",[228,448,449],{},"ZeeqMessaging",".",[204,452,453,462],{},[207,454,455],{},[210,456,457,459],{},[213,458,404],{},[213,460,461],{},"What it controls",[220,463,464,474,484],{},[210,465,466,471],{},[225,467,468],{},[228,469,470],{},"Defaults",[225,472,473],{},"Baseline buffer size, performers, visibility timeout, and empty-poll delay.",[210,475,476,481],{},[225,477,478],{},[228,479,480],{},"TopicOverrides",[225,482,483],{},"Per-topic overrides for one logical message topic.",[210,485,486,491],{},[225,487,488],{},[228,489,490],{},"TenantBuckets",[225,492,493],{},"Bucket counts for priority, default, and low organization tiers.",[169,495,496,499,500,503,504,507,508,511,512,514,515,517],{},[228,497,498],{},"PriorityDefaults"," also exists in code and is keyed by approved priority marker types such as ",[228,501,502],{},"PriorityMessage",", ",[228,505,506],{},"DefaultMessage",", and ",[228,509,510],{},"LowPriorityMessage",". Use ",[228,513,498],{}," when an entire class of work should run faster or slower. Use ",[228,516,480],{}," when one topic needs different behavior from the rest of its priority class.",[169,519,520,521,450],{},"PostgreSQL-specific settings live under ",[228,522,523],{},"ZeeqMessaging:Postgres",[204,525,526,534],{},[207,527,528],{},[210,529,530,532],{},[213,531,404],{},[213,533,461],{},[220,535,536,546,556,566],{},[210,537,538,543],{},[225,539,540],{},[228,541,542],{},"SchemaName",[225,544,545],{},"PostgreSQL schema that owns queue tables.",[210,547,548,553],{},[225,549,550],{},[228,551,552],{},"QueueTables",[225,554,555],{},"Table names for priority, default, low, and system queues.",[210,557,558,563],{},[225,559,560],{},[228,561,562],{},"DeadLetterTable",[225,564,565],{},"Table used for failed messages after retries are exhausted.",[210,567,568,573],{},[225,569,570],{},[228,571,572],{},"BinaryMessagePayload",[225,574,575,576,579,580,450],{},"Whether Brighter stores message payloads as ",[228,577,578],{},"jsonb"," instead of ",[228,581,582],{},"json",[169,584,585,586,589,590,450],{},"Google Cloud Pub\u002FSub is selected with ",[228,587,588],{},"ZeeqMessaging:Provider = GcpPubSub",".\nPostgres remains the default provider when the setting is omitted or set to\n",[228,591,592],{},"Postgres",[344,594,597],{"className":595,"code":596,"language":582,"meta":350,"style":350},"language-json shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","{\n  \"ZeeqMessaging\": {\n    \"Provider\": \"GcpPubSub\",\n    \"GcpPubSub\": {\n      \"ProjectId\": \"zeeq-dev-local\",\n      \"MissingChannelPolicy\": \"Validate\",\n      \"SubscriptionMode\": \"Stream\",\n      \"UseEmulatorDetection\": true\n    }\n  }\n}\n",[228,598,599,608,626,652,665,688,709,730,745,751,757],{"__ignoreMap":350},[600,601,604],"span",{"class":602,"line":603},"line",1,[600,605,607],{"class":606},"sMK4o","{\n",[600,609,611,614,617,620,623],{"class":602,"line":610},2,[600,612,613],{"class":606},"  \"",[600,615,449],{"class":616},"spNyl",[600,618,619],{"class":606},"\"",[600,621,622],{"class":606},":",[600,624,625],{"class":606}," {\n",[600,627,629,632,636,638,640,643,647,649],{"class":602,"line":628},3,[600,630,631],{"class":606},"    \"",[600,633,635],{"class":634},"sBMFI","Provider",[600,637,619],{"class":606},[600,639,622],{"class":606},[600,641,642],{"class":606}," \"",[600,644,646],{"class":645},"sfazB","GcpPubSub",[600,648,619],{"class":606},[600,650,651],{"class":606},",\n",[600,653,655,657,659,661,663],{"class":602,"line":654},4,[600,656,631],{"class":606},[600,658,646],{"class":634},[600,660,619],{"class":606},[600,662,622],{"class":606},[600,664,625],{"class":606},[600,666,668,671,675,677,679,681,684,686],{"class":602,"line":667},5,[600,669,670],{"class":606},"      \"",[600,672,674],{"class":673},"sbssI","ProjectId",[600,676,619],{"class":606},[600,678,622],{"class":606},[600,680,642],{"class":606},[600,682,683],{"class":645},"zeeq-dev-local",[600,685,619],{"class":606},[600,687,651],{"class":606},[600,689,691,693,696,698,700,702,705,707],{"class":602,"line":690},6,[600,692,670],{"class":606},[600,694,695],{"class":673},"MissingChannelPolicy",[600,697,619],{"class":606},[600,699,622],{"class":606},[600,701,642],{"class":606},[600,703,704],{"class":645},"Validate",[600,706,619],{"class":606},[600,708,651],{"class":606},[600,710,712,714,717,719,721,723,726,728],{"class":602,"line":711},7,[600,713,670],{"class":606},[600,715,716],{"class":673},"SubscriptionMode",[600,718,619],{"class":606},[600,720,622],{"class":606},[600,722,642],{"class":606},[600,724,725],{"class":645},"Stream",[600,727,619],{"class":606},[600,729,651],{"class":606},[600,731,733,735,738,740,742],{"class":602,"line":732},8,[600,734,670],{"class":606},[600,736,737],{"class":673},"UseEmulatorDetection",[600,739,619],{"class":606},[600,741,622],{"class":606},[600,743,744],{"class":606}," true\n",[600,746,748],{"class":602,"line":747},9,[600,749,750],{"class":606},"    }\n",[600,752,754],{"class":602,"line":753},10,[600,755,756],{"class":606},"  }\n",[600,758,760],{"class":602,"line":759},11,[600,761,762],{"class":606},"}\n",[169,764,765,766,769,770,773,774,777,778,781,782,784,785,787,788,450],{},"In local Aspire runs, the Pub\u002FSub emulator exports ",[228,767,768],{},"PUBSUB_EMULATOR_HOST"," and\n",[228,771,772],{},"PUBSUB_PROJECT_ID"," to both ",[228,775,776],{},"zeeq-server"," and the split-mode ",[228,779,780],{},"zeeq-worker",".\nIf ",[228,783,674],{}," is not set in configuration, the runtime falls back to\n",[228,786,772],{}," and then ",[228,789,790],{},"GCP_PROJECT_ID",[169,792,793,794,797],{},"Use ",[228,795,796],{},"MissingChannelPolicy = Validate"," for steady-state local and production\nstartup. Validation makes the app fail fast when a topic or subscription is\nmissing, but it does not try to mutate Pub\u002FSub topology during normal process\nstartup.",[169,799,793,800,803],{},[228,801,802],{},"MissingChannelPolicy = Create"," only for explicit topology provisioning or\nexploratory emulator tests. Brighter's GCP Pub\u002FSub provider creates missing\ntopics\u002Fsubscriptions, but an already-provisioned emulator can reject Brighter's\ntopic update path, and production runtime mutation would require broader IAM\nthan steady-state publishing and subscribing need. Runtime services should be\ngranted publisher\u002Fsubscriber permissions for normal operation; topology creation\nshould be handled by deployment\u002Fbootstrap tooling.",[169,805,806],{},"Topic retention is intentionally not enabled. This accepts the low criticality\nmessage-loss tradeoff during initial provisioning or cutover rather than paying\nthe operational complexity and cost of retained undelivered messages.",[164,808,810],{"id":809},"immediate-github-acknowledgements","Immediate GitHub acknowledgements",[169,812,813,814,817,818,821],{},"GitHub webhooks produce a few user-visible acknowledgement tasks that should not wait behind normal workflow work. Zeeq routes these through ",[228,815,816],{},"ImmediateMessage",", which uses the shared ",[228,819,820],{},"messaging.brighter_messages_immediate"," queue table instead of tenant tier\u002Fbucket routing.",[169,823,824],{},"Current immediate GitHub topics:",[204,826,827,839],{},[207,828,829],{},[210,830,831,834,837],{},[213,832,833],{},"Topic",[213,835,836],{},"Message",[213,838,407],{},[220,840,841,856],{},[210,842,843,848,853],{},[225,844,845],{},[228,846,847],{},"github.comment.write",[225,849,850],{},[228,851,852],{},"GitHubCommentWriteRequested",[225,854,855],{},"Updates the Zeeq-owned PR comment through the DOM\u002Fanchor\u002Flease renderer.",[210,857,858,863,868],{},[225,859,860],{},[228,861,862],{},"github.comment.reaction",[225,864,865],{},[228,866,867],{},"GitHubCommentReactionRequested",[225,869,870],{},"Adds a lightweight reaction to a user-authored issue or review comment that starts with a supported Zeeq command.",[169,872,873,874,877,878,450],{},"The two topics have different safety models. Comment writes mutate a Zeeq-owned Markdown document and must use the comment lease so concurrent workers do not overwrite sections. Reaction writes are best-effort acknowledgement calls to GitHub's reaction API. They do not need a Zeeq dedupe table because GitHub treats repeated reactions by the same app\u002Fuser as idempotent: a new reaction returns ",[228,875,876],{},"201",", and an already-present reaction returns ",[228,879,880],{},"200",[169,882,883,884,887],{},"For reactions, validation failures such as GitHub ",[228,885,886],{},"422"," are logged and acknowledged as no-op. Transient provider failures still flow through the normal message retry and dead-letter policy.",[164,889,891],{"id":890},"common-scenarios","Common scenarios",[893,894,896],"h3",{"id":895},"slow-down-local-polling","Slow down local polling",[169,898,899],{},"Use this when local development does not need low-latency background work and you want fewer idle database polls.",[344,901,903],{"className":595,"code":902,"language":582,"meta":350,"style":350},"{\n  \"ZeeqMessaging\": {\n    \"Defaults\": {\n      \u002F\u002F Empty queues are checked less often, which lowers idle database traffic.\n      \"PollIntervalMilliseconds\": 5000\n    }\n  }\n}\n",[228,904,905,909,921,933,939,953,957,961],{"__ignoreMap":350},[600,906,907],{"class":602,"line":603},[600,908,607],{"class":606},[600,910,911,913,915,917,919],{"class":602,"line":610},[600,912,613],{"class":606},[600,914,449],{"class":616},[600,916,619],{"class":606},[600,918,622],{"class":606},[600,920,625],{"class":606},[600,922,923,925,927,929,931],{"class":602,"line":628},[600,924,631],{"class":606},[600,926,470],{"class":634},[600,928,619],{"class":606},[600,930,622],{"class":606},[600,932,625],{"class":606},[600,934,935],{"class":602,"line":654},[600,936,938],{"class":937},"sHwdD","      \u002F\u002F Empty queues are checked less often, which lowers idle database traffic.\n",[600,940,941,943,946,948,950],{"class":602,"line":667},[600,942,670],{"class":606},[600,944,945],{"class":673},"PollIntervalMilliseconds",[600,947,619],{"class":606},[600,949,622],{"class":606},[600,951,952],{"class":673}," 5000\n",[600,954,955],{"class":602,"line":690},[600,956,750],{"class":606},[600,958,959],{"class":602,"line":711},[600,960,756],{"class":606},[600,962,963],{"class":602,"line":732},[600,964,762],{"class":606},[893,966,968],{"id":967},"give-one-topic-a-longer-timeout","Give one topic a longer timeout",[169,970,971],{},"Use this when a specific handler normally takes longer than the default visibility timeout.",[344,973,975],{"className":595,"code":974,"language":582,"meta":350,"style":350},"{\n  \"ZeeqMessaging\": {\n    \"TopicOverrides\": {\n      \u002F\u002F This is the logical topic from ConfigurePublisher, not the final queue route.\n      \"reports.refresh\": {\n        \u002F\u002F Pull one expensive report refresh at a time.\n        \"BufferSize\": 1,\n        \u002F\u002F Keep the message invisible long enough for the handler to finish.\n        \"VisibleTimeoutSeconds\": 600\n      }\n    }\n  }\n}\n",[228,976,977,981,993,1005,1010,1023,1028,1046,1051,1065,1070,1074,1079],{"__ignoreMap":350},[600,978,979],{"class":602,"line":603},[600,980,607],{"class":606},[600,982,983,985,987,989,991],{"class":602,"line":610},[600,984,613],{"class":606},[600,986,449],{"class":616},[600,988,619],{"class":606},[600,990,622],{"class":606},[600,992,625],{"class":606},[600,994,995,997,999,1001,1003],{"class":602,"line":628},[600,996,631],{"class":606},[600,998,480],{"class":634},[600,1000,619],{"class":606},[600,1002,622],{"class":606},[600,1004,625],{"class":606},[600,1006,1007],{"class":602,"line":654},[600,1008,1009],{"class":937},"      \u002F\u002F This is the logical topic from ConfigurePublisher, not the final queue route.\n",[600,1011,1012,1014,1017,1019,1021],{"class":602,"line":667},[600,1013,670],{"class":606},[600,1015,1016],{"class":673},"reports.refresh",[600,1018,619],{"class":606},[600,1020,622],{"class":606},[600,1022,625],{"class":606},[600,1024,1025],{"class":602,"line":690},[600,1026,1027],{"class":937},"        \u002F\u002F Pull one expensive report refresh at a time.\n",[600,1029,1030,1033,1037,1039,1041,1044],{"class":602,"line":711},[600,1031,1032],{"class":606},"        \"",[600,1034,1036],{"class":1035},"swJcz","BufferSize",[600,1038,619],{"class":606},[600,1040,622],{"class":606},[600,1042,1043],{"class":673}," 1",[600,1045,651],{"class":606},[600,1047,1048],{"class":602,"line":732},[600,1049,1050],{"class":937},"        \u002F\u002F Keep the message invisible long enough for the handler to finish.\n",[600,1052,1053,1055,1058,1060,1062],{"class":602,"line":747},[600,1054,1032],{"class":606},[600,1056,1057],{"class":1035},"VisibleTimeoutSeconds",[600,1059,619],{"class":606},[600,1061,622],{"class":606},[600,1063,1064],{"class":673}," 600\n",[600,1066,1067],{"class":602,"line":753},[600,1068,1069],{"class":606},"      }\n",[600,1071,1072],{"class":602,"line":759},[600,1073,750],{"class":606},[600,1075,1077],{"class":602,"line":1076},12,[600,1078,756],{"class":606},[600,1080,1082],{"class":602,"line":1081},13,[600,1083,762],{"class":606},[893,1085,1087],{"id":1086},"change-tenant-bucket-allocation","Change tenant bucket allocation",[169,1089,1090],{},"Use this when the relative capacity between organization tiers changes.",[344,1092,1094],{"className":595,"code":1093,"language":582,"meta":350,"style":350},"{\n  \"ZeeqMessaging\": {\n    \"TenantBuckets\": {\n      \u002F\u002F More buckets means more independent routes for tenants in that tier.\n      \"PriorityBucketCount\": 8,\n      \"DefaultBucketCount\": 8,\n      \u002F\u002F Low-tier tenants get fewer routes and therefore less total capacity.\n      \"LowBucketCount\": 4\n    }\n  }\n}\n",[228,1095,1096,1100,1112,1124,1129,1145,1160,1165,1179,1183,1187],{"__ignoreMap":350},[600,1097,1098],{"class":602,"line":603},[600,1099,607],{"class":606},[600,1101,1102,1104,1106,1108,1110],{"class":602,"line":610},[600,1103,613],{"class":606},[600,1105,449],{"class":616},[600,1107,619],{"class":606},[600,1109,622],{"class":606},[600,1111,625],{"class":606},[600,1113,1114,1116,1118,1120,1122],{"class":602,"line":628},[600,1115,631],{"class":606},[600,1117,490],{"class":634},[600,1119,619],{"class":606},[600,1121,622],{"class":606},[600,1123,625],{"class":606},[600,1125,1126],{"class":602,"line":654},[600,1127,1128],{"class":937},"      \u002F\u002F More buckets means more independent routes for tenants in that tier.\n",[600,1130,1131,1133,1136,1138,1140,1143],{"class":602,"line":667},[600,1132,670],{"class":606},[600,1134,1135],{"class":673},"PriorityBucketCount",[600,1137,619],{"class":606},[600,1139,622],{"class":606},[600,1141,1142],{"class":673}," 8",[600,1144,651],{"class":606},[600,1146,1147,1149,1152,1154,1156,1158],{"class":602,"line":690},[600,1148,670],{"class":606},[600,1150,1151],{"class":673},"DefaultBucketCount",[600,1153,619],{"class":606},[600,1155,622],{"class":606},[600,1157,1142],{"class":673},[600,1159,651],{"class":606},[600,1161,1162],{"class":602,"line":711},[600,1163,1164],{"class":937},"      \u002F\u002F Low-tier tenants get fewer routes and therefore less total capacity.\n",[600,1166,1167,1169,1172,1174,1176],{"class":602,"line":732},[600,1168,670],{"class":606},[600,1170,1171],{"class":673},"LowBucketCount",[600,1173,619],{"class":606},[600,1175,622],{"class":606},[600,1177,1178],{"class":673}," 4\n",[600,1180,1181],{"class":602,"line":747},[600,1182,750],{"class":606},[600,1184,1185],{"class":602,"line":753},[600,1186,756],{"class":606},[600,1188,1189],{"class":602,"line":759},[600,1190,762],{"class":606},[169,1192,1193],{},"Changing bucket counts changes route names, so treat bucket-count changes as queue migrations.",[164,1195,1197],{"id":1196},"declaring-messages","Declaring messages",[169,1199,1200,1201,1203],{},"A tenant message implements ",[228,1202,270],{}," and declares its logical topic.",[344,1205,1209],{"className":1206,"code":1207,"language":1208,"meta":350,"style":350},"language-csharp shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u002F\u002F The generic priority marker selects the default processing profile.\n[ConfigurePublisher\u003CDefaultMessage>(\"reports.refresh\")]\npublic sealed record RefreshReportMessage(\n    \u002F\u002F Tenant messages must expose OrganizationId so Zeeq can resolve tier and bucket.\n    string OrganizationId,\n    string? TeamId,\n    string ReportId\n) : Event(Id.Random()), ITenantMessage;\n","csharp",[228,1210,1211,1216,1221,1226,1231,1236,1241,1246],{"__ignoreMap":350},[600,1212,1213],{"class":602,"line":603},[600,1214,1215],{},"\u002F\u002F The generic priority marker selects the default processing profile.\n",[600,1217,1218],{"class":602,"line":610},[600,1219,1220],{},"[ConfigurePublisher\u003CDefaultMessage>(\"reports.refresh\")]\n",[600,1222,1223],{"class":602,"line":628},[600,1224,1225],{},"public sealed record RefreshReportMessage(\n",[600,1227,1228],{"class":602,"line":654},[600,1229,1230],{},"    \u002F\u002F Tenant messages must expose OrganizationId so Zeeq can resolve tier and bucket.\n",[600,1232,1233],{"class":602,"line":667},[600,1234,1235],{},"    string OrganizationId,\n",[600,1237,1238],{"class":602,"line":690},[600,1239,1240],{},"    string? TeamId,\n",[600,1242,1243],{"class":602,"line":711},[600,1244,1245],{},"    string ReportId\n",[600,1247,1248],{"class":602,"line":732},[600,1249,1250],{},") : Event(Id.Random()), ITenantMessage;\n",[169,1252,1253],{},"A consumer handler declares the message type and channel.",[344,1255,1257],{"className":1206,"code":1256,"language":1208,"meta":350,"style":350},"\u002F\u002F The channel is the logical consumer identity for this handler.\n[ConfigureConsumer\u003CRefreshReportMessage>(\"reports.refresh.worker\")]\npublic sealed class RefreshReportHandler(\n    IDeadLetterWriter deadLetterWriter\n) : ZeeqMessageHandler\u003CRefreshReportMessage>(deadLetterWriter)\n{\n    protected override Task\u003CRefreshReportMessage> HandleMessageAsync(\n        RefreshReportMessage message,\n        CancellationToken cancellationToken\n    )\n    {\n        \u002F\u002F Feature work goes here. Retry and dead-letter behavior live in the base class.\n        return Task.FromResult(message);\n    }\n}\n",[228,1258,1259,1264,1269,1274,1279,1284,1288,1293,1298,1303,1308,1313,1318,1323,1328],{"__ignoreMap":350},[600,1260,1261],{"class":602,"line":603},[600,1262,1263],{},"\u002F\u002F The channel is the logical consumer identity for this handler.\n",[600,1265,1266],{"class":602,"line":610},[600,1267,1268],{},"[ConfigureConsumer\u003CRefreshReportMessage>(\"reports.refresh.worker\")]\n",[600,1270,1271],{"class":602,"line":628},[600,1272,1273],{},"public sealed class RefreshReportHandler(\n",[600,1275,1276],{"class":602,"line":654},[600,1277,1278],{},"    IDeadLetterWriter deadLetterWriter\n",[600,1280,1281],{"class":602,"line":667},[600,1282,1283],{},") : ZeeqMessageHandler\u003CRefreshReportMessage>(deadLetterWriter)\n",[600,1285,1286],{"class":602,"line":690},[600,1287,607],{},[600,1289,1290],{"class":602,"line":711},[600,1291,1292],{},"    protected override Task\u003CRefreshReportMessage> HandleMessageAsync(\n",[600,1294,1295],{"class":602,"line":732},[600,1296,1297],{},"        RefreshReportMessage message,\n",[600,1299,1300],{"class":602,"line":747},[600,1301,1302],{},"        CancellationToken cancellationToken\n",[600,1304,1305],{"class":602,"line":753},[600,1306,1307],{},"    )\n",[600,1309,1310],{"class":602,"line":759},[600,1311,1312],{},"    {\n",[600,1314,1315],{"class":602,"line":1076},[600,1316,1317],{},"        \u002F\u002F Feature work goes here. Retry and dead-letter behavior live in the base class.\n",[600,1319,1320],{"class":602,"line":1081},[600,1321,1322],{},"        return Task.FromResult(message);\n",[600,1324,1326],{"class":602,"line":1325},14,[600,1327,750],{},[600,1329,1331],{"class":602,"line":1330},15,[600,1332,762],{},[169,1334,1335,1336,450],{},"Feature code should publish through ",[228,1337,250],{},[344,1339,1341],{"className":1206,"code":1340,"language":1208,"meta":350,"style":350},"await publisher.PublishAsync(\n    \u002F\u002F The publisher resolves the concrete route; feature code only creates the message.\n    new RefreshReportMessage(organizationId, teamId, reportId),\n    cancellationToken\n);\n",[228,1342,1343,1348,1353,1358,1363],{"__ignoreMap":350},[600,1344,1345],{"class":602,"line":603},[600,1346,1347],{},"await publisher.PublishAsync(\n",[600,1349,1350],{"class":602,"line":610},[600,1351,1352],{},"    \u002F\u002F The publisher resolves the concrete route; feature code only creates the message.\n",[600,1354,1355],{"class":602,"line":628},[600,1356,1357],{},"    new RefreshReportMessage(organizationId, teamId, reportId),\n",[600,1359,1360],{"class":602,"line":654},[600,1361,1362],{},"    cancellationToken\n",[600,1364,1365],{"class":602,"line":667},[600,1366,1367],{},");\n",[164,1369,1371],{"id":1370},"failure-behavior","Failure behavior",[169,1373,1374,1375,1377],{},"Handlers use a shared retry pipeline through ",[228,1376,260],{},". If retries are exhausted, Zeeq writes the failed message to the PostgreSQL dead-letter table.",[169,1379,1380],{},"The dead-letter table is for inspection and future replay tooling. It is not another active Brighter queue.",[164,1382,1384],{"id":1383},"observability","Observability",[169,1386,1387],{},"The messaging layer participates in the same OpenTelemetry setup as the rest of the runtime. Zeeq registers the Brighter activity source and Npgsql instrumentation so publish, poll, acknowledge, and dead-letter database work can show up in traces.",[169,1389,1390],{},"Brighter also writes W3C trace context into the serialized queue message. That means a consumer can continue the trace from the request or background workflow that published the message instead of appearing as unrelated work.",[169,1392,1393,1394,1397],{},"Zeeq intentionally drops Brighter's root ",[228,1395,1396],{},"receive"," spans from export. Those spans are created before each Postgres poll and are mostly empty-queue timer noise. Message publish, process, handler, acknowledge\u002Fdelete, and dead-letter spans are still recorded.",[169,1399,1400],{},"In development, Zeeq exposes a local-only diagnostic endpoint for a quick end-to-end check:",[344,1402,1406],{"className":1403,"code":1404,"language":1405,"meta":350,"style":350},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","# Publishes a system diagnostic message and returns the smoke test id.\ncurl -X POST \"http:\u002F\u002Fzeeq-api.localhost:8095\u002Fapi\u002Fv1\u002Fdiagnostics\u002Fmessage-queue\u002Fsmoke-test?note=local-check\"\n","bash",[228,1407,1408,1413],{"__ignoreMap":350},[600,1409,1410],{"class":602,"line":603},[600,1411,1412],{"class":937},"# Publishes a system diagnostic message and returns the smoke test id.\n",[600,1414,1415,1418,1421,1424,1426,1429],{"class":602,"line":610},[600,1416,1417],{"class":634},"curl",[600,1419,1420],{"class":645}," -X",[600,1422,1423],{"class":645}," POST",[600,1425,642],{"class":606},[600,1427,1428],{"class":645},"http:\u002F\u002Fzeeq-api.localhost:8095\u002Fapi\u002Fv1\u002Fdiagnostics\u002Fmessage-queue\u002Fsmoke-test?note=local-check",[600,1430,1431],{"class":606},"\"\n",[169,1433,1434,1435,1438,1439,1442,1443,1446],{},"The server logs should show the same ",[228,1436,1437],{},"SmokeTestId"," on the publish and consume entries. When Aspire dashboard telemetry is available, ",[228,1440,1441],{},"aspire otel traces"," and ",[228,1444,1445],{},"aspire otel spans"," can be used to inspect the same flow.",[164,1448,1450],{"id":1449},"references","References",[1452,1453,1454,1462,1468,1475,1482],"ul",{},[1455,1456,1457],"li",{},[179,1458,1461],{"href":1459,"rel":1460},"https:\u002F\u002Fbrightercommand.gitbook.io\u002Fparamore-brighter-documentation",[183],"Brighter documentation",[1455,1463,1464],{},[179,1465,1467],{"href":181,"rel":1466},[183],"Brighter GitHub repository",[1455,1469,1470],{},[179,1471,1474],{"href":1472,"rel":1473},"https:\u002F\u002Fbrightercommand.gitbook.io\u002Fparamore-brighter-documentation\u002Fusing-an-external-bus\u002Fimplementingexternalbus",[183],"Brighter external bus overview",[1455,1476,1477],{},[179,1478,1481],{"href":1479,"rel":1480},"https:\u002F\u002Fbrightercommand.gitbook.io\u002Fparamore-brighter-documentation\u002Fusing-an-external-bus\u002Frouting",[183],"Brighter routing",[1455,1483,1484],{},[179,1485,1488],{"href":1486,"rel":1487},"https:\u002F\u002Fgithub.com\u002FBrighterCommand\u002FDocs\u002Fblob\u002Fmaster\u002Fcontents\u002FPostgreSQLMessageBroker.md",[183],"Brighter PostgreSQL message broker",[1490,1491,1492],"style",{},"html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}",{"title":350,"searchDepth":610,"depth":610,"links":1494},[1495,1496,1497,1498,1499,1500,1501,1502,1503,1508,1509,1510,1511],{"id":166,"depth":610,"text":167},{"id":188,"depth":610,"text":189},{"id":201,"depth":610,"text":202},{"id":296,"depth":610,"text":297},{"id":322,"depth":610,"text":323},{"id":358,"depth":610,"text":359},{"id":442,"depth":610,"text":443},{"id":809,"depth":610,"text":810},{"id":890,"depth":610,"text":891,"children":1504},[1505,1506,1507],{"id":895,"depth":628,"text":896},{"id":967,"depth":628,"text":968},{"id":1086,"depth":628,"text":1087},{"id":1196,"depth":610,"text":1197},{"id":1370,"depth":610,"text":1371},{"id":1383,"depth":610,"text":1384},{"id":1449,"depth":610,"text":1450},"Overview of messaging and queueing systems in the architecture.","md",null,{},{"icon":127},{"title":124,"description":1512},"a1ZUlCAosSLrUSzCHXx7yYJu9QXMuJdbWk5BVh1WvS4",[1520,1522],{"title":119,"path":120,"stem":121,"description":1521,"icon":122,"children":-1},"Overview of caching systems in the architecture.",{"title":129,"path":130,"stem":131,"description":1523,"icon":132,"children":-1},"How Zeeq renders and updates owned GitHub comments.",1784297365800]