@north-light/cli 0.3.580 → 0.3.581

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (192) hide show
  1. package/dist/api/client.d.ts +6 -1
  2. package/dist/api/client.js +3 -3
  3. package/dist/api/contract.d.ts +1 -1
  4. package/dist/api/contract.js +1 -1
  5. package/dist/api/dto/execution-start.d.ts +14 -0
  6. package/dist/api/dto/health.d.ts +20 -0
  7. package/dist/api/dto/imports.d.ts +142 -0
  8. package/dist/api/dto/imports.js +0 -0
  9. package/dist/api/index.d.ts +1 -0
  10. package/dist/api/index.js +1 -1
  11. package/dist/api/routes.d.ts +2 -0
  12. package/dist/api/routes.js +1 -1
  13. package/dist/clients/attach/session/connection.d.ts +3 -1
  14. package/dist/clients/attach/session/connection.js +1 -1
  15. package/dist/clients/attach/viewer.js +510 -510
  16. package/dist/commands/api-client.d.ts +7 -4
  17. package/dist/commands/api-client.js +3 -3
  18. package/dist/commands/sys/daemon.js +1 -1
  19. package/dist/commands/sys/import/resume.d.ts +1 -0
  20. package/dist/commands/sys/import/resume.js +1 -0
  21. package/dist/commands/sys/import/start.d.ts +5 -0
  22. package/dist/commands/sys/import/start.js +4 -0
  23. package/dist/commands/sys/join.d.ts +2 -0
  24. package/dist/commands/sys/join.js +1 -1
  25. package/dist/commands/sys/machine/rotate.d.ts +1 -0
  26. package/dist/commands/sys/machine/rotate.js +1 -0
  27. package/dist/commands/sys/signin.d.ts +7 -0
  28. package/dist/commands/sys/signin.js +2 -0
  29. package/dist/commands/sys.js +1 -1
  30. package/dist/core/blobs/r2.d.ts +4 -0
  31. package/dist/core/blobs/r2.js +7 -4
  32. package/dist/core/canvas/daemon-owner.d.ts +1 -1
  33. package/dist/core/canvas/executions.d.ts +1 -1
  34. package/dist/core/canvas/node-tokens.d.ts +2 -0
  35. package/dist/core/canvas/node-tokens.js +3 -3
  36. package/dist/core/directory/person-credential.d.ts +36 -0
  37. package/dist/core/directory/person-credential.js +1 -0
  38. package/dist/core/graph/deletion.d.ts +4 -1
  39. package/dist/core/graph/deletion.js +3 -3
  40. package/dist/core/graph/documents.d.ts +4 -0
  41. package/dist/core/graph/documents.js +10 -10
  42. package/dist/core/graph/reader.js +2 -2
  43. package/dist/core/import/bundle/blob-keys.d.ts +10 -0
  44. package/dist/core/import/bundle/blob-keys.js +1 -0
  45. package/dist/core/import/bundle/layout.d.ts +89 -0
  46. package/dist/core/import/bundle/layout.js +5 -0
  47. package/dist/core/import/export/archive.d.ts +11 -0
  48. package/dist/core/import/export/archive.js +1 -0
  49. package/dist/core/import/export/cloud.d.ts +21 -0
  50. package/dist/core/import/export/cloud.js +1 -0
  51. package/dist/core/import/export/dump.d.ts +34 -0
  52. package/dist/core/import/export/dump.js +3 -0
  53. package/dist/core/import/export/files.d.ts +22 -0
  54. package/dist/core/import/export/files.js +1 -0
  55. package/dist/core/import/export/inline.d.ts +3 -0
  56. package/dist/core/import/export/inline.js +1 -0
  57. package/dist/core/import/export/source.d.ts +2 -0
  58. package/dist/core/import/export/source.js +1 -0
  59. package/dist/core/import/export/stores.d.ts +9 -0
  60. package/dist/core/import/export/stores.js +1 -0
  61. package/dist/core/import/load/bind.d.ts +19 -0
  62. package/dist/core/import/load/bind.js +15 -0
  63. package/dist/core/import/load/blobs.d.ts +10 -0
  64. package/dist/core/import/load/blobs.js +1 -0
  65. package/dist/core/import/load/catalog.d.ts +32 -0
  66. package/dist/core/import/load/catalog.js +9 -0
  67. package/dist/core/import/load/copy.d.ts +26 -0
  68. package/dist/core/import/load/copy.js +6 -0
  69. package/dist/core/import/load/path-map.d.ts +7 -0
  70. package/dist/core/import/load/path-map.js +1 -0
  71. package/dist/core/import/load/prepare.d.ts +37 -0
  72. package/dist/core/import/load/prepare.js +10 -0
  73. package/dist/core/import/load/rewrite.d.ts +39 -0
  74. package/dist/core/import/load/rewrite.js +9 -0
  75. package/dist/core/import/load/rollback.d.ts +3 -0
  76. package/dist/core/import/load/rollback.js +6 -0
  77. package/dist/core/import/load/run.d.ts +22 -0
  78. package/dist/core/import/load/run.js +3 -0
  79. package/dist/core/import/load/stage.d.ts +15 -0
  80. package/dist/core/import/load/stage.js +1 -0
  81. package/dist/core/import/load/stores.d.ts +24 -0
  82. package/dist/core/import/load/stores.js +2 -0
  83. package/dist/core/import/load/verify.d.ts +4 -0
  84. package/dist/core/import/load/verify.js +11 -0
  85. package/dist/core/runtime/broker.js +1 -1
  86. package/dist/core/runtime/revive.d.ts +5 -2
  87. package/dist/core/runtime/revive.js +2 -2
  88. package/dist/core/runtime/runner/blaxel/runner.js +3 -3
  89. package/dist/core/runtime/runner/index.d.ts +1 -0
  90. package/dist/core/runtime/runner/index.js +1 -1
  91. package/dist/core/runtime/runner/machine/call.d.ts +8 -0
  92. package/dist/core/runtime/runner/machine/call.js +3 -0
  93. package/dist/core/runtime/runner/machine/environment.d.ts +1 -0
  94. package/dist/core/runtime/runner/machine/environment.js +1 -0
  95. package/dist/core/runtime/runner/machine/executor.d.ts +23 -0
  96. package/dist/core/runtime/runner/machine/executor.js +1 -0
  97. package/dist/core/runtime/runner/machine/machine-executor.test.d.ts +1 -0
  98. package/dist/core/runtime/runner/machine/machine-executor.test.js +4 -0
  99. package/dist/core/runtime/runner/machine/ops.d.ts +127 -0
  100. package/dist/core/runtime/runner/machine/ops.js +1 -0
  101. package/dist/core/runtime/runner/machine/runner.d.ts +12 -0
  102. package/dist/core/runtime/runner/machine/runner.js +1 -0
  103. package/dist/core/runtime/runner/types.d.ts +7 -1
  104. package/dist/core/runtime/warm-pool.d.ts +2 -0
  105. package/dist/core/runtime/warm-pool.js +1 -1
  106. package/dist/core/secrets/env-rows.d.ts +1 -0
  107. package/dist/core/secrets/env-rows.js +1 -1
  108. package/dist/core/storage/purge.d.ts +6 -0
  109. package/dist/core/storage/purge.js +4 -0
  110. package/dist/core/subscription-state.d.ts +1 -0
  111. package/dist/core/subscription-state.js +1 -1
  112. package/dist/daemon/api/bridge.js +3 -3
  113. package/dist/daemon/api/handlers/attach.js +1 -1
  114. package/dist/daemon/api/handlers/crons.js +1 -1
  115. package/dist/daemon/api/handlers/daemon.js +1 -1
  116. package/dist/daemon/api/handlers/execution-start.js +2 -2
  117. package/dist/daemon/api/handlers/files.js +1 -1
  118. package/dist/daemon/api/handlers/imports.d.ts +2 -0
  119. package/dist/daemon/api/handlers/imports.js +1 -0
  120. package/dist/daemon/api/handlers/machine-credential.d.ts +2 -0
  121. package/dist/daemon/api/handlers/machine-credential.js +1 -0
  122. package/dist/daemon/api/handlers/profiles.js +1 -1
  123. package/dist/daemon/api/handlers/providers.js +1 -1
  124. package/dist/daemon/api/handlers/runs.js +1 -1
  125. package/dist/daemon/api/import-auth.d.ts +6 -0
  126. package/dist/daemon/api/import-auth.js +1 -0
  127. package/dist/daemon/api/operations.d.ts +2 -2
  128. package/dist/daemon/api/operations.js +1 -1
  129. package/dist/daemon/api/router.d.ts +7 -1
  130. package/dist/daemon/api/router.js +1 -1
  131. package/dist/daemon/api/server.js +1 -1
  132. package/dist/daemon/broker-signals/bus.d.ts +3 -2
  133. package/dist/daemon/broker-signals/bus.js +1 -1
  134. package/dist/daemon/cron/armed-context.d.ts +1 -0
  135. package/dist/daemon/cron/armed-context.js +1 -1
  136. package/dist/daemon/cron/closed.d.ts +31 -0
  137. package/dist/daemon/cron/closed.js +1 -0
  138. package/dist/daemon/cron/ended.js +2 -2
  139. package/dist/daemon/cron/lease-recovery.js +1 -1
  140. package/dist/daemon/cron/machine-exec.d.ts +5 -0
  141. package/dist/daemon/cron/machine-exec.js +1 -0
  142. package/dist/daemon/cron/machine-fire.d.ts +26 -0
  143. package/dist/daemon/cron/machine-fire.js +1 -0
  144. package/dist/daemon/cron/recurrence.d.ts +14 -0
  145. package/dist/daemon/cron/recurrence.js +1 -0
  146. package/dist/daemon/cron/run-token.d.ts +7 -0
  147. package/dist/daemon/cron/run-token.js +1 -0
  148. package/dist/daemon/cron-run.js +2 -2
  149. package/dist/daemon/crtrd-cli.js +1 -1
  150. package/dist/daemon/crtrd.d.ts +2 -2
  151. package/dist/daemon/crtrd.js +10 -10
  152. package/dist/daemon/drain.d.ts +25 -0
  153. package/dist/daemon/drain.js +9 -0
  154. package/dist/daemon/due-sources.d.ts +1 -1
  155. package/dist/daemon/due-sources.js +1 -1
  156. package/dist/daemon/due.js +8 -8
  157. package/dist/daemon/fleet.js +5 -5
  158. package/dist/daemon/import-dump-run.d.ts +1 -0
  159. package/dist/daemon/import-dump-run.js +5 -0
  160. package/dist/daemon/machine-daemon.d.ts +7 -0
  161. package/dist/daemon/machine-daemon.js +1 -0
  162. package/dist/daemon/machine-link/client.d.ts +29 -0
  163. package/dist/daemon/machine-link/client.js +1 -0
  164. package/dist/daemon/machine-link/config.d.ts +14 -0
  165. package/dist/daemon/machine-link/config.js +3 -0
  166. package/dist/daemon/machine-link/credential.d.ts +7 -0
  167. package/dist/daemon/machine-link/credential.js +2 -0
  168. package/dist/daemon/machine-link/forward.d.ts +16 -0
  169. package/dist/daemon/machine-link/forward.js +1 -0
  170. package/dist/daemon/machine-link/frames.d.ts +49 -0
  171. package/dist/daemon/machine-link/frames.js +1 -0
  172. package/dist/daemon/machine-link/holder.d.ts +73 -0
  173. package/dist/daemon/machine-link/holder.js +9 -0
  174. package/dist/daemon/manage.d.ts +19 -3
  175. package/dist/daemon/manage.js +3 -3
  176. package/dist/daemon/reconcilers/cron-lane.js +1 -1
  177. package/dist/daemon/reconcilers/delivery-lane.js +2 -2
  178. package/dist/daemon/reconcilers/import-lane.d.ts +9 -0
  179. package/dist/daemon/reconcilers/import-lane.js +1 -0
  180. package/dist/daemon/reconcilers/node-lifecycle/freeze-lane.js +1 -1
  181. package/dist/daemon/reconcilers/node-lifecycle/remote-wake.d.ts +3 -0
  182. package/dist/daemon/reconcilers/node-lifecycle/remote-wake.js +1 -1
  183. package/dist/daemon/reconcilers/node-lifecycle/tick.js +1 -1
  184. package/dist/daemon/reconcilers/purge-due-work.d.ts +13 -0
  185. package/dist/daemon/reconcilers/purge-due-work.js +1 -0
  186. package/dist/daemon/reconcilers/row-claim.d.ts +1 -0
  187. package/dist/daemon/reconcilers/row-claim.js +2 -2
  188. package/dist/daemon/runs/observer.js +3 -3
  189. package/package.json +2 -2
  190. package/packages/crouter-identity/package.json +1 -1
  191. package/runtime.lock.json +13 -13
  192. package/scripts/sandbox-start.mjs +185 -2
@@ -1,8 +1,8 @@
1
- var j=Object.defineProperty;var a=(r,e)=>j(r,"name",{value:e,configurable:!0});import{WebSocketServer as K}from"ws";import{isSafeNodeId as P,viewSocketPath as Q}from"../../core/canvas/paths.js";import{getNode as T}from"../../core/canvas/index.js";import{requestPersonId as z,runAsPerson as G}from"../../core/canvas/person-state.js";import{BROKER_READ_CAPS as X,CLIENT_READ_CAPS as Y,FrameDecoder as Z,FrameOverflowError as I}from"../../core/runtime/broker-protocol.js";import{emitEvent as E}from"../../core/events/emit.js";import{classify as $}from"../../core/fault-classifier.js";import{clearFault as g,markNodeRead as ee,recordFault as x}from"../../core/canvas/recovery.js";import{decideAttachRevive as re}from"../../core/runtime/revive.js";import{connectViewSocket as te,connectWssView as ne,probeViewSocket as ae,waitForBrokerViewSocket as oe,waitForWssView as O}from"../../core/runtime/view-socket.js";import{liveExecutionForNode as ie}from"../../core/canvas/executions.js";import{runnerForExecution as ce}from"../../core/runtime/runner/index.js";import{isBeneathRefusal as se}from"../../core/spaces/open-beneath.js";import{attachViewer as V,detachViewer as le,holdViewerForRedial as fe,presentViewer as ue}from"./viewer-presence.js";const me=new K({noServer:!0,maxPayload:X.maxLineBytes}),de=25e3,he=6e4;function ye(r,e=de,s=he){let n=!0,h=!1,u=Date.now();const l=a(()=>{n=!0},"acknowledge"),i=a(()=>{h||(h=!0,clearInterval(o),r.off("pong",l))},"stop");r.on("pong",l);const o=setInterval(()=>{if(Date.now()-u>s){n=!0;return}if(!n){i(),r.terminate();return}n=!1;try{r.ping()}catch{i(),r.terminate()}},e);return o.unref(),{stop:i,noteActivity:a(()=>{u=Date.now()},"noteActivity")}}a(ye,"startRemoteAttachHeartbeat");function C(r){const e=Buffer.from(r,"utf8");return e.byteLength<=123?r:e.subarray(0,123).toString("utf8")}a(C,"clampReason");function w(r,e,s,n,h){const u=JSON.stringify({error:{code:n,message:h,origin:"runtime"}}),l=`HTTP/1.1 ${e} ${s}\r
1
+ var j=Object.defineProperty;var i=(r,e)=>j(r,"name",{value:e,configurable:!0});import{WebSocketServer as K}from"ws";import{isSafeNodeId as N,viewSocketPath as Q}from"../../core/canvas/paths.js";import{getNode as T}from"../../core/canvas/index.js";import{requestPersonId as z,runAsPerson as G}from"../../core/canvas/person-state.js";import{BROKER_READ_CAPS as X,CLIENT_READ_CAPS as Y,FrameDecoder as Z,FrameOverflowError as I}from"../../core/runtime/broker-protocol.js";import{emitEvent as E}from"../../core/events/emit.js";import{classify as $}from"../../core/fault-classifier.js";import{clearFault as g,markNodeRead as ee,recordFault as x}from"../../core/canvas/recovery.js";import{decideAttachRevive as re}from"../../core/runtime/revive.js";import{connectViewSocket as te,connectWssView as ne,probeViewSocket as ie,waitForBrokerViewSocket as ae,waitForWssView as P}from"../../core/runtime/view-socket.js";import{liveExecutionForNode as oe}from"../../core/canvas/executions.js";import{runnerForExecution as ce}from"../../core/runtime/runner/index.js";import{isBeneathRefusal as se}from"../../core/spaces/open-beneath.js";import{openMachineView as le,probeMachineView as fe,waitForMachineView as ue}from"../machine-link/forward.js";import{attachViewer as F,detachViewer as me,holdViewerForRedial as de,presentViewer as he}from"./viewer-presence.js";const pe=3e4,we=new K({noServer:!0,maxPayload:X.maxLineBytes}),ye=25e3,ve=6e4;function ke(r,e=ye,s=ve){let n=!0,h=!1,u=Date.now();const l=i(()=>{n=!0},"acknowledge"),a=i(()=>{h||(h=!0,clearInterval(o),r.off("pong",l))},"stop");r.on("pong",l);const o=setInterval(()=>{if(Date.now()-u>s){n=!0;return}if(!n){a(),r.terminate();return}n=!1;try{r.ping()}catch{a(),r.terminate()}},e);return o.unref(),{stop:a,noteActivity:i(()=>{u=Date.now()},"noteActivity")}}i(ke,"startRemoteAttachHeartbeat");function C(r){const e=Buffer.from(r,"utf8");return e.byteLength<=123?r:e.subarray(0,123).toString("utf8")}i(C,"clampReason");function y(r,e,s,n,h){const u=JSON.stringify({error:{code:n,message:h,origin:"runtime"}}),l=`HTTP/1.1 ${e} ${s}\r
2
2
  content-type: application/json; charset=utf-8\r
3
3
  content-length: ${Buffer.byteLength(u)}\r
4
4
  connection: close\r
5
5
  \r
6
- `+u;try{r.end(l)}catch{try{r.destroy()}catch{}}}a(w,"refuseUpgrade");function pe(r){if(new URL(r.url??"/","http://127.0.0.1").searchParams.get("revive")==="0")return!0;const s=r.headers["x-crtr-attach-revive"];return(Array.isArray(s)?s[0]:s)==="0"}a(pe,"reviveOptedOut");function we(r){return new URL(r.url??"/","http://127.0.0.1").searchParams.get("wakeParked")==="1"}a(we,"wakeParkedRequested");function F(r){return r===1e3||r===1001||r===1005}a(F,"isCleanViewerClose");function y(r,e,s){s.catch(n=>E({level:"error",event:e,node_id:r,error:n}))}a(y,"detach");async function D(r){const e=await ie(r);return e===null||e.runner==="process"?{kind:"unix",path:Q(r)}:ce(e.runner).viewerAddress(e)}a(D,"brokerViewAddress");function ve(r,e){return e.kind==="wss"?ne(e.url,e.headers):te(r)}a(ve,"connectBrokerView");function ke(r,e,s){return e.kind==="wss"?O(e.url,e.headers,void 0,s):ae(r,s)}a(ke,"probeBrokerView");async function ge(r,e,s,n){const h=[];let u=a(t=>{h.push(t)},"relayMessage");if(r.on("message",t=>u(t)),!P(e)){try{r.close(1008,C(`invalid node id: ${e}`))}catch{}return}if(await T(e)===null){try{r.close(1011,C(`no node ${e}`))}catch{}return}await g(e,{link:"viewer\u2194crtrd"});const l=ve(e,s),i=new Z(Y);let o=!1,v=!1,d,b=!1,B=!1,A="controller",N=Promise.resolve(),_=!1;const S=[];let R=!1;l.on("connect",()=>{b=!0,!o&&_&&y(e,"daemon.bridge.presence_failed",V(e,r)),y(e,"daemon.bridge.connect_failed",g(e,{link:"relay\u2194broker"}))});const p=a((t,f,c=!1)=>{if(!o){if(o=!0,y(e,"daemon.bridge.presence_failed",c?fe(e,r):le(e,r)),d?.stop(),!l.destroyed)try{l.destroy()}catch{}try{r.close(t,C(f))}catch{}}},"teardown");d=ye(r);const U=a(async t=>{d?.noteActivity();let f;try{f=i.push(t)}catch(c){const m=c instanceof I?`broker sent an oversized frame (${c.message})`:`failed to decode a broker frame for ${e}`;p(1009,m);return}S.push(...f),R||await L()},"onData"),L=a(async()=>{R=!0;try{for(;S.length>0&&!o;){const t=S.splice(0);let f;try{f=await n(A)}catch(m){E({level:"error",event:"daemon.bridge.attach_recheck_failed",node_id:e,error:m}),p(1011,"revoked");return}if(o)return;if(!f){p(1008,"revoked");return}let c=!1;for(const m of t)r.readyState===r.OPEN&&(r.send(JSON.stringify(m)),m?.type==="welcome"&&(c=!0));if(_&&c)try{await ue(e,r)&&await ee(e)}catch(m){E({level:"warn",event:"daemon.bridge.present_failed",node_id:e,error:m})}}}finally{R=!1}},"drainOutbound");l.on("data",t=>y(e,"daemon.bridge.data_failed",U(t)));const W=a(async t=>{const f=$("relay\u2194broker",t);await x(e,{link:"relay\u2194broker",op:"bridge socket",kind:f.kind,retry:{disposition:"manual"},message:t.message});const c=t.code==="ECONNREFUSED"||t.code==="ENOENT"?`no running broker for ${e}`:`view socket error for ${e}: ${t.message}`;p(1011,c,!0)},"onSocketError");l.on("error",t=>y(e,"daemon.bridge.socket_error_failed",W(t)));const H=a(async t=>{o||t||(await g(e,{link:"relay\u2194broker"}),p(1e3,"broker socket closed",!0))},"onSocketClose");l.on("close",t=>y(e,"daemon.bridge.socket_close_failed",H(t)));const M=a(async(t,f)=>{const c=f.toString("utf8");if(o){F(t)&&await g(e,{link:"viewer\u2194crtrd"});return}if(F(t))await g(e,{link:"viewer\u2194crtrd"});else if(!v){v=!0;const m=$("viewer\u2194crtrd",{code:t,reason:c});await x(e,{link:"viewer\u2194crtrd",op:"remote attach websocket",kind:m.kind,retry:{disposition:"auto"},message:c||`remote attach closed (${t})`})}p(t,c||`remote attach closed (${t})`)},"onWsClose");r.on("close",(t,f)=>y(e,"daemon.bridge.ws_close_failed",M(t,f)));const J=a(async()=>{if(!v){v=!0;const t=$("viewer\u2194crtrd",{code:1006,reason:"remote attach websocket error"});await x(e,{link:"viewer\u2194crtrd",op:"remote attach websocket",kind:t.kind,retry:{disposition:"auto"},message:"remote attach websocket error"})}p(1011,"remote attach websocket error")},"onWsError");r.on("error",()=>y(e,"daemon.bridge.ws_error_failed",J())),u=a(t=>{if(o||l.destroyed)return;d?.noteActivity();const c=(Array.isArray(t)?Buffer.concat(t):Buffer.isBuffer(t)?t:Buffer.from(new Uint8Array(t))).toString("utf8");if(!B)try{const k=JSON.parse(c);k?.type==="hello"&&(B=!0,A=k.role==="observer"?"observer":"controller",_=k.attends!==!1,_&&b&&y(e,"daemon.bridge.presence_failed",V(e,r)))}catch{}const m=c.endsWith(`
6
+ `+u;try{r.end(l)}catch{try{r.destroy()}catch{}}}i(y,"refuseUpgrade");function ge(r){if(new URL(r.url??"/","http://127.0.0.1").searchParams.get("revive")==="0")return!0;const s=r.headers["x-crtr-attach-revive"];return(Array.isArray(s)?s[0]:s)==="0"}i(ge,"reviveOptedOut");function be(r){return new URL(r.url??"/","http://127.0.0.1").searchParams.get("wakeParked")==="1"}i(be,"wakeParkedRequested");function O(r){return r===1e3||r===1001||r===1005}i(O,"isCleanViewerClose");function p(r,e,s){s.catch(n=>E({level:"error",event:e,node_id:r,error:n}))}i(p,"detach");async function D(r){const e=await oe(r);return e===null||e.runner==="process"?{kind:"unix",path:Q(r)}:ce(e.runner).viewerAddress(e)}i(D,"brokerViewAddress");function _e(r,e){return e.kind==="machine"?le(e.environmentId,r):e.kind==="wss"?ne(e.url,e.headers):te(r)}i(_e,"connectBrokerView");function Ee(r,e,s){return e.kind==="machine"?fe(e.environmentId,r,s):e.kind==="wss"?P(e.url,e.headers,void 0,s):ie(r,s)}i(Ee,"probeBrokerView");async function Ae(r,e,s,n){const h=[];let u=i(t=>{h.push(t)},"relayMessage");if(r.on("message",t=>u(t)),!N(e)){try{r.close(1008,C(`invalid node id: ${e}`))}catch{}return}if(await T(e)===null){try{r.close(1011,C(`no node ${e}`))}catch{}return}await g(e,{link:"viewer\u2194crtrd"});const l=_e(e,s),a=new Z(Y);let o=!1,v=!1,d,b=!1,V=!1,A="controller",B=Promise.resolve(),_=!1;const S=[];let R=!1;l.on("connect",()=>{b=!0,!o&&_&&p(e,"daemon.bridge.presence_failed",F(e,r)),p(e,"daemon.bridge.connect_failed",g(e,{link:"relay\u2194broker"}))});const w=i((t,f,c=!1)=>{if(!o){if(o=!0,p(e,"daemon.bridge.presence_failed",c?de(e,r):me(e,r)),d?.stop(),!l.destroyed)try{l.destroy()}catch{}try{r.close(t,C(f))}catch{}}},"teardown");d=ke(r);const M=i(async t=>{d?.noteActivity();let f;try{f=a.push(t)}catch(c){const m=c instanceof I?`broker sent an oversized frame (${c.message})`:`failed to decode a broker frame for ${e}`;w(1009,m);return}S.push(...f),R||await U()},"onData"),U=i(async()=>{R=!0;try{for(;S.length>0&&!o;){const t=S.splice(0);let f;try{f=await n(A)}catch(m){E({level:"error",event:"daemon.bridge.attach_recheck_failed",node_id:e,error:m}),w(1011,"revoked");return}if(o)return;if(!f){w(1008,"revoked");return}let c=!1;for(const m of t)r.readyState===r.OPEN&&(r.send(JSON.stringify(m)),m?.type==="welcome"&&(c=!0));if(_&&c)try{await he(e,r)&&await ee(e)}catch(m){E({level:"warn",event:"daemon.bridge.present_failed",node_id:e,error:m})}}}finally{R=!1}},"drainOutbound");l.on("data",t=>p(e,"daemon.bridge.data_failed",M(t)));const W=i(async t=>{const f=$("relay\u2194broker",t);await x(e,{link:"relay\u2194broker",op:"bridge socket",kind:f.kind,retry:{disposition:"manual"},message:t.message});const c=t.code==="ECONNREFUSED"||t.code==="ENOENT"?`no running broker for ${e}`:`view socket error for ${e}: ${t.message}`;w(1011,c,!0)},"onSocketError");l.on("error",t=>p(e,"daemon.bridge.socket_error_failed",W(t)));const H=i(async t=>{o||t||(await g(e,{link:"relay\u2194broker"}),w(1e3,"broker socket closed",!0))},"onSocketClose");l.on("close",t=>p(e,"daemon.bridge.socket_close_failed",H(t)));const L=i(async(t,f)=>{const c=f.toString("utf8");if(o){O(t)&&await g(e,{link:"viewer\u2194crtrd"});return}if(O(t))await g(e,{link:"viewer\u2194crtrd"});else if(!v){v=!0;const m=$("viewer\u2194crtrd",{code:t,reason:c});await x(e,{link:"viewer\u2194crtrd",op:"remote attach websocket",kind:m.kind,retry:{disposition:"auto"},message:c||`remote attach closed (${t})`})}w(t,c||`remote attach closed (${t})`)},"onWsClose");r.on("close",(t,f)=>p(e,"daemon.bridge.ws_close_failed",L(t,f)));const J=i(async()=>{if(!v){v=!0;const t=$("viewer\u2194crtrd",{code:1006,reason:"remote attach websocket error"});await x(e,{link:"viewer\u2194crtrd",op:"remote attach websocket",kind:t.kind,retry:{disposition:"auto"},message:"remote attach websocket error"})}w(1011,"remote attach websocket error")},"onWsError");r.on("error",()=>p(e,"daemon.bridge.ws_error_failed",J())),u=i(t=>{if(o||l.destroyed)return;d?.noteActivity();const c=(Array.isArray(t)?Buffer.concat(t):Buffer.isBuffer(t)?t:Buffer.from(new Uint8Array(t))).toString("utf8");if(!V)try{const k=JSON.parse(c);k?.type==="hello"&&(V=!0,A=k.role==="observer"?"observer":"controller",_=k.attends!==!1,_&&b&&p(e,"daemon.bridge.presence_failed",F(e,r)))}catch{}const m=c.endsWith(`
7
7
  `)?c:c+`
8
- `,q=a(()=>{try{l.write(m)}catch{}},"write");N=N.then(async()=>{o||(await n(A)?q():p(1008,"revoked"))}).catch(k=>{E({level:"error",event:"daemon.bridge.attach_recheck_failed",node_id:e,error:k}),p(1011,"revoked")})},"relayMessage");for(const t of h.splice(0))u(t)}a(ge,"bridgeConnection");async function Fe(r,e,s,n,h){const u=z(),l=u===void 0?h:i=>G(u,()=>h(i));e.on("error",()=>{});try{if(!P(n)){w(e,400,"Bad Request","invalid_node_id",`invalid node id: ${n}`);return}if(await T(n)===null){w(e,404,"Not Found","node_not_found",`no node ${n}`);return}let i;if(pe(r)){if(i=await D(n),!await ke(n,i,250)){w(e,409,"Conflict","node_dormant",`node ${n} is dormant and revive was opted out (?revive=0)`);return}}else{let o;try{const d=await re(n,{resume:!0,capacity:"refuse"},we(r));if(d.parked){w(e,409,"Conflict","node_dormant",`node ${n} is parked; attach with ?wakeParked=1 to wake it`);return}o=d.result.launch?.exited}catch(d){const b=d instanceof Error?d.message:String(d);w(e,409,"Conflict","attach_revive_failed",`attach revive failed: ${b}`);return}if(i=await D(n),!(i.kind==="wss"?await O(i.url,i.headers,o):await oe(n,o))){w(e,409,"Conflict","broker_not_ready",`broker view socket for ${n} did not become listenable after revive`);return}}if(e.destroyed)return;me.handleUpgrade(r,e,s,o=>y(n,"daemon.bridge.connection_failed",ge(o,n,i,l)))}catch(i){const o=i instanceof Error?i.message:String(i);if(se(i)){w(e,400,"Bad Request","invalid_request",o);return}w(e,500,"Internal Server Error","internal",`attach upgrade failed: ${o}`)}}a(Fe,"handleAttachUpgrade");export{de as REMOTE_ATTACH_HEARTBEAT_MS,he as REMOTE_ATTACH_QUIET_MS,Fe as handleAttachUpgrade,F as isCleanViewerClose,w as refuseUpgrade,ye as startRemoteAttachHeartbeat};
8
+ `,q=i(()=>{try{l.write(m)}catch{}},"write");B=B.then(async()=>{o||(await n(A)?q():w(1008,"revoked"))}).catch(k=>{E({level:"error",event:"daemon.bridge.attach_recheck_failed",node_id:e,error:k}),w(1011,"revoked")})},"relayMessage");for(const t of h.splice(0))u(t)}i(Ae,"bridgeConnection");async function He(r,e,s,n,h){const u=z(),l=u===void 0?h:a=>G(u,()=>h(a));e.on("error",()=>{});try{if(!N(n)){y(e,400,"Bad Request","invalid_node_id",`invalid node id: ${n}`);return}if(await T(n)===null){y(e,404,"Not Found","node_not_found",`no node ${n}`);return}let a;if(ge(r)){if(a=await D(n),!await Ee(n,a,250)){y(e,409,"Conflict","node_dormant",`node ${n} is dormant and revive was opted out (?revive=0)`);return}}else{let o;try{const d=await re(n,{resume:!0,capacity:"refuse"},be(r));if(d.parked){y(e,409,"Conflict","node_dormant",`node ${n} is parked; attach with ?wakeParked=1 to wake it`);return}o=d.result.launch?.exited}catch(d){const b=d instanceof Error?d.message:String(d);y(e,409,"Conflict","attach_revive_failed",`attach revive failed: ${b}`);return}if(a=await D(n),!(a.kind==="machine"?await ue(a.environmentId,n,o,pe):a.kind==="wss"?await P(a.url,a.headers,o):await ae(n,o))){y(e,409,"Conflict","broker_not_ready",`broker view socket for ${n} did not become listenable after revive`);return}}if(e.destroyed)return;we.handleUpgrade(r,e,s,o=>p(n,"daemon.bridge.connection_failed",Ae(o,n,a,l)))}catch(a){const o=a instanceof Error?a.message:String(a);if(se(a)){y(e,400,"Bad Request","invalid_request",o);return}y(e,500,"Internal Server Error","internal",`attach upgrade failed: ${o}`)}}i(He,"handleAttachUpgrade");export{ye as REMOTE_ATTACH_HEARTBEAT_MS,ve as REMOTE_ATTACH_QUIET_MS,He as handleAttachUpgrade,O as isCleanViewerClose,y as refuseUpgrade,ke as startRemoteAttachHeartbeat};
@@ -1 +1 @@
1
- var w=Object.defineProperty;var n=(t,e)=>w(t,"name",{value:e,configurable:!0});import{getNode as k}from"../../../core/canvas/canvas.js";import{decideAttachRevive as l}from"../../../core/runtime/revive.js";import{viewSocketPath as v}from"../../../core/canvas/paths.js";import{waitForBrokerViewSocket as h}from"../../../core/runtime/placement-tmux.js";import{notFound as m,usage as i}from"../../../core/errors.js";import{mayOnNode as u,ownerOnly as p}from"../router.js";function b(t){if(t==null)return{resume:!0,revive:!0,wakeParked:!0};if(typeof t!="object")throw i("attach body must be a JSON object { resume?, revive?, wakeParked? }");const e=t.resume,r=t.revive,a=t.wakeParked;if(e!==void 0&&typeof e!="boolean")throw i("resume must be a boolean");if(r!==void 0&&typeof r!="boolean")throw i("revive must be a boolean");if(a!==void 0&&typeof a!="boolean")throw i("wakeParked must be a boolean");return{resume:e??!0,revive:r??!0,wakeParked:a??!0}}n(b,"parseAttachBody");async function y(t,e,r){return e==="controller"?await u(t,"message",r):await u(t,"read",r)?!0:e==="either"&&await u(t,"message",r)}n(y,"mayAttachNode");async function P(t){const e=t.params.id;if(await k(e)===null)throw m(`unknown node: ${e}`,{received:e});if(!await y(t.principal,"either",e))throw m(`unknown node: ${e}`,{received:e});const{resume:r,revive:a,wakeParked:c}=b(t.body);let s=!1,d=!1,f=!1;if(a){const o=await l(e,{resume:r,capacity:"refuse"},c);f=o.parked,o.parked||(s=o.result.launch!==void 0,d=o.result.resumed,await h(e))}return{status:200,body:{node_id:e,socket_path:v(e),revived:s,resumed:d,parked:f}}}n(P,"handleAttach");const R=p([{method:"POST",pattern:"/v1/nodes/:id/attach",handler:P}]);export{R as attachRoutes,y as mayAttachNode};
1
+ var l=Object.defineProperty;var i=(r,e)=>l(r,"name",{value:e,configurable:!0});import{getNode as v}from"../../../core/canvas/canvas.js";import{decideAttachRevive as k}from"../../../core/runtime/revive.js";import{viewSocketPath as p}from"../../../core/canvas/paths.js";import{waitForBrokerViewSocket as b}from"../../../core/runtime/placement-tmux.js";import{liveExecutionForNode as y}from"../../../core/canvas/executions.js";import{runnerForExecution as P}from"../../../core/runtime/runner/index.js";import{waitForMachineView as A}from"../../machine-link/forward.js";import{general as N,notFound as w,usage as u}from"../../../core/errors.js";import{mayOnNode as d,ownerOnly as _}from"../router.js";const g=3e4;function x(r){if(r==null)return{resume:!0,revive:!0,wakeParked:!0};if(typeof r!="object")throw u("attach body must be a JSON object { resume?, revive?, wakeParked? }");const e=r.resume,t=r.revive,o=r.wakeParked;if(e!==void 0&&typeof e!="boolean")throw u("resume must be a boolean");if(t!==void 0&&typeof t!="boolean")throw u("revive must be a boolean");if(o!==void 0&&typeof o!="boolean")throw u("wakeParked must be a boolean");return{resume:e??!0,revive:t??!0,wakeParked:o??!0}}i(x,"parseAttachBody");async function E(r,e,t){return e==="controller"?await d(r,"message",t):await d(r,"read",t)?!0:e==="either"&&await d(r,"message",t)}i(E,"mayAttachNode");async function F(r){const e=r.params.id;if(await v(e)===null)throw w(`unknown node: ${e}`,{received:e});if(!await E(r.principal,"either",e))throw w(`unknown node: ${e}`,{received:e});const{resume:t,revive:o,wakeParked:h}=x(r.body);let c=!1,f=!1,m=!1;if(o){const a=await k(e,{resume:t,capacity:"refuse"},h);if(m=a.parked,!a.parked){c=a.result.launch!==void 0,f=a.result.resumed;const n=await y(e);if(n!==null&&n.runner==="machine"){const s=await P(n.runner).viewerAddress(n);if(s.kind!=="machine")throw new Error(`the machine execution for ${e} resolved a ${s.kind} viewer address`);if(!await A(s.environmentId,e,a.result.launch?.exited,g))throw N(`the broker for ${e} on its machine did not accept a viewer after revive`)}else await b(e)}}return{status:200,body:{node_id:e,socket_path:p(e),revived:c,resumed:f,parked:m}}}i(F,"handleAttach");const C=_([{method:"POST",pattern:"/v1/nodes/:id/attach",handler:F}]);export{C as attachRoutes,E as mayAttachNode};
@@ -1 +1 @@
1
- var X=Object.defineProperty;var t=(e,n)=>X(e,"name",{value:n,configurable:!0});import{randomUUID as nn}from"node:crypto";import{relative as en,resolve as G,sep as F}from"node:path";import{ApiError as s}from"../../../api/errors.js";import{armCron as rn,parseCronSink as on,getCron as k,listCrons as A,listCronRuns as L,pokeHeldCrons as tn,setCronState as Z}from"../../../core/canvas/crons.js";import{getNode as p}from"../../../core/canvas/canvas.js";import{appSpace as an,cronFolder as sn}from"../../../core/layout.js";import{appProcessesSandboxed as cn,cwdIsMounted as ln}from"../../../core/spaces/process.js";import{activeGrant as dn,activeGrantForApp as un,grantFor as pn}from"../../../core/grants/mirror.js";import{callerTerminalGrant as fn}from"../../../core/grants/terminal.js";import{assertScope as H}from"../../../core/scopes.js";import{InputError as c}from"../../../core/io.js";import{nextSlotAfter as wn}from"../../../core/wake.js";import{executeCron as _n,markSelfCanceledCronRun as hn}from"../../cron-run.js";import{toCronDTO as v,toCronRunDTO as M}from"../map.js";import{appOf as P,assertCeiling as mn,canReadNode as gn}from"../router.js";import{checkCronBirth as yn,checkCronFire as bn,cronRefusalError as kn}from"../../cron/authority.js";import{endCron as vn}from"../../cron/ended.js";import{requireUsableProfile as Sn}from"./profiles.js";import{filterAsync as $n,mapAsync as On}from"../../../core/async-array.js";import{personId as Cn}from"../../../core/canvas/person.js";function E(e,n){const r=e[n];if(typeof r!="string"||r.trim()==="")throw new c({error:"missing_field",message:`cron arm request requires a non-empty "${n}"`,field:n,next:`Send a non-empty string "${n}".`});return r}t(E,"requireString");function O(e,n,r,o){if(e==null)return o;if(typeof e=="string"&&r.includes(e))return e;throw new c({error:`bad_${n}`,message:`"${String(e)}" is not a valid ${n}`,field:n,next:`Use one of: ${r.join(", ")}.`})}t(O,"requireEnum");async function In(e){if(e==="human")return JSON.stringify({human:!0});if(e.startsWith("node:")){const n=e.slice(5).trim();if(n==="")throw new c({error:"bad_sink",message:"node sink requires a node id",field:"sink",next:'Use sink "node:<node-id>".'});if(await p(n)===null)throw new c({error:"sink_node_not_found",message:`sink node ${n} does not exist on the canvas`,field:"sink",next:"Name a live node id, or use spawn:<kind> for a delivery that outlives any one node."});return JSON.stringify({node:n})}if(e.startsWith("spawn:")){const n=e.slice(6).trim();if(n==="")throw new c({error:"bad_sink",message:"spawn sink requires a node kind",field:"sink",next:'Use sink "spawn:<kind>" (e.g. spawn:general).'});return JSON.stringify({spawn:n})}throw new c({error:"bad_sink",message:`"${e}" is not a valid sink`,field:"sink",next:"Use node:<node-id>, spawn:<kind>, or human."})}t(In,"parseSinkSpec");const qn=/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/;function Y(e,n){if(!qn.test(e)||Number.isNaN(Date.parse(e)))throw new c({error:`bad_${n}`,message:`"${e}" is not a UTC ISO instant`,field:n,next:'Send a resolved UTC instant like "2026-06-07T09:00:00.000Z" (the client resolves the time grammar).'});return e}t(Y,"requireUtcIso");function Nn(e){try{wn(e,new Date)}catch(n){throw new c({error:"bad_recur",message:`recur is not advanceable: ${n instanceof Error?n.message:String(n)}`,field:"recur",next:'Send recur JSON the daemon can advance: {"every":"6h"} or {"cron":"0 9 * * *","tz":"America/New_York"}.'})}return e}t(Nn,"requireRecur");function jn(e){let n;try{n=JSON.parse(e)}catch{throw new c({error:"bad_env_json",message:"env_json is not valid JSON",field:"env_json",next:'Send a JSON object of string values, e.g. {"API_BASE":"https://x"}.'})}if(typeof n!="object"||n===null||Array.isArray(n))throw new c({error:"bad_env_json",message:"env_json must be a JSON object",field:"env_json",next:'Send a JSON object of string values, e.g. {"API_BASE":"https://x"}.'});for(const[r,o]of Object.entries(n))if(typeof o!="string")throw new c({error:"bad_env_json",message:`env_json value for "${r}" is not a string`,field:"env_json",next:"Every env value must be a string."});return e}t(jn,"requireEnvJson");function U(e){const n=e.query.get("profile");return n!==null&&n.trim()!==""?n.trim():null}t(U,"callerProfile");function K(e,n){return n===null||e.scope==="global"||e.profile===n}t(K,"inScope");function J(e){if(e.principal.class==="person"&&e.principal.sub!==void 0)throw new s(403,"permission_error","schedules are managed by the install's person, a node or an app");return e.principal.class==="person"?null:P(e.principal)}t(J,"callerApp");async function R(e,n){if(!await gn(e.principal,n.grantee))return!1;const r=e.principal,o=r.class==="broker"?await p(r.node):r.class==="cron"?{run_id:r.origin_run,isolated:r.isolated}:r.class==="forwarded"&&r.actor.kind==="app"?{run_id:r.actor.place.run,isolated:r.actor.place.isolated}:null;return!(o!==null&&(o.isolated||n.isolated)&&o.run_id!==n.origin_run_id)}t(R,"visibleTo");function Tn(e){return typeof e.anchor_node=="string"&&e.anchor_node!==""?e.anchor_node:null}t(Tn,"anchorNodeOf");async function An(e,n,r,o,i,d){const u=typeof e.cwd=="string"&&e.cwd.trim()!==""?G(e.cwd):null;if(!cn()){if(u===null)throw new c({error:"missing_field",message:'cron arm request requires a non-empty "cwd"',field:"cwd",next:'Send a non-empty string "cwd".'});return u}const h=o!==null&&await p(o)!==null?o:void 0,l=t(async q=>await ln(n,q,h,i,void 0,d),"reaches");if(u!==null&&await l(u))return u;const a=an(n).files;if(u!==null&&e.cwd_is_default!==!0)throw new s(400,"invalid_request",`cwd ${u} is not inside a directory this app's cron runs can see, so every run would fail to start; use a directory under ${a}`,{cwd:u,default:a},void 0,{param:"cwd"});const f=r===null?null:(await p(r))?.cwd??null;return f!==null&&await l(f)?f:a}t(An,"resolveArmedCwd");async function Pn(e,n,r){const o=e.principal;if(o.class==="person"){if(o.sub!==void 0)throw new s(403,"permission_error","schedules are managed by the install's person, a node or an app");const a=await fn();return{sub:a?.sub??Cn(),grant_id:a?.id??null,scopes:null,origin_run_id:null}}if(o.class==="app"){const a=await un(n);if(a===null)throw new s(401,"grant_revoked",`no active grant for ${n}`);const f=e.ceiling??o.scopes;return{sub:a.sub,grant_id:a.id,scopes:f===null?null:[...f],origin_run_id:null}}if(o.class==="cron"){const a=await bn({grantee:o.app,sub:o.sub,grant_id:o.grant,scopes:o.scopes});if(!a.allowed)throw new s(a.reason==="scope_missing"?403:401,a.reason==="scope_missing"?"scope_missing":"grant_revoked",a.message);return{sub:o.sub,grant_id:o.grant,scopes:o.scopes===null?null:[...o.scopes],origin_run_id:o.origin_run,armed_from_exec:o.from_exec}}if(o.class!=="broker")throw new s(401,"unauthorized","this caller cannot arm a cron");const i=r??await p(o.node),d=i?.grant_id??o.grant,u=i?.sub??o.sub,h=d!=null?await dn(d):await pn(n,u);if(h===null)throw new s(401,"grant_revoked","the node grant is not active");const l=i?.scopes??o.scopes;return{sub:h.sub,grant_id:h.id,scopes:l===null?null:[...l],origin_run_id:i?.run_id??o.run}}t(Pn,"armPrincipal");async function En(e){const n=e.body??{},r=E(n,"name"),o=E(n,"command"),i=Y(E(n,"fire_at"),"fire_at"),d=typeof n.recur=="string"&&n.recur!==""?Nn(n.recur):null,u=typeof n.expires_at=="string"&&n.expires_at!==""?Y(n.expires_at,"expires_at"):null,h=typeof n.env_json=="string"&&n.env_json!==""?jn(n.env_json):null,l=typeof n.sink=="string"&&n.sink.trim()!==""?n.sink.trim():null,a=O(n.on_output,"on_output",["silent","on-failure","always","on-change"],l?.startsWith("node:")?"on-change":"on-failure"),f=O(n.overlap,"overlap",["skip","queue","replace"],"skip"),q=O(n.tier,"tier",["normal","urgent","critical"],"normal"),W=a==="always"||a==="on-change";if(W&&l===null)throw new c({error:"sink_required",message:`on_output "${a}" delivers stdout every qualifying run \u2014 it needs a sink`,field:"sink",next:"Pass sink node:<node-id> (a living node's inbox), spawn:<kind> (birth a fresh node per delivery), or human (the humanloop inbox)."});if(!W&&l!==null)throw new c({error:"sink_unused",message:`on_output "${a}" never delivers stdout \u2014 a sink would be dead config`,field:"sink",next:'Drop the sink, or use on_output "always"/"on-change" if delivery is the point.'});const Q=l!==null?await In(l):"{}";if(l?.startsWith("spawn:")&&(mn(e.ceiling,"crtr:act"),e.principal.class==="cron")){const y=await yn({grantee:e.principal.app,sub:e.principal.sub,grant_id:e.principal.grant,scopes:e.principal.scopes,from_exec:e.principal.from_exec},{kind:l.slice(6)});if(!y.allowed)throw kn(y)}const g=P(e.principal)??"app:terminal",b=e.principal.class==="broker"?e.principal.node:e.principal.class==="cron"?null:typeof n.created_by=="string"&&n.created_by!==""?n.created_by:null;if(e.principal.class==="broker"&&n.created_by!=null&&n.created_by!==b)throw new s(403,"permission_error","a broker may only arm a cron as its own node");const m=b===null?null:await p(b);if(m!==null){if(m.grantee!==g)throw new c({error:"creator_app_mismatch",message:"creator node belongs to another app",field:"created_by",next:"Use a node of the calling app."});H(m,"crtr:schedule"),l?.startsWith("spawn:")&&H(m,"crtr:act")}if(n.isolated!==void 0&&typeof n.isolated!="boolean")throw new s(400,"invalid_request","isolated must be a boolean");if(m?.isolated&&n.isolated===!1)throw new s(403,"isolated_run","a cron created by an isolated node cannot escape isolation");const x=m?.isolated===!0||e.principal.class==="cron"&&e.principal.isolated;if(x&&n.isolated===!1)throw new s(403,"isolated_run","a cron created by an isolated caller cannot escape isolation");const N=x||n.isolated===!0,w=await Pn(e,g,m),j=nn(),S=N?sn(g,j):null;if(S!==null&&n.cwd_is_default!==!0){const y=en(S,typeof n.cwd=="string"&&n.cwd.trim()!==""?G(n.cwd):S);if(y===".."||y.startsWith(`..${F}`)||y.startsWith(F))throw new s(400,"invalid_request","an isolated cron must run in its own cron folder",{param:"cwd"})}const V=S??await An(n,g,b,Tn(n),N,{sub:w.sub,grantId:w.grant_id,scopes:w.scopes}),_=typeof n.anchor_node=="string"&&n.anchor_node!==""?n.anchor_node:null;if(_!==null&&await p(_)!==null&&(await p(_)).grantee!==g)throw new c({error:"anchor_app_mismatch",message:"anchor node belongs to another app",field:"anchor_node",next:"Use an anchor of the calling app."});if(_!==null&&await p(_)===null)throw new c({error:"anchor_not_found",message:`anchor node ${_} does not exist on the canvas`,field:"anchor_node",next:"Name a live node id, or drop the anchor \u2014 an unanchored cron outlives every node."});const B=n.cancel_on_wake===!0;if(B&&_===null)throw new c({error:"cancel_on_wake_needs_anchor",message:"cancel_on_wake couples the cron to a node's dormancy \u2014 it requires an anchor node",field:"anchor_node",next:"Pass anchor_node with the node whose wake should cancel this cron."});const $=n.run_timeout_s??300;if(typeof $!="number"||!Number.isInteger($)||$<=0)throw new c({error:"bad_run_timeout",message:`run_timeout_s must be a positive integer (got ${String(n.run_timeout_s)})`,field:"run_timeout_s",next:"Send the timeout in whole seconds, e.g. 300."});const T=typeof n.profile=="string"&&n.profile!==""?(await Sn(e,n.profile)).profileId:null,z=O(n.scope,"scope",["profile","global"],T!==null?"profile":"global");if(z==="profile"&&T===null)throw new c({error:"scope_needs_profile",message:'scope "profile" names the profile the cron lists and cancels within \u2014 it requires a profile',field:"profile",next:'Send a profile, or use scope "global" for a canvas-home-wide cron.'});return await rn({cron_id:j,name:r,created_by:b,grantee:g,sub:w.sub,grant_id:w.grant_id,scopes:w.scopes,origin_run_id:w.origin_run_id,armed_from_exec:w.armed_from_exec===!0,isolated:N,command:o,fire_at:i,recur:d,tz:typeof n.tz=="string"?n.tz:null,expires_at:u,anchor_node:_,cancel_on_wake:B,cwd:V,env_json:h,profile:T,scope:z,run_timeout_s:$,overlap:f,on_output:a,sink:Q,tier:q}),{status:201,body:v(await k(j),null)}}t(En,"handleArm");async function Un(e){J(e);const n=U(e),r=e.query.get("created_by");if(r==="")throw new s(400,"invalid_request","created_by must be nonempty when present");const o=await $n((n===null?await A({all:!0}):[...await A({profile:n}),...await A({global:!0})].sort((i,d)=>i.fire_at<d.fire_at?-1:i.fire_at>d.fire_at?1:0)).filter(i=>r===null||i.created_by===r),async i=>await R(e,i));return{status:200,body:await On(o,async i=>v(i,await D(i)))}}t(Un,"handleList");async function D(e){return(await L(e.cron_id))[0]??null}t(D,"lastRunOf");async function C(e){const n=e.params.cronId,r=await k(n);if(r===null||!await R(e,r))throw new s(404,"cron_not_found",`no cron ${n}`);if(!K(r,U(e)))throw new s(404,"cron_not_found",`no cron ${n} in this profile \u2014 it is scoped to another profile`);return r}t(C,"requireCron");function I(e,n){const r=J(e);if(r!==null&&n.grantee!==r)throw new s(403,"other_app_run",`cron ${n.cron_id} belongs to another app`);return n}t(I,"requireOwnCron");async function Jn(e){const n=await C(e),r=await L(n.cron_id);return{status:200,body:{cron:v(n,r[0]??null),runs:r.map(M)}}}t(Jn,"handleShow");async function Rn(e){const n=I(e,await C(e));await Z(n.cron_id,"paused");const r=await k(n.cron_id);return{status:200,body:v(r,await D(r))}}t(Rn,"handlePause");async function Dn(e){const n=I(e,await C(e));await Z(n.cron_id,"active");const r=await k(n.cron_id);return{status:200,body:v(r,await D(r))}}t(Dn,"handleResume");async function Wn(e){const n=I(e,await C(e));if(n.run_state==="running")throw new s(409,"cron_running",`cron ${n.cron_id} has a run in flight \u2014 wait for it to settle`);const r=await _n(n,{outOfBand:!0});if(r===null)throw new s(409,"cron_running",`cron ${n.cron_id} was claimed by a concurrent run \u2014 check \`cron show\` for its result`);return{status:200,body:M(r)}}t(Wn,"handleRun");async function xn(e){const n=new Date().toISOString();return{status:200,body:{unparked:await tn(n,J(e)),at:n}}}t(xn,"handlePoke");function Bn(e,n){const r=e.principal;if(r.class==="cron")return r.kind==="exec"?{kind:"canceled",by:r.app}:r.cron===n.cron_id?null:{kind:"canceled",by:`cron ${r.cron}`};if(r.class==="broker"){const i=on(n.sink);return i?.kind==="node"&&i.node===r.node?null:{kind:"canceled",by:`node ${r.node}`}}const o=P(r);return{kind:"canceled",by:o===null||o==="app:terminal"?"the person":o}}t(Bn,"cancelNotice");async function zn(e){const n=e.params.cronId,r=await k(n);if(r!==null&&!await R(e,r))throw new s(404,"cron_not_found",`no cron ${n}`);if(r!==null&&!K(r,U(e)))throw new s(404,"cron_not_found",`no cron ${n} in this profile \u2014 it is scoped to another profile`);r!==null&&I(e,r);const o=e.query.get("run_id"),i=e.principal.class==="cron"?e.principal.kind==="cron"&&o===e.principal.execution&&n===e.principal.cron:!0,d=o!==null&&i&&hn(n,o);return r!==null&&await vn(r,d?null:Bn(e,r),{preserveRunningProcess:d}),{status:204}}t(zn,"handleCancel");const de=[{method:"POST",pattern:"/v1/crons",scope:"crtr:schedule",handler:En},{method:"GET",pattern:"/v1/crons",handler:Un},{method:"GET",pattern:"/v1/crons/:cronId",handler:Jn},{method:"POST",pattern:"/v1/crons/:cronId/pause",scope:"crtr:schedule",handler:Rn},{method:"POST",pattern:"/v1/crons/:cronId/resume",scope:"crtr:schedule",handler:Dn},{method:"POST",pattern:"/v1/crons/poke",scope:"crtr:schedule",handler:xn},{method:"POST",pattern:"/v1/crons/:cronId/run",scope:"crtr:schedule",handler:Wn},{method:"DELETE",pattern:"/v1/crons/:cronId",scope:"crtr:schedule",handler:zn}];export{de as cronRoutes};
1
+ var tn=Object.defineProperty;var t=(e,n)=>tn(e,"name",{value:n,configurable:!0});import{randomUUID as an}from"node:crypto";import{relative as sn,resolve as H,sep as M}from"node:path";import{ApiError as a}from"../../../api/errors.js";import{armCron as ln,parseCronSink as cn,getCron as v,listCrons as A,listCronRuns as Z,pokeHeldCrons as un,setCronState as Y}from"../../../core/canvas/crons.js";import{getNode as f}from"../../../core/canvas/canvas.js";import{appSpace as dn,cronFolder as pn}from"../../../core/layout.js";import{appProcessesSandboxed as fn,cwdIsMounted as wn}from"../../../core/spaces/process.js";import{activeGrant as _n,activeGrantForApp as hn,grantFor as mn}from"../../../core/grants/mirror.js";import{callerTerminalGrant as gn}from"../../../core/grants/terminal.js";import{assertScope as K}from"../../../core/scopes.js";import{InputError as l}from"../../../core/io.js";import{nextSlotAfter as yn}from"../../../core/wake.js";import{executeCron as bn,markSelfCanceledCronRun as kn}from"../../cron-run.js";import{toCronDTO as S,toCronRunDTO as Q}from"../map.js";import{appOf as R,assertCeiling as vn,canReadNode as Sn}from"../router.js";import{checkCronBirth as $n,checkCronFire as On,cronRefusalError as Cn}from"../../cron/authority.js";import{endCron as In}from"../../cron/ended.js";import{requireUsableProfile as qn}from"./profiles.js";import{lookupProfile as Nn}from"../../../core/profiles/manifest.js";import{filterAsync as Tn,mapAsync as jn}from"../../../core/async-array.js";import{personId as Pn}from"../../../core/canvas/person.js";import{openDb as En}from"../../../core/canvas/db.js";function U(e,n){const r=e[n];if(typeof r!="string"||r.trim()==="")throw new l({error:"missing_field",message:`cron arm request requires a non-empty "${n}"`,field:n,next:`Send a non-empty string "${n}".`});return r}t(U,"requireString");function C(e,n,r,o){if(e==null)return o;if(typeof e=="string"&&r.includes(e))return e;throw new l({error:`bad_${n}`,message:`"${String(e)}" is not a valid ${n}`,field:n,next:`Use one of: ${r.join(", ")}.`})}t(C,"requireEnum");async function An(e){if(e==="human")return JSON.stringify({human:!0});if(e.startsWith("node:")){const n=e.slice(5).trim();if(n==="")throw new l({error:"bad_sink",message:"node sink requires a node id",field:"sink",next:'Use sink "node:<node-id>".'});if(await f(n)===null)throw new l({error:"sink_node_not_found",message:`sink node ${n} does not exist on the canvas`,field:"sink",next:"Name a live node id, or use spawn:<kind> for a delivery that outlives any one node."});return JSON.stringify({node:n})}if(e.startsWith("spawn:")){const n=e.slice(6).trim();if(n==="")throw new l({error:"bad_sink",message:"spawn sink requires a node kind",field:"sink",next:'Use sink "spawn:<kind>" (e.g. spawn:general).'});return JSON.stringify({spawn:n})}throw new l({error:"bad_sink",message:`"${e}" is not a valid sink`,field:"sink",next:"Use node:<node-id>, spawn:<kind>, or human."})}t(An,"parseSinkSpec");const Rn=/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/;function V(e,n){if(!Rn.test(e)||Number.isNaN(Date.parse(e)))throw new l({error:`bad_${n}`,message:`"${e}" is not a UTC ISO instant`,field:n,next:'Send a resolved UTC instant like "2026-06-07T09:00:00.000Z" (the client resolves the time grammar).'});return e}t(V,"requireUtcIso");function Un(e){try{yn(e,new Date)}catch(n){throw new l({error:"bad_recur",message:`recur is not advanceable: ${n instanceof Error?n.message:String(n)}`,field:"recur",next:'Send recur JSON the daemon can advance: {"every":"6h"} or {"cron":"0 9 * * *","tz":"America/New_York"}.'})}return e}t(Un,"requireRecur");function Jn(e){let n;try{n=JSON.parse(e)}catch{throw new l({error:"bad_env_json",message:"env_json is not valid JSON",field:"env_json",next:'Send a JSON object of string values, e.g. {"API_BASE":"https://x"}.'})}if(typeof n!="object"||n===null||Array.isArray(n))throw new l({error:"bad_env_json",message:"env_json must be a JSON object",field:"env_json",next:'Send a JSON object of string values, e.g. {"API_BASE":"https://x"}.'});for(const[r,o]of Object.entries(n))if(typeof o!="string")throw new l({error:"bad_env_json",message:`env_json value for "${r}" is not a string`,field:"env_json",next:"Every env value must be a string."});return e}t(Jn,"requireEnvJson");function J(e){const n=e.query.get("profile");return n!==null&&n.trim()!==""?n.trim():null}t(J,"callerProfile");function X(e,n){return n===null||e.scope==="global"||e.profile===n}t(X,"inScope");function nn(e){return e.principal.class==="person"?null:R(e.principal)}t(nn,"callerApp");function I(e){return e.principal.class==="person"?e.principal.sub??null:null}t(I,"bearerPerson");async function en(e,n){return(await(await En()).prepare("SELECT person FROM profiles WHERE id = ?").get(e))?.person===n}t(en,"profileBelongsTo");async function W(e,n){const r=I(e);if(r!==null)return n.profile!==null&&await en(n.profile,r);if(!await Sn(e.principal,n.grantee))return!1;const o=e.principal,i=o.class==="broker"?await f(o.node):o.class==="cron"?{run_id:o.origin_run,isolated:o.isolated}:o.class==="forwarded"&&o.actor.kind==="app"?{run_id:o.actor.place.run,isolated:o.actor.place.isolated}:null;return!(i!==null&&(i.isolated||n.isolated)&&i.run_id!==n.origin_run_id)}t(W,"visibleTo");function Wn(e){return typeof e.anchor_node=="string"&&e.anchor_node!==""?e.anchor_node:null}t(Wn,"anchorNodeOf");async function Dn(e,n,r,o,i,u){const p=typeof e.cwd=="string"&&e.cwd.trim()!==""?H(e.cwd):null;if(!fn()){if(p===null)throw new l({error:"missing_field",message:'cron arm request requires a non-empty "cwd"',field:"cwd",next:'Send a non-empty string "cwd".'});return p}const g=o!==null&&await f(o)!==null?o:void 0,c=t(async T=>await wn(n,T,g,i,void 0,u),"reaches");if(p!==null&&await c(p))return p;const s=dn(n).files;if(p!==null&&e.cwd_is_default!==!0)throw new a(400,"invalid_request",`cwd ${p} is not inside a directory this app's cron runs can see, so every run would fail to start; use a directory under ${s}`,{cwd:p,default:s},void 0,{param:"cwd"});const w=r===null?null:(await f(r))?.cwd??null;return w!==null&&await c(w)?w:s}t(Dn,"resolveArmedCwd");async function Bn(e,n,r){const o=e.principal;if(o.class==="person"){const s=await gn();return{sub:s?.sub??o.sub??Pn(),grant_id:s?.id??null,scopes:null,origin_run_id:null}}if(o.class==="app"){const s=await hn(n);if(s===null)throw new a(401,"grant_revoked",`no active grant for ${n}`);const w=e.ceiling??o.scopes;return{sub:s.sub,grant_id:s.id,scopes:w===null?null:[...w],origin_run_id:null}}if(o.class==="cron"){const s=await On({grantee:o.app,sub:o.sub,grant_id:o.grant,scopes:o.scopes});if(!s.allowed)throw new a(s.reason==="scope_missing"?403:401,s.reason==="scope_missing"?"scope_missing":"grant_revoked",s.message);return{sub:o.sub,grant_id:o.grant,scopes:o.scopes===null?null:[...o.scopes],origin_run_id:o.origin_run,armed_from_exec:o.from_exec}}if(o.class!=="broker")throw new a(401,"unauthorized","this caller cannot arm a cron");const i=r??await f(o.node),u=i?.grant_id??o.grant,p=i?.sub??o.sub,g=u!=null?await _n(u):await mn(n,p);if(g===null)throw new a(401,"grant_revoked","the node grant is not active");const c=i?.scopes??o.scopes;return{sub:g.sub,grant_id:g.id,scopes:c===null?null:[...c],origin_run_id:i?.run_id??o.run}}t(Bn,"armPrincipal");async function zn(e){const n=e.body??{},r=U(n,"name"),o=U(n,"command"),i=V(U(n,"fire_at"),"fire_at"),u=typeof n.recur=="string"&&n.recur!==""?Un(n.recur):null,p=typeof n.expires_at=="string"&&n.expires_at!==""?V(n.expires_at,"expires_at"):null,g=typeof n.env_json=="string"&&n.env_json!==""?Jn(n.env_json):null,c=typeof n.sink=="string"&&n.sink.trim()!==""?n.sink.trim():null,s=C(n.on_output,"on_output",["silent","on-failure","always","on-change"],c?.startsWith("node:")?"on-change":"on-failure"),w=C(n.overlap,"overlap",["skip","queue","replace"],"skip"),T=C(n.tier,"tier",["normal","urgent","critical"],"normal"),B=s==="always"||s==="on-change";if(B&&c===null)throw new l({error:"sink_required",message:`on_output "${s}" delivers stdout every qualifying run \u2014 it needs a sink`,field:"sink",next:"Pass sink node:<node-id> (a living node's inbox), spawn:<kind> (birth a fresh node per delivery), or human (the humanloop inbox)."});if(!B&&c!==null)throw new l({error:"sink_unused",message:`on_output "${s}" never delivers stdout \u2014 a sink would be dead config`,field:"sink",next:'Drop the sink, or use on_output "always"/"on-change" if delivery is the point.'});const rn=c!==null?await An(c):"{}",z=I(e);if(z!==null)for(const d of[c?.startsWith("node:")?c.slice(5).trim():null,typeof n.created_by=="string"?n.created_by:null,typeof n.anchor_node=="string"?n.anchor_node:null]){const L=d===null||d===""?null:await f(d);if(L!==null&&L.sub!==z)throw new a(404,"not_found","node not found")}if(c?.startsWith("spawn:")&&(vn(e.ceiling,"crtr:act"),e.principal.class==="cron")){const d=await $n({grantee:e.principal.app,sub:e.principal.sub,grant_id:e.principal.grant,scopes:e.principal.scopes,from_exec:e.principal.from_exec},{kind:c.slice(6)});if(!d.allowed)throw Cn(d)}const b=R(e.principal)??"app:terminal",k=e.principal.class==="broker"?e.principal.node:e.principal.class==="cron"?null:typeof n.created_by=="string"&&n.created_by!==""?n.created_by:null;if(e.principal.class==="broker"&&n.created_by!=null&&n.created_by!==k)throw new a(403,"permission_error","a broker may only arm a cron as its own node");const y=k===null?null:await f(k);if(y!==null){if(y.grantee!==b)throw new l({error:"creator_app_mismatch",message:"creator node belongs to another app",field:"created_by",next:"Use a node of the calling app."});K(y,"crtr:schedule"),c?.startsWith("spawn:")&&K(y,"crtr:act")}if(n.isolated!==void 0&&typeof n.isolated!="boolean")throw new a(400,"invalid_request","isolated must be a boolean");if(y?.isolated&&n.isolated===!1)throw new a(403,"isolated_run","a cron created by an isolated node cannot escape isolation");const F=y?.isolated===!0||e.principal.class==="cron"&&e.principal.isolated;if(F&&n.isolated===!1)throw new a(403,"isolated_run","a cron created by an isolated caller cannot escape isolation");const j=F||n.isolated===!0,_=await Bn(e,b,y),P=an(),$=j?pn(b,P):null;if($!==null&&n.cwd_is_default!==!0){const d=sn($,typeof n.cwd=="string"&&n.cwd.trim()!==""?H(n.cwd):$);if(d===".."||d.startsWith(`..${M}`)||d.startsWith(M))throw new a(400,"invalid_request","an isolated cron must run in its own cron folder",{param:"cwd"})}const on=$??await Dn(n,b,k,Wn(n),j,{sub:_.sub,grantId:_.grant_id,scopes:_.scopes}),h=typeof n.anchor_node=="string"&&n.anchor_node!==""?n.anchor_node:null;if(h!==null&&await f(h)!==null&&(await f(h)).grantee!==b)throw new l({error:"anchor_app_mismatch",message:"anchor node belongs to another app",field:"anchor_node",next:"Use an anchor of the calling app."});if(h!==null&&await f(h)===null)throw new l({error:"anchor_not_found",message:`anchor node ${h} does not exist on the canvas`,field:"anchor_node",next:"Name a live node id, or drop the anchor \u2014 an unanchored cron outlives every node."});const G=n.cancel_on_wake===!0;if(G&&h===null)throw new l({error:"cancel_on_wake_needs_anchor",message:"cancel_on_wake couples the cron to a node's dormancy \u2014 it requires an anchor node",field:"anchor_node",next:"Pass anchor_node with the node whose wake should cancel this cron."});const O=n.run_timeout_s??300;if(typeof O!="number"||!Number.isInteger(O)||O<=0)throw new l({error:"bad_run_timeout",message:`run_timeout_s must be a positive integer (got ${String(n.run_timeout_s)})`,field:"run_timeout_s",next:"Send the timeout in whole seconds, e.g. 300."});const E=I(e),m=typeof n.profile!="string"||n.profile===""?null:E===null?(await qn(e,n.profile)).profileId:(()=>{const d=Nn(n.profile);if(d.kind!=="resolved")throw new a(404,"not_found","profile not found");return d.entry.profileId})();if(E!==null&&(m===null||!await en(m,E)))throw new a(m===null?400:404,m===null?"invalid_request":"not_found",m===null?"a person's cron names the profile it belongs to":"profile not found",void 0,void 0,{param:"profile"});const x=C(n.scope,"scope",["profile","global"],m!==null?"profile":"global");if(x==="profile"&&m===null)throw new l({error:"scope_needs_profile",message:'scope "profile" names the profile the cron lists and cancels within \u2014 it requires a profile',field:"profile",next:'Send a profile, or use scope "global" for a canvas-home-wide cron.'});return await ln({cron_id:P,name:r,created_by:k,grantee:b,sub:_.sub,grant_id:_.grant_id,scopes:_.scopes,origin_run_id:_.origin_run_id,armed_from_exec:_.armed_from_exec===!0,isolated:j,command:o,fire_at:i,recur:u,tz:typeof n.tz=="string"?n.tz:null,expires_at:p,anchor_node:h,cancel_on_wake:G,cwd:on,env_json:g,profile:m,scope:x,run_timeout_s:O,overlap:w,on_output:s,sink:rn,tier:T}),{status:201,body:S(await v(P),null)}}t(zn,"handleArm");async function Fn(e){const n=J(e),r=e.query.get("created_by");if(r==="")throw new a(400,"invalid_request","created_by must be nonempty when present");const o=await Tn((n===null?await A({all:!0}):[...await A({profile:n}),...await A({global:!0})].sort((i,u)=>i.fire_at<u.fire_at?-1:i.fire_at>u.fire_at?1:0)).filter(i=>r===null||i.created_by===r),async i=>await W(e,i));return{status:200,body:await jn(o,async i=>S(i,await D(i)))}}t(Fn,"handleList");async function D(e){return(await Z(e.cron_id))[0]??null}t(D,"lastRunOf");async function q(e){const n=e.params.cronId,r=await v(n);if(r===null||!await W(e,r))throw new a(404,"cron_not_found",`no cron ${n}`);if(!X(r,J(e)))throw new a(404,"cron_not_found",`no cron ${n} in this profile \u2014 it is scoped to another profile`);return r}t(q,"requireCron");function N(e,n){const r=nn(e);if(r!==null&&n.grantee!==r)throw new a(403,"other_app_run",`cron ${n.cron_id} belongs to another app`);return n}t(N,"requireOwnCron");async function Gn(e){const n=await q(e),r=await Z(n.cron_id);return{status:200,body:{cron:S(n,r[0]??null),runs:r.map(Q)}}}t(Gn,"handleShow");async function xn(e){const n=N(e,await q(e));await Y(n.cron_id,"paused");const r=await v(n.cron_id);return{status:200,body:S(r,await D(r))}}t(xn,"handlePause");async function Ln(e){const n=N(e,await q(e));await Y(n.cron_id,"active");const r=await v(n.cron_id);return{status:200,body:S(r,await D(r))}}t(Ln,"handleResume");async function Hn(e){const n=N(e,await q(e));if(n.run_state==="running")throw new a(409,"cron_running",`cron ${n.cron_id} has a run in flight \u2014 wait for it to settle`);const r=await bn(n,{outOfBand:!0});if(r===null)throw new a(409,"cron_running",`cron ${n.cron_id} was claimed by a concurrent run \u2014 check \`cron show\` for its result`);return{status:200,body:Q(r)}}t(Hn,"handleRun");async function Mn(e){if(I(e)!==null)throw new a(403,"permission_error","a person's sign-in does not poke the whole control plane's crons");const n=new Date().toISOString();return{status:200,body:{unparked:await un(n,nn(e)),at:n}}}t(Mn,"handlePoke");function Zn(e,n){const r=e.principal;if(r.class==="cron")return r.kind==="exec"?{kind:"canceled",by:r.app}:r.cron===n.cron_id?null:{kind:"canceled",by:`cron ${r.cron}`};if(r.class==="broker"){const i=cn(n.sink);return i?.kind==="node"&&i.node===r.node?null:{kind:"canceled",by:`node ${r.node}`}}const o=R(r);return{kind:"canceled",by:o===null||o==="app:terminal"?"the person":o}}t(Zn,"cancelNotice");async function Yn(e){const n=e.params.cronId,r=await v(n);if(r!==null&&!await W(e,r))throw new a(404,"cron_not_found",`no cron ${n}`);if(r!==null&&!X(r,J(e)))throw new a(404,"cron_not_found",`no cron ${n} in this profile \u2014 it is scoped to another profile`);r!==null&&N(e,r);const o=e.query.get("run_id"),i=e.principal.class==="cron"?e.principal.kind==="cron"&&o===e.principal.execution&&n===e.principal.cron:!0,u=o!==null&&i&&kn(n,o);return r!==null&&await In(r,u?null:Zn(e,r),{preserveRunningProcess:u}),{status:204}}t(Yn,"handleCancel");const ye=[{method:"POST",pattern:"/v1/crons",scope:"crtr:schedule",handler:zn},{method:"GET",pattern:"/v1/crons",handler:Fn},{method:"GET",pattern:"/v1/crons/:cronId",handler:Gn},{method:"POST",pattern:"/v1/crons/:cronId/pause",scope:"crtr:schedule",handler:xn},{method:"POST",pattern:"/v1/crons/:cronId/resume",scope:"crtr:schedule",handler:Ln},{method:"POST",pattern:"/v1/crons/poke",scope:"crtr:schedule",handler:Mn},{method:"POST",pattern:"/v1/crons/:cronId/run",scope:"crtr:schedule",handler:Hn},{method:"DELETE",pattern:"/v1/crons/:cronId",scope:"crtr:schedule",handler:Yn}];export{ye as cronRoutes};
@@ -1 +1 @@
1
- var i=Object.defineProperty;var r=(e,t)=>i(e,"name",{value:t,configurable:!0});import{toMigrateStateDTO as u}from"../map.js";import{migrateLooseKeys as m}from"../../../core/layout-migrate/loose-keys.js";import{boundDaemonControl as s}from"../../control.js";import{ownerOnly as c}from"../router.js";function l(){const e=s();if(e===null)throw{status:503,code:"daemon_control_unavailable",message:"this API server is not hosted by a live daemon and cannot hand over"};const{graceMs:t}=e.requestHandover();return{status:202,body:{accepted:!0,predecessor_pid:process.pid,predecessor_epoch:e.epoch,grace_ms:t}}}r(l,"handleRestart");async function y(e){if(e.body!==void 0)throw{status:400,code:"invalid_request",message:"admission request must not include a body"};const t=s();if(t===null)throw{status:503,code:"daemon_control_unavailable",message:"this API server is not hosted by a live daemon and cannot admit normal work"};return{status:200,body:await t.admitStartup()}}r(y,"handleAdmit");async function h(e){const t=s();if(t===null)throw{status:503,code:"daemon_control_unavailable",message:"this API server is not hosted by a live daemon and cannot run the canvas import"};const o=e.body;if(o!==void 0&&(typeof o!="object"||o===null||Array.isArray(o)))throw{status:400,code:"invalid_request",message:"migration request body must be an object"};const n=o??{};if(n.dry_run!==void 0&&typeof n.dry_run!="boolean")throw{status:400,code:"invalid_request",message:"dry_run must be a boolean"};const d=await m(n.dry_run===!0),a=await t.repairMigrations({dryRun:n.dry_run===!0});return{status:200,body:{...u(a.result,{resumed:a.resumed,resumeError:a.resumeError}),loose_keys:d}}}r(h,"handleMigrate");const g=c([{method:"POST",pattern:"/v1/daemon/restart",handler:r(e=>l(),"handler")},{method:"POST",pattern:"/v1/daemon/migrate",handler:h},{method:"POST",pattern:"/v1/daemon/admit",handler:y}]);export{g as daemonRoutes};
1
+ var i=Object.defineProperty;var a=(t,e)=>i(t,"name",{value:e,configurable:!0});import{drainState as u,endDrain as m,isDrained as c,settleDrain as l,startDrain as y}from"../../drain.js";import{toMigrateStateDTO as b}from"../map.js";import{migrateLooseKeys as h}from"../../../core/layout-migrate/loose-keys.js";import{boundDaemonControl as s}from"../../control.js";import{ownerOnly as f}from"../router.js";function p(){const t=s();if(t===null)throw{status:503,code:"daemon_control_unavailable",message:"this API server is not hosted by a live daemon and cannot hand over"};const{graceMs:e}=t.requestHandover();return{status:202,body:{accepted:!0,predecessor_pid:process.pid,predecessor_epoch:t.epoch,grace_ms:e}}}a(p,"handleRestart");async function _(t){if(t.body!==void 0)throw{status:400,code:"invalid_request",message:"admission request must not include a body"};const e=s();if(e===null)throw{status:503,code:"daemon_control_unavailable",message:"this API server is not hosted by a live daemon and cannot admit normal work"};return{status:200,body:await e.admitStartup()}}a(_,"handleAdmit");async function v(t){const e=s();if(e===null)throw{status:503,code:"daemon_control_unavailable",message:"this API server is not hosted by a live daemon and cannot run the canvas import"};const n=t.body;if(n!==void 0&&(typeof n!="object"||n===null||Array.isArray(n)))throw{status:400,code:"invalid_request",message:"migration request body must be an object"};const o=n??{};if(o.dry_run!==void 0&&typeof o.dry_run!="boolean")throw{status:400,code:"invalid_request",message:"dry_run must be a boolean"};const d=await h(o.dry_run===!0),r=await e.repairMigrations({dryRun:o.dry_run===!0});return{status:200,body:{...b(r.result,{resumed:r.resumed,resumeError:r.resumeError}),loose_keys:d}}}a(v,"handleMigrate");async function w(t){const e=t.body??{};if(typeof e!="object"||e===null||Array.isArray(e))throw{status:400,code:"invalid_request",message:"drain request body must be an object"};const n=e.settle_limit_ms;if(n!==void 0&&(typeof n!="number"||!Number.isFinite(n)||n<0))throw{status:400,code:"invalid_request",message:"settle_limit_ms must be a non-negative number"};return await y(),{status:200,body:n===void 0?await u():await l({limitMs:n})}}a(w,"handleDrain");async function g(){const t=await c();return{status:200,body:{...await m(),was_drained:t}}}a(g,"handleEndDrain");const O=f([{method:"POST",pattern:"/v1/daemon/restart",handler:a(t=>p(),"handler")},{method:"POST",pattern:"/v1/daemon/migrate",handler:v},{method:"POST",pattern:"/v1/daemon/admit",handler:_},{method:"POST",pattern:"/v1/daemon/drain",handler:w},{method:"DELETE",pattern:"/v1/daemon/drain",handler:g}]);export{O as daemonRoutes};
@@ -1,2 +1,2 @@
1
- var w=Object.defineProperty;var p=(r,n)=>w(r,"name",{value:n,configurable:!0});import{ApiError as s}from"../../../api/errors.js";import{getNode as f}from"../../../core/canvas/canvas.js";import{openDb as d}from"../../../core/canvas/db.js";import{grantedProviderIds as h,isProviderBundleRow as g}from"../../../core/grants/mirror.js";import{environmentOfNode as E,recipeOf as k}from"../environment-of.js";import{mintInstallationToken as b}from"./git-credential.js";async function v(r,n){const i=await d(),u=n===null?["user"]:["user",`profile:${n}`],t=await i.prepare(`SELECT owner_key, name, version, source_marketplace, source_path FROM scope_plugins
2
- WHERE person = ? AND enabled = 1 AND owner_key IN (${u.map(()=>"?").join(", ")}) ORDER BY name`).all(r,...u),o=new Map;for(const e of t)(!o.has(e.name)||e.owner_key!=="user")&&o.set(e.name,e);const a=[],l=await h(r);for(const e of o.values()){if(g(e,l))continue;if(e.source_path!==null){a.push({name:e.name,source:{path:e.source_path},version:e.version});continue}if(e.source_marketplace===null)throw new s(500,"plugin_source_missing",`plugin ${e.name} names neither a marketplace nor a path`);const c=await i.prepare("SELECT url, ref FROM scope_marketplaces WHERE person = ? AND owner_key = ? AND name = ?").get(r,e.owner_key,e.source_marketplace);a.push({name:e.name,source:{marketplace:{url:c?.url??null,ref:c?.ref??null}},version:e.version})}return a}p(v,"plugins");async function x(r){const{principal:n}=r;if(n.class!=="broker")throw new s(403,"broker_only","the start bundle is read with a node token");const i=r.params.execution_id??"",u=await d(),t=await u.prepare("SELECT node_id, state, launch_json FROM executions WHERE execution_id = ?").get(i);if(t===void 0||t.node_id!==n.node)throw new s(403,"not_your_execution",`execution ${i} is not an execution of this node`);if(t.state!=="launching"&&t.state!=="running")throw new s(403,"execution_ended",`execution ${i} is ${t.state}`);if(t.launch_json===null)throw new s(409,"launch_missing",`execution ${i} carries no launch recipe`);const o=await f(n.node);if(o===null)throw new s(404,"node_not_found",`no node ${n.node}`);const a=await u.prepare("SELECT plugins_revision FROM person_settings WHERE person = ?").get(n.sub),l=await E(o.profile_id??null),e=await k(l),c=e?.repositories.map(_=>_.github_full_name)??[],m=c.length===0?null:(await b(n.sub,c)).author;return{status:200,body:{launch:JSON.parse(t.launch_json),node:{id:o.node_id,app:n.app,cwd:o.cwd,profile:o.profile_id??null,isolated:o.isolated===!0,environment_id:l},recipe:e,git_identity:m,plugins_revision:a===void 0?0:Number(a.plugins_revision),plugins:await v(n.sub,o.profile_id??null)}}}p(x,"handleStart");const A=[{method:"GET",pattern:"/v1/executions/:execution_id/start",handler:x}];export{A as executionStartRoutes};
1
+ var f=Object.defineProperty;var c=(i,e)=>f(i,"name",{value:e,configurable:!0});import{ApiError as a}from"../../../api/errors.js";import{getNode as w}from"../../../core/canvas/canvas.js";import{openDb as d}from"../../../core/canvas/db.js";import{blobStore as h}from"../../../core/blobs/index.js";import{presignGet as E,releaseSignerFromEnv as b}from"../../../core/secrets/blaxel-launch.js";import{grantedProviderIds as g,isProviderBundleRow as k}from"../../../core/grants/mirror.js";import{profileMetadataEnv as x}from"../../../core/runtime/spawn-env.js";import{localEnvironmentSecrets as y}from"../../../core/secrets/environment-owner.js";import{environmentOfNode as v,recipeOf as R}from"../environment-of.js";import{mintInstallationToken as S}from"./git-credential.js";async function $(i,e){const n=await d(),s=e===null?["user"]:["user",`profile:${e}`],t=await n.prepare(`SELECT owner_key, name, version, source_marketplace, source_path FROM scope_plugins
2
+ WHERE person = ? AND enabled = 1 AND owner_key IN (${s.map(()=>"?").join(", ")}) ORDER BY name`).all(i,...s),r=new Map;for(const o of t)(!r.has(o.name)||o.owner_key!=="user")&&r.set(o.name,o);const u=[],p=await g(i);for(const o of r.values()){if(k(o,p))continue;if(o.source_path!==null){u.push({name:o.name,source:{path:o.source_path},version:o.version});continue}if(o.source_marketplace===null)throw new a(500,"plugin_source_missing",`plugin ${o.name} names neither a marketplace nor a path`);const l=await n.prepare("SELECT url, ref FROM scope_marketplaces WHERE person = ? AND owner_key = ? AND name = ?").get(i,o.owner_key,o.source_marketplace);u.push({name:o.name,source:{marketplace:{url:l?.url??null,ref:l?.ref??null}},version:o.version})}return u}c($,"plugins");const O=900;async function T(i){const e=await d(),n=await e.prepare("SELECT blob_key, unpacked_at FROM import_node_files WHERE node = ?").get(i);if(n===void 0)return null;if(!(await(await h()).list(n.blob_key)).includes(n.blob_key)){if(n.unpacked_at!==null)return null;throw new a(500,"import_files_missing",`the imported archive ${n.blob_key} of ${i} is gone and no sandbox unpacked it`)}const s=/^imports\/([^/]+)\/node-files\//.exec(n.blob_key)?.[1];if(s===void 0)throw new a(500,"import_files_invalid",`import_node_files names ${n.blob_key} for ${i}, which is no imports/<run>/node-files key`);const t=await e.prepare("SELECT source_value, target_value FROM import_id_map WHERE run_id = ? AND kind = 'path' ORDER BY source_value").all(s);return{url:E(b(),n.blob_key,O),path_map:t.map(r=>({from:r.source_value,to:r.target_value}))}}c(T,"importFiles");async function N(i){const{principal:e}=i;if(e.class!=="broker")throw new a(403,"broker_only","this route is called with a node token");const n=i.params.execution_id??"",t=await(await d()).prepare("SELECT node_id, state FROM executions WHERE execution_id = ?").get(n);if(t===void 0||t.node_id!==e.node)throw new a(403,"not_your_execution",`execution ${n} is not an execution of this node`);if(t.state!=="launching"&&t.state!=="running")throw new a(403,"execution_ended",`execution ${n} is ${t.state}`);return e.node}c(N,"ownedExecution");async function D(i){const e=await N(i);return await(await d()).prepare("UPDATE import_node_files SET unpacked_at = ? WHERE node = ? AND unpacked_at IS NULL").run(new Date().toISOString(),e),{status:200,body:{}}}c(D,"handleImportUnpacked");async function L(i){const{principal:e}=i;if(e.class!=="broker")throw new a(403,"broker_only","the start bundle is read with a node token");const n=i.params.execution_id??"",s=await d(),t=await s.prepare("SELECT node_id, state, launch_json, runner FROM executions WHERE execution_id = ?").get(n);if(t===void 0||t.node_id!==e.node)throw new a(403,"not_your_execution",`execution ${n} is not an execution of this node`);if(t.state!=="launching"&&t.state!=="running")throw new a(403,"execution_ended",`execution ${n} is ${t.state}`);if(t.launch_json===null)throw new a(409,"launch_missing",`execution ${n} carries no launch recipe`);const r=await w(e.node);if(r===null)throw new a(404,"node_not_found",`no node ${e.node}`);const u=await s.prepare("SELECT plugins_revision FROM person_settings WHERE person = ?").get(e.sub),p=await v(r.profile_id??null),o=await R(p),l=o?.repositories.map(m=>m.github_full_name)??[],_=l.length===0?null:(await S(e.sub,l)).author;return{status:200,body:{launch:JSON.parse(t.launch_json),node:{id:r.node_id,app:e.app,cwd:r.cwd,profile:r.profile_id??null,isolated:r.isolated===!0,environment_id:p},recipe:o,git_identity:_,plugins_revision:u===void 0?0:Number(u.plugins_revision),plugins:await $(e.sub,r.profile_id??null),import_node_files:await T(r.node_id),...t.runner==="machine"?{machine_env:{...x(r.profile_id??null),...await y(r.profile_id??null,e.app)}}:{}}}}c(L,"handleStart");const Y=[{method:"GET",pattern:"/v1/executions/:execution_id/start",handler:L},{method:"POST",pattern:"/v1/executions/:execution_id/import-unpacked",handler:D}];export{Y as executionStartRoutes};
@@ -1 +1 @@
1
- var R=Object.defineProperty;var s=(e,t)=>R(e,"name",{value:t,configurable:!0});import{closeSync as T,constants as q,fstatSync as B,readSync as v}from"node:fs";import{isAbsolute as L,relative as F,resolve as b,sep as I}from"node:path";import{filterAsync as M}from"../../../core/async-array.js";import{appSpace as g}from"../../../core/layout.js";import{ApiError as f}from"../../../api/index.js";import{notFound as E,usage as c}from"../../../core/errors.js";import{InputError as k}from"../../../core/io.js";import{RunError as m}from"../../../core/runs/operations.js";import{isBeneathRefusal as z,listBeneath as N,openBeneath as O,writeFileBeneath as D}from"../../../core/spaces/open-beneath.js";import{fileRoot as P}from"../../../core/spaces/permissions.js";import{openDb as C}from"../../../core/canvas/db.js";import{canReadNode as $,resolvedScopes as S}from"../router.js";const h=512*1024,_=1024*1024,W=1e3;function y(e,t){if(e===null||e==="")throw c(`a file ${t} requires a \`path\` ${t==="write"?"body field":"query param"}`);if(!L(e))throw c(`file ${t} path must be absolute`,{received:e});return e}s(y,"requireAbsolutePath");function A(e){if(e==null)return"utf8";if(e==="utf8"||e==="base64")return e;throw c("encoding must be `utf8` or `base64`",{received:e})}s(A,"parseEncoding");function p(e,t,r){if(e instanceof f||e instanceof k||e instanceof m)throw e;if(z(e))throw new m(400,"invalid_request",`${t} meets a symlink, leaves its permitted root, or is not a regular file`,"path");const n=e.code;throw n==="ENOENT"?E(`no such file: ${t}`,{received:t}):n==="EACCES"||n==="EPERM"?new f(403,"forbidden",`permission denied ${r} ${t}`):new f(500,"internal",`cannot ${r} ${t}: ${e instanceof Error?e.message:String(e)}`)}s(p,"mapFilesystemError");async function Y(e,t,r,n){const i=await(await C()).prepare("SELECT isolated FROM nodes WHERE node_id = ?").get(t),[l,d]=F(g(r).root,b(n)).split(I),a=l==="nodes"&&d?await $(e.principal,r,d):!1;return{isolated:i?.isolated===1,reaches:s(()=>a,"reaches")}}s(Y,"runConfinementFor");async function w(e,t,r){const n=e.principal;if(n.class==="forwarded"||n.class==="machine")throw new f(403,"permission_error","this operation is not available to the caller");if(n.class==="person"&&n.sub!==void 0)throw new f(403,"permission_error","files are read by the install's person or a node");const o=n.class==="person"?null:n.class==="app"?{app:n.app,resolved:n.scopes,launched:null}:n.class==="cron"?{app:n.app,resolved:await S(n),launched:n.scopes}:{app:n.app,resolved:await S(n),launched:n.scopes,run:await Y(e,n.node,n.app,t)},i=P(o,t,r);if("root"in i)return i.root;throw"hidden"in i?E(`no such file: ${t}`,{received:t}):new m(403,"scope_missing",i.missing?`missing scope ${i.missing}`:`no permission names ${t}`,void 0,void 0,i.missing??void 0)}s(w,"rootFor");function Z(e,t,r){let n;try{n=O(e,t,q.O_RDONLY)}catch(o){return p(o,t,"reading")}try{const o=B(n);if(o.isDirectory())throw c(`file peek path is a directory: ${t}`,{received:t});const i=Buffer.allocUnsafe(Math.min(o.size,h)+1),l=v(n,i,0,i.length,0),d=l>h,a=i.subarray(0,Math.min(l,h)).toString(r);return{path:t,content:a,truncated:d}}catch(o){return p(o,t,"reading")}finally{T(n)}}s(Z,"readHead");async function j(e){const t=y(e.query.get("path"),"peek");return{status:200,body:Z(await w(e,t,"read"),t,A(e.query.get("encoding")))}}s(j,"handlePeek");function G(e){return e.length%4===0&&/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(e)}s(G,"isBase64");function H(e){if(typeof e!="object"||e===null||Array.isArray(e))throw c("file write requires an object body");const t=e,r=y(typeof t.path=="string"?t.path:null,"write");if(typeof t.content!="string")throw c("file write requires a string `content` body field");const n=A(t.encoding);if(n==="base64"&&!G(t.content))throw c("base64 file content must be valid base64");return{path:r,content:t.content,encoding:n}}s(H,"parseWriteBody");function U(e,t,r){if(r.byteLength>_)throw new f(413,"file_too_large",`file content exceeds the ${_} byte limit`);try{return D(e,t,r,{mode:process.platform==="linux"?432:384,atomic:!0}),{path:t,bytes_written:r.byteLength}}catch(n){return p(n,t,"writing")}}s(U,"writeAtomically");async function X(e){const{path:t,content:r,encoding:n}=H(e.body);return{status:200,body:U(await w(e,t,"rw"),t,Buffer.from(r,n))}}s(X,"handleWrite");function K(e){if(e===null)return W;const t=Number(e);if(!Number.isInteger(t)||t<1)throw c("file list limit must be a positive integer",{received:e});return t}s(K,"parseLimit");async function J(e){const t=y(e.query.get("path"),"list"),r=K(e.query.get("limit")),n=await w(e,t,"read");let o;try{o=N(n,t,r+1)}catch(a){return p(a,t,"listing")}const i=e.principal;i.class==="broker"&&b(t)===g(i.app).nodes&&(o=await M(o,async({name:a})=>await $(i,i.app,a)));const l=o.slice(0,r).map(({name:a,stat:u})=>({name:a,type:u.isFile()?"file":u.isDirectory()?"dir":"other",size:u.size,modified:u.mtime.toISOString()}));return{status:200,body:{path:t,entries:l,truncated:o.length>r}}}s(J,"handleList");const fe=[{method:"GET",pattern:"/v1/files/peek",scope:"crtr:act",handler:j},{method:"POST",pattern:"/v1/files/write",scope:"crtr:act",handler:X},{method:"GET",pattern:"/v1/files/list",scope:"crtr:act",handler:J}];export{fe as fileRoutes};
1
+ var R=Object.defineProperty;var s=(e,t)=>R(e,"name",{value:t,configurable:!0});import{closeSync as T,constants as q,fstatSync as B,readSync as v}from"node:fs";import{isAbsolute as L,relative as F,resolve as b,sep as I}from"node:path";import{filterAsync as M}from"../../../core/async-array.js";import{appSpace as g}from"../../../core/layout.js";import{ApiError as f}from"../../../api/index.js";import{notFound as E,usage as c}from"../../../core/errors.js";import{InputError as k}from"../../../core/io.js";import{RunError as m}from"../../../core/runs/operations.js";import{isBeneathRefusal as z,listBeneath as N,openBeneath as O,writeFileBeneath as D}from"../../../core/spaces/open-beneath.js";import{fileRoot as P}from"../../../core/spaces/permissions.js";import{openDb as C}from"../../../core/canvas/db.js";import{canReadNode as $,resolvedScopes as S}from"../router.js";const h=512*1024,_=1024*1024,W=1e3;function y(e,t){if(e===null||e==="")throw c(`a file ${t} requires a \`path\` ${t==="write"?"body field":"query param"}`);if(!L(e))throw c(`file ${t} path must be absolute`,{received:e});return e}s(y,"requireAbsolutePath");function A(e){if(e==null)return"utf8";if(e==="utf8"||e==="base64")return e;throw c("encoding must be `utf8` or `base64`",{received:e})}s(A,"parseEncoding");function u(e,t,r){if(e instanceof f||e instanceof k||e instanceof m)throw e;if(z(e))throw new m(400,"invalid_request",`${t} meets a symlink, leaves its permitted root, or is not a regular file`,"path");const n=e.code;throw n==="ENOENT"?E(`no such file: ${t}`,{received:t}):n==="EACCES"||n==="EPERM"?new f(403,"forbidden",`permission denied ${r} ${t}`):new f(500,"internal",`cannot ${r} ${t}: ${e instanceof Error?e.message:String(e)}`)}s(u,"mapFilesystemError");async function Y(e,t,r,n){const i=await(await C()).prepare("SELECT isolated FROM nodes WHERE node_id = ?").get(t),[l,d]=F(g(r).root,b(n)).split(I),a=l==="nodes"&&d?await $(e.principal,r,d):!1;return{isolated:i?.isolated===1,reaches:s(()=>a,"reaches")}}s(Y,"runConfinementFor");async function w(e,t,r){const n=e.principal;if(n.class==="forwarded"||n.class==="machine"||n.class==="import")throw new f(403,"permission_error","this operation is not available to the caller");if(n.class==="person"&&n.sub!==void 0)throw new f(403,"permission_error","files are read by the install's person or a node");const o=n.class==="person"?null:n.class==="app"?{app:n.app,resolved:n.scopes,launched:null}:n.class==="cron"?{app:n.app,resolved:await S(n),launched:n.scopes}:{app:n.app,resolved:await S(n),launched:n.scopes,run:await Y(e,n.node,n.app,t)},i=P(o,t,r);if("root"in i)return i.root;throw"hidden"in i?E(`no such file: ${t}`,{received:t}):new m(403,"scope_missing",i.missing?`missing scope ${i.missing}`:`no permission names ${t}`,void 0,void 0,i.missing??void 0)}s(w,"rootFor");function Z(e,t,r){let n;try{n=O(e,t,q.O_RDONLY)}catch(o){return u(o,t,"reading")}try{const o=B(n);if(o.isDirectory())throw c(`file peek path is a directory: ${t}`,{received:t});const i=Buffer.allocUnsafe(Math.min(o.size,h)+1),l=v(n,i,0,i.length,0),d=l>h,a=i.subarray(0,Math.min(l,h)).toString(r);return{path:t,content:a,truncated:d}}catch(o){return u(o,t,"reading")}finally{T(n)}}s(Z,"readHead");async function j(e){const t=y(e.query.get("path"),"peek");return{status:200,body:Z(await w(e,t,"read"),t,A(e.query.get("encoding")))}}s(j,"handlePeek");function G(e){return e.length%4===0&&/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(e)}s(G,"isBase64");function H(e){if(typeof e!="object"||e===null||Array.isArray(e))throw c("file write requires an object body");const t=e,r=y(typeof t.path=="string"?t.path:null,"write");if(typeof t.content!="string")throw c("file write requires a string `content` body field");const n=A(t.encoding);if(n==="base64"&&!G(t.content))throw c("base64 file content must be valid base64");return{path:r,content:t.content,encoding:n}}s(H,"parseWriteBody");function U(e,t,r){if(r.byteLength>_)throw new f(413,"file_too_large",`file content exceeds the ${_} byte limit`);try{return D(e,t,r,{mode:process.platform==="linux"?432:384,atomic:!0}),{path:t,bytes_written:r.byteLength}}catch(n){return u(n,t,"writing")}}s(U,"writeAtomically");async function X(e){const{path:t,content:r,encoding:n}=H(e.body);return{status:200,body:U(await w(e,t,"rw"),t,Buffer.from(r,n))}}s(X,"handleWrite");function K(e){if(e===null)return W;const t=Number(e);if(!Number.isInteger(t)||t<1)throw c("file list limit must be a positive integer",{received:e});return t}s(K,"parseLimit");async function J(e){const t=y(e.query.get("path"),"list"),r=K(e.query.get("limit")),n=await w(e,t,"read");let o;try{o=N(n,t,r+1)}catch(a){return u(a,t,"listing")}const i=e.principal;i.class==="broker"&&b(t)===g(i.app).nodes&&(o=await M(o,async({name:a})=>await $(i,i.app,a)));const l=o.slice(0,r).map(({name:a,stat:p})=>({name:a,type:p.isFile()?"file":p.isDirectory()?"dir":"other",size:p.size,modified:p.mtime.toISOString()}));return{status:200,body:{path:t,entries:l,truncated:o.length>r}}}s(J,"handleList");const fe=[{method:"GET",pattern:"/v1/files/peek",scope:"crtr:act",handler:j},{method:"POST",pattern:"/v1/files/write",scope:"crtr:act",handler:X},{method:"GET",pattern:"/v1/files/list",scope:"crtr:act",handler:J}];export{fe as fileRoutes};
@@ -0,0 +1,2 @@
1
+ import type { RouteTable } from '../router.js';
2
+ export declare const importRoutes: RouteTable;
@@ -0,0 +1 @@
1
+ var k=Object.defineProperty;var a=(t,n)=>k(t,"name",{value:n,configurable:!0});import{randomBytes as E}from"node:crypto";import{ApiError as o}from"../../../api/errors.js";import{blobStore as w}from"../../../core/blobs/index.js";import{presignPutUrl as $,r2ConfigFromEnv as v}from"../../../core/blobs/r2.js";import{openDb as c}from"../../../core/canvas/db.js";import{bundleKeys as m}from"../../../core/import/bundle/layout.js";import{putStore as O,StoreBodyError as T,STORE_NAMES as R}from"../../../core/import/load/stores.js";import{rollbackRun as D}from"../../../core/import/load/rollback.js";import{crtrVersion as f}from"../../../shared/crtr-version.js";import{importTokenFor as N}from"../import-auth.js";const h=3600;function y(t){if(t.principal.class!=="person"||t.principal.sub===void 0)throw new o(403,"person_only","an import is made by a signed-in person");return t.principal.sub}a(y,"personOf");async function _(t,n=!1){const e=t.params.run_id??"",r=await(await c()).prepare("SELECT * FROM import_runs WHERE run_id = ?").get(e),s=t.principal;if(s.class==="import"){if(s.run===null&&!n)throw new o(403,"permission_error","the service credential starts and reads runs; the import token uploads and loads");if(r===void 0||(s.run===null?r.source_kind!=="hosted":s.run!==r.run_id))throw new o(404,"import_not_found",`no import run ${e}`);if(s.run!==null&&!n&&r.state!=="uploading"&&r.state!=="loading")throw new o(403,"import_token_expired",`import run ${r.run_id} is ${r.state}: its import token no longer works`);return r}if(r===void 0||r.person!==y(t))throw new o(404,"import_not_found",`no import run ${e}`);return r}a(_,"ownRun");function A(t,n,e){const r=t.principal;if(r.class!=="import")return y(t);if(r.run!==null)throw new o(403,"permission_error","an import token reaches only its own run");const s=n.person;if(typeof s!="string"||!/^user:.+/.test(s))throw new o(400,"invalid_body","person (user:<id>) is required");if(e!=="hosted")throw new o(400,"invalid_body","the service credential starts hosted runs only: source_kind must be hosted");return s}a(A,"startPerson");const g=a(t=>{if(t===null||typeof t!="object"||Array.isArray(t))throw new o(400,"invalid_body","the body must be a JSON object");return t},"objectBody");async function j(t){const n=g(t.body),e=A(t,n,n.source_kind),r=n.source_install_id,s=n.source_kind,u=n.release;if(typeof r!="string"||r===""||s!=="machine"&&s!=="hosted"||typeof u!="string")throw new o(400,"invalid_body","source_install_id, source_kind (machine | hosted) and release are required");if(u!==f())throw new o(409,"release_mismatch",`the source runs ${u}, the control plane accepts ${f()}`);const p=await c();let i=await p.prepare("SELECT run_id, state FROM import_runs WHERE source_install_id = ? AND state IN ('uploading', 'loading', 'committed', 'done')").get(r);if(i?.state==="uploading"){const S={step:"uploading",code:"superseded",message:`a new import run was started for install ${r} before this one asked for the load`};await p.prepare("UPDATE import_runs SET state = 'failed', failure_json = ?, finished_at = ? WHERE run_id = ? AND state = 'uploading'").run(JSON.stringify(S),new Date().toISOString(),i.run_id),i=await p.prepare("SELECT run_id, state FROM import_runs WHERE source_install_id = ? AND state IN ('uploading', 'loading', 'committed', 'done')").get(r)}if(i?.state==="done")throw new o(409,"already_imported",`install ${r} was imported by run ${i.run_id}`);if(i!==void 0)throw new o(409,"import_running",`install ${r} has an open run ${i.run_id} (${i.state})`);const d=`imp_${E(8).toString("hex")}`;await p.prepare("INSERT INTO import_runs (run_id, source_install_id, source_kind, person, release, state, created_at) VALUES (?, ?, ?, ?, ?, 'uploading', ?)").run(d,r,s,e,u,new Date().toISOString());const l=t.principal.class==="import"?N(d):null;return{status:201,body:{run_id:d,upload:{rows_prefix:m.prefix(d),url_template:`POST /v1/imports/${d}/upload-urls {"keys":[...]}`},...l===null?{}:{import_token:l}}}}a(j,"handleStart");async function P(t){const n=await _(t);if(n.state!=="uploading")throw new o(409,"import_not_uploading",`import run ${n.run_id} is ${n.state}`);const e=g(t.body).keys;if(!Array.isArray(e)||e.some(i=>typeof i!="string"))throw new o(400,"invalid_body","keys must be a list of blob keys");const r=m.prefix(n.run_id);for(const i of e)if(!(i.startsWith(r)&&!i.startsWith(`${r}stores/`)&&i!==m.manifest(n.run_id))||i.split("/").some(l=>l===""||l==="."||l===".."))throw new o(400,"invalid_key",`${i} is not a key this run may upload`);const s=v();if(s===null)throw new o(501,"upload_not_supported","this control plane keeps blobs in a directory: there is no URL to upload to");const u={};for(const i of e)u[i]=$(s,i,h);return{status:200,body:{urls:u,expires_at:new Date(Date.now()+h*1e3).toISOString()}}}a(P,"handleUploadUrls");async function I(t){const n=await _(t);if(n.state!=="uploading")throw new o(409,"import_not_uploading",`import run ${n.run_id} is ${n.state}`);const e=t.params.name;if(!R.includes(e))throw new o(404,"unknown_store",`no file store ${e}`);try{await O(await w(),n.run_id,e,t.body)}catch(r){throw r instanceof T?new o(400,"invalid_body",r.message):r}return{status:204}}a(I,"handleStore");async function U(t){const n=await _(t),e=await c();if(n.state==="uploading"){if(await(await w()).get(m.source(n.run_id))===null)throw new o(409,"bundle_invalid","source.json is not uploaded");await e.prepare("UPDATE import_runs SET state = 'loading', step = 'queued', loaded_at = ? WHERE run_id = ? AND state = 'uploading'").run(new Date().toISOString(),n.run_id)}else if(n.state!=="loading"&&n.state!=="committed"&&n.state!=="done")throw new o(409,"import_not_uploading",`import run ${n.run_id} is ${n.state}`);return{status:202,body:await b(n.run_id)}}a(U,"handleLoad");async function b(t){const n=await(await c()).prepare("SELECT * FROM import_runs WHERE run_id = ?").get(t),e=n.counts_json===null?{}:JSON.parse(n.counts_json),{unresolved_plugins:r,...s}=e;return{run_id:n.run_id,source_install_id:n.source_install_id,source_kind:n.source_kind,person:n.person,release:n.release,state:n.state,step:n.step,counts:s,verify:n.verify_json===null?null:JSON.parse(n.verify_json),failure:n.failure_json===null?null:JSON.parse(n.failure_json),unresolved_plugins:r??[],created:n.created_at,loaded:n.loaded_at,finished:n.finished_at}}a(b,"runDto");async function L(t){const n=await _(t,!0);return{status:200,body:await b(n.run_id)}}a(L,"handleGet");async function W(t){const n=await _(t),e=await D(n.run_id,await w());if(!e.rolled_back)throw new o(409,"rollback_has_dependents",`later rows depend on import run ${n.run_id}`,e.dependents);return{status:200,body:e}}a(W,"handleRollback");const X=[{method:"POST",pattern:"/v1/imports",handler:j},{method:"POST",pattern:"/v1/imports/:run_id/upload-urls",handler:P},{method:"PUT",pattern:"/v1/imports/:run_id/stores/:name",handler:I},{method:"POST",pattern:"/v1/imports/:run_id/load",handler:U},{method:"GET",pattern:"/v1/imports/:run_id",handler:L},{method:"POST",pattern:"/v1/imports/:run_id/rollback",handler:W}];export{X as importRoutes};
@@ -0,0 +1,2 @@
1
+ import type { RouteTable } from '../router.js';
2
+ export declare const machineCredentialRoutes: RouteTable;
@@ -0,0 +1 @@
1
+ var s=Object.defineProperty;var a=(e,t)=>s(e,"name",{value:t,configurable:!0});import{ApiError as i}from"../../../api/errors.js";import{openDb as d}from"../../../core/canvas/db.js";import{hasMachineCredential as c,mintMachineCredential as m}from"../../machine-link/credential.js";async function p(e){if(e.principal.class!=="person")throw new i(403,"person_only","the machine credential is issued to a person");const t=e.params.run_id??"",o=await d(),n=await o.prepare("SELECT source_install_id, source_kind, person, state FROM import_runs WHERE run_id = ?").get(t);if(n===void 0||e.principal.sub!==void 0&&n.person!==e.principal.sub)throw new i(404,"import_not_found",`no import run ${t}`);if(n.source_kind!=="machine")throw new i(409,"not_a_machine_import","only a machine source gets a machine credential");if(n.state!=="done")throw new i(409,"import_not_done",`import run ${t} is ${n.state}`);const r=await o.prepare("SELECT object FROM environments WHERE machine_id = ? AND kind = 'machine'").get(n.source_install_id);if(r===void 0)throw new i(409,"environment_missing",`no machine environment for install ${n.source_install_id}`);if(!(e.query.get("rotate")==="1")&&await c(r.object))throw new i(409,"credential_exists","the machine credential was already issued; rotate it");return{status:200,body:{environment_id:r.object,credential:await m(r.object)}}}a(p,"handleMint");const f=[{method:"POST",pattern:"/v1/imports/:run_id/machine-credential",handler:p}];export{f as machineCredentialRoutes};
@@ -1 +1 @@
1
- var O=Object.defineProperty;var o=(e,t)=>O(e,"name",{value:t,configurable:!0});import{assertLaunchKind as c}from"../../../core/runtime/launch-target.js";import{listNodesByProfile as v,shiftProfileNodeDeadlines as q}from"../../../core/canvas/canvas.js";import{liveExecutionForNode as L}from"../../../core/canvas/executions.js";import{shiftJobDeadlines as $}from"../../../core/graph/jobs.js";import{boundFleet as S}from"../../../core/runtime/fleet.js";import{recordedPidLiveness as V}from"../../../core/canvas/pid.js";import{withCanvasWrite as w}from"../../../core/canvas/db.js";import{reposForCwd as F,warmCheckoutIdentities as M}from"../../../core/graph/repo-sync/repos.js";import{renameProfileObject as C}from"../../../core/graph/objects.js";import{usage as l}from"../../../core/errors.js";import{ApiError as f}from"../../../api/errors.js";import{scopeAllowed as E}from"../../../core/scopes.js";import{validateEnvVarName as R}from"../../../core/env-name.js";import{listProfileEnvNames as G,removeProfileEnvVar as U,setProfileEnvVar as K}from"../../../core/profiles/env-store.js";import{isDeploymentName as H,listAppEnvNames as B,removeAppEnvVar as W,setAppEnvVar as J}from"../../../core/profiles/app-env-store.js";import{addProfileProject as z,assertDeliveryLimits as Q,setProfileDeliveryLimit as g,assertProfileProjects as y,createProfile as j,ensureRootProfile as X,listProfiles as u,loadProfileManifest as h,pauseProfile as Y,removeProfileProject as Z,renameProfile as x,resumeProfile as ee,setProfileDefaultKind as te,updateProfileMetadata as ae}from"../../../core/profiles/manifest.js";import{requestProfileDeletion as ne}from"../../profile-delete.js";import{brokerSignalBus as re}from"../../broker-signals/bus.js";import{toProfileDTO as ie}from"../map.js";import{machineEnvironmentId as oe}from"../../../core/environments.js";import{actsAsPerson as _,appOf as b,resolvedScopes as se}from"../router.js";const T="root";function d(e){return{...ie(e),environment_id:e.manifest.environment_id??oe()}}o(d,"toProfileDTO");function m(e){return b(e.principal)??"app:terminal"}o(m,"callerApp");function A(e,t,n=null){if(u().some(a=>a.manifest.name===t&&a.profileId!==n&&(a.manifest.grantee??"app:terminal")===e))throw new f(400,"invalid_request",`${e} already has a profile named ${t}`)}o(A,"assertNameFree");async function p(e,t){const n=await k(e,t),a=n.manifest.grantee??"app:terminal";if(!_(e.principal)&&a!==b(e.principal))throw new f(403,"permission_error",`profile ${n.profileId} belongs to ${a}; only that app changes it`);return n}o(p,"requireOwnProfile");async function de(e){const t=e.body??{};if(typeof t.name!="string"||t.name.trim()==="")throw l("a profile name is required",{field:"name"});t.projects!==void 0&&y(t.projects);const n=m(e),a=t.name.trim();A(n,a),t.default_kind!==void 0&&await c(t.default_kind,process.cwd(),null);const r=await j(a,t.projects??[],{defaultKind:t.default_kind,metadata:t.metadata,grantee:n});return{status:201,body:d(r)}}o(de,"handleCreate");async function le(e,t){const n=t.trim();if(n==="")throw l("profile name must not be empty");return await w(async a=>(await C(a,e,n),await x(e,n)))}o(le,"renameProfileAndHandle");async function fe(e,t,n){await M(n);const a=await w(async r=>(await F(r,n)).map(i=>i.name));await z(e,n);for(const r of a)t[r]===void 0&&t[`repo:${r}`]===void 0&&await g(e,r,"preview")}o(fe,"addProjectWithDefaultLimit");async function pe(e){const{profileId:t,manifest:n}=await p(e,e.params.name),a=e.body??{};return a.name!==void 0&&A(n.grantee??"app:terminal",a.name.trim(),t),a.default_kind!==void 0&&await c(a.default_kind,process.cwd(),t),a.add_project!==void 0&&y([a.add_project]),a.set_delivery_limit!==void 0&&Q({[a.set_delivery_limit.owner]:a.set_delivery_limit.level}),a.name!==void 0&&await le(t,a.name),a.default_kind!==void 0&&await te(t,a.default_kind),a.add_project!==void 0&&await fe(t,n.delivery_limits??{},a.add_project.path),a.set_delivery_limit!==void 0&&await g(t,a.set_delivery_limit.owner,a.set_delivery_limit.level),a.remove_project!==void 0&&await Z(t,a.remove_project),{status:200,body:d(h(t))}}o(pe,"handleUpdate");async function me(e){const{profileId:t}=await p(e,e.params.name);return{status:200,body:{profile_id:t,names:await G(t)}}}o(me,"handleEnvList");function P(e){const t=e.params.var,n=R(t);if(!n.isValid)throw l(`invalid env var name: ${n.errors.join("; ")}`,{field:"name",received:t});return t}o(P,"requireEnvVarName");function I(e){const t=P(e);if(H(t)&&!_(e.principal))throw new f(403,"permission_error",`${t} is deployment-wide; only a person changes it`);return t}o(I,"requireSelfEnvVarName");async function ue(e){const{profileId:t}=await p(e,e.params.name),n=P(e),{value:a}=e.body??{};if(typeof a!="string"||a==="")throw l("a non-empty env value is required",{field:"value"});return{status:200,body:{profile_id:t,changed:await K(t,n,a)}}}o(ue,"handleEnvSet");async function ce(e){const{profileId:t}=await p(e,e.params.name),n=P(e);return{status:200,body:{profile_id:t,changed:await U(t,n)}}}o(ce,"handleEnvRemove");async function ve(e){const t=e.params.name,n=e.body??{};n.projects!==void 0&&y(n.projects);const a=m(e);if(t===T&&a==="app:terminal")return{status:200,body:d(await X())};if(t===T)throw new f(403,"permission_error","root belongs to the terminal app");const r=u().find(s=>s.manifest.name===t&&(s.manifest.grantee??"app:terminal")===a);if(r!==void 0)return{status:200,body:d(r)};n.default_kind!==void 0&&await c(n.default_kind,process.cwd(),null);const i=await j(t,n.projects??[],{defaultKind:n.default_kind,metadata:n.metadata,grantee:a});return{status:200,body:d(i)}}o(ve,"handleEnsure");async function we(e){const t=await p(e,e.params.name),n=e.body??{};if(typeof n!="object"||n===null||Array.isArray(n))throw l("profile metadata body must be an object",{field:"body"});const a=n,r=a.unset===void 0?[]:a.unset;if(!Array.isArray(r)||r.some(N=>typeof N!="string"))throw l("profile metadata `unset` must be an array of strings",{field:"unset"});const i=a.set===void 0?{}:a.set,s=await ae(t.profileId,i,r);return{status:200,body:d(s)}}o(we,"handleSetMetadata");async function ye(e){const t=h(e.params.name),n=await Y(t.profileId),a=(await v(t.profileId)).filter(i=>i.status==="active"||i.status==="idle");if(a.length===0)return{status:200,body:{profile_id:t.profileId,paused_at:n.manifest.paused_at}};const r=S();for(const i of a){const s=r.get(i.node_id);if(s!==void 0){await r.interruptForProfilePause(i.node_id,s.pid);continue}i.pi_pid!=null&&V(i.pi_pid,i.pi_pid_identity)==="alive"&&await r.interruptForProfilePause(i.node_id,i.pi_pid)}return{status:200,body:{profile_id:t.profileId,paused_at:n.manifest.paused_at}}}o(ye,"handlePause");async function he(e){const t=h(e.params.name),n=await ee(t.profileId);for(const a of await v(t.profileId))(a.status==="active"||a.status==="idle"||await L(a.node_id)!==null)&&re.markMail(a.node_id);if(n.pausedAt!==null){const a=Date.parse(n.pausedAt);if(!Number.isNaN(a)){const r=Math.max(0,Date.now()-a);await q(t.profileId,r);for(const i of await v(t.profileId))i.status!=="dead"&&await w(s=>$(s,i.node_id,r))}}return{status:200,body:{profile_id:t.profileId,paused_at:n.manifest.paused_at}}}o(he,"handleResume");async function D(e){const t=e.principal;if(_(t))return u();const n=await se(t);return u().filter(({profileId:a,manifest:r})=>{const i=r.grantee??"app:terminal";if(i===b(t))return!0;const s=`crtr:memory:rw:${i}:profile:${a}`;return E(n,s)&&(t.class!=="broker"||E(t.scopes,s))})}o(D,"visibleProfiles");async function k(e,t){const n=await D(e),a=n.find(({profileId:i})=>i===t),r=a?[a]:n.filter(({manifest:i})=>i.name===t);if(r.length===0)throw new f(404,"not_found","profile not found");if(r.length>1)throw new f(400,"ambiguous","profile name matches multiple visible profiles; use an exact id");return r[0]}o(k,"requireUsableProfile");async function _e(e){return{status:200,body:(await D(e)).map(d)}}o(_e,"handleList");async function be(e){return{status:200,body:d(await k(e,e.params.name))}}o(be,"handleGet");async function Pe(e){return ne((await p(e,e.params.name)).profileId,e.body)}o(Pe,"handleDelete");const He=[{method:"GET",pattern:"/v1/apps/self/env",handler:o(async e=>({status:200,body:{names:await B(m(e))}}),"handler")},{method:"PUT",pattern:"/v1/apps/self/env/:var",handler:o(async e=>{const t=I(e),{value:n}=e.body??{};if(typeof n!="string"||!n)throw l("a non-empty env value is required",{field:"value"});return{status:200,body:{changed:await J(m(e),t,n)}}},"handler")},{method:"DELETE",pattern:"/v1/apps/self/env/:var",handler:o(async e=>({status:200,body:{changed:await W(m(e),I(e))}}),"handler")},{method:"PUT",pattern:"/v1/profiles/:name",handler:ve},{method:"POST",pattern:"/v1/profiles/:name/pause",ownerOnly:!0,handler:ye},{method:"POST",pattern:"/v1/profiles/:name/resume",ownerOnly:!0,handler:he},{method:"PATCH",pattern:"/v1/profiles/:name/metadata",handler:we},{method:"POST",pattern:"/v1/profiles",handler:de},{method:"PATCH",pattern:"/v1/profiles/:name",handler:pe},{method:"GET",pattern:"/v1/profiles/:name/env",handler:me},{method:"PUT",pattern:"/v1/profiles/:name/env/:var",handler:ue},{method:"DELETE",pattern:"/v1/profiles/:name/env/:var",handler:ce},{method:"DELETE",pattern:"/v1/profiles/:name",handler:Pe},{method:"GET",pattern:"/v1/profiles",handler:_e},{method:"GET",pattern:"/v1/profiles/:name",handler:be}];export{fe as addProjectWithDefaultLimit,He as profileRoutes,le as renameProfileAndHandle,k as requireUsableProfile};
1
+ var N=Object.defineProperty;var o=(t,e)=>N(t,"name",{value:e,configurable:!0});import{openDb as q}from"../../../core/canvas/db.js";import{assertLaunchKind as c}from"../../../core/runtime/launch-target.js";import{listNodesByProfile as v,shiftProfileNodeDeadlines as L}from"../../../core/canvas/canvas.js";import{liveExecutionForNode as S}from"../../../core/canvas/executions.js";import{shiftJobDeadlines as $}from"../../../core/graph/jobs.js";import{boundFleet as F}from"../../../core/runtime/fleet.js";import{recordedPidLiveness as M}from"../../../core/canvas/pid.js";import{withCanvasWrite as w}from"../../../core/canvas/db.js";import{reposForCwd as R,warmCheckoutIdentities as V}from"../../../core/graph/repo-sync/repos.js";import{renameProfileObject as C}from"../../../core/graph/objects.js";import{usage as l}from"../../../core/errors.js";import{ApiError as f}from"../../../api/errors.js";import{scopeAllowed as E}from"../../../core/scopes.js";import{validateEnvVarName as G}from"../../../core/env-name.js";import{listProfileEnvNames as U,removeProfileEnvVar as H,setProfileEnvVar as K}from"../../../core/profiles/env-store.js";import{isDeploymentName as W,listAppEnvNames as B,removeAppEnvVar as J,setAppEnvVar as z}from"../../../core/profiles/app-env-store.js";import{addProfileProject as Q,assertDeliveryLimits as X,setProfileDeliveryLimit as g,assertProfileProjects as h,createProfile as j,ensureRootProfile as Y,listProfiles as m,loadProfileManifest as y,pauseProfile as Z,removeProfileProject as x,renameProfile as ee,resumeProfile as te,setProfileDefaultKind as ae,updateProfileMetadata as ne}from"../../../core/profiles/manifest.js";import{requestProfileDeletion as re}from"../../profile-delete.js";import{brokerSignalBus as ie}from"../../broker-signals/bus.js";import{toProfileDTO as oe}from"../map.js";import{machineEnvironmentId as se}from"../../../core/environments.js";import{actsAsPerson as _,appOf as b,resolvedScopes as de}from"../router.js";const T="root";function d(t){return{...oe(t),environment_id:t.manifest.environment_id??se()}}o(d,"toProfileDTO");function u(t){return b(t.principal)??"app:terminal"}o(u,"callerApp");function A(t,e,n=null){if(m().some(a=>a.manifest.name===e&&a.profileId!==n&&(a.manifest.grantee??"app:terminal")===t))throw new f(400,"invalid_request",`${t} already has a profile named ${e}`)}o(A,"assertNameFree");async function p(t,e){const n=await k(t,e),a=n.manifest.grantee??"app:terminal";if(!_(t.principal)&&a!==b(t.principal))throw new f(403,"permission_error",`profile ${n.profileId} belongs to ${a}; only that app changes it`);return n}o(p,"requireOwnProfile");async function le(t){const e=t.body??{};if(typeof e.name!="string"||e.name.trim()==="")throw l("a profile name is required",{field:"name"});e.projects!==void 0&&h(e.projects);const n=u(t),a=e.name.trim();A(n,a),e.default_kind!==void 0&&await c(e.default_kind,process.cwd(),null);const i=await j(a,e.projects??[],{defaultKind:e.default_kind,metadata:e.metadata,grantee:n});return{status:201,body:d(i)}}o(le,"handleCreate");async function fe(t,e){const n=e.trim();if(n==="")throw l("profile name must not be empty");return await w(async a=>(await C(a,t,n),await ee(t,n)))}o(fe,"renameProfileAndHandle");async function pe(t,e,n){await V(n);const a=await w(async i=>(await R(i,n)).map(r=>r.name));await Q(t,n);for(const i of a)e[i]===void 0&&e[`repo:${i}`]===void 0&&await g(t,i,"preview")}o(pe,"addProjectWithDefaultLimit");async function me(t){const{profileId:e,manifest:n}=await p(t,t.params.name),a=t.body??{};return a.name!==void 0&&A(n.grantee??"app:terminal",a.name.trim(),e),a.default_kind!==void 0&&await c(a.default_kind,process.cwd(),e),a.add_project!==void 0&&h([a.add_project]),a.set_delivery_limit!==void 0&&X({[a.set_delivery_limit.owner]:a.set_delivery_limit.level}),a.name!==void 0&&await fe(e,a.name),a.default_kind!==void 0&&await ae(e,a.default_kind),a.add_project!==void 0&&await pe(e,n.delivery_limits??{},a.add_project.path),a.set_delivery_limit!==void 0&&await g(e,a.set_delivery_limit.owner,a.set_delivery_limit.level),a.remove_project!==void 0&&await x(e,a.remove_project),{status:200,body:d(y(e))}}o(me,"handleUpdate");async function ue(t){const{profileId:e}=await p(t,t.params.name);return{status:200,body:{profile_id:e,names:await U(e)}}}o(ue,"handleEnvList");function P(t){const e=t.params.var,n=G(e);if(!n.isValid)throw l(`invalid env var name: ${n.errors.join("; ")}`,{field:"name",received:e});return e}o(P,"requireEnvVarName");function D(t){const e=P(t);if(W(e)&&!_(t.principal))throw new f(403,"permission_error",`${e} is deployment-wide; only a person changes it`);return e}o(D,"requireSelfEnvVarName");async function ce(t){const{profileId:e}=await p(t,t.params.name),n=P(t),{value:a}=t.body??{};if(typeof a!="string"||a==="")throw l("a non-empty env value is required",{field:"value"});return{status:200,body:{profile_id:e,changed:await K(e,n,a)}}}o(ce,"handleEnvSet");async function ve(t){const{profileId:e}=await p(t,t.params.name),n=P(t);return{status:200,body:{profile_id:e,changed:await H(e,n)}}}o(ve,"handleEnvRemove");async function we(t){const e=t.params.name,n=t.body??{};n.projects!==void 0&&h(n.projects);const a=u(t);if(e===T&&a==="app:terminal")return{status:200,body:d(await Y())};if(e===T)throw new f(403,"permission_error","root belongs to the terminal app");const i=m().find(s=>s.manifest.name===e&&(s.manifest.grantee??"app:terminal")===a);if(i!==void 0)return{status:200,body:d(i)};n.default_kind!==void 0&&await c(n.default_kind,process.cwd(),null);const r=await j(e,n.projects??[],{defaultKind:n.default_kind,metadata:n.metadata,grantee:a});return{status:200,body:d(r)}}o(we,"handleEnsure");async function he(t){const e=await p(t,t.params.name),n=t.body??{};if(typeof n!="object"||n===null||Array.isArray(n))throw l("profile metadata body must be an object",{field:"body"});const a=n,i=a.unset===void 0?[]:a.unset;if(!Array.isArray(i)||i.some(O=>typeof O!="string"))throw l("profile metadata `unset` must be an array of strings",{field:"unset"});const r=a.set===void 0?{}:a.set,s=await ne(e.profileId,r,i);return{status:200,body:d(s)}}o(he,"handleSetMetadata");async function ye(t){const e=y(t.params.name),n=await Z(e.profileId),a=(await v(e.profileId)).filter(r=>r.status==="active"||r.status==="idle");if(a.length===0)return{status:200,body:{profile_id:e.profileId,paused_at:n.manifest.paused_at}};const i=F();for(const r of a){const s=i.get(r.node_id);if(s!==void 0){await i.interruptForProfilePause(r.node_id,s.pid);continue}r.pi_pid!=null&&M(r.pi_pid,r.pi_pid_identity)==="alive"&&await i.interruptForProfilePause(r.node_id,r.pi_pid)}return{status:200,body:{profile_id:e.profileId,paused_at:n.manifest.paused_at}}}o(ye,"handlePause");async function _e(t){const e=y(t.params.name),n=await te(e.profileId);for(const a of await v(e.profileId))(a.status==="active"||a.status==="idle"||await S(a.node_id)!==null)&&ie.markMail(a.node_id);if(n.pausedAt!==null){const a=Date.parse(n.pausedAt);if(!Number.isNaN(a)){const i=Math.max(0,Date.now()-a);await L(e.profileId,i);for(const r of await v(e.profileId))r.status!=="dead"&&await w(s=>$(s,r.node_id,i))}}return{status:200,body:{profile_id:e.profileId,paused_at:n.manifest.paused_at}}}o(_e,"handleResume");async function I(t){const e=t.principal;if(_(e))return m();if(e.class==="person"&&e.sub!==void 0){const a=await q(),i=new Set((await a.prepare("SELECT id FROM profiles WHERE person = ?").all(e.sub)).map(r=>r.id));return m().filter(({profileId:r})=>i.has(r))}const n=await de(e);return m().filter(({profileId:a,manifest:i})=>{const r=i.grantee??"app:terminal";if(r===b(e))return!0;const s=`crtr:memory:rw:${r}:profile:${a}`;return E(n,s)&&(e.class!=="broker"||E(e.scopes,s))})}o(I,"visibleProfiles");async function k(t,e){const n=await I(t),a=n.find(({profileId:r})=>r===e),i=a?[a]:n.filter(({manifest:r})=>r.name===e);if(i.length===0)throw new f(404,"not_found","profile not found");if(i.length>1)throw new f(400,"ambiguous","profile name matches multiple visible profiles; use an exact id");return i[0]}o(k,"requireUsableProfile");async function be(t){return{status:200,body:(await I(t)).map(d)}}o(be,"handleList");async function Pe(t){return{status:200,body:d(await k(t,t.params.name))}}o(Pe,"handleGet");async function Ee(t){return re((await p(t,t.params.name)).profileId,t.body)}o(Ee,"handleDelete");const Be=[{method:"GET",pattern:"/v1/apps/self/env",handler:o(async t=>({status:200,body:{names:await B(u(t))}}),"handler")},{method:"PUT",pattern:"/v1/apps/self/env/:var",handler:o(async t=>{const e=D(t),{value:n}=t.body??{};if(typeof n!="string"||!n)throw l("a non-empty env value is required",{field:"value"});return{status:200,body:{changed:await z(u(t),e,n)}}},"handler")},{method:"DELETE",pattern:"/v1/apps/self/env/:var",handler:o(async t=>({status:200,body:{changed:await J(u(t),D(t))}}),"handler")},{method:"PUT",pattern:"/v1/profiles/:name",handler:we},{method:"POST",pattern:"/v1/profiles/:name/pause",ownerOnly:!0,handler:ye},{method:"POST",pattern:"/v1/profiles/:name/resume",ownerOnly:!0,handler:_e},{method:"PATCH",pattern:"/v1/profiles/:name/metadata",handler:he},{method:"POST",pattern:"/v1/profiles",handler:le},{method:"PATCH",pattern:"/v1/profiles/:name",handler:me},{method:"GET",pattern:"/v1/profiles/:name/env",handler:ue},{method:"PUT",pattern:"/v1/profiles/:name/env/:var",handler:ce},{method:"DELETE",pattern:"/v1/profiles/:name/env/:var",handler:ve},{method:"DELETE",pattern:"/v1/profiles/:name",handler:Ee},{method:"GET",pattern:"/v1/profiles",handler:be},{method:"GET",pattern:"/v1/profiles/:name",handler:Pe}];export{pe as addProjectWithDefaultLimit,Be as profileRoutes,fe as renameProfileAndHandle,k as requireUsableProfile};
@@ -1 +1 @@
1
- var q=Object.defineProperty;var s=(e,r)=>q(e,"name",{value:r,configurable:!0});import{randomUUID as G}from"node:crypto";import{cloudPersonScope as N}from"../../../core/runs/operations.js";import{directoryIssuer as T}from"../../../core/directory/issuer.js";import{activeGrantForApp as F,allGrants as x,grantFor as C,localGrants as j,providerIdsFromGrants as y}from"../../../core/grants/mirror.js";import{getNode as k}from"../../../core/canvas/canvas.js";import{scopeAllowed as B}from"../../../core/scopes.js";import{ProviderError as f,providerErrorBody as D,scopeMissing as M}from"../../../core/providers/errors.js";import{callProvider as U,providerTools as z}from"../../../core/providers/call.js";import{providerInstallState as L}from"../../../core/providers/bundles.js";import{installedProviderPlugins as V,providerRootClaims as Z,resolveProviderRef as W}from"../../../core/providers/roots.js";import{isCloudRole as h}from"../../../core/canvas/person-state.js";import{listAudit as J,writeProviderAudit as X}from"../../../core/providers/audit.js";import{emitEvent as S}from"../../../core/events/emit.js";import{resolvedScopes as P}from"../router.js";const Ae=new Set(["provider.tools","provider.call","audit.list"]);async function H(e){if(e.class==="forwarded"){if(e.actor.kind==="person")return{class:"person",sub:null,grantee:null,client_id:null,resolved:null,nodeScopes:null,run_id:null,node_id:null,origin:{node:null,run:null,machine:e.via}};const r=await C(e.actor.app,e.sub);if(!r)throw new f("grant_revoked",`app ${e.actor.app} has no active grant`);return{class:"app",sub:e.sub,grantee:e.actor.app,client_id:null,resolved:r.resolved,nodeScopes:e.actor.node_scopes,run_id:null,node_id:null,origin:{node:e.actor.node,run:e.actor.place?.run??null,machine:e.via}}}if(e.class==="machine")throw new f("permission_error","this operation is not available to the caller");if(e.class==="person"&&e.sub!==void 0)throw new f("permission_error","provider calls are made by the install's person, a node or an app");if(e.class==="person")return{class:"person",sub:null,grantee:null,client_id:null,resolved:null,nodeScopes:null,run_id:null,node_id:null};if(e.class==="app"){const r=await F(e.app);if(!r)throw new f("grant_revoked",`app ${e.app} has no active grant`);return{class:"app",sub:r.sub,grantee:e.app,client_id:null,resolved:r.resolved,nodeScopes:e.scopes,run_id:null,node_id:null}}return e.class==="cron"?{class:"app",sub:e.sub,grantee:e.app,client_id:null,resolved:await P(e),nodeScopes:e.scopes,run_id:e.origin_run,node_id:null}:{class:"broker",sub:e.sub,grantee:e.app,client_id:e.client_id,resolved:await P(e),nodeScopes:e.scopes,run_id:e.run??(await k(e.node))?.run_id??null,node_id:e.node}}s(H,"callerFromRequest");function Ee(e){return{class:"app",sub:e.sub,grantee:e.grantee,client_id:e.client_id,resolved:e.resolved,nodeScopes:null,run_id:null,node_id:null}}s(Ee,"callerFromApp");const K=/^([A-Za-z0-9_-]{1,64}):([A-Za-z0-9_-]{1,64})$/;function Q(e){const r=K.exec(e);return r!==null&&r[1]!=="crtr"}s(Q,"isProviderScope");const A=new WeakMap;function Y(e){let r=A.get(e);return r===void 0&&(r=new Set(e),A.set(e,r)),r}s(Y,"heldSet");function E(e,r){return e===null?!0:Q(r)?Y(e).has(r):B(e,r)}s(E,"holds");function R(e,r){return e.class==="person"?!0:E(e.resolved??[],r)&&E(e.nodeScopes,r)}s(R,"callerAllows");function Re(e,r,n){const o=`${r}:${n}`;if(!R(e,o))throw M(o)}s(Re,"requireGroup");function ee(){return!!T()}s(ee,"providerOperationsEnabled");const re=/^[A-Za-z0-9_-]{1,64}$/;function m(e,r){return new f("invalid_request",e,{param:r})}s(m,"invalid");function I(e){if(typeof e!="string"||!re.test(e)||e==="crtr")throw m("provider must be a provider id","provider");return e}s(I,"parseProvider");function oe(e){if(!e||typeof e!="object"||Array.isArray(e))throw new f("invalid_request","a JSON object is required");const r=e,n=I(r.provider),o=r.tool;if(typeof o!="string"||!o.trim())throw m("tool must be a non-empty string","tool");const i=r.arguments??{};if(!i||typeof i!="object"||Array.isArray(i))throw m("arguments must be an object","arguments");const a=r.version;if(a!==void 0&&(typeof a!="number"||!Number.isInteger(a)||a<1))throw m("version must be an integer >= 1","version");const d=r.approval;if(d!==void 0&&(typeof d!="string"||!d))throw m("approval must be a question id","approval");const u=r.machine;if(u!==void 0&&(typeof u!="string"||!u.trim()))throw m("machine must be a machine id","machine");return{provider:n,tool:o.trim(),arguments:i,...a===void 0?{}:{version:a},...d===void 0?{}:{approval:d},...u===void 0?{}:{machine:u.trim()}}}s(oe,"parseCallInput");async function te(e,r=new URLSearchParams){const n=s((l,v,w)=>{const p=r.get(l);if(p===null)return v;if(!/^\d+$/.test(p)||Number(p)>w||Number(p)<1)throw m(`invalid ${l}`,l);return Number(p)},"numeric"),o=r.has("after")?n("after",0,Number.MAX_SAFE_INTEGER):void 0,i=n("limit",100,1e3),a=Object.fromEntries(["operation","outcome","run_id","since","until"].filter(l=>r.has(l)).map(l=>[l,r.get(l)])),d=N(e.sub),u=d===void 0?{}:{sub:d};return{status:200,body:await J(e.class==="person"?null:e.grantee,{after:o,limit:i,...u,...a})}}s(te,"auditList");async function ne(e,r,n){let o=r;const i=performance.now(),a=V(),{skipped:d}=Z(a),u=h()?await x():await j(),l=h()?new Set(y(u.filter(t=>t.sub===e.sub))):null,v=s(t=>!d.has(t)&&(l===null||l.has(t)),"shown"),w=new Set(a.filter(t=>t.enabled&&t.manifest.provider?.id===t.name&&v(t.name)).map(t=>t.name)),p=y(u).filter(v),_=[...new Set([...w,...p])].sort();if(o!==void 0){const t=W(o);if(t===null)throw new f("not_found",`provider "${o}" is not available`);o=t}if(o!==void 0&&!_.includes(o))throw new f("not_found",`provider "${o}" is not available`);const $=o===void 0?_:[o],O=e.class==="person"?u.flatMap(t=>t.status==="active"&&(!h()||t.sub===e.sub)?t.resolved_scopes.map(({scope:c})=>c):[]):e.resolved??[],b=await Promise.all($.map(async t=>{if(w.has(t))return z(e,t);if(!O.some(g=>g.startsWith(`${t}:`)&&R(e,g)))return null;const c=L(t);return c===void 0?null:{provider:t,display_name:t,status:c.status,...c.error?{error:c.error}:{},groups:[],commands:[]}})).then(t=>t.filter(c=>c!==null));if(S({level:"debug",event:"api.provider.tools",duration_ms:performance.now()-i,fields:{caller:e.class,provider:o??null,providers:b.length}}),o!==void 0){if(b.length===0)throw new f("not_found",`provider "${o}" is not available`);return{status:200,body:b[0]}}return{status:200,body:{providers:b}}}s(ne,"providerTools");async function se(e,r,n){return U(e,r,n)}s(se,"providerCall");async function ie(e,r,n,o){if(!ee())throw new f("not_found","provider operations need a directory-connected runtime");if(e==="audit.list")return await te(r,n.query);if(e==="provider.tools")return ne(r,n.params.provider===void 0?void 0:I(n.params.provider),o);if(e==="provider.call"){let i;try{i=oe(n.body)}catch(a){const d=n.body&&typeof n.body=="object"&&!Array.isArray(n.body)?n.body:{};throw await X({request_id:o.requestId,caller:r,provider:typeof d.provider=="string"?d.provider:"",tool:typeof d.tool=="string"?d.tool:"",outcome:`refused:${a instanceof f?a.code:"invalid_request"}`}),a}return se(r,i,o)}throw new f("not_found","unknown operation")}s(ie,"handleProviderOperation");function ae(e){const r=new AbortController,n=s(()=>{e.writableFinished||r.abort()},"abort");return e.on("close",n),{signal:r.signal,release:s(()=>e.off("close",n),"release")}}s(ae,"abortOnClose");function de(e,r,n,o){const i=D(e,r,n,o??void 0);return i.status>=500&&!(e instanceof f)&&S({event:"api.provider.failed",level:"error",error:e instanceof Error?e:new Error(String(e)),fields:{request_id:r}}),i}s(de,"providerFailure");const Ie=[{method:"GET",pattern:"/v1/audit",name:"audit.list"},{method:"GET",pattern:"/v1/providers/tools",name:"provider.tools"},{method:"GET",pattern:"/v1/providers/:provider/tools",name:"provider.tools"},{method:"POST",pattern:"/v1/provider/call",name:"provider.call"}].map(({method:e,pattern:r,name:n})=>({method:e,pattern:r,handler:s(async o=>{const i=G(),{signal:a,release:d}=ae(o.res);let u=null;try{return u=await H(o.principal),{...await ie(n,u,{params:o.params,body:o.body,query:new URL(o.req.url??"/","http://runtime.local").searchParams},{requestId:i,signal:a,...o.causedBy===void 0?{}:{causedBy:o.causedBy}}),headers:{"Request-Id":i}}}catch(l){return de(l,i,!1,u?.run_id)}finally{d()}},"handler")}));export{Ae as PROVIDER_OPERATIONS,ae as abortOnClose,R as callerAllows,Ee as callerFromApp,H as callerFromRequest,ie as handleProviderOperation,oe as parseCallInput,I as parseProvider,de as providerFailure,ee as providerOperationsEnabled,Ie as providerRoutes,Re as requireGroup};
1
+ var q=Object.defineProperty;var s=(e,r)=>q(e,"name",{value:r,configurable:!0});import{randomUUID as G}from"node:crypto";import{cloudPersonScope as N}from"../../../core/runs/operations.js";import{directoryIssuer as T}from"../../../core/directory/issuer.js";import{activeGrantForApp as F,allGrants as x,grantFor as C,localGrants as j,providerIdsFromGrants as y}from"../../../core/grants/mirror.js";import{getNode as k}from"../../../core/canvas/canvas.js";import{scopeAllowed as B}from"../../../core/scopes.js";import{ProviderError as f,providerErrorBody as D,scopeMissing as M}from"../../../core/providers/errors.js";import{callProvider as U,providerTools as z}from"../../../core/providers/call.js";import{providerInstallState as L}from"../../../core/providers/bundles.js";import{installedProviderPlugins as V,providerRootClaims as Z,resolveProviderRef as W}from"../../../core/providers/roots.js";import{isCloudRole as h}from"../../../core/canvas/person-state.js";import{listAudit as J,writeProviderAudit as X}from"../../../core/providers/audit.js";import{emitEvent as S}from"../../../core/events/emit.js";import{resolvedScopes as P}from"../router.js";const Ae=new Set(["provider.tools","provider.call","audit.list"]);async function H(e){if(e.class==="forwarded"){if(e.actor.kind==="person")return{class:"person",sub:null,grantee:null,client_id:null,resolved:null,nodeScopes:null,run_id:null,node_id:null,origin:{node:null,run:null,machine:e.via}};const r=await C(e.actor.app,e.sub);if(!r)throw new f("grant_revoked",`app ${e.actor.app} has no active grant`);return{class:"app",sub:e.sub,grantee:e.actor.app,client_id:null,resolved:r.resolved,nodeScopes:e.actor.node_scopes,run_id:null,node_id:null,origin:{node:e.actor.node,run:e.actor.place?.run??null,machine:e.via}}}if(e.class==="machine"||e.class==="import")throw new f("permission_error","this operation is not available to the caller");if(e.class==="person"&&e.sub!==void 0)throw new f("permission_error","provider calls are made by the install's person, a node or an app");if(e.class==="person")return{class:"person",sub:null,grantee:null,client_id:null,resolved:null,nodeScopes:null,run_id:null,node_id:null};if(e.class==="app"){const r=await F(e.app);if(!r)throw new f("grant_revoked",`app ${e.app} has no active grant`);return{class:"app",sub:r.sub,grantee:e.app,client_id:null,resolved:r.resolved,nodeScopes:e.scopes,run_id:null,node_id:null}}return e.class==="cron"?{class:"app",sub:e.sub,grantee:e.app,client_id:null,resolved:await P(e),nodeScopes:e.scopes,run_id:e.origin_run,node_id:null}:{class:"broker",sub:e.sub,grantee:e.app,client_id:e.client_id,resolved:await P(e),nodeScopes:e.scopes,run_id:e.run??(await k(e.node))?.run_id??null,node_id:e.node}}s(H,"callerFromRequest");function Ee(e){return{class:"app",sub:e.sub,grantee:e.grantee,client_id:e.client_id,resolved:e.resolved,nodeScopes:null,run_id:null,node_id:null}}s(Ee,"callerFromApp");const K=/^([A-Za-z0-9_-]{1,64}):([A-Za-z0-9_-]{1,64})$/;function Q(e){const r=K.exec(e);return r!==null&&r[1]!=="crtr"}s(Q,"isProviderScope");const A=new WeakMap;function Y(e){let r=A.get(e);return r===void 0&&(r=new Set(e),A.set(e,r)),r}s(Y,"heldSet");function E(e,r){return e===null?!0:Q(r)?Y(e).has(r):B(e,r)}s(E,"holds");function R(e,r){return e.class==="person"?!0:E(e.resolved??[],r)&&E(e.nodeScopes,r)}s(R,"callerAllows");function Re(e,r,n){const o=`${r}:${n}`;if(!R(e,o))throw M(o)}s(Re,"requireGroup");function ee(){return!!T()}s(ee,"providerOperationsEnabled");const re=/^[A-Za-z0-9_-]{1,64}$/;function m(e,r){return new f("invalid_request",e,{param:r})}s(m,"invalid");function I(e){if(typeof e!="string"||!re.test(e)||e==="crtr")throw m("provider must be a provider id","provider");return e}s(I,"parseProvider");function oe(e){if(!e||typeof e!="object"||Array.isArray(e))throw new f("invalid_request","a JSON object is required");const r=e,n=I(r.provider),o=r.tool;if(typeof o!="string"||!o.trim())throw m("tool must be a non-empty string","tool");const i=r.arguments??{};if(!i||typeof i!="object"||Array.isArray(i))throw m("arguments must be an object","arguments");const a=r.version;if(a!==void 0&&(typeof a!="number"||!Number.isInteger(a)||a<1))throw m("version must be an integer >= 1","version");const d=r.approval;if(d!==void 0&&(typeof d!="string"||!d))throw m("approval must be a question id","approval");const u=r.machine;if(u!==void 0&&(typeof u!="string"||!u.trim()))throw m("machine must be a machine id","machine");return{provider:n,tool:o.trim(),arguments:i,...a===void 0?{}:{version:a},...d===void 0?{}:{approval:d},...u===void 0?{}:{machine:u.trim()}}}s(oe,"parseCallInput");async function te(e,r=new URLSearchParams){const n=s((l,v,w)=>{const p=r.get(l);if(p===null)return v;if(!/^\d+$/.test(p)||Number(p)>w||Number(p)<1)throw m(`invalid ${l}`,l);return Number(p)},"numeric"),o=r.has("after")?n("after",0,Number.MAX_SAFE_INTEGER):void 0,i=n("limit",100,1e3),a=Object.fromEntries(["operation","outcome","run_id","since","until"].filter(l=>r.has(l)).map(l=>[l,r.get(l)])),d=N(e.sub),u=d===void 0?{}:{sub:d};return{status:200,body:await J(e.class==="person"?null:e.grantee,{after:o,limit:i,...u,...a})}}s(te,"auditList");async function ne(e,r,n){let o=r;const i=performance.now(),a=V(),{skipped:d}=Z(a),u=h()?await x():await j(),l=h()?new Set(y(u.filter(t=>t.sub===e.sub))):null,v=s(t=>!d.has(t)&&(l===null||l.has(t)),"shown"),w=new Set(a.filter(t=>t.enabled&&t.manifest.provider?.id===t.name&&v(t.name)).map(t=>t.name)),p=y(u).filter(v),_=[...new Set([...w,...p])].sort();if(o!==void 0){const t=W(o);if(t===null)throw new f("not_found",`provider "${o}" is not available`);o=t}if(o!==void 0&&!_.includes(o))throw new f("not_found",`provider "${o}" is not available`);const $=o===void 0?_:[o],O=e.class==="person"?u.flatMap(t=>t.status==="active"&&(!h()||t.sub===e.sub)?t.resolved_scopes.map(({scope:c})=>c):[]):e.resolved??[],b=await Promise.all($.map(async t=>{if(w.has(t))return z(e,t);if(!O.some(g=>g.startsWith(`${t}:`)&&R(e,g)))return null;const c=L(t);return c===void 0?null:{provider:t,display_name:t,status:c.status,...c.error?{error:c.error}:{},groups:[],commands:[]}})).then(t=>t.filter(c=>c!==null));if(S({level:"debug",event:"api.provider.tools",duration_ms:performance.now()-i,fields:{caller:e.class,provider:o??null,providers:b.length}}),o!==void 0){if(b.length===0)throw new f("not_found",`provider "${o}" is not available`);return{status:200,body:b[0]}}return{status:200,body:{providers:b}}}s(ne,"providerTools");async function se(e,r,n){return U(e,r,n)}s(se,"providerCall");async function ie(e,r,n,o){if(!ee())throw new f("not_found","provider operations need a directory-connected runtime");if(e==="audit.list")return await te(r,n.query);if(e==="provider.tools")return ne(r,n.params.provider===void 0?void 0:I(n.params.provider),o);if(e==="provider.call"){let i;try{i=oe(n.body)}catch(a){const d=n.body&&typeof n.body=="object"&&!Array.isArray(n.body)?n.body:{};throw await X({request_id:o.requestId,caller:r,provider:typeof d.provider=="string"?d.provider:"",tool:typeof d.tool=="string"?d.tool:"",outcome:`refused:${a instanceof f?a.code:"invalid_request"}`}),a}return se(r,i,o)}throw new f("not_found","unknown operation")}s(ie,"handleProviderOperation");function ae(e){const r=new AbortController,n=s(()=>{e.writableFinished||r.abort()},"abort");return e.on("close",n),{signal:r.signal,release:s(()=>e.off("close",n),"release")}}s(ae,"abortOnClose");function de(e,r,n,o){const i=D(e,r,n,o??void 0);return i.status>=500&&!(e instanceof f)&&S({event:"api.provider.failed",level:"error",error:e instanceof Error?e:new Error(String(e)),fields:{request_id:r}}),i}s(de,"providerFailure");const Ie=[{method:"GET",pattern:"/v1/audit",name:"audit.list"},{method:"GET",pattern:"/v1/providers/tools",name:"provider.tools"},{method:"GET",pattern:"/v1/providers/:provider/tools",name:"provider.tools"},{method:"POST",pattern:"/v1/provider/call",name:"provider.call"}].map(({method:e,pattern:r,name:n})=>({method:e,pattern:r,handler:s(async o=>{const i=G(),{signal:a,release:d}=ae(o.res);let u=null;try{return u=await H(o.principal),{...await ie(n,u,{params:o.params,body:o.body,query:new URL(o.req.url??"/","http://runtime.local").searchParams},{requestId:i,signal:a,...o.causedBy===void 0?{}:{causedBy:o.causedBy}}),headers:{"Request-Id":i}}}catch(l){return de(l,i,!1,u?.run_id)}finally{d()}},"handler")}));export{Ae as PROVIDER_OPERATIONS,ae as abortOnClose,R as callerAllows,Ee as callerFromApp,H as callerFromRequest,ie as handleProviderOperation,oe as parseCallInput,I as parseProvider,de as providerFailure,ee as providerOperationsEnabled,Ie as providerRoutes,Re as requireGroup};
@@ -1 +1 @@
1
- var S=Object.defineProperty;var c=(a,t)=>S(a,"name",{value:t,configurable:!0});import{callerGrant as g}from"../principal.js";import{grantVersion as b,payerOfGrant as O}from"../../../core/grants/mirror.js";import{holdGrant as $}from"../../../core/grants/watch.js";import{RunError as n,idempotencyKeyOf as v,cancelRun as N,cloudPersonScope as A,deleteRun as j,interruptRun as x,listRuns as B,messageRun as J,renameRun as z,requestedScopes as D,startRun as E,traceRun as G,waitRun as K}from"../../../core/runs/operations.js";import{canReadRun as y,requireRun as w,runCallerApp as M}from"../router.js";import{waitSeconds as q}from"../long-poll-wait.js";import{outputSchemaProblem as P}from"../../../core/runtime/structured-output.js";function m(a){if(!a||typeof a!="object"||Array.isArray(a))throw new n(400,"invalid_request","a JSON object is required");return a}c(m,"object");function k(a,t,i,u,l){if(a===null)return l;if(!/^(0|[1-9]\d*)$/.test(a)||!Number.isSafeInteger(Number(a))||Number(a)<i||Number(a)>u)throw new n(400,"invalid_request",`invalid ${t}`,t);return Number(a)}c(k,"integer");const T=256*1024;async function Y(a,t,i,u,l,_,h){switch(a.name){case"grant.get":{if("class"in t)throw new n(403,"permission_error","an app token is required");const e=q("grant.get",u),s=u.get("since");if(s!==null&&!/^[A-Za-z0-9_-]{1,64}$/.test(s))throw new n(400,"invalid_request","invalid since","since");const d=e&&s!==null?$(t.grant):void 0;let o;try{if(o=await g(t),d){const f=Date.now()+e*1e3;for(;b(o)===s&&Date.now()<f;)await d.changed(Math.max(0,(f-Date.now())/1e3)),o=await g(t)}}finally{d?.release()}return{status:200,body:{sub:t.sub,grantee:t.grantee,client_id:t.client_id,grant:t.grant,rev:o.rev,resolved_scopes:o.resolved_scopes,spend_cap:o.spend_cap,exp:t.exp,payer:await O(t.grant),dependencies:o.dependencies??{},version:b(o)}}}case"run.start":{if("class"in t)throw new n(403,"permission_error","an app token is required");const e=m(l),s=["mode","parent","fork_from","node_id","env","tools","system_prompt"];for(const r of s)if(e[r]!==void 0)throw new n(403,"owner_only",`${r} is owner-only`,r);const d=["payer","model_policy","kind"];for(const r of d)if(e[r]!==void 0)throw new n(400,"invalid_request",`${r} is not available`,r);for(const r of Object.keys(e))if(!["prompt","scopes","metadata","profile","isolated","cwd","worktree","output_schema","message","model","deadline"].includes(r))throw new n(400,"invalid_request",`unknown field ${r}`,r);if(e.model!==void 0&&(typeof e.model!="string"||!e.model.trim()))throw new n(400,"invalid_request","model must be a non-empty string","model");if(e.deadline!==void 0&&(typeof e.deadline!="string"||!e.deadline.trim()))throw new n(400,"invalid_request","deadline must be a duration string such as 10m","deadline");if(e.profile!==void 0&&(typeof e.profile!="string"||!e.profile))throw new n(400,"invalid_request","profile must be a profile id","profile");if(e.isolated!==void 0&&typeof e.isolated!="boolean")throw new n(400,"invalid_request","isolated must be a boolean","isolated");if(e.cwd!==void 0&&(e.isolated!==!0||typeof e.cwd!="string"||!e.cwd))throw new n(400,"invalid_request","cwd is available only for an isolated run and must be an absolute path inside its root folder","cwd");if(e.worktree!==void 0&&e.isolated!==!0)throw new n(400,"invalid_request","worktree is not available","worktree");if(e.isolated===!0&&e.worktree!==void 0)throw new n(400,"invalid_request","worktree is unavailable for an isolated run","worktree");if(typeof e.prompt!="string"||!e.prompt.trim())throw new n(400,"invalid_request","prompt is required","prompt");if(e.message!==void 0&&(typeof e.message!="string"||!e.message.trim()))throw new n(400,"invalid_request","message must be a non-empty string","message");const o=e.metadata===void 0?{}:m(e.metadata);if(Buffer.byteLength(JSON.stringify(o))>4096)throw new n(400,"invalid_request","metadata exceeds 4 KB","metadata");let f;if(e.output_schema!==void 0){const r=e.output_schema;if(!r||typeof r!="object"||Array.isArray(r))throw new n(400,"invalid_request","output_schema must be a JSON Schema object","output_schema");if(Buffer.byteLength(JSON.stringify(r))>T)throw new n(400,"invalid_request","output_schema exceeds 256 KB","output_schema");const p=await P(r);if(p!==null)throw new n(400,"invalid_request",`output_schema is not usable: ${p}`,"output_schema");f=r}const R=D(t.resolved,e.scopes,t.grantee);return{status:201,body:await E(t,{prompt:e.prompt,scopes:R,metadata:o,profile:e.profile,isolated:e.isolated,cwd:e.cwd,...e.model===void 0?{}:{model:e.model},...e.deadline===void 0?{}:{deadline:e.deadline},...f===void 0?{}:{output_schema:f},...e.message===void 0?{}:{message:e.message},...h===void 0?{}:{causedBy:h}},v(_))}}case"run.list":{const e=u.get("status")??void 0;if(e&&!["running","waiting_on_user","idle","settled"].includes(e))throw new n(400,"invalid_request","invalid status","status");const s=u.get("app");if(s!==null&&!/^(?:app:)?[a-z0-9][a-z0-9-]*$/.test(s))throw new n(400,"invalid_request","invalid app","app");const d=s===null?M(t):s.startsWith("app:")?s:`app:${s}`;if(d!==null&&!await y(t,d))throw new n(404,"not_found","run not found");const o=A(t.sub);return{status:200,body:await B(d,{status:e,...o===void 0?{}:{sub:o},after:u.get("after")??void 0,limit:k(u.get("limit"),"limit",1,100,50),..."class"in t&&(t.class==="broker"||t.class==="cron")&&d!==null?{visible:c(async f=>await y(t,d,f),"visible")}:{}})}}case"run.get":{const e=q("run.get",u),s=await w(t,i),d=await K(i,s.grantee,e);return await w(t,i),{status:200,body:d}}case"run.message":{const e=m(l);if(typeof e.message!="string"||!e.message.trim())throw new n(400,"invalid_request","message is required","message");if(e.start_turn!==void 0&&typeof e.start_turn!="boolean")throw new n(400,"invalid_request","start_turn must be a boolean","start_turn");if(e.steer!==void 0&&typeof e.steer!="boolean")throw new n(400,"invalid_request","steer must be a boolean","steer");if(e.start_turn===!1&&e.steer===!0)throw new n(400,"invalid_request","steer requires start_turn","steer");return{status:202,body:await J(await w(t,i,!0),e.message,void 0,e.start_turn,v(_),e.steer)}}case"run.interrupt":{const e=m(l===void 0?{}:l);if(e.children!==void 0&&e.children!=="stop"&&e.children!=="continue")throw new n(400,"invalid_request","invalid children","children");return{status:200,body:await x(await w(t,i,!0),e.children)}}case"run.cancel":{const e=m(l===void 0?{}:l);if(e.keep_schedules!==void 0&&typeof e.keep_schedules!="boolean")throw new n(400,"invalid_request","invalid keep_schedules","keep_schedules");return{status:200,body:await N(await w(t,i,!0),{keepSchedules:e.keep_schedules})}}case"run.rename":{const e=m(l);for(const s of Object.keys(e))if(s!=="name")throw new n(400,"invalid_request",`unknown field ${s}`,s);return{status:200,body:await z(await w(t,i,!0),e.name)}}case"run.delete":{const{uploads_pending:e}=await j(await w(t,i,!0,!0));return{status:200,body:{run_id:i,uploads_pending:e}}}case"trace.get":{const e=u.get("format")??void 0;if(e&&e!=="entries")throw new n(400,"invalid_request","unknown trace format","format");const s=await w(t,i);return{status:200,body:await G(i,s.grantee,e)}}default:throw new n(404,"not_found","unknown operation")}}c(Y,"handleRunOperation");export{Y as handleRunOperation,k as integer};
1
+ var S=Object.defineProperty;var c=(a,t)=>S(a,"name",{value:t,configurable:!0});import{callerGrant as g}from"../principal.js";import{grantVersion as b,payerOfGrant as O}from"../../../core/grants/mirror.js";import{holdGrant as $}from"../../../core/grants/watch.js";import{RunError as n,idempotencyKeyOf as v,cancelRun as N,cloudPersonScope as A,deleteRun as j,interruptRun as x,listRuns as B,messageRun as J,renameRun as z,requestedScopes as D,startRun as E,traceRun as G,waitRun as K}from"../../../core/runs/operations.js";import{canReadRun as y,requireRun as w,runCallerApp as M}from"../router.js";import{waitSeconds as q}from"../long-poll-wait.js";import{outputSchemaProblem as P}from"../../../core/runtime/structured-output.js";function m(a){if(!a||typeof a!="object"||Array.isArray(a))throw new n(400,"invalid_request","a JSON object is required");return a}c(m,"object");function k(a,t,i,u,l){if(a===null)return l;if(!/^(0|[1-9]\d*)$/.test(a)||!Number.isSafeInteger(Number(a))||Number(a)<i||Number(a)>u)throw new n(400,"invalid_request",`invalid ${t}`,t);return Number(a)}c(k,"integer");const T=256*1024;async function Y(a,t,i,u,l,_,h){switch(a.name){case"grant.get":{if("class"in t)throw new n(403,"permission_error","an app token is required");const e=q("grant.get",u),s=u.get("since");if(s!==null&&!/^[A-Za-z0-9_-]{1,64}$/.test(s))throw new n(400,"invalid_request","invalid since","since");const d=e&&s!==null?$(t.grant):void 0;let o;try{if(o=await g(t),d){const f=Date.now()+e*1e3;for(;b(o)===s&&Date.now()<f;)await d.changed(Math.max(0,(f-Date.now())/1e3)),o=await g(t)}}finally{d?.release()}return{status:200,body:{sub:t.sub,grantee:t.grantee,client_id:t.client_id,grant:t.grant,rev:o.rev,resolved_scopes:o.resolved_scopes,spend_cap:o.spend_cap,exp:t.exp,payer:await O(t.grant),dependencies:o.dependencies??{},version:b(o)}}}case"run.start":{if("class"in t)throw new n(403,"permission_error","an app token is required");const e=m(l),s=["mode","parent","fork_from","node_id","env","tools","system_prompt"];for(const r of s)if(e[r]!==void 0)throw new n(403,"owner_only",`${r} is owner-only`,r);const d=["payer","model_policy","kind"];for(const r of d)if(e[r]!==void 0)throw new n(400,"invalid_request",`${r} is not available`,r);for(const r of Object.keys(e))if(!["prompt","scopes","metadata","profile","isolated","cwd","worktree","output_schema","message","model","deadline"].includes(r))throw new n(400,"invalid_request",`unknown field ${r}`,r);if(e.model!==void 0&&(typeof e.model!="string"||!e.model.trim()))throw new n(400,"invalid_request","model must be a non-empty string","model");if(e.deadline!==void 0&&(typeof e.deadline!="string"||!e.deadline.trim()))throw new n(400,"invalid_request","deadline must be a duration string such as 10m","deadline");if(e.profile!==void 0&&(typeof e.profile!="string"||!e.profile))throw new n(400,"invalid_request","profile must be a profile id","profile");if(e.isolated!==void 0&&typeof e.isolated!="boolean")throw new n(400,"invalid_request","isolated must be a boolean","isolated");if(e.cwd!==void 0&&(e.isolated!==!0||typeof e.cwd!="string"||!e.cwd))throw new n(400,"invalid_request","cwd is available only for an isolated run and must be an absolute path inside its root folder","cwd");if(e.worktree!==void 0&&e.isolated!==!0)throw new n(400,"invalid_request","worktree is not available","worktree");if(e.isolated===!0&&e.worktree!==void 0)throw new n(400,"invalid_request","worktree is unavailable for an isolated run","worktree");if(typeof e.prompt!="string"||!e.prompt.trim())throw new n(400,"invalid_request","prompt is required","prompt");if(e.message!==void 0&&(typeof e.message!="string"||!e.message.trim()))throw new n(400,"invalid_request","message must be a non-empty string","message");const o=e.metadata===void 0?{}:m(e.metadata);if(Buffer.byteLength(JSON.stringify(o))>4096)throw new n(400,"invalid_request","metadata exceeds 4 KB","metadata");let f;if(e.output_schema!==void 0){const r=e.output_schema;if(!r||typeof r!="object"||Array.isArray(r))throw new n(400,"invalid_request","output_schema must be a JSON Schema object","output_schema");if(Buffer.byteLength(JSON.stringify(r))>T)throw new n(400,"invalid_request","output_schema exceeds 256 KB","output_schema");const p=await P(r);if(p!==null)throw new n(400,"invalid_request",`output_schema is not usable: ${p}`,"output_schema");f=r}const R=D(t.resolved,e.scopes,t.grantee);return{status:201,body:await E(t,{prompt:e.prompt,scopes:R,metadata:o,profile:e.profile,isolated:e.isolated,cwd:e.cwd,...e.model===void 0?{}:{model:e.model},...e.deadline===void 0?{}:{deadline:e.deadline},...f===void 0?{}:{output_schema:f},...e.message===void 0?{}:{message:e.message},...h===void 0?{}:{causedBy:h}},v(_))}}case"run.list":{const e=u.get("status")??void 0;if(e&&!["running","waiting_on_user","idle","settled"].includes(e))throw new n(400,"invalid_request","invalid status","status");const s=u.get("app");if(s!==null&&!/^(?:app:)?[a-z0-9][a-z0-9-]*$/.test(s))throw new n(400,"invalid_request","invalid app","app");const d=s===null?M(t):s.startsWith("app:")?s:`app:${s}`;if(d!==null&&!await y(t,d))throw new n(404,"not_found","run not found");const o=A("sub"in t?t.sub:void 0);return{status:200,body:await B(d,{status:e,...o===void 0?{}:{sub:o},after:u.get("after")??void 0,limit:k(u.get("limit"),"limit",1,100,50),..."class"in t&&(t.class==="broker"||t.class==="cron")&&d!==null?{visible:c(async f=>await y(t,d,f),"visible")}:{}})}}case"run.get":{const e=q("run.get",u),s=await w(t,i),d=await K(i,s.grantee,e);return await w(t,i),{status:200,body:d}}case"run.message":{const e=m(l);if(typeof e.message!="string"||!e.message.trim())throw new n(400,"invalid_request","message is required","message");if(e.start_turn!==void 0&&typeof e.start_turn!="boolean")throw new n(400,"invalid_request","start_turn must be a boolean","start_turn");if(e.steer!==void 0&&typeof e.steer!="boolean")throw new n(400,"invalid_request","steer must be a boolean","steer");if(e.start_turn===!1&&e.steer===!0)throw new n(400,"invalid_request","steer requires start_turn","steer");return{status:202,body:await J(await w(t,i,!0),e.message,void 0,e.start_turn,v(_),e.steer)}}case"run.interrupt":{const e=m(l===void 0?{}:l);if(e.children!==void 0&&e.children!=="stop"&&e.children!=="continue")throw new n(400,"invalid_request","invalid children","children");return{status:200,body:await x(await w(t,i,!0),e.children)}}case"run.cancel":{const e=m(l===void 0?{}:l);if(e.keep_schedules!==void 0&&typeof e.keep_schedules!="boolean")throw new n(400,"invalid_request","invalid keep_schedules","keep_schedules");return{status:200,body:await N(await w(t,i,!0),{keepSchedules:e.keep_schedules})}}case"run.rename":{const e=m(l);for(const s of Object.keys(e))if(s!=="name")throw new n(400,"invalid_request",`unknown field ${s}`,s);return{status:200,body:await z(await w(t,i,!0),e.name)}}case"run.delete":{const{uploads_pending:e}=await j(await w(t,i,!0,!0));return{status:200,body:{run_id:i,uploads_pending:e}}}case"trace.get":{const e=u.get("format")??void 0;if(e&&e!=="entries")throw new n(400,"invalid_request","unknown trace format","format");const s=await w(t,i);return{status:200,body:await G(i,s.grantee,e)}}default:throw new n(404,"not_found","unknown operation")}}c(Y,"handleRunOperation");export{Y as handleRunOperation,k as integer};
@@ -0,0 +1,6 @@
1
+ import type { ImportPrincipal } from './router.js';
2
+ export declare const IMPORT_TOKEN_PREFIX = "crtr_imp_";
3
+ /** The import token for `run`: `crtr_imp_<run>.<hex hmac-sha256(run) under the service token>`; null when the service token is unset. */
4
+ export declare function importTokenFor(run: string): string | null;
5
+ /** The principal a bearer is, when it is the service credential or a valid import token; null otherwise (including when the service token is unset). */
6
+ export declare function importPrincipalFor(token: string): ImportPrincipal | null;
@@ -0,0 +1 @@
1
+ var u=Object.defineProperty;var e=(t,n)=>u(t,"name",{value:n,configurable:!0});import{createHash as a,createHmac as p,timingSafeEqual as m}from"node:crypto";const s="crtr_imp_",i=e(()=>{const t=process.env.CRTR_IMPORT_SERVICE_TOKEN;return t===void 0||t===""?null:t},"serviceToken"),c=e(t=>a("sha256").update(t).digest(),"digest"),o=e((t,n)=>m(c(t),c(n)),"sameString");function d(t){const n=i();return n===null?null:`${s}${t}.${p("sha256",n).update(t).digest("hex")}`}e(d,"importTokenFor");function _(t){const n=i();if(n===null)return null;if(t.startsWith(s)){const r=t.slice(s.length).split(".")[0]??"",l=d(r);return r!==""&&l!==null&&o(t,l)?{class:"import",run:r}:null}return o(t,n)?{class:"import",run:null}:null}e(_,"importPrincipalFor");export{s as IMPORT_TOKEN_PREFIX,_ as importPrincipalFor,d as importTokenFor};
@@ -1,6 +1,6 @@
1
1
  import type { Scope } from '@north-light/identity';
2
- export type CallerClass = 'anonymous' | 'person' | 'app' | 'broker' | 'cron' | 'directory' | 'forwarded' | 'machine';
3
- export type ResourceRule = 'none' | 'node-read' | 'node-write' | 'node-own' | 'nodes-list' | 'canvas-read' | 'cron' | 'profile' | 'human' | 'run' | 'daemon-bash' | 'objects';
2
+ export type CallerClass = 'anonymous' | 'person' | 'app' | 'broker' | 'cron' | 'directory' | 'forwarded' | 'machine' | 'import';
3
+ export type ResourceRule = 'none' | 'node-read' | 'node-write' | 'node-own' | 'nodes-list' | 'canvas-read' | 'cron' | 'profile' | 'human' | 'run' | 'daemon-bash' | 'daemon-control' | 'objects';
4
4
  export interface Operation {
5
5
  name: string;
6
6
  method: string;