@fabrika/provider-zerops 0.3.3 → 0.4.1

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 (36) hide show
  1. package/package.json +7 -6
  2. package/src/api/client.ts +11 -2
  3. package/src/api/types.ts +13 -0
  4. package/src/cli.ts +21 -4
  5. package/src/collaborators.ts +4 -0
  6. package/src/control.ts +37 -8
  7. package/src/execution.ts +371 -0
  8. package/src/exposure.ts +95 -0
  9. package/src/installation/deploy-options.ts +12 -1
  10. package/src/installation/deploy.ts +42 -18
  11. package/src/installation/generated/platform-console-schema.ts +4 -0
  12. package/src/installation/generated/platform-proxy-manifest.ts +533 -33
  13. package/src/installation/index.ts +11 -0
  14. package/src/installation/install-environment.ts +1 -2
  15. package/src/installation/machine-key.ts +3 -4
  16. package/src/installation/manifest.ts +1 -1
  17. package/src/installation/mcp.ts +102 -0
  18. package/src/installation/placement-options.ts +2 -1
  19. package/src/installation/proxy-manifest.ts +124 -23
  20. package/src/installation/secrets.ts +2 -2
  21. package/src/installation/templates/README.md +20 -11
  22. package/src/installation/templates/platform.yml +2 -0
  23. package/src/installation/topology/setups.ts +5 -5
  24. package/src/managed-env.ts +94 -6
  25. package/src/manifest.ts +248 -14
  26. package/src/namespace/codec.ts +23 -12
  27. package/src/namespace/errors.ts +1 -1
  28. package/src/namespace/manifest.ts +25 -6
  29. package/src/namespace/provisioning.ts +10 -29
  30. package/src/namespace/proxy-attempt.ts +13 -2
  31. package/src/namespace/proxy-secrets.ts +21 -0
  32. package/src/namespace/update.ts +212 -0
  33. package/src/namespace/validation.ts +43 -0
  34. package/src/plan.ts +6 -0
  35. package/src/provider.ts +160 -12
  36. package/src/types.ts +14 -3
@@ -4,13 +4,17 @@
4
4
  // Regenerate: bun run --filter @fabrika/platform-artifacts gen
5
5
  //
6
6
  // The installation-independent half of `FABRIKA_PROXY_MANIFEST_JSON`: which apps the platform proxy
7
- // fronts, at which private upstream, on which public listener, under which gates. The hosts and the
8
- // browser-facing scheme belong to ONE installation and are supplied at deploy time —
7
+ // fronts, at which private upstream, on which public listener, under which service table. The hosts and
8
+ // the browser-facing scheme belong to ONE installation and are supplied at deploy time —
9
9
  // `resolvePlatformProxyManifest` binds them.
10
10
  //
11
- // The gate rules below are `CONTROL_PROXY_GATES` and `OPERATIONS_PROXY_GATES` as of the last
12
- // regeneration, copied on purpose: a PUBLISHED deploy command may not import the private package that
13
- // owns them. `gen:check` in CI is what makes the copy a fact rather than a hope.
11
+ // The tables below are the services' own service contracts as of the last regeneration, copied on
12
+ // purpose: a PUBLISHED deploy command may not import the private packages that own them. `gen:check` in
13
+ // CI is what makes the copy a fact rather than a hope.
14
+ //
15
+ // The console's `oauthResource` is its MCP endpoint, copied the same way. A deploy publishes the console's
16
+ // `table` and registers the resource only on an installation that enables MCP authoring, and its
17
+ // `tableWithoutOAuth` otherwise.
14
18
 
15
19
  import type { PlatformProxyManifestTemplate } from '../proxy-manifest'
16
20
 
@@ -21,10 +25,170 @@ export const PLATFORM_PROXY_MANIFEST_TEMPLATE: PlatformProxyManifestTemplate = {
21
25
  service: 'iam',
22
26
  port: 8080,
23
27
  upstream: 'iam:3000',
24
- gates: {
25
- rules: [
26
- { path: '/*', kind: 'public' },
28
+ table: {
29
+ endpoints: [
30
+ {
31
+ id: 'health',
32
+ kind: 'health',
33
+ method: '*',
34
+ path: '/healthz',
35
+ callers: [],
36
+ requirement: null,
37
+ admission: null,
38
+ oauth: null,
39
+ },
40
+ {
41
+ id: 'jwks',
42
+ kind: 'protocol',
43
+ method: '*',
44
+ path: '/.well-known/jwks.json',
45
+ callers: [],
46
+ requirement: null,
47
+ admission: null,
48
+ oauth: null,
49
+ },
50
+ {
51
+ id: 'oauth-metadata',
52
+ kind: 'protocol',
53
+ method: '*',
54
+ path: '/.well-known/oauth-authorization-server',
55
+ callers: [],
56
+ requirement: null,
57
+ admission: null,
58
+ oauth: null,
59
+ },
60
+ {
61
+ id: 'oauth',
62
+ kind: 'protocol',
63
+ method: '*',
64
+ path: '/oauth/*',
65
+ callers: [],
66
+ requirement: null,
67
+ admission: null,
68
+ oauth: null,
69
+ },
70
+ {
71
+ id: 'auth-root',
72
+ kind: 'protocol',
73
+ method: '*',
74
+ path: '/auth',
75
+ callers: [],
76
+ requirement: null,
77
+ admission: null,
78
+ oauth: null,
79
+ },
80
+ {
81
+ id: 'auth',
82
+ kind: 'protocol',
83
+ method: '*',
84
+ path: '/auth/*',
85
+ callers: [],
86
+ requirement: null,
87
+ admission: null,
88
+ oauth: null,
89
+ },
90
+ {
91
+ id: 'mint-session',
92
+ kind: 'protocol',
93
+ method: '*',
94
+ path: '/auth/mint/session',
95
+ callers: [],
96
+ requirement: null,
97
+ admission: null,
98
+ oauth: null,
99
+ },
100
+ {
101
+ id: 'mint-key',
102
+ kind: 'protocol',
103
+ method: '*',
104
+ path: '/auth/mint/key',
105
+ callers: [],
106
+ requirement: null,
107
+ admission: null,
108
+ oauth: null,
109
+ },
110
+ {
111
+ id: 'mint-exchange',
112
+ kind: 'protocol',
113
+ method: '*',
114
+ path: '/auth/mint/exchange',
115
+ callers: [],
116
+ requirement: null,
117
+ admission: null,
118
+ oauth: null,
119
+ },
120
+ {
121
+ id: 'mint-delegation',
122
+ kind: 'protocol',
123
+ method: '*',
124
+ path: '/auth/mint/delegation',
125
+ callers: [],
126
+ requirement: null,
127
+ admission: null,
128
+ oauth: null,
129
+ },
130
+ {
131
+ id: 'admin-rpc',
132
+ kind: 'protocol',
133
+ method: 'POST',
134
+ path: '/admin/rpc',
135
+ callers: [],
136
+ requirement: null,
137
+ admission: null,
138
+ oauth: null,
139
+ },
140
+ {
141
+ id: 'admin-root',
142
+ kind: 'protocol',
143
+ method: '*',
144
+ path: '/admin',
145
+ callers: [],
146
+ requirement: null,
147
+ admission: null,
148
+ oauth: null,
149
+ },
150
+ {
151
+ id: 'admin',
152
+ kind: 'protocol',
153
+ method: '*',
154
+ path: '/admin/*',
155
+ callers: [],
156
+ requirement: null,
157
+ admission: null,
158
+ oauth: null,
159
+ },
160
+ {
161
+ id: 'membership',
162
+ kind: 'protocol',
163
+ method: '*',
164
+ path: '/membership/*',
165
+ callers: [],
166
+ requirement: null,
167
+ admission: null,
168
+ oauth: null,
169
+ },
170
+ {
171
+ id: 'rpc-root',
172
+ kind: 'protocol',
173
+ method: '*',
174
+ path: '/rpc',
175
+ callers: [],
176
+ requirement: null,
177
+ admission: null,
178
+ oauth: null,
179
+ },
180
+ {
181
+ id: 'rpc',
182
+ kind: 'protocol',
183
+ method: '*',
184
+ path: '/rpc/*',
185
+ callers: [],
186
+ requirement: null,
187
+ admission: null,
188
+ oauth: null,
189
+ },
27
190
  ],
191
+ reservedPrefixes: ['/auth/mint', '/rpc'],
28
192
  },
29
193
  },
30
194
  {
@@ -32,28 +196,345 @@ export const PLATFORM_PROXY_MANIFEST_TEMPLATE: PlatformProxyManifestTemplate = {
32
196
  service: 'control',
33
197
  port: 8082,
34
198
  upstream: 'control:3000',
35
- gates: {
36
- rules: [
37
- { path: '/healthz', kind: 'public' },
38
- { path: '/.well-known/fabrika-builder', kind: 'public' },
39
- { path: '/builder/connect', kind: 'public' },
40
- { path: '/api/health', kind: 'public' },
41
- { path: '/webhooks/github', kind: 'public' },
42
- { path: '/webhooks/github/*', kind: 'public' },
43
- { path: '/iam/admin', kind: 'service' },
44
- { path: '/iam/admin', kind: 'human' },
45
- { path: '/iam/admin/*', kind: 'service' },
46
- { path: '/iam/admin/*', kind: 'human' },
47
- { path: '/operations/api', kind: 'service' },
48
- { path: '/operations/api', kind: 'human' },
49
- { path: '/operations/api/*', kind: 'service' },
50
- { path: '/operations/api/*', kind: 'human' },
51
- { path: '/api/source/github/manifest/*', kind: 'human' },
52
- { path: '/api/source/github/callback', kind: 'human' },
53
- { path: '/api/*', kind: 'service' },
54
- { path: '/api/*', kind: 'human' },
55
- { path: '/*', kind: 'human' },
199
+ table: {
200
+ endpoints: [
201
+ {
202
+ id: 'health',
203
+ kind: 'health',
204
+ method: '*',
205
+ path: '/healthz',
206
+ callers: [],
207
+ requirement: null,
208
+ admission: null,
209
+ oauth: null,
210
+ },
211
+ {
212
+ id: 'api-health',
213
+ kind: 'health',
214
+ method: '*',
215
+ path: '/api/health',
216
+ callers: [],
217
+ requirement: null,
218
+ admission: null,
219
+ oauth: null,
220
+ },
221
+ {
222
+ id: 'builder-discovery',
223
+ kind: 'protocol',
224
+ method: '*',
225
+ path: '/.well-known/fabrika-builder',
226
+ callers: [],
227
+ requirement: null,
228
+ admission: null,
229
+ oauth: null,
230
+ },
231
+ {
232
+ id: 'builder-connect',
233
+ kind: 'protocol',
234
+ method: '*',
235
+ path: '/builder/connect',
236
+ callers: [],
237
+ requirement: null,
238
+ admission: null,
239
+ oauth: null,
240
+ },
241
+ {
242
+ id: 'github-webhook',
243
+ kind: 'protocol',
244
+ method: 'POST',
245
+ path: '/webhooks/github/:connectionId',
246
+ callers: [],
247
+ requirement: null,
248
+ admission: null,
249
+ oauth: null,
250
+ },
251
+ {
252
+ id: 'webhooks-root',
253
+ kind: 'service',
254
+ method: '*',
255
+ path: '/webhooks',
256
+ callers: ['human'],
257
+ requirement: { kind: 'authenticated' },
258
+ admission: null,
259
+ oauth: null,
260
+ },
261
+ {
262
+ id: 'github-manifest',
263
+ kind: 'service',
264
+ method: 'GET',
265
+ path: '/api/source/github/manifest/:connectionId',
266
+ callers: ['human'],
267
+ requirement: { kind: 'authenticated' },
268
+ admission: null,
269
+ oauth: null,
270
+ },
271
+ {
272
+ id: 'github-callback',
273
+ kind: 'service',
274
+ method: 'GET',
275
+ path: '/api/source/github/callback',
276
+ callers: ['human'],
277
+ requirement: { kind: 'authenticated' },
278
+ admission: null,
279
+ oauth: null,
280
+ },
281
+ {
282
+ id: 'rpc',
283
+ kind: 'service',
284
+ method: 'POST',
285
+ path: '/api/rpc',
286
+ callers: ['human', 'machine'],
287
+ requirement: { kind: 'authenticated' },
288
+ admission: { contentTypes: ['application/json'], maxBytes: null },
289
+ oauth: null,
290
+ },
291
+ {
292
+ id: 'api-root',
293
+ kind: 'service',
294
+ method: '*',
295
+ path: '/api',
296
+ callers: ['human'],
297
+ requirement: { kind: 'authenticated' },
298
+ admission: null,
299
+ oauth: null,
300
+ },
301
+ {
302
+ id: 'iam-admin-rpc',
303
+ kind: 'service',
304
+ method: 'POST',
305
+ path: '/iam/admin/rpc',
306
+ callers: ['human', 'machine'],
307
+ requirement: { kind: 'authenticated' },
308
+ admission: { contentTypes: ['application/json'], maxBytes: null },
309
+ oauth: null,
310
+ },
311
+ {
312
+ id: 'iam-admin-root',
313
+ kind: 'service',
314
+ method: '*',
315
+ path: '/iam/admin',
316
+ callers: ['human', 'machine'],
317
+ requirement: { kind: 'authenticated' },
318
+ admission: null,
319
+ oauth: null,
320
+ },
321
+ {
322
+ id: 'operations-rpc',
323
+ kind: 'service',
324
+ method: 'POST',
325
+ path: '/operations/api/rpc',
326
+ callers: ['human', 'machine'],
327
+ requirement: { kind: 'authenticated' },
328
+ admission: { contentTypes: ['application/json'], maxBytes: null },
329
+ oauth: null,
330
+ },
331
+ {
332
+ id: 'operations-api-root',
333
+ kind: 'service',
334
+ method: '*',
335
+ path: '/operations/api',
336
+ callers: ['human', 'machine'],
337
+ requirement: { kind: 'authenticated' },
338
+ admission: null,
339
+ oauth: null,
340
+ },
341
+ {
342
+ id: 'console',
343
+ kind: 'service',
344
+ method: 'GET',
345
+ path: '/*',
346
+ callers: ['human'],
347
+ requirement: { kind: 'authenticated' },
348
+ admission: null,
349
+ oauth: null,
350
+ },
351
+ {
352
+ id: 'mcp',
353
+ kind: 'service',
354
+ method: '*',
355
+ path: '/mcp',
356
+ callers: ['oauth'],
357
+ requirement: { kind: 'authenticated' },
358
+ admission: null,
359
+ oauth: { scopes: ['authoring'] },
360
+ },
361
+ ],
362
+ reservedPrefixes: ['/api', '/iam/admin', '/operations/api', '/webhooks', '/mcp'],
363
+ },
364
+ oauthResource: {
365
+ path: '/mcp',
366
+ scopes: ['authoring'],
367
+ scopeGrants: [
368
+ {
369
+ scope: 'authoring',
370
+ actions: ['source.workspace.read', 'source.workspace.write', 'source.pull-request.create', 'deploy.trigger', 'deploy.read'],
371
+ },
372
+ ],
373
+ },
374
+ tableWithoutOAuth: {
375
+ endpoints: [
376
+ {
377
+ id: 'health',
378
+ kind: 'health',
379
+ method: '*',
380
+ path: '/healthz',
381
+ callers: [],
382
+ requirement: null,
383
+ admission: null,
384
+ oauth: null,
385
+ },
386
+ {
387
+ id: 'api-health',
388
+ kind: 'health',
389
+ method: '*',
390
+ path: '/api/health',
391
+ callers: [],
392
+ requirement: null,
393
+ admission: null,
394
+ oauth: null,
395
+ },
396
+ {
397
+ id: 'builder-discovery',
398
+ kind: 'protocol',
399
+ method: '*',
400
+ path: '/.well-known/fabrika-builder',
401
+ callers: [],
402
+ requirement: null,
403
+ admission: null,
404
+ oauth: null,
405
+ },
406
+ {
407
+ id: 'builder-connect',
408
+ kind: 'protocol',
409
+ method: '*',
410
+ path: '/builder/connect',
411
+ callers: [],
412
+ requirement: null,
413
+ admission: null,
414
+ oauth: null,
415
+ },
416
+ {
417
+ id: 'github-webhook',
418
+ kind: 'protocol',
419
+ method: 'POST',
420
+ path: '/webhooks/github/:connectionId',
421
+ callers: [],
422
+ requirement: null,
423
+ admission: null,
424
+ oauth: null,
425
+ },
426
+ {
427
+ id: 'webhooks-root',
428
+ kind: 'service',
429
+ method: '*',
430
+ path: '/webhooks',
431
+ callers: ['human'],
432
+ requirement: { kind: 'authenticated' },
433
+ admission: null,
434
+ oauth: null,
435
+ },
436
+ {
437
+ id: 'github-manifest',
438
+ kind: 'service',
439
+ method: 'GET',
440
+ path: '/api/source/github/manifest/:connectionId',
441
+ callers: ['human'],
442
+ requirement: { kind: 'authenticated' },
443
+ admission: null,
444
+ oauth: null,
445
+ },
446
+ {
447
+ id: 'github-callback',
448
+ kind: 'service',
449
+ method: 'GET',
450
+ path: '/api/source/github/callback',
451
+ callers: ['human'],
452
+ requirement: { kind: 'authenticated' },
453
+ admission: null,
454
+ oauth: null,
455
+ },
456
+ {
457
+ id: 'rpc',
458
+ kind: 'service',
459
+ method: 'POST',
460
+ path: '/api/rpc',
461
+ callers: ['human', 'machine'],
462
+ requirement: { kind: 'authenticated' },
463
+ admission: { contentTypes: ['application/json'], maxBytes: null },
464
+ oauth: null,
465
+ },
466
+ {
467
+ id: 'api-root',
468
+ kind: 'service',
469
+ method: '*',
470
+ path: '/api',
471
+ callers: ['human'],
472
+ requirement: { kind: 'authenticated' },
473
+ admission: null,
474
+ oauth: null,
475
+ },
476
+ {
477
+ id: 'iam-admin-rpc',
478
+ kind: 'service',
479
+ method: 'POST',
480
+ path: '/iam/admin/rpc',
481
+ callers: ['human', 'machine'],
482
+ requirement: { kind: 'authenticated' },
483
+ admission: { contentTypes: ['application/json'], maxBytes: null },
484
+ oauth: null,
485
+ },
486
+ {
487
+ id: 'iam-admin-root',
488
+ kind: 'service',
489
+ method: '*',
490
+ path: '/iam/admin',
491
+ callers: ['human', 'machine'],
492
+ requirement: { kind: 'authenticated' },
493
+ admission: null,
494
+ oauth: null,
495
+ },
496
+ {
497
+ id: 'operations-rpc',
498
+ kind: 'service',
499
+ method: 'POST',
500
+ path: '/operations/api/rpc',
501
+ callers: ['human', 'machine'],
502
+ requirement: { kind: 'authenticated' },
503
+ admission: { contentTypes: ['application/json'], maxBytes: null },
504
+ oauth: null,
505
+ },
506
+ {
507
+ id: 'operations-api-root',
508
+ kind: 'service',
509
+ method: '*',
510
+ path: '/operations/api',
511
+ callers: ['human', 'machine'],
512
+ requirement: { kind: 'authenticated' },
513
+ admission: null,
514
+ oauth: null,
515
+ },
516
+ {
517
+ id: 'console',
518
+ kind: 'service',
519
+ method: 'GET',
520
+ path: '/*',
521
+ callers: ['human'],
522
+ requirement: { kind: 'authenticated' },
523
+ admission: null,
524
+ oauth: null,
525
+ },
526
+ {
527
+ id: 'mcp',
528
+ kind: 'service',
529
+ method: '*',
530
+ path: '/mcp',
531
+ callers: ['human'],
532
+ requirement: { kind: 'authenticated' },
533
+ admission: null,
534
+ oauth: null,
535
+ },
56
536
  ],
537
+ reservedPrefixes: ['/api', '/iam/admin', '/operations/api', '/webhooks', '/mcp'],
57
538
  },
58
539
  },
59
540
  {
@@ -61,11 +542,30 @@ export const PLATFORM_PROXY_MANIFEST_TEMPLATE: PlatformProxyManifestTemplate = {
61
542
  service: 'operations',
62
543
  port: 8083,
63
544
  upstream: 'operations:3000',
64
- gates: {
65
- rules: [
66
- { path: '/api/*/envelope/', kind: 'public' },
67
- { path: '/api/artifacts/source-maps/', kind: 'public' },
545
+ table: {
546
+ endpoints: [
547
+ {
548
+ id: 'ingest',
549
+ kind: 'protocol',
550
+ method: '*',
551
+ path: '/api/:projectId/envelope/',
552
+ callers: [],
553
+ requirement: null,
554
+ admission: null,
555
+ oauth: null,
556
+ },
557
+ {
558
+ id: 'source-map-upload',
559
+ kind: 'protocol',
560
+ method: '*',
561
+ path: '/api/artifacts/source-maps/',
562
+ callers: [],
563
+ requirement: null,
564
+ admission: null,
565
+ oauth: null,
566
+ },
68
567
  ],
568
+ reservedPrefixes: [],
69
569
  },
70
570
  },
71
571
  ],
@@ -28,13 +28,17 @@ import { parsePlatformUpgradeArgs, UPGRADE_DRY_RUN_FLAG } from './upgrade-option
28
28
  export { PLATFORM_CONCURRENT_DEPLOY, PLATFORM_DEPLOY_ORDER, PLATFORM_PROXY_SERVICE, PLATFORM_SEQUENTIAL_DEPLOY } from './deploy'
29
29
  export { PLATFORM_CONSOLE_APP_SCHEMA } from './generated/platform-console-schema'
30
30
  export { PLATFORM_PROXY_MANIFEST_TEMPLATE } from './generated/platform-proxy-manifest'
31
+ export { type IamOAuthConfig, type McpClient, parseMcpClientsJson, platformIamOAuthConfig } from './mcp'
31
32
  export {
33
+ type PlatformOAuthResourceTemplate,
32
34
  type PlatformProxyApp,
33
35
  platformProxyAppFor,
34
36
  type PlatformProxyComposition,
37
+ type PlatformProxyManifest,
35
38
  type PlatformProxyManifestTemplate,
36
39
  type PlatformProxyPlacement,
37
40
  type PlatformProxyService,
41
+ platformServiceTableDigest,
38
42
  resolvePlatformProxyManifest,
39
43
  } from './proxy-manifest'
40
44
 
@@ -201,6 +205,7 @@ Options (a flag beats the environment variable beside it):
201
205
  --operations-host=<host> FABRIKA_PLATFORM_OPERATIONS_HOST
202
206
  --scheme=<http|https> FABRIKA_PLATFORM_SCHEME default https
203
207
  --source-dir=<checkout> required clean full checkout running this CLI
208
+ --mcp-clients=<json> FABRIKA_PLATFORM_MCP_CLIENTS_JSON MCP authoring clients; unset = off
204
209
  --dry-run read everything, change nothing
205
210
 
206
211
  Naming NO host derives all three from the proxy service's Zerops subdomains and makes this a
@@ -208,6 +213,12 @@ Naming NO host derives all three from the proxy service's Zerops subdomains and
208
213
  \`custom-domain\` installation: the domains are bound to the project balancer out of band and no
209
214
  subdomain is published.
210
215
 
216
+ MCP authoring (ADR-0049) is opt-in. --mcp-clients takes a JSON array of the MCP clients this
217
+ installation trusts: [{"provider":"claude"|"chatgpt","clientId":"<https CIMD URL>","redirectUris":[...],
218
+ "metadataUrl":"<https URL>"?}]. With it, the console's /mcp admits OAuth bearers and IAM gets
219
+ FABRIKA_IAM_OAUTH_CONFIG. Without it, /mcp admits only a signed-in person and FABRIKA_IAM_OAUTH_CONFIG on
220
+ iam is left as it is.
221
+
211
222
  Credentials are read from the ENVIRONMENT ONLY and have no flag, so they cannot reach a CI log or a
212
223
  process listing:
213
224
 
@@ -75,8 +75,7 @@ export const installationServiceEnv = (input: InstallationEnvInput): ReadonlyMap
75
75
  const control = new Map([
76
76
  ['FABRIKA_CONTROL_DATABASE_URL', POSTGRES_URL],
77
77
  ['ENVIRONMENT', input.environment],
78
- // A bare HOST, not an origin: `controlPublicOrigin` accepts either, and this is also what the
79
- // same-origin check on both first-party gateways is decided against.
78
+ // A bare HOST, not an origin: `controlPublicOrigin` accepts either.
80
79
  ['FABRIKA_CONTROL_DOMAIN', input.hosts.control],
81
80
  ['FABRIKA_IAM_ISSUER', iamOrigin],
82
81
  ['OPERATIONS_ARTIFACT_ORIGIN', operationsOrigin],
@@ -11,8 +11,7 @@
11
11
  // The acceptance is a fresh shell holding nothing: paste the block, supply the two keys, mint a key on
12
12
  // the first try. So the origin is an `export` — a bare `NAME=value` sets a shell variable the child
13
13
  // process never sees — and EVERY other line is a `#` comment. The command line is commented too, and
14
- // that is not timidity: `--label=<name>` is a shell redirection, and `[--expires-in=<seconds>]` ends in
15
- // a `>` that would create a file called `]` in whatever directory the operator pasted into.
14
+ // that is not timidity: `--label=<name>` is a shell redirection, and so is every other `<…>` placeholder.
16
15
  //
17
16
  // HARD RULE: no function here takes a key. The block is composed from an ORIGIN and variable NAMES,
18
17
  // so a caller that is holding the real values (both verbs are) cannot leak one through it.
@@ -28,8 +27,8 @@ export interface MachineKeyNextStep {
28
27
  /** The boxed hand-off's title. Exported so a test names it once. */
29
28
  export const MACHINE_KEY_NEXT_STEP_TITLE = 'NEXT — mint a machine key for `fabrika control`'
30
29
 
31
- /** The command the block leads to, with the two flags `key issue` requires. */
32
- export const MACHINE_KEY_ISSUE_COMMAND = 'fabrika control key issue --label=<name> --permissions=<a,b,c> [--expires-in=<seconds>]'
30
+ /** The command the block leads to, with the three flags `key issue` requires. */
31
+ export const MACHINE_KEY_ISSUE_COMMAND = 'fabrika control key issue --label=<name> --permissions=<a,b,c> --expires-in=<seconds>'
33
32
 
34
33
  const WIDEST_NAME = 'FABRIKA_IAM_PROVISIONING_KEY'.length + 1
35
34
 
@@ -5,7 +5,7 @@
5
5
  // On the `light` tier an application shares the platform's project and therefore the platform proxy's
6
6
  // single `FABRIKA_PROXY_MANIFEST_JSON`. The live document on `fabrika-test` carries a fourth app —
7
7
  // `notes` → `notesapi:3000` on listener 8084 — beside the three platform ones. `platformProxyManifest`
8
- // deliberately emits only the platform apps (it is generated from the platform's own gate modules and
8
+ // deliberately emits only the platform apps (it is generated from the platform's own service contracts and
9
9
  // knows nothing about applications), so writing it alone would DELETE every application entry and take
10
10
  // the deployed applications offline at the next proxy build.
11
11
  //