[{"data":1,"prerenderedAt":4063},["ShallowReactive",2],{"search-en":3,"content-en-developer\u002Fplugin-contributions":4,"surround-en-\u002Fdeveloper\u002Fplugin-contributions":4055},[],{"id":5,"title":6,"body":7,"description":4048,"extension":4049,"meta":4050,"navigation":470,"path":4051,"seo":4052,"stem":4053,"__hash__":4054},"content_en\u002F3.developer\u002F42.plugin-contributions.md","Contribution Reference",{"type":8,"value":9,"toc":4024},"minimark",[10,24,27,53,58,63,66,236,240,247,348,378,382,392,402,1048,1086,1093,1097,1125,1139,1168,1190,1215,1226,1230,1240,1246,1289,1292,1296,1396,1429,1449,1453,1456,1462,1497,1522,1540,1561,1854,1890,1903,1939,1957,1961,1964,1970,1977,1993,2017,2562,2575,2610,2624,2628,2634,2656,2824,2831,2908,2919,2923,2929,2944,2950,2956,2962,2972,2982,2986,2992,2995,3330,3336,3354,3370,3374,3380,3396,3405,3409,3415,3425,3436,3723,3739,3742,3748,3751,3784,3794,3798,3804,3810,3817,3830,3834,3839,3920,3946,3961,3965,4000,4020],[11,12,13,14,18,19,23],"p",{},"A contribution is a ",[15,16,17],"strong",{},"data descriptor"," in ",[20,21,22],"code",{},"contributes.\u003Cbucket>[]",". Its executable half, when it has one, lives at one condition-independent package export that codegen verifies without running.",[11,25,26],{},"Two rules hold for every bucket without exception:",[28,29,30,46],"ul",{},[31,32,33,34,37,38,41,42,45],"li",{},"The manifest must declare the bucket's capability ",[15,35,36],{},"and"," an explicit ",[20,39,40],{},"flag",". Both are rechecked, together with the operator grant and any ",[20,43,44],{},"requiredEnvVars",", immediately before plugin code runs.",[31,47,48,49,52],{},"Contributed kinds are namespaced ",[20,50,51],{},"plugin.\u003CpluginId>.\u003ClocalId>",", so a plugin can never shadow or collide with a core kind or another plugin's kind.",[54,55,57],"h2",{"id":56},"bucket-summary","Bucket summary",[59,60,62],"h3",{"id":61},"wired-end-to-end","Wired end to end",[11,64,65],{},"A production host path resolves and runs these contributions today.",[67,68,69,88],"table",{},[70,71,72],"thead",{},[73,74,75,79,82,85],"tr",{},[76,77,78],"th",{},"Bucket",[76,80,81],{},"Capability",[76,83,84],{},"Runs in",[76,86,87],{},"Failure direction",[89,90,91,110,127,148,165,185,202,220],"tbody",{},[73,92,93,99,104,107],{},[94,95,96],"td",{},[20,97,98],{},"sendTransports",[94,100,101],{},[20,102,103],{},"send:transport",[94,105,106],{},"Convex Node action",[94,108,109],{},"Typed failure code; host owns retries",[73,111,112,117,122,124],{},[94,113,114],{},[20,115,116],{},"agentSteps",[94,118,119],{},[20,120,121],{},"agent:step",[94,123,106],{},[94,125,126],{},"Fails the inbox lifecycle closed",[73,128,129,134,139,141],{},[94,130,131],{},[20,132,133],{},"draftStrategies",[94,135,136],{},[20,137,138],{},"draft:strategy",[94,140,106],{},[94,142,143,144,147],{},"Falls back to the built-in ",[20,145,146],{},"default"," strategy",[73,149,150,155,160,162],{},[94,151,152],{},[20,153,154],{},"sendGates",[94,156,157],{},[20,158,159],{},"send:gate",[94,161,106],{},[94,163,164],{},"Routes the reply to human review",[73,166,167,172,177,179],{},[94,168,169],{},[20,170,171],{},"automationSteps",[94,173,174],{},[20,175,176],{},"automation:step",[94,178,106],{},[94,180,181,184],{},[20,182,183],{},"failed"," step outcome, host-owned retry",[73,186,187,192,197,199],{},[94,188,189],{},[20,190,191],{},"crons",[94,193,194],{},[20,195,196],{},"scheduler:cron",[94,198,106],{},[94,200,201],{},"Run is skipped",[73,203,204,209,214,217],{},[94,205,206],{},[20,207,208],{},"navItems",[94,210,211],{},[20,212,213],{},"ui:navigation",[94,215,216],{},"Nuxt (data only)",[94,218,219],{},"Entry is dropped",[73,221,222,227,232,234],{},[94,223,224],{},[20,225,226],{},"settingsPanels",[94,228,229],{},[20,230,231],{},"ui:settings",[94,233,216],{},[94,235,219],{},[59,237,239],{"id":238},"declared-and-catalogued-but-not-yet-invoked","Declared and catalogued — but not yet invoked",[11,241,242,243,246],{},"The manifest type, validator capability check, and codegen metadata exist for\nthese four. ",[15,244,245],{},"No host dispatch or authorization entry is shipped",", so declaring\none has no runtime effect today. They are listed here rather than with reserved\nnames because their manifest shapes are available, but executable host adapters\nmust land with a concrete producer.",[67,248,249,263],{},[70,250,251],{},[73,252,253,255,257,260],{},[76,254,78],{},[76,256,81],{},[76,258,259],{},"Would run in",[76,261,262],{},"What is missing today",[89,264,265,283,309,327],{},[73,266,267,272,277,280],{},[94,268,269],{},[20,270,271],{},"automationTriggers",[94,273,274],{},[20,275,276],{},"automation:trigger",[94,278,279],{},"Convex mutation",[94,281,282],{},"No host firing seam exists",[73,284,285,290,295,298],{},[94,286,287],{},[20,288,289],{},"automationConditions",[94,291,292],{},[20,293,294],{},"automation:condition",[94,296,297],{},"Convex query",[94,299,300,301,304,305,308],{},"There is no plugin condition evaluator — ",[20,302,303],{},"conditions\u002Findex.ts"," throws for a ",[20,306,307],{},"plugin.*"," kind",[73,310,311,316,321,324],{},[94,312,313],{},[20,314,315],{},"webhookEvents",[94,317,318],{},[20,319,320],{},"webhooks:publish",[94,322,323],{},"Data only",[94,325,326],{},"The persisted event validator is a closed core-only union and no publish path authorizes a plugin event",[73,328,329,334,339,341],{},[94,330,331],{},[20,332,333],{},"importProviders",[94,335,336],{},[20,337,338],{},"imports:provide",[94,340,106],{},[94,342,343,344,347],{},"The import walker's provider registry is core-only, and ",[20,345,346],{},"integrationImports.provider"," cannot hold a plugin kind",[11,349,350,351,354,355,358,359,362,363,366,367,372,373,377],{},"Four capabilities have no contribution bucket: the host mediates ",[20,352,353],{},"llm:invoke",", ",[20,356,357],{},"plugin-storage:read"," and ",[20,360,361],{},"plugin-storage:write",", while ",[20,364,365],{},"worker:enqueue"," is reserved for a Tier-3 adapter that is not shipped. The storage quotas and LLM budget live on the ",[368,369,371],"a",{"href":370},"\u002Fdeveloper\u002Fplugin-capabilities","capability reference","; the dormant worker protocol is documented under ",[368,374,376],{"href":375},"\u002Fdeveloper\u002Fplugin-sandboxed-jobs","Sandboxed Jobs",".",[54,379,381],{"id":380},"send-transports","Send transports",[383,384,390],"pre",{"className":385,"code":387,"language":388,"meta":389},[386],"language-text","sendTransports: [{\n  id, label, module: { exportPath }, retryDelays: [\u002F* ≤ 3 bounded delays *\u002F],\n  requiredEnvVars: ['PLUGIN_ACME_TOKEN'], optionalEnvVars?,  \u002F\u002F PLUGIN_-prefixed, no \"__\"\n  credentialFields?,                                  \u002F\u002F the form, joined to those\n  supportsCustomReturnPath?, messageIdSource?, deduplicatesOnIdempotencyKey?,\n  webhook?, domainIdentity?\n}]\n","text","",[20,391,387],{"__ignoreMap":389},[11,393,394,395,397,398,401],{},"The stored provider kind is ",[20,396,51],{},". Codegen emits an isolate-safe metadata catalog plus a separate ",[20,399,400],{},"'use node'"," executable registry.",[383,403,407],{"className":404,"code":405,"language":406,"meta":389,"style":389},"language-ts shiki shiki-themes github-light github-dark-dimmed","import type {\n    PluginSendAttempt,\n    PluginSendTransportConfig,\n    PluginSendTransportModule,\n    PluginSendTransportParams,\n} from '@owlat\u002Fplugin-kit';\n\ninterface RelayExtras {\n    readonly endpoint: string;\n}\n\n\u002F** One attempt only. Owlat owns retries, health, routing, and audit. *\u002F\nexport const transport: PluginSendTransportModule\u003CRelayExtras> = {\n    parseExtras(input: unknown): RelayExtras {\n        if (typeof input !== 'object' || input === null) {\n            throw new TypeError('extras must be an object');\n        }\n        const endpoint = (input as { endpoint?: unknown }).endpoint;\n        if (typeof endpoint !== 'string' || !endpoint.startsWith('https:\u002F\u002F')) {\n            throw new TypeError('extras.endpoint must be an https URL');\n        }\n        return { endpoint };\n    },\n\n    async send(\n        params: PluginSendTransportParams,\n        extras: RelayExtras,\n        \u002F\u002F THIS INSTANCE's credentials, keyed by the name the manifest declared.\n        \u002F\u002F Never `process.env`: that reads the deployment-default instance's token\n        \u002F\u002F whichever transport id the send was addressed to.\n        config: PluginSendTransportConfig\n    ): Promise\u003CPluginSendAttempt> {\n        const response = await fetch(extras.endpoint, {\n            method: 'POST',\n            headers: {\n                'content-type': 'application\u002Fjson',\n                authorization: `Bearer ${config.env['PLUGIN_ACME_TOKEN'] ?? ''}`,\n            },\n            body: JSON.stringify({ to: params.to, from: params.from, subject: params.subject }),\n        });\n        if (response.status === 429) return { success: false, code: 'rate_limited' };\n        if (!response.ok) return { success: false, code: 'temporary_failure' };\n        return { success: true, id: response.headers.get('x-message-id') ?? '' };\n    },\n};\n","ts",[20,408,409,425,431,437,443,449,465,472,484,503,509,514,521,552,579,614,634,640,671,707,723,728,737,743,748,760,774,786,792,798,804,815,834,853,864,870,884,918,924,941,947,981,1007,1037,1042],{"__ignoreMap":389},[410,411,414,418,421],"span",{"class":412,"line":413},"line",1,[410,415,417],{"class":416},"s7YZ4","import",[410,419,420],{"class":416}," type",[410,422,424],{"class":423},"sYgZi"," {\n",[410,426,428],{"class":412,"line":427},2,[410,429,430],{"class":423},"    PluginSendAttempt,\n",[410,432,434],{"class":412,"line":433},3,[410,435,436],{"class":423},"    PluginSendTransportConfig,\n",[410,438,440],{"class":412,"line":439},4,[410,441,442],{"class":423},"    PluginSendTransportModule,\n",[410,444,446],{"class":412,"line":445},5,[410,447,448],{"class":423},"    PluginSendTransportParams,\n",[410,450,452,455,458,462],{"class":412,"line":451},6,[410,453,454],{"class":423},"} ",[410,456,457],{"class":416},"from",[410,459,461],{"class":460},"s-HuK"," '@owlat\u002Fplugin-kit'",[410,463,464],{"class":423},";\n",[410,466,468],{"class":412,"line":467},7,[410,469,471],{"emptyLinePlaceholder":470},true,"\n",[410,473,475,478,482],{"class":412,"line":474},8,[410,476,477],{"class":416},"interface",[410,479,481],{"class":480},"sOLd2"," RelayExtras",[410,483,424],{"class":423},[410,485,487,490,494,497,501],{"class":412,"line":486},9,[410,488,489],{"class":416},"    readonly",[410,491,493],{"class":492},"stnAF"," endpoint",[410,495,496],{"class":416},":",[410,498,500],{"class":499},"sviXB"," string",[410,502,464],{"class":423},[410,504,506],{"class":412,"line":505},10,[410,507,508],{"class":423},"}\n",[410,510,512],{"class":412,"line":511},11,[410,513,471],{"emptyLinePlaceholder":470},[410,515,517],{"class":412,"line":516},12,[410,518,520],{"class":519},"sDN9O","\u002F** One attempt only. Owlat owns retries, health, routing, and audit. *\u002F\n",[410,522,524,527,530,533,535,538,541,544,547,550],{"class":412,"line":523},13,[410,525,526],{"class":416},"export",[410,528,529],{"class":416}," const",[410,531,532],{"class":499}," transport",[410,534,496],{"class":416},[410,536,537],{"class":480}," PluginSendTransportModule",[410,539,540],{"class":423},"\u003C",[410,542,543],{"class":480},"RelayExtras",[410,545,546],{"class":423},"> ",[410,548,549],{"class":416},"=",[410,551,424],{"class":423},[410,553,555,559,562,565,567,570,573,575,577],{"class":412,"line":554},14,[410,556,558],{"class":557},"sPO5f","    parseExtras",[410,560,561],{"class":423},"(",[410,563,564],{"class":492},"input",[410,566,496],{"class":416},[410,568,569],{"class":499}," unknown",[410,571,572],{"class":423},")",[410,574,496],{"class":416},[410,576,481],{"class":480},[410,578,424],{"class":423},[410,580,582,585,588,591,594,597,600,603,605,608,611],{"class":412,"line":581},15,[410,583,584],{"class":416},"        if",[410,586,587],{"class":423}," (",[410,589,590],{"class":416},"typeof",[410,592,593],{"class":423}," input ",[410,595,596],{"class":416},"!==",[410,598,599],{"class":460}," 'object'",[410,601,602],{"class":416}," ||",[410,604,593],{"class":423},[410,606,607],{"class":416},"===",[410,609,610],{"class":499}," null",[410,612,613],{"class":423},") {\n",[410,615,617,620,623,626,628,631],{"class":412,"line":616},16,[410,618,619],{"class":416},"            throw",[410,621,622],{"class":416}," new",[410,624,625],{"class":557}," TypeError",[410,627,561],{"class":423},[410,629,630],{"class":460},"'extras must be an object'",[410,632,633],{"class":423},");\n",[410,635,637],{"class":412,"line":636},17,[410,638,639],{"class":423},"        }\n",[410,641,643,646,648,651,654,657,660,663,666,668],{"class":412,"line":642},18,[410,644,645],{"class":416},"        const",[410,647,493],{"class":499},[410,649,650],{"class":416}," =",[410,652,653],{"class":423}," (input ",[410,655,656],{"class":416},"as",[410,658,659],{"class":423}," { ",[410,661,662],{"class":492},"endpoint",[410,664,665],{"class":416},"?:",[410,667,569],{"class":499},[410,669,670],{"class":423}," }).endpoint;\n",[410,672,674,676,678,680,683,685,688,690,693,696,699,701,704],{"class":412,"line":673},19,[410,675,584],{"class":416},[410,677,587],{"class":423},[410,679,590],{"class":416},[410,681,682],{"class":423}," endpoint ",[410,684,596],{"class":416},[410,686,687],{"class":460}," 'string'",[410,689,602],{"class":416},[410,691,692],{"class":416}," !",[410,694,695],{"class":423},"endpoint.",[410,697,698],{"class":557},"startsWith",[410,700,561],{"class":423},[410,702,703],{"class":460},"'https:\u002F\u002F'",[410,705,706],{"class":423},")) {\n",[410,708,710,712,714,716,718,721],{"class":412,"line":709},20,[410,711,619],{"class":416},[410,713,622],{"class":416},[410,715,625],{"class":557},[410,717,561],{"class":423},[410,719,720],{"class":460},"'extras.endpoint must be an https URL'",[410,722,633],{"class":423},[410,724,726],{"class":412,"line":725},21,[410,727,639],{"class":423},[410,729,731,734],{"class":412,"line":730},22,[410,732,733],{"class":416},"        return",[410,735,736],{"class":423}," { endpoint };\n",[410,738,740],{"class":412,"line":739},23,[410,741,742],{"class":423},"    },\n",[410,744,746],{"class":412,"line":745},24,[410,747,471],{"emptyLinePlaceholder":470},[410,749,751,754,757],{"class":412,"line":750},25,[410,752,753],{"class":416},"    async",[410,755,756],{"class":557}," send",[410,758,759],{"class":423},"(\n",[410,761,763,766,768,771],{"class":412,"line":762},26,[410,764,765],{"class":492},"        params",[410,767,496],{"class":416},[410,769,770],{"class":480}," PluginSendTransportParams",[410,772,773],{"class":423},",\n",[410,775,777,780,782,784],{"class":412,"line":776},27,[410,778,779],{"class":492},"        extras",[410,781,496],{"class":416},[410,783,481],{"class":480},[410,785,773],{"class":423},[410,787,789],{"class":412,"line":788},28,[410,790,791],{"class":519},"        \u002F\u002F THIS INSTANCE's credentials, keyed by the name the manifest declared.\n",[410,793,795],{"class":412,"line":794},29,[410,796,797],{"class":519},"        \u002F\u002F Never `process.env`: that reads the deployment-default instance's token\n",[410,799,801],{"class":412,"line":800},30,[410,802,803],{"class":519},"        \u002F\u002F whichever transport id the send was addressed to.\n",[410,805,807,810,812],{"class":412,"line":806},31,[410,808,809],{"class":492},"        config",[410,811,496],{"class":416},[410,813,814],{"class":480}," PluginSendTransportConfig\n",[410,816,818,821,823,826,828,831],{"class":412,"line":817},32,[410,819,820],{"class":423},"    )",[410,822,496],{"class":416},[410,824,825],{"class":480}," Promise",[410,827,540],{"class":423},[410,829,830],{"class":480},"PluginSendAttempt",[410,832,833],{"class":423},"> {\n",[410,835,837,839,842,844,847,850],{"class":412,"line":836},33,[410,838,645],{"class":416},[410,840,841],{"class":499}," response",[410,843,650],{"class":416},[410,845,846],{"class":416}," await",[410,848,849],{"class":557}," fetch",[410,851,852],{"class":423},"(extras.endpoint, {\n",[410,854,856,859,862],{"class":412,"line":855},34,[410,857,858],{"class":423},"            method: ",[410,860,861],{"class":460},"'POST'",[410,863,773],{"class":423},[410,865,867],{"class":412,"line":866},35,[410,868,869],{"class":423},"            headers: {\n",[410,871,873,876,879,882],{"class":412,"line":872},36,[410,874,875],{"class":460},"                'content-type'",[410,877,878],{"class":423},": ",[410,880,881],{"class":460},"'application\u002Fjson'",[410,883,773],{"class":423},[410,885,887,890,893,896,898,901,904,907,910,913,916],{"class":412,"line":886},37,[410,888,889],{"class":423},"                authorization: ",[410,891,892],{"class":460},"`Bearer ${",[410,894,895],{"class":423},"config",[410,897,377],{"class":460},[410,899,900],{"class":423},"env",[410,902,903],{"class":460},"[",[410,905,906],{"class":460},"'PLUGIN_ACME_TOKEN'",[410,908,909],{"class":460},"] ",[410,911,912],{"class":416},"??",[410,914,915],{"class":460}," ''}`",[410,917,773],{"class":423},[410,919,921],{"class":412,"line":920},38,[410,922,923],{"class":423},"            },\n",[410,925,927,930,933,935,938],{"class":412,"line":926},39,[410,928,929],{"class":423},"            body: ",[410,931,932],{"class":499},"JSON",[410,934,377],{"class":423},[410,936,937],{"class":557},"stringify",[410,939,940],{"class":423},"({ to: params.to, from: params.from, subject: params.subject }),\n",[410,942,944],{"class":412,"line":943},40,[410,945,946],{"class":423},"        });\n",[410,948,950,952,955,957,960,963,966,969,972,975,978],{"class":412,"line":949},41,[410,951,584],{"class":416},[410,953,954],{"class":423}," (response.status ",[410,956,607],{"class":416},[410,958,959],{"class":499}," 429",[410,961,962],{"class":423},") ",[410,964,965],{"class":416},"return",[410,967,968],{"class":423}," { success: ",[410,970,971],{"class":499},"false",[410,973,974],{"class":423},", code: ",[410,976,977],{"class":460},"'rate_limited'",[410,979,980],{"class":423}," };\n",[410,982,984,986,988,991,994,996,998,1000,1002,1005],{"class":412,"line":983},42,[410,985,584],{"class":416},[410,987,587],{"class":423},[410,989,990],{"class":416},"!",[410,992,993],{"class":423},"response.ok) ",[410,995,965],{"class":416},[410,997,968],{"class":423},[410,999,971],{"class":499},[410,1001,974],{"class":423},[410,1003,1004],{"class":460},"'temporary_failure'",[410,1006,980],{"class":423},[410,1008,1010,1012,1014,1017,1020,1023,1025,1028,1030,1032,1035],{"class":412,"line":1009},43,[410,1011,733],{"class":416},[410,1013,968],{"class":423},[410,1015,1016],{"class":499},"true",[410,1018,1019],{"class":423},", id: response.headers.",[410,1021,1022],{"class":557},"get",[410,1024,561],{"class":423},[410,1026,1027],{"class":460},"'x-message-id'",[410,1029,962],{"class":423},[410,1031,912],{"class":416},[410,1033,1034],{"class":460}," ''",[410,1036,980],{"class":423},[410,1038,1040],{"class":412,"line":1039},44,[410,1041,742],{"class":423},[410,1043,1045],{"class":412,"line":1044},45,[410,1046,1047],{"class":423},"};\n",[11,1049,1050,1053,1054,1057,1058,1061,1062,354,1065,354,1068,354,1071,354,1074,354,1077,354,1080,354,1083,377],{},[20,1051,1052],{},"parseExtras"," is the sole unknown-input boundary and must return the honest extras type or throw. ",[20,1055,1056],{},"send"," performs ",[15,1059,1060],{},"exactly one"," network attempt. Failure codes are the fixed vocabulary ",[20,1063,1064],{},"rate_limited",[20,1066,1067],{},"temporary_failure",[20,1069,1070],{},"ambiguous_timeout",[20,1072,1073],{},"invalid_recipient",[20,1075,1076],{},"invalid_sender",[20,1078,1079],{},"authentication_failed",[20,1081,1082],{},"content_rejected",[20,1084,1085],{},"unknown",[11,1087,1088,1089,1092],{},"Immediately before every attempt the host rechecks the singleton organization, registration, flag, declaration, exact grant, and required env presence in a mutation. Denial never invokes plugin code and is audited as ",[20,1090,1091],{},"access_denied",". Terminal audit rows contain only system attribution, outcome and attempt count — never addresses, content, provider ids, or raw errors.",[59,1094,1096],{"id":1095},"configuration-and-named-instances","Configuration, and named instances",[11,1098,1099,1100,1102,1103,1106,1107,1109,1110,1113,1114,1117,1118,1124],{},"Declare the deployment variables your transport reads in ",[20,1101,44],{}," \u002F ",[20,1104,1105],{},"optionalEnvVars",". The host resolves them and hands ",[20,1108,1056],{}," a third argument — ",[20,1111,1112],{},"{ instanceKey, env }"," — carrying ",[15,1115,1116],{},"only"," those variables, keyed by the name you declared. ",[15,1119,1120,1121],{},"Read your credentials from there, not from ",[20,1122,1123],{},"process.env",": the environment names the deployment-default instance whichever transport id the send was addressed to.",[11,1126,1127,1128,1131,1132,1135,1136,1138],{},"Names must be ",[20,1129,1130],{},"PLUGIN_","-prefixed and contain no ",[20,1133,1134],{},"__",". The prefix is the namespace that keeps a manifest from being handed a credential that is not the plugin's; ",[20,1137,1134],{}," is the instance separator, so a base name containing it would alias another instance's variable.",[11,1140,1141,1142,878,1145,1148,1149,1152,1153,1156,1157,1159,1160,1163,1164,1167],{},"Declaring configuration is also what earns your kind ",[15,1143,1144],{},"named instances",[20,1146,1147],{},"plugin.\u003CpluginId>.\u003ClocalId>#eu"," listed in ",[20,1150,1151],{},"SEND_TRANSPORT_INSTANCES"," reads ",[20,1154,1155],{},"PLUGIN_ACME_TOKEN__EU"," and arrives at ",[20,1158,1056],{}," as ",[20,1161,1162],{},"PLUGIN_ACME_TOKEN"," with ",[20,1165,1166],{},"instanceKey: 'eu'",". A required variable that is missing fails the attempt before your module runs. A transport that declares no configuration keeps reading the plugin's deployment-wide variables and is refused named instances, because a suffix reaches none of them.",[11,1169,1170,1171,1174,1175,1178,1179,1182,1183,1185,1186,1189],{},"Your transport counts as ",[15,1172,1173],{},"configured"," when the plugin's own ",[20,1176,1177],{},"flag.requiredEnvVars"," ",[1180,1181,36],"em",{}," your ",[20,1184,44],{}," are all present — the union, not one or the other. A transport whose token is set inside a plugin nobody enabled is not sendable, and reporting it as ready would put it on a route the dispatch path then refuses. Only your own variables take the ",[20,1187,1188],{},"__\u003CINSTANCEKEY>"," suffix; a flag variable is a deployment-wide switch and is read unsuffixed for every instance.",[11,1191,1192,1195,1196,1198,1199,1201,1202,1204,1205,1207,1208,1211,1212,1214],{},[15,1193,1194],{},"The two lists may not overlap."," A variable named in both ",[20,1197,1177],{}," and a transport's ",[20,1200,44],{},"\u002F",[20,1203,1105],{}," fails manifest validation, because only one of the two scopes takes the suffix: a named instance would be graded configured on ",[20,1206,1155],{}," alone while the deployment-wide variable that gates the whole plugin went unchecked, and every send to that instance would then be refused by the authorization path forever. Name the two separately — ",[20,1209,1210],{},"PLUGIN_ACME_ENABLED"," for the pack, ",[20,1213,1162],{}," for the transport.",[11,1216,1217,1219,1220,1222,1223,377],{},[20,1218,1105],{}," may only accompany at least one ",[20,1221,44],{}," entry. A transport whose whole configuration is optional has no credential a deployment must set, so nothing could decide whether one of its named instances is configured — the manifest is refused rather than silently given ",[20,1224,1225],{},"instances_unsupported",[59,1227,1229],{"id":1228},"the-credentials-form","The credentials form",[11,1231,1232,1235,1236,1239],{},[20,1233,1234],{},"credentialFields"," describes how to ASK an operator for the variables above — the same typed descriptors a core provider's catalog entry carries, in the ",[20,1237,1238],{},"settingsSchema"," vocabulary you already know:",[383,1241,1244],{"className":1242,"code":1243,"language":388,"meta":389},[386],"credentialFields: [\n  { kind: 'secret', key: 'token', label: 'Server token', required: true, envVar: 'PLUGIN_ACME_TOKEN' },\n  { kind: 'string', key: 'stream', label: 'Message stream', envVar: 'PLUGIN_ACME_STREAM' },\n]\n",[20,1245,1243],{"__ignoreMap":389},[11,1247,1248,1249,354,1252,354,1255,354,1258,358,1261,1264,1265,354,1268,1271,1272,1275,1276,878,1279,1282,1283,1285,1286,1288],{},"Kinds are ",[20,1250,1251],{},"string",[20,1253,1254],{},"secret",[20,1256,1257],{},"number",[20,1259,1260],{},"boolean",[20,1262,1263],{},"select"," — the catalog's two composites (",[20,1266,1267],{},"region-select",[20,1269,1270],{},"host-port",") are ours, and a transport expresses the same configuration as their parts. Every field's ",[20,1273,1274],{},"envVar"," must be one this transport declared, matched to the field's own ",[20,1277,1278],{},"required",[20,1280,1281],{},"required: true"," names a member of ",[20,1284,44],{},", anything else names a member of ",[20,1287,1105],{},". That join is what keeps a rendered form from asking for a variable no send reads, or omitting the one that gates the transport.",[11,1290,1291],{},"Descriptors are DESCRIPTIVE ONLY — nothing here decides what a send reads, and no surface renders a plugin's form yet.",[59,1293,1295],{"id":1294},"capabilities","Capabilities",[67,1297,1298,1311],{},[70,1299,1300],{},[73,1301,1302,1305,1308],{},[76,1303,1304],{},"Field",[76,1306,1307],{},"Values",[76,1309,1310],{},"Absent means",[89,1312,1313,1341,1368],{},[73,1314,1315,1320,1325],{},[94,1316,1317],{},[20,1318,1319],{},"supportsCustomReturnPath",[94,1321,1322],{},[20,1323,1324],{},"no",[94,1326,1327,1329,1330,358,1333,1336,1337,1340],{},[20,1328,1324],{},". The only value this tier has, and the field is here so you can spell it. The core catalog's ",[20,1331,1332],{},"yes",[20,1334,1335],{},"probe"," both claim that Owlat's own bounce processor can attribute your bounces, which needs an envelope sender whose local part is a VERP token Owlat signs with a deployment secret — a key no bundled module is handed. Declaring one would grade your arm's bounce data as comparable with our own while the bounces land at your provider, so the manifest is refused instead. Your feedback path is the ",[20,1338,1339],{},"webhook"," below.",[73,1342,1343,1348,1356],{},[94,1344,1345],{},[20,1346,1347],{},"messageIdSource",[94,1349,1350,354,1353],{},[20,1351,1352],{},"provider",[20,1354,1355],{},"composed",[94,1357,1358,1360,1361,1363,1364,1367],{},[20,1359,1352],{},". ",[20,1362,1355],{}," says you echo back the ",[20,1365,1366],{},"Message-ID"," Owlat minted.",[73,1369,1370,1375,1381],{},[94,1371,1372],{},[20,1373,1374],{},"deduplicatesOnIdempotencyKey",[94,1376,1377,354,1379],{},[20,1378,1016],{},[20,1380,971],{},[94,1382,1383,1360,1385,1387,1388,1391,1392,1395],{},[20,1384,971],{},[20,1386,1016],{}," also ",[15,1389,1390],{},"requires"," a ",[20,1393,1394],{},"buildSystemMailExtras"," export that carries the key into your request; the host refuses to register the transport otherwise, because a claim without the wiring turns a double delivery into a \"safe\" retry.",[11,1397,1398,1399,1402,1403,1405,1406,1409,1410,1413,1414,1417,1418,1421,1422,358,1425,1428],{},"Two catalog values are DERIVED from what the contribution carries rather than declared beside it, because a boolean next to the thing it describes could only ever disagree with it. ",[20,1400,1401],{},"hasProviderFeedback"," is true exactly when the contribution carries a ",[20,1404,1339],{},"; ",[20,1407,1408],{},"domainVerification"," is ",[20,1411,1412],{},"api"," exactly when it carries a ",[20,1415,1416],{},"domainIdentity",", and ",[20,1419,1420],{},"none"," otherwise. The core catalog's remaining values — ",[20,1423,1424],{},"acceptanceSemantics: 'accepted'",[20,1426,1427],{},"messageIdSource: 'idempotency-key'"," — are not available to this tier; each turns on host machinery that is not yet general, so they are refused at authoring time rather than mislabelling your sends.",[11,1430,1431,1432,1435,1436,1438,1439,1441,1442,1444,1445,1448],{},"Both extras builders are optional, pure and synchronous — no I\u002FO, no clock, no environment. ",[20,1433,1434],{},"buildDispatchExtras"," receives one governed send's routing facts (idempotency key, message type, delivery domain, IP pool, warm-up overflow, engagement score) and ",[20,1437,1394],{}," receives the caller's idempotency key, when there was one. Whatever you return goes back through your own ",[20,1440,1052],{}," before ",[20,1443,1056],{}," sees it. The return-path host Owlat resolves for its own relay arm is deliberately not among them: it is authorised against ",[1180,1446,1447],{},"that"," relay's published SPF, so stamping it from another transport would fail SPF on the bounce domain of every message you stamped.",[59,1450,1452],{"id":1451},"feedback-webhook","Feedback webhook",[11,1454,1455],{},"A transport that receives bounces, complaints, deliveries and deferrals from its provider declares a second module export on the SAME contribution — its feedback half:",[383,1457,1460],{"className":1458,"code":1459,"language":388,"meta":389},[386],"webhook: {\n  module: { exportPath },\n  signature: \u003Cone of the two host-verified schemes below>,\n  storeRawPayload?: boolean                         \u002F\u002F default false\n}\n\n\u002F\u002F The default. A contract that spells no `scheme` is this one.\nsignature: {\n  scheme?: 'hmac-timestamp-body',\n  header, algorithm, encoding, secretEnvVar,        \u002F\u002F PLUGIN_-prefixed\n  replay: { timestampHeader, toleranceSeconds }     \u002F\u002F ≤ 900\n}\n\n\u002F\u002F Svix — what Resend and many other ESP consoles sign with.\nsignature: {\n  scheme: 'svix',\n  secretEnvVar,                                     \u002F\u002F PLUGIN_-prefixed\n  toleranceSeconds                                  \u002F\u002F ≤ 900\n}\n",[20,1461,1459],{"__ignoreMap":389},[11,1463,1464,1467,1468,1102,1471,1102,1474,1477,1478,1481,1482,1485,1486,358,1489,1492,1493,1496],{},[15,1465,1466],{},"Pick the scheme your provider's console actually signs with."," The Svix arm carries only those two fields because everything else — the ",[20,1469,1470],{},"svix-id",[20,1472,1473],{},"svix-timestamp",[20,1475,1476],{},"svix-signature"," headers, HMAC-SHA256, base64, the signed string ",[20,1479,1480],{},"`${id}.${timestamp}.${body}`",", the ",[20,1483,1484],{},"whsec_"," secret form — belongs to the scheme and is implemented once in the host; a manifest that could spell them could only disagree with the scheme it named. The two remaining host schemes, ",[20,1487,1488],{},"aws-sns",[20,1490,1491],{},"mandrill-form",", are not available to this tier: the first is host infrastructure (a certificate Owlat fetches and caches, bound to a topic the ",[1180,1494,1495],{},"deployment"," owns), the second is a legacy vendor shape signed over the deployment's own public URL.",[11,1498,1499,1500,1503,1504,1507,1508,1511,1512,1514,1515,1517,1518,1521],{},"All of them arrive on one route, ",[20,1501,1502],{},"POST \u002Fwebhooks\u002Fplugin\u002F\u003CpluginId>",". Because the route is keyed by plugin id, ",[15,1505,1506],{},"at most one transport per plugin may declare a webhook","; a second one fails manifest validation, as does a webhook with no ",[20,1509,1510],{},"signature",", a ",[20,1513,1510],{}," naming a scheme Owlat does not verify with, or a default-arm ",[20,1516,1510],{}," with no ",[20,1519,1520],{},"replay"," provisions.",[11,1523,1524,1532,1533,1536,1537,1539],{},[15,1525,1526,1527,18,1530],{},"List ",[20,1528,1529],{},"secretEnvVar",[20,1531,1177],{}," — the manifest validator requires it. Without the secret the host cannot verify anything and answers every delivery ",[20,1534,1535],{},"503",", and a run of non-2xx is what makes a provider deactivate your endpoint; naming the variable in ",[20,1538,44],{}," turns that invisible failure into a plugin an operator simply cannot enable until the secret is set.",[11,1541,1542,1545,1546,1548,1549,1552,1553,1556,1557,1560],{},[15,1543,1544],{},"The host verifies; the plugin parses."," Owlat reads the secret from ",[20,1547,1529],{},", verifies the bytes under the scheme you named — the same code path that verifies Owlat's own core providers, so choosing a word never means supplying a verifier — refuses a timestamp further from now than ",[20,1550,1551],{},"toleranceSeconds",", applies a delivery it has already accepted exactly zero further times (a repeat is answered ",[20,1554,1555],{},"200 { success: true, duplicate: true }",", because the usual cause is a lost acknowledgement rather than an attacker, and a ",[20,1558,1559],{},"4xx"," would count against your endpoint), and rechecks flag, grant and env before the events land. A plugin never sees the secret and never decides whether a request is authentic — the endpoint is unauthenticated and internet-facing, so its strength cannot be a property of third-party code.",[383,1562,1564],{"className":404,"code":1563,"language":406,"meta":389,"style":389},"import type {\n    PluginSendTransportWebhookModule,\n    PluginWebhookFeedbackEvent,\n} from '@owlat\u002Fplugin-kit';\n\n\u002F** Parse ONLY: these bytes are already verified, fresh, and not a replay. *\u002F\nexport const webhook: PluginSendTransportWebhookModule = {\n    parseEvents(rawBody: string): readonly PluginWebhookFeedbackEvent[] {\n        const batch = JSON.parse(rawBody) as { readonly records?: readonly unknown[] };\n        return (batch.records ?? []).flatMap((record) => {\n            const { type, id, at } = record as { type?: string; id?: string; at?: number };\n            if (type !== 'HardBounce' || typeof id !== 'string' || typeof at !== 'number') return [];\n            return [{ kind: 'bounced', providerMessageId: id, at, bounceType: 'hard' } as const];\n        });\n    },\n};\n",[20,1565,1566,1574,1579,1584,1594,1598,1603,1621,1648,1687,1715,1772,1816,1842,1846,1850],{"__ignoreMap":389},[410,1567,1568,1570,1572],{"class":412,"line":413},[410,1569,417],{"class":416},[410,1571,420],{"class":416},[410,1573,424],{"class":423},[410,1575,1576],{"class":412,"line":427},[410,1577,1578],{"class":423},"    PluginSendTransportWebhookModule,\n",[410,1580,1581],{"class":412,"line":433},[410,1582,1583],{"class":423},"    PluginWebhookFeedbackEvent,\n",[410,1585,1586,1588,1590,1592],{"class":412,"line":439},[410,1587,454],{"class":423},[410,1589,457],{"class":416},[410,1591,461],{"class":460},[410,1593,464],{"class":423},[410,1595,1596],{"class":412,"line":445},[410,1597,471],{"emptyLinePlaceholder":470},[410,1599,1600],{"class":412,"line":451},[410,1601,1602],{"class":519},"\u002F** Parse ONLY: these bytes are already verified, fresh, and not a replay. *\u002F\n",[410,1604,1605,1607,1609,1612,1614,1617,1619],{"class":412,"line":467},[410,1606,526],{"class":416},[410,1608,529],{"class":416},[410,1610,1611],{"class":499}," webhook",[410,1613,496],{"class":416},[410,1615,1616],{"class":480}," PluginSendTransportWebhookModule",[410,1618,650],{"class":416},[410,1620,424],{"class":423},[410,1622,1623,1626,1628,1631,1633,1635,1637,1639,1642,1645],{"class":412,"line":474},[410,1624,1625],{"class":557},"    parseEvents",[410,1627,561],{"class":423},[410,1629,1630],{"class":492},"rawBody",[410,1632,496],{"class":416},[410,1634,500],{"class":499},[410,1636,572],{"class":423},[410,1638,496],{"class":416},[410,1640,1641],{"class":416}," readonly",[410,1643,1644],{"class":480}," PluginWebhookFeedbackEvent",[410,1646,1647],{"class":423},"[] {\n",[410,1649,1650,1652,1655,1657,1660,1662,1665,1668,1670,1672,1675,1678,1680,1682,1684],{"class":412,"line":486},[410,1651,645],{"class":416},[410,1653,1654],{"class":499}," batch",[410,1656,650],{"class":416},[410,1658,1659],{"class":499}," JSON",[410,1661,377],{"class":423},[410,1663,1664],{"class":557},"parse",[410,1666,1667],{"class":423},"(rawBody) ",[410,1669,656],{"class":416},[410,1671,659],{"class":423},[410,1673,1674],{"class":416},"readonly",[410,1676,1677],{"class":492}," records",[410,1679,665],{"class":416},[410,1681,1641],{"class":416},[410,1683,569],{"class":499},[410,1685,1686],{"class":423},"[] };\n",[410,1688,1689,1691,1694,1696,1699,1702,1705,1708,1710,1713],{"class":412,"line":505},[410,1690,733],{"class":416},[410,1692,1693],{"class":423}," (batch.records ",[410,1695,912],{"class":416},[410,1697,1698],{"class":423}," []).",[410,1700,1701],{"class":557},"flatMap",[410,1703,1704],{"class":423},"((",[410,1706,1707],{"class":492},"record",[410,1709,962],{"class":423},[410,1711,1712],{"class":416},"=>",[410,1714,424],{"class":423},[410,1716,1717,1720,1722,1725,1727,1730,1732,1735,1738,1740,1743,1745,1747,1749,1751,1753,1755,1757,1759,1761,1763,1765,1767,1770],{"class":412,"line":511},[410,1718,1719],{"class":416},"            const",[410,1721,659],{"class":423},[410,1723,1724],{"class":499},"type",[410,1726,354],{"class":423},[410,1728,1729],{"class":499},"id",[410,1731,354],{"class":423},[410,1733,1734],{"class":499},"at",[410,1736,1737],{"class":423}," } ",[410,1739,549],{"class":416},[410,1741,1742],{"class":423}," record ",[410,1744,656],{"class":416},[410,1746,659],{"class":423},[410,1748,1724],{"class":492},[410,1750,665],{"class":416},[410,1752,500],{"class":499},[410,1754,1405],{"class":423},[410,1756,1729],{"class":492},[410,1758,665],{"class":416},[410,1760,500],{"class":499},[410,1762,1405],{"class":423},[410,1764,1734],{"class":492},[410,1766,665],{"class":416},[410,1768,1769],{"class":499}," number",[410,1771,980],{"class":423},[410,1773,1774,1777,1780,1782,1785,1787,1790,1793,1795,1797,1799,1801,1804,1806,1809,1811,1813],{"class":412,"line":516},[410,1775,1776],{"class":416},"            if",[410,1778,1779],{"class":423}," (type ",[410,1781,596],{"class":416},[410,1783,1784],{"class":460}," 'HardBounce'",[410,1786,602],{"class":416},[410,1788,1789],{"class":416}," typeof",[410,1791,1792],{"class":423}," id ",[410,1794,596],{"class":416},[410,1796,687],{"class":460},[410,1798,602],{"class":416},[410,1800,1789],{"class":416},[410,1802,1803],{"class":423}," at ",[410,1805,596],{"class":416},[410,1807,1808],{"class":460}," 'number'",[410,1810,962],{"class":423},[410,1812,965],{"class":416},[410,1814,1815],{"class":423}," [];\n",[410,1817,1818,1821,1824,1827,1830,1833,1835,1837,1839],{"class":412,"line":523},[410,1819,1820],{"class":416},"            return",[410,1822,1823],{"class":423}," [{ kind: ",[410,1825,1826],{"class":460},"'bounced'",[410,1828,1829],{"class":423},", providerMessageId: id, at, bounceType: ",[410,1831,1832],{"class":460},"'hard'",[410,1834,1737],{"class":423},[410,1836,656],{"class":416},[410,1838,529],{"class":416},[410,1840,1841],{"class":423},"];\n",[410,1843,1844],{"class":412,"line":554},[410,1845,946],{"class":423},[410,1847,1848],{"class":412,"line":581},[410,1849,742],{"class":423},[410,1851,1852],{"class":412,"line":616},[410,1853,1047],{"class":423},[11,1855,1856,1857,354,1860,354,1863,358,1866,1869,1870,1873,1874,1877,1878,1881,1882,1885,1886,1889],{},"The vocabulary is ",[20,1858,1859],{},"delivered",[20,1861,1862],{},"bounced",[20,1864,1865],{},"complained",[20,1867,1868],{},"deferred"," — the facts the send lifecycle and the measurement plane consume. Return ",[20,1871,1872],{},"[]"," for a batch carrying nothing Owlat acts on (a provider's verification ping, event kinds we ignore); throwing is answered ",[20,1875,1876],{},"400"," and the provider may redeliver. Every field you return is re-validated by the host, and ",[20,1879,1880],{},"providerType"," is stamped from the registry, so a batch cannot attribute itself to another transport. Provider message ids in the namespaces Owlat reserves for messages it minted itself — ",[20,1883,1884],{},"pb-"," (Postbox personal mail) and ",[20,1887,1888],{},"rp-probe."," (return-path capability probes) — are refused for the same reason: the id chooses which lane the event is dispatched into.",[11,1891,1892,1895,1896,1899,1900,1902],{},[15,1893,1894],{},"Size your batches to these two limits."," Both are answered ",[20,1897,1898],{},"413"," with nothing applied, and a provider retrying an over-limit delivery gets ",[20,1901,1898],{}," again until it gives up — so the feedback in it is lost rather than delayed. Configure the provider to chunk; Owlat will not split what it refused.",[67,1904,1905,1915],{},[70,1906,1907],{},[73,1908,1909,1912],{},[76,1910,1911],{},"Limit",[76,1913,1914],{},"Ceiling",[89,1916,1917,1928],{},[73,1918,1919,1925],{},[94,1920,1921,1922,572],{},"Request body (",[20,1923,1924],{},"PLUGIN_WEBHOOK_MAX_BODY_BYTES",[94,1926,1927],{},"1 048 576 bytes of UTF-8",[73,1929,1930,1936],{},[94,1931,1932,1933,572],{},"Events returned per delivery (",[20,1934,1935],{},"PLUGIN_WEBHOOK_MAX_BATCH_EVENTS",[94,1937,1938],{},"5 000",[11,1940,1941,1942,1945,1946,1949,1950,1182,1953,1956],{},"This module runs in the Convex ",[15,1943,1944],{},"isolate"," (it is imported by the HTTP router), so it must not import Node builtins. Raw request bodies are retained only when you set ",[20,1947,1948],{},"storeRawPayload: true"," — and when you do, a verified body is kept ",[15,1951,1952],{},"before",[20,1954,1955],{},"parseEvents"," runs, so the deliveries you most need the bytes of (the ones your parse half rejected) are the ones you get.",[59,1958,1960],{"id":1959},"sending-domain-identity","Sending-domain identity",[11,1962,1963],{},"A transport whose provider must be told about a customer's sending domain — and asked whether it may sign for it — declares a third module export on the same contribution:",[383,1965,1968],{"className":1966,"code":1967,"language":388,"meta":389},[386],"domainIdentity: { module: { exportPath } }\n",[20,1969,1967],{"__ignoreMap":389},[11,1971,1972,1973,1976],{},"One field, because everything else about a domain identity is the host's. Declaring it IS ",[20,1974,1975],{},"domainVerification: 'api'"," for this kind, and it registers your transport into the host's relay-identity registry at composition time: from then on the routing gate will ask you whether a From domain may be relayed, the identity backfill will provision domains an operator connected earlier, and the alignment pre-flight will ask you to describe your DKIM\u002FSPF arm.",[11,1978,1979,1178,1982,1985,1986,1989,1990,1992],{},[15,1980,1981],{},"Two calls, and the split is the one every identity API draws.",[20,1983,1984],{},"registerDomain"," is the WRITE — create or confirm the identity at the provider, idempotently, because the host re-registers on an operator's explicit repair. ",[20,1987,1988],{},"checkDomain"," is the READ the host repeats on its own schedule (daily once verified, hourly while DNS is outstanding) to keep the proof fresh. Both are handed the transport's resolved configuration and must read credentials from it rather than from ",[20,1991,1123],{},", and neither may be slow: the host calls them from a scheduled action, and a hung call is an unrefreshed proof that ages out.",[11,1994,1995,1178,1998,2001,2002,2005,2006,2009,2010,2013,2014,2016],{},[15,1996,1997],{},"Three answers, because the host writes each one differently.",[20,1999,2000],{},"ok"," carries observations and is the only outcome that is EVIDENCE — the only one that refreshes the proof's age. ",[20,2003,2004],{},"auth_failed"," says the provider rejected this deployment's credential: terminal until an operator fixes it, recorded as such, and it does ",[15,2007,2008],{},"not"," overwrite the SPF\u002FDKIM verdicts already stored, because a bad API key is not evidence that the operator's DNS stopped being valid. ",[20,2011,2012],{},"unavailable"," says the provider did not answer: evidence of nothing, so only the retry moves. A module that throws is read as ",[20,2015,2012],{}," — the host cannot tell a bug in your code from an outage, and the conservative reading is the one that neither condemns a credential nor refreshes a proof.",[383,2018,2020],{"className":404,"code":2019,"language":406,"meta":389,"style":389},"import type {\n    PluginDomainIdentityResult,\n    PluginSendTransportDomainIdentityModule,\n} from '@owlat\u002Fplugin-kit';\n\n\u002F** Observations ONLY: the host derives the status and owns the freshness bound. *\u002F\nexport const domainIdentity: PluginSendTransportDomainIdentityModule = {\n    \u002F\u002F The credential comes from the resolved configuration, never from\n    \u002F\u002F `process.env`: an environment read resolves the deployment-default instance\n    \u002F\u002F whichever instance the host meant.\n    registerDomain: (domain, config) => askProvider('POST', domain, config.env['PLUGIN_ACME_TOKEN']),\n    checkDomain: (domain, config) => askProvider('GET', domain, config.env['PLUGIN_ACME_TOKEN']),\n};\n\nasync function askProvider(\n    method: 'GET' | 'POST',\n    domain: string,\n    token: string | undefined\n): Promise\u003CPluginDomainIdentityResult> {\n    const response = await fetch(`https:\u002F\u002Fapi.example.net\u002Fdomains\u002F${domain}`, {\n        method,\n        headers: { Authorization: `Bearer ${token ?? ''}` },\n    });\n    \u002F\u002F Three distinguishable answers, because the host writes each one differently:\n    \u002F\u002F only `ok` refreshes the proof's age, and only `auth_failed` condemns a key.\n    if (response.status === 401 || response.status === 403) {\n        return { outcome: 'auth_failed', error: 'the provider rejected the token' };\n    }\n    if (!response.ok) return { outcome: 'unavailable', error: `HTTP ${response.status}` };\n    const body = (await response.json()) as {\n        readonly owned?: boolean;\n        readonly spf?: boolean;\n        readonly dkim?: boolean;\n        readonly selector?: string;\n    };\n    return {\n        outcome: 'ok',\n        state: {\n            isOwnershipVerified: body.owned === true,\n            spf: { isValid: body.spf === true },\n            dkim: { isValid: body.dkim === true },\n            dkimSelectors: body.selector ? [body.selector] : [],\n            spfMechanisms: ['include:spf.example.net'],\n        },\n    };\n}\n",[20,2021,2022,2030,2035,2040,2050,2054,2059,2077,2082,2087,2092,2126,2156,2160,2164,2176,2194,2205,2219,2234,2260,2265,2283,2288,2293,2298,2322,2340,2345,2379,2406,2421,2434,2447,2460,2465,2472,2482,2487,2499,2510,2521,2537,2548,2553,2557],{"__ignoreMap":389},[410,2023,2024,2026,2028],{"class":412,"line":413},[410,2025,417],{"class":416},[410,2027,420],{"class":416},[410,2029,424],{"class":423},[410,2031,2032],{"class":412,"line":427},[410,2033,2034],{"class":423},"    PluginDomainIdentityResult,\n",[410,2036,2037],{"class":412,"line":433},[410,2038,2039],{"class":423},"    PluginSendTransportDomainIdentityModule,\n",[410,2041,2042,2044,2046,2048],{"class":412,"line":439},[410,2043,454],{"class":423},[410,2045,457],{"class":416},[410,2047,461],{"class":460},[410,2049,464],{"class":423},[410,2051,2052],{"class":412,"line":445},[410,2053,471],{"emptyLinePlaceholder":470},[410,2055,2056],{"class":412,"line":451},[410,2057,2058],{"class":519},"\u002F** Observations ONLY: the host derives the status and owns the freshness bound. *\u002F\n",[410,2060,2061,2063,2065,2068,2070,2073,2075],{"class":412,"line":467},[410,2062,526],{"class":416},[410,2064,529],{"class":416},[410,2066,2067],{"class":499}," domainIdentity",[410,2069,496],{"class":416},[410,2071,2072],{"class":480}," PluginSendTransportDomainIdentityModule",[410,2074,650],{"class":416},[410,2076,424],{"class":423},[410,2078,2079],{"class":412,"line":474},[410,2080,2081],{"class":519},"    \u002F\u002F The credential comes from the resolved configuration, never from\n",[410,2083,2084],{"class":412,"line":486},[410,2085,2086],{"class":519},"    \u002F\u002F `process.env`: an environment read resolves the deployment-default instance\n",[410,2088,2089],{"class":412,"line":505},[410,2090,2091],{"class":519},"    \u002F\u002F whichever instance the host meant.\n",[410,2093,2094,2097,2100,2103,2105,2107,2109,2111,2114,2116,2118,2121,2123],{"class":412,"line":511},[410,2095,2096],{"class":557},"    registerDomain",[410,2098,2099],{"class":423},": (",[410,2101,2102],{"class":492},"domain",[410,2104,354],{"class":423},[410,2106,895],{"class":492},[410,2108,962],{"class":423},[410,2110,1712],{"class":416},[410,2112,2113],{"class":557}," askProvider",[410,2115,561],{"class":423},[410,2117,861],{"class":460},[410,2119,2120],{"class":423},", domain, config.env[",[410,2122,906],{"class":460},[410,2124,2125],{"class":423},"]),\n",[410,2127,2128,2131,2133,2135,2137,2139,2141,2143,2145,2147,2150,2152,2154],{"class":412,"line":516},[410,2129,2130],{"class":557},"    checkDomain",[410,2132,2099],{"class":423},[410,2134,2102],{"class":492},[410,2136,354],{"class":423},[410,2138,895],{"class":492},[410,2140,962],{"class":423},[410,2142,1712],{"class":416},[410,2144,2113],{"class":557},[410,2146,561],{"class":423},[410,2148,2149],{"class":460},"'GET'",[410,2151,2120],{"class":423},[410,2153,906],{"class":460},[410,2155,2125],{"class":423},[410,2157,2158],{"class":412,"line":523},[410,2159,1047],{"class":423},[410,2161,2162],{"class":412,"line":554},[410,2163,471],{"emptyLinePlaceholder":470},[410,2165,2166,2169,2172,2174],{"class":412,"line":581},[410,2167,2168],{"class":416},"async",[410,2170,2171],{"class":416}," function",[410,2173,2113],{"class":557},[410,2175,759],{"class":423},[410,2177,2178,2181,2183,2186,2189,2192],{"class":412,"line":616},[410,2179,2180],{"class":492},"    method",[410,2182,496],{"class":416},[410,2184,2185],{"class":460}," 'GET'",[410,2187,2188],{"class":416}," |",[410,2190,2191],{"class":460}," 'POST'",[410,2193,773],{"class":423},[410,2195,2196,2199,2201,2203],{"class":412,"line":636},[410,2197,2198],{"class":492},"    domain",[410,2200,496],{"class":416},[410,2202,500],{"class":499},[410,2204,773],{"class":423},[410,2206,2207,2210,2212,2214,2216],{"class":412,"line":642},[410,2208,2209],{"class":492},"    token",[410,2211,496],{"class":416},[410,2213,500],{"class":499},[410,2215,2188],{"class":416},[410,2217,2218],{"class":499}," undefined\n",[410,2220,2221,2223,2225,2227,2229,2232],{"class":412,"line":673},[410,2222,572],{"class":423},[410,2224,496],{"class":416},[410,2226,825],{"class":480},[410,2228,540],{"class":423},[410,2230,2231],{"class":480},"PluginDomainIdentityResult",[410,2233,833],{"class":423},[410,2235,2236,2239,2241,2243,2245,2247,2249,2252,2254,2257],{"class":412,"line":709},[410,2237,2238],{"class":416},"    const",[410,2240,841],{"class":499},[410,2242,650],{"class":416},[410,2244,846],{"class":416},[410,2246,849],{"class":557},[410,2248,561],{"class":423},[410,2250,2251],{"class":460},"`https:\u002F\u002Fapi.example.net\u002Fdomains\u002F${",[410,2253,2102],{"class":423},[410,2255,2256],{"class":460},"}`",[410,2258,2259],{"class":423},", {\n",[410,2261,2262],{"class":412,"line":725},[410,2263,2264],{"class":423},"        method,\n",[410,2266,2267,2270,2272,2275,2278,2280],{"class":412,"line":730},[410,2268,2269],{"class":423},"        headers: { Authorization: ",[410,2271,892],{"class":460},[410,2273,2274],{"class":423},"token",[410,2276,2277],{"class":416}," ??",[410,2279,915],{"class":460},[410,2281,2282],{"class":423}," },\n",[410,2284,2285],{"class":412,"line":739},[410,2286,2287],{"class":423},"    });\n",[410,2289,2290],{"class":412,"line":745},[410,2291,2292],{"class":519},"    \u002F\u002F Three distinguishable answers, because the host writes each one differently:\n",[410,2294,2295],{"class":412,"line":750},[410,2296,2297],{"class":519},"    \u002F\u002F only `ok` refreshes the proof's age, and only `auth_failed` condemns a key.\n",[410,2299,2300,2303,2305,2307,2310,2312,2315,2317,2320],{"class":412,"line":762},[410,2301,2302],{"class":416},"    if",[410,2304,954],{"class":423},[410,2306,607],{"class":416},[410,2308,2309],{"class":499}," 401",[410,2311,602],{"class":416},[410,2313,2314],{"class":423}," response.status ",[410,2316,607],{"class":416},[410,2318,2319],{"class":499}," 403",[410,2321,613],{"class":423},[410,2323,2324,2326,2329,2332,2335,2338],{"class":412,"line":776},[410,2325,733],{"class":416},[410,2327,2328],{"class":423}," { outcome: ",[410,2330,2331],{"class":460},"'auth_failed'",[410,2333,2334],{"class":423},", error: ",[410,2336,2337],{"class":460},"'the provider rejected the token'",[410,2339,980],{"class":423},[410,2341,2342],{"class":412,"line":788},[410,2343,2344],{"class":423},"    }\n",[410,2346,2347,2349,2351,2353,2355,2357,2359,2362,2364,2367,2370,2372,2375,2377],{"class":412,"line":794},[410,2348,2302],{"class":416},[410,2350,587],{"class":423},[410,2352,990],{"class":416},[410,2354,993],{"class":423},[410,2356,965],{"class":416},[410,2358,2328],{"class":423},[410,2360,2361],{"class":460},"'unavailable'",[410,2363,2334],{"class":423},[410,2365,2366],{"class":460},"`HTTP ${",[410,2368,2369],{"class":423},"response",[410,2371,377],{"class":460},[410,2373,2374],{"class":423},"status",[410,2376,2256],{"class":460},[410,2378,980],{"class":423},[410,2380,2381,2383,2386,2388,2390,2393,2396,2399,2402,2404],{"class":412,"line":800},[410,2382,2238],{"class":416},[410,2384,2385],{"class":499}," body",[410,2387,650],{"class":416},[410,2389,587],{"class":423},[410,2391,2392],{"class":416},"await",[410,2394,2395],{"class":423}," response.",[410,2397,2398],{"class":557},"json",[410,2400,2401],{"class":423},"()) ",[410,2403,656],{"class":416},[410,2405,424],{"class":423},[410,2407,2408,2411,2414,2416,2419],{"class":412,"line":806},[410,2409,2410],{"class":416},"        readonly",[410,2412,2413],{"class":492}," owned",[410,2415,665],{"class":416},[410,2417,2418],{"class":499}," boolean",[410,2420,464],{"class":423},[410,2422,2423,2425,2428,2430,2432],{"class":412,"line":817},[410,2424,2410],{"class":416},[410,2426,2427],{"class":492}," spf",[410,2429,665],{"class":416},[410,2431,2418],{"class":499},[410,2433,464],{"class":423},[410,2435,2436,2438,2441,2443,2445],{"class":412,"line":836},[410,2437,2410],{"class":416},[410,2439,2440],{"class":492}," dkim",[410,2442,665],{"class":416},[410,2444,2418],{"class":499},[410,2446,464],{"class":423},[410,2448,2449,2451,2454,2456,2458],{"class":412,"line":855},[410,2450,2410],{"class":416},[410,2452,2453],{"class":492}," selector",[410,2455,665],{"class":416},[410,2457,500],{"class":499},[410,2459,464],{"class":423},[410,2461,2462],{"class":412,"line":866},[410,2463,2464],{"class":423},"    };\n",[410,2466,2467,2470],{"class":412,"line":872},[410,2468,2469],{"class":416},"    return",[410,2471,424],{"class":423},[410,2473,2474,2477,2480],{"class":412,"line":886},[410,2475,2476],{"class":423},"        outcome: ",[410,2478,2479],{"class":460},"'ok'",[410,2481,773],{"class":423},[410,2483,2484],{"class":412,"line":920},[410,2485,2486],{"class":423},"        state: {\n",[410,2488,2489,2492,2494,2497],{"class":412,"line":926},[410,2490,2491],{"class":423},"            isOwnershipVerified: body.owned ",[410,2493,607],{"class":416},[410,2495,2496],{"class":499}," true",[410,2498,773],{"class":423},[410,2500,2501,2504,2506,2508],{"class":412,"line":943},[410,2502,2503],{"class":423},"            spf: { isValid: body.spf ",[410,2505,607],{"class":416},[410,2507,2496],{"class":499},[410,2509,2282],{"class":423},[410,2511,2512,2515,2517,2519],{"class":412,"line":949},[410,2513,2514],{"class":423},"            dkim: { isValid: body.dkim ",[410,2516,607],{"class":416},[410,2518,2496],{"class":499},[410,2520,2282],{"class":423},[410,2522,2523,2526,2529,2532,2534],{"class":412,"line":983},[410,2524,2525],{"class":423},"            dkimSelectors: body.selector ",[410,2527,2528],{"class":416},"?",[410,2530,2531],{"class":423}," [body.selector] ",[410,2533,496],{"class":416},[410,2535,2536],{"class":423}," [],\n",[410,2538,2539,2542,2545],{"class":412,"line":1009},[410,2540,2541],{"class":423},"            spfMechanisms: [",[410,2543,2544],{"class":460},"'include:spf.example.net'",[410,2546,2547],{"class":423},"],\n",[410,2549,2550],{"class":412,"line":1039},[410,2551,2552],{"class":423},"        },\n",[410,2554,2555],{"class":412,"line":1044},[410,2556,2464],{"class":423},[410,2558,2560],{"class":412,"line":2559},46,[410,2561,508],{"class":423},[11,2563,2564,2567,2568,2570,2571,2574],{},[15,2565,2566],{},"You report observations; the host decides."," There is no ",[20,2569,2374],{}," field you can return. Owlat derives it from your three observations — ownership confirmed, SPF valid, DKIM valid, and at least one selector to resolve — so \"verified\" means the same thing at every relay tier, and a module cannot report a domain verified while telling us its DKIM record is invalid. The freshness bound is a host constant (",[20,2572,2573],{},"PLUGIN_RELAY_PROOF_MAX_AGE_MS",", seven days) and not a manifest field: it is the only thing that retires a proof for an identity revoked at your end while our row survives, so a declarable window would be a declarable weakening of it. Where the identity row lives, what a failed call may overwrite, and when to ask again are the host's too.",[11,2576,2577,358,2580,2583,2584,2586,2587,2590,2591,2593,2594,2597,2598,2601,2602,2605,2606,2609],{},[20,2578,2579],{},"dkimSelectors",[20,2581,2582],{},"spfMechanisms"," are carried on the STATE rather than declared in the manifest because both provider shapes are real — one shared account-wide selector, or per-domain tokens that only exist after registration. They are what the dual-transport alignment pre-flight resolves live. The two empty lists do NOT mean the same thing, so it is worth being exact: no ",[20,2585,2579],{}," means \"we cannot describe this domain's signing identity\", which is a HOLD on the ramp and never an opened gate (the domain does not reach ",[20,2588,2589],{},"verified"," and no reference arm is described for it). No ",[20,2592,2582],{}," means \"this relay needs no SPF authorization on the customer's From domain\" — the pre-flight merges your mechanisms with the own MTA's into one required set, so contributing none simply drops your requirement and the SPF check can pass on a record that does not name you. Return them whenever you know them; a shared include you cannot read out of your API is better hard-coded than omitted. At most 8 of each (",[20,2595,2596],{},"PLUGIN_DOMAIN_IDENTITY_MAX_DNS_FACTS","), each at most 255 characters (",[20,2599,2600],{},"PLUGIN_DOMAIN_IDENTITY_MAX_DNS_FACT_LENGTH","), and anything over is dropped rather than refused. The ",[20,2603,2604],{},"error"," on a failed outcome or an invalid record is provider free text kept for an operator log line only, truncated at 500 characters (",[20,2607,2608],{},"PLUGIN_DOMAIN_IDENTITY_MAX_ERROR_LENGTH",") and never rendered as guidance.",[11,2611,2612,2613,2616,2617,2619,2620,2623],{},"Like the webhook half, this module is imported by code on the enqueue path and must not import Node builtins; its calls are HTTP and ",[20,2614,2615],{},"fetch"," is available. Every call is re-authorized first — flag on, ",[20,2618,103],{}," still granted, configuration present — and audited as ",[20,2621,2622],{},"transport.domain_identity",", because it spends this deployment's credential at your provider under a customer's domain name. Turning the plugin off stops it, visibly.",[54,2625,2627],{"id":2626},"agent-steps","Agent steps",[383,2629,2632],{"className":2630,"code":2631,"language":388,"meta":389},[386],"agentSteps: [{ id, after, module: { exportPath }, lifecycleEdges: [] }]\n",[20,2633,2631],{"__ignoreMap":389},[11,2635,2636,2639,2640,354,2643,354,2646,354,2649,354,2652,2655],{},[20,2637,2638],{},"after"," is a core step (",[20,2641,2642],{},"security_scan",[20,2644,2645],{},"context_retrieval",[20,2647,2648],{},"classify",[20,2650,2651],{},"clarify",[20,2653,2654],{},"draft",") or another plugin step. Codegen rejects unknown or terminal anchors, duplicate kinds, insertion cycles, and edges outside the host's restrict-only policy.",[383,2657,2659],{"className":404,"code":2658,"language":406,"meta":389,"style":389},"import type {\n    PluginAgentStepInput,\n    PluginAgentStepModule,\n    PluginAgentStepResult,\n} from '@owlat\u002Fplugin-kit';\n\nexport const agentStep: PluginAgentStepModule = {\n    async execute(input: PluginAgentStepInput): Promise\u003CPluginAgentStepResult> {\n        if (input.subject.toLowerCase().startsWith('[auto-reply]')) {\n            \u002F\u002F Restrict-only: a step may request a DECLARED caution edge, but never\n            \u002F\u002F choose the next step, approve, or send.\n            return { kind: 'caution', to: 'archived', reason: 'vendor auto-reply' };\n        }\n        return { kind: 'continue' };\n    },\n};\n",[20,2660,2661,2669,2674,2679,2684,2694,2698,2716,2745,2767,2772,2777,2801,2805,2816,2820],{"__ignoreMap":389},[410,2662,2663,2665,2667],{"class":412,"line":413},[410,2664,417],{"class":416},[410,2666,420],{"class":416},[410,2668,424],{"class":423},[410,2670,2671],{"class":412,"line":427},[410,2672,2673],{"class":423},"    PluginAgentStepInput,\n",[410,2675,2676],{"class":412,"line":433},[410,2677,2678],{"class":423},"    PluginAgentStepModule,\n",[410,2680,2681],{"class":412,"line":439},[410,2682,2683],{"class":423},"    PluginAgentStepResult,\n",[410,2685,2686,2688,2690,2692],{"class":412,"line":445},[410,2687,454],{"class":423},[410,2689,457],{"class":416},[410,2691,461],{"class":460},[410,2693,464],{"class":423},[410,2695,2696],{"class":412,"line":451},[410,2697,471],{"emptyLinePlaceholder":470},[410,2699,2700,2702,2704,2707,2709,2712,2714],{"class":412,"line":467},[410,2701,526],{"class":416},[410,2703,529],{"class":416},[410,2705,2706],{"class":499}," agentStep",[410,2708,496],{"class":416},[410,2710,2711],{"class":480}," PluginAgentStepModule",[410,2713,650],{"class":416},[410,2715,424],{"class":423},[410,2717,2718,2720,2723,2725,2727,2729,2732,2734,2736,2738,2740,2743],{"class":412,"line":474},[410,2719,753],{"class":416},[410,2721,2722],{"class":557}," execute",[410,2724,561],{"class":423},[410,2726,564],{"class":492},[410,2728,496],{"class":416},[410,2730,2731],{"class":480}," PluginAgentStepInput",[410,2733,572],{"class":423},[410,2735,496],{"class":416},[410,2737,825],{"class":480},[410,2739,540],{"class":423},[410,2741,2742],{"class":480},"PluginAgentStepResult",[410,2744,833],{"class":423},[410,2746,2747,2749,2752,2755,2758,2760,2762,2765],{"class":412,"line":486},[410,2748,584],{"class":416},[410,2750,2751],{"class":423}," (input.subject.",[410,2753,2754],{"class":557},"toLowerCase",[410,2756,2757],{"class":423},"().",[410,2759,698],{"class":557},[410,2761,561],{"class":423},[410,2763,2764],{"class":460},"'[auto-reply]'",[410,2766,706],{"class":423},[410,2768,2769],{"class":412,"line":505},[410,2770,2771],{"class":519},"            \u002F\u002F Restrict-only: a step may request a DECLARED caution edge, but never\n",[410,2773,2774],{"class":412,"line":511},[410,2775,2776],{"class":519},"            \u002F\u002F choose the next step, approve, or send.\n",[410,2778,2779,2781,2784,2787,2790,2793,2796,2799],{"class":412,"line":516},[410,2780,1820],{"class":416},[410,2782,2783],{"class":423}," { kind: ",[410,2785,2786],{"class":460},"'caution'",[410,2788,2789],{"class":423},", to: ",[410,2791,2792],{"class":460},"'archived'",[410,2794,2795],{"class":423},", reason: ",[410,2797,2798],{"class":460},"'vendor auto-reply'",[410,2800,980],{"class":423},[410,2802,2803],{"class":412,"line":523},[410,2804,639],{"class":423},[410,2806,2807,2809,2811,2814],{"class":412,"line":554},[410,2808,733],{"class":416},[410,2810,2783],{"class":423},[410,2812,2813],{"class":460},"'continue'",[410,2815,980],{"class":423},[410,2817,2818],{"class":412,"line":581},[410,2819,742],{"class":423},[410,2821,2822],{"class":412,"line":616},[410,2823,1047],{"class":423},[11,2825,2826,2827,2830],{},"Five of the six built-in steps map to three host-owned placements; the sixth, the terminal ",[20,2828,2829],{},"route"," step, has no placement and cannot be used as an anchor:",[67,2832,2833,2846],{},[70,2834,2835],{},[73,2836,2837,2840,2843],{},[76,2838,2839],{},"Placement",[76,2841,2842],{},"Anchors",[76,2844,2845],{},"Edges a descendant may request",[89,2847,2848,2869,2888],{},[73,2849,2850,2855,2858],{},[94,2851,2852],{},[20,2853,2854],{},"classification",[94,2856,2857],{},"security scan, context retrieval, classify",[94,2859,2860,354,2863,2865,2866],{},[20,2861,2862],{},"archived",[20,2864,183],{}," from ",[20,2867,2868],{},"classifying",[73,2870,2871,2876,2878],{},[94,2872,2873],{},[20,2874,2875],{},"before_draft",[94,2877,2651],{},[94,2879,2880,354,2882,2865,2884,2887],{},[20,2881,2862],{},[20,2883,183],{},[20,2885,2886],{},"drafting"," (no draft is guaranteed to exist yet)",[73,2889,2890,2895,2897],{},[94,2891,2892],{},[20,2893,2894],{},"after_draft",[94,2896,2654],{},[94,2898,2899,354,2901,2903,2904,2907],{},[20,2900,2862],{},[20,2902,183],{},", and the ",[20,2905,2906],{},"drafting → draft_ready"," review edge",[11,2909,2910,2911,2914,2915,2918],{},"A plugin chained after another plugin inherits its placement. No plugin may request ",[20,2912,2913],{},"approved"," or ",[20,2916,2917],{},"sent",", choose the next step, run before the security scan, or edit the core legality graph. The walker always resumes the original core continuation; invalid output or an exception fails the lifecycle closed.",[54,2920,2922],{"id":2921},"draft-strategies","Draft strategies",[383,2924,2927],{"className":2925,"code":2926,"language":388,"meta":389},[386],"draftStrategies: [{ id, label, module: { exportPath }, timeoutMs \u002F* ≤ 30 000 *\u002F }]\n",[20,2928,2926],{"__ignoreMap":389},[11,2930,2931,2932,2934,2935,2937,2938,2940,2941,377],{},"A strategy replaces ",[15,2933,1116],{}," primary generation. Selection order is contact, then mailbox, then classification, then the built-in ",[20,2936,146],{},". The module receives a frozen bounded projection plus the attributed, budgeted LLM service (which separately requires ",[20,2939,353],{},") and returns ",[20,2942,2943],{},"{ draftBody }",[11,2945,2946,2947,2949],{},"Owlat keeps assembled-context injection scanning, the quality self-check, review options, persistence, routing, autonomy, and sending outside the strategy. Denial, timeout, failure, stale selection, or malformed\u002Foversized\u002Finjection-like output all fall back once to ",[20,2948,146],{},". See ADR-0050.",[54,2951,2953,2954,572],{"id":2952},"autonomy-gates-sendgates","Autonomy gates (",[20,2955,154],{},[383,2957,2960],{"className":2958,"code":2959,"language":388,"meta":389},[386],"sendGates: [{ id, label, module: { exportPath }, timeoutMs \u002F* ≤ 30 000 *\u002F }]\n",[20,2961,2959],{"__ignoreMap":389},[11,2963,2964,2965,2967,2968,2971],{},"Plugin gates run ",[15,2966,2638],{}," every immutable core route-time gate, in generated catalog order, once at the route-time approval boundary. The module receives a frozen, bounded mail projection and an ",[20,2969,2970],{},"AbortSignal"," — no host service, credential, or Convex context.",[11,2973,2974,2975,2914,2978,2981],{},"The result type is structurally incapable of approval: ",[20,2976,2977],{},"{ outcome: 'no-objection' }",[20,2979,2980],{},"{ outcome: 'objection', reason }",". Disabled, revoked, stale, missing, timed-out, failed, or malformed gates all conservatively route the reply to human review. Audit records fixed operation\u002Foutcome\u002Freason codes only. See ADR-0051.",[54,2983,2985],{"id":2984},"automations","Automations",[383,2987,2990],{"className":2988,"code":2989,"language":388,"meta":389},[386],"automationTriggers: [{ id, label, description, icon, module: { exportPath } }]\nautomationSteps:    [{ id, label, description, icon, module: { exportPath } }]\nautomationConditions: [{ id, label, description, icon, module: { exportPath } }]\n",[20,2991,2989],{"__ignoreMap":389},[11,2993,2994],{},"Each registry has its own capability so a grant can enable one without the others. Editor metadata is copied verbatim into the generated catalog so the automation builder can render a contribution without importing plugin code; the frontend still treats it as untrusted text.",[383,2996,2998],{"className":404,"code":2997,"language":406,"meta":389,"style":389},"import type {\n    PluginAutomationStepInput,\n    PluginAutomationStepModule,\n    PluginAutomationStepResult,\n} from '@owlat\u002Fplugin-kit';\n\ninterface NotifyConfig {\n    readonly channel: string;\n}\n\nexport const automationStep: PluginAutomationStepModule\u003CNotifyConfig> = {\n    parseConfig(raw: unknown): NotifyConfig {\n        const channel = (raw as { channel?: unknown } | null)?.channel;\n        if (typeof channel !== 'string' || channel.length === 0) {\n            throw new TypeError('config.channel is required');\n        }\n        return { channel };\n    },\n\n    async execute(\n        input: PluginAutomationStepInput,\n        config: NotifyConfig\n    ): Promise\u003CPluginAutomationStepResult> {\n        if (!input.contactEmail.includes('@')) {\n            return { kind: 'failed', reason: 'contact has no address' };\n        }\n        await Promise.resolve(config.channel);\n        return { kind: 'completed' };\n    },\n};\n",[20,2999,3000,3008,3013,3018,3023,3033,3037,3046,3059,3063,3067,3092,3114,3146,3177,3192,3196,3203,3207,3211,3219,3231,3240,3255,3276,3292,3296,3311,3322,3326],{"__ignoreMap":389},[410,3001,3002,3004,3006],{"class":412,"line":413},[410,3003,417],{"class":416},[410,3005,420],{"class":416},[410,3007,424],{"class":423},[410,3009,3010],{"class":412,"line":427},[410,3011,3012],{"class":423},"    PluginAutomationStepInput,\n",[410,3014,3015],{"class":412,"line":433},[410,3016,3017],{"class":423},"    PluginAutomationStepModule,\n",[410,3019,3020],{"class":412,"line":439},[410,3021,3022],{"class":423},"    PluginAutomationStepResult,\n",[410,3024,3025,3027,3029,3031],{"class":412,"line":445},[410,3026,454],{"class":423},[410,3028,457],{"class":416},[410,3030,461],{"class":460},[410,3032,464],{"class":423},[410,3034,3035],{"class":412,"line":451},[410,3036,471],{"emptyLinePlaceholder":470},[410,3038,3039,3041,3044],{"class":412,"line":467},[410,3040,477],{"class":416},[410,3042,3043],{"class":480}," NotifyConfig",[410,3045,424],{"class":423},[410,3047,3048,3050,3053,3055,3057],{"class":412,"line":474},[410,3049,489],{"class":416},[410,3051,3052],{"class":492}," channel",[410,3054,496],{"class":416},[410,3056,500],{"class":499},[410,3058,464],{"class":423},[410,3060,3061],{"class":412,"line":486},[410,3062,508],{"class":423},[410,3064,3065],{"class":412,"line":505},[410,3066,471],{"emptyLinePlaceholder":470},[410,3068,3069,3071,3073,3076,3078,3081,3083,3086,3088,3090],{"class":412,"line":511},[410,3070,526],{"class":416},[410,3072,529],{"class":416},[410,3074,3075],{"class":499}," automationStep",[410,3077,496],{"class":416},[410,3079,3080],{"class":480}," PluginAutomationStepModule",[410,3082,540],{"class":423},[410,3084,3085],{"class":480},"NotifyConfig",[410,3087,546],{"class":423},[410,3089,549],{"class":416},[410,3091,424],{"class":423},[410,3093,3094,3097,3099,3102,3104,3106,3108,3110,3112],{"class":412,"line":516},[410,3095,3096],{"class":557},"    parseConfig",[410,3098,561],{"class":423},[410,3100,3101],{"class":492},"raw",[410,3103,496],{"class":416},[410,3105,569],{"class":499},[410,3107,572],{"class":423},[410,3109,496],{"class":416},[410,3111,3043],{"class":480},[410,3113,424],{"class":423},[410,3115,3116,3118,3120,3122,3125,3127,3129,3132,3134,3136,3138,3141,3143],{"class":412,"line":523},[410,3117,645],{"class":416},[410,3119,3052],{"class":499},[410,3121,650],{"class":416},[410,3123,3124],{"class":423}," (raw ",[410,3126,656],{"class":416},[410,3128,659],{"class":423},[410,3130,3131],{"class":492},"channel",[410,3133,665],{"class":416},[410,3135,569],{"class":499},[410,3137,1737],{"class":423},[410,3139,3140],{"class":416},"|",[410,3142,610],{"class":499},[410,3144,3145],{"class":423},")?.channel;\n",[410,3147,3148,3150,3152,3154,3157,3159,3161,3163,3166,3169,3172,3175],{"class":412,"line":554},[410,3149,584],{"class":416},[410,3151,587],{"class":423},[410,3153,590],{"class":416},[410,3155,3156],{"class":423}," channel ",[410,3158,596],{"class":416},[410,3160,687],{"class":460},[410,3162,602],{"class":416},[410,3164,3165],{"class":423}," channel.",[410,3167,3168],{"class":499},"length",[410,3170,3171],{"class":416}," ===",[410,3173,3174],{"class":499}," 0",[410,3176,613],{"class":423},[410,3178,3179,3181,3183,3185,3187,3190],{"class":412,"line":581},[410,3180,619],{"class":416},[410,3182,622],{"class":416},[410,3184,625],{"class":557},[410,3186,561],{"class":423},[410,3188,3189],{"class":460},"'config.channel is required'",[410,3191,633],{"class":423},[410,3193,3194],{"class":412,"line":616},[410,3195,639],{"class":423},[410,3197,3198,3200],{"class":412,"line":636},[410,3199,733],{"class":416},[410,3201,3202],{"class":423}," { channel };\n",[410,3204,3205],{"class":412,"line":642},[410,3206,742],{"class":423},[410,3208,3209],{"class":412,"line":673},[410,3210,471],{"emptyLinePlaceholder":470},[410,3212,3213,3215,3217],{"class":412,"line":709},[410,3214,753],{"class":416},[410,3216,2722],{"class":557},[410,3218,759],{"class":423},[410,3220,3221,3224,3226,3229],{"class":412,"line":725},[410,3222,3223],{"class":492},"        input",[410,3225,496],{"class":416},[410,3227,3228],{"class":480}," PluginAutomationStepInput",[410,3230,773],{"class":423},[410,3232,3233,3235,3237],{"class":412,"line":730},[410,3234,809],{"class":492},[410,3236,496],{"class":416},[410,3238,3239],{"class":480}," NotifyConfig\n",[410,3241,3242,3244,3246,3248,3250,3253],{"class":412,"line":739},[410,3243,820],{"class":423},[410,3245,496],{"class":416},[410,3247,825],{"class":480},[410,3249,540],{"class":423},[410,3251,3252],{"class":480},"PluginAutomationStepResult",[410,3254,833],{"class":423},[410,3256,3257,3259,3261,3263,3266,3269,3271,3274],{"class":412,"line":745},[410,3258,584],{"class":416},[410,3260,587],{"class":423},[410,3262,990],{"class":416},[410,3264,3265],{"class":423},"input.contactEmail.",[410,3267,3268],{"class":557},"includes",[410,3270,561],{"class":423},[410,3272,3273],{"class":460},"'@'",[410,3275,706],{"class":423},[410,3277,3278,3280,3282,3285,3287,3290],{"class":412,"line":750},[410,3279,1820],{"class":416},[410,3281,2783],{"class":423},[410,3283,3284],{"class":460},"'failed'",[410,3286,2795],{"class":423},[410,3288,3289],{"class":460},"'contact has no address'",[410,3291,980],{"class":423},[410,3293,3294],{"class":412,"line":762},[410,3295,639],{"class":423},[410,3297,3298,3301,3303,3305,3308],{"class":412,"line":776},[410,3299,3300],{"class":416},"        await",[410,3302,825],{"class":499},[410,3304,377],{"class":423},[410,3306,3307],{"class":557},"resolve",[410,3309,3310],{"class":423},"(config.channel);\n",[410,3312,3313,3315,3317,3320],{"class":412,"line":788},[410,3314,733],{"class":416},[410,3316,2783],{"class":423},[410,3318,3319],{"class":460},"'completed'",[410,3321,980],{"class":423},[410,3323,3324],{"class":412,"line":794},[410,3325,742],{"class":423},[410,3327,3328],{"class":412,"line":800},[410,3329,1047],{"class":423},[11,3331,3332,3333,377],{},"The step walker owns retries, the idempotent claim, cancellation, and the circuit breaker; the hosted runner owns exactly one authorized attempt with a host-owned 30-second deadline. A plugin step may complete or fail — it can never force a run to advance. Failure reasons are clamped and control-stripped before they reach ",[20,3334,3335],{},"errorMessage",[11,3337,3338,3339,3342,3343,3346,3347,1417,3350,3353],{},"Triggers only ",[1180,3340,3341],{},"decide"," whether a firing starts an automation; the host fans out. Plugin trigger config rides a ",[20,3344,3345],{},"{ pluginConfig }"," arm and is unwrapped before ",[20,3348,3349],{},"parseConfig",[20,3351,3352],{},"buildTriggerData"," output is clamped to bounded primitive keys before it reaches the run row. Conditions are contracted to evaluate synchronously inside a query.",[3355,3356,3359],"callout",{"title":3357,"type":3358},"Only `automationSteps` runs today","warning",[11,3360,3361,3362,3364,3365,304,3367,3369],{},"Of the three automation registries, only ",[20,3363,171],{}," is dispatched. There is no plugin-trigger firing seam and no plugin condition evaluator: ",[20,3366,303],{},[20,3368,307],{}," kind rather than returning \"does not match\", and no condition-kind validator lets one be persisted in a segment filter. The catalog and capability ceilings are reserved; executable host paths are not shipped ahead of a producer.",[54,3371,3373],{"id":3372},"webhook-events","Webhook events",[383,3375,3378],{"className":3376,"code":3377,"language":388,"meta":389},[386],"webhookEvents: [{ id, description, subscribable }]\n",[20,3379,3377],{"__ignoreMap":389},[11,3381,3382,3383,354,3386,3389,3390,1360,3392,3395],{},"Data only — the plugin ships no executable code for the event. Core events keep flat literals (",[20,3384,3385],{},"email.sent",[20,3387,3388],{},"contact.created","); plugin events are ",[20,3391,51],{},[20,3393,3394],{},"subscribable: false"," means customer endpoints cannot subscribe and the event is only ever delivered to a single explicit target. Payload data handed to the host at emit time is untrusted and is clamped and scrubbed before delivery.",[3355,3397,3399],{"title":3398,"type":3358},"No publish path yet",[11,3400,3401,3402,3404],{},"The composed event catalog exists, but no publish or authorization seam is shipped: persisted webhook-event validators are hand-enumerated closed unions pinned to the core registry, so a ",[20,3403,307],{}," event kind cannot be stored on an endpoint subscription and is never delivered. Declaring the bucket is inert today.",[54,3406,3408],{"id":3407},"import-providers","Import providers",[383,3410,3413],{"className":3411,"code":3412,"language":388,"meta":389},[386],"importProviders: [{ id, label, module: { exportPath }, signature, attestSource? }]\n",[20,3414,3412],{"__ignoreMap":389},[11,3416,3417,1409,3419,878,3421,3424],{},[20,3418,1510],{},[15,3420,1278],{},[20,3422,3423],{},"{ header, algorithm: 'hmac-sha256' | 'hmac-sha1', encoding: 'hex' | 'base64', secretEnvVar }",". The host reads the secret from the env var, recomputes the HMAC over the raw body, and compares in constant time. Verification fails closed when the secret is unset or the header is missing, malformed, or mismatched — a plugin cannot opt out.",[3355,3426,3428],{"title":3427,"type":3358},"Origin only, no replay resistance",[11,3429,3430,3431,3435],{},"The signature contract signs the raw body alone — no timestamp, tolerance, or nonce — so passing it proves origin, not freshness. It gates no HTTP endpoint today. The piece that wires an inbound HTTP surface must layer replay defense on top before any endpoint accepts plugin-sourced traffic. Contrast the Tier-2 ",[368,3432,3434],{"href":3433},"\u002Fdeveloper\u002Fplugin-connected-apps","signed hooks",", which do sign a timestamp and a nonce.",[383,3437,3439],{"className":404,"code":3438,"language":406,"meta":389,"style":389},"import type {\n    JsonObject,\n    PluginImportPageResult,\n    PluginImportProviderInput,\n    PluginImportProviderModule,\n} from '@owlat\u002Fplugin-kit';\n\nexport const importProvider: PluginImportProviderModule = {\n    validateConfig(config: JsonObject) {\n        return typeof config['listId'] === 'string'\n            ? ({ ok: true } as const)\n            : ({ ok: false, reason: 'listId is required' } as const);\n    },\n\n    async fetchPage(input: PluginImportProviderInput): Promise\u003CPluginImportPageResult> {\n        \u002F\u002F `cursor` is `''` on the first page; return `null` to end the walk.\n        const page = input.cursor === '' ? 1 : Number(input.cursor);\n        return {\n            rows: [{ email: `contact-${page}@example.com`, fields: { source: 'vendor' } }],\n            nextCursor: page >= 2 ? null : String(page + 1),\n        };\n    },\n};\n",[20,3440,3441,3449,3454,3459,3464,3469,3479,3483,3501,3517,3536,3555,3577,3581,3585,3614,3619,3650,3656,3679,3710,3715,3719],{"__ignoreMap":389},[410,3442,3443,3445,3447],{"class":412,"line":413},[410,3444,417],{"class":416},[410,3446,420],{"class":416},[410,3448,424],{"class":423},[410,3450,3451],{"class":412,"line":427},[410,3452,3453],{"class":423},"    JsonObject,\n",[410,3455,3456],{"class":412,"line":433},[410,3457,3458],{"class":423},"    PluginImportPageResult,\n",[410,3460,3461],{"class":412,"line":439},[410,3462,3463],{"class":423},"    PluginImportProviderInput,\n",[410,3465,3466],{"class":412,"line":445},[410,3467,3468],{"class":423},"    PluginImportProviderModule,\n",[410,3470,3471,3473,3475,3477],{"class":412,"line":451},[410,3472,454],{"class":423},[410,3474,457],{"class":416},[410,3476,461],{"class":460},[410,3478,464],{"class":423},[410,3480,3481],{"class":412,"line":467},[410,3482,471],{"emptyLinePlaceholder":470},[410,3484,3485,3487,3489,3492,3494,3497,3499],{"class":412,"line":474},[410,3486,526],{"class":416},[410,3488,529],{"class":416},[410,3490,3491],{"class":499}," importProvider",[410,3493,496],{"class":416},[410,3495,3496],{"class":480}," PluginImportProviderModule",[410,3498,650],{"class":416},[410,3500,424],{"class":423},[410,3502,3503,3506,3508,3510,3512,3515],{"class":412,"line":486},[410,3504,3505],{"class":557},"    validateConfig",[410,3507,561],{"class":423},[410,3509,895],{"class":492},[410,3511,496],{"class":416},[410,3513,3514],{"class":480}," JsonObject",[410,3516,613],{"class":423},[410,3518,3519,3521,3523,3526,3529,3531,3533],{"class":412,"line":505},[410,3520,733],{"class":416},[410,3522,1789],{"class":416},[410,3524,3525],{"class":423}," config[",[410,3527,3528],{"class":460},"'listId'",[410,3530,909],{"class":423},[410,3532,607],{"class":416},[410,3534,3535],{"class":460}," 'string'\n",[410,3537,3538,3541,3544,3546,3548,3550,3552],{"class":412,"line":511},[410,3539,3540],{"class":416},"            ?",[410,3542,3543],{"class":423}," ({ ok: ",[410,3545,1016],{"class":499},[410,3547,1737],{"class":423},[410,3549,656],{"class":416},[410,3551,529],{"class":416},[410,3553,3554],{"class":423},")\n",[410,3556,3557,3560,3562,3564,3566,3569,3571,3573,3575],{"class":412,"line":516},[410,3558,3559],{"class":416},"            :",[410,3561,3543],{"class":423},[410,3563,971],{"class":499},[410,3565,2795],{"class":423},[410,3567,3568],{"class":460},"'listId is required'",[410,3570,1737],{"class":423},[410,3572,656],{"class":416},[410,3574,529],{"class":416},[410,3576,633],{"class":423},[410,3578,3579],{"class":412,"line":523},[410,3580,742],{"class":423},[410,3582,3583],{"class":412,"line":554},[410,3584,471],{"emptyLinePlaceholder":470},[410,3586,3587,3589,3592,3594,3596,3598,3601,3603,3605,3607,3609,3612],{"class":412,"line":581},[410,3588,753],{"class":416},[410,3590,3591],{"class":557}," fetchPage",[410,3593,561],{"class":423},[410,3595,564],{"class":492},[410,3597,496],{"class":416},[410,3599,3600],{"class":480}," PluginImportProviderInput",[410,3602,572],{"class":423},[410,3604,496],{"class":416},[410,3606,825],{"class":480},[410,3608,540],{"class":423},[410,3610,3611],{"class":480},"PluginImportPageResult",[410,3613,833],{"class":423},[410,3615,3616],{"class":412,"line":616},[410,3617,3618],{"class":519},"        \u002F\u002F `cursor` is `''` on the first page; return `null` to end the walk.\n",[410,3620,3621,3623,3626,3628,3631,3633,3635,3638,3641,3644,3647],{"class":412,"line":636},[410,3622,645],{"class":416},[410,3624,3625],{"class":499}," page",[410,3627,650],{"class":416},[410,3629,3630],{"class":423}," input.cursor ",[410,3632,607],{"class":416},[410,3634,1034],{"class":460},[410,3636,3637],{"class":416}," ?",[410,3639,3640],{"class":499}," 1",[410,3642,3643],{"class":416}," :",[410,3645,3646],{"class":557}," Number",[410,3648,3649],{"class":423},"(input.cursor);\n",[410,3651,3652,3654],{"class":412,"line":642},[410,3653,733],{"class":416},[410,3655,424],{"class":423},[410,3657,3658,3661,3664,3667,3670,3673,3676],{"class":412,"line":673},[410,3659,3660],{"class":423},"            rows: [{ email: ",[410,3662,3663],{"class":460},"`contact-${",[410,3665,3666],{"class":423},"page",[410,3668,3669],{"class":460},"}@example.com`",[410,3671,3672],{"class":423},", fields: { source: ",[410,3674,3675],{"class":460},"'vendor'",[410,3677,3678],{"class":423}," } }],\n",[410,3680,3681,3684,3687,3690,3692,3694,3696,3699,3702,3705,3707],{"class":412,"line":709},[410,3682,3683],{"class":423},"            nextCursor: page ",[410,3685,3686],{"class":416},">=",[410,3688,3689],{"class":499}," 2",[410,3691,3637],{"class":416},[410,3693,610],{"class":499},[410,3695,3643],{"class":416},[410,3697,3698],{"class":557}," String",[410,3700,3701],{"class":423},"(page ",[410,3703,3704],{"class":416},"+",[410,3706,3640],{"class":499},[410,3708,3709],{"class":423},"),\n",[410,3711,3712],{"class":412,"line":725},[410,3713,3714],{"class":423},"        };\n",[410,3716,3717],{"class":412,"line":730},[410,3718,742],{"class":423},[410,3720,3721],{"class":412,"line":739},[410,3722,1047],{"class":423},[3355,3724,3726],{"title":3725,"type":3358},"No import walk reaches a plugin provider yet",[11,3727,3728,3729,3732,3733,3735,3736,3738],{},"The generated import-provider module registry exists, but no start-authorization seam is shipped: the import walker dispatches through a core-only ",[20,3730,3731],{},"INTEGRATION_IMPORT_PROVIDERS"," map, and ",[20,3734,346],{}," is a two-literal union that cannot hold ",[20,3737,51],{},". An import run can therefore never reach a contributed provider today.",[54,3740,3741],{"id":191},"Crons",[383,3743,3746],{"className":3744,"code":3745,"language":388,"meta":389},[386],"crons: [{ id, label, module: { exportPath }, schedule: { intervalMinutes }, timeoutMs }]\n",[20,3747,3745],{"__ignoreMap":389},[11,3749,3750],{},"Scheduling limits, enforced at manifest validation, codegen and registration:",[67,3752,3753,3762],{},[70,3754,3755],{},[73,3756,3757,3759],{},[76,3758,1911],{},[76,3760,3761],{},"Value",[89,3763,3764,3774],{},[73,3765,3766,3771],{},[94,3767,3768],{},[20,3769,3770],{},"schedule.intervalMinutes",[94,3772,3773],{},"15 … 40 320 (four weeks)",[73,3775,3776,3781],{},[94,3777,3778],{},[20,3779,3780],{},"timeoutMs",[94,3782,3783],{},"1 000 … 300 000 (five minutes)",[11,3785,3786,3787,3790,3791,377],{},"A plugin can add background work but never a hot loop or an effectively-never cron. The registered Convex cron name is the namespaced kind, so registrations are unique. Each execution receives ",[20,3788,3789],{},"{ signal, logger, llm }"," — no Convex context, tenant id, or credential — and cancellation is cooperative through ",[20,3792,3793],{},"signal",[54,3795,3797],{"id":3796},"navigation-and-settings-entries","Navigation and settings entries",[383,3799,3802],{"className":3800,"code":3801,"language":388,"meta":389},[386],"navItems: [{ id, section, name, href, icon, order? }]\nsettingsPanels: [{ id, name, href, icon, order? }]\n",[20,3803,3801],{"__ignoreMap":389},[11,3805,3806,3807,3809],{},"Both are data only: a labelled link to an internal dashboard path. The label is clamped to 64 UTF-16 code units (an astral character counts as two, so the manifest validator and the render-side clamp agree on the budget) with control and bidi-format characters stripped when the entry is derived — that is spoofing defense, so a plugin cannot draw a label that visually impersonates a core one; HTML escaping in Vue is the XSS defense. The entry is gated behind the plugin flag and ordered deterministically ",[15,3808,2638],{}," every core entry.",[11,3811,3812,3813,3816],{},"Registry dedup is by destination ",[20,3814,3815],{},"href",", first-registered-wins, and core is always registered first — that is what prevents a plugin from shadowing a core destination. A nav item targeting an unknown or feature-off section is dropped; a plugin cannot create a new top-level section.",[11,3818,3819,3822,3823,3825,3826,3829],{},[15,3820,3821],{},"A plugin cannot ship a page."," No arbitrary browser code is loaded at runtime, and codegen emits no Nuxt routes, so ",[20,3824,3815],{}," must resolve to a route the dashboard build already has — otherwise the link renders and then 404s. The destination every plugin gets for free is its own schema-rendered settings page at ",[20,3827,3828],{},"\u002Fdashboard\u002Fadmin\u002Finstance\u002Fplugins\u002F\u003CpluginId>",", which is what both reference manifests link to. Anything else has to be a core route, or a route the operator's own build provides.",[54,3831,3833],{"id":3832},"settings-schema","Settings schema",[11,3835,3836,3838],{},[20,3837,1238],{}," is not a contribution bucket — it is a top-level declarative form the host renders, validates, persists, and redacts, so a plugin needs no custom client code.",[67,3840,3841,3851],{},[70,3842,3843],{},[73,3844,3845,3848],{},[76,3846,3847],{},"Field kind",[76,3849,3850],{},"Extra fields",[89,3852,3853,3867,3881,3897,3907],{},[73,3854,3855,3859],{},[94,3856,3857],{},[20,3858,1251],{},[94,3860,3861,354,3864],{},[20,3862,3863],{},"default?",[20,3865,3866],{},"maxLength?",[73,3868,3869,3873],{},[94,3870,3871],{},[20,3872,1254],{},[94,3874,3875,3877,3878,3880],{},[20,3876,1274],{}," (required, ",[20,3879,1130],{},"-prefixed) — declaration only, see below",[73,3882,3883,3887],{},[94,3884,3885],{},[20,3886,1257],{},[94,3888,3889,354,3891,354,3894],{},[20,3890,3863],{},[20,3892,3893],{},"min?",[20,3895,3896],{},"max?",[73,3898,3899,3903],{},[94,3900,3901],{},[20,3902,1260],{},[94,3904,3905],{},[20,3906,3863],{},[73,3908,3909,3913],{},[94,3910,3911],{},[20,3912,1263],{},[94,3914,3915,354,3918],{},[20,3916,3917],{},"options: [{ value, label }]",[20,3919,3863],{},[11,3921,3922,3923,354,3926,354,3929,354,3932,3935,3936,354,3939,1417,3942,3945],{},"All kinds carry ",[20,3924,3925],{},"key",[20,3927,3928],{},"label",[20,3930,3931],{},"description?",[20,3933,3934],{},"required?",". Ceilings: 64 fields, 64 options per select, 8 192 characters per text value. ",[20,3937,3938],{},"__proto__",[20,3940,3941],{},"constructor",[20,3943,3944],{},"prototype"," are rejected as keys.",[11,3947,3948,3954,3955,3957,3958,3960],{},[15,3949,3950,3951,3953],{},"A ",[20,3952,1254],{}," field stores nothing."," It names a ",[20,3956,1130],{},"-prefixed deployment environment variable, and the host reports only whether that variable is present; a write for a secret key is rejected outright. Owlat therefore holds no plugin credential plaintext at all — there is no row to leak, no envelope to rotate, and no key to compromise. Nothing else would be safe to build: no host path ever hands a plugin its settings (plugins receive host-mediated services only), so a persisted credential would be write-only storage. Use ",[20,3959,1177],{}," when the plugin must not run at all without the variable.",[54,3962,3964],{"id":3963},"reserved-names","Reserved names",[11,3966,3967,3970,3971,354,3974,354,3977,354,3980,354,3983,354,3986,354,3989,1417,3992,3995,3996,3999],{},[20,3968,3969],{},"PLUGIN_CONTRIBUTION_KINDS"," also contains ",[20,3972,3973],{},"lifecycleEffects",[20,3975,3976],{},"assistantTools",[20,3978,3979],{},"inboundAdapters",[20,3981,3982],{},"emailBlocks",[20,3984,3985],{},"commands",[20,3987,3988],{},"panels",[20,3990,3991],{},"widgets",[20,3993,3994],{},"taskCards",". Those buckets are reserved in the manifest type and accepted by the validator as opaque arrays, but ",[15,3997,3998],{},"no codegen or host seam consumes them yet"," — the corresponding core registries exist and are open to core modules only. Declaring one has no runtime effect today. Treat them as reserved names, not as extension points.",[11,4001,4002,4003,4006,4007,4009,4010,4012,4013,4015,4016,4019],{},"A reservation is only free while it names something real, so it can be withdrawn. ",[20,4004,4005],{},"channelAdapters"," was: the bidirectional channel-adapter interface it pointed at is gone (its two faking implementations deleted, its three working ones moved next to the single action that dispatched them), and the seams that replaced it — ",[20,4008,98],{}," for outbound and ",[20,4011,3979],{}," for inbound — already have buckets of their own. A manifest that declares ",[20,4014,4005],{}," today is rejected with ",[20,4017,4018],{},"unknown_field",", the same as a typo. Nothing stops the name coming back the day a channel seam does.",[4021,4022,4023],"style",{},"html pre.shiki code .s7YZ4, html code.shiki .s7YZ4{--shiki-default:#D73A49;--shiki-dark:#F47067}html pre.shiki code .sYgZi, html code.shiki .sYgZi{--shiki-default:#24292E;--shiki-dark:#ADBAC7}html pre.shiki code .s-HuK, html code.shiki .s-HuK{--shiki-default:#032F62;--shiki-dark:#96D0FF}html pre.shiki code .sOLd2, html code.shiki .sOLd2{--shiki-default:#6F42C1;--shiki-dark:#F69D50}html pre.shiki code .stnAF, html code.shiki .stnAF{--shiki-default:#E36209;--shiki-dark:#F69D50}html pre.shiki code .sviXB, html code.shiki .sviXB{--shiki-default:#005CC5;--shiki-dark:#6CB6FF}html pre.shiki code .sDN9O, html code.shiki .sDN9O{--shiki-default:#6A737D;--shiki-dark:#768390}html pre.shiki code .sPO5f, html code.shiki .sPO5f{--shiki-default:#6F42C1;--shiki-dark:#DCBDFB}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":389,"searchDepth":427,"depth":427,"links":4025},[4026,4030,4037,4038,4039,4041,4042,4043,4044,4045,4046,4047],{"id":56,"depth":427,"text":57,"children":4027},[4028,4029],{"id":61,"depth":433,"text":62},{"id":238,"depth":433,"text":239},{"id":380,"depth":427,"text":381,"children":4031},[4032,4033,4034,4035,4036],{"id":1095,"depth":433,"text":1096},{"id":1228,"depth":433,"text":1229},{"id":1294,"depth":433,"text":1295},{"id":1451,"depth":433,"text":1452},{"id":1959,"depth":433,"text":1960},{"id":2626,"depth":427,"text":2627},{"id":2921,"depth":427,"text":2922},{"id":2952,"depth":427,"text":4040},"Autonomy gates (sendGates)",{"id":2984,"depth":427,"text":2985},{"id":3372,"depth":427,"text":3373},{"id":3407,"depth":427,"text":3408},{"id":191,"depth":427,"text":3741},{"id":3796,"depth":427,"text":3797},{"id":3832,"depth":427,"text":3833},{"id":3963,"depth":427,"text":3964},"Every contribution bucket a plugin manifest can declare, its capability, its module contract, and the host semantics around it.","md",{},"\u002Fdeveloper\u002Fplugin-contributions",{"title":6,"description":4048},"3.developer\u002F42.plugin-contributions","p2roplru7aHqJypeuUexn-Ju0q4lUPFRM2H1zUGydZ8",[4056,4060],{"title":4057,"path":4058,"stem":4059,"children":-1},"Building a Plugin","\u002Fdeveloper\u002Fplugin-authoring","3.developer\u002F41.plugin-authoring",{"title":4061,"path":370,"stem":4062,"children":-1},"Capabilities, Grants & Trust","3.developer\u002F43.plugin-capabilities",1786915092435]