[{"data":1,"prerenderedAt":677},["ShallowReactive",2],{"navigation":3,"\u002Fapi\u002Ftrusted-effects":155,"\u002Fapi\u002Ftrusted-effects-surround":672},[4,36,57,113,144],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":35},"Getting Started","i-lucide-rocket","\u002Fgetting-started","1.getting-started",[10,15,20,25,30],{"title":11,"path":12,"stem":13,"icon":14},"Introduction","\u002Fgetting-started\u002Fintroduction","1.getting-started\u002F1.introduction","i-lucide-info",{"title":16,"path":17,"stem":18,"icon":19},"Install","\u002Fgetting-started\u002Finstall","1.getting-started\u002F2.install","i-lucide-download",{"title":21,"path":22,"stem":23,"icon":24},"Quickstart","\u002Fgetting-started\u002Fquickstart","1.getting-started\u002F3.quickstart","i-lucide-zap",{"title":26,"path":27,"stem":28,"icon":29},"Testing","\u002Fgetting-started\u002Ftesting","1.getting-started\u002F4.testing","i-lucide-flask-conical",{"title":31,"path":32,"stem":33,"icon":34},"CLI","\u002Fgetting-started\u002Fcli","1.getting-started\u002F5.cli","i-lucide-terminal",false,{"title":37,"icon":38,"path":39,"stem":40,"children":41,"page":35},"Concepts","i-lucide-boxes","\u002Fconcepts","2.concepts",[42,47,52],{"title":43,"path":44,"stem":45,"icon":46},"SQL-first contract","\u002Fconcepts\u002Fsql-first-contract","2.concepts\u002F1.sql-first-contract","i-lucide-database",{"title":48,"path":49,"stem":50,"icon":51},"Jobs and status","\u002Fconcepts\u002Fjobs-and-status","2.concepts\u002F2.jobs-and-status","i-lucide-list-checks",{"title":53,"path":54,"stem":55,"icon":56},"Fencing and settles","\u002Fconcepts\u002Ffencing-and-settles","2.concepts\u002F3.fencing-and-settles","i-lucide-shield",{"title":58,"icon":59,"path":60,"stem":61,"children":62,"page":35},"API","i-lucide-code-2","\u002Fapi","3.api",[63,68,73,78,83,88,93,98,103,108],{"title":64,"path":65,"stem":66,"icon":67},"Enqueue","\u002Fapi\u002Fenqueue","3.api\u002F1.enqueue","i-lucide-plus-circle",{"title":69,"path":70,"stem":71,"icon":72},"Trusted Effects","\u002Fapi\u002Ftrusted-effects","3.api\u002F10.trusted-effects","i-lucide-lock-keyhole",{"title":74,"path":75,"stem":76,"icon":77},"Claim and heartbeat","\u002Fapi\u002Fclaim-and-heartbeat","3.api\u002F2.claim-and-heartbeat","i-lucide-hand",{"title":79,"path":80,"stem":81,"icon":82},"Worker and handlers","\u002Fapi\u002Fworker-and-handlers","3.api\u002F3.worker-and-handlers","i-lucide-cpu",{"title":84,"path":85,"stem":86,"icon":87},"HTTP facade","\u002Fapi\u002Fhttp-facade","3.api\u002F4.http-facade","i-lucide-globe",{"title":89,"path":90,"stem":91,"icon":92},"Authorization","\u002Fapi\u002Fauthorization","3.api\u002F5.authorization","i-lucide-key-round",{"title":94,"path":95,"stem":96,"icon":97},"Follow-ups","\u002Fapi\u002Ffollow-ups","3.api\u002F6.follow-ups","i-lucide-git-branch",{"title":99,"path":100,"stem":101,"icon":102},"Workflows and schedules","\u002Fapi\u002Fworkflows-and-schedules","3.api\u002F7.workflows-and-schedules","i-lucide-workflow",{"title":104,"path":105,"stem":106,"icon":107},"Durable admission","\u002Fapi\u002Fdurable-admission","3.api\u002F8.durable-admission","i-lucide-ticket",{"title":109,"path":110,"stem":111,"icon":112},"Workflow Continuations","\u002Fapi\u002Fworkflow-continuations","3.api\u002F9.workflow-continuations","i-lucide-git-fork",{"title":114,"icon":115,"path":116,"stem":117,"children":118,"page":35},"Operations","i-lucide-activity","\u002Foperations","4.operations",[119,124,129,134,139],{"title":120,"path":121,"stem":122,"icon":123},"Standalone Scheduler","\u002Foperations\u002Fstandalone-scheduler","4.operations\u002F0.standalone-scheduler","i-lucide-calendar-clock",{"title":125,"path":126,"stem":127,"icon":128},"Housekeeping","\u002Foperations\u002Fhousekeeping","4.operations\u002F1.housekeeping","i-lucide-timer",{"title":130,"path":131,"stem":132,"icon":133},"Redrive and dead letters","\u002Foperations\u002Fredrive-and-dlq","4.operations\u002F2.redrive-and-dlq","i-lucide-rotate-ccw",{"title":135,"path":136,"stem":137,"icon":138},"Flow control","\u002Foperations\u002Fflow-control","4.operations\u002F3.flow-control","i-lucide-gauge",{"title":140,"path":141,"stem":142,"icon":143},"Queue Boundaries","\u002Foperations\u002Fqueue-boundaries","4.operations\u002F4.queue-boundaries","i-lucide-panels-top-left",{"title":145,"icon":146,"path":147,"stem":148,"children":149,"page":35},"Reference","i-lucide-book-marked","\u002Freference","5.reference",[150],{"title":151,"path":152,"stem":153,"icon":154},"Configuration","\u002Freference\u002Fconfiguration","5.reference\u002F1.configuration","i-lucide-settings",{"id":156,"title":69,"body":157,"description":665,"extension":666,"links":667,"meta":668,"navigation":669,"path":70,"seo":670,"stem":71,"__hash__":671},"docs\u002F3.api\u002F10.trusted-effects.md",{"type":158,"value":159,"toc":658},"minimark",[160,169,172,188,195,200,203,262,276,280,288,515,529,540,544,585,595,599,626,632,636,654],[161,162,163,164,168],"p",{},"A handler sometimes needs the host application to commit a domain mutation\nwhile its TaskQ attempt is still active. Reading ",[165,166,167],"code",{},"taskq.jobs"," directly breaks\nthe table-privilege boundary; reading an observer projection does not lock or\nfence the attempt.",[161,170,171],{},"The current SQL contract retains the domain-neutral function and Python adapter for\nthis case:",[173,174,179],"pre",{"className":175,"code":176,"language":177,"meta":178,"style":178},"language-python shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","from taskq.sql import lock_active_effect_attempt\n","python","",[165,180,181],{"__ignoreMap":178},[182,183,186],"span",{"class":184,"line":185},"line",1,[182,187,176],{},[161,189,190,191,194],{},"It ships in migration ",[165,192,193],{},"0018_trusted_effect_fence.sql",". There is no matching\nHTTP route; this is for a trusted, co-resident host transaction.",[196,197,199],"h2",{"id":198},"handler-side","Handler side",[161,201,202],{},"A handler sees only a bounded request\u002Fresponse operation. Attempt identity\nstays inside the worker runtime:",[173,204,207],{"className":175,"code":205,"filename":206,"language":177,"meta":178,"style":178},"from taskq import Complete, JobContext\n\n\nasync def apply_payment(ctx: JobContext, payload: PaymentInput) -> Complete:\n    result = await ctx.report_effect({\n        \"operation\": \"payment.apply\",\n        \"payment_id\": payload.payment_id,\n    })\n    return Complete(result={\"applied\": result[\"applied\"]})\n","tasks.py",[165,208,209,214,221,226,232,238,244,250,256],{"__ignoreMap":178},[182,210,211],{"class":184,"line":185},[182,212,213],{},"from taskq import Complete, JobContext\n",[182,215,217],{"class":184,"line":216},2,[182,218,220],{"emptyLinePlaceholder":219},true,"\n",[182,222,224],{"class":184,"line":223},3,[182,225,220],{"emptyLinePlaceholder":219},[182,227,229],{"class":184,"line":228},4,[182,230,231],{},"async def apply_payment(ctx: JobContext, payload: PaymentInput) -> Complete:\n",[182,233,235],{"class":184,"line":234},5,[182,236,237],{},"    result = await ctx.report_effect({\n",[182,239,241],{"class":184,"line":240},6,[182,242,243],{},"        \"operation\": \"payment.apply\",\n",[182,245,247],{"class":184,"line":246},7,[182,248,249],{},"        \"payment_id\": payload.payment_id,\n",[182,251,253],{"class":184,"line":252},8,[182,254,255],{},"    })\n",[182,257,259],{"class":184,"line":258},9,[182,260,261],{},"    return Complete(result={\"applied\": result[\"applied\"]})\n",[161,263,264,267,268,271,272,275],{},[165,265,266],{},"ctx.report_effect(...)"," is available only when the worker was constructed with\na trusted effect reporter. Requests and responses must be JSON objects and are\nlimited to 8 KiB by default (",[165,269,270],{},"WorkerOptions.effect_request_max_bytes"," and\n",[165,273,274],{},"effect_response_max_bytes",").",[196,277,279],{"id":278},"host-reporter","Host reporter",[161,281,282,283,287],{},"The reporter receives the hidden active-attempt identity. It must lock that\nattempt and perform the domain write in the ",[284,285,286],"strong",{},"same transaction",":",[173,289,292],{"className":175,"code":290,"filename":291,"language":177,"meta":178,"style":178},"from taskq import TaskqConflictError, WorkerEffectAttempt\nfrom taskq.sql import lock_active_effect_attempt\n\n\nclass HostEffects:\n    def __init__(self, engine):\n        self.engine = engine\n\n    async def report_effect(\n        self,\n        attempt: WorkerEffectAttempt,\n        request: dict,\n    ) -> dict:\n        async with self.engine.begin() as connection:\n            active = await lock_active_effect_attempt(\n                connection,\n                job_id=attempt.job_id,\n                attempt_id=attempt.attempt_id,\n                worker_id=attempt.worker_id,\n                queue=attempt.queue,\n                job_type=attempt.job_type,\n            )\n            if active is None:\n                raise TaskqConflictError(\n                    details={\"reason\": \"inactive_attempt\"},\n                )\n\n            # Bind the requested subject to the admitted job payload before\n            # writing. Never trust request[\"payment_id\"] by itself.\n            if request[\"payment_id\"] != active.payload[\"payment_id\"]:\n                raise TaskqConflictError(\n                    details={\"reason\": \"subject_mismatch\"},\n                )\n\n            result = await apply_idempotently(\n                connection,\n                operation_key=f\"{attempt.job_id}:payment.apply\",\n                payment_id=request[\"payment_id\"],\n            )\n            return {\"applied\": result.applied}\n","effects.py",[165,293,294,299,303,307,311,316,321,326,330,335,341,347,353,359,365,371,377,383,389,395,401,407,413,419,425,431,437,442,448,454,460,465,471,476,481,487,492,498,504,509],{"__ignoreMap":178},[182,295,296],{"class":184,"line":185},[182,297,298],{},"from taskq import TaskqConflictError, WorkerEffectAttempt\n",[182,300,301],{"class":184,"line":216},[182,302,176],{},[182,304,305],{"class":184,"line":223},[182,306,220],{"emptyLinePlaceholder":219},[182,308,309],{"class":184,"line":228},[182,310,220],{"emptyLinePlaceholder":219},[182,312,313],{"class":184,"line":234},[182,314,315],{},"class HostEffects:\n",[182,317,318],{"class":184,"line":240},[182,319,320],{},"    def __init__(self, engine):\n",[182,322,323],{"class":184,"line":246},[182,324,325],{},"        self.engine = engine\n",[182,327,328],{"class":184,"line":252},[182,329,220],{"emptyLinePlaceholder":219},[182,331,332],{"class":184,"line":258},[182,333,334],{},"    async def report_effect(\n",[182,336,338],{"class":184,"line":337},10,[182,339,340],{},"        self,\n",[182,342,344],{"class":184,"line":343},11,[182,345,346],{},"        attempt: WorkerEffectAttempt,\n",[182,348,350],{"class":184,"line":349},12,[182,351,352],{},"        request: dict,\n",[182,354,356],{"class":184,"line":355},13,[182,357,358],{},"    ) -> dict:\n",[182,360,362],{"class":184,"line":361},14,[182,363,364],{},"        async with self.engine.begin() as connection:\n",[182,366,368],{"class":184,"line":367},15,[182,369,370],{},"            active = await lock_active_effect_attempt(\n",[182,372,374],{"class":184,"line":373},16,[182,375,376],{},"                connection,\n",[182,378,380],{"class":184,"line":379},17,[182,381,382],{},"                job_id=attempt.job_id,\n",[182,384,386],{"class":184,"line":385},18,[182,387,388],{},"                attempt_id=attempt.attempt_id,\n",[182,390,392],{"class":184,"line":391},19,[182,393,394],{},"                worker_id=attempt.worker_id,\n",[182,396,398],{"class":184,"line":397},20,[182,399,400],{},"                queue=attempt.queue,\n",[182,402,404],{"class":184,"line":403},21,[182,405,406],{},"                job_type=attempt.job_type,\n",[182,408,410],{"class":184,"line":409},22,[182,411,412],{},"            )\n",[182,414,416],{"class":184,"line":415},23,[182,417,418],{},"            if active is None:\n",[182,420,422],{"class":184,"line":421},24,[182,423,424],{},"                raise TaskqConflictError(\n",[182,426,428],{"class":184,"line":427},25,[182,429,430],{},"                    details={\"reason\": \"inactive_attempt\"},\n",[182,432,434],{"class":184,"line":433},26,[182,435,436],{},"                )\n",[182,438,440],{"class":184,"line":439},27,[182,441,220],{"emptyLinePlaceholder":219},[182,443,445],{"class":184,"line":444},28,[182,446,447],{},"            # Bind the requested subject to the admitted job payload before\n",[182,449,451],{"class":184,"line":450},29,[182,452,453],{},"            # writing. Never trust request[\"payment_id\"] by itself.\n",[182,455,457],{"class":184,"line":456},30,[182,458,459],{},"            if request[\"payment_id\"] != active.payload[\"payment_id\"]:\n",[182,461,463],{"class":184,"line":462},31,[182,464,424],{},[182,466,468],{"class":184,"line":467},32,[182,469,470],{},"                    details={\"reason\": \"subject_mismatch\"},\n",[182,472,474],{"class":184,"line":473},33,[182,475,436],{},[182,477,479],{"class":184,"line":478},34,[182,480,220],{"emptyLinePlaceholder":219},[182,482,484],{"class":184,"line":483},35,[182,485,486],{},"            result = await apply_idempotently(\n",[182,488,490],{"class":184,"line":489},36,[182,491,376],{},[182,493,495],{"class":184,"line":494},37,[182,496,497],{},"                operation_key=f\"{attempt.job_id}:payment.apply\",\n",[182,499,501],{"class":184,"line":500},38,[182,502,503],{},"                payment_id=request[\"payment_id\"],\n",[182,505,507],{"class":184,"line":506},39,[182,508,412],{},[182,510,512],{"class":184,"line":511},40,[182,513,514],{},"            return {\"applied\": result.applied}\n",[161,516,517,520,521,524,525,528],{},[165,518,519],{},"lock_active_effect_attempt(...)"," returns a row only when the job is running,\nthe attempt\u002Fworker\u002Fqueue\u002Fjob type all match, the database-clock lease is live,\nand cancellation has not been requested. The returned ",[165,522,523],{},"payload",", optional\n",[165,526,527],{},"workflow_id",", and optional workflow status counts are safe projections; fence\nmaterial, headers, progress, result, and error are not exposed.",[161,530,531,532,535,536,539],{},"The database role needs ",[165,533,534],{},"EXECUTE"," on the function through ",[165,537,538],{},"taskq_producer"," and\nnormal rights on the host’s domain tables. It does not need direct access to\nTaskQ tables or runner\u002Foperator capabilities.",[196,541,543],{"id":542},"wire-the-worker","Wire the worker",[173,545,548],{"className":175,"code":546,"filename":547,"language":177,"meta":178,"style":178},"service = WorkerService(\n    runner_transport,\n    registry,\n    worker_id=\"worker-1\",\n    options=worker_options,\n    effect_reporter=HostEffects(domain_engine),\n)\n","worker.py",[165,549,550,555,560,565,570,575,580],{"__ignoreMap":178},[182,551,552],{"class":184,"line":185},[182,553,554],{},"service = WorkerService(\n",[182,556,557],{"class":184,"line":216},[182,558,559],{},"    runner_transport,\n",[182,561,562],{"class":184,"line":223},[182,563,564],{},"    registry,\n",[182,566,567],{"class":184,"line":228},[182,568,569],{},"    worker_id=\"worker-1\",\n",[182,571,572],{"class":184,"line":234},[182,573,574],{},"    options=worker_options,\n",[182,576,577],{"class":184,"line":240},[182,578,579],{},"    effect_reporter=HostEffects(domain_engine),\n",[182,581,582],{"class":184,"line":246},[182,583,584],{},")\n",[161,586,587,590,591,594],{},[165,588,589],{},"WorkerSupervisor"," accepts the same ",[165,592,593],{},"effect_reporter="," argument when you build\nthe lower-level runtime directly.",[196,596,598],{"id":597},"failure-and-retry-rules","Failure and retry rules",[600,601,602,610,617,620,623],"ul",{},[603,604,605,606,609],"li",{},"The worker retries ",[165,607,608],{},"TaskqUnavailableError",", timeouts, and connection errors\nwith the settlement retry budget.",[603,611,612,613,616],{},"A retry replays the ",[284,614,615],{},"same"," request. The host effect must therefore have a\ndurable idempotency key.",[603,618,619],{},"Non-retryable TaskQ errors return to the handler as failures; the handler must\nnot convert an inactive-attempt result into success.",[603,621,622],{},"The held TaskQ job-row lock serializes the domain transaction with settlement,\ncancellation, lease expiry, and another trusted effect transaction.",[603,624,625],{},"Commit or roll back the domain effect before returning a response.",[627,628,629],"warning",{},[161,630,631],{},"Do not split the lock and domain write across transactions. Releasing the lock\nfirst removes the guarantee this API exists to provide.",[196,633,635],{"id":634},"related","Related",[600,637,638,644,649],{},[603,639,640],{},[641,642,643],"a",{"href":54},"Fencing & Settles",[603,645,646],{},[641,647,648],{"href":80},"Worker & Handlers",[603,650,651],{},[641,652,653],{"href":44},"SQL-first Contract",[655,656,657],"style",{},"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);}",{"title":178,"searchDepth":185,"depth":216,"links":659},[660,661,662,663,664],{"id":198,"depth":216,"text":199},{"id":278,"depth":216,"text":279},{"id":542,"depth":216,"text":543},{"id":597,"depth":216,"text":598},{"id":634,"depth":216,"text":635},"Fence a co-resident domain mutation against the exact active TaskQ attempt.","md",null,{},{"icon":72},{"title":69,"description":665},"dQkUO-WG7O9ACxACZRRcZZc_VGlES_ci1Y-U20dVZHA",[673,675],{"title":64,"path":65,"stem":66,"description":674,"icon":67,"children":-1},"Typed enqueue results and 0.1 uniqueness.",{"title":74,"path":75,"stem":76,"description":676,"icon":77,"children":-1},"How workers lease work and extend ownership.",1787515776866]