@fungi.computer/oyster 0.0.0-stage → 0.1.0

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 (339) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1134 -2
  3. package/dist/agents.d.ts +114 -0
  4. package/dist/agents.js +322 -0
  5. package/dist/app-builds.d.ts +210 -0
  6. package/dist/app-builds.js +499 -0
  7. package/dist/app-sandbox.d.ts +2 -0
  8. package/dist/app-sandbox.js +2 -0
  9. package/dist/app-sources.d.ts +31 -0
  10. package/dist/app-sources.js +370 -0
  11. package/dist/app-storage.d.ts +141 -0
  12. package/dist/app-storage.js +281 -0
  13. package/dist/apps/gate.d.ts +105 -0
  14. package/dist/apps/gate.js +273 -0
  15. package/dist/apps/index.d.ts +5 -0
  16. package/dist/apps/index.js +5 -0
  17. package/dist/apps/installation.d.ts +248 -0
  18. package/dist/apps/installation.js +307 -0
  19. package/dist/apps/operations.d.ts +8 -0
  20. package/dist/apps/operations.js +98 -0
  21. package/dist/apps/tools.d.ts +33 -0
  22. package/dist/apps/tools.js +17 -0
  23. package/dist/auth.d.ts +11 -0
  24. package/dist/auth.js +93 -0
  25. package/dist/backend-request.d.ts +3 -0
  26. package/dist/backend-request.js +53 -0
  27. package/dist/build/artifact-writer.d.ts +40 -0
  28. package/dist/build/artifact-writer.js +181 -0
  29. package/dist/build/build-jobs.d.ts +128 -0
  30. package/dist/build/build-jobs.js +203 -0
  31. package/dist/build/build-policy.d.ts +10 -0
  32. package/dist/build/build-policy.js +10 -0
  33. package/dist/build/build-step.d.ts +37 -0
  34. package/dist/build/build-step.js +28 -0
  35. package/dist/build/bundled-html.d.ts +7 -0
  36. package/dist/build/bundled-html.js +64 -0
  37. package/dist/build/candidate-builder.d.ts +68 -0
  38. package/dist/build/candidate-builder.js +91 -0
  39. package/dist/build/candidate-cli.d.ts +1 -0
  40. package/dist/build/candidate-cli.js +119 -0
  41. package/dist/build/candidate-cli.mjs +28170 -0
  42. package/dist/build/content-objects.d.ts +37 -0
  43. package/dist/build/content-objects.js +53 -0
  44. package/dist/build/definition-emission.d.ts +34 -0
  45. package/dist/build/definition-emission.js +201 -0
  46. package/dist/build/effect.d.ts +249 -0
  47. package/dist/build/effect.js +436 -0
  48. package/dist/build/git-environment.d.ts +9 -0
  49. package/dist/build/git-environment.js +60 -0
  50. package/dist/build/html-recipe.d.ts +20 -0
  51. package/dist/build/html-recipe.js +51 -0
  52. package/dist/build/local-build.d.ts +67 -0
  53. package/dist/build/local-build.js +1128 -0
  54. package/dist/build/snapshot-package-store.d.ts +12 -0
  55. package/dist/build/snapshot-package-store.js +36 -0
  56. package/dist/build/snapshot-program-scripts.d.ts +6 -0
  57. package/dist/build/snapshot-program-scripts.js +2 -0
  58. package/dist/build/snapshot-programs/snapshot-path.d.ts +2 -0
  59. package/dist/build/snapshot-programs/snapshot-path.js +6 -0
  60. package/dist/build/submission-contract.d.ts +828 -0
  61. package/dist/build/submission-contract.js +180 -0
  62. package/dist/build/workspace-build.d.ts +55 -0
  63. package/dist/build/workspace-build.js +200 -0
  64. package/dist/build-contract.d.ts +7 -0
  65. package/dist/build-contract.js +7 -0
  66. package/dist/build-egress.d.ts +62 -0
  67. package/dist/build-egress.js +148 -0
  68. package/dist/build-jobs.d.ts +2 -0
  69. package/dist/build-jobs.js +2 -0
  70. package/dist/build.d.ts +12 -0
  71. package/dist/build.js +14 -0
  72. package/dist/capability-descriptor.d.ts +46 -0
  73. package/dist/capability-descriptor.js +51 -0
  74. package/dist/catalog-frame.d.ts +39 -0
  75. package/dist/catalog-frame.js +72 -0
  76. package/dist/cause-class.d.ts +19 -0
  77. package/dist/cause-class.js +54 -0
  78. package/dist/cloudflare.d.ts +123 -0
  79. package/dist/cloudflare.js +391 -0
  80. package/dist/computer-terminal.d.ts +38 -0
  81. package/dist/computer-terminal.js +410 -0
  82. package/dist/computer.d.ts +60 -0
  83. package/dist/computer.js +33 -0
  84. package/dist/event-recipients.d.ts +57 -0
  85. package/dist/event-recipients.js +144 -0
  86. package/dist/facet-invocation.d.ts +4 -0
  87. package/dist/facet-invocation.js +45 -0
  88. package/dist/files.d.ts +42 -0
  89. package/dist/files.js +123 -0
  90. package/dist/frame-binding-wire-script.d.ts +2 -0
  91. package/dist/frame-binding-wire-script.js +1 -0
  92. package/dist/frame-binding-wire.d.ts +28 -0
  93. package/dist/frame-binding-wire.js +54 -0
  94. package/dist/git-admission.d.ts +52 -0
  95. package/dist/git-admission.js +188 -0
  96. package/dist/git-auth.d.ts +11 -0
  97. package/dist/git-auth.js +27 -0
  98. package/dist/git-door.d.ts +35 -0
  99. package/dist/git-door.js +108 -0
  100. package/dist/git-journal.d.ts +31 -0
  101. package/dist/git-journal.js +188 -0
  102. package/dist/git.d.ts +31 -0
  103. package/dist/git.js +64 -0
  104. package/dist/guest-policy.d.ts +9 -0
  105. package/dist/guest-policy.js +13 -0
  106. package/dist/host.d.ts +244 -0
  107. package/dist/host.js +1043 -0
  108. package/dist/index.d.ts +166 -0
  109. package/dist/index.js +320 -0
  110. package/dist/installation-consent.d.ts +79 -0
  111. package/dist/installation-consent.js +82 -0
  112. package/dist/internal/background-origins.d.ts +3 -0
  113. package/dist/internal/background-origins.js +8 -0
  114. package/dist/internal/delivery.d.ts +12 -0
  115. package/dist/internal/delivery.js +167 -0
  116. package/dist/internal/host-gate.d.ts +14 -0
  117. package/dist/internal/host-gate.js +62 -0
  118. package/dist/internal/registry-installations.d.ts +6 -0
  119. package/dist/internal/registry-installations.js +12 -0
  120. package/dist/invocation.d.ts +251 -0
  121. package/dist/invocation.js +377 -0
  122. package/dist/keys.d.ts +48 -0
  123. package/dist/keys.js +52 -0
  124. package/dist/mcp-owner.d.ts +27 -0
  125. package/dist/mcp-owner.js +155 -0
  126. package/dist/mcp.d.ts +89 -0
  127. package/dist/mcp.js +429 -0
  128. package/dist/media.d.ts +23 -0
  129. package/dist/media.js +12 -0
  130. package/dist/pack.d.ts +79 -0
  131. package/dist/pack.js +151 -0
  132. package/dist/page/capabilities.d.ts +23 -0
  133. package/dist/page/capabilities.js +256 -0
  134. package/dist/page/computer-handles.d.ts +79 -0
  135. package/dist/page/computer-handles.js +44 -0
  136. package/dist/page/gill-observation.d.ts +4 -0
  137. package/dist/page/gill-observation.js +31 -0
  138. package/dist/page/lifetime.d.ts +6 -0
  139. package/dist/page/lifetime.js +19 -0
  140. package/dist/page/mount.d.ts +163 -0
  141. package/dist/page/mount.js +390 -0
  142. package/dist/page/storage.d.ts +3 -0
  143. package/dist/page/storage.js +82 -0
  144. package/dist/page/terminal.d.ts +5 -0
  145. package/dist/page/terminal.js +7 -0
  146. package/dist/page/window-launch.d.ts +18 -0
  147. package/dist/page/window-launch.js +11 -0
  148. package/dist/page-script.d.ts +14 -0
  149. package/dist/page-script.js +5 -0
  150. package/dist/r2-files.d.ts +3 -0
  151. package/dist/r2-files.js +79 -0
  152. package/dist/release.d.ts +83 -0
  153. package/dist/release.js +167 -0
  154. package/dist/root-data.d.ts +86 -0
  155. package/dist/root-data.js +69 -0
  156. package/dist/shell/app-directory.d.ts +542 -0
  157. package/dist/shell/app-directory.js +199 -0
  158. package/dist/shell/browser-actions.d.ts +7 -0
  159. package/dist/shell/browser-actions.js +56 -0
  160. package/dist/shell/command-host.d.ts +112 -0
  161. package/dist/shell/command-host.js +502 -0
  162. package/dist/shell/commands.d.ts +25 -0
  163. package/dist/shell/commands.js +78 -0
  164. package/dist/shell/index.d.ts +8 -0
  165. package/dist/shell/index.js +8 -0
  166. package/dist/shell/keys.d.ts +41 -0
  167. package/dist/shell/keys.js +134 -0
  168. package/dist/shell/shell-module.d.ts +55 -0
  169. package/dist/shell/shell-module.js +86 -0
  170. package/dist/shell/window-manager.d.ts +124 -0
  171. package/dist/shell/window-manager.js +711 -0
  172. package/dist/shell/workbench-contract.d.ts +68 -0
  173. package/dist/shell/workbench-contract.js +1 -0
  174. package/dist/shell/workbench-directory.d.ts +332 -0
  175. package/dist/shell/workbench-directory.js +35 -0
  176. package/dist/shell/workbench-host-types.d.ts +96 -0
  177. package/dist/shell/workbench-host-types.js +1 -0
  178. package/dist/shell/workbench-host.d.ts +34 -0
  179. package/dist/shell/workbench-host.js +347 -0
  180. package/dist/shell/workbench-model.d.ts +192 -0
  181. package/dist/shell/workbench-model.js +53 -0
  182. package/dist/shell/workbench.d.ts +4 -0
  183. package/dist/shell/workbench.js +2 -0
  184. package/dist/source/fork.d.ts +27 -0
  185. package/dist/source/fork.js +46 -0
  186. package/dist/source/index.d.ts +10 -0
  187. package/dist/source/index.js +10 -0
  188. package/dist/source/seed-receipt.d.ts +53 -0
  189. package/dist/source/seed-receipt.js +18 -0
  190. package/dist/source/team-source.d.ts +32 -0
  191. package/dist/source/team-source.js +42 -0
  192. package/dist/source/template-push.d.ts +33 -0
  193. package/dist/source/template-push.js +223 -0
  194. package/dist/source/template-repository.d.ts +4 -0
  195. package/dist/source/template-repository.js +21 -0
  196. package/dist/source/template-seed.d.ts +68 -0
  197. package/dist/source/template-seed.js +80 -0
  198. package/dist/standalone/agent-directory.d.ts +4 -0
  199. package/dist/standalone/agent-directory.js +4 -0
  200. package/dist/standalone/agents.d.ts +220 -0
  201. package/dist/standalone/agents.js +795 -0
  202. package/dist/standalone/app-builds-binding.d.ts +36 -0
  203. package/dist/standalone/app-builds-binding.js +119 -0
  204. package/dist/standalone/app-set.d.ts +91 -0
  205. package/dist/standalone/app-set.js +395 -0
  206. package/dist/standalone/app-source.d.ts +41 -0
  207. package/dist/standalone/app-source.js +59 -0
  208. package/dist/standalone/builds.d.ts +18 -0
  209. package/dist/standalone/builds.js +101 -0
  210. package/dist/standalone/computer-egress.d.ts +37 -0
  211. package/dist/standalone/computer-egress.js +111 -0
  212. package/dist/standalone/computer.d.ts +122 -0
  213. package/dist/standalone/computer.js +714 -0
  214. package/dist/standalone/install-apps-cli.d.ts +2 -0
  215. package/dist/standalone/install-apps-cli.js +126 -0
  216. package/dist/standalone/instance.d.ts +35 -0
  217. package/dist/standalone/instance.js +74 -0
  218. package/dist/standalone/media.d.ts +76 -0
  219. package/dist/standalone/media.js +227 -0
  220. package/dist/standalone/preview.d.ts +51 -0
  221. package/dist/standalone/preview.js +167 -0
  222. package/dist/standalone/registry.d.ts +102 -0
  223. package/dist/standalone/registry.js +193 -0
  224. package/dist/standalone/tenancy.d.ts +42 -0
  225. package/dist/standalone/tenancy.js +241 -0
  226. package/dist/standalone/worker.d.ts +55 -0
  227. package/dist/standalone/worker.js +147 -0
  228. package/dist/standalone/workflow-authority-readers.d.ts +16 -0
  229. package/dist/standalone/workflow-authority-readers.js +35 -0
  230. package/dist/standalone/workflow-binding.d.ts +216 -0
  231. package/dist/standalone/workflow-binding.js +217 -0
  232. package/dist/standalone/workflow-events.d.ts +46 -0
  233. package/dist/standalone/workflow-events.js +39 -0
  234. package/dist/standalone/workflow-git-capture.d.ts +7 -0
  235. package/dist/standalone/workflow-git-capture.js +62 -0
  236. package/dist/standalone/workflow-host.d.ts +931 -0
  237. package/dist/standalone/workflow-host.js +751 -0
  238. package/dist/standalone/workflow-node-capture.d.ts +13 -0
  239. package/dist/standalone/workflow-node-capture.js +72 -0
  240. package/dist/standalone/workflow-root-authority.d.ts +27 -0
  241. package/dist/standalone/workflow-root-authority.js +87 -0
  242. package/dist/standalone/workflow-schedules.d.ts +52 -0
  243. package/dist/standalone/workflow-schedules.js +40 -0
  244. package/dist/standalone/workflow-trigger-host.d.ts +1694 -0
  245. package/dist/standalone/workflow-trigger-host.js +52 -0
  246. package/dist/standalone/workflows.d.ts +657 -0
  247. package/dist/standalone/workflows.js +554 -0
  248. package/dist/storage-frame.d.ts +64 -0
  249. package/dist/storage-frame.js +28 -0
  250. package/dist/tenancy/auth0.d.ts +64 -0
  251. package/dist/tenancy/auth0.js +198 -0
  252. package/dist/tenancy/cloudflare/rate-limit.d.ts +8 -0
  253. package/dist/tenancy/cloudflare/rate-limit.js +19 -0
  254. package/dist/tenancy/cloudflare/registry.d.ts +50 -0
  255. package/dist/tenancy/cloudflare/registry.js +301 -0
  256. package/dist/tenancy/cloudflare.d.ts +2 -0
  257. package/dist/tenancy/cloudflare.js +2 -0
  258. package/dist/tenancy/cold-directory.d.ts +13 -0
  259. package/dist/tenancy/cold-directory.js +43 -0
  260. package/dist/tenancy/contracts.d.ts +195 -0
  261. package/dist/tenancy/contracts.js +1 -0
  262. package/dist/tenancy/index.d.ts +19 -0
  263. package/dist/tenancy/index.js +156 -0
  264. package/dist/tenancy/owner-key.d.ts +26 -0
  265. package/dist/tenancy/owner-key.js +63 -0
  266. package/dist/tenancy/oyster.d.ts +4 -0
  267. package/dist/tenancy/oyster.js +25 -0
  268. package/dist/tenancy/registry-record.d.ts +7 -0
  269. package/dist/tenancy/registry-record.js +44 -0
  270. package/dist/tenancy/registry.d.ts +27 -0
  271. package/dist/tenancy/registry.js +138 -0
  272. package/dist/tenancy/resolve.d.ts +7 -0
  273. package/dist/tenancy/resolve.js +64 -0
  274. package/dist/tenant.d.ts +81 -0
  275. package/dist/tenant.js +71 -0
  276. package/dist/testing/capabilities.d.ts +29 -0
  277. package/dist/testing/capabilities.js +130 -0
  278. package/dist/testing/catalog.d.ts +3 -0
  279. package/dist/testing/catalog.js +53 -0
  280. package/dist/testing/index.d.ts +10 -0
  281. package/dist/testing/index.js +10 -0
  282. package/dist/testing/installation.d.ts +11 -0
  283. package/dist/testing/installation.js +25 -0
  284. package/dist/testing/native-worker.d.ts +21 -0
  285. package/dist/testing/native-worker.js +106 -0
  286. package/dist/testing/worker.d.ts +18 -0
  287. package/dist/testing/worker.js +67 -0
  288. package/dist/testing/workers-runtime.d.ts +24 -0
  289. package/dist/testing/workers-runtime.js +33 -0
  290. package/dist/webhook-delivery.d.ts +14 -0
  291. package/dist/webhook-delivery.js +17 -0
  292. package/dist/workers-git.d.ts +285 -0
  293. package/dist/workers-git.js +1174 -0
  294. package/dist/workers.d.ts +352 -0
  295. package/dist/workers.js +868 -0
  296. package/docs/api/README.md +82 -0
  297. package/docs/api/agents.md +332 -0
  298. package/docs/api/app-storage.md +209 -0
  299. package/docs/api/apps.md +1375 -0
  300. package/docs/api/auth.md +63 -0
  301. package/docs/api/build-contract.md +1304 -0
  302. package/docs/api/build.md +1712 -0
  303. package/docs/api/cloudflare.md +421 -0
  304. package/docs/api/computer.md +203 -0
  305. package/docs/api/files.md +117 -0
  306. package/docs/api/git.md +470 -0
  307. package/docs/api/host.md +1328 -0
  308. package/docs/api/index.md +430 -0
  309. package/docs/api/invocation.md +738 -0
  310. package/docs/api/keys.md +175 -0
  311. package/docs/api/mcp.md +301 -0
  312. package/docs/api/media.md +40 -0
  313. package/docs/api/modules.md +50 -0
  314. package/docs/api/pack.md +141 -0
  315. package/docs/api/page/mount.md +298 -0
  316. package/docs/api/root-data.md +111 -0
  317. package/docs/api/shell/app-directory.md +599 -0
  318. package/docs/api/shell/command-host.md +224 -0
  319. package/docs/api/shell/commands.md +197 -0
  320. package/docs/api/shell/keys.md +183 -0
  321. package/docs/api/shell/shell-module.md +204 -0
  322. package/docs/api/shell/window-manager.md +267 -0
  323. package/docs/api/shell/workbench-directory.md +119 -0
  324. package/docs/api/shell/workbench.md +205 -0
  325. package/docs/api/shell.md +1915 -0
  326. package/docs/api/tenancy/auth0.md +110 -0
  327. package/docs/api/tenancy/cloudflare.md +221 -0
  328. package/docs/api/tenancy/cold-directory.md +46 -0
  329. package/docs/api/tenancy/owner-key.md +63 -0
  330. package/docs/api/tenancy/registry.md +77 -0
  331. package/docs/api/tenancy/resolve.md +69 -0
  332. package/docs/api/tenancy.md +430 -0
  333. package/docs/api/tenant.md +194 -0
  334. package/docs/api/workers-git.md +1646 -0
  335. package/docs/api/workers.md +1691 -0
  336. package/docs/site/agent-quickstart.mdx +346 -0
  337. package/examples/standalone/worker.ts +7 -0
  338. package/examples/standalone/wrangler.jsonc +82 -0
  339. package/package.json +300 -3
@@ -0,0 +1,203 @@
1
+ var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
2
+ if (value !== null && value !== void 0) {
3
+ if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
4
+ var dispose, inner;
5
+ if (async) {
6
+ if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
7
+ dispose = value[Symbol.asyncDispose];
8
+ }
9
+ if (dispose === void 0) {
10
+ if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
11
+ dispose = value[Symbol.dispose];
12
+ if (async) inner = dispose;
13
+ }
14
+ if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
15
+ if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
16
+ env.stack.push({ value: value, dispose: dispose, async: async });
17
+ }
18
+ else if (async) {
19
+ env.stack.push({ async: true });
20
+ }
21
+ return value;
22
+ };
23
+ var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
24
+ return function (env) {
25
+ function fail(e) {
26
+ env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
27
+ env.hasError = true;
28
+ }
29
+ var r, s = 0;
30
+ function next() {
31
+ while (r = env.stack.pop()) {
32
+ try {
33
+ if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
34
+ if (r.dispose) {
35
+ var result = r.dispose.call(r.value);
36
+ if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
37
+ }
38
+ else s |= 1;
39
+ }
40
+ catch (e) {
41
+ fail(e);
42
+ }
43
+ }
44
+ if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
45
+ if (env.hasError) throw env.error;
46
+ }
47
+ return next();
48
+ };
49
+ })(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
50
+ var e = new Error(message);
51
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
52
+ });
53
+ import * as Schema from "effect/Schema";
54
+ import { base64url } from "jose";
55
+ import { workspaceBuildPolicy } from "./build-policy.js";
56
+ /** The secret-free Team job; no provider token enters publication. */
57
+ const jobSchema = Schema.Struct({
58
+ jobId: Schema.NonEmptyString,
59
+ computerId: Schema.NonEmptyString,
60
+ credentialId: Schema.NonEmptyString,
61
+ issuedAt: Schema.Number,
62
+ repository: Schema.NonEmptyString,
63
+ expiresAt: Schema.Number,
64
+ remote: Schema.NonEmptyString,
65
+ reportUrl: Schema.NonEmptyString,
66
+ status: Schema.Literals(["running", "succeeded", "failed", "revoked"]),
67
+ artifact: Schema.NullOr(Schema.Struct({ digest: Schema.NonEmptyString, bytes: Schema.Number })),
68
+ detail: Schema.NullOr(Schema.String),
69
+ });
70
+ const executionSchema = Schema.Struct({
71
+ output: Schema.optional(Schema.NullOr(Schema.Struct({
72
+ exitCode: Schema.NullOr(Schema.Number),
73
+ stderr: Schema.optional(Schema.String),
74
+ }))),
75
+ });
76
+ /** Publisher executes builds; Team retains job authority, Computer runs them. */
77
+ export function createTeamBuildPublisher(options) {
78
+ const now = options.now ?? Date.now;
79
+ const maxPermissionAgeMs = options.maxPermissionAgeMs ?? 5 * 60_000;
80
+ const decodeJob = Schema.decodeUnknownSync(jobSchema);
81
+ return Object.freeze({
82
+ mint(input) {
83
+ return options.team.mintTeamBuildCredential(input);
84
+ },
85
+ async read(jobId) {
86
+ const env_1 = { stack: [], error: void 0, hasError: false };
87
+ try {
88
+ const build = __addDisposableResource(env_1, await options.team.build(), false);
89
+ return await build.read(jobId);
90
+ }
91
+ catch (e_1) {
92
+ env_1.error = e_1;
93
+ env_1.hasError = true;
94
+ }
95
+ finally {
96
+ __disposeResources(env_1);
97
+ }
98
+ },
99
+ revoke(jobId) {
100
+ return options.team.revokeTeamBuild(jobId);
101
+ },
102
+ async run(input) {
103
+ const env_2 = { stack: [], error: void 0, hasError: false };
104
+ try {
105
+ const build = __addDisposableResource(env_2, await options.team.build(), false);
106
+ const job = decodeJob(await build.prepare(input.jobId));
107
+ const issuedAt = now();
108
+ if (job.status !== "running" || job.expiresAt <= issuedAt)
109
+ throw new Error("Team build expired");
110
+ const computer = options.computer(job.computerId);
111
+ const decision = {
112
+ kind: "build",
113
+ schemaVersion: 1,
114
+ teamId: options.teamId,
115
+ computerId: job.computerId,
116
+ jobId: job.jobId,
117
+ credentialId: job.credentialId,
118
+ operations: ["computer.execute"],
119
+ sequence: 1,
120
+ issuedAt,
121
+ expiresAt: Math.min(job.expiresAt, issuedAt + maxPermissionAgeMs),
122
+ status: "allowed",
123
+ basis: { credentialExpiresAt: job.expiresAt },
124
+ };
125
+ await computer.applyBuildDecision(decision);
126
+ try {
127
+ const result = Schema.decodeUnknownSync(executionSchema)(await computer.execute({
128
+ teamId: options.teamId,
129
+ computerId: job.computerId,
130
+ jobId: job.jobId,
131
+ credentialId: job.credentialId,
132
+ command: "export TEAM_BUILD_REMOTE=" +
133
+ job.remote +
134
+ " TEAM_BUILD_REPORT=" +
135
+ job.reportUrl +
136
+ "\n" +
137
+ input.command,
138
+ timeoutMs: input.timeoutMs,
139
+ }));
140
+ await computer.deliverTeamGitEvents();
141
+ const current = decodeJob(await build.read(input.jobId));
142
+ if (current.status !== "running") {
143
+ await build.deliverProjection();
144
+ return current;
145
+ }
146
+ const exitCode = result.output?.exitCode ?? null;
147
+ const completed = decodeJob(await build.complete(job.jobId, exitCode === 0 && current.artifact !== null
148
+ ? "succeeded"
149
+ : "failed", exitCode === 0
150
+ ? "Build exited without reporting status"
151
+ : "Build exited with " +
152
+ String(exitCode) +
153
+ ": " +
154
+ (result.output?.stderr ?? "").slice(-1_000)));
155
+ await build.deliverProjection();
156
+ return completed;
157
+ }
158
+ finally {
159
+ await computer.applyBuildDecision({
160
+ ...decision,
161
+ sequence: 2,
162
+ status: "revoked",
163
+ issuedAt: Math.min(now(), decision.expiresAt - 1),
164
+ });
165
+ }
166
+ }
167
+ catch (e_2) {
168
+ env_2.error = e_2;
169
+ env_2.hasError = true;
170
+ }
171
+ finally {
172
+ __disposeResources(env_2);
173
+ }
174
+ },
175
+ });
176
+ }
177
+ /** Fixed captured builder command; neither source nor the customer supplies shell code. */
178
+ export function teamAppBuildCommand(configuration, uploadGrant) {
179
+ const encoded = base64url.encode(JSON.stringify({
180
+ uploadGrant,
181
+ // A builder that predates this policy refuses the whole request.
182
+ buildPolicy: workspaceBuildPolicy,
183
+ appId: configuration.appId,
184
+ sourceRevision: configuration.sourceRevision,
185
+ recipe: configuration.source.recipe,
186
+ buildRecipeDigest: configuration.source.buildRecipeDigest,
187
+ }));
188
+ return [
189
+ "set -eu",
190
+ // Build scratch (clone, snapshot, installed packages) lives on the
191
+ // Computer's disk: an on-box VM's /tmp is RAM-backed tmpfs, which a
192
+ // workspace install fills while the compiler needs that memory.
193
+ "work=$(mktemp -d /var/tmp/fungi-app-build-XXXXXXXX)",
194
+ `trap 'rm -rf "$work"' EXIT`,
195
+ 'mkdir "$work/tmp"',
196
+ 'export TMPDIR="$work/tmp"',
197
+ "clone_started=$(date +%s%3N)",
198
+ 'git clone -q --no-checkout -- "$TEAM_BUILD_REMOTE" "$work/source" 2>/dev/null',
199
+ // The builder reports each phase's duration with its outcome.
200
+ "export TEAM_BUILD_CLONE_MS=$(( $(date +%s%3N) - clone_started ))",
201
+ `node /opt/fungi/team-candidate-builder.mjs '${encoded}' "$work/source" "$work/candidate"`,
202
+ ].join("\n");
203
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * The pinned workspace build policy: how a captured workspace's manifests
3
+ * build (`package.json#fungi.build`). v2 lets a dependency name its own
4
+ * package build script, which only an admitted Computer build runs.
5
+ *
6
+ * It is part of every workspace HTML recipe's toolchain, so its digest moves
7
+ * with it, and the Computer build request carries it at the top level, where
8
+ * a builder compiled before it existed refuses the request outright.
9
+ */
10
+ export declare const workspaceBuildPolicy = "package-json-fungi-build-v2";
@@ -0,0 +1,10 @@
1
+ /**
2
+ * The pinned workspace build policy: how a captured workspace's manifests
3
+ * build (`package.json#fungi.build`). v2 lets a dependency name its own
4
+ * package build script, which only an admitted Computer build runs.
5
+ *
6
+ * It is part of every workspace HTML recipe's toolchain, so its digest moves
7
+ * with it, and the Computer build request carries it at the top level, where
8
+ * a builder compiled before it existed refuses the request outright.
9
+ */
10
+ export const workspaceBuildPolicy = "package-json-fungi-build-v2";
@@ -0,0 +1,37 @@
1
+ import * as Schema from "effect/Schema";
2
+ /**
3
+ * One captured dependency build step. Paths are relative to the captured
4
+ * snapshot root. A TypeScript step compiles one config; a Tailwind step
5
+ * compiles one stylesheet input into its output (the same path for an App's
6
+ * own stylesheet, which compiles in place). A script step runs the one
7
+ * `package.json` script a workspace dependency names as its build
8
+ * (`fungi.build.script`), with pnpm, in that package's directory; only an
9
+ * admitted Computer build executes it.
10
+ */
11
+ export declare const workspaceBuildStepSchema: Schema.Union<readonly [Schema.Struct<{
12
+ readonly kind: Schema.Literal<"typescript">;
13
+ readonly config: Schema.String;
14
+ }>, Schema.Struct<{
15
+ readonly kind: Schema.Literal<"tailwind">;
16
+ readonly input: Schema.String;
17
+ readonly output: Schema.String;
18
+ }>, Schema.Struct<{
19
+ readonly kind: Schema.Literal<"script">;
20
+ readonly directory: Schema.String;
21
+ readonly script: Schema.String;
22
+ }>]>;
23
+ /** One captured dependency build step. */
24
+ export type WorkspaceBuildStep = Schema.Schema.Type<typeof workspaceBuildStepSchema>;
25
+ /** The steps one dependency-build process runs, encoded as one argument. */
26
+ export declare const workspaceBuildStepsJsonSchema: Schema.fromJsonString<Schema.$Array<Schema.Union<readonly [Schema.Struct<{
27
+ readonly kind: Schema.Literal<"typescript">;
28
+ readonly config: Schema.String;
29
+ }>, Schema.Struct<{
30
+ readonly kind: Schema.Literal<"tailwind">;
31
+ readonly input: Schema.String;
32
+ readonly output: Schema.String;
33
+ }>, Schema.Struct<{
34
+ readonly kind: Schema.Literal<"script">;
35
+ readonly directory: Schema.String;
36
+ readonly script: Schema.String;
37
+ }>]>>>;
@@ -0,0 +1,28 @@
1
+ import * as Schema from "effect/Schema";
2
+ /**
3
+ * One captured dependency build step. Paths are relative to the captured
4
+ * snapshot root. A TypeScript step compiles one config; a Tailwind step
5
+ * compiles one stylesheet input into its output (the same path for an App's
6
+ * own stylesheet, which compiles in place). A script step runs the one
7
+ * `package.json` script a workspace dependency names as its build
8
+ * (`fungi.build.script`), with pnpm, in that package's directory; only an
9
+ * admitted Computer build executes it.
10
+ */
11
+ export const workspaceBuildStepSchema = Schema.Union([
12
+ Schema.Struct({
13
+ kind: Schema.Literal("typescript"),
14
+ config: Schema.String,
15
+ }),
16
+ Schema.Struct({
17
+ kind: Schema.Literal("tailwind"),
18
+ input: Schema.String,
19
+ output: Schema.String,
20
+ }),
21
+ Schema.Struct({
22
+ kind: Schema.Literal("script"),
23
+ directory: Schema.String,
24
+ script: Schema.String,
25
+ }),
26
+ ]);
27
+ /** The steps one dependency-build process runs, encoded as one argument. */
28
+ export const workspaceBuildStepsJsonSchema = Schema.fromJsonString(Schema.Array(workspaceBuildStepSchema));
@@ -0,0 +1,7 @@
1
+ import type { PinnedRecipe, RawBuildOutput } from "./effect.js";
2
+ /** Read only regular files under the captured entry's directory. No scripts,
3
+ * dependency hydration, URL rewriting, or access to the producer's live build.
4
+ */
5
+ export declare function readBundledHtml(snapshot: string, recipe: PinnedRecipe): Promise<Extract<RawBuildOutput, {
6
+ kind: "assets";
7
+ }>>;
@@ -0,0 +1,64 @@
1
+ import { lstat, opendir, readFile } from "node:fs/promises";
2
+ import { dirname, extname, join } from "node:path";
3
+ import * as Schema from "effect/Schema";
4
+ import { assetPathSchema } from "@fungi.computer/puffball/assets";
5
+ import { boundedBundlePolicy, bundledHtmlRecipe } from "./html-recipe.js";
6
+ /** Read only regular files under the captured entry's directory. No scripts,
7
+ * dependency hydration, URL rewriting, or access to the producer's live build.
8
+ */
9
+ export async function readBundledHtml(snapshot, recipe) {
10
+ const policy = recipe.assets;
11
+ if (!policy ||
12
+ !recipe.assetBase ||
13
+ !recipe.entry.endsWith("/index.html") ||
14
+ !boundedBundlePolicy(policy, recipe.maxOutputBytes))
15
+ throw new Error("Invalid bundled HTML policy");
16
+ // Check every ancestor before descending: a symlink is not a bundle root.
17
+ const parts = dirname(recipe.entry).split("/");
18
+ let root = snapshot;
19
+ for (const part of parts) {
20
+ if (!part || part === "." || part === ".." || part.includes("\\"))
21
+ throw new Error("Invalid bundle directory");
22
+ root = join(root, part);
23
+ if (!(await lstat(root)).isDirectory())
24
+ throw new Error("Bundle directory must be a captured directory");
25
+ }
26
+ const assets = [];
27
+ let total = 0;
28
+ let entries = 0;
29
+ const visit = async (directory, prefix) => {
30
+ const children = await opendir(directory);
31
+ for await (const child of children) {
32
+ // Bound traversal too, including empty directories.
33
+ if (++entries > policy.maxObjects * 2)
34
+ throw new Error("Bundle directory count exceeds policy");
35
+ const path = Schema.decodeUnknownSync(assetPathSchema)(`${prefix}/${child.name}`);
36
+ const source = join(directory, child.name);
37
+ if (child.isDirectory()) {
38
+ await visit(source, path);
39
+ continue;
40
+ }
41
+ if (!child.isFile() ||
42
+ !bundledHtmlRecipe.extensions.includes(extname(child.name)))
43
+ throw new Error("Bundle contains an unsupported file type");
44
+ if (assets.length >= policy.maxObjects)
45
+ throw new Error("Bundle file count exceeds policy");
46
+ const stat = await lstat(source);
47
+ total += stat.size;
48
+ if (!stat.isFile() ||
49
+ stat.size > policy.maxObjectBytes ||
50
+ !Number.isSafeInteger(total) ||
51
+ total > policy.maxTotalBytes)
52
+ throw new Error("Bundle bytes exceed policy");
53
+ const bytes = await readFile(source);
54
+ if (bytes.byteLength !== stat.size)
55
+ throw new Error("Bundle changed during capture");
56
+ assets.push({ path, bytes });
57
+ }
58
+ };
59
+ await visit(root, "");
60
+ if (!assets.some((asset) => asset.path === "/index.html"))
61
+ throw new Error("Bundle is missing its HTML entry");
62
+ assets.sort((a, b) => a.path.localeCompare(b.path));
63
+ return { kind: "assets", base: recipe.assetBase, assets };
64
+ }
@@ -0,0 +1,68 @@
1
+ import * as Schema from "effect/Schema";
2
+ export declare class TeamCandidateBuildError extends Error {
3
+ readonly code: "invalid_recipe" | "output_limit" | "report_failed";
4
+ constructor(code: "invalid_recipe" | "output_limit" | "report_failed");
5
+ }
6
+ export declare const teamCandidateConfigurationSchema: Schema.Struct<{
7
+ /** The build policy the host expects; a builder that predates it refuses
8
+ * the request, and this one refuses any other. */
9
+ readonly buildPolicy: Schema.Literal<"package-json-fungi-build-v2">;
10
+ readonly appId: Schema.refine<import("@fungi.computer/puffball").AppId, Schema.String>;
11
+ readonly sourceRevision: Schema.refine<import("@fungi.computer/puffball").SourceRevision, Schema.String>;
12
+ readonly recipe: Schema.Struct<{
13
+ readonly kind: Schema.optional<Schema.Literals<readonly ["bundled-html", "source-html"]>>;
14
+ readonly bundle: Schema.Literal<true>;
15
+ readonly entry: Schema.String;
16
+ readonly format: Schema.Literals<readonly ["html", "esm", "assets"]>;
17
+ readonly assets: Schema.optional<Schema.Struct<{
18
+ readonly maxManifestBytes: Schema.Number;
19
+ readonly maxObjects: Schema.Number;
20
+ readonly maxObjectBytes: Schema.Number;
21
+ readonly maxTotalBytes: Schema.Number;
22
+ }>>;
23
+ readonly assetBase: Schema.optionalKey<Schema.String>;
24
+ readonly toolchain: Schema.$Record<Schema.String, Schema.String>;
25
+ readonly maxOutputBytes: Schema.Number;
26
+ readonly platform: Schema.Literals<readonly ["browser", "neutral"]>;
27
+ readonly backend: Schema.Union<readonly [Schema.Null, Schema.Struct<{
28
+ readonly entry: Schema.optional<Schema.String>;
29
+ readonly runtime: Schema.Struct<{
30
+ readonly kind: Schema.Literal<"durable-facet">;
31
+ readonly stateSchema: Schema.Number;
32
+ }>;
33
+ readonly compatibilityDate: Schema.String;
34
+ readonly exportName: Schema.String;
35
+ }>]>;
36
+ readonly target: Schema.Tuple<readonly [Schema.Literal<"es2022">]>;
37
+ }>;
38
+ readonly buildRecipeDigest: Schema.refine<import("@fungi.computer/puffball").Sha256Digest, Schema.String>;
39
+ }>;
40
+ /** Where a built candidate's manifest and its objects are written. */
41
+ export declare function teamCandidateLayout(outputPath: string): {
42
+ manifest: string;
43
+ object: (digest: string) => string;
44
+ };
45
+ /**
46
+ * Runs inside the admitted Computer, using Oyster's captured builder.
47
+ * This is the one composition that runs a dependency's declared package
48
+ * build script; its executor refuses a recipe pinned under another policy.
49
+ * It writes a new directory: each built object under its digest, and the
50
+ * small manifest that names them, ready to upload one at a time.
51
+ */
52
+ export declare function buildTeamCandidate(input: {
53
+ configuration: unknown;
54
+ repositoryPath: string;
55
+ /** A directory this build creates; it must not exist yet. */
56
+ outputPath: string;
57
+ /** Trusted Computer-local cache; never part of customer configuration. */
58
+ packageStoreDirectory?: string;
59
+ }): Promise<Readonly<{
60
+ digest: string;
61
+ bytes: number;
62
+ /** Milliseconds: capturing the cloned source, installing, compiling. */
63
+ phases: Readonly<{
64
+ capture: number;
65
+ install: number;
66
+ build: number;
67
+ }>;
68
+ }>>;
@@ -0,0 +1,91 @@
1
+ import { createHash } from "node:crypto";
2
+ import { mkdir, writeFile } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import * as Effect from "effect/Effect";
5
+ import * as Schema from "effect/Schema";
6
+ import { appIdSchema, sourceRevisionSchema, sha256DigestSchema, } from "@fungi.computer/puffball/effect";
7
+ import { parseReleaseId } from "@fungi.computer/puffball";
8
+ import { capturedPinnedRecipeSchema, createCapturedCandidatePreparer, digestPinnedRecipe, encodeTeamAppCandidateManifest, TEAM_APP_CANDIDATE_MANIFEST_MAX_BYTES, workspaceBuildPolicy, } from "../build-contract.js";
9
+ import { createLocalCommitSourceCapture, createLocalBuildExecutor, stageBuildOutput, } from "../build.js";
10
+ export class TeamCandidateBuildError extends Error {
11
+ code;
12
+ constructor(code) {
13
+ super(`Team candidate ${code}`);
14
+ this.code = code;
15
+ this.name = "TeamCandidateBuildError";
16
+ }
17
+ }
18
+ export const teamCandidateConfigurationSchema = Schema.Struct({
19
+ /** The build policy the host expects; a builder that predates it refuses
20
+ * the request, and this one refuses any other. */
21
+ buildPolicy: Schema.Literal(workspaceBuildPolicy),
22
+ appId: appIdSchema,
23
+ sourceRevision: sourceRevisionSchema,
24
+ recipe: capturedPinnedRecipeSchema,
25
+ buildRecipeDigest: sha256DigestSchema,
26
+ });
27
+ /** Where a built candidate's manifest and its objects are written. */
28
+ export function teamCandidateLayout(outputPath) {
29
+ return {
30
+ manifest: join(outputPath, "manifest.json"),
31
+ object: (digest) => join(outputPath, "objects", digest.replace(":", "-")),
32
+ };
33
+ }
34
+ /**
35
+ * Runs inside the admitted Computer, using Oyster's captured builder.
36
+ * This is the one composition that runs a dependency's declared package
37
+ * build script; its executor refuses a recipe pinned under another policy.
38
+ * It writes a new directory: each built object under its digest, and the
39
+ * small manifest that names them, ready to upload one at a time.
40
+ */
41
+ export async function buildTeamCandidate(input) {
42
+ const config = Schema.decodeUnknownSync(teamCandidateConfigurationSchema, {
43
+ onExcessProperty: "error",
44
+ })(input.configuration);
45
+ if (digestPinnedRecipe(config.recipe) !== config.buildRecipeDigest)
46
+ throw new TeamCandidateBuildError("invalid_recipe");
47
+ const phases = { install: 0, build: 0 };
48
+ const onPhase = (phase, milliseconds) => {
49
+ phases[phase] += milliseconds;
50
+ };
51
+ const prepareStarted = performance.now();
52
+ const candidate = await Effect.runPromise(createCapturedCandidatePreparer(config, {
53
+ sourceCapture: createLocalCommitSourceCapture(input.repositoryPath, config.sourceRevision),
54
+ buildExecutor: createLocalBuildExecutor(input.packageStoreDirectory === undefined
55
+ ? { packageScripts: "run", onPhase }
56
+ : {
57
+ packageScripts: "run",
58
+ packageStoreDirectory: input.packageStoreDirectory,
59
+ onPhase,
60
+ }),
61
+ }).prepareCandidate(parseReleaseId("rel_build")));
62
+ const prepared = performance.now() - prepareStarted;
63
+ const staged = stageBuildOutput(candidate.output);
64
+ const bytes = Buffer.from(encodeTeamAppCandidateManifest({
65
+ appId: candidate.appId,
66
+ output: staged.manifest,
67
+ provenance: candidate.provenance,
68
+ }));
69
+ if (bytes.byteLength > TEAM_APP_CANDIDATE_MANIFEST_MAX_BYTES)
70
+ throw new TeamCandidateBuildError("output_limit");
71
+ const layout = teamCandidateLayout(input.outputPath);
72
+ // A new directory: an earlier build's output is never mixed in.
73
+ await mkdir(input.outputPath, { mode: 0o700 });
74
+ await mkdir(join(input.outputPath, "objects"), { mode: 0o700 });
75
+ for (const object of staged.objects)
76
+ await writeFile(layout.object(object.digest), object.bytes, {
77
+ flag: "wx",
78
+ mode: 0o600,
79
+ });
80
+ await writeFile(layout.manifest, bytes, { flag: "wx", mode: 0o600 });
81
+ return {
82
+ digest: `sha256:${createHash("sha256").update(bytes).digest("hex")}`,
83
+ bytes: bytes.byteLength,
84
+ phases: {
85
+ // Snapshot capture and staging: everything but the install and compile.
86
+ capture: Math.max(0, prepared - phases.install - phases.build),
87
+ install: phases.install,
88
+ build: phases.build,
89
+ },
90
+ };
91
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,119 @@
1
+ import * as Schema from "effect/Schema";
2
+ import { releaseSchema } from "@fungi.computer/puffball/effect";
3
+ import { readFile } from "node:fs/promises";
4
+ import { candidateManifestObjects, teamAppCandidateManifestSchema, } from "../build-contract.js";
5
+ import { buildTeamCandidate, teamCandidateConfigurationSchema, teamCandidateLayout, TeamCandidateBuildError, } from "./candidate-builder.js";
6
+ /** Objects in flight at once; each is read from disk only while it uploads. */
7
+ const uploadConcurrency = 4;
8
+ /** Attempts per object when its connection drops before a response. */
9
+ const uploadAttempts = 3;
10
+ /** Report a failed build to the host's build output: its kind, never its text. */
11
+ function reportFailure(cause) {
12
+ process.stderr.write(`${JSON.stringify({
13
+ source: "oyster-candidate-builder",
14
+ failureKind: cause instanceof Error ? "Error" : "NonErrorThrown",
15
+ })}\n`);
16
+ }
17
+ const [configuration, repositoryPath, outputPath] = process.argv.slice(2);
18
+ if (!configuration || !repositoryPath || !outputPath)
19
+ throw new Error("Usage: team-candidate-builder CONFIG_BASE64 REPOSITORY OUTPUT_DIRECTORY");
20
+ try {
21
+ const { uploadGrant, ...config } = Schema.decodeUnknownSync(Schema.Struct({
22
+ ...teamCandidateConfigurationSchema.fields,
23
+ uploadGrant: Schema.String,
24
+ }), { onExcessProperty: "error" })(JSON.parse(Buffer.from(configuration, "base64url").toString("utf8")));
25
+ const { phases, ...artifact } = await buildTeamCandidate({
26
+ configuration: config,
27
+ repositoryPath,
28
+ outputPath,
29
+ // Computer scratch survives captured snapshot cleanup, without a global or durable Workspace store.
30
+ packageStoreDirectory: "/var/tmp/fungi-app-package-store",
31
+ });
32
+ const reportUrl = process.env.TEAM_BUILD_REPORT;
33
+ if (!reportUrl)
34
+ throw new TeamCandidateBuildError("report_failed");
35
+ const layout = teamCandidateLayout(outputPath);
36
+ const manifestBytes = await readFile(layout.manifest);
37
+ const manifest = Schema.decodeUnknownSync(Schema.fromJsonString(teamAppCandidateManifestSchema))(new TextDecoder().decode(manifestBytes));
38
+ // Every object first, each streamed alone; the manifest only names them.
39
+ const uploadStarted = performance.now();
40
+ const pending = [...candidateManifestObjects(manifest.output)];
41
+ // A write-once PUT is safe to repeat, so a dropped connection is retried.
42
+ const put = async (object) => {
43
+ for (let attempt = 1;; attempt += 1) {
44
+ const stored = await fetch(`${reportUrl}/objects/${object.digest.replace(":", "-")}`, {
45
+ method: "PUT",
46
+ headers: {
47
+ "Content-Type": object.mediaType,
48
+ "X-Team-App-Build": uploadGrant,
49
+ },
50
+ body: await readFile(layout.object(object.digest)),
51
+ }).catch((cause) => {
52
+ if (attempt >= uploadAttempts)
53
+ throw cause;
54
+ return null;
55
+ });
56
+ await stored?.body?.cancel();
57
+ if (stored?.ok)
58
+ return;
59
+ if (stored !== null || attempt >= uploadAttempts)
60
+ throw new TeamCandidateBuildError("report_failed");
61
+ }
62
+ };
63
+ const upload = async () => {
64
+ for (let object = pending.shift(); object; object = pending.shift())
65
+ await put(object);
66
+ };
67
+ await Promise.all(Array.from({ length: uploadConcurrency }, upload));
68
+ const candidate = await fetch(`${reportUrl}/candidate`, {
69
+ method: "POST",
70
+ headers: {
71
+ "Content-Type": "application/json",
72
+ "X-Team-App-Build": uploadGrant,
73
+ },
74
+ body: manifestBytes,
75
+ });
76
+ if (!candidate.ok)
77
+ throw new TeamCandidateBuildError("report_failed");
78
+ const receipt = Schema.decodeUnknownSync(Schema.Struct({
79
+ kind: Schema.Literal("submitted"),
80
+ release: releaseSchema,
81
+ }))(await candidate.json());
82
+ const recorded = await fetch(`${reportUrl}/artifact`, {
83
+ method: "POST",
84
+ headers: { "Content-Type": "application/json" },
85
+ body: JSON.stringify(artifact),
86
+ });
87
+ if (!recorded.ok)
88
+ throw new TeamCandidateBuildError("report_failed");
89
+ await recorded.body?.cancel();
90
+ const uploaded = performance.now() - uploadStarted;
91
+ // The success detail reports where the build's time went; the owner reads
92
+ // it with the build. The clone ran before this program (TEAM_BUILD_CLONE_MS).
93
+ const cloned = Number(process.env.TEAM_BUILD_CLONE_MS ?? "0");
94
+ const status = await fetch(`${reportUrl}/status`, {
95
+ method: "POST",
96
+ headers: { "Content-Type": "application/json" },
97
+ body: JSON.stringify({
98
+ status: "succeeded",
99
+ detail: JSON.stringify({
100
+ phasesMs: {
101
+ clone: Math.round((Number.isFinite(cloned) ? cloned : 0) + phases.capture),
102
+ install: Math.round(phases.install),
103
+ build: Math.round(phases.build),
104
+ upload: Math.round(uploaded),
105
+ },
106
+ }),
107
+ }),
108
+ });
109
+ if (!status.ok)
110
+ throw new TeamCandidateBuildError("report_failed");
111
+ await status.body?.cancel();
112
+ process.stdout.write(JSON.stringify({ ...artifact, release: receipt.release }));
113
+ }
114
+ catch (cause) {
115
+ reportFailure(cause);
116
+ // Customer source and provider messages can contain secrets; only expose the stage.
117
+ process.stderr.write("Team App source/build failed\n");
118
+ process.exitCode = 1;
119
+ }