hammerkit 1.27.0-next.9 → 2.0.0-next.2

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 (122) hide show
  1. package/build.schema.json +176 -6
  2. package/dist/cache/backends/local-cache-backend.js +6 -0
  3. package/dist/cache/backends/registry/registry-client.js +1 -0
  4. package/dist/cache/cache-sync.js +1 -1
  5. package/dist/cache/explain.js +17 -0
  6. package/dist/cli.js +2 -0
  7. package/dist/dashboard/assets/index-CjnKm5-3.css +2 -0
  8. package/dist/dashboard/assets/index-zPXa4klZ.js +13 -0
  9. package/dist/dashboard/favicon.svg +1 -0
  10. package/dist/dashboard/index.html +15 -0
  11. package/dist/docker/item-network.js +13 -5
  12. package/dist/executer/archive-task-entry.js +4 -2
  13. package/dist/executer/deadline.js +17 -3
  14. package/dist/executer/deferred-restore.js +36 -0
  15. package/dist/executer/docker-service.js +30 -8
  16. package/dist/executer/docker-task.js +21 -1
  17. package/dist/executer/execute-work-service.js +2 -0
  18. package/dist/executer/execute-work-task.js +11 -4
  19. package/dist/executer/execution-steps.js +25 -4
  20. package/dist/executer/reset-work-tree.js +4 -0
  21. package/dist/executer/scheduler/enqueue-next.js +57 -6
  22. package/dist/executer/scheduler/state-key.js +42 -5
  23. package/dist/executer/watch-loop.js +6 -3
  24. package/dist/harness/connect.js +20 -6
  25. package/dist/harness/mcp-command.js +22 -6
  26. package/dist/harness/mcp.js +63 -114
  27. package/dist/harness/opencode.js +158 -50
  28. package/dist/harness/pi.js +29 -0
  29. package/dist/harness/sandbox-client.js +194 -23
  30. package/dist/harness/service-tools.js +141 -0
  31. package/dist/harness/task-tools.js +221 -0
  32. package/dist/kubernetes/apply.js +49 -1
  33. package/dist/kubernetes/await-running-state.js +172 -13
  34. package/dist/kubernetes/cluster-state.js +245 -0
  35. package/dist/kubernetes/ensure-kubernetes-deployment-exists.js +133 -9
  36. package/dist/kubernetes/ensure-kubernetes-service-exists.js +2 -0
  37. package/dist/kubernetes/ensure-persistent-data.js +211 -6
  38. package/dist/kubernetes/follow-pod-logs.js +182 -0
  39. package/dist/kubernetes/job-placement.js +36 -12
  40. package/dist/kubernetes/kubernetes-instance.js +1 -0
  41. package/dist/kubernetes/network-policy.js +2 -1
  42. package/dist/kubernetes/remove-stale-items.js +46 -0
  43. package/dist/kubernetes/restore-kubernetes-data.js +9 -5
  44. package/dist/kubernetes/sandbox-runner.js +628 -0
  45. package/dist/kubernetes/service-rollouts.js +74 -0
  46. package/dist/kubernetes/store-kubernetes-data.js +5 -2
  47. package/dist/kubernetes/volumes.js +94 -9
  48. package/dist/main.js +2 -0
  49. package/dist/optimizer/calculate-checksum.js +3 -2
  50. package/dist/optimizer/check-key.js +94 -0
  51. package/dist/optimizer/check-keys.js +64 -0
  52. package/dist/optimizer/get-work-cache-stats.js +17 -3
  53. package/dist/optimizer/work-task-cache-description.js +49 -1
  54. package/dist/planner/service-definition.js +9 -4
  55. package/dist/planner/utils/append-work-dependencies.js +5 -3
  56. package/dist/planner/utils/append-work-service.js +18 -1
  57. package/dist/planner/utils/append-work-task.js +13 -3
  58. package/dist/planner/utils/get-work-runtime.js +6 -1
  59. package/dist/planner/utils/normalize-path.js +19 -1
  60. package/dist/planner/utils/parse-work-mount.js +7 -1
  61. package/dist/planner/utils/parse-work-port.js +4 -1
  62. package/dist/planner/validate.js +22 -0
  63. package/dist/planner/work-network.js +3 -1
  64. package/dist/planner/work-runtime-aggregate.js +32 -0
  65. package/dist/planner/work-runtime-kubernetes.js +136 -13
  66. package/dist/planner/work-task.js +6 -1
  67. package/dist/program.js +27 -9
  68. package/dist/remote/device-login.js +7 -2
  69. package/dist/remote/host-config.js +69 -3
  70. package/dist/remote/remote-commands.js +33 -7
  71. package/dist/remote/remote-status.js +112 -0
  72. package/dist/remote/remote-work-commands.js +562 -45
  73. package/dist/remote/server-client.js +15 -2
  74. package/dist/remote/token-source.js +64 -26
  75. package/dist/run-program.js +9 -0
  76. package/dist/schema/build-file-base-task-schema.js +3 -3
  77. package/dist/schema/build-file-container-service-schema.js +10 -0
  78. package/dist/schema/build-file-container-task-schema.js +16 -1
  79. package/dist/schema/build-file-include-schema.js +18 -4
  80. package/dist/schema/build-file-kubernetes-service-schema.js +2 -0
  81. package/dist/schema/build-file-task-schema.js +5 -1
  82. package/dist/schema/capabilities-schema.js +56 -0
  83. package/dist/schema/reference-parser.js +119 -11
  84. package/dist/schema/removed-properties.js +20 -0
  85. package/dist/schema/schema-parser.js +94 -41
  86. package/dist/schema/work-scope-parser.js +33 -10
  87. package/dist/secrets/command-secret-provider.js +3 -1
  88. package/dist/secrets/github-app-token-command.js +3 -1
  89. package/dist/secrets/secret-catalog.js +43 -19
  90. package/dist/server/accounts-api.js +4 -1
  91. package/dist/server/approvals-api.js +1 -1
  92. package/dist/server/capacity-format.js +36 -0
  93. package/dist/server/capacity.js +300 -0
  94. package/dist/server/checks-api.js +83 -0
  95. package/dist/server/checks.js +116 -0
  96. package/dist/server/dashboard.js +196 -0
  97. package/dist/server/definitions-api.js +66 -3
  98. package/dist/server/endpoints.js +794 -0
  99. package/dist/server/executor.js +164 -9
  100. package/dist/server/kubernetes-backend.js +415 -48
  101. package/dist/server/kubernetes-sandbox-storage.js +46 -2
  102. package/dist/server/overview.js +271 -0
  103. package/dist/server/plan.js +170 -121
  104. package/dist/server/policy.js +34 -2
  105. package/dist/server/proposals-api.js +17 -8
  106. package/dist/server/repo-access.js +5 -0
  107. package/dist/server/run-logs.js +4 -0
  108. package/dist/server/run-plan.js +38 -4
  109. package/dist/server/run-request.js +58 -3
  110. package/dist/server/run-worker.js +262 -13
  111. package/dist/server/runs.js +962 -36
  112. package/dist/server/sandbox-pod.js +28 -0
  113. package/dist/server/sandbox-storage.js +23 -0
  114. package/dist/server/sandboxes.js +8 -0
  115. package/dist/server/server-config.js +246 -10
  116. package/dist/server/server.js +429 -32
  117. package/dist/server/service-runs.js +82 -0
  118. package/dist/server/timings.js +132 -0
  119. package/dist/server/wait-reason.js +21 -0
  120. package/dist/utils/tar.js +28 -1
  121. package/dist/utils/terminal-prompt.js +53 -0
  122. package/package.json +8 -2
@@ -11,6 +11,7 @@ const tar_2 = require("../utils/tar");
11
11
  const apply_1 = require("../kubernetes/apply");
12
12
  const kubernetes_instance_1 = require("../kubernetes/kubernetes-instance");
13
13
  const network_policy_1 = require("../kubernetes/network-policy");
14
+ const sandbox_runner_1 = require("../kubernetes/sandbox-runner");
14
15
  const volumes_1 = require("../kubernetes/volumes");
15
16
  const log_1 = require("../log");
16
17
  const sleep_1 = require("../utils/sleep");
@@ -260,6 +261,41 @@ class KubernetesSandboxStorage {
260
261
  await (0, promises_1.rm)(repository, { recursive: true, force: true });
261
262
  }
262
263
  }
264
+ // The pod writes the tree's files beside the sandbox on the claim and sends them as a tar,
265
+ // which is extracted here as the files of `attach` are: what a run plans with, up to the
266
+ // bytes a sandbox may take.
267
+ async exportTree(key, tree, into, signal) {
268
+ await (0, promises_1.mkdir)(into, { recursive: true });
269
+ let bytes = 0;
270
+ const extract = (0, tar_1.extract)({
271
+ cwd: into,
272
+ filter: (_path, entry) => 'type' in entry && ['File', 'OldFile', 'Directory', 'SymbolicLink'].includes(entry.type),
273
+ });
274
+ const counted = new stream_1.PassThrough();
275
+ counted.on('data', (chunk) => {
276
+ bytes += chunk.length;
277
+ if (bytes > this.limits.bytes) {
278
+ counted.destroy(new Error(`the files of the sandbox ${key.id} take more than ${(0, units_1.formatSize)(this.limits.bytes)}`));
279
+ }
280
+ });
281
+ const extracted = new Promise((resolve, reject) => {
282
+ extract.on('finish', resolve);
283
+ extract.on('error', reject);
284
+ counted.on('error', reject);
285
+ });
286
+ extracted.catch(() => undefined);
287
+ counted.pipe(extract);
288
+ const answer = await this.exclusive(key.id, () => this.call(key, { op: 'archive', id: key.id, tree }, signal, true));
289
+ if ('stream' in answer) {
290
+ // a pipeline, so the answer is ended with the copy: an error of the pod after the copy
291
+ // stopped (over the size limit) lands nowhere else
292
+ await (0, promises_2.pipeline)(answer.stream, counted);
293
+ }
294
+ else {
295
+ counted.end(answer.body);
296
+ }
297
+ await extracted;
298
+ }
263
299
  async moveTo(key, commit) {
264
300
  await this.request(key, { op: 'move', id: key.id, commit }, AbortSignal.timeout(FETCH_TIMEOUT_MS));
265
301
  }
@@ -284,6 +320,8 @@ class KubernetesSandboxStorage {
284
320
  // runs on a sandbox go one at a time: a proxy the pod of the files does not own was
285
321
  // left by a run before (the server stopped, or its release could not remove it)
286
322
  await (0, network_policy_1.removeSandboxProxies)(this.instance, namespace, { sandbox: names.label }, (deployment) => isOwnedBy(deployment, pod.uid)).catch(() => undefined);
323
+ // and a runner, which the pod did not own when it made way for a run
324
+ await (0, sandbox_runner_1.removeSandboxRunners)(this.instance, namespace, names.label, (runner) => isOwnedBy(runner, pod.uid)).catch(() => undefined);
287
325
  await (0, promises_1.mkdir)(into, { recursive: true });
288
326
  await this.copyOut(key, ['tar', 'cf', '-', '-C', `${CLAIM_PATH}/${key.id}/files`, '.'], into,
289
327
  // what the run plans with: files, directories and links, nothing outside of it
@@ -388,7 +426,8 @@ class KubernetesSandboxStorage {
388
426
  async removeIn(namespace, id) {
389
427
  const names = getSandboxNames(id);
390
428
  await this.stopPod({ id, owner: { iss: '', sub: '' } }, namespace);
391
- // the cluster removes the proxies the pod owned; those it did not own go here
429
+ // the cluster removes the proxies and the runner the pod owned; those it did not own go here
430
+ await (0, sandbox_runner_1.removeSandboxRunners)(this.instance, namespace, names.label);
392
431
  await (0, network_policy_1.removeSandboxProxies)(this.instance, namespace, { sandbox: names.label });
393
432
  for (const name of [names.claim, names.git]) {
394
433
  try {
@@ -502,7 +541,7 @@ class KubernetesSandboxStorage {
502
541
  const reader = new AnswerReader(body);
503
542
  // the process ends when the server stops waiting for it: closing the connection does
504
543
  // not end a command in a pod
505
- const seconds = Math.ceil((['create', 'fetch', 'move'].includes(request.op) ? FETCH_TIMEOUT_MS : REQUEST_TIMEOUT_MS) / 1000);
544
+ const seconds = Math.ceil((['create', 'fetch', 'move', 'archive'].includes(request.op) ? FETCH_TIMEOUT_MS : REQUEST_TIMEOUT_MS) / 1000);
506
545
  const done = this.withPod(key, (pod) => this.exec(pod, ['timeout', String(seconds), 'hammerkit', 'server-sandbox', CLAIM_PATH], stream_1.Readable.from([Buffer.from(JSON.stringify(encoded))]), reader, signal));
507
546
  done.then(() => reader.complete(), (e) => reader.fail(e));
508
547
  const answer = await reader.answer;
@@ -885,6 +924,11 @@ class AnswerReader extends stream_1.Writable {
885
924
  }
886
925
  _write(chunk, _encoding, callback) {
887
926
  if (this.stream) {
927
+ if (this.stream.destroyed) {
928
+ // its reader stopped (over a size limit): the rest of the answer is nobody's
929
+ callback();
930
+ return;
931
+ }
888
932
  this.stream.write(chunk, callback);
889
933
  return;
890
934
  }
@@ -0,0 +1,271 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.visibleCapacity = exports.filterRuns = exports.FilterRefused = exports.parseRunFilter = exports.getOverview = exports.queuePlace = exports.visibleReason = exports.toListedRun = exports.mayRead = exports.seesActivityOf = exports.seesActivity = exports.maySee = exports.decides = exports.owns = exports.mayDecide = exports.runActions = exports.getViewer = exports.describeWait = void 0;
4
+ const policy_1 = require("./policy");
5
+ const runs_1 = require("./runs");
6
+ const service_runs_1 = require("./service-runs");
7
+ const repo_1 = require("./repo");
8
+ var wait_reason_1 = require("./wait-reason");
9
+ Object.defineProperty(exports, "describeWait", { enumerable: true, get: function () { return wait_reason_1.describeWait; } });
10
+ function getViewer(config, principal) {
11
+ return {
12
+ principal,
13
+ grant: (0, policy_1.getGrant)(config.entitlements, principal),
14
+ activity: config.runs.activity === 'everyone',
15
+ };
16
+ }
17
+ exports.getViewer = getViewer;
18
+ function runActions(viewer, run) {
19
+ if ((0, runs_1.isFinal)(run.state)) {
20
+ return undefined;
21
+ }
22
+ const mine = owns(viewer, run);
23
+ const may = {
24
+ ...(mine || decides(viewer, run) ? { stop: true } : {}),
25
+ ...(run.up && run.upAt && (mine || (0, service_runs_1.mayOpenEndpoints)(viewer.grant, run)) ? { extend: true } : {}),
26
+ ...(mayDecide(viewer, run) ? { decide: true } : {}),
27
+ };
28
+ return Object.keys(may).length > 0 ? may : undefined;
29
+ }
30
+ exports.runActions = runActions;
31
+ // A run that waits for an approval the caller may give: not their own.
32
+ function mayDecide(viewer, run) {
33
+ return (run.state === 'awaiting-approval' &&
34
+ !owns(viewer, run) &&
35
+ (run.approvals ?? []).some((approval) => !approval.approvedBy && (0, policy_1.canUse)(viewer.grant, approval.account)));
36
+ }
37
+ exports.mayDecide = mayDecide;
38
+ function owns(viewer, run) {
39
+ return run.owner.iss === viewer.principal.iss && run.owner.sub === viewer.principal.sub;
40
+ }
41
+ exports.owns = owns;
42
+ // Whether the caller decides about the run: they use an account it asked to be approved.
43
+ function decides(viewer, run) {
44
+ return (run.approvals ?? []).some((approval) => (0, policy_1.canUse)(viewer.grant, approval.account));
45
+ }
46
+ exports.decides = decides;
47
+ /**
48
+ * Whether the caller may see a run, with the entitlements of the moment: their own; any
49
+ * with `observe`; one that asked for an approval of an account they use (they decide about
50
+ * it); a service run whose endpoints they may open; and with `runs.activity: everyone`, one
51
+ * of a repository they have access to (seesActivityOf).
52
+ */
53
+ function maySee(viewer, run) {
54
+ return (owns(viewer, run) ||
55
+ viewer.grant.observe ||
56
+ decides(viewer, run) ||
57
+ (!!run.up && (0, service_runs_1.mayOpenEndpoints)(viewer.grant, run)) ||
58
+ seesActivityOf(viewer, run));
59
+ }
60
+ exports.maySee = maySee;
61
+ /**
62
+ * With `runs.activity: everyone`, a caller with a `builds` level sees the runs of others in
63
+ * the repositories they have access to: the `repos` and the `sandboxes` repositories of their
64
+ * entitlements. Who may only open endpoints or read checks sees none, and a run of uploaded
65
+ * files has no repository: it is for its owner, who decides about it and `observe`.
66
+ */
67
+ function seesActivity(viewer) {
68
+ return !!viewer.activity && viewer.grant.builds !== null;
69
+ }
70
+ exports.seesActivity = seesActivity;
71
+ function seesActivityOf(viewer, run) {
72
+ if (!seesActivity(viewer) || run.source.type === 'upload') {
73
+ return false;
74
+ }
75
+ const repo = run.source.repo;
76
+ return ((0, repo_1.matchesAnyRepo)(viewer.grant.repos, repo) ||
77
+ viewer.grant.sandboxes.some((sandbox) => (0, repo_1.matchesRepo)(sandbox.repo, repo)));
78
+ }
79
+ exports.seesActivityOf = seesActivityOf;
80
+ /**
81
+ * Whether the caller may read a run whole: its tasks, why it failed and its log. Its owner,
82
+ * who decides about it, and `observe`.
83
+ */
84
+ function mayRead(viewer, run) {
85
+ return owns(viewer, run) || viewer.grant.observe || decides(viewer, run);
86
+ }
87
+ exports.mayRead = mayRead;
88
+ function toListedRun(viewer, run, queue) {
89
+ const { owner, results, plan, options, up, error, ...rest } = run;
90
+ const mine = owns(viewer, run);
91
+ const may = runActions(viewer, run);
92
+ const opens = mine || viewer.grant.observe || (!!up && (0, service_runs_1.mayOpenEndpoints)(viewer.grant, run));
93
+ const timings = Object.fromEntries(Object.entries(results ?? {}).map(([task, result]) => [
94
+ task,
95
+ {
96
+ status: result.status,
97
+ ...(result.startedAt ? { startedAt: result.startedAt } : {}),
98
+ ...(result.finishedAt ? { finishedAt: result.finishedAt } : {}),
99
+ ...(result.durationMs !== undefined ? { durationMs: result.durationMs } : {}),
100
+ ...(result.cache ? { cached: result.cache.hit } : {}),
101
+ },
102
+ ]));
103
+ return {
104
+ ...rest,
105
+ ...(mine ? { plan, options } : { owner: { iss: owner.iss, sub: owner.sub } }),
106
+ ...(up
107
+ ? {
108
+ up: {
109
+ ...up,
110
+ endpoints: opens ? up.endpoints : up.endpoints.map(({ service, port }) => ({ service, port })),
111
+ },
112
+ }
113
+ : {}),
114
+ ...(error !== undefined && mayRead(viewer, run) ? { error } : {}),
115
+ ...(Object.keys(timings).length > 0 ? { timings } : {}),
116
+ ...(queue ? { queue } : {}),
117
+ ...(may ? { may } : {}),
118
+ ...(!mine && mayRead(viewer, run) ? { readable: true } : {}),
119
+ };
120
+ }
121
+ exports.toListedRun = toListedRun;
122
+ // A reason names a run only the caller may see.
123
+ function visibleReason(reason, visible) {
124
+ if ((reason.type === 'sandbox' || reason.type === 'held') && !visible(reason.run)) {
125
+ return { type: reason.type };
126
+ }
127
+ return { ...reason };
128
+ }
129
+ exports.visibleReason = visibleReason;
130
+ // Where a queued run stands, and why it waits: the run that holds it is named when the
131
+ // caller may see it.
132
+ function queuePlace(registry, viewer, run) {
133
+ const queue = run.state === 'queued' ? registry.queued(run.id) : undefined;
134
+ if (!queue) {
135
+ return undefined;
136
+ }
137
+ const visible = (id) => {
138
+ const holder = registry.peek(id);
139
+ return !!holder && maySee(viewer, holder);
140
+ };
141
+ return { position: queue.position, waitsFor: visibleReason(queue.waitsFor, visible) };
142
+ }
143
+ exports.queuePlace = queuePlace;
144
+ function getOverview(snapshot, usage, viewer) {
145
+ const runs = new Map();
146
+ for (const run of [
147
+ ...snapshot.running,
148
+ ...snapshot.queue.map((entry) => entry.run),
149
+ ...snapshot.awaiting.runs,
150
+ ...snapshot.services.runs,
151
+ ]) {
152
+ runs.set(run.id, run);
153
+ }
154
+ const visible = (id) => {
155
+ const run = runs.get(id);
156
+ return !!run && maySee(viewer, run);
157
+ };
158
+ const queue = snapshot.queue.map((entry, index) => {
159
+ const place = { position: index + 1, waitsFor: visibleReason(entry.waitsFor, visible) };
160
+ return {
161
+ ...place,
162
+ mine: owns(viewer, entry.run),
163
+ ...(maySee(viewer, entry.run) ? { run: toListedRun(viewer, entry.run, place) } : {}),
164
+ };
165
+ });
166
+ const running = snapshot.running.filter((run) => maySee(viewer, run));
167
+ const limits = snapshot.limits;
168
+ return {
169
+ you: { iss: viewer.principal.iss, sub: viewer.principal.sub, observe: viewer.grant.observe },
170
+ places: {
171
+ max: snapshot.places.max,
172
+ used: snapshot.places.used,
173
+ runs: running.map((run) => toListedRun(viewer, run)),
174
+ others: snapshot.running.length - running.length,
175
+ },
176
+ queue,
177
+ approvals: {
178
+ waiting: snapshot.awaiting.runs.length,
179
+ max: snapshot.awaiting.max,
180
+ runs: snapshot.awaiting.runs.filter((run) => maySee(viewer, run)).map((run) => toListedRun(viewer, run)),
181
+ },
182
+ services: {
183
+ open: snapshot.services.taken,
184
+ ...(snapshot.services.max !== undefined ? { max: snapshot.services.max } : {}),
185
+ runs: snapshot.services.runs.filter((run) => maySee(viewer, run)).map((run) => toListedRun(viewer, run)),
186
+ },
187
+ limits: {
188
+ waiting: meter(usage.waiting, limits?.waiting),
189
+ queued: meter(usage.queued, limits?.queued),
190
+ running: meter(usage.running, limits?.running),
191
+ services: meter(usage.services, snapshot.services.perCaller),
192
+ asleep: meter(usage.asleep, snapshot.services.asleepPerCaller),
193
+ },
194
+ };
195
+ }
196
+ exports.getOverview = getOverview;
197
+ function meter(runs, limit) {
198
+ return { used: runs.length, ...(limit !== undefined ? { limit } : {}), runs: [...runs] };
199
+ }
200
+ const STATES = [
201
+ 'awaiting-approval',
202
+ 'queued',
203
+ 'running',
204
+ 'up',
205
+ 'asleep',
206
+ 'succeeded',
207
+ 'failed',
208
+ 'cancelled',
209
+ 'rejected',
210
+ ];
211
+ // `scope`, `state` (a comma separated list) and `kind` of GET /v1/runs.
212
+ function parseRunFilter(params, grant) {
213
+ const scope = params.get('scope') ?? 'mine';
214
+ if (scope !== 'mine' && scope !== 'visible' && scope !== 'all') {
215
+ throw new Error('scope: mine, visible or all');
216
+ }
217
+ if (scope === 'all' && !grant.observe) {
218
+ throw new FilterRefused('scope all needs an entitlement with observe');
219
+ }
220
+ const state = params.get('state');
221
+ const states = state
222
+ ? state.split(',').map((value) => {
223
+ if (!STATES.includes(value)) {
224
+ throw new Error(`state: one or more of ${STATES.join(', ')}`);
225
+ }
226
+ return value;
227
+ })
228
+ : undefined;
229
+ const kind = params.get('kind') ?? undefined;
230
+ if (kind !== undefined && kind !== 'tasks' && kind !== 'services') {
231
+ throw new Error('kind: tasks or services');
232
+ }
233
+ const id = params.get('id') ?? undefined;
234
+ if (id !== undefined && !/^run_[A-Za-z0-9_-]{16}$/.test(id)) {
235
+ throw new Error('id: the id of a run, run_...');
236
+ }
237
+ return { scope, ...(states ? { states } : {}), ...(kind ? { kind } : {}), ...(id ? { id } : {}) };
238
+ }
239
+ exports.parseRunFilter = parseRunFilter;
240
+ class FilterRefused extends Error {
241
+ }
242
+ exports.FilterRefused = FilterRefused;
243
+ function filterRuns(runs, viewer, filter) {
244
+ return runs.filter((run) => (filter.scope === 'mine' ? owns(viewer, run) : maySee(viewer, run)) &&
245
+ (!filter.id || run.id === filter.id) &&
246
+ (!filter.states || filter.states.includes(run.state)) &&
247
+ (!filter.kind || (filter.kind === 'services') === !!run.up));
248
+ }
249
+ exports.filterRuns = filterRuns;
250
+ // The machines and what the runs take, with whose the namespaces are where the caller may
251
+ // know: their own, and every one with `observe`.
252
+ function visibleCapacity(capacity, viewer) {
253
+ const everyone = viewer.grant.observe;
254
+ return {
255
+ ...capacity,
256
+ // what the cluster answered is for operators; others learn that something is missing
257
+ problems: viewer.grant.observe || capacity.problems.length === 0 ? capacity.problems : ['some of it could not be read'],
258
+ groups: capacity.groups.map((group) => {
259
+ const mine = group.owner?.iss === viewer.principal.iss && group.owner?.sub === viewer.principal.sub;
260
+ if (everyone || mine || !group.owner) {
261
+ return group;
262
+ }
263
+ const { owner, run, ...rest } = group;
264
+ void owner;
265
+ void run;
266
+ return rest;
267
+ }),
268
+ };
269
+ }
270
+ exports.visibleCapacity = visibleCapacity;
271
+ //# sourceMappingURL=overview.js.map